normalization of methods

This commit is contained in:
Miha Kralj
2026-02-10 21:33:16 -08:00
parent 915d7a007b
commit 6d6259a47d
527 changed files with 10525 additions and 2123 deletions
+9 -9
View File
@@ -115,7 +115,7 @@ public class AlmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Alma.Calculate(input.AsSpan(), output.AsSpan(), 10);
Alma.Batch(input.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < input.Length; i++)
{
@@ -284,7 +284,7 @@ public class AlmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Alma.Calculate(spanInput, spanOutput, period);
Alma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
@@ -317,12 +317,12 @@ public class AlmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Alma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Alma.Calculate(source.AsSpan(), output.AsSpan(), 3, sigma: 0));
Assert.Throws<ArgumentException>(() => Alma.Calculate(source.AsSpan(), output.AsSpan(), 3, sigma: -1));
Assert.Throws<ArgumentOutOfRangeException>(() => Alma.Calculate(source.AsSpan(), output.AsSpan(), 3, offset: -0.1));
Assert.Throws<ArgumentOutOfRangeException>(() => Alma.Calculate(source.AsSpan(), output.AsSpan(), 3, offset: 1.1));
Assert.Throws<ArgumentException>(() => Alma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Alma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Alma.Batch(source.AsSpan(), output.AsSpan(), 3, sigma: 0));
Assert.Throws<ArgumentException>(() => Alma.Batch(source.AsSpan(), output.AsSpan(), 3, sigma: -1));
Assert.Throws<ArgumentOutOfRangeException>(() => Alma.Batch(source.AsSpan(), output.AsSpan(), 3, offset: -0.1));
Assert.Throws<ArgumentOutOfRangeException>(() => Alma.Batch(source.AsSpan(), output.AsSpan(), 3, offset: 1.1));
Assert.Throws<ArgumentException>(() => Alma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -331,7 +331,7 @@ public class AlmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Alma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Alma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
+1 -1
View File
@@ -102,7 +102,7 @@ public sealed class AlmaValidationTests : IDisposable
{
// Calculate QuanTAlib ALMA (Span API)
double[] qOutput = new double[sourceData.Length];
global::QuanTAlib.Alma.Calculate(sourceData, qOutput.AsSpan(), period, offset, sigma);
global::QuanTAlib.Alma.Batch(sourceData, qOutput.AsSpan(), period, offset, sigma);
// Calculate Skender ALMA
var sResult = _testData.SkenderQuotes.GetAlma(period, offset, sigma).ToList();
+17 -5
View File
@@ -26,6 +26,7 @@ public sealed class Alma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State(double LastValidValue, bool IsInitialized);
@@ -83,9 +84,13 @@ public sealed class Alma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -181,7 +186,7 @@ public sealed class Alma : AbstractBase
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period, _offset, _sigma);
Batch(source.Values, vSpan, _period, _offset, _sigma);
source.Times.CopyTo(tSpan);
// Restore state
@@ -305,7 +310,7 @@ public sealed class Alma : AbstractBase
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, double offset = 0.85, double sigma = 6.0)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, double offset = 0.85, double sigma = 6.0)
{
if (period <= 0)
{
@@ -433,6 +438,13 @@ public sealed class Alma : AbstractBase
}
}
public static (TSeries Results, Alma Indicator) Calculate(TSeries source, int period, double offset = 0.85, double sigma = 6.0)
{
var indicator = new Alma(period, offset, sigma);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -440,4 +452,4 @@ public sealed class Alma : AbstractBase
_p_state = _state;
Last = default;
}
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ public class BlmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Blma.Calculate(spanInput, spanOutput, period);
Blma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
+11 -11
View File
@@ -151,7 +151,7 @@ public sealed class Blma : AbstractBase
try
{
Calculate(source.Values, output, _period);
BatchCore(source.Values, output, _period);
for (int i = 0; i < len; i++)
{
@@ -243,17 +243,11 @@ public sealed class Blma : AbstractBase
/// <summary>
/// Calculates BLMA values for a TSeries and returns both results and a primed indicator.
/// </summary>
public static (TSeries Results, Blma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Blma(period);
var results = indicator.Update(source);
return (results, indicator);
}
/// <summary>
/// Calculates BLMA values using spans (high-performance batch API).
/// Core batch calculation of BLMA values using spans (high-performance batch API).
/// </summary>
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period)
private static void BatchCore(ReadOnlySpan<double> source, Span<double> destination, int period)
{
if (period < 1)
{
@@ -377,6 +371,12 @@ public sealed class Blma : AbstractBase
/// </summary>
public static void Batch(ReadOnlySpan<double> source, Span<double> destination, int period)
{
Calculate(source, destination, period);
BatchCore(source, destination, period);
}
}
public static (TSeries Results, Blma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Blma(period);
var results = indicator.Update(source);
return (results, indicator);
}
}
+2 -2
View File
@@ -152,7 +152,7 @@ public class BwmaTests
}
var spanResults = new double[series.Count];
Bwma.Calculate(series.Values, spanResults, 10);
Bwma.Batch(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
@@ -175,7 +175,7 @@ public class BwmaTests
}
var spanResults = new double[series.Count];
Bwma.Calculate(series.Values, spanResults, 10, 2);
Bwma.Batch(series.Values, spanResults, 10, 2);
for (int i = 0; i < spanResults.Length; i++)
{
+2 -2
View File
@@ -60,7 +60,7 @@ public sealed class BwmaValidationTests : IDisposable
// 3. Span API
ReadOnlySpan<double> sourceData = _testData.RawData.Span;
double[] spanOutput = new double[sourceData.Length];
Bwma.Calculate(sourceData, spanOutput.AsSpan(), period, order);
Bwma.Batch(sourceData, spanOutput.AsSpan(), period, order);
// Verify streaming vs batch
Assert.Equal(streamingResults.Count, batchResults.Count);
@@ -257,7 +257,7 @@ public sealed class BwmaValidationTests : IDisposable
// Span
double[] spanOutput = new double[dataWithNaN.Count];
Bwma.Calculate(dataWithNaN.Values, spanOutput.AsSpan(), period);
Bwma.Batch(dataWithNaN.Values, spanOutput.AsSpan(), period);
// Verify all produce same results
for (int i = 0; i < streamingResults.Count; i++)
+17 -5
View File
@@ -26,6 +26,7 @@ public sealed class Bwma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State
@@ -81,9 +82,13 @@ public sealed class Bwma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -199,7 +204,7 @@ public sealed class Bwma : AbstractBase
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period, _order);
Batch(source.Values, vSpan, _period, _order);
source.Times.CopyTo(tSpan);
_buffer.Clear();
@@ -338,7 +343,7 @@ public sealed class Bwma : AbstractBase
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int order = 0)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, int order = 0)
{
if (period <= 0)
{
@@ -477,6 +482,13 @@ public sealed class Bwma : AbstractBase
}
}
public static (TSeries Results, Bwma Indicator) Calculate(TSeries source, int period, int order = 0)
{
var indicator = new Bwma(period, order);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -484,4 +496,4 @@ public sealed class Bwma : AbstractBase
_p_state = _state;
Last = default;
}
}
}
+15 -3
View File
@@ -22,6 +22,7 @@ public sealed class Conv : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _subHandler;
private bool _isNew = true;
private bool _disposed;
private record struct State(double LastValidValue);
private State _state;
@@ -56,9 +57,13 @@ public sealed class Conv : AbstractBase
protected override void Dispose(bool disposing)
{
if (_source != null && _subHandler != null)
if (!_disposed)
{
_source.Pub -= _subHandler;
if (disposing && _source != null && _subHandler != null)
{
_source.Pub -= _subHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -276,6 +281,13 @@ public sealed class Conv : AbstractBase
}
}
public static (TSeries Results, Conv Indicator) Calculate(TSeries source, double[] kernel)
{
var indicator = new Conv(kernel);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -283,4 +295,4 @@ public sealed class Conv : AbstractBase
_p_state.LastValidValue = double.NaN;
Last = default;
}
}
}
+4 -4
View File
@@ -150,8 +150,8 @@ public class DwmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Dwma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Dwma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Dwma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Dwma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -160,7 +160,7 @@ public class DwmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Dwma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Dwma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
@@ -185,7 +185,7 @@ public class DwmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Dwma.Calculate(spanInput, spanOutput, period);
Dwma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
+17 -5
View File
@@ -22,6 +22,7 @@ public sealed class Dwma : AbstractBase
private readonly Wma _wma2;
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _handler;
private bool _disposed;
private int _sampleCount;
public override bool IsHot => _sampleCount >= WarmupPeriod;
@@ -53,9 +54,13 @@ public sealed class Dwma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _handler != null)
if (!_disposed)
{
_source.Pub -= _handler;
if (disposing && _source != null && _handler != null)
{
_source.Pub -= _handler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -91,7 +96,7 @@ public sealed class Dwma : AbstractBase
var vSpan = CollectionsMarshal.AsSpan(v);
source.Times.CopyTo(tSpan);
Calculate(source.Values, vSpan, _period);
Batch(source.Values, vSpan, _period);
Reset();
@@ -129,7 +134,7 @@ public sealed class Dwma : AbstractBase
}
[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 (period <= 0)
{
@@ -166,6 +171,13 @@ public sealed class Dwma : AbstractBase
}
}
public static (TSeries Results, Dwma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Dwma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_wma1.Reset();
@@ -173,4 +185,4 @@ public sealed class Dwma : AbstractBase
_sampleCount = 0;
Last = default;
}
}
}
+8 -8
View File
@@ -114,7 +114,7 @@ public class GwmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Gwma.Calculate(input.AsSpan(), output.AsSpan(), 10);
Gwma.Batch(input.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < input.Length; i++)
{
@@ -283,7 +283,7 @@ public class GwmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Gwma.Calculate(spanInput, spanOutput, period);
Gwma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
@@ -316,11 +316,11 @@ public class GwmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Gwma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Gwma.Calculate(source.AsSpan(), output.AsSpan(), 3, sigma: 0));
Assert.Throws<ArgumentException>(() => Gwma.Calculate(source.AsSpan(), output.AsSpan(), 3, sigma: -1));
Assert.Throws<ArgumentOutOfRangeException>(() => Gwma.Calculate(source.AsSpan(), output.AsSpan(), 3, sigma: 1.1));
Assert.Throws<ArgumentException>(() => Gwma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Gwma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Gwma.Batch(source.AsSpan(), output.AsSpan(), 3, sigma: 0));
Assert.Throws<ArgumentException>(() => Gwma.Batch(source.AsSpan(), output.AsSpan(), 3, sigma: -1));
Assert.Throws<ArgumentOutOfRangeException>(() => Gwma.Batch(source.AsSpan(), output.AsSpan(), 3, sigma: 1.1));
Assert.Throws<ArgumentException>(() => Gwma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -329,7 +329,7 @@ public class GwmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Gwma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Gwma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
+1 -1
View File
@@ -83,7 +83,7 @@ public sealed class GwmaValidationTests : IDisposable
{
// Calculate QuanTAlib GWMA (Span API)
double[] spanOutput = new double[sourceData.Length];
Gwma.Calculate(sourceData, spanOutput.AsSpan(), period, sigma);
Gwma.Batch(sourceData, spanOutput.AsSpan(), period, sigma);
// Calculate QuanTAlib GWMA (batch TSeries)
var gwmaBatch = new Gwma(period, sigma);
+17 -5
View File
@@ -25,6 +25,7 @@ public sealed class Gwma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State
@@ -84,9 +85,13 @@ public sealed class Gwma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -178,7 +183,7 @@ public sealed class Gwma : AbstractBase
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period, _sigma);
Batch(source.Values, vSpan, _period, _sigma);
source.Times.CopyTo(tSpan);
// Restore internal state to match the streaming path:
@@ -288,7 +293,7 @@ public sealed class Gwma : AbstractBase
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, double sigma = 0.4)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, double sigma = 0.4)
{
if (period <= 0)
{
@@ -438,6 +443,13 @@ public sealed class Gwma : AbstractBase
}
}
public static (TSeries Results, Gwma Indicator) Calculate(TSeries source, int period, double sigma = 0.4)
{
var indicator = new Gwma(period, sigma);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -445,4 +457,4 @@ public sealed class Gwma : AbstractBase
_p_state = _state;
Last = default;
}
}
}
+5 -5
View File
@@ -108,7 +108,7 @@ public class HammaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Hamma.Calculate(input.AsSpan(), output.AsSpan(), 10);
Hamma.Batch(input.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < input.Length; i++)
{
@@ -277,7 +277,7 @@ public class HammaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Hamma.Calculate(spanInput, spanOutput, period);
Hamma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
@@ -310,8 +310,8 @@ public class HammaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Hamma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hamma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Hamma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hamma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -320,7 +320,7 @@ public class HammaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Hamma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Hamma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
@@ -75,7 +75,7 @@ public sealed class HammaValidationTests : IDisposable
{
// Calculate QuanTAlib HAMMA (Span API)
double[] spanOutput = new double[sourceData.Length];
Hamma.Calculate(sourceData, spanOutput.AsSpan(), period);
Hamma.Batch(sourceData, spanOutput.AsSpan(), period);
// Calculate QuanTAlib HAMMA (batch TSeries)
var hammaBatch = new Hamma(period);
+17 -5
View File
@@ -24,6 +24,7 @@ public sealed class Hamma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State
@@ -78,9 +79,13 @@ public sealed class Hamma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -183,7 +188,7 @@ public sealed class Hamma : AbstractBase
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
@@ -271,7 +276,7 @@ public sealed class Hamma : AbstractBase
/// <param name="period">Lookback period for the Hamming window (default: 10)</param>
/// <exception cref="ArgumentException">Thrown when output length doesn't match source length.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 10)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period = 10)
{
if (period <= 0)
{
@@ -389,6 +394,13 @@ public sealed class Hamma : AbstractBase
}
}
public static (TSeries Results, Hamma Indicator) Calculate(TSeries source, int period = 10)
{
var indicator = new Hamma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -397,4 +409,4 @@ public sealed class Hamma : AbstractBase
_p_state = _state;
Last = default;
}
}
}
+5 -5
View File
@@ -111,7 +111,7 @@ public class HanmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Hanma.Calculate(input.AsSpan(), output.AsSpan(), 10);
Hanma.Batch(input.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < input.Length; i++)
{
@@ -292,7 +292,7 @@ public class HanmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Hanma.Calculate(spanInput, spanOutput, period);
Hanma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
@@ -325,8 +325,8 @@ public class HanmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Hanma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hanma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Hanma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hanma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -335,7 +335,7 @@ public class HanmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Hanma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Hanma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
@@ -104,7 +104,7 @@ public class HanmaValidationTests
// Span
double[] input = series.Values.ToArray();
double[] spanOutput = new double[input.Length];
Hanma.Calculate(input.AsSpan(), spanOutput.AsSpan(), period);
Hanma.Batch(input.AsSpan(), spanOutput.AsSpan(), period);
// All should match
for (int i = 0; i < series.Count; i++)
+18 -6
View File
@@ -24,6 +24,7 @@ public sealed class Hanma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State(double LastValidValue, bool IsInitialized);
@@ -56,7 +57,7 @@ public sealed class Hanma : AbstractBase
}
/// <summary>
///
/// Creates HANMA chained to a source publisher for event-based updates.
/// </summary>
/// <param name="source">Data source for event-based updates</param>
/// <param name="period">Lookback period for the Hanning window (default: 10)</param>
@@ -73,9 +74,13 @@ public sealed class Hanma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -177,7 +182,7 @@ public sealed class Hanma : AbstractBase
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
@@ -277,7 +282,7 @@ public sealed class Hanma : AbstractBase
/// <param name="period">Lookback period for the Hanning window (default: 10)</param>
/// <exception cref="ArgumentException">Thrown when output length doesn't match source length.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 10)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period = 10)
{
if (period <= 0)
{
@@ -425,6 +430,13 @@ public sealed class Hanma : AbstractBase
}
}
public static (TSeries Results, Hanma Indicator) Calculate(TSeries source, int period = 10)
{
var indicator = new Hanma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_buffer.Clear();
@@ -432,4 +444,4 @@ public sealed class Hanma : AbstractBase
_p_state = _state;
Last = default;
}
}
}
+5 -5
View File
@@ -114,7 +114,7 @@ public class HmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Hma.Calculate(input.AsSpan(), output.AsSpan(), 14);
Hma.Batch(input.AsSpan(), output.AsSpan(), 14);
for (int i = 0; i < input.Length; i++)
{
@@ -219,8 +219,8 @@ public class HmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Hma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Hma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -229,7 +229,7 @@ public class HmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Hma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Hma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
@@ -254,7 +254,7 @@ public class HmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Hma.Calculate(spanInput, spanOutput, period);
Hma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
+1 -1
View File
@@ -140,7 +140,7 @@ public sealed class HmaValidationTests : IDisposable
{
// Calculate QuanTAlib HMA (Span API)
double[] qOutput = new double[sourceData.Length];
global::QuanTAlib.Hma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period);
global::QuanTAlib.Hma.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period);
// Calculate Skender HMA
var sResult = _testData.SkenderQuotes.GetHma(period).ToList();
+11 -4
View File
@@ -94,7 +94,7 @@ public sealed class Hma : AbstractBase
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 for streaming
@@ -144,14 +144,14 @@ public sealed class Hma : AbstractBase
CollectionsMarshal.SetCount(t, len);
CollectionsMarshal.SetCount(v, len);
Calculate(source.Values, CollectionsMarshal.AsSpan(v), period);
Batch(source.Values, CollectionsMarshal.AsSpan(v), period);
source.Times.CopyTo(CollectionsMarshal.AsSpan(t));
return new TSeries(t, v);
}
[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)
{
@@ -204,6 +204,13 @@ public sealed class Hma : AbstractBase
}
}
public static (TSeries Results, Hma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Hma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateIntermediate(ReadOnlySpan<double> halfWma, ReadOnlySpan<double> fullWma, Span<double> output)
{
@@ -262,4 +269,4 @@ public sealed class Hma : AbstractBase
_sampleCount = 0;
Last = default;
}
}
}
+5 -5
View File
@@ -122,7 +122,7 @@ public class HwmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Hwma.Calculate(input.AsSpan(), output.AsSpan(), 10);
Hwma.Batch(input.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < input.Length; i++)
{
@@ -292,7 +292,7 @@ public class HwmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Hwma.Calculate(spanInput, spanOutput, period);
Hwma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
@@ -325,8 +325,8 @@ public class HwmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Hwma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hwma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Hwma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Hwma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -335,7 +335,7 @@ public class HwmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Hwma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Hwma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
+1 -1
View File
@@ -99,7 +99,7 @@ public class HwmaValidationTests
// Span
double[] input = series.Values.ToArray();
double[] spanOutput = new double[input.Length];
Hwma.Calculate(input.AsSpan(), spanOutput.AsSpan(), period);
Hwma.Batch(input.AsSpan(), spanOutput.AsSpan(), period);
// All should match
for (int i = 0; i < series.Count; i++)
+16 -4
View File
@@ -26,6 +26,7 @@ public sealed class Hwma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State(
@@ -118,9 +119,13 @@ public sealed class Hwma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -263,7 +268,7 @@ public sealed class Hwma : AbstractBase
/// <param name="period">Period for smoothing factors (default: 10)</param>
/// <exception cref="ArgumentException">Thrown when output length doesn't match source length.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 10)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period = 10)
{
if (period <= 0)
{
@@ -342,10 +347,17 @@ public sealed class Hwma : AbstractBase
}
}
public static (TSeries Results, Hwma Indicator) Calculate(TSeries source, int period = 10)
{
var indicator = new Hwma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
{
_state = new State(double.NaN, 0, 0, double.NaN, IsInitialized: false);
_p_state = _state;
Last = default;
}
}
}
+1 -1
View File
@@ -161,7 +161,7 @@ public class LsmaTests
values[i] = bar.Close;
}
Lsma.Calculate(values, output, period);
Lsma.Batch(values, output, period);
var lsma = new Lsma(period);
for (int i = 0; i < count; i++)
+1 -1
View File
@@ -67,7 +67,7 @@ public class LsmaValidationTests
{
// Calculate QuanTAlib LSMA (Span API)
double[] qOutput = new double[_testData.RawData.Length];
global::QuanTAlib.Lsma.Calculate(_testData.RawData.Span, qOutput.AsSpan(), period);
global::QuanTAlib.Lsma.Batch(_testData.RawData.Span, qOutput.AsSpan(), period);
// Calculate Skender EPMA
var sResult = _testData.SkenderQuotes.GetEpma(period).ToList();
+10 -3
View File
@@ -221,7 +221,7 @@ public sealed class Lsma : AbstractBase
var vSpan = CollectionsMarshal.AsSpan(v);
double initialLastValid = _state.LastValidValue;
Calculate(source.Values, vSpan, _period, _offset, initialLastValid);
Batch(source.Values, vSpan, _period, _offset, initialLastValid);
source.Times.CopyTo(tSpan);
// Restore state
@@ -282,7 +282,7 @@ public sealed class Lsma : AbstractBase
/// Zero-allocation method for maximum performance.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int offset = 0, double initialLastValid = double.NaN)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, int offset = 0, double initialLastValid = double.NaN)
{
if (source.Length != output.Length)
{
@@ -395,6 +395,13 @@ public sealed class Lsma : AbstractBase
}
}
public static (TSeries Results, Lsma Indicator) Calculate(TSeries source, int period, int offset = 0)
{
var indicator = new Lsma(period, offset);
TSeries results = indicator.Update(source);
return (results, indicator);
}
/// <summary>
/// Resets the LSMA state.
/// </summary>
@@ -422,4 +429,4 @@ public sealed class Lsma : AbstractBase
}
base.Dispose(disposing);
}
}
}
+6 -6
View File
@@ -300,11 +300,11 @@ public class PwmaTests
double[] wrongSizeOutput = new double[3];
// Period must be > 0
Assert.Throws<ArgumentException>(() => Pwma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Pwma.Calculate(source.AsSpan(), output.AsSpan(), -1));
Assert.Throws<ArgumentException>(() => Pwma.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Pwma.Batch(source.AsSpan(), output.AsSpan(), -1));
// Output must be same length as source
Assert.Throws<ArgumentException>(() => Pwma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Pwma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -326,7 +326,7 @@ public class PwmaTests
var tseriesResult = Pwma.Batch(series, 10);
// Calculate with Span API
Pwma.Calculate(source.AsSpan(), output.AsSpan(), 10);
Pwma.Batch(source.AsSpan(), output.AsSpan(), 10);
// Compare results
for (int i = 0; i < 100; i++)
@@ -341,7 +341,7 @@ public class PwmaTests
double[] source = [10, 20, 30];
double[] output = new double[3];
Pwma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Pwma.Batch(source.AsSpan(), output.AsSpan(), 3);
// PWMA(3) warmup:
// i=0: 10 (1^2*10 / 1^2) = 10
@@ -369,7 +369,7 @@ public class PwmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Pwma.Calculate(spanInput, spanOutput, period);
Pwma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
+10 -3
View File
@@ -175,7 +175,7 @@ public sealed class Pwma : AbstractBase
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
@@ -234,7 +234,7 @@ public sealed class Pwma : AbstractBase
}
[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)
{
@@ -255,6 +255,13 @@ public sealed class Pwma : AbstractBase
CalculateScalarCore(source, output, period);
}
public static (TSeries Results, Pwma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Pwma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateScalarCore(ReadOnlySpan<double> source, Span<double> output, int period)
{
@@ -356,4 +363,4 @@ public sealed class Pwma : AbstractBase
_p_state = default;
Last = default;
}
}
}
+6 -6
View File
@@ -109,7 +109,7 @@ public class SgmaTests
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Sgma.Calculate(input.AsSpan(), output.AsSpan(), 9);
Sgma.Batch(input.AsSpan(), output.AsSpan(), 9);
for (int i = 0; i < input.Length; i++)
{
@@ -268,7 +268,7 @@ public class SgmaTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan<double>(tValues);
var spanOutput = new double[tValues.Length];
Sgma.Calculate(spanInput, spanOutput, period);
Sgma.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
var streamingInd = new Sgma(period);
@@ -298,9 +298,9 @@ public class SgmaTests
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Sgma.Calculate(source.AsSpan(), output.AsSpan(), 2));
Assert.Throws<ArgumentException>(() => Sgma.Calculate(source.AsSpan(), output.AsSpan(), 5, 5));
Assert.Throws<ArgumentException>(() => Sgma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
Assert.Throws<ArgumentException>(() => Sgma.Batch(source.AsSpan(), output.AsSpan(), 2));
Assert.Throws<ArgumentException>(() => Sgma.Batch(source.AsSpan(), output.AsSpan(), 5, 5));
Assert.Throws<ArgumentException>(() => Sgma.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
@@ -309,7 +309,7 @@ public class SgmaTests
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Sgma.Calculate(source.AsSpan(), output.AsSpan(), 3);
Sgma.Batch(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
+3 -3
View File
@@ -65,7 +65,7 @@ public class SgmaValidationTests
double[] sgmaOutput = new double[input.Length];
double[] smaOutput = new double[input.Length];
Sgma.Calculate(input.AsSpan(), sgmaOutput.AsSpan(), 9, 0);
Sgma.Batch(input.AsSpan(), sgmaOutput.AsSpan(), 9, 0);
Sma.Batch(input.AsSpan(), smaOutput.AsSpan(), 9);
for (int i = 0; i < input.Length; i++)
@@ -209,7 +209,7 @@ public class SgmaValidationTests
// Span
double[] input = series.Values.ToArray();
double[] output = new double[input.Length];
Sgma.Calculate(input.AsSpan(), output.AsSpan(), 9, degree);
Sgma.Batch(input.AsSpan(), output.AsSpan(), 9, degree);
Assert.Equal(expected, output[^1], Tolerance);
// Streaming
@@ -238,7 +238,7 @@ public class SgmaValidationTests
double[] sourceWithNaN = [100, 110, 120, double.NaN, 140, 150, 160, 170, 180];
double[] output = new double[sourceWithNaN.Length];
Sgma.Calculate(sourceWithNaN.AsSpan(), output.AsSpan(), 5, 2);
Sgma.Batch(sourceWithNaN.AsSpan(), output.AsSpan(), 5, 2);
// All outputs should be finite
foreach (var val in output)
+17 -5
View File
@@ -25,6 +25,7 @@ public sealed class Sgma : AbstractBase
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private bool _disposed;
private double _lastValidValue = double.NaN;
private double _p_lastValidValue = double.NaN;
@@ -163,9 +164,13 @@ public sealed class Sgma : AbstractBase
protected override void Dispose(bool disposing)
{
if (disposing && _source != null && _pubHandler != null)
if (!_disposed)
{
_source.Pub -= _pubHandler;
if (disposing && _source != null && _pubHandler != null)
{
_source.Pub -= _pubHandler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -277,7 +282,7 @@ public sealed class Sgma : AbstractBase
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Calculate(source.Values, vSpan, _period, _degree);
Batch(source.Values, vSpan, _period, _degree);
source.Times.CopyTo(tSpan);
// Restore state by replaying last period bars
@@ -317,7 +322,7 @@ public sealed class Sgma : AbstractBase
/// <param name="degree">Polynomial degree (default: 2)</param>
/// <exception cref="ArgumentException">Thrown when output length doesn't match source length.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 9, int degree = 2)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period = 9, int degree = 2)
{
if (period < 3)
{
@@ -422,6 +427,13 @@ public sealed class Sgma : AbstractBase
}
}
public static (TSeries Results, Sgma Indicator) Calculate(TSeries source, int period = 9, int degree = 2)
{
var indicator = new Sgma(period, degree);
TSeries results = indicator.Update(source);
return (results, indicator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateWeightedSumFull(RingBuffer buffer, double[] weights, double invWeightSum, double fallbackValue)
{
@@ -557,4 +569,4 @@ public sealed class Sgma : AbstractBase
_p_lastValidValue = double.NaN;
Last = default;
}
}
}
+8 -1
View File
@@ -165,4 +165,11 @@ public sealed class Trima : AbstractBase
ArrayPool<double>.Shared.Return(tempArray);
}
}
}
public static (TSeries Results, Trima Indicator) Calculate(TSeries source, int period)
{
var indicator = new Trima(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
}
+15 -3
View File
@@ -24,6 +24,7 @@ public sealed class Wma : AbstractBase
private readonly RingBuffer _buffer;
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _handler;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State(double Sum, double WSum, double LastInput, double LastValidValue, int TickCount, bool HasSeenValidData);
@@ -68,9 +69,13 @@ public sealed class Wma : AbstractBase
protected override void Dispose(bool disposing)
{
if (_source != null && _handler != null)
if (!_disposed)
{
_source.Pub -= _handler;
if (disposing && _source != null && _handler != null)
{
_source.Pub -= _handler;
}
_disposed = true;
}
base.Dispose(disposing);
}
@@ -298,6 +303,13 @@ public sealed class Wma : AbstractBase
CalculateScalarCore(source, output, period);
}
public static (TSeries Results, Wma Indicator) Calculate(TSeries source, int period)
{
var indicator = new Wma(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateScalarCore(ReadOnlySpan<double> source, Span<double> output, int period)
{
@@ -856,4 +868,4 @@ public sealed class Wma : AbstractBase
Unsafe.Add(ref outRef, idx) = wsum * invDivisor;
}
}
}
}