mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 04:58:08 +00:00
docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files - Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.) - Move test files into tests/ subdirectories for consistent project structure - Add trader-focused bullet points to indicator documentation
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class VaIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void VaIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
|
||||
Assert.Equal("VA - Volume Accumulation", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
Assert.Equal(1, indicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_ShortName_IsConstant()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
Assert.Equal("VA", indicator.ShortName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_MinHistoryDepths_EqualsOne()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
|
||||
Assert.Equal(1, indicator.MinHistoryDepths);
|
||||
Assert.Equal(1, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_Initialize_CreatesInternalVa()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
|
||||
// Initialize should not throw
|
||||
indicator.Initialize();
|
||||
|
||||
// After init, line series should exist
|
||||
Assert.Single(indicator.LinesSeries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
double close = 100 + i * 0.5;
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), close - 2, close + 2, close - 3, close, 100000);
|
||||
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.True(double.IsFinite(val));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 30; 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(30), 105, 115, 100, 112, 80000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_CloseAboveMidpoint_PositiveAccumulation()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Bar: H=110, L=90, C=105, V=1000
|
||||
// midpoint = (110 + 90) / 2 = 100
|
||||
// va_period = 1000 * (105 - 100) = 5000
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(5000, val, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_CloseBelowMidpoint_NegativeAccumulation()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Bar: H=110, L=90, C=95, V=1000
|
||||
// midpoint = (110 + 90) / 2 = 100
|
||||
// va_period = 1000 * (95 - 100) = -5000
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 95, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(-5000, val, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_CloseAtMidpoint_ZeroAccumulation()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Bar: H=110, L=90, C=100, V=1000
|
||||
// midpoint = (110 + 90) / 2 = 100
|
||||
// va_period = 1000 * (100 - 100) = 0
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 100, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(0, val, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_MultipleBarAccumulation_CorrectSum()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Bar 1: midpoint=100, close=105, vol=1000 -> va=5000
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
// Bar 2: midpoint=100, close=95, vol=500 -> va_period=-2500, total=2500
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100, 110, 90, 95, 500);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(2500, val, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_LargeVolume_LargerImpact()
|
||||
{
|
||||
var indicator1 = new VaIndicator();
|
||||
indicator1.Initialize();
|
||||
|
||||
var indicator2 = new VaIndicator();
|
||||
indicator2.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Same price action, different volume
|
||||
indicator1.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
|
||||
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
indicator2.HistoricalData.AddBar(now, 100, 110, 90, 105, 10000);
|
||||
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val1 = indicator1.LinesSeries[0].GetValue(0);
|
||||
double val2 = indicator2.LinesSeries[0].GetValue(0);
|
||||
|
||||
// 10x volume should produce 10x VA
|
||||
Assert.Equal(val1 * 10, val2, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_CumulativeNature_AlwaysAccumulates()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
double lastVa = 0;
|
||||
|
||||
// Add multiple positive bars - VA should keep increasing
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 110, 90, 108, 1000);
|
||||
var args = i == 0
|
||||
? new UpdateArgs(UpdateReason.HistoricalBar)
|
||||
: new UpdateArgs(UpdateReason.NewBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
|
||||
double currentVa = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.True(currentVa > lastVa, $"VA should increase: {currentVa} > {lastVa}");
|
||||
lastVa = currentVa;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VaIndicator_MixedPressure_CorrectNetEffect()
|
||||
{
|
||||
var indicator = new VaIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Equal positive and negative with same volume should net to zero
|
||||
// Bar 1: +5000 (close above midpoint)
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
// Bar 2: -5000 (close below midpoint by same amount)
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100, 110, 90, 95, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(0, val, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class VaTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_CreatesValidIndicator()
|
||||
{
|
||||
var va = new Va();
|
||||
Assert.Equal("Va", va.Name);
|
||||
Assert.Equal(1, Va.WarmupPeriod);
|
||||
Assert.False(va.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_WithTBar_ReturnsValidValue()
|
||||
{
|
||||
var va = new Va();
|
||||
// Bar: H=110, L=90, C=105, V=1000
|
||||
// midpoint = (110 + 90) / 2 = 100
|
||||
// va_period = 1000 * (105 - 100) = 5000
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000);
|
||||
var result = va.Update(bar);
|
||||
Assert.Equal(5000, result.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_CloseAboveMidpoint_PositiveValue()
|
||||
{
|
||||
var va = new Va();
|
||||
// Close above midpoint = buying pressure = positive
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 108, 1000);
|
||||
// midpoint = 100, va = 1000 * (108 - 100) = 8000
|
||||
var result = va.Update(bar);
|
||||
Assert.True(result.Value > 0);
|
||||
Assert.Equal(8000, result.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_CloseBelowMidpoint_NegativeValue()
|
||||
{
|
||||
var va = new Va();
|
||||
// Close below midpoint = selling pressure = negative
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 92, 1000);
|
||||
// midpoint = 100, va = 1000 * (92 - 100) = -8000
|
||||
var result = va.Update(bar);
|
||||
Assert.True(result.Value < 0);
|
||||
Assert.Equal(-8000, result.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_CloseAtMidpoint_ZeroValue()
|
||||
{
|
||||
var va = new Va();
|
||||
// Close at midpoint = neutral
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 100, 1000);
|
||||
// midpoint = 100, va = 1000 * (100 - 100) = 0
|
||||
var result = va.Update(bar);
|
||||
Assert.Equal(0, result.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_MultipleValues_Accumulates()
|
||||
{
|
||||
var va = new Va();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
// Bar 1: midpoint=100, close=105, vol=1000 -> va=5000
|
||||
va.Update(new TBar(time, 100, 110, 90, 105, 1000));
|
||||
Assert.Equal(5000, va.Last.Value, 10);
|
||||
|
||||
// Bar 2: midpoint=100, close=95, vol=500 -> va_period=-2500, total=2500
|
||||
va.Update(new TBar(time.AddMinutes(1), 100, 110, 90, 95, 500));
|
||||
Assert.Equal(2500, va.Last.Value, 10);
|
||||
|
||||
// Bar 3: midpoint=100, close=100, vol=2000 -> va_period=0, total=2500
|
||||
va.Update(new TBar(time.AddMinutes(2), 100, 110, 90, 100, 2000));
|
||||
Assert.Equal(2500, va.Last.Value, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IsNewTrue_AdvancesState()
|
||||
{
|
||||
var va = new Va();
|
||||
var bar1 = new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000);
|
||||
var result1 = va.Update(bar1, isNew: true);
|
||||
|
||||
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 105, 115, 95, 110, 800);
|
||||
var result2 = va.Update(bar2, isNew: true);
|
||||
|
||||
Assert.NotEqual(result1.Time, result2.Time);
|
||||
Assert.NotEqual(result1.Value, result2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IsNewFalse_UpdatesCurrentBar()
|
||||
{
|
||||
var va = new Va();
|
||||
var gbm = new GBM(seed: 42);
|
||||
|
||||
// Build up history
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
va.Update(gbm.Next(), isNew: true);
|
||||
}
|
||||
|
||||
// New bar
|
||||
var bar1 = gbm.Next();
|
||||
va.Update(bar1, isNew: true);
|
||||
|
||||
// Correction - restore previous state
|
||||
va.Update(bar1, isNew: false);
|
||||
|
||||
// Value should change based on bar correction
|
||||
Assert.True(double.IsFinite(va.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IterativeCorrections_RestoresState()
|
||||
{
|
||||
var va = new Va();
|
||||
var gbm = new GBM(seed: 123);
|
||||
|
||||
// Build up history
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
va.Update(gbm.Next(), isNew: true);
|
||||
}
|
||||
|
||||
// New bar
|
||||
var originalBar = gbm.Next();
|
||||
va.Update(originalBar, isNew: true);
|
||||
|
||||
// Correction with same values using isNew=false should restore
|
||||
va.Update(originalBar, isNew: false);
|
||||
|
||||
Assert.True(double.IsFinite(va.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_WarmupPeriod_IsHotAfterFirstBar()
|
||||
{
|
||||
var va = new Va();
|
||||
Assert.False(va.IsHot);
|
||||
|
||||
va.Update(new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000), isNew: true);
|
||||
Assert.True(va.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_WithNaN_UsesLastValidValue()
|
||||
{
|
||||
var va = new Va();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
// Process valid bar first
|
||||
va.Update(new TBar(time, 100, 110, 90, 105, 1000));
|
||||
|
||||
// Process bar with NaN close
|
||||
var nanBar = new TBar(time.AddMinutes(1), 100, 110, 90, double.NaN, 500);
|
||||
var result = va.Update(nanBar);
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var va = new Va();
|
||||
var gbm = new GBM(seed: 42);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
va.Update(gbm.Next(), isNew: true);
|
||||
}
|
||||
|
||||
Assert.True(va.IsHot);
|
||||
Assert.NotEqual(0, va.Last.Value);
|
||||
|
||||
va.Reset();
|
||||
|
||||
Assert.False(va.IsHot);
|
||||
Assert.Equal(default, va.Last);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchCalculate_MatchesStreaming()
|
||||
{
|
||||
var bars = new TBarSeries();
|
||||
var gbm = new GBM(seed: 42);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
bars.Add(gbm.Next());
|
||||
}
|
||||
|
||||
// Streaming
|
||||
var va = new Va();
|
||||
var streamingValues = new List<double>();
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
streamingValues.Add(va.Update(bar).Value);
|
||||
}
|
||||
|
||||
// Batch
|
||||
var batchResult = Va.Batch(bars);
|
||||
|
||||
Assert.Equal(bars.Count, batchResult.Count);
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingValues[i], batchResult[i].Value, 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalculate_MatchesStreaming()
|
||||
{
|
||||
var gbm = new GBM(seed: 42);
|
||||
int count = 100;
|
||||
var high = new double[count];
|
||||
var low = new double[count];
|
||||
var close = new double[count];
|
||||
var volume = new double[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var bar = gbm.Next();
|
||||
high[i] = bar.High;
|
||||
low[i] = bar.Low;
|
||||
close[i] = bar.Close;
|
||||
volume[i] = bar.Volume;
|
||||
}
|
||||
|
||||
// Streaming
|
||||
var va = new Va();
|
||||
var streamingValues = new List<double>();
|
||||
var time = DateTime.UtcNow;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
streamingValues.Add(va.Update(new TBar(time.AddMinutes(i), 0, high[i], low[i], close[i], volume[i])).Value);
|
||||
}
|
||||
|
||||
// Span
|
||||
var output = new double[count];
|
||||
Va.Batch(high, low, close, volume, output);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Assert.Equal(streamingValues[i], output[i], 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalculate_InvalidLengths_ThrowsArgumentException()
|
||||
{
|
||||
var high = new double[100];
|
||||
var low = new double[100];
|
||||
var close = new double[100];
|
||||
var volume = new double[99]; // Different length
|
||||
var output = new double[100];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Va.Batch(high, low, close, volume, output));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalculate_EmptyInput_HandlesGracefully()
|
||||
{
|
||||
var high = Array.Empty<double>();
|
||||
var low = Array.Empty<double>();
|
||||
var close = Array.Empty<double>();
|
||||
var volume = Array.Empty<double>();
|
||||
var output = Array.Empty<double>();
|
||||
|
||||
Va.Batch(high, low, close, volume, output);
|
||||
|
||||
Assert.Empty(output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Event_PubFiresOnUpdate()
|
||||
{
|
||||
var va = new Va();
|
||||
TValue? receivedValue = null;
|
||||
bool receivedIsNew = false;
|
||||
|
||||
va.Pub += (object? sender, in TValueEventArgs args) =>
|
||||
{
|
||||
receivedValue = args.Value;
|
||||
receivedIsNew = args.IsNew;
|
||||
};
|
||||
|
||||
va.Update(new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000), isNew: true);
|
||||
|
||||
Assert.NotNull(receivedValue);
|
||||
Assert.True(receivedIsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargeDataset_HandlesWithoutError()
|
||||
{
|
||||
var bars = new TBarSeries();
|
||||
var gbm = new GBM(seed: 42);
|
||||
|
||||
for (int i = 0; i < 10000; i++)
|
||||
{
|
||||
bars.Add(gbm.Next());
|
||||
}
|
||||
|
||||
var va = new Va();
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
var result = va.Update(bar);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
Assert.True(va.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaVerification_ManualCalculation()
|
||||
{
|
||||
var va = new Va();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
// Bar 1: H=110, L=90, C=105, V=1000
|
||||
// midpoint = (110+90)/2 = 100
|
||||
// va_period = 1000 * (105 - 100) = 5000
|
||||
va.Update(new TBar(time, 100, 110, 90, 105, 1000));
|
||||
Assert.Equal(5000, va.Last.Value, 10);
|
||||
|
||||
// Bar 2: H=120, L=100, C=115, V=2000
|
||||
// midpoint = (120+100)/2 = 110
|
||||
// va_period = 2000 * (115 - 110) = 10000
|
||||
// total = 5000 + 10000 = 15000
|
||||
va.Update(new TBar(time.AddMinutes(1), 100, 120, 100, 115, 2000));
|
||||
Assert.Equal(15000, va.Last.Value, 10);
|
||||
|
||||
// Bar 3: H=115, L=95, C=98, V=1500
|
||||
// midpoint = (115+95)/2 = 105
|
||||
// va_period = 1500 * (98 - 105) = -10500
|
||||
// total = 15000 - 10500 = 4500
|
||||
va.Update(new TBar(time.AddMinutes(2), 100, 115, 95, 98, 1500));
|
||||
Assert.Equal(4500, va.Last.Value, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Va: Mathematical property validation tests
|
||||
// Volume Accumulation is a cumulative indicator. No standard external library equivalents
|
||||
// with matching implementation. Validation uses mathematical property testing.
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
using Xunit;
|
||||
|
||||
public class VaValidationTests
|
||||
{
|
||||
private const int TestDataLength = 500;
|
||||
|
||||
[Fact]
|
||||
public void Va_Output_IsFiniteForGbmData()
|
||||
{
|
||||
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var va = new Va();
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
var result = va.Update(bars[i], isNew: true);
|
||||
Assert.True(double.IsFinite(result.Value),
|
||||
$"Va output must be finite at bar {i}, got {result.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_CloseAboveMidpoint_PositiveAccumulation()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Close is above midpoint: (H+L)/2 = 100, Close = 102
|
||||
var bar = new TBar(DateTime.UtcNow, 101, 101, 99, 102, 1000);
|
||||
var result = va.Update(bar, isNew: true);
|
||||
|
||||
// VA_period = volume * (close - midpoint) = 1000 * (102 - 100) = 2000
|
||||
Assert.True(result.Value > 0,
|
||||
$"VA should be positive when close > midpoint, got {result.Value}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_CloseBelowMidpoint_NegativeAccumulation()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Close is below midpoint: (H+L)/2 = 100, Close = 98
|
||||
var bar = new TBar(DateTime.UtcNow, 101, 101, 99, 98, 1000);
|
||||
var result = va.Update(bar, isNew: true);
|
||||
|
||||
// VA_period = volume * (close - midpoint) = 1000 * (98 - 100) = -2000
|
||||
Assert.True(result.Value < 0,
|
||||
$"VA should be negative when close < midpoint, got {result.Value}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_CloseAtMidpoint_ZeroAccumulation()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Close is exactly at midpoint
|
||||
var bar = new TBar(DateTime.UtcNow, 101, 101, 99, 100, 1000);
|
||||
var result = va.Update(bar, isNew: true);
|
||||
|
||||
Assert.Equal(0.0, result.Value, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_ZeroVolume_ZeroAccumulation()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Even with close above midpoint, zero volume = zero VA contribution
|
||||
var bar = new TBar(DateTime.UtcNow, 101, 101, 99, 102, 0);
|
||||
var result = va.Update(bar, isNew: true);
|
||||
|
||||
Assert.Equal(0.0, result.Value, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_IsCumulative_AccumulatesOverBars()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Bar 1: close above midpoint
|
||||
var bar1 = new TBar(DateTime.UtcNow, 101, 101, 99, 102, 1000);
|
||||
var r1 = va.Update(bar1, isNew: true);
|
||||
double expectedVa1 = 1000 * (102 - 100.0); // 2000
|
||||
|
||||
// Bar 2: close below midpoint
|
||||
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 101, 101, 99, 98, 500);
|
||||
var r2 = va.Update(bar2, isNew: true);
|
||||
double expectedVa2 = expectedVa1 + 500 * (98 - 100.0); // 2000 + (-1000) = 1000
|
||||
|
||||
Assert.Equal(expectedVa1, r1.Value, precision: 10);
|
||||
Assert.Equal(expectedVa2, r2.Value, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_KnownCalculation_MatchesManual()
|
||||
{
|
||||
var va = new Va();
|
||||
|
||||
// Manually verified calculation
|
||||
// Bar: O=100, H=105, L=95, C=103, V=2000
|
||||
// Midpoint = (105 + 95) / 2 = 100
|
||||
// VA_period = 2000 * (103 - 100) = 6000
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 103, 2000);
|
||||
var result = va.Update(bar, isNew: true);
|
||||
|
||||
Assert.Equal(6000.0, result.Value, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_BatchAndStreaming_ProduceSameResults()
|
||||
{
|
||||
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Batch
|
||||
var batchResults = Va.Batch(bars);
|
||||
|
||||
// Streaming
|
||||
var streamVa = new Va();
|
||||
var streamResults = new double[bars.Count];
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
var result = streamVa.Update(bars[i], isNew: true);
|
||||
streamResults[i] = result.Value;
|
||||
}
|
||||
|
||||
Assert.Equal(batchResults.Count, bars.Count);
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResults.Values[i], streamResults[i], precision: 8);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_SpanAndStreaming_ProduceSameResults()
|
||||
{
|
||||
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var spanOutput = new double[bars.Count];
|
||||
|
||||
Va.Batch(
|
||||
bars.High.Values, bars.Low.Values,
|
||||
bars.Close.Values, bars.Volume.Values,
|
||||
spanOutput);
|
||||
|
||||
// Streaming
|
||||
var streamVa = new Va();
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
var result = streamVa.Update(bars[i], isNew: true);
|
||||
Assert.Equal(spanOutput[i], result.Value, precision: 8);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Va_BarCorrection_IsNewFalse_RestoresState()
|
||||
{
|
||||
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var va = new Va();
|
||||
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
va.Update(bars[i], isNew: true);
|
||||
}
|
||||
|
||||
va.Update(bars[30], isNew: true);
|
||||
double afterNew = va.Last.Value;
|
||||
|
||||
va.Update(bars[30], isNew: false);
|
||||
double afterCorrection = va.Last.Value;
|
||||
|
||||
Assert.Equal(afterNew, afterCorrection, precision: 10);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user