mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-30 18:47:42 +00:00
060649192f
- 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
177 lines
5.5 KiB
C#
177 lines
5.5 KiB
C#
// 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);
|
|
}
|
|
}
|