Files
QuanTAlib/lib/volume/obv/tests/Obv.Validation.Tests.cs
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

163 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);
}
}