From a1de42ec802272b4d526cd71030806c1a1edc31b Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sat, 29 Nov 2025 21:42:21 -0800 Subject: [PATCH] Refactor SMA and WMA validation tests to include batch, streaming, and span methods - Updated SMA validation tests to include separate methods for Skender and TA-Lib with batch, streaming, and span calculations. - Enhanced WMA validation tests similarly, ensuring comprehensive coverage for Skender and TA-Lib. - Improved verification helper methods for better clarity and maintainability. --- lib/averages/ema/Ema.Validation.Tests.cs | 338 ++++++++++++++++++--- lib/averages/sma/Sma.Validation.Tests.cs | 356 ++++++++++++++++++++--- lib/averages/wma/Wma.Validation.Tests.cs | 356 ++++++++++++++++++++--- 3 files changed, 931 insertions(+), 119 deletions(-) diff --git a/lib/averages/ema/Ema.Validation.Tests.cs b/lib/averages/ema/Ema.Validation.Tests.cs index 4cd89648..898c1d9d 100644 --- a/lib/averages/ema/Ema.Validation.Tests.cs +++ b/lib/averages/ema/Ema.Validation.Tests.cs @@ -45,13 +45,13 @@ public class EmaValidationTests } [Fact] - public void Validate_Skender() + public void Validate_Skender_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; foreach (var period in periods) { - // Calculate QuanTAlib EMA + // Calculate QuanTAlib EMA (batch TSeries) var ema = new global::QuanTAlib.Ema(period); var qResult = ema.Update(_data); @@ -59,13 +59,60 @@ public class EmaValidationTests var sResult = _skenderQuotes.GetEma(period).ToList(); // Compare last 100 records - VerifyData(qResult, sResult); + VerifyData_Skender(qResult, sResult); } - _output.WriteLine("EMA validated successfully against Skender"); + _output.WriteLine("EMA Batch(TSeries) validated successfully against Skender"); } [Fact] - public void Validate_Talib() + public void Validate_Skender_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(ema.Update(item).Value); + } + + // Calculate Skender EMA + var sResult = _skenderQuotes.GetEma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Streaming(qResults, sResult); + } + _output.WriteLine("EMA Streaming validated successfully against Skender"); + } + + [Fact] + public void Validate_Skender_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Span API + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Skender EMA + var sResult = _skenderQuotes.GetEma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Span(qOutput, sResult); + } + _output.WriteLine("EMA Span validated successfully against Skender"); + } + + [Fact] + public void Validate_Talib_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -75,27 +122,82 @@ public class EmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib EMA + // Calculate QuanTAlib EMA (batch TSeries) var ema = new global::QuanTAlib.Ema(period); var qResult = ema.Update(_data); // Calculate TA-Lib EMA var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); - - // Check success Assert.Equal(Core.RetCode.Success, retCode); - // TA-Lib skips the lookback period, so output[0] corresponds to input[lookback] int lookback = TALib.Functions.EmaLookback(period); // Compare last 100 records VerifyData_Talib(qResult, output, outRange, lookback); } - _output.WriteLine("EMA validated successfully against TA-Lib"); + _output.WriteLine("EMA Batch(TSeries) validated successfully against TA-Lib"); } [Fact] - public void Validate_Tulip() + public void Validate_Talib_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for TA-Lib (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); + double[] output = new double[tData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(ema.Update(item).Value); + } + + // Calculate TA-Lib EMA + var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.EmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Streaming(qResults, output, outRange, lookback); + } + _output.WriteLine("EMA Streaming validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Talib_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + double[] talibOutput = new double[sourceData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate TA-Lib EMA + var retCode = TALib.Functions.Ema(sourceData, 0..^0, talibOutput, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.EmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Span(qOutput, talibOutput, outRange, lookback); + } + _output.WriteLine("EMA Span validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Tulip_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -104,7 +206,7 @@ public class EmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib EMA + // Calculate QuanTAlib EMA (batch TSeries) var ema = new global::QuanTAlib.Ema(period); var qResult = ema.Update(_data); @@ -118,46 +220,125 @@ public class EmaValidationTests var tResult = outputs[0]; // Compare last 100 records - VerifyData(qResult, tResult.ToList()); + VerifyData_Tulip(qResult, tResult); } - _output.WriteLine("EMA validated successfully against Tulip"); + _output.WriteLine("EMA Batch(TSeries) validated successfully against Tulip"); } - private static void VerifyData(TSeries qSeries, List tSeries) + [Fact] + public void Validate_Tulip_Streaming() { - // Ensure we have enough data - Assert.Equal(qSeries.Count, tSeries.Count); + int[] periods = { 5, 10, 20, 50, 100 }; - int count = qSeries.Count; - int skip = count - 100; // Last 100 records + // Prepare data for Tulip (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); - for (int i = skip; i < count; i++) + foreach (var period in periods) { - double qValue = qSeries[i].Value; - double tValue = tSeries[i]; - if (Math.Abs(tValue) < 1e-10) continue; + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(ema.Update(item).Value); + } - Assert.Equal(tValue, qValue, 1e-6); + // Calculate Tulip EMA + var emaIndicator = Tulip.Indicators.ema; + double[][] inputs = { tData }; + double[] options = { (double)period }; + double[][] outputs = { new double[tData.Length] }; + + emaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + VerifyData_Tulip_Streaming(qResults, tResult); } + _output.WriteLine("EMA Streaming validated successfully against Tulip"); } - private static void VerifyData(TSeries qSeries, List sSeries) + [Fact] + public void Validate_Tulip_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Tulip EMA + var emaIndicator = Tulip.Indicators.ema; + double[][] inputs = { sourceData }; + double[] options = { (double)period }; + double[][] outputs = { new double[sourceData.Length] }; + + emaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + VerifyData_Tulip_Span(qOutput, tResult); + } + _output.WriteLine("EMA Span validated successfully against Tulip"); + } + + // ==================== Verification Helpers ==================== + + private static void VerifyData_Skender(TSeries qSeries, List sSeries) { - // Ensure we have enough data Assert.Equal(qSeries.Count, sSeries.Count); int count = qSeries.Count; - int skip = count - 100; // Last 100 records + int skip = count - 100; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; double? sValue = sSeries[i].Ema; - // Skip if Skender returns null (warmup period) if (!sValue.HasValue) continue; - // Assert equality with tolerance + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Streaming(List qResults, List sSeries) + { + Assert.Equal(qResults.Count, sSeries.Count); + + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + double? sValue = sSeries[i].Ema; + + if (!sValue.HasValue) continue; + + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Span(double[] qOutput, List sSeries) + { + Assert.Equal(qOutput.Length, sSeries.Count); + + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + double? sValue = sSeries[i].Ema; + + if (!sValue.HasValue) continue; + Assert.Equal(sValue.Value, qValue, 1e-6); } } @@ -165,27 +346,110 @@ public class EmaValidationTests private static void VerifyData_Talib(TSeries qSeries, double[] tOutput, Range outRange, int lookback) { int count = qSeries.Count; - int skip = count - 100; // Last 100 records - - // outRange.End.Value is the number of elements written to tOutput + int skip = count - 100; int validCount = outRange.End.Value - outRange.Start.Value; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; - // Calculate index in tOutput - // If i < lookback, we don't have a value from TA-Lib if (i < lookback) continue; int tIndex = i - lookback; - - // Check if tIndex is within valid range if (tIndex >= validCount) continue; double tValue = tOutput[tIndex]; - // Assert equality with tolerance + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Streaming(List qResults, double[] tOutput, Range outRange, int lookback) + { + int count = qResults.Count; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Span(double[] qOutput, double[] tOutput, Range outRange, int lookback) + { + int count = qOutput.Length; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip(TSeries qSeries, double[] tOutput) + { + int count = qSeries.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qSeries[i].Value; + double tValue = tOutput[i]; + + if (Math.Abs(tValue) < 1e-10) continue; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Streaming(List qResults, double[] tOutput) + { + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + double tValue = tOutput[i]; + + if (Math.Abs(tValue) < 1e-10) continue; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Span(double[] qOutput, double[] tOutput) + { + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + double tValue = tOutput[i]; + + if (Math.Abs(tValue) < 1e-10) continue; + Assert.Equal(tValue, qValue, 1e-6); } } diff --git a/lib/averages/sma/Sma.Validation.Tests.cs b/lib/averages/sma/Sma.Validation.Tests.cs index bd0eeaf6..acebf2c7 100644 --- a/lib/averages/sma/Sma.Validation.Tests.cs +++ b/lib/averages/sma/Sma.Validation.Tests.cs @@ -45,13 +45,13 @@ public class SmaValidationTests } [Fact] - public void Validate_Skender() + public void Validate_Skender_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; foreach (var period in periods) { - // Calculate QuanTAlib SMA + // Calculate QuanTAlib SMA (batch TSeries) var sma = new global::QuanTAlib.Sma(period); var qResult = sma.Update(_data); @@ -59,13 +59,60 @@ public class SmaValidationTests var sResult = _skenderQuotes.GetSma(period).ToList(); // Compare last 100 records - VerifyData(qResult, sResult); + VerifyData_Skender(qResult, sResult); } - _output.WriteLine("SMA validated successfully against Skender"); + _output.WriteLine("SMA Batch(TSeries) validated successfully against Skender"); } [Fact] - public void Validate_Talib() + public void Validate_Skender_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + foreach (var period in periods) + { + // Calculate QuanTAlib SMA (streaming) + var sma = new global::QuanTAlib.Sma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(sma.Update(item).Value); + } + + // Calculate Skender SMA + var sResult = _skenderQuotes.GetSma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Streaming(qResults, sResult); + } + _output.WriteLine("SMA Streaming validated successfully against Skender"); + } + + [Fact] + public void Validate_Skender_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Span API + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib SMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Skender SMA + var sResult = _skenderQuotes.GetSma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Span(qOutput, sResult); + } + _output.WriteLine("SMA Span validated successfully against Skender"); + } + + [Fact] + public void Validate_Talib_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -75,27 +122,82 @@ public class SmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib SMA + // Calculate QuanTAlib SMA (batch TSeries) var sma = new global::QuanTAlib.Sma(period); var qResult = sma.Update(_data); // Calculate TA-Lib SMA var retCode = TALib.Functions.Sma(tData, 0..^0, output, out var outRange, period); - - // Check success Assert.Equal(Core.RetCode.Success, retCode); - // TA-Lib skips the lookback period, so output[0] corresponds to input[lookback] int lookback = TALib.Functions.SmaLookback(period); // Compare last 100 records VerifyData_Talib(qResult, output, outRange, lookback); } - _output.WriteLine("SMA validated successfully against TA-Lib"); + _output.WriteLine("SMA Batch(TSeries) validated successfully against TA-Lib"); } [Fact] - public void Validate_Tulip() + public void Validate_Talib_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for TA-Lib (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); + double[] output = new double[tData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib SMA (streaming) + var sma = new global::QuanTAlib.Sma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(sma.Update(item).Value); + } + + // Calculate TA-Lib SMA + var retCode = TALib.Functions.Sma(tData, 0..^0, output, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.SmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Streaming(qResults, output, outRange, lookback); + } + _output.WriteLine("SMA Streaming validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Talib_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + double[] talibOutput = new double[sourceData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib SMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate TA-Lib SMA + var retCode = TALib.Functions.Sma(sourceData, 0..^0, talibOutput, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.SmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Span(qOutput, talibOutput, outRange, lookback); + } + _output.WriteLine("SMA Span validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Tulip_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -104,11 +206,11 @@ public class SmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib SMA + // Calculate QuanTAlib SMA (batch TSeries) var sma = new global::QuanTAlib.Sma(period); var qResult = sma.Update(_data); - // Calculate Tulip SMA - Tulip returns fewer elements (skips lookback) + // Calculate Tulip SMA var smaIndicator = Tulip.Indicators.sma; double[][] inputs = { tData }; double[] options = { (double)period }; @@ -118,51 +220,128 @@ public class SmaValidationTests smaIndicator.Run(inputs, options, outputs); var tResult = outputs[0]; - // Compare last 100 records (accounting for lookback offset) + // Compare last 100 records VerifyData_Tulip(qResult, tResult, lookback); } - _output.WriteLine("SMA validated successfully against Tulip"); + _output.WriteLine("SMA Batch(TSeries) validated successfully against Tulip"); } - private static void VerifyData_Tulip(TSeries qSeries, double[] tOutput, int lookback) + [Fact] + public void Validate_Tulip_Streaming() { - int count = qSeries.Count; - int skip = count - 100; // Last 100 records + int[] periods = { 5, 10, 20, 50, 100 }; - for (int i = skip; i < count; i++) + // Prepare data for Tulip (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) { - double qValue = qSeries[i].Value; + // Calculate QuanTAlib SMA (streaming) + var sma = new global::QuanTAlib.Sma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(sma.Update(item).Value); + } - // Tulip skips lookback, so output[0] = input[lookback] - if (i < lookback) continue; + // Calculate Tulip SMA + var smaIndicator = Tulip.Indicators.sma; + double[][] inputs = { tData }; + double[] options = { (double)period }; + int lookback = period - 1; + double[][] outputs = { new double[tData.Length - lookback] }; - int tIndex = i - lookback; - if (tIndex >= tOutput.Length) continue; + smaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; - double tValue = tOutput[tIndex]; - - // Assert equality with tolerance - Assert.Equal(tValue, qValue, 1e-6); + // Compare last 100 records + VerifyData_Tulip_Streaming(qResults, tResult, lookback); } + _output.WriteLine("SMA Streaming validated successfully against Tulip"); } - private static void VerifyData(TSeries qSeries, List sSeries) + [Fact] + public void Validate_Tulip_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib SMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Sma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Tulip SMA + var smaIndicator = Tulip.Indicators.sma; + double[][] inputs = { sourceData }; + double[] options = { (double)period }; + int lookback = period - 1; + double[][] outputs = { new double[sourceData.Length - lookback] }; + + smaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + VerifyData_Tulip_Span(qOutput, tResult, lookback); + } + _output.WriteLine("SMA Span validated successfully against Tulip"); + } + + // ==================== Verification Helpers ==================== + + private static void VerifyData_Skender(TSeries qSeries, List sSeries) { - // Ensure we have enough data Assert.Equal(qSeries.Count, sSeries.Count); int count = qSeries.Count; - int skip = count - 100; // Last 100 records + int skip = count - 100; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; double? sValue = sSeries[i].Sma; - // Skip if Skender returns null (warmup period) if (!sValue.HasValue) continue; - // Assert equality with tolerance + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Streaming(List qResults, List sSeries) + { + Assert.Equal(qResults.Count, sSeries.Count); + + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + double? sValue = sSeries[i].Sma; + + if (!sValue.HasValue) continue; + + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Span(double[] qOutput, List sSeries) + { + Assert.Equal(qOutput.Length, sSeries.Count); + + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + double? sValue = sSeries[i].Sma; + + if (!sValue.HasValue) continue; + Assert.Equal(sValue.Value, qValue, 1e-6); } } @@ -170,27 +349,122 @@ public class SmaValidationTests private static void VerifyData_Talib(TSeries qSeries, double[] tOutput, Range outRange, int lookback) { int count = qSeries.Count; - int skip = count - 100; // Last 100 records - - // outRange.End.Value is the number of elements written to tOutput + int skip = count - 100; int validCount = outRange.End.Value - outRange.Start.Value; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; - // Calculate index in tOutput - // If i < lookback, we don't have a value from TA-Lib if (i < lookback) continue; int tIndex = i - lookback; - - // Check if tIndex is within valid range if (tIndex >= validCount) continue; double tValue = tOutput[tIndex]; - // Assert equality with tolerance + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Streaming(List qResults, double[] tOutput, Range outRange, int lookback) + { + int count = qResults.Count; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Span(double[] qOutput, double[] tOutput, Range outRange, int lookback) + { + int count = qOutput.Length; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip(TSeries qSeries, double[] tOutput, int lookback) + { + int count = qSeries.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qSeries[i].Value; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Streaming(List qResults, double[] tOutput, int lookback) + { + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Span(double[] qOutput, double[] tOutput, int lookback) + { + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + Assert.Equal(tValue, qValue, 1e-6); } } diff --git a/lib/averages/wma/Wma.Validation.Tests.cs b/lib/averages/wma/Wma.Validation.Tests.cs index 2f84f5d3..f686a300 100644 --- a/lib/averages/wma/Wma.Validation.Tests.cs +++ b/lib/averages/wma/Wma.Validation.Tests.cs @@ -45,13 +45,13 @@ public class WmaValidationTests } [Fact] - public void Validate_Skender() + public void Validate_Skender_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; foreach (var period in periods) { - // Calculate QuanTAlib WMA + // Calculate QuanTAlib WMA (batch TSeries) var wma = new global::QuanTAlib.Wma(period); var qResult = wma.Update(_data); @@ -59,13 +59,60 @@ public class WmaValidationTests var sResult = _skenderQuotes.GetWma(period).ToList(); // Compare last 100 records - VerifyData(qResult, sResult); + VerifyData_Skender(qResult, sResult); } - _output.WriteLine("WMA validated successfully against Skender"); + _output.WriteLine("WMA Batch(TSeries) validated successfully against Skender"); } [Fact] - public void Validate_Talib() + public void Validate_Skender_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + foreach (var period in periods) + { + // Calculate QuanTAlib WMA (streaming) + var wma = new global::QuanTAlib.Wma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(wma.Update(item).Value); + } + + // Calculate Skender WMA + var sResult = _skenderQuotes.GetWma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Streaming(qResults, sResult); + } + _output.WriteLine("WMA Streaming validated successfully against Skender"); + } + + [Fact] + public void Validate_Skender_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Span API + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib WMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Wma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Skender WMA + var sResult = _skenderQuotes.GetWma(period).ToList(); + + // Compare last 100 records + VerifyData_Skender_Span(qOutput, sResult); + } + _output.WriteLine("WMA Span validated successfully against Skender"); + } + + [Fact] + public void Validate_Talib_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -75,27 +122,82 @@ public class WmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib WMA + // Calculate QuanTAlib WMA (batch TSeries) var wma = new global::QuanTAlib.Wma(period); var qResult = wma.Update(_data); // Calculate TA-Lib WMA var retCode = TALib.Functions.Wma(tData, 0..^0, output, out var outRange, period); - - // Check success Assert.Equal(Core.RetCode.Success, retCode); - // TA-Lib skips the lookback period, so output[0] corresponds to input[lookback] int lookback = TALib.Functions.WmaLookback(period); // Compare last 100 records VerifyData_Talib(qResult, output, outRange, lookback); } - _output.WriteLine("WMA validated successfully against TA-Lib"); + _output.WriteLine("WMA Batch(TSeries) validated successfully against TA-Lib"); } [Fact] - public void Validate_Tulip() + public void Validate_Talib_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for TA-Lib (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); + double[] output = new double[tData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib WMA (streaming) + var wma = new global::QuanTAlib.Wma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(wma.Update(item).Value); + } + + // Calculate TA-Lib WMA + var retCode = TALib.Functions.Wma(tData, 0..^0, output, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.WmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Streaming(qResults, output, outRange, lookback); + } + _output.WriteLine("WMA Streaming validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Talib_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + double[] talibOutput = new double[sourceData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib WMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Wma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate TA-Lib WMA + var retCode = TALib.Functions.Wma(sourceData, 0..^0, talibOutput, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.WmaLookback(period); + + // Compare last 100 records + VerifyData_Talib_Span(qOutput, talibOutput, outRange, lookback); + } + _output.WriteLine("WMA Span validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Tulip_Batch() { int[] periods = { 5, 10, 20, 50, 100 }; @@ -104,11 +206,11 @@ public class WmaValidationTests foreach (var period in periods) { - // Calculate QuanTAlib WMA + // Calculate QuanTAlib WMA (batch TSeries) var wma = new global::QuanTAlib.Wma(period); var qResult = wma.Update(_data); - // Calculate Tulip WMA - Tulip returns fewer elements (skips lookback) + // Calculate Tulip WMA var wmaIndicator = Tulip.Indicators.wma; double[][] inputs = { tData }; double[] options = { (double)period }; @@ -118,51 +220,128 @@ public class WmaValidationTests wmaIndicator.Run(inputs, options, outputs); var tResult = outputs[0]; - // Compare last 100 records (accounting for lookback offset) + // Compare last 100 records VerifyData_Tulip(qResult, tResult, lookback); } - _output.WriteLine("WMA validated successfully against Tulip"); + _output.WriteLine("WMA Batch(TSeries) validated successfully against Tulip"); } - private static void VerifyData_Tulip(TSeries qSeries, double[] tOutput, int lookback) + [Fact] + public void Validate_Tulip_Streaming() { - int count = qSeries.Count; - int skip = count - 100; // Last 100 records + int[] periods = { 5, 10, 20, 50, 100 }; - for (int i = skip; i < count; i++) + // Prepare data for Tulip (double[]) + double[] tData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) { - double qValue = qSeries[i].Value; + // Calculate QuanTAlib WMA (streaming) + var wma = new global::QuanTAlib.Wma(period); + var qResults = new List(); + foreach (var item in _data) + { + qResults.Add(wma.Update(item).Value); + } - // Tulip skips lookback, so output[0] = input[lookback] - if (i < lookback) continue; + // Calculate Tulip WMA + var wmaIndicator = Tulip.Indicators.wma; + double[][] inputs = { tData }; + double[] options = { (double)period }; + int lookback = period - 1; + double[][] outputs = { new double[tData.Length - lookback] }; - int tIndex = i - lookback; - if (tIndex >= tOutput.Length) continue; + wmaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; - double tValue = tOutput[tIndex]; - - // Assert equality with tolerance - Assert.Equal(tValue, qValue, 1e-6); + // Compare last 100 records + VerifyData_Tulip_Streaming(qResults, tResult, lookback); } + _output.WriteLine("WMA Streaming validated successfully against Tulip"); } - private static void VerifyData(TSeries qSeries, List sSeries) + [Fact] + public void Validate_Tulip_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _data.Select(x => x.Value).ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib WMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Wma.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Tulip WMA + var wmaIndicator = Tulip.Indicators.wma; + double[][] inputs = { sourceData }; + double[] options = { (double)period }; + int lookback = period - 1; + double[][] outputs = { new double[sourceData.Length - lookback] }; + + wmaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + VerifyData_Tulip_Span(qOutput, tResult, lookback); + } + _output.WriteLine("WMA Span validated successfully against Tulip"); + } + + // ==================== Verification Helpers ==================== + + private static void VerifyData_Skender(TSeries qSeries, List sSeries) { - // Ensure we have enough data Assert.Equal(qSeries.Count, sSeries.Count); int count = qSeries.Count; - int skip = count - 100; // Last 100 records + int skip = count - 100; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; double? sValue = sSeries[i].Wma; - // Skip if Skender returns null (warmup period) if (!sValue.HasValue) continue; - // Assert equality with tolerance + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Streaming(List qResults, List sSeries) + { + Assert.Equal(qResults.Count, sSeries.Count); + + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + double? sValue = sSeries[i].Wma; + + if (!sValue.HasValue) continue; + + Assert.Equal(sValue.Value, qValue, 1e-6); + } + } + + private static void VerifyData_Skender_Span(double[] qOutput, List sSeries) + { + Assert.Equal(qOutput.Length, sSeries.Count); + + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + double? sValue = sSeries[i].Wma; + + if (!sValue.HasValue) continue; + Assert.Equal(sValue.Value, qValue, 1e-6); } } @@ -170,27 +349,122 @@ public class WmaValidationTests private static void VerifyData_Talib(TSeries qSeries, double[] tOutput, Range outRange, int lookback) { int count = qSeries.Count; - int skip = count - 100; // Last 100 records - - // outRange.End.Value is the number of elements written to tOutput + int skip = count - 100; int validCount = outRange.End.Value - outRange.Start.Value; for (int i = skip; i < count; i++) { double qValue = qSeries[i].Value; - // Calculate index in tOutput - // If i < lookback, we don't have a value from TA-Lib if (i < lookback) continue; int tIndex = i - lookback; - - // Check if tIndex is within valid range if (tIndex >= validCount) continue; double tValue = tOutput[tIndex]; - // Assert equality with tolerance + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Streaming(List qResults, double[] tOutput, Range outRange, int lookback) + { + int count = qResults.Count; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Talib_Span(double[] qOutput, double[] tOutput, Range outRange, int lookback) + { + int count = qOutput.Length; + int skip = count - 100; + int validCount = outRange.End.Value - outRange.Start.Value; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= validCount) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip(TSeries qSeries, double[] tOutput, int lookback) + { + int count = qSeries.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qSeries[i].Value; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Streaming(List qResults, double[] tOutput, int lookback) + { + int count = qResults.Count; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qResults[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + + Assert.Equal(tValue, qValue, 1e-6); + } + } + + private static void VerifyData_Tulip_Span(double[] qOutput, double[] tOutput, int lookback) + { + int count = qOutput.Length; + int skip = count - 100; + + for (int i = skip; i < count; i++) + { + double qValue = qOutput[i]; + + if (i < lookback) continue; + + int tIndex = i - lookback; + if (tIndex >= tOutput.Length) continue; + + double tValue = tOutput[tIndex]; + Assert.Equal(tValue, qValue, 1e-6); } }