feat: Add Prime method to various indicators for initializing state with historical data

- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes.
- The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator.
- Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity.
- Updated benchmark tests to use Batch methods for performance evaluation.
This commit is contained in:
Miha Kralj
2026-02-11 20:38:38 -08:00
parent 75c6a9f135
commit 653aafacd8
71 changed files with 10527 additions and 242 deletions
+226 -27
View File
@@ -4,6 +4,8 @@ namespace QuanTAlib.Tests;
public class VwapsdIndicatorTests
{
// ── Constructor & Defaults ──────────────────────────────────────────
[Fact]
public void VwapsdIndicator_Constructor_SetsDefaults()
{
@@ -16,6 +18,37 @@ public class VwapsdIndicatorTests
Assert.True(indicator.OnBackGround);
}
[Fact]
public void VwapsdIndicator_Constructor_Description_IsNotEmpty()
{
var indicator = new VwapsdIndicator();
Assert.False(string.IsNullOrWhiteSpace(indicator.Description));
Assert.Contains("volume", indicator.Description, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void VwapsdIndicator_Constructor_CreatesFourLineSeries()
{
var indicator = new VwapsdIndicator();
Assert.Equal(4, indicator.LinesSeries.Count);
}
[Fact]
public void VwapsdIndicator_Constructor_LineSeriesNames_BeforeInit()
{
var indicator = new VwapsdIndicator();
// Before OnInit, series have their constructor names
Assert.Equal("VWAP", indicator.LinesSeries[0].Name);
Assert.Equal("Upper", indicator.LinesSeries[1].Name);
Assert.Equal("Lower", indicator.LinesSeries[2].Name);
Assert.Equal("Width", indicator.LinesSeries[3].Name);
}
// ── MinHistoryDepths ────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_MinHistoryDepths_EqualsTwo()
{
@@ -25,6 +58,16 @@ public class VwapsdIndicatorTests
Assert.Equal(2, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
// ── ShortName ───────────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_ShortName_DefaultFormat()
{
var indicator = new VwapsdIndicator();
Assert.Equal("VWAPSD (2.0)", indicator.ShortName);
}
[Fact]
public void VwapsdIndicator_ShortName_IncludesNumDevs()
{
@@ -34,37 +77,92 @@ public class VwapsdIndicatorTests
Assert.Contains("2.5", indicator.ShortName, StringComparison.Ordinal);
}
// ── SourceCodeLink ──────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_Initialize_CreatesFourLineSeries()
public void VwapsdIndicator_SourceCodeLink_PointsToGitHub()
{
var indicator = new VwapsdIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Vwapsd.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
}
// ── OnInit σ Rename ─────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_Initialize_RenamesSeriesWithSigmaNotation()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
// After OnInit, Upper/Lower should have σ notation
Assert.Equal("Upper (+2.0σ)", indicator.LinesSeries[1].Name);
Assert.Equal("Lower (-2.0σ)", indicator.LinesSeries[2].Name);
}
[Fact]
public void VwapsdIndicator_Initialize_SigmaNotation_ReflectsNumDevs()
{
var indicator = new VwapsdIndicator { NumDevs = 1.5 };
indicator.Initialize();
Assert.Equal("Upper (+1.5σ)", indicator.LinesSeries[1].Name);
Assert.Equal("Lower (-1.5σ)", indicator.LinesSeries[2].Name);
}
[Fact]
public void VwapsdIndicator_Initialize_PreservesSeriesCount()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (VWAP, Upper, Lower, Width)
indicator.Initialize();
Assert.Equal(4, indicator.LinesSeries.Count);
}
// ── Parameters ──────────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_Parameters_CanBeChanged()
{
var indicator = new VwapsdIndicator { NumDevs = 1.5 };
Assert.Equal(1.5, indicator.NumDevs);
indicator.NumDevs = 2.5;
Assert.Equal(2.5, indicator.NumDevs);
}
[Fact]
public void VwapsdIndicator_ShowColdValues_CanBeChanged()
{
var indicator = new VwapsdIndicator();
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
}
// ── ProcessUpdate: HistoricalBar ────────────────────────────────────
[Fact]
public void VwapsdIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
// Add historical data with volume
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have values
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
// ── ProcessUpdate: NewBar ───────────────────────────────────────────
[Fact]
public void VwapsdIndicator_ProcessUpdate_NewBar_ComputesValue()
{
@@ -81,6 +179,8 @@ public class VwapsdIndicatorTests
Assert.Equal(2, indicator.LinesSeries[0].Count);
}
// ── ProcessUpdate: NewTick ──────────────────────────────────────────
[Fact]
public void VwapsdIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
@@ -100,6 +200,8 @@ public class VwapsdIndicatorTests
Assert.True(double.IsFinite(secondValue));
}
// ── MultipleUpdates ─────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_MultipleUpdates_ProducesCorrectSequence()
{
@@ -129,15 +231,7 @@ public class VwapsdIndicatorTests
Assert.True(lastVwap >= 95 && lastVwap <= 110);
}
[Fact]
public void VwapsdIndicator_Parameters_CanBeChanged()
{
var indicator = new VwapsdIndicator { NumDevs = 1.5 };
Assert.Equal(1.5, indicator.NumDevs);
indicator.NumDevs = 2.5;
Assert.Equal(2.5, indicator.NumDevs);
}
// ── AllBandsUpdate ──────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_AllBandsUpdate_Correctly()
@@ -161,6 +255,8 @@ public class VwapsdIndicatorTests
}
}
// ── BandRelationships ───────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_BandRelationships_AreCorrect()
{
@@ -168,7 +264,6 @@ public class VwapsdIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
// Add varied data to generate band width
double[] closes = { 100, 105, 95, 110, 90, 105, 100, 108, 92, 103 };
double[] volumes = { 1000, 1500, 2000, 1200, 1800, 1100, 1600, 1300, 1900, 1400 };
@@ -180,21 +275,19 @@ public class VwapsdIndicatorTests
now = now.AddMinutes(1);
}
// Get last values: VWAP=0, Upper=1, Lower=2, Width=3
double vwap = indicator.LinesSeries[0].GetValue(0);
double upper = indicator.LinesSeries[1].GetValue(0);
double lower = indicator.LinesSeries[2].GetValue(0);
double width = indicator.LinesSeries[3].GetValue(0);
// Band relationships: Upper > VWAP > Lower
Assert.True(upper >= vwap, $"Upper ({upper}) should be >= VWAP ({vwap})");
Assert.True(vwap >= lower, $"VWAP ({vwap}) should be >= Lower ({lower})");
// Width = Upper - Lower (2 × numDevs × StdDev)
Assert.True(Math.Abs(width - (upper - lower)) < 0.0001,
$"Width ({width}) should equal Upper - Lower ({upper - lower})");
}
// ── VolumeWeighting ─────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_VolumeWeighting_AffectsVwap()
{
@@ -205,9 +298,6 @@ public class VwapsdIndicatorTests
var now = DateTime.UtcNow;
// Same prices but different volume distributions
// Process both bars for each indicator
// Indicator1: high volume on low price, low volume on high price
indicator1.HistoricalData.AddBar(now, 100, 102, 98, 100, 10000);
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
@@ -223,11 +313,11 @@ public class VwapsdIndicatorTests
double vwap1 = indicator1.LinesSeries[0].GetValue(0);
double vwap2 = indicator2.LinesSeries[0].GetValue(0);
// VWAP1 should be lower (weighted toward 100 due to high volume at low price)
// VWAP2 should be higher (weighted toward 110 due to high volume at high price)
Assert.True(vwap1 < vwap2, $"VWAP1 ({vwap1}) should be less than VWAP2 ({vwap2}) due to volume weighting");
}
// ── NumDevs Effect ──────────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_NumDevs_AffectsBandWidth()
{
@@ -250,7 +340,6 @@ public class VwapsdIndicatorTests
now = now.AddMinutes(1);
}
// Width should be proportional to numDevs
double width1 = indicator1.LinesSeries[3].GetValue(0);
double width2 = indicator2.LinesSeries[3].GetValue(0);
@@ -258,4 +347,114 @@ public class VwapsdIndicatorTests
Assert.True(Math.Abs(width2 - 2 * width1) < 0.0001,
$"Width2 ({width2}) should be ~2x Width1 ({width1})");
}
// ── Width Non-Negative ──────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_Width_IsNonNegative()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
var now = DateTime.UtcNow;
double[] closes = { 100, 102, 98, 105, 97, 103, 101, 99 };
double[] volumes = { 1000, 1200, 800, 1500, 900, 1100, 1300, 700 };
for (int i = 0; i < closes.Length; i++)
{
double close = closes[i];
indicator.HistoricalData.AddBar(now, close, close + 2, close - 2, close, volumes[i]);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
now = now.AddMinutes(1);
}
// Width should be non-negative at every bar
for (int i = 0; i < closes.Length; i++)
{
double w = indicator.LinesSeries[3].GetValue(closes.Length - 1 - i);
Assert.True(w >= 0.0, $"Width at bar {i} ({w}) should be >= 0");
}
}
// ── SingleBar Zero Width ────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_SingleBar_ProducesZeroWidth()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// With only one bar, stddev is 0 → width should be 0
double width = indicator.LinesSeries[3].GetValue(0);
Assert.Equal(0.0, width, 4);
}
// ── ShowColdValues False ────────────────────────────────────────────
[Fact]
public void VwapsdIndicator_ShowColdValues_False_SuppressesColdValues()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0, ShowColdValues = false };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// With ShowColdValues=false, cold bars produce NaN
double vwap = indicator.LinesSeries[0].GetValue(0);
// Value is either NaN (suppressed) or finite (hot)
Assert.True(double.IsNaN(vwap) || double.IsFinite(vwap));
}
[Fact]
public void VwapsdIndicator_ShowColdValues_True_ShowsAllValues()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0, ShowColdValues = true };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// With ShowColdValues=true, all values should be finite
double vwap = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(vwap));
}
// ── ReInitialize Updates Series Names ───────────────────────────────
[Fact]
public void VwapsdIndicator_ReInitialize_UpdatesSigmaNotation()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
Assert.Equal("Upper (+2.0σ)", indicator.LinesSeries[1].Name);
Assert.Equal("Lower (-2.0σ)", indicator.LinesSeries[2].Name);
// Change NumDevs and re-init
indicator.NumDevs = 3.0;
indicator.Initialize();
Assert.Equal("Upper (+3.0σ)", indicator.LinesSeries[1].Name);
Assert.Equal("Lower (-3.0σ)", indicator.LinesSeries[2].Name);
}
// ── VWAP Series Name Unchanged After Init ───────────────────────────
[Fact]
public void VwapsdIndicator_Initialize_VwapAndWidthNames_Unchanged()
{
var indicator = new VwapsdIndicator { NumDevs = 2.0 };
indicator.Initialize();
// VWAP and Width series names should remain as constructor set them
Assert.Equal("VWAP", indicator.LinesSeries[0].Name);
Assert.Equal("Width", indicator.LinesSeries[3].Name);
}
}