Enhance validation tests for various indicators with external library comparisons

- Added detailed comments explaining the validation limitations for MMA and ZLEMA due to differences in algorithm implementations.
- Implemented validation tests for True Range against TALib and Tulip, ensuring directional agreement.
- Updated Ulcer Index validation to clarify differences in algorithmic approaches between QuanTAlib and Skender.
- Enhanced Ease of Movement tests to verify directional agreement with Tulip's EMV, noting differences in volume scaling.
- Expanded Klinger Volume Oscillator tests to validate against Skender and Tulip, focusing on directional agreement across multiple period configurations.
- Improved Negative Volume Index tests to compare percentage changes with Tulip, addressing differences in starting values.
- Updated Positive Volume Index tests to validate against Tulip, emphasizing percentage change comparisons.
- Enhanced Williams Accumulation/Distribution tests to verify directional agreement with Tulip, highlighting formula differences.
This commit is contained in:
Miha Kralj
2026-02-11 14:46:56 -08:00
parent 6d6259a47d
commit 75c6a9f135
51 changed files with 7893 additions and 1274 deletions
+56
View File
@@ -1,3 +1,5 @@
using TALib;
namespace QuanTAlib.Test;
using Xunit;
@@ -657,4 +659,58 @@ public class TrValidationTests
Assert.True(output[i] >= 0, $"Output at index {i} should be non-negative");
}
}
// === External Library Validation ===
[Fact]
public void Validate_Talib_TrueRange()
{
var bars = GenerateTestData(500);
double[] high = bars.Select(b => b.High).ToArray();
double[] low = bars.Select(b => b.Low).ToArray();
double[] close = bars.Select(b => b.Close).ToArray();
double[] output = new double[high.Length];
var retCode = Functions.TRange<double>(high, low, close, 0..^0, output, out var outRange);
Assert.Equal(Core.RetCode.Success, retCode);
int lookback = Functions.TRangeLookback();
// Batch comparison
double[] qOutput = new double[high.Length];
Tr.Batch(high, low, close, qOutput);
// Use ValidationHelper for correct TALib index mapping
QuanTAlib.Tests.ValidationHelper.VerifyData(qOutput, output, outRange, lookback);
}
[Fact]
public void Validate_Tulip_TrueRange()
{
var bars = GenerateTestData(500);
double[] high = bars.Select(b => b.High).ToArray();
double[] low = bars.Select(b => b.Low).ToArray();
double[] close = bars.Select(b => b.Close).ToArray();
var trIndicator = Tulip.Indicators.tr;
double[][] inputs = { high, low, close };
double[] options = Array.Empty<double>();
int lookback = trIndicator.Start(options);
double[][] outputs = { new double[high.Length - lookback] };
trIndicator.Run(inputs, options, outputs);
double[] qOutput = new double[high.Length];
Tr.Batch(high, low, close, qOutput);
int tulipLen = outputs[0].Length;
int count = Math.Min(tulipLen, 100);
int start = tulipLen - count;
for (int i = start; i < tulipLen; i++)
{
int qIdx = lookback + i;
Assert.True(
Math.Abs(qOutput[qIdx] - outputs[0][i]) <= 1e-7,
$"TR mismatch at {qIdx}: QuanTAlib={qOutput[qIdx]:G17}, Tulip={outputs[0][i]:G17}");
}
}
}
+8
View File
@@ -661,4 +661,12 @@ public class UiValidationTests
double expected = Math.Sqrt(22.6757369614512 / 3.0);
Assert.Equal(expected, result.Value, 5);
}
// === External Library Validation ===
// NOTE: Skender.Stock.Indicators uses a different Ulcer Index algorithm variant:
// Skender: For each bar j in the period window, highestClose = max(closes from window_start to j)
// Each bar gets its own "growing" highest reference within the evaluation window.
// QuanTAlib: highestClose = max(closes over the entire rolling period window)
// Both are valid implementations of the Ulcer Index concept, but produce different values.
// No external validation test is added for UI due to this algorithmic difference.
}