mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-20 19:48:05 +00:00
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.
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user