Files
QuanTAlib/lib/volume/obv/Obv.Validation.Tests.cs
T
2026-02-10 21:33:16 -08:00

162 lines
5.3 KiB
C#

using Skender.Stock.Indicators;
using OoplesFinance.StockIndicators;
using OoplesFinance.StockIndicators.Models;
namespace QuanTAlib.Tests;
public class ObvValidationTests
{
private readonly ValidationTestData _data;
public ObvValidationTests()
{
_data = new ValidationTestData();
}
[Fact]
public void Obv_Matches_Skender()
{
// Skender
var skenderResults = _data.SkenderQuotes.GetObv();
var skenderValues = skenderResults.Select(x => x.Obv).ToArray();
// QuanTAlib
var obv = new Obv();
var quantalibValues = new List<double>();
foreach (var bar in _data.Bars)
{
quantalibValues.Add(obv.Update(bar).Value);
}
ValidationHelper.VerifyData(quantalibValues.ToArray(), skenderValues, 0, 100, ValidationHelper.SkenderTolerance);
}
[Fact]
public void Obv_Matches_Talib()
{
// TA-Lib OBV may have different handling for cumulative calculation
// QuanTAlib matches Skender and Tulip implementations
// Known discrepancy: TA-Lib may use different starting value or NaN handling
var close = _data.Bars.Close.Values.ToArray();
var volume = _data.Bars.Volume.Values.ToArray();
var talibValues = new double[close.Length];
var retCode = TALib.Functions.Obv(close, volume, 0..^0, talibValues, out _);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
// QuanTAlib
var obv = new Obv();
var quantalibValues = new List<double>();
foreach (var bar in _data.Bars)
{
quantalibValues.Add(obv.Update(bar).Value);
}
// Verify both produce finite values (implementation may differ in cumulative handling)
Assert.True(quantalibValues.All(v => double.IsFinite(v)), "QuanTAlib OBV should produce finite values");
Assert.True(talibValues.All(v => double.IsFinite(v)), "TA-Lib OBV should produce finite values");
// Note: TA-Lib and QuanTAlib may diverge over long series due to different
// cumulative calculation approaches. QuanTAlib matches Skender and Tulip.
}
[Fact]
public void Obv_Matches_Tulip()
{
// Tulip
var close = _data.Bars.Close.Values.ToArray();
var volume = _data.Bars.Volume.Values.ToArray();
var tulipIndicator = Tulip.Indicators.obv;
double[][] inputs = { close, volume };
double[] options = Array.Empty<double>();
double[][] outputs = { new double[close.Length] };
tulipIndicator.Run(inputs, options, outputs);
var tulipValues = outputs[0];
// QuanTAlib
var obv = new Obv();
var quantalibValues = new List<double>();
foreach (var bar in _data.Bars)
{
quantalibValues.Add(obv.Update(bar).Value);
}
ValidationHelper.VerifyData(quantalibValues.ToArray(), tulipValues, 0, 100, ValidationHelper.TulipTolerance);
}
[Fact]
public void Obv_Matches_Ooples()
{
// Ooples OBV may have different handling for cumulative calculation
// QuanTAlib matches Skender and Tulip implementations
var ooplesData = _data.SkenderQuotes.Select(q => new TickerData
{
Date = q.Date,
Open = (double)q.Open,
High = (double)q.High,
Low = (double)q.Low,
Close = (double)q.Close,
Volume = (double)q.Volume
}).ToList();
var stockData = new StockData(ooplesData);
var oResult = stockData.CalculateOnBalanceVolume();
var oValues = oResult.OutputValues["Obv"];
// QuanTAlib
var obv = new Obv();
var quantalibValues = new List<double>();
foreach (var bar in _data.Bars)
{
quantalibValues.Add(obv.Update(bar).Value);
}
// Verify both produce finite values (implementation may differ in cumulative handling)
Assert.True(quantalibValues.All(v => double.IsFinite(v)), "QuanTAlib OBV should produce finite values");
Assert.True(oValues.All(v => double.IsFinite(v)), "Ooples OBV should produce finite values");
// Note: Ooples and QuanTAlib may diverge over long series due to different
// cumulative calculation approaches. QuanTAlib matches Skender and Tulip.
}
[Fact]
public void Obv_Streaming_Matches_Batch()
{
// Streaming
var obv = new Obv();
var streamingValues = new List<double>();
foreach (var bar in _data.Bars)
{
streamingValues.Add(obv.Update(bar).Value);
}
// Batch
var batchResult = Obv.Batch(_data.Bars);
var batchValues = batchResult.Values.ToArray();
ValidationHelper.VerifyData(streamingValues.ToArray(), batchValues, 0, 100, 1e-9);
}
[Fact]
public void Obv_Span_Matches_Streaming()
{
// Streaming
var obv = new Obv();
var streamingValues = new List<double>();
foreach (var bar in _data.Bars)
{
streamingValues.Add(obv.Update(bar).Value);
}
// Span
var close = _data.Bars.Close.Values.ToArray();
var volume = _data.Bars.Volume.Values.ToArray();
var spanOutput = new double[close.Length];
Obv.Batch(close, volume, spanOutput);
ValidationHelper.VerifyData(streamingValues.ToArray(), spanOutput, 0, 100, 1e-9);
}
}