mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-26 06:18:05 +00:00
refactoring
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]}");
|
||||
|
||||
Reference in New Issue
Block a user