refactoring

This commit is contained in:
Miha Kralj
2025-12-16 21:16:50 -08:00
parent a67ad65fa5
commit d277e08056
137 changed files with 5074 additions and 3178 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ public class WmaIndicatorTests
{
var indicator = new WmaIndicator();
indicator.Initialize();
var method = indicator.GetType().GetMethod("OnPaintChart");
Assert.NotNull(method);
Assert.Equal(typeof(WmaIndicator), method.DeclaringType);
+4 -4
View File
@@ -101,7 +101,7 @@ public class WmaTests
}
[Fact]
public void StaticCalculate_Matches_Streaming()
public void StaticBatch_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
@@ -114,7 +114,7 @@ public class WmaTests
streamingResults.Add(wma.Update(series[i]).Value);
}
var staticResults = Wma.Calculate(series, 10);
var staticResults = Wma.Batch(series, 10);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
@@ -124,7 +124,7 @@ public class WmaTests
}
[Fact]
public void StaticCalculateSpan_Matches_Streaming()
public void StaticBatchSpan_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
@@ -138,7 +138,7 @@ public class WmaTests
}
var spanResults = new double[series.Count];
Wma.Calculate(series.Values, spanResults, 10);
Wma.Batch(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
+3 -3
View File
@@ -75,7 +75,7 @@ public class WmaValidationTests
{
// Calculate QuanTAlib WMA (Span API)
double[] qOutput = new double[_testData.RawData.Length];
global::QuanTAlib.Wma.Calculate(_testData.RawData.Span, qOutput.AsSpan(), period);
global::QuanTAlib.Wma.Batch(_testData.RawData.Span, qOutput.AsSpan(), period);
// Calculate Skender WMA
var sResult = _testData.SkenderQuotes.GetWma(period).ToList();
@@ -154,7 +154,7 @@ public class WmaValidationTests
{
// Calculate QuanTAlib WMA (Span API)
double[] qOutput = new double[_testData.RawData.Length];
global::QuanTAlib.Wma.Calculate(_testData.RawData.Span, qOutput.AsSpan(), period);
global::QuanTAlib.Wma.Batch(_testData.RawData.Span, qOutput.AsSpan(), period);
// Calculate TA-Lib WMA
var retCode = TALib.Functions.Wma<double>(_testData.RawData.Span, 0..^0, talibOutput, out var outRange, period);
@@ -235,7 +235,7 @@ public class WmaValidationTests
{
// Calculate QuanTAlib WMA (Span API)
double[] qOutput = new double[_testData.RawData.Length];
global::QuanTAlib.Wma.Calculate(_testData.RawData.Span, qOutput.AsSpan(), period);
global::QuanTAlib.Wma.Batch(_testData.RawData.Span, qOutput.AsSpan(), period);
// Calculate Tulip WMA
var wmaIndicator = Tulip.Indicators.wma;
+68 -59
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -26,7 +27,7 @@ namespace QuanTAlib;
/// Becomes true when the buffer is full (period samples processed).
/// </remarks>
[SkipLocalsInit]
public sealed class Wma : ITValuePublisher
public sealed class Wma : AbstractBase
{
private readonly int _period;
private readonly double _divisor;
@@ -38,11 +39,6 @@ public sealed class Wma : ITValuePublisher
private const int ResyncInterval = 1000;
public string Name { get; }
public TValue Last { get; private set; }
public bool IsHot => _buffer.IsFull;
public event Action<TValue>? Pub;
public Wma(int period)
{
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
@@ -51,6 +47,7 @@ public sealed class Wma : ITValuePublisher
_divisor = (double)period * (period + 1) * 0.5;
_buffer = new RingBuffer(period);
Name = $"Wma({period})";
WarmupPeriod = period;
}
public Wma(ITValuePublisher source, int period) : this(period)
@@ -58,6 +55,8 @@ public sealed class Wma : ITValuePublisher
source.Pub += (item) => Update(item);
}
public override bool IsHot => _buffer.IsFull;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double GetValidValue(double input)
{
@@ -107,7 +106,7 @@ public sealed class Wma : ITValuePublisher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TValue input, bool isNew = true)
public override TValue Update(TValue input, bool isNew = true)
{
if (isNew)
{
@@ -131,73 +130,91 @@ public sealed class Wma : ITValuePublisher
double currentDivisor = _buffer.IsFull ? _divisor : (double)_buffer.Count * (_buffer.Count + 1) * 0.5;
Last = new TValue(input.Time, _state.WSum / currentDivisor);
Pub?.Invoke(Last);
PubEvent(Last);
return Last;
}
public TSeries Update(TSeries source)
public override TSeries Update(TSeries source)
{
if (source.Count == 0) return [];
int len = source.Count;
List<long> t = new(len);
List<double> v = new(len);
var t = new List<long>(len);
var v = new List<double>(len);
CollectionsMarshal.SetCount(t, len);
CollectionsMarshal.SetCount(v, len);
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period);
Batch(source.Values, vSpan, _period);
source.Times.CopyTo(tSpan);
// Restore state
int windowSize = Math.Min(len, _period);
int startIndex = len - windowSize;
if (startIndex > 0)
{
_state.LastValidValue = 0;
for (int i = startIndex - 1; i >= 0; i--)
{
if (double.IsFinite(source.Values[i]))
{
_state.LastValidValue = source.Values[i];
break;
}
}
}
else
{
_state.LastValidValue = 0;
}
_buffer.Clear();
_state.Sum = 0;
_state.WSum = 0;
_state.TickCount = 0;
for (int i = startIndex; i < len; i++)
{
double val = GetValidValue(source.Values[i]);
UpdateState(val);
_state.LastInput = val;
}
_p_state = _state;
Prime(source.Values);
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
return new TSeries(t, v);
}
public static TSeries Calculate(TSeries source, int period)
public override void Prime(ReadOnlySpan<double> source)
{
if (source.Length == 0) return;
int len = source.Length;
int windowSize = Math.Min(len, _period);
int startIndex = len - windowSize;
// Seed LastValidValue
_state.LastValidValue = 0;
if (startIndex > 0)
{
for (int i = startIndex - 1; i >= 0; i--)
{
if (double.IsFinite(source[i]))
{
_state.LastValidValue = source[i];
break;
}
}
}
// Reset state
_buffer.Clear();
_state.Sum = 0;
_state.WSum = 0;
_state.TickCount = 0;
// Process window
for (int i = startIndex; i < len; i++)
{
double val = GetValidValue(source[i]);
UpdateState(val);
_state.LastInput = val;
}
// Calculate Last
double currentDivisor = _buffer.IsFull ? _divisor : (double)_buffer.Count * (_buffer.Count + 1) * 0.5;
Last = new TValue(DateTime.MinValue, _state.WSum / currentDivisor);
_p_state = _state;
}
public override void Reset()
{
_buffer.Clear();
_state = default;
_p_state = default;
Last = default;
}
public static TSeries Batch(TSeries source, int period)
{
var wma = new Wma(period);
return wma.Update(source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
{
if (source.Length != output.Length)
throw new ArgumentException("Source and output must have the same length");
@@ -286,12 +303,12 @@ public sealed class Wma : ITValuePublisher
tickCount = 0;
double recalcSum = 0;
double recalcWsum = 0;
for (int k = 0; k < period; k++)
{
int idx = bufferIdx + k;
if (idx >= period) idx -= period;
double v = buffer[idx];
recalcSum += v;
recalcWsum += (k + 1) * v;
@@ -744,12 +761,4 @@ public sealed class Wma : ITValuePublisher
Unsafe.Add(ref outRef, idx) = wsum * invDivisor;
}
}
public void Reset()
{
_buffer.Clear();
_state = default;
_p_state = default;
Last = default;
}
}
+3 -3
View File
@@ -81,12 +81,12 @@ Console.WriteLine($"IsHot: {wma.IsHot}"); // true when buffer is full
// Batch calculation (TSeries API)
TSeries source = ...;
TSeries results = Wma.Calculate(source, 10);
TSeries results = Wma.Batch(source, 10);
// High-performance Span API (zero allocation)
double[] prices = new double[10000];
double[] output = new double[10000];
Wma.Calculate(prices.AsSpan(), output.AsSpan(), period: 10);
Wma.Batch(prices.AsSpan(), output.AsSpan(), period: 10);
```
### Zero-Allocation Span API
@@ -99,7 +99,7 @@ double[] source = new double[200000];
double[] wmaOutput = new double[200000];
// Zero heap allocation during calculation
Wma.Calculate(source.AsSpan(), wmaOutput.AsSpan(), period: 100);
Wma.Batch(source.AsSpan(), wmaOutput.AsSpan(), period: 100);
// Results are written directly to output buffer
Console.WriteLine($"Last WMA: {wmaOutput[^1]}");