Files
QuanTAlib/lib/volume/pvd/Pvd.Quantower.Tests.cs
T
Miha Kralj 76d2b50cbb Add Price Volume Trend (PVT) Indicator and Tests
- Implemented the PvtIndicator class for calculating Price Volume Trend in Quantower.
- Created unit tests for the Pvt class to validate calculations and state management.
- Added validation tests to ensure consistency with OoplesFinance's implementation.
- Developed a comprehensive documentation (Pvt.md) explaining the PVT concept, calculations, and usage.
- Included methods for batch calculations and streaming updates for PVT.
2026-01-28 17:54:43 -08:00

223 lines
6.9 KiB
C#

using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class PvdIndicatorTests
{
[Fact]
public void PvdIndicator_Constructor_SetsDefaults()
{
var indicator = new PvdIndicator();
Assert.Equal("PVD - Price Volume Divergence", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
Assert.Equal(14, indicator.PricePeriod);
Assert.Equal(14, indicator.VolumePeriod);
Assert.Equal(3, indicator.SmoothingPeriod);
}
[Fact]
public void PvdIndicator_ShortName_IsConstant()
{
var indicator = new PvdIndicator();
Assert.Equal("PVD", indicator.ShortName);
}
[Fact]
public void PvdIndicator_MinHistoryDepths_CalculatedCorrectly()
{
var indicator = new PvdIndicator
{
PricePeriod = 10,
VolumePeriod = 20,
SmoothingPeriod = 5
};
// max(10,20) + 5 + 1 = 26
Assert.Equal(26, indicator.MinHistoryDepths);
Assert.Equal(26, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void PvdIndicator_MinHistoryDepths_DefaultValue()
{
var indicator = new PvdIndicator();
// max(14,14) + 3 + 1 = 18
Assert.Equal(18, indicator.MinHistoryDepths);
}
[Fact]
public void PvdIndicator_Initialize_CreatesInternalPvd()
{
var indicator = new PvdIndicator();
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void PvdIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new PvdIndicator
{
PricePeriod = 5,
VolumePeriod = 5,
SmoothingPeriod = 2
};
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 30; i++)
{
double close = 100 + i * 0.5;
double volume = 100000 + (i % 3 == 0 ? 20000 : -10000);
indicator.HistoricalData.AddBar(now.AddMinutes(i), close - 1, close + 1, close - 2, close, volume);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
}
double val = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
}
[Fact]
public void PvdIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new PvdIndicator
{
PricePeriod = 3,
VolumePeriod = 3,
SmoothingPeriod = 2
};
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 110, 90, 105, 100000);
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Add new bar
indicator.HistoricalData.AddBar(now.AddMinutes(10), 105, 115, 100, 112, 80000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(2, indicator.LinesSeries[0].Count);
}
[Fact]
public void PvdIndicator_PositiveDivergence_PriceUpVolumeDown()
{
var indicator = new PvdIndicator
{
PricePeriod = 2,
VolumePeriod = 2,
SmoothingPeriod = 1
};
indicator.Initialize();
var now = DateTime.UtcNow;
// Establish baseline with stable prices and volumes
indicator.HistoricalData.AddBar(now, 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
indicator.HistoricalData.AddBar(now.AddMinutes(2), 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
// Price up, volume down = positive divergence
indicator.HistoricalData.AddBar(now.AddMinutes(3), 108, 112, 105, 110, 70000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
double val = indicator.LinesSeries[0].GetValue(0);
Assert.True(val > 0, $"PVD should be positive when price up and volume down: {val}");
}
[Fact]
public void PvdIndicator_NegativeDivergence_PriceUpVolumeUp()
{
var indicator = new PvdIndicator
{
PricePeriod = 2,
VolumePeriod = 2,
SmoothingPeriod = 1
};
indicator.Initialize();
var now = DateTime.UtcNow;
// Establish baseline
indicator.HistoricalData.AddBar(now, 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
indicator.HistoricalData.AddBar(now.AddMinutes(2), 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
// Price up, volume up = negative (same direction, no divergence)
indicator.HistoricalData.AddBar(now.AddMinutes(3), 108, 112, 105, 110, 130000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
double val = indicator.LinesSeries[0].GetValue(0);
Assert.True(val < 0, $"PVD should be negative when price and volume move same direction: {val}");
}
[Fact]
public void PvdIndicator_NoDivergence_StablePriceAndVolume()
{
var indicator = new PvdIndicator
{
PricePeriod = 2,
VolumePeriod = 2,
SmoothingPeriod = 1
};
indicator.Initialize();
var now = DateTime.UtcNow;
// All bars with same values - no momentum
for (int i = 0; i < 10; i++)
{
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 100, 100000);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
double val = indicator.LinesSeries[0].GetValue(0);
Assert.Equal(0, val, precision: 5);
}
[Fact]
public void PvdIndicator_CustomPeriods_Applied()
{
var indicator = new PvdIndicator
{
PricePeriod = 5,
VolumePeriod = 10,
SmoothingPeriod = 3
};
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double close = 100 + i;
indicator.HistoricalData.AddBar(now.AddMinutes(i), close - 1, close + 2, close - 2, close, 100000 + i * 1000);
indicator.ProcessUpdate(new UpdateArgs(i == 0 ? UpdateReason.HistoricalBar : UpdateReason.NewBar));
}
double val = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
}
}