mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-25 05:48:06 +00:00
refactoring
This commit is contained in:
@@ -116,7 +116,7 @@ public class SmaIndicatorTests
|
||||
{
|
||||
var indicator = new SmaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
|
||||
var method = indicator.GetType().GetMethod("OnPaintChart");
|
||||
Assert.NotNull(method);
|
||||
Assert.Equal(typeof(SmaIndicator), method.DeclaringType);
|
||||
|
||||
@@ -59,8 +59,12 @@ public class SmaIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
public override void OnPaintChart(PaintChartEventArgs args)
|
||||
{
|
||||
var savedColor = Series!.Color;
|
||||
Series.Color = Color.Transparent;
|
||||
base.OnPaintChart(args);
|
||||
Series.Color = savedColor;
|
||||
|
||||
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
|
||||
this.PaintSmoothCurve(args, Series!, warmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
|
||||
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
|
||||
}
|
||||
}
|
||||
|
||||
+97
-19
@@ -331,7 +331,7 @@ public class SmaTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_StaticCalculate_Works()
|
||||
public void Sma_StaticBatch_Works()
|
||||
{
|
||||
var series = new TSeries();
|
||||
series.Add(DateTime.UtcNow.Ticks, 10);
|
||||
@@ -340,7 +340,7 @@ public class SmaTests
|
||||
series.Add(DateTime.UtcNow.Ticks + 3, 40);
|
||||
series.Add(DateTime.UtcNow.Ticks + 4, 50);
|
||||
|
||||
var results = Sma.Calculate(series, 3);
|
||||
var results = Sma.Batch(series, 3);
|
||||
|
||||
Assert.Equal(5, results.Count);
|
||||
// SMA(3) for last value: (30+40+50)/3 = 40
|
||||
@@ -360,22 +360,22 @@ public class SmaTests
|
||||
// ============== Span API Tests ==============
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_ValidatesInput()
|
||||
public void Sma_SpanBatch_ValidatesInput()
|
||||
{
|
||||
double[] source = [1, 2, 3, 4, 5];
|
||||
double[] output = new double[5];
|
||||
double[] wrongSizeOutput = new double[3];
|
||||
|
||||
// Period must be > 0
|
||||
Assert.Throws<ArgumentException>(() => Sma.Calculate(source.AsSpan(), output.AsSpan(), 0));
|
||||
Assert.Throws<ArgumentException>(() => Sma.Calculate(source.AsSpan(), output.AsSpan(), -1));
|
||||
Assert.Throws<ArgumentException>(() => Sma.Batch(source.AsSpan(), output.AsSpan(), 0));
|
||||
Assert.Throws<ArgumentException>(() => Sma.Batch(source.AsSpan(), output.AsSpan(), -1));
|
||||
|
||||
// Output must be same length as source
|
||||
Assert.Throws<ArgumentException>(() => Sma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
|
||||
Assert.Throws<ArgumentException>(() => Sma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_MatchesTSeriesCalc()
|
||||
public void Sma_SpanBatch_MatchesTSeriesBatch()
|
||||
{
|
||||
var series = new TSeries();
|
||||
double[] source = new double[100];
|
||||
@@ -390,10 +390,10 @@ public class SmaTests
|
||||
}
|
||||
|
||||
// Calculate with TSeries API
|
||||
var tseriesResult = Sma.Calculate(series, 10);
|
||||
var tseriesResult = Sma.Batch(series, 10);
|
||||
|
||||
// Calculate with Span API
|
||||
Sma.Calculate(source.AsSpan(), output.AsSpan(), 10);
|
||||
Sma.Batch(source.AsSpan(), output.AsSpan(), 10);
|
||||
|
||||
// Compare results
|
||||
for (int i = 0; i < 100; i++)
|
||||
@@ -403,12 +403,12 @@ public class SmaTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_CalculatesCorrectly()
|
||||
public void Sma_SpanBatch_CalculatesCorrectly()
|
||||
{
|
||||
double[] source = [10, 20, 30, 40, 50];
|
||||
double[] output = new double[5];
|
||||
|
||||
Sma.Calculate(source.AsSpan(), output.AsSpan(), 3);
|
||||
Sma.Batch(source.AsSpan(), output.AsSpan(), 3);
|
||||
|
||||
// SMA(3) warmup: 10, (10+20)/2=15, (10+20+30)/3=20, then sliding: (20+30+40)/3=30, (30+40+50)/3=40
|
||||
Assert.Equal(10.0, output[0], 1e-10);
|
||||
@@ -419,7 +419,7 @@ public class SmaTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_ZeroAllocation()
|
||||
public void Sma_SpanBatch_ZeroAllocation()
|
||||
{
|
||||
double[] source = new double[10000];
|
||||
|
||||
@@ -429,7 +429,7 @@ public class SmaTests
|
||||
source[i] = gbm.Next().Close;
|
||||
|
||||
// Warm up
|
||||
Sma.Calculate(source.AsSpan(), output.AsSpan(), 100);
|
||||
Sma.Batch(source.AsSpan(), output.AsSpan(), 100);
|
||||
|
||||
// This test verifies the method runs without throwing
|
||||
// (allocation is measured by BenchmarkDotNet, not unit tests)
|
||||
@@ -437,12 +437,12 @@ public class SmaTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_HandlesNaN()
|
||||
public void Sma_SpanBatch_HandlesNaN()
|
||||
{
|
||||
double[] source = [100, 110, double.NaN, 120, 130];
|
||||
double[] output = new double[5];
|
||||
|
||||
Sma.Calculate(source.AsSpan(), output.AsSpan(), 3);
|
||||
Sma.Batch(source.AsSpan(), output.AsSpan(), 3);
|
||||
|
||||
// All outputs should be finite
|
||||
foreach (var val in output)
|
||||
@@ -452,12 +452,12 @@ public class SmaTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sma_SpanCalc_Period1_ReturnsInput()
|
||||
public void Sma_SpanBatch_Period1_ReturnsInput()
|
||||
{
|
||||
double[] source = [10, 20, 30, 40, 50];
|
||||
double[] output = new double[5];
|
||||
|
||||
Sma.Calculate(source.AsSpan(), output.AsSpan(), 1);
|
||||
Sma.Batch(source.AsSpan(), output.AsSpan(), 1);
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
@@ -474,14 +474,14 @@ public class SmaTests
|
||||
var series = bars.Close;
|
||||
|
||||
// 1. Batch Mode
|
||||
var batchSeries = Sma.Calculate(series, period);
|
||||
var batchSeries = Sma.Batch(series, period);
|
||||
double expected = batchSeries.Last.Value;
|
||||
|
||||
// 2. Span Mode
|
||||
var tValues = series.Values.ToArray();
|
||||
var spanInput = new ReadOnlySpan<double>(tValues);
|
||||
var spanOutput = new double[tValues.Length];
|
||||
Sma.Calculate(spanInput, spanOutput, period);
|
||||
Sma.Batch(spanInput, spanOutput, period);
|
||||
double spanResult = spanOutput[^1];
|
||||
|
||||
// 3. Streaming Mode
|
||||
@@ -516,4 +516,82 @@ public class SmaTests
|
||||
source.Add(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100, sma.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmupPeriod_IsSetCorrectly()
|
||||
{
|
||||
var sma = new Sma(10);
|
||||
Assert.Equal(10, sma.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_SetsStateCorrectly()
|
||||
{
|
||||
var sma = new Sma(5);
|
||||
double[] history = [10, 20, 30, 40, 50]; // SMA(5) = 30
|
||||
|
||||
sma.Prime(history);
|
||||
|
||||
Assert.True(sma.IsHot);
|
||||
Assert.Equal(30.0, sma.Last.Value, 1e-10);
|
||||
|
||||
// Verify it continues correctly
|
||||
sma.Update(new TValue(DateTime.UtcNow, 60)); // 20,30,40,50,60 -> 40
|
||||
Assert.Equal(40.0, sma.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_WithInsufficientHistory_IsNotHot()
|
||||
{
|
||||
var sma = new Sma(10);
|
||||
double[] history = [10, 20, 30, 40, 50];
|
||||
|
||||
sma.Prime(history);
|
||||
|
||||
Assert.False(sma.IsHot);
|
||||
Assert.Equal(30.0, sma.Last.Value, 1e-10); // It still calculates what it can
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prime_HandlesNaN_InHistory()
|
||||
{
|
||||
var sma = new Sma(3);
|
||||
double[] history = [10, 20, double.NaN, 40];
|
||||
// 10
|
||||
// 10, 20
|
||||
// 10, 20, 20 (NaN replaced by 20) -> Avg(10,20,20) = 16.666...
|
||||
// 20, 20, 40 -> Avg(20,20,40) = 26.666...
|
||||
|
||||
sma.Prime(history);
|
||||
|
||||
Assert.True(sma.IsHot);
|
||||
Assert.Equal(80.0 / 3.0, sma.Last.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 10; i++) series.Add(DateTime.UtcNow, i * 10);
|
||||
// 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
|
||||
|
||||
// SMA(5)
|
||||
var (results, indicator) = Sma.Calculate(series, 5);
|
||||
|
||||
// Check results
|
||||
Assert.Equal(10, results.Count);
|
||||
Assert.Equal(30.0, results[4].Value); // 5th element (index 4) is SMA(10..50) = 30
|
||||
Assert.Equal(80.0, results.Last.Value); // Last element is SMA(60..100) = 80
|
||||
|
||||
// Check indicator state
|
||||
Assert.True(indicator.IsHot);
|
||||
Assert.Equal(80.0, indicator.Last.Value);
|
||||
Assert.Equal(5, indicator.WarmupPeriod);
|
||||
|
||||
// Verify indicator continues correctly
|
||||
indicator.Update(new TValue(DateTime.UtcNow, 110));
|
||||
// Window was [60, 70, 80, 90, 100] -> Avg 80
|
||||
// New Window [70, 80, 90, 100, 110] -> Avg 90
|
||||
Assert.Equal(90.0, indicator.Last.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class SmaValidationTests : IDisposable
|
||||
{
|
||||
// Calculate QuanTAlib SMA (Span API)
|
||||
double[] qOutput = new double[sourceData.Length];
|
||||
global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
global::QuanTAlib.Sma.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
|
||||
// Calculate Skender SMA
|
||||
var sResult = _testData.SkenderQuotes.GetSma(period).ToList();
|
||||
@@ -173,7 +173,7 @@ public class SmaValidationTests : IDisposable
|
||||
{
|
||||
// Calculate QuanTAlib SMA (Span API)
|
||||
double[] qOutput = new double[sourceData.Length];
|
||||
global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
global::QuanTAlib.Sma.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
|
||||
// Calculate TA-Lib SMA
|
||||
var retCode = TALib.Functions.Sma<double>(sourceData, 0..^0, talibOutput, out var outRange, period);
|
||||
@@ -263,7 +263,7 @@ public class SmaValidationTests : IDisposable
|
||||
{
|
||||
// Calculate QuanTAlib SMA (Span API)
|
||||
double[] qOutput = new double[sourceData.Length];
|
||||
global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
global::QuanTAlib.Sma.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period);
|
||||
|
||||
// Calculate Tulip SMA
|
||||
var smaIndicator = Tulip.Indicators.sma;
|
||||
|
||||
+111
-65
@@ -26,7 +26,7 @@ namespace QuanTAlib;
|
||||
/// Becomes true when the buffer is full (period samples processed).
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Sma : ITValuePublisher
|
||||
public sealed class Sma : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly RingBuffer _buffer;
|
||||
@@ -37,13 +37,6 @@ public sealed class Sma : ITValuePublisher
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Creates SMA with specified period.
|
||||
/// </summary>
|
||||
@@ -56,6 +49,7 @@ public sealed class Sma : ITValuePublisher
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
Name = $"Sma({period})";
|
||||
WarmupPeriod = period;
|
||||
}
|
||||
|
||||
public Sma(ITValuePublisher source, int period) : this(period)
|
||||
@@ -63,16 +57,93 @@ public sealed class Sma : ITValuePublisher
|
||||
source.Pub += (item) => Update(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current SMA value.
|
||||
/// </summary>
|
||||
public TValue Last { get; private set; }
|
||||
public Sma(TSeries source, int period) : this(period)
|
||||
{
|
||||
Prime(source.Values);
|
||||
if (source.Count > 0)
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mode B: Streaming (Stateful)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// True if the SMA has enough data to produce valid results.
|
||||
/// SMA is "hot" when the buffer is full (has received at least 'period' values).
|
||||
/// </summary>
|
||||
public bool IsHot => _buffer.IsFull;
|
||||
public override bool IsHot => _buffer.IsFull;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mode C: Priming (The Bridge)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided history.
|
||||
/// Efficiently processes only the last 'Period' values required to sync the buffer.
|
||||
/// </summary>
|
||||
/// <param name="source">Historical data (only the last 'period' is actually needed)</param>
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
|
||||
// Reset state
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
|
||||
// We only need the last 'period' values to fully restore state
|
||||
// If history is shorter than period, we take it all.
|
||||
int warmupLength = Math.Min(source.Length, WarmupPeriod);
|
||||
int startIndex = source.Length - warmupLength;
|
||||
|
||||
// 1. Seed the LastValidValue (crucial for NaN handling)
|
||||
// We must look backwards from start of our warmup window to find a valid predecessor
|
||||
_state.LastValidValue = double.NaN;
|
||||
for (int i = startIndex - 1; i >= 0; i--)
|
||||
{
|
||||
if (double.IsFinite(source[i]))
|
||||
{
|
||||
_state.LastValidValue = source[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a valid value in history, try finding one inside the warmup window
|
||||
if (double.IsNaN(_state.LastValidValue))
|
||||
{
|
||||
for (int i = startIndex; i < source.Length; i++)
|
||||
{
|
||||
if (double.IsFinite(source[i]))
|
||||
{
|
||||
_state.LastValidValue = source[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Feed the RingBuffer and State
|
||||
for (int i = startIndex; i < source.Length; i++)
|
||||
{
|
||||
double val = GetValidValue(source[i]);
|
||||
UpdateState(val);
|
||||
_state.LastInput = val;
|
||||
}
|
||||
|
||||
// 3. Finalize State
|
||||
// Calculate the initial "Last" value so the indicator is ready to be read immediately
|
||||
double result = _buffer.Count > 0 ? _state.Sum / _buffer.Count : double.NaN;
|
||||
|
||||
// Note: We can't infer accurate Time from a simple Span<double>,
|
||||
// so we leave 'Last' with default time or user updates it on next Tick.
|
||||
Last = new TValue(DateTime.MinValue, result);
|
||||
|
||||
// Backup state for the next update cycle
|
||||
_p_state = _state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a valid input value, using last-value substitution for non-finite inputs.
|
||||
@@ -106,7 +177,7 @@ public sealed class Sma : ITValuePublisher
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
@@ -127,11 +198,11 @@ public sealed class Sma : ITValuePublisher
|
||||
|
||||
double result = _state.Sum / _buffer.Count;
|
||||
Last = new TValue(input.Time, result);
|
||||
Pub?.Invoke(Last);
|
||||
PubEvent(Last);
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TSeries source)
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
|
||||
@@ -144,65 +215,26 @@ public sealed class Sma : ITValuePublisher
|
||||
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;
|
||||
|
||||
_state.LastValidValue = double.NaN;
|
||||
bool found = false;
|
||||
|
||||
if (startIndex > 0)
|
||||
{
|
||||
for (int i = startIndex - 1; i >= 0; i--)
|
||||
{
|
||||
if (double.IsFinite(source.Values[i]))
|
||||
{
|
||||
_state.LastValidValue = source.Values[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (double.IsFinite(source.Values[i]))
|
||||
{
|
||||
_state.LastValidValue = source.Values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
_state.Sum = 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);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mode A: Batch (Stateless)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Calculates SMA for the entire series using a new instance.
|
||||
/// </summary>
|
||||
/// <param name="source">Input series</param>
|
||||
/// <param name="period">SMA period</param>
|
||||
/// <returns>SMA series</returns>
|
||||
public static TSeries Calculate(TSeries source, int period)
|
||||
public static TSeries Batch(TSeries source, int period)
|
||||
{
|
||||
var sma = new Sma(period);
|
||||
return sma.Update(source);
|
||||
@@ -218,7 +250,7 @@ public sealed class Sma : ITValuePublisher
|
||||
/// <param name="output">Output span (must be same length as source)</param>
|
||||
/// <param name="period">SMA period (must be > 0)</param>
|
||||
[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");
|
||||
@@ -256,6 +288,20 @@ public sealed class Sma : ITValuePublisher
|
||||
CalculateScalarCore(source, output, period);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a high-performance SIMD batch calculation on history and returns
|
||||
/// a "Hot" Sma instance ready to process the next tick immediately.
|
||||
/// </summary>
|
||||
/// <param name="source">Historical time series</param>
|
||||
/// <param name="period">SMA Period</param>
|
||||
/// <returns>A tuple containing the full calculation results and the hot indicator instance</returns>
|
||||
public static (TSeries Results, Sma Indicator) Calculate(TSeries source, int period)
|
||||
{
|
||||
var sma = new Sma(period);
|
||||
TSeries results = sma.Update(source);
|
||||
return (results, sma);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CalculateScalarCore(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
@@ -268,7 +314,7 @@ public sealed class Sma : ITValuePublisher
|
||||
|
||||
double sum = 0;
|
||||
double lastValid = double.NaN;
|
||||
|
||||
|
||||
// Find first valid value to seed lastValid
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
@@ -541,7 +587,7 @@ public sealed class Sma : ITValuePublisher
|
||||
/// <summary>
|
||||
/// Resets the SMA state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
public override void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
|
||||
@@ -68,12 +68,12 @@ Console.WriteLine($"IsHot: {sma.IsHot}"); // true when buffer is full
|
||||
|
||||
// Batch calculation (TSeries API)
|
||||
TSeries source = ...;
|
||||
TSeries results = Sma.Calculate(source, 10);
|
||||
TSeries results = Sma.Batch(source, 10);
|
||||
|
||||
// High-performance Span API (zero allocation)
|
||||
double[] prices = new double[10000];
|
||||
double[] output = new double[10000];
|
||||
Sma.Calculate(prices.AsSpan(), output.AsSpan(), period: 10);
|
||||
Sma.Batch(prices.AsSpan(), output.AsSpan(), period: 10);
|
||||
```
|
||||
|
||||
### Zero-Allocation Span API
|
||||
@@ -86,7 +86,7 @@ double[] source = new double[200000];
|
||||
double[] smaOutput = new double[200000];
|
||||
|
||||
// Zero heap allocation during calculation
|
||||
Sma.Calculate(source.AsSpan(), smaOutput.AsSpan(), period: 100);
|
||||
Sma.Batch(source.AsSpan(), smaOutput.AsSpan(), period: 100);
|
||||
|
||||
// Results are written directly to output buffer
|
||||
Console.WriteLine($"Last SMA: {smaOutput[^1]}");
|
||||
|
||||
Reference in New Issue
Block a user