Files
QuanTAlib/lib/momentum/rocr/tests/Rocr.Validation.Tests.cs
T
Miha Kralj 060649192f docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files
- Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.)
- Move test files into tests/ subdirectories for consistent project structure
- Add trader-focused bullet points to indicator documentation
2026-03-12 12:34:16 -07:00

310 lines
9.9 KiB
C#

using TALib;
using Xunit;
using Xunit.Abstractions;
namespace QuanTAlib.Tests;
/// <summary>
/// Validation tests for ROCR (Rate of Change Ratio) against external libraries.
/// ROCR = Price / Price[N]
///
/// TALib's RocR returns the same ratio. Tulip's rocr returns the same ratio.
/// No scaling adjustment needed.
/// </summary>
public sealed class RocrValidationTests(ITestOutputHelper output) : IDisposable
{
private readonly ValidationTestData _testData = new();
private readonly ITestOutputHelper _output = output;
private bool _disposed;
public void Dispose()
{
Dispose(disposing: true);
}
private void Dispose(bool disposing)
{
if (_disposed) { return; }
_disposed = true;
if (disposing) { _testData?.Dispose(); }
}
private const int TestPeriod = 9;
#region TALib Validation
[Fact]
public void Rocr_MatchesTalib_Batch()
{
double[] tData = _testData.RawData.ToArray();
// QuanTAlib ROCR (batch TSeries)
var rocr = new Rocr(TestPeriod);
var qResult = rocr.Update(_testData.Data);
// TALib RocR
double[] tOutput = new double[tData.Length];
var retCode = TALib.Functions.RocR<double>(tData, 0..^0, tOutput, out var outRange, TestPeriod);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
int lookback = TALib.Functions.RocRLookback(TestPeriod);
int count = qResult.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length);
for (int i = start; i < count; i++)
{
if (i < lookback) { continue; }
int tIndex = i - offset;
if (tIndex < 0 || tIndex >= length) { continue; }
Assert.True(
Math.Abs(qResult[i].Value - tOutput[tIndex]) <= ValidationHelper.TalibTolerance,
$"Mismatch at index {i}: QuanTAlib={qResult[i].Value:G17}, TALib={tOutput[tIndex]:G17}");
}
_output.WriteLine("ROCR Batch validated successfully against TALib");
}
[Fact]
public void Rocr_MatchesTalib_Span()
{
double[] tData = _testData.RawData.ToArray();
// QuanTAlib ROCR (Span)
double[] qOutput = new double[tData.Length];
Rocr.Batch(tData.AsSpan(), qOutput.AsSpan(), TestPeriod);
// TALib RocR
double[] tOutput = new double[tData.Length];
var retCode = TALib.Functions.RocR<double>(tData, 0..^0, tOutput, out var outRange, TestPeriod);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
int lookback = TALib.Functions.RocRLookback(TestPeriod);
int count = qOutput.Length;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length);
for (int i = start; i < count; i++)
{
if (i < lookback) { continue; }
int tIndex = i - offset;
if (tIndex < 0 || tIndex >= length) { continue; }
Assert.True(
Math.Abs(qOutput[i] - tOutput[tIndex]) <= ValidationHelper.TalibTolerance,
$"Mismatch at index {i}: QuanTAlib={qOutput[i]:G17}, TALib={tOutput[tIndex]:G17}");
}
_output.WriteLine("ROCR Span validated successfully against TALib");
}
[Fact]
public void Rocr_MatchesTalib_Streaming()
{
double[] tData = _testData.RawData.ToArray();
// QuanTAlib ROCR (streaming)
var rocr = new Rocr(TestPeriod);
var qResults = new List<double>();
foreach (var item in _testData.Data)
{
qResults.Add(rocr.Update(item).Value);
}
// TALib RocR
double[] tOutput = new double[tData.Length];
var retCode = TALib.Functions.RocR<double>(tData, 0..^0, tOutput, out var outRange, TestPeriod);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
int lookback = TALib.Functions.RocRLookback(TestPeriod);
int count = qResults.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length);
for (int i = start; i < count; i++)
{
if (i < lookback) { continue; }
int tIndex = i - offset;
if (tIndex < 0 || tIndex >= length) { continue; }
Assert.True(
Math.Abs(qResults[i] - tOutput[tIndex]) <= ValidationHelper.TalibTolerance,
$"Mismatch at index {i}: QuanTAlib={qResults[i]:G17}, TALib={tOutput[tIndex]:G17}");
}
_output.WriteLine("ROCR Streaming validated successfully against TALib");
}
[Theory]
[InlineData(5)]
[InlineData(14)]
[InlineData(20)]
[InlineData(50)]
public void Rocr_MatchesTalib_DifferentPeriods(int period)
{
double[] tData = _testData.RawData.ToArray();
var rocr = new Rocr(period);
var qResult = rocr.Update(_testData.Data);
double[] tOutput = new double[tData.Length];
var retCode = TALib.Functions.RocR<double>(tData, 0..^0, tOutput, out var outRange, period);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
int lookback = TALib.Functions.RocRLookback(period);
var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length);
int count = qResult.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
if (i < lookback) { continue; }
int tIndex = i - offset;
if (tIndex < 0 || tIndex >= length) { continue; }
Assert.True(
Math.Abs(qResult[i].Value - tOutput[tIndex]) <= ValidationHelper.TalibTolerance,
$"Period {period}, index {i}: QuanTAlib={qResult[i].Value:G17}, TALib={tOutput[tIndex]:G17}");
}
_output.WriteLine($"ROCR period={period} validated against TALib");
}
#endregion
#region Tulip Validation
[Fact]
public void Rocr_MatchesTulip_Batch()
{
double[] tData = _testData.RawData.ToArray();
// QuanTAlib ROCR
var rocr = new Rocr(TestPeriod);
var qResult = rocr.Update(_testData.Data);
// Tulip rocr
var rocrIndicator = Tulip.Indicators.rocr;
double[][] inputs = [tData];
double[] options = [TestPeriod];
int lookback = rocrIndicator.Start(options);
double[][] outputs = [new double[tData.Length - lookback]];
rocrIndicator.Run(inputs, options, outputs);
double[] tulipResult = outputs[0];
// Compare after lookback
ValidationHelper.VerifyData(qResult, tulipResult, lookback);
_output.WriteLine("ROCR Batch validated successfully against Tulip");
}
[Fact]
public void Rocr_MatchesTulip_Streaming()
{
double[] tData = _testData.RawData.ToArray();
// QuanTAlib ROCR (streaming)
var rocr = new Rocr(TestPeriod);
var qResults = new List<double>();
foreach (var item in _testData.Data)
{
qResults.Add(rocr.Update(item).Value);
}
// Tulip rocr
var rocrIndicator = Tulip.Indicators.rocr;
double[][] inputs = [tData];
double[] options = [TestPeriod];
int lookback = rocrIndicator.Start(options);
double[][] outputs = [new double[tData.Length - lookback]];
rocrIndicator.Run(inputs, options, outputs);
double[] tulipResult = outputs[0];
ValidationHelper.VerifyData(qResults, tulipResult, lookback);
_output.WriteLine("ROCR Streaming validated successfully against Tulip");
}
[Theory]
[InlineData(5)]
[InlineData(14)]
[InlineData(20)]
public void Rocr_MatchesTulip_DifferentPeriods(int period)
{
double[] tData = _testData.RawData.ToArray();
var rocr = new Rocr(period);
var qResult = rocr.Update(_testData.Data);
var rocrIndicator = Tulip.Indicators.rocr;
double[][] inputs = [tData];
double[] options = [period];
int lookback = rocrIndicator.Start(options);
double[][] outputs = [new double[tData.Length - lookback]];
rocrIndicator.Run(inputs, options, outputs);
double[] tulipResult = outputs[0];
ValidationHelper.VerifyData(qResult, tulipResult, lookback);
}
#endregion
#region Mathematical Validation
[Fact]
public void Rocr_ManualCalculation_MatchesExpected()
{
var rocr = new Rocr(3);
var time = DateTime.UtcNow;
var values = new double[] { 100, 105, 110, 115, 120, 125 };
for (int i = 0; i < values.Length; i++)
{
var result = rocr.Update(new TValue(time.AddSeconds(i), values[i]), true);
if (i >= 3)
{
double expected = values[i] / values[i - 3];
Assert.Equal(expected, result.Value, 10);
}
else
{
Assert.Equal(1.0, result.Value, 10);
}
}
}
[Fact]
public void Batch_MatchesStreaming_IdenticalResults()
{
var source = _testData.Data;
// Streaming
var streamingRocr = new Rocr(TestPeriod);
var streamingResults = new List<double>();
for (int i = 0; i < source.Count; i++)
{
streamingResults.Add(streamingRocr.Update(source[i]).Value);
}
// Batch
var batchRocr = new Rocr(TestPeriod);
var batchResult = batchRocr.Update(source);
int count = source.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
Assert.Equal(batchResult[i].Value, streamingResults[i], ValidationHelper.DefaultTolerance);
}
_output.WriteLine("ROCR Batch vs Streaming consistency validated");
}
#endregion
}