mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 01:28:05 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user