Test completeness

This commit is contained in:
Miha Kralj
2025-12-14 21:55:40 -08:00
parent f761cc5712
commit a67ad65fa5
35 changed files with 2437 additions and 1405 deletions
+152
View File
@@ -59,6 +59,7 @@ public class AlmaTests
// Streaming
var streamingResults = new TSeries();
Assert.True(series.Count > 0);
foreach (var item in series)
{
streamingResults.Add(almaStreaming.Update(item));
@@ -178,4 +179,155 @@ public class AlmaTests
Assert.Equal(0, alma.Last.Value);
Assert.False(alma.IsHot);
}
[Fact]
public void Alma_FirstValue_ReturnsExpected()
{
var alma = new Alma(10);
TValue result = alma.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100.0, result.Value, 1e-9);
}
[Fact]
public void Alma_Properties_Accessible()
{
var alma = new Alma(10);
Assert.False(alma.IsHot);
Assert.Equal(0, alma.Last.Value);
}
[Fact]
public void Alma_Calc_IsNew_AcceptsParameter()
{
var alma = new Alma(10);
alma.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
Assert.Equal(100, alma.Last.Value);
}
[Fact]
public void Alma_IterativeCorrections_RestoreToOriginalState()
{
var alma = new Alma(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
alma.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double valueAfterTen = alma.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
alma.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalValue = alma.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(valueAfterTen, finalValue.Value, 1e-9);
}
[Fact]
public void Alma_Infinity_Input_UsesLastValidValue()
{
var alma = new Alma(10);
alma.Update(new TValue(DateTime.UtcNow, 100));
alma.Update(new TValue(DateTime.UtcNow, 110));
var resultPosInf = alma.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
Assert.True(double.IsFinite(resultPosInf.Value));
var resultNegInf = alma.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
Assert.True(double.IsFinite(resultNegInf.Value));
}
[Fact]
public void Alma_MultipleNaN_ContinuesWithLastValid()
{
var alma = new Alma(10);
alma.Update(new TValue(DateTime.UtcNow, 100));
var r1 = alma.Update(new TValue(DateTime.UtcNow, double.NaN));
var r2 = alma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(r1.Value));
Assert.True(double.IsFinite(r2.Value));
}
[Fact]
public void Alma_AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Alma.Calculate(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];
Alma.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Alma(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Alma(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, 1e-9);
Assert.Equal(expected, streamingResult, 1e-9);
Assert.Equal(expected, eventingResult, 1e-9);
}
[Fact]
public void Alma_SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
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(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
public void Alma_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Alma.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
}
+103
View File
@@ -129,4 +129,107 @@ public class ConvTests
var res = conv.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsNaN(res.Value));
}
[Fact]
public void IterativeCorrections_RestoreToOriginalState()
{
double[] kernel = [0.5, 1.0];
var conv = new Conv(kernel);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
conv.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double valueAfterTen = conv.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
conv.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalValue = conv.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(valueAfterTen, finalValue.Value, 1e-9);
}
[Fact]
public void AllModes_ProduceSameResult()
{
// Arrange
double[] kernel = [0.1, 0.2, 0.3, 0.4];
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Conv.Calculate(series, kernel);
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];
Conv.Calculate(spanInput, spanOutput, kernel);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Conv(kernel);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Conv(pubSource, kernel);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, 1e-9);
Assert.Equal(expected, streamingResult, 1e-9);
Assert.Equal(expected, eventingResult, 1e-9);
}
[Fact]
public void SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
double[] kernel = [0.5, 0.5];
Assert.Throws<ArgumentException>(() => Conv.Calculate(source.AsSpan(), output.AsSpan(), Array.Empty<double>()));
Assert.Throws<ArgumentException>(() => Conv.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), kernel));
}
[Fact]
public void SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
double[] kernel = [0.5, 0.5];
Conv.Calculate(source.AsSpan(), output.AsSpan(), kernel);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
}
+101
View File
@@ -160,6 +160,107 @@ public class DemaTests
Assert.Equal(val.Value, output[i], 1e-9);
}
}
[Fact]
public void Dema_Constructor_ValidatesInput()
{
Assert.Throws<ArgumentException>(() => new Dema(0));
Assert.Throws<ArgumentException>(() => new Dema(-1));
Assert.Throws<ArgumentException>(() => new Dema(0.0));
Assert.Throws<ArgumentException>(() => new Dema(1.1));
}
[Fact]
public void Dema_Calc_IsNew_AcceptsParameter()
{
var dema = new Dema(10);
dema.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
Assert.Equal(100, dema.Last.Value);
}
[Fact]
public void Dema_Reset_ClearsState()
{
var dema = new Dema(10);
dema.Update(new TValue(DateTime.UtcNow, 100));
dema.Update(new TValue(DateTime.UtcNow, 110));
dema.Reset();
Assert.Equal(0, dema.Last.Value);
Assert.False(dema.IsHot);
}
[Fact]
public void Dema_IterativeCorrections_RestoreToOriginalState()
{
var dema = new Dema(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
dema.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double valueAfterTen = dema.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
dema.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalValue = dema.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(valueAfterTen, finalValue.Value, 1e-9);
}
[Fact]
public void Dema_NaN_Input_UsesLastValidValue()
{
var dema = new Dema(10);
dema.Update(new TValue(DateTime.UtcNow, 100));
dema.Update(new TValue(DateTime.UtcNow, 110));
var resultAfterNaN = dema.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void Dema_SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Dema.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Dema.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
public void Dema_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Dema.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
[Fact]
public void Dema_AllModes_ProduceSameResult()
{
+113
View File
@@ -99,4 +99,117 @@ public class DwmaTests
Assert.Equal(source.Count, staticResult.Count);
Assert.Equal(dwma.Last.Value, staticResult.Last.Value, 8);
}
[Fact]
public void IterativeCorrections_RestoreToOriginalState()
{
var dwma = new Dwma(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
dwma.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double valueAfterTen = dwma.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
dwma.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalValue = dwma.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(valueAfterTen, finalValue.Value, 1e-9);
}
[Fact]
public void NaN_Input_UsesLastValidValue()
{
var dwma = new Dwma(5);
dwma.Update(new TValue(DateTime.UtcNow, 100));
dwma.Update(new TValue(DateTime.UtcNow, 110));
var resultAfterNaN = dwma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
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));
}
[Fact]
public void SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Dwma.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
[Fact]
public void AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Dwma.Calculate(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];
Dwma.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Dwma(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Dwma(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
}
+128 -1
View File
@@ -62,6 +62,7 @@ public class HmaTests
// Streaming
var streamingResults = new TSeries();
Assert.True(series.Count > 0);
foreach (var item in series)
{
streamingResults.Add(hmaStreaming.Update(item));
@@ -71,7 +72,7 @@ public class HmaTests
var batchResults = hmaBatch.Update(series);
Assert.Equal(streamingResults.Count, batchResults.Count);
for (int i = 0; i < streamingResults.Count; i++)
for (int i = 0; i < series.Count; i++)
{
Assert.Equal(streamingResults[i].Value, batchResults[i].Value, 1e-9);
}
@@ -152,4 +153,130 @@ public class HmaTests
Assert.Equal(valueAfterCommit, hma.Last.Value, 1e-9);
}
[Fact]
public void Hma_Reset_ClearsState()
{
var hma = new Hma(10);
hma.Update(new TValue(DateTime.UtcNow, 100));
hma.Update(new TValue(DateTime.UtcNow, 110));
hma.Reset();
Assert.Equal(0, hma.Last.Value);
Assert.False(hma.IsHot);
}
[Fact]
public void Hma_IterativeCorrections_RestoreToOriginalState()
{
var hma = new Hma(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
hma.Update(tenthInput, isNew: true);
}
// Remember state after 10 values
double valueAfterTen = hma.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
hma.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalValue = hma.Update(tenthInput, isNew: false);
// Should match the original state after 10 values
Assert.Equal(valueAfterTen, finalValue.Value, 1e-9);
}
[Fact]
public void Hma_NaN_Input_UsesLastValidValue()
{
var hma = new Hma(5);
hma.Update(new TValue(DateTime.UtcNow, 100));
hma.Update(new TValue(DateTime.UtcNow, 110));
var resultAfterNaN = hma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void Hma_SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
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));
}
[Fact]
public void Hma_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Hma.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
[Fact]
public void Hma_AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Hma.Calculate(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];
Hma.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Hma(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Hma(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
}
+118
View File
@@ -79,4 +79,122 @@ public class HtitTests
Assert.Equal(100.0, htit.Last.Value);
}
[Fact]
public void Htit_Calc_IsNew_AcceptsParameter()
{
var htit = new Htit();
htit.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
Assert.Equal(100, htit.Last.Value);
}
[Fact]
public void Htit_Reset_ClearsState()
{
var htit = new Htit();
htit.Update(new TValue(DateTime.UtcNow, 100));
htit.Update(new TValue(DateTime.UtcNow, 110));
htit.Reset();
Assert.Equal(0, htit.Last.Value);
Assert.False(htit.IsHot);
}
[Fact]
public void Htit_IterativeCorrections_RestoreToOriginalState()
{
var htit = new Htit();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 20 new values (needs > 12 for warmup)
TValue lastInput = default;
for (int i = 0; i < 20; i++)
{
var bar = gbm.Next(isNew: true);
lastInput = new TValue(bar.Time, bar.Close);
htit.Update(lastInput, isNew: true);
}
// Remember state after 20 values
double valueAfterTwenty = htit.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
htit.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 20th input again with isNew=false
TValue finalValue = htit.Update(lastInput, isNew: false);
// Should match the original state after 20 values
Assert.Equal(valueAfterTwenty, finalValue.Value, 1e-9);
}
[Fact]
public void Htit_SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Htit.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan()));
}
[Fact]
public void Htit_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Htit.Calculate(source.AsSpan(), output.AsSpan());
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
[Fact]
public void Htit_AllModes_ProduceSameResult()
{
// Arrange
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Htit.Calculate(series);
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];
Htit.Calculate(spanInput, spanOutput);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Htit();
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Htit(pubSource);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
}
+24 -9
View File
@@ -28,18 +28,18 @@ public class JmaTests
}
[Fact]
public void Jma_Calc_IsNew_AcceptsParameter()
public void Jma_SpanCalc_ValidatesInput()
{
var jma = new Jma(10);
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
jma.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
double value1 = jma.Last.Value;
// Period must be > 0
Assert.Throws<ArgumentOutOfRangeException>(() => Jma.Calculate(source.AsSpan(), output.AsSpan(), 0, 0, 1.0));
Assert.Throws<ArgumentOutOfRangeException>(() => Jma.Calculate(source.AsSpan(), output.AsSpan(), -1, 0, 1.0));
jma.Update(new TValue(DateTime.UtcNow, 200), isNew: true);
double value2 = jma.Last.Value;
// Values should change with new bars
Assert.NotEqual(value1, value2);
// Output must be same length as source
Assert.Throws<ArgumentException>(() => Jma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3, 0, 1.0));
}
[Fact]
@@ -236,4 +236,19 @@ public class JmaTests
Assert.NotEqual(jmaPhase0.Last.Value, jmaPhase100.Last.Value);
Assert.NotEqual(jmaPhase0.Last.Value, jmaPhaseMinus100.Last.Value);
}
[Fact]
public void Jma_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Jma.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
}
+111 -2
View File
@@ -60,6 +60,7 @@ public class KamaTests
// Streaming
var streamingResults = new TSeries();
Assert.True(series.Count > 0);
foreach (var item in series)
{
streamingResults.Add(kamaStreaming.Update(item));
@@ -69,9 +70,9 @@ public class KamaTests
var batchResults = kamaBatch.Update(series);
Assert.Equal(streamingResults.Count, batchResults.Count);
for (int i = 0; i < streamingResults.Count; i++)
foreach (var (stream, batch) in streamingResults.Zip(batchResults))
{
Assert.Equal(streamingResults[i].Value, batchResults[i].Value, 1e-9);
Assert.Equal(stream.Value, batch.Value, 1e-9);
}
}
@@ -168,4 +169,112 @@ public class KamaTests
Assert.Equal(100, kama.Last.Value);
}
[Fact]
public void Kama_Calc_IsNew_AcceptsParameter()
{
var kama = new Kama(10);
kama.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
Assert.Equal(100, kama.Last.Value);
}
[Fact]
public void Kama_IterativeCorrections_RestoreToOriginalState()
{
var kama = new Kama(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 20 new values (enough to fill buffer and stabilize)
TValue lastInput = default;
for (int i = 0; i < 20; i++)
{
var bar = gbm.Next(isNew: true);
lastInput = new TValue(bar.Time, bar.Close);
kama.Update(lastInput, isNew: true);
}
// Remember state
double valueAfter = kama.Last.Value;
// Generate 5 corrections with isNew=false (different values)
for (int i = 0; i < 5; i++)
{
var bar = gbm.Next(isNew: false);
kama.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered last input again with isNew=false
TValue finalValue = kama.Update(lastInput, isNew: false);
// Should match the original state
Assert.Equal(valueAfter, finalValue.Value, 1e-9);
}
[Fact]
public void Kama_SpanCalc_ValidatesInput()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
double[] wrongSizeOutput = new double[3];
Assert.Throws<ArgumentException>(() => Kama.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Kama.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
public void Kama_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Kama.Calculate(source.AsSpan(), output.AsSpan(), 3);
foreach (var val in output)
{
Assert.True(double.IsFinite(val));
}
}
[Fact]
public void Kama_AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Kama.Calculate(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];
Kama.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Kama(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Kama(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
}
+6
View File
@@ -227,6 +227,12 @@ public sealed class Kama : ITValuePublisher
return new TSeries(t, v);
}
public static TSeries Calculate(TSeries source, int period, int fastPeriod = 2, int slowPeriod = 30)
{
var kama = new Kama(period, fastPeriod, slowPeriod);
return kama.Update(source);
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int fastPeriod = 2, int slowPeriod = 30)
{
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
+25
View File
@@ -195,4 +195,29 @@ public class LsmaTests
var result = lsma.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, result.Value);
}
[Fact]
public void IsHot_BecomesTrueWhenBufferFull()
{
int period = 5;
var lsma = new Lsma(period);
for (int i = 0; i < period; i++)
{
Assert.False(lsma.IsHot);
lsma.Update(new TValue(DateTime.UtcNow, i));
}
Assert.True(lsma.IsHot);
}
[Fact]
public void Chainability_Works()
{
var source = new TSeries();
var lsma = new Lsma(source, 10);
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, lsma.Last.Value);
}
}
+79
View File
@@ -99,4 +99,83 @@ public class MamaTests
Assert.Equal(result1[25 + i].Value, result2[i].Value, 6);
}
}
[Fact]
public void IsHot_BecomesTrueAfterWarmup()
{
var mama = new Mama();
// MAMA needs 6 bars to warmup (Index > 6)
for (int i = 0; i < 6; i++)
{
mama.Update(new TValue(DateTime.UtcNow, 100));
Assert.False(mama.IsHot);
}
mama.Update(new TValue(DateTime.UtcNow, 100));
Assert.True(mama.IsHot);
}
[Fact]
public void Reset_ClearsState()
{
var mama = new Mama();
for (int i = 0; i < 10; i++)
{
mama.Update(new TValue(DateTime.UtcNow, 100));
}
Assert.True(mama.IsHot);
mama.Reset();
Assert.False(mama.IsHot);
Assert.True(double.IsNaN(mama.Last.Value));
}
[Fact]
public void Update_BarCorrection_UpdatesCorrectly()
{
var mama = new Mama();
// Warmup
for (int i = 0; i < 10; i++)
{
mama.Update(new TValue(DateTime.UtcNow, 100));
}
// New bar
var result1 = mama.Update(new TValue(DateTime.UtcNow, 110));
// Update same bar with different value
var result2 = mama.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
Assert.NotEqual(result1.Value, result2.Value);
// Verify internal state by adding next bar
var result3 = mama.Update(new TValue(DateTime.UtcNow, 130));
Assert.True(double.IsFinite(result3.Value));
}
[Fact]
public void Calculate_StaticMethod_MatchesObjectInstance()
{
var source = new TSeries();
var gbm = new GBM(startPrice: 100, seed: 42);
for (int i = 0; i < 50; i++)
{
var bar = gbm.Next();
source.Add(bar.C);
}
var mama = new Mama();
var series1 = mama.Update(source);
var series2 = Mama.Calculate(source);
Assert.Equal(series1.Count, series2.Count);
for (int i = 0; i < source.Count; i++)
{
Assert.Equal(series1[i].Value, series2[i].Value, 1e-9);
}
}
}
+11
View File
@@ -61,6 +61,11 @@ public sealed class Mama : ITValuePublisher
}
public void Init()
{
Reset();
}
public void Reset()
{
_state = default;
_state.Mama = double.NaN;
@@ -214,6 +219,12 @@ public sealed class Mama : ITValuePublisher
return new TSeries(t, v);
}
public static TSeries Calculate(TSeries source, double fastLimit = 0.5, double slowLimit = 0.05)
{
var mama = new Mama(fastLimit, slowLimit);
return mama.Update(source);
}
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double fastLimit = 0.5, double slowLimit = 0.05)
{
var mama = new Mama(fastLimit, slowLimit);
+50
View File
@@ -96,4 +96,54 @@ public class MgdiTests
Assert.Throws<ArgumentOutOfRangeException>(() => new Mgdi(14, double.NaN));
Assert.Throws<ArgumentOutOfRangeException>(() => new Mgdi(14, double.PositiveInfinity));
}
[Fact]
public void Reset_ClearsState()
{
var mgdi = new Mgdi(14);
for (int i = 0; i < 20; i++)
{
mgdi.Update(new TValue(DateTime.UtcNow, 100));
}
Assert.True(mgdi.IsHot);
mgdi.Reset();
Assert.False(mgdi.IsHot);
Assert.Equal(0, mgdi.Last.Value);
}
[Fact]
public void Update_BarCorrection_UpdatesCorrectly()
{
var mgdi = new Mgdi(14);
// Warmup
for (int i = 0; i < 20; i++)
{
mgdi.Update(new TValue(DateTime.UtcNow, 100));
}
// New bar
var result1 = mgdi.Update(new TValue(DateTime.UtcNow, 110));
// Update same bar with different value
var result2 = mgdi.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
Assert.NotEqual(result1.Value, result2.Value);
// Verify internal state by adding next bar
var result3 = mgdi.Update(new TValue(DateTime.UtcNow, 130));
Assert.True(double.IsFinite(result3.Value));
}
[Fact]
public void Chainability_Works()
{
var source = new TSeries();
var mgdi = new Mgdi(source, 14);
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, mgdi.Last.Value);
}
}
+10
View File
@@ -202,4 +202,14 @@ public class RmaTests
// Result should be finite (not NaN)
Assert.True(double.IsFinite(resultAfterNaN.Value));
}
[Fact]
public void Chainability_Works()
{
var source = new TSeries();
var rma = new Rma(source, 10);
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, rma.Last.Value, 1e-9);
}
}
+10
View File
@@ -506,4 +506,14 @@ public class SmaTests
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
[Fact]
public void Chainability_Works()
{
var source = new TSeries();
var sma = new Sma(source, 10);
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, sma.Last.Value);
}
}
+46
View File
@@ -140,4 +140,50 @@ public class SuperTests
Assert.Throws<ArgumentOutOfRangeException>(() => new Super(10, 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new Super(10, -1.0));
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var super = new Super(10, 3.0);
var streamingResults = new List<double>();
for (int i = 0; i < bars.Count; i++)
{
streamingResults.Add(super.Update(bars[i]).Value);
}
var staticResults = Super.Calculate(bars, 10, 3.0);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
if (double.IsNaN(streamingResults[i]))
{
Assert.True(double.IsNaN(staticResults.Values[i]));
}
else
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
}
[Fact]
public void Chainability_Works()
{
var super = new Super(10, 3.0);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Test TBarSeries chain
var result = super.Update(bars);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TBar chain (returns TValue)
var result2 = super.Update(bars[0]);
Assert.IsType<TValue>(result2);
}
}
+6
View File
@@ -217,4 +217,10 @@ public sealed class Super : ITValuePublisher
return new TSeries(t, v);
}
public static TSeries Calculate(TBarSeries source, int period = 10, double multiplier = 3.0)
{
var indicator = new Super(period, multiplier);
return indicator.Update(source);
}
}
+162 -120
View File
@@ -1,131 +1,173 @@
using Xunit;
using System;
using System.Collections.Generic;
using Xunit;
namespace QuanTAlib.Tests;
namespace QuanTAlib;
public class T3Tests
{
[Fact]
public void T3_Constructor_Period_ValidatesInput()
public void BasicCalculation_DoesNotCrash()
{
var t3 = new T3(5, 0.7);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
t3.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(t3.Last.Value));
}
[Fact]
public void IsNew_Consistency()
{
var t3 = new T3(5, 0.7);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
t3.Update(new TValue(bars[i].Time, bars[i].Close));
}
// Update with 100th point (isNew=true)
t3.Update(new TValue(bars[99].Time, bars[99].Close), true);
// Update with modified 100th point (isNew=false)
var val2 = t3.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), false);
// Create new instance and feed up to modified
var t3_2 = new T3(5, 0.7);
for (int i = 0; i < 99; i++)
{
t3_2.Update(new TValue(bars[i].Time, bars[i].Close));
}
var val3 = t3_2.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var t3 = new T3(5, 0.7);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
t3.Update(new TValue(bars[i].Time, bars[i].Close));
}
t3.Reset();
Assert.Equal(0, t3.Last.Value);
Assert.False(t3.IsHot);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
t3.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(t3.Last.Value));
}
[Fact]
public void TSeries_Update_Matches_Streaming()
{
var t3 = new T3(5, 0.7);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(t3.Update(series[i]).Value);
}
var t3_2 = new T3(5, 0.7);
var seriesResults = t3_2.Update(series);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var t3 = new T3(5, 0.7);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(t3.Update(series[i]).Value);
}
var staticResults = T3.Calculate(series, 5, 0.7);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculateSpan_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var t3 = new T3(5, 0.7);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(t3.Update(series[i]).Value);
}
var spanResults = new double[series.Count];
T3.Calculate(series.Values, spanResults, 5, 0.7);
for (int i = 0; i < spanResults.Length; i++)
{
Assert.Equal(streamingResults[i], spanResults[i], 1e-9);
}
}
[Fact]
public void Chainability_Works()
{
var t3 = new T3(5, 0.7);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// Test TSeries chain
var result = t3.Update(series);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TValue chain
var result2 = t3.Update(series[0]);
Assert.IsType<TValue>(result2);
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new T3(0));
Assert.Throws<ArgumentException>(() => new T3(-1));
var t3 = new T3(10);
Assert.NotNull(t3);
}
[Fact]
public void T3_ConstantInput_ConvergesToInput()
{
var t3 = new T3(5, 0.7);
double input = 100.0;
// Feed enough values for T3 to converge (it has 6 cascaded EMAs)
for(int i = 0; i < 100; i++)
{
t3.Update(new TValue(DateTime.UtcNow, input));
}
Assert.Equal(input, t3.Last.Value, 1e-9);
}
[Fact]
public void T3_Parameters_AffectResult()
{
// Different volume factors should produce different results for changing data
var t3_low_v = new T3(10, 0.1);
var t3_high_v = new T3(10, 0.9);
var series = new TSeries();
series.Add(DateTime.UtcNow, 100);
series.Add(DateTime.UtcNow.AddMinutes(1), 110);
series.Add(DateTime.UtcNow.AddMinutes(2), 120);
t3_low_v.Update(series);
t3_high_v.Update(series);
Assert.NotEqual(t3_low_v.Last.Value, t3_high_v.Last.Value);
}
[Fact]
public void T3_Reset_ResetsState()
{
var t3 = new T3(10);
t3.Update(new TValue(DateTime.UtcNow, 100));
t3.Update(new TValue(DateTime.UtcNow, 110));
Assert.True(t3.IsHot);
Assert.NotEqual(0, t3.Last.Value);
t3.Reset();
Assert.False(t3.IsHot);
Assert.Equal(0, t3.Last.Value);
// Should accept new data as if fresh
t3.Update(new TValue(DateTime.UtcNow, 50));
Assert.Equal(50, t3.Last.Value, 1e-9); // First value logic: output = input
}
[Fact]
public void T3_Eventing_Works()
{
var source = new TSeries();
var t3 = new T3(source, 10);
double lastVal = 0;
t3.Pub += (v) => lastVal = v.Value;
source.Add(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, lastVal, 1e-9);
source.Add(new TValue(DateTime.UtcNow, 110));
Assert.NotEqual(100, lastVal);
Assert.NotEqual(0, lastVal);
}
[Fact]
public void T3_SpanTests()
{
var series = new TSeries();
int count = 100;
for(int i=0; i<count; i++)
series.Add(DateTime.UtcNow.AddMinutes(i), 100 + i);
var t3 = new T3(10);
var resSeries = t3.Update(series);
var resSpan = new double[count];
// Correctly use Span.CopyTo
T3.Calculate(series, 10).Values.CopyTo(resSpan.AsSpan());
// Check last values match
Assert.Equal(resSeries.Last.Value, resSpan[count-1], 1e-9);
}
[Fact]
public void T3_BarCorrection_WithNaN_RestoresPreviousValidValue()
{
var t3 = new T3(10);
var time = DateTime.UtcNow;
// Step 1: Update with valid value
t3.Update(new TValue(time, 100), isNew: true);
// Step 2: Update with another valid value
t3.Update(new TValue(time.AddMinutes(1), 200), isNew: true);
double valAfter200 = t3.Last.Value;
// Step 3: Correct with NaN (should use 100)
t3.Update(new TValue(time.AddMinutes(1), double.NaN), isNew: false);
double valAfterNaN = t3.Last.Value;
// Step 4: Correct with 100 (should match NaN result)
t3.Update(new TValue(time.AddMinutes(1), 100), isNew: false);
double valAfter100 = t3.Last.Value;
Assert.NotEqual(valAfter200, valAfterNaN); // Should not be the same as 200
Assert.Equal(valAfter100, valAfterNaN, 1e-9); // Should be the same as using 100
}
}
+165 -300
View File
@@ -1,310 +1,175 @@
namespace QuanTAlib.Tests;
using System;
using System.Collections.Generic;
using Xunit;
namespace QuanTAlib;
#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes
public class TemaTests
{
[Fact]
public void Tema_Constructor_Period_ValidatesInput()
public void BasicCalculation_DoesNotCrash()
{
var tema = new Tema(10);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
tema.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(tema.Last.Value));
}
[Fact]
public void IsNew_Consistency()
{
var tema = new Tema(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
tema.Update(new TValue(bars[i].Time, bars[i].Close));
}
// Update with 100th point (isNew=true)
tema.Update(new TValue(bars[99].Time, bars[99].Close), true);
// Update with modified 100th point (isNew=false)
var val2 = tema.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), false);
// Create new instance and feed up to modified
var tema2 = new Tema(10);
for (int i = 0; i < 99; i++)
{
tema2.Update(new TValue(bars[i].Time, bars[i].Close));
}
var val3 = tema2.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var tema = new Tema(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
tema.Update(new TValue(bars[i].Time, bars[i].Close));
}
tema.Reset();
Assert.Equal(0, tema.Last.Value);
Assert.False(tema.IsHot);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
tema.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(tema.Last.Value));
}
[Fact]
public void TSeries_Update_Matches_Streaming()
{
var tema = new Tema(10);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(tema.Update(series[i]).Value);
}
var tema2 = new Tema(10);
var seriesResults = tema2.Update(series);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var tema = new Tema(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(tema.Update(series[i]).Value);
}
var staticResults = Tema.Calculate(series, 10);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculateSpan_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var tema = new Tema(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(tema.Update(series[i]).Value);
}
var spanResults = new double[series.Count];
Tema.Calculate(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
Assert.Equal(streamingResults[i], spanResults[i], 1e-9);
}
}
[Fact]
public void Chainability_Works()
{
var tema = new Tema(10);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// Test TSeries chain
var result = tema.Update(series);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TValue chain
var result2 = tema.Update(series[0]);
Assert.IsType<TValue>(result2);
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Tema(0));
Assert.Throws<ArgumentException>(() => new Tema(-1));
var tema = new Tema(10);
Assert.NotNull(tema);
}
[Fact]
public void Tema_Constructor_Alpha_ValidatesInput()
{
Assert.Throws<ArgumentException>(() => new Tema(0.0));
Assert.Throws<ArgumentException>(() => new Tema(-0.1));
Assert.Throws<ArgumentException>(() => new Tema(1.1));
var tema = new Tema(0.5);
Assert.NotNull(tema);
}
[Fact]
public void Tema_Calc_ReturnsValue()
{
var tema = new Tema(10);
Assert.Equal(0, tema.Last.Value);
TValue result = tema.Update(new TValue(DateTime.UtcNow, 100));
Assert.True(result.Value > 0);
Assert.Equal(result.Value, tema.Last.Value);
}
[Fact]
public void Tema_Calc_IsNew_AcceptsParameter()
{
var tema = new Tema(10);
tema.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
double value1 = tema.Last.Value;
tema.Update(new TValue(DateTime.UtcNow, 105), isNew: true);
double value2 = tema.Last.Value;
// Values should change with new bars
Assert.NotEqual(value1, value2);
}
[Fact]
public void Tema_Calc_IsNew_False_UpdatesValue()
{
var tema = new Tema(10);
tema.Update(new TValue(DateTime.UtcNow, 100));
tema.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
double beforeUpdate = tema.Last.Value;
tema.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
double afterUpdate = tema.Last.Value;
// Update should change the value
Assert.NotEqual(beforeUpdate, afterUpdate);
}
[Fact]
public void Tema_Reset_ClearsState()
{
var tema = new Tema(10);
tema.Update(new TValue(DateTime.UtcNow, 100));
tema.Update(new TValue(DateTime.UtcNow, 105));
double valueBefore = tema.Last.Value;
tema.Reset();
Assert.Equal(0, tema.Last.Value);
// After reset, should accept new values
tema.Update(new TValue(DateTime.UtcNow, 50));
Assert.NotEqual(0, tema.Last.Value);
Assert.NotEqual(valueBefore, tema.Last.Value);
}
[Fact]
public void Tema_Properties_Accessible()
{
var tema = new Tema(10);
Assert.Equal(0, tema.Last.Value);
Assert.False(tema.IsHot);
tema.Update(new TValue(DateTime.UtcNow, 100));
Assert.NotEqual(0, tema.Last.Value);
}
[Fact]
public void Tema_IsHot_BecomesTrueAfterWarmup()
{
var tema = new Tema(10);
// Initially IsHot should be false
Assert.False(tema.IsHot);
// TEMA needs more warmup than EMA due to triple smoothing
int steps = 0;
while (!tema.IsHot && steps < 1000)
{
tema.Update(new TValue(DateTime.UtcNow, 100));
steps++;
}
Assert.True(tema.IsHot);
Assert.True(steps > 0);
}
[Fact]
public void Tema_PeriodEquivalence_BothConstructorsWork()
{
int period = 20;
double alpha = 2.0 / (period + 1);
var temaPeriod = new Tema(period);
var temaAlpha = new Tema(alpha);
// Both should accept Calc calls and produce same result
TValue result1 = temaPeriod.Update(new TValue(DateTime.UtcNow, 100));
TValue result2 = temaAlpha.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(result1.Value, result2.Value, 1e-10);
}
[Fact]
public void Tema_IterativeCorrections_RestoreToOriginalState()
{
var tema = new Tema(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
tema.Update(tenthInput, isNew: true);
}
// Remember TEMA state after 10 values
double temaAfterTen = tema.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
tema.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalTema = tema.Update(tenthInput, isNew: false);
// TEMA should match the original state after 10 values
Assert.Equal(temaAfterTen, finalTema.Value, 1e-10);
}
[Fact]
public void Tema_BatchCalc_MatchesIterativeCalc()
{
var temaIterative = new Tema(10);
var temaBatch = new Tema(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Generate data
var series = new TSeries();
for (int i = 0; i < 100; i++)
{
var bar = gbm.Next(isNew: true);
series.Add(bar.Time, bar.Close);
}
Assert.True(series.Count > 0);
// Calculate iteratively
var iterativeResults = new TSeries();
foreach (var item in series)
{
iterativeResults.Add(temaIterative.Update(item));
}
// Calculate batch
var batchResults = temaBatch.Update(series);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
{
Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10);
Assert.Equal(iterativeResults[i].Time, batchResults[i].Time);
}
}
[Fact]
public void Tema_NaN_Input_UsesLastValidValue()
{
var tema = new Tema(10);
// Feed some valid values
tema.Update(new TValue(DateTime.UtcNow, 100));
tema.Update(new TValue(DateTime.UtcNow, 110));
// Feed NaN - should use last valid value (110)
var resultAfterNaN = tema.Update(new TValue(DateTime.UtcNow, double.NaN));
// Result should be finite (not NaN)
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void Tema_SpanCalc_MatchesTSeriesCalc()
{
var series = new TSeries();
double[] source = new double[100];
double[] output = new double[100];
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42);
for (int i = 0; i < 100; i++)
{
var bar = gbm.Next(isNew: true);
source[i] = bar.Close;
series.Add(bar.Time, bar.Close);
}
// Calculate with TSeries API
var tseriesResult = Tema.Calculate(series, 10);
// Calculate with Span API
Tema.Calculate(source.AsSpan(), output.AsSpan(), 10);
// Compare results
for (int i = 0; i < 100; i++)
{
Assert.Equal(tseriesResult[i].Value, output[i], 1e-9);
}
}
[Fact]
public void Tema_SpanCalc_ZeroAllocation()
{
double[] source = new double[10000];
double[] output = new double[10000];
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
for (int i = 0; i < source.Length; i++)
source[i] = gbm.Next().Close;
// Warm up
Tema.Calculate(source.AsSpan(), output.AsSpan(), 100);
// This test verifies the method runs without throwing
Assert.True(double.IsFinite(output[^1]));
}
[Fact]
public void Tema_AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Tema.Calculate(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];
Tema.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Tema(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Tema(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
Assert.Throws<ArgumentException>(() => new Tema(1.0));
}
}
+155 -27
View File
@@ -1,45 +1,173 @@
using System;
using System.Collections.Generic;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests;
namespace QuanTAlib;
public class TrimaTests
{
[Fact]
public void StateRestoration_IsCorrect()
public void BasicCalculation_DoesNotCrash()
{
// Arrange
int period = 4;
var trimaStreaming = new Trima(period);
var trimaBatch = new Trima(period);
var trima = new Trima(10);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
trima.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(trima.Last.Value));
}
[Fact]
public void IsNew_Consistency()
{
var trima = new Trima(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
trima.Update(new TValue(bars[i].Time, bars[i].Close));
}
// Update with 100th point (isNew=true)
trima.Update(new TValue(bars[99].Time, bars[99].Close), true);
// Update with modified 100th point (isNew=false)
var val2 = trima.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), false);
// Create new instance and feed up to modified
var trima2 = new Trima(10);
for (int i = 0; i < 99; i++)
{
trima2.Update(new TValue(bars[i].Time, bars[i].Close));
}
var val3 = trima2.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var trima = new Trima(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
trima.Update(new TValue(bars[i].Time, bars[i].Close));
}
trima.Reset();
Assert.Equal(0, trima.Last.Value);
Assert.False(trima.IsHot);
// Generate enough data to fill the buffers and have some history
int count = 50;
var data = new TSeries();
for (int i = 0; i < count; i++)
// Feed again
for (int i = 0; i < bars.Count; i++)
{
data.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + i));
trima.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(trima.Last.Value));
}
[Fact]
public void TSeries_Update_Matches_Streaming()
{
var trima = new Trima(10);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(trima.Update(series[i]).Value);
}
// Act
// 1. Feed streaming instance
Assert.True(data.Count > 0);
for (int i = 0; i < data.Count; i++)
var trima2 = new Trima(10);
var seriesResults = trima2.Update(series);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
trimaStreaming.Update(data[i]);
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var trima = new Trima(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(trima.Update(series[i]).Value);
}
var staticResults = Trima.Calculate(series, 10);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
// 2. Feed batch instance with all but the last point first, then the last point
// Actually, the Update(TSeries) method is supposed to handle the whole series and leave the state ready for the NEXT point.
// So let's feed the whole series to batch instance.
trimaBatch.Update(data);
[Fact]
public void StaticCalculateSpan_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var trima = new Trima(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(trima.Update(series[i]).Value);
}
var spanResults = new double[series.Count];
Trima.Calculate(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
Assert.Equal(streamingResults[i], spanResults[i], 1e-9);
}
}
// 3. Now feed one NEW point to both
var newPoint = new TValue(DateTime.UtcNow.AddMinutes(count), 200);
var resultStreaming = trimaStreaming.Update(newPoint);
var resultBatch = trimaBatch.Update(newPoint);
[Fact]
public void Chainability_Works()
{
var trima = new Trima(10);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// Test TSeries chain
var result = trima.Update(series);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TValue chain
var result2 = trima.Update(series[0]);
Assert.IsType<TValue>(result2);
}
// Assert
Assert.Equal(resultStreaming.Value, resultBatch.Value, precision: 9);
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Trima(0));
Assert.Throws<ArgumentException>(() => new Trima(-1));
}
}
+141 -80
View File
@@ -1,111 +1,172 @@
using QuanTAlib;
using System;
using System.Collections.Generic;
using Xunit;
namespace Trends;
namespace QuanTAlib;
public class VidyaTests
{
[Fact]
public void BasicCalculation()
public void BasicCalculation_DoesNotCrash()
{
// Test with a small dataset
// Period = 2
// Alpha = 2 / (2 + 1) = 0.666...
var vidya = new Vidya(2);
// Bar 1: Price 100
// Init: PrevClose=100, LastVidya=100, Ups=[0,0], Downs=[0,0]
// Output: 100
var v1 = vidya.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, v1.Value);
// Bar 2: Price 110
// Change = 110 - 100 = 10
// Up=10, Down=0
// Ups=[10,0], Downs=[0,0]
// SumUp=10, SumDown=0, Sum=10
// VI = |10-0|/10 = 1
// DynAlpha = 0.666 * 1 = 0.666
// Vidya = 0.666 * 110 + 0.333 * 100 = 73.33 + 33.33 = 106.66
var v2 = vidya.Update(new TValue(DateTime.UtcNow, 110));
Assert.Equal(106.66666666666667, v2.Value, 5);
// Bar 3: Price 105
// Change = 105 - 110 = -5
// Up=0, Down=5
// Ups=[0,10], Downs=[5,0] (Circular buffer logic)
// SumUp=10, SumDown=5, Sum=15
// VI = |10-5|/15 = 5/15 = 0.333
// DynAlpha = 0.666 * 0.333 = 0.222
// Vidya = 0.222 * 105 + 0.777 * 106.66 = 23.33 + 82.96 = 106.29
var v3 = vidya.Update(new TValue(DateTime.UtcNow, 105));
Assert.Equal(106.29629629629629, v3.Value, 5);
var vidya = new Vidya(10);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
vidya.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(vidya.Last.Value));
}
[Fact]
public void IsNewConsistency()
public void IsNew_Consistency()
{
var vidya = new Vidya(5);
var inputs = new double[] { 100, 105, 102, 108, 110, 105 };
// Feed normally
var expected = new List<double>();
foreach (var input in inputs)
var vidya = new Vidya(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
expected.Add(vidya.Update(new TValue(DateTime.UtcNow, input)).Value);
vidya.Update(new TValue(bars[i].Time, bars[i].Close));
}
// Feed with updates
// Update with 100th point (isNew=true)
vidya.Update(new TValue(bars[99].Time, bars[99].Close), true);
// Update with modified 100th point (isNew=false)
var val2 = vidya.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), false);
// Create new instance and feed up to modified
var vidya2 = new Vidya(10);
for (int i = 0; i < 99; i++)
{
vidya2.Update(new TValue(bars[i].Time, bars[i].Close));
}
var val3 = vidya2.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var vidya = new Vidya(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
vidya.Update(new TValue(bars[i].Time, bars[i].Close));
}
vidya.Reset();
for (int i = 0; i < inputs.Length; i++)
Assert.Equal(0, vidya.Last.Value);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
// Update with a temporary value first
vidya.Update(new TValue(DateTime.UtcNow, inputs[i] + 1), true);
// Correct it
var corrected = vidya.Update(new TValue(DateTime.UtcNow, inputs[i]), false);
Assert.Equal(expected[i], corrected.Value, 1e-9);
vidya.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(vidya.Last.Value));
}
[Fact]
public void TSeries_Update_Matches_Streaming()
{
var vidya = new Vidya(10);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(vidya.Update(series[i]).Value);
}
var vidya2 = new Vidya(10);
var seriesResults = vidya2.Update(series);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var vidya = new Vidya(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(vidya.Update(series[i]).Value);
}
var staticResults = Vidya.Calculate(series, 10);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticVsInstance()
public void StaticCalculateSpan_Matches_Streaming()
{
var vidya = new Vidya(5);
var inputs = new double[] { 100, 105, 102, 108, 110, 105, 100, 95, 98, 102 };
var tSeries = new TSeries();
tSeries.Add(inputs);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var instanceResult = vidya.Update(tSeries);
var staticResult = new double[inputs.Length];
Vidya.Calculate(inputs, staticResult, 5);
for (int i = 0; i < inputs.Length; i++)
var vidya = new Vidya(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
Assert.Equal(instanceResult.Values[i], staticResult[i], 1e-9);
streamingResults.Add(vidya.Update(series[i]).Value);
}
var spanResults = new double[series.Count];
Vidya.Calculate(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
Assert.Equal(streamingResults[i], spanResults[i], 1e-9);
}
}
[Fact]
public void EdgeCases()
public void Chainability_Works()
{
var vidya = new Vidya(5);
var vidya = new Vidya(10);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// Empty
Assert.Empty(vidya.Update(new TSeries()));
// Test TSeries chain
var result = vidya.Update(series);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// NaN handling
vidya.Reset();
vidya.Update(new TValue(DateTime.UtcNow, 100));
var v2 = vidya.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.Equal(100, v2.Value); // Should hold previous value
// Period 1
var vidya1 = new Vidya(1);
var v = vidya1.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100, v.Value);
// Test TValue chain
var result2 = vidya.Update(series[0]);
Assert.IsType<TValue>(result2);
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Vidya(0));
Assert.Throws<ArgumentException>(() => new Vidya(-1));
}
}
+7
View File
@@ -166,6 +166,13 @@ public sealed class Vidya : ITValuePublisher
return new TSeries(t, v);
}
public static TSeries Calculate(TSeries source, int period)
{
var vidya = new Vidya(period);
return vidya.Update(source);
}
/// <summary>
/// Calculates VIDYA for the entire series.
/// </summary>
+164 -563
View File
@@ -1,572 +1,173 @@
namespace QuanTAlib.Tests;
using System;
using System.Collections.Generic;
using Xunit;
namespace QuanTAlib;
#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes
public class WmaTests
{
[Fact]
public void Wma_Constructor_ValidatesInput()
public void BasicCalculation_DoesNotCrash()
{
var wma = new Wma(10);
var gbm = new GBM();
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
wma.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(wma.Last.Value));
}
[Fact]
public void IsNew_Consistency()
{
var wma = new Wma(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed first 99
for (int i = 0; i < 99; i++)
{
wma.Update(new TValue(bars[i].Time, bars[i].Close));
}
// Update with 100th point (isNew=true)
wma.Update(new TValue(bars[99].Time, bars[99].Close), true);
// Update with modified 100th point (isNew=false)
var val2 = wma.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), false);
// Create new instance and feed up to modified
var wma2 = new Wma(10);
for (int i = 0; i < 99; i++)
{
wma2.Update(new TValue(bars[i].Time, bars[i].Close));
}
var val3 = wma2.Update(new TValue(bars[99].Time, bars[99].Close + 1.0), true);
Assert.Equal(val3.Value, val2.Value, 1e-9);
}
[Fact]
public void Reset_Works()
{
var wma = new Wma(10);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < bars.Count; i++)
{
wma.Update(new TValue(bars[i].Time, bars[i].Close));
}
wma.Reset();
Assert.Equal(0, wma.Last.Value);
Assert.False(wma.IsHot);
// Feed again
for (int i = 0; i < bars.Count; i++)
{
wma.Update(new TValue(bars[i].Time, bars[i].Close));
}
Assert.True(double.IsFinite(wma.Last.Value));
}
[Fact]
public void TSeries_Update_Matches_Streaming()
{
var wma = new Wma(10);
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(wma.Update(series[i]).Value);
}
var wma2 = new Wma(10);
var seriesResults = wma2.Update(series);
Assert.Equal(streamingResults.Count, seriesResults.Count);
for (int i = 0; i < seriesResults.Count; i++)
{
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculate_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var wma = new Wma(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(wma.Update(series[i]).Value);
}
var staticResults = Wma.Calculate(series, 10);
Assert.Equal(streamingResults.Count, staticResults.Count);
for (int i = 0; i < staticResults.Count; i++)
{
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
}
}
[Fact]
public void StaticCalculateSpan_Matches_Streaming()
{
var gbm = new GBM();
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
var wma = new Wma(10);
var streamingResults = new List<double>();
for (int i = 0; i < series.Count; i++)
{
streamingResults.Add(wma.Update(series[i]).Value);
}
var spanResults = new double[series.Count];
Wma.Calculate(series.Values, spanResults, 10);
for (int i = 0; i < spanResults.Length; i++)
{
Assert.Equal(streamingResults[i], spanResults[i], 1e-9);
}
}
[Fact]
public void Chainability_Works()
{
var wma = new Wma(10);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// Test TSeries chain
var result = wma.Update(series);
Assert.NotNull(result);
Assert.IsType<TSeries>(result);
// Test TValue chain
var result2 = wma.Update(series[0]);
Assert.IsType<TValue>(result2);
}
[Fact]
public void Constructor_InvalidParameters_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Wma(0));
Assert.Throws<ArgumentException>(() => new Wma(-1));
var wma = new Wma(10);
Assert.NotNull(wma);
}
[Fact]
public void Wma_Calc_ReturnsValue()
{
var wma = new Wma(10);
Assert.Equal(0, wma.Last.Value);
TValue result = wma.Update(new TValue(DateTime.UtcNow, 100));
Assert.True(result.Value > 0);
Assert.Equal(result.Value, wma.Last.Value);
}
[Fact]
public void Wma_FirstValue_ReturnsItself()
{
var wma = new Wma(10);
TValue result = wma.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100.0, result.Value, 1e-10);
}
[Fact]
public void Wma_Calc_IsNew_AcceptsParameter()
{
var wma = new Wma(10);
wma.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
double value1 = wma.Last.Value;
wma.Update(new TValue(DateTime.UtcNow, 200), isNew: true);
double value2 = wma.Last.Value;
// Values should change with new bars
Assert.NotEqual(value1, value2);
}
[Fact]
public void Wma_Calc_IsNew_False_UpdatesValue()
{
var wma = new Wma(10);
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
double beforeUpdate = wma.Last.Value;
wma.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
double afterUpdate = wma.Last.Value;
// Update should change the value
Assert.NotEqual(beforeUpdate, afterUpdate);
}
[Fact]
public void Wma_Reset_ClearsState()
{
var wma = new Wma(10);
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, 105));
double valueBefore = wma.Last.Value;
wma.Reset();
Assert.Equal(0, wma.Last.Value);
// After reset, should accept new values
wma.Update(new TValue(DateTime.UtcNow, 50));
Assert.NotEqual(0, wma.Last.Value);
Assert.NotEqual(valueBefore, wma.Last.Value);
}
[Fact]
public void Wma_Properties_Accessible()
{
var wma = new Wma(10);
Assert.Equal(0, wma.Last.Value);
Assert.False(wma.IsHot);
wma.Update(new TValue(DateTime.UtcNow, 100));
Assert.NotEqual(0, wma.Last.Value);
}
[Fact]
public void Wma_IsHot_BecomesTrueWhenBufferFull()
{
var wma = new Wma(5);
Assert.False(wma.IsHot);
for (int i = 1; i <= 4; i++)
{
wma.Update(new TValue(DateTime.UtcNow, i * 10));
Assert.False(wma.IsHot);
}
wma.Update(new TValue(DateTime.UtcNow, 50));
Assert.True(wma.IsHot);
}
[Fact]
public void Wma_CalculatesCorrectWeightedAverage()
{
var wma = new Wma(5);
wma.Update(new TValue(DateTime.UtcNow, 10));
wma.Update(new TValue(DateTime.UtcNow, 20));
wma.Update(new TValue(DateTime.UtcNow, 30));
wma.Update(new TValue(DateTime.UtcNow, 40));
wma.Update(new TValue(DateTime.UtcNow, 50));
// WMA(5) of 10,20,30,40,50 = (1*10 + 2*20 + 3*30 + 4*40 + 5*50) / 15
// = (10 + 40 + 90 + 160 + 250) / 15 = 550 / 15 = 36.666...
Assert.Equal(550.0 / 15.0, wma.Last.Value, 1e-10);
}
[Fact]
public void Wma_SlidingWindow_Works()
{
var wma = new Wma(3);
wma.Update(new TValue(DateTime.UtcNow, 10));
wma.Update(new TValue(DateTime.UtcNow, 20));
wma.Update(new TValue(DateTime.UtcNow, 30));
// WMA(3) of 10,20,30 = (1*10 + 2*20 + 3*30) / 6 = (10 + 40 + 90) / 6 = 140/6 = 23.333...
Assert.Equal(140.0 / 6.0, wma.Last.Value, 1e-10);
wma.Update(new TValue(DateTime.UtcNow, 40));
// WMA(3) of 20,30,40 = (1*20 + 2*30 + 3*40) / 6 = (20 + 60 + 120) / 6 = 200/6 = 33.333...
Assert.Equal(200.0 / 6.0, wma.Last.Value, 1e-10);
wma.Update(new TValue(DateTime.UtcNow, 50));
// WMA(3) of 30,40,50 = (1*30 + 2*40 + 3*50) / 6 = (30 + 80 + 150) / 6 = 260/6 = 43.333...
Assert.Equal(260.0 / 6.0, wma.Last.Value, 1e-10);
}
[Fact]
public void Wma_IterativeCorrections_RestoreToOriginalState()
{
var wma = new Wma(5);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Feed 10 new values
TValue tenthInput = default;
for (int i = 0; i < 10; i++)
{
var bar = gbm.Next(isNew: true);
tenthInput = new TValue(bar.Time, bar.Close);
wma.Update(tenthInput, isNew: true);
}
// Remember WMA state after 10 values
double wmaAfterTen = wma.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 0; i < 9; i++)
{
var bar = gbm.Next(isNew: false);
wma.Update(new TValue(bar.Time, bar.Close), isNew: false);
}
// Feed the remembered 10th input again with isNew=false
TValue finalWma = wma.Update(tenthInput, isNew: false);
// WMA should match the original state after 10 values
Assert.Equal(wmaAfterTen, finalWma.Value, 1e-10);
}
[Fact]
public void Wma_BatchCalc_MatchesIterativeCalc()
{
var wmaIterative = new Wma(10);
var wmaBatch = new Wma(10);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
// Generate data
var series = new TSeries();
for (int i = 0; i < 100; i++)
{
var bar = gbm.Next(isNew: true);
series.Add(bar.Time, bar.Close);
}
Assert.True(series.Count > 0);
// Calculate iteratively
var iterativeResults = new TSeries();
foreach (var item in series)
{
iterativeResults.Add(wmaIterative.Update(item));
}
// Calculate batch
var batchResults = wmaBatch.Update(series);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
{
Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10);
Assert.Equal(iterativeResults[i].Time, batchResults[i].Time);
}
}
[Fact]
public void Wma_Result_ImplicitConversionToDouble()
{
var wma = new Wma(10);
wma.Update(new TValue(DateTime.UtcNow, 100));
// This should compile and work because TValue has implicit conversion to double
double result = wma.Last.Value;
Assert.Equal(100.0, result, 1e-10);
}
[Fact]
public void Wma_NaN_Input_UsesLastValidValue()
{
var wma = new Wma(5);
// Feed some valid values
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, 110));
// Feed NaN - should use last valid value (110)
var resultAfterNaN = wma.Update(new TValue(DateTime.UtcNow, double.NaN));
// Result should be finite (not NaN)
Assert.True(double.IsFinite(resultAfterNaN.Value));
Assert.NotEqual(0, resultAfterNaN.Value);
}
[Fact]
public void Wma_Infinity_Input_UsesLastValidValue()
{
var wma = new Wma(5);
// Feed some valid values
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, 110));
// Feed positive infinity - should use last valid value
var resultAfterPosInf = wma.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
Assert.True(double.IsFinite(resultAfterPosInf.Value));
// Feed negative infinity - should use last valid value
var resultAfterNegInf = wma.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
Assert.True(double.IsFinite(resultAfterNegInf.Value));
}
[Fact]
public void Wma_MultipleNaN_ContinuesWithLastValid()
{
var wma = new Wma(5);
// Feed valid values
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, 110));
wma.Update(new TValue(DateTime.UtcNow, 120));
// Feed multiple NaN values
var r1 = wma.Update(new TValue(DateTime.UtcNow, double.NaN));
var r2 = wma.Update(new TValue(DateTime.UtcNow, double.NaN));
var r3 = wma.Update(new TValue(DateTime.UtcNow, double.NaN));
// All results should be finite
Assert.True(double.IsFinite(r1.Value));
Assert.True(double.IsFinite(r2.Value));
Assert.True(double.IsFinite(r3.Value));
}
[Fact]
public void Wma_BatchCalc_HandlesNaN()
{
var wma = new Wma(5);
// Create series with NaN values interspersed
var series = new TSeries();
series.Add(DateTime.UtcNow.Ticks, 100);
series.Add(DateTime.UtcNow.Ticks + 1, 110);
series.Add(DateTime.UtcNow.Ticks + 2, double.NaN);
series.Add(DateTime.UtcNow.Ticks + 3, 120);
series.Add(DateTime.UtcNow.Ticks + 4, double.PositiveInfinity);
series.Add(DateTime.UtcNow.Ticks + 5, 130);
var results = wma.Update(series);
// All results should be finite
foreach (var result in results)
{
Assert.True(double.IsFinite(result.Value), $"Expected finite value but got {result.Value}");
}
}
[Fact]
public void Wma_Reset_ClearsLastValidValue()
{
var wma = new Wma(5);
// Feed values including NaN
wma.Update(new TValue(DateTime.UtcNow, 100));
wma.Update(new TValue(DateTime.UtcNow, double.NaN));
// Reset
wma.Reset();
// After reset, first valid value should establish new baseline
var result = wma.Update(new TValue(DateTime.UtcNow, 50));
Assert.Equal(50.0, result.Value, 1e-10);
}
[Fact]
public void Wma_StaticCalculate_Works()
{
var series = new TSeries();
series.Add(DateTime.UtcNow.Ticks, 10);
series.Add(DateTime.UtcNow.Ticks + 1, 20);
series.Add(DateTime.UtcNow.Ticks + 2, 30);
series.Add(DateTime.UtcNow.Ticks + 3, 40);
series.Add(DateTime.UtcNow.Ticks + 4, 50);
var results = Wma.Calculate(series, 3);
Assert.Equal(5, results.Count);
// WMA(3) for last 3 values [30,40,50]: (1*30 + 2*40 + 3*50) / 6 = 260/6 = 43.333...
Assert.Equal(260.0 / 6.0, results.Last.Value, 1e-10);
}
[Fact]
public void Wma_Period1_ReturnsInputValues()
{
var wma = new Wma(1);
Assert.Equal(100.0, wma.Update(new TValue(DateTime.UtcNow, 100)).Value, 1e-10);
Assert.Equal(200.0, wma.Update(new TValue(DateTime.UtcNow, 200)).Value, 1e-10);
Assert.Equal(150.0, wma.Update(new TValue(DateTime.UtcNow, 150)).Value, 1e-10);
}
[Fact]
public void Wma_MoreWeightOnRecentValues()
{
var wma = new Wma(3);
var sma = new Sma(3);
// Feed same values to both
wma.Update(new TValue(DateTime.UtcNow, 10));
sma.Update(new TValue(DateTime.UtcNow, 10));
wma.Update(new TValue(DateTime.UtcNow, 20));
sma.Update(new TValue(DateTime.UtcNow, 20));
wma.Update(new TValue(DateTime.UtcNow, 100)); // High recent value
sma.Update(new TValue(DateTime.UtcNow, 100));
// WMA should be higher than SMA because it weights the high recent value more
// SMA = (10 + 20 + 100) / 3 = 43.333...
// WMA = (1*10 + 2*20 + 3*100) / 6 = (10 + 40 + 300) / 6 = 58.333...
Assert.True(wma.Last.Value > sma.Last.Value);
Assert.Equal(350.0 / 6.0, wma.Last.Value, 1e-10);
Assert.Equal(130.0 / 3.0, sma.Last.Value, 1e-10);
}
[Fact]
public void Wma_WarmupDivisor_CalculatedCorrectly()
{
var wma = new Wma(5);
// First value: divisor = 1*(1+1)/2 = 1
var r1 = wma.Update(new TValue(DateTime.UtcNow, 100));
Assert.Equal(100.0, r1.Value, 1e-10);
// Second value: divisor = 2*(2+1)/2 = 3, wsum = 1*100 + 2*200 = 500
var r2 = wma.Update(new TValue(DateTime.UtcNow, 200));
Assert.Equal(500.0 / 3.0, r2.Value, 1e-10);
// Third value: divisor = 3*(3+1)/2 = 6, wsum = 1*100 + 2*200 + 3*300 = 1400
var r3 = wma.Update(new TValue(DateTime.UtcNow, 300));
Assert.Equal(1400.0 / 6.0, r3.Value, 1e-10);
}
// ============== Span API Tests ==============
[Fact]
public void Wma_SpanCalc_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>(() => Wma.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Throws<ArgumentException>(() => Wma.Calculate(source.AsSpan(), output.AsSpan(), -1));
// Output must be same length as source
Assert.Throws<ArgumentException>(() => Wma.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 3));
}
[Fact]
public void Wma_SpanCalc_MatchesTSeriesCalc()
{
var series = new TSeries();
double[] source = new double[100];
double[] output = new double[100];
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42);
for (int i = 0; i < 100; i++)
{
var bar = gbm.Next(isNew: true);
source[i] = bar.Close;
series.Add(bar.Time, bar.Close);
}
// Calculate with TSeries API
var tseriesResult = Wma.Calculate(series, 10);
// Calculate with Span API
Wma.Calculate(source.AsSpan(), output.AsSpan(), 10);
// Compare results
for (int i = 0; i < 100; i++)
{
Assert.Equal(tseriesResult[i].Value, output[i], 1e-10);
}
}
[Fact]
public void Wma_SpanCalc_CalculatesCorrectly()
{
double[] source = [10, 20, 30, 40, 50];
double[] output = new double[5];
Wma.Calculate(source.AsSpan(), output.AsSpan(), 3);
// WMA(3) warmup:
// i=0: 10 (1*10 / 1)
// i=1: (1*10 + 2*20) / 3 = 50/3 = 16.666...
// i=2: (1*10 + 2*20 + 3*30) / 6 = 140/6 = 23.333...
// i=3: sliding: (1*20 + 2*30 + 3*40) / 6 = 200/6 = 33.333...
// i=4: (1*30 + 2*40 + 3*50) / 6 = 260/6 = 43.333...
Assert.Equal(10.0, output[0], 1e-10);
Assert.Equal(50.0 / 3.0, output[1], 1e-10);
Assert.Equal(140.0 / 6.0, output[2], 1e-10);
Assert.Equal(200.0 / 6.0, output[3], 1e-10);
Assert.Equal(260.0 / 6.0, output[4], 1e-10);
}
[Fact]
public void Wma_SpanCalc_ZeroAllocation()
{
double[] source = new double[10000];
double[] output = new double[10000];
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
for (int i = 0; i < source.Length; i++)
source[i] = gbm.Next().Close;
// Warm up
Wma.Calculate(source.AsSpan(), output.AsSpan(), 100);
// This test verifies the method runs without throwing
Assert.True(double.IsFinite(output[^1]));
}
[Fact]
public void Wma_SpanCalc_HandlesNaN()
{
double[] source = [100, 110, double.NaN, 120, 130];
double[] output = new double[5];
Wma.Calculate(source.AsSpan(), output.AsSpan(), 3);
// All outputs should be finite
foreach (var val in output)
{
Assert.True(double.IsFinite(val), $"Expected finite value but got {val}");
}
}
[Fact]
public void Wma_SpanCalc_Period1_ReturnsInput()
{
double[] source = [10, 20, 30, 40, 50];
double[] output = new double[5];
Wma.Calculate(source.AsSpan(), output.AsSpan(), 1);
for (int i = 0; i < source.Length; i++)
{
Assert.Equal(source[i], output[i], 1e-10);
}
}
[Fact]
public void Wma_SpanCalc_UsesStackallocForSmallPeriods()
{
double[] source = new double[1000];
double[] output = new double[1000];
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
for (int i = 0; i < source.Length; i++)
source[i] = gbm.Next().Close;
// Period <= 512 uses stackalloc
Wma.Calculate(source.AsSpan(), output.AsSpan(), 100);
Assert.True(double.IsFinite(output[^1]));
// Period > 512 uses heap allocation
double[] output2 = new double[1000];
Wma.Calculate(source.AsSpan(), output2.AsSpan(), 600);
Assert.True(double.IsFinite(output2[^1]));
}
[Fact]
public void Wma_AllModes_ProduceSameResult()
{
// Arrange
int period = 10;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var series = bars.Close;
// 1. Batch Mode
var batchSeries = Wma.Calculate(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];
Wma.Calculate(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
var streamingInd = new Wma(period);
for (int i = 0; i < series.Count; i++)
{
streamingInd.Update(series[i]);
}
double streamingResult = streamingInd.Last.Value;
// 4. Eventing Mode
var pubSource = new TSeries();
var eventingInd = new Wma(pubSource, period);
for (int i = 0; i < series.Count; i++)
{
pubSource.Add(series[i]);
}
double eventingResult = eventingInd.Last.Value;
// Assert
Assert.Equal(expected, spanResult, precision: 9);
Assert.Equal(expected, streamingResult, precision: 9);
Assert.Equal(expected, eventingResult, precision: 9);
}
}