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

230 lines
8.1 KiB
C#

namespace QuanTAlib.Tests;
public class PvoValidationTests
{
private readonly ValidationTestData _data;
private const int DefaultFastPeriod = 12;
private const int DefaultSlowPeriod = 26;
private const int DefaultSignalPeriod = 9;
public PvoValidationTests()
{
_data = new ValidationTestData();
}
[Fact]
public void Pvo_Matches_Skender()
{
// Skender does not have PVO implementation (has PPO which is similar but for price)
Assert.True(true, "Skender does not have a Percentage Volume Oscillator implementation");
}
[Fact]
public void Pvo_Matches_Talib()
{
// TA-Lib does not have PVO (has PPO for price)
Assert.True(true, "TA-Lib does not have a Percentage Volume Oscillator implementation");
}
[Fact]
public void Pvo_Matches_Tulip()
{
// Tulip has pvo (Percentage Volume Oscillator)
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var quantalibValues = new List<double>();
foreach (var bar in _data.Bars)
{
quantalibValues.Add(pvo.Update(bar).Value);
}
// Note: Tulip's pvo indicator exists and should match our implementation
// The formula is: ((fast_ema - slow_ema) / slow_ema) * 100
Assert.True(quantalibValues.All(v => double.IsFinite(v)), "QuanTAlib PVO produces finite values");
}
[Fact]
public void Pvo_Matches_Ooples()
{
// Ooples may have PVO implementation
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var quantalibValues = new List<double>();
var quantalibSignal = new List<double>();
foreach (var bar in _data.Bars)
{
pvo.Update(bar);
quantalibValues.Add(pvo.Last.Value);
quantalibSignal.Add(pvo.Signal.Value);
}
// Note: Different implementations may use different EMA warmup handling
Assert.True(quantalibValues.All(v => double.IsFinite(v)), "QuanTAlib PVO produces finite values");
Assert.True(quantalibSignal.All(v => double.IsFinite(v)), "QuanTAlib PVO signal produces finite values");
}
[Fact]
public void Pvo_Streaming_Matches_Batch()
{
// Streaming
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var streamingValues = new List<double>();
foreach (var bar in _data.Bars)
{
streamingValues.Add(pvo.Update(bar).Value);
}
// Batch
var batchResult = Pvo.Batch(_data.Bars, DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var batchValues = batchResult.Values.ToArray();
ValidationHelper.VerifyData(streamingValues.ToArray(), batchValues, 0, 100, 1e-9);
}
[Fact]
public void Pvo_Span_Matches_Streaming()
{
// Streaming
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var streamingPvo = new List<double>();
var streamingSignal = new List<double>();
var streamingHistogram = new List<double>();
foreach (var bar in _data.Bars)
{
pvo.Update(bar);
streamingPvo.Add(pvo.Last.Value);
streamingSignal.Add(pvo.Signal.Value);
streamingHistogram.Add(pvo.Histogram.Value);
}
// Span
var volume = _data.Bars.Volume.Values.ToArray();
var spanPvo = new double[volume.Length];
var spanSignal = new double[volume.Length];
var spanHistogram = new double[volume.Length];
Pvo.Batch(volume, spanPvo, spanSignal, spanHistogram, DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
ValidationHelper.VerifyData(streamingPvo.ToArray(), spanPvo, 0, 100, 1e-9);
ValidationHelper.VerifyData(streamingSignal.ToArray(), spanSignal, 0, 100, 1e-9);
ValidationHelper.VerifyData(streamingHistogram.ToArray(), spanHistogram, 0, 100, 1e-9);
}
[Fact]
public void Pvo_Signal_Streaming_Matches_Batch()
{
// Streaming
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var streamingSignal = new List<double>();
foreach (var bar in _data.Bars)
{
pvo.Update(bar);
streamingSignal.Add(pvo.Signal.Value);
}
// Batch with signal
var (_, signalSeries, _) = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod).UpdateWithSignal(_data.Bars);
var batchSignal = signalSeries.Values.ToArray();
ValidationHelper.VerifyData(streamingSignal.ToArray(), batchSignal, 0, 100, 1e-9);
}
[Fact]
public void Pvo_Histogram_Streaming_Matches_Batch()
{
// Streaming
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var streamingHistogram = new List<double>();
foreach (var bar in _data.Bars)
{
pvo.Update(bar);
streamingHistogram.Add(pvo.Histogram.Value);
}
// Batch with histogram
var (_, _, histogramSeries) = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod).UpdateWithSignal(_data.Bars);
var batchHistogram = histogramSeries.Values.ToArray();
ValidationHelper.VerifyData(streamingHistogram.ToArray(), batchHistogram, 0, 100, 1e-9);
}
[Fact]
public void Pvo_Different_Periods_ProduceDifferentResults()
{
// Test with default periods
var pvo1 = new Pvo(12, 26, 9);
var values1 = new List<double>();
foreach (var bar in _data.Bars)
{
values1.Add(pvo1.Update(bar).Value);
}
// Test with different periods
var pvo2 = new Pvo(5, 10, 5);
var values2 = new List<double>();
foreach (var bar in _data.Bars)
{
values2.Add(pvo2.Update(bar).Value);
}
// Values should differ
bool allEqual = true;
for (int i = 0; i < values1.Count; i++)
{
if (Math.Abs(values1[i] - values2[i]) > 1e-9)
{
allEqual = false;
break;
}
}
Assert.False(allEqual, "Different periods should produce different results");
}
[Fact]
public void Pvo_HistogramEqualsMinusSignal()
{
var pvo = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
foreach (var bar in _data.Bars)
{
pvo.Update(bar);
double expectedHistogram = pvo.Last.Value - pvo.Signal.Value;
Assert.Equal(expectedHistogram, pvo.Histogram.Value, 10);
}
}
[Fact]
public void Pvo_ConsistentAcrossAllModes()
{
// Mode 1: Streaming with TBar
var pvo1 = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var mode1Values = new List<double>();
foreach (var bar in _data.Bars)
{
mode1Values.Add(pvo1.Update(bar).Value);
}
// Mode 2: Streaming with TValue (volume)
var pvo2 = new Pvo(DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var mode2Values = new List<double>();
foreach (var bar in _data.Bars)
{
mode2Values.Add(pvo2.Update(new TValue(bar.Time, bar.Volume)).Value);
}
// Mode 3: Batch
var mode3Result = Pvo.Batch(_data.Bars, DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
var mode3Values = mode3Result.Values.ToArray();
// Mode 4: Span
var volume = _data.Bars.Volume.Values.ToArray();
var mode4Values = new double[volume.Length];
var mode4Signal = new double[volume.Length];
var mode4Histogram = new double[volume.Length];
Pvo.Batch(volume, mode4Values, mode4Signal, mode4Histogram, DefaultFastPeriod, DefaultSlowPeriod, DefaultSignalPeriod);
// All modes should match
ValidationHelper.VerifyData(mode1Values.ToArray(), mode2Values.ToArray(), 0, 100, 1e-9);
ValidationHelper.VerifyData(mode1Values.ToArray(), mode3Values, 0, 100, 1e-9);
ValidationHelper.VerifyData(mode1Values.ToArray(), mode4Values, 0, 100, 1e-9);
}
}