mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-05 20:47:43 +00:00
951842acca
- Introduced Massi validation tests to ensure mathematical properties hold for the Mass Index indicator. - Added Va validation tests for Volume Accumulation, checking for finite outputs and correct accumulation behavior. - Implemented Vf validation tests for Volume Force, verifying outputs for rising and falling prices, and ensuring batch and streaming results match. - Created Vo validation tests for Volume Oscillator, confirming behavior with constant, increasing, and decreasing volumes. - Developed Vroc validation tests for Volume Rate of Change, validating outputs for constant volume and changes in volume. - Updated project file to include new momentum indicators (MACD and RSI) in the compilation.
199 lines
6.5 KiB
C#
199 lines
6.5 KiB
C#
// Vo: Mathematical property validation tests
|
|
// Volume Oscillator compares short and long SMAs of volume.
|
|
// No standard external library equivalents with matching implementation.
|
|
// Validation uses mathematical property testing.
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
using Xunit;
|
|
|
|
public class VoValidationTests
|
|
{
|
|
private const int DefaultShortPeriod = 5;
|
|
private const int DefaultLongPeriod = 10;
|
|
private const int DefaultSignalPeriod = 10;
|
|
private const int TestDataLength = 500;
|
|
|
|
[Fact]
|
|
public void Vo_Output_IsFiniteForGbmData()
|
|
{
|
|
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
var result = vo.Update(bars[i], isNew: true);
|
|
Assert.True(double.IsFinite(result.Value),
|
|
$"Vo output must be finite at bar {i}, got {result.Value}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_ConstantVolume_ZeroOscillator()
|
|
{
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
// Feed bars with identical volume
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var bar = new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
100, 101, 99, 100, 1000); // constant volume
|
|
vo.Update(bar, isNew: true);
|
|
}
|
|
|
|
// When volume is constant, short MA == long MA, VO = 0
|
|
Assert.Equal(0.0, vo.Last.Value, precision: 8);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_IncreasingVolume_PositiveOscillator()
|
|
{
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
// Feed bars with steadily increasing volume
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
double volume = 1000 + i * 100; // increasing
|
|
var bar = new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
100, 101, 99, 100, volume);
|
|
vo.Update(bar, isNew: true);
|
|
}
|
|
|
|
// Short MA should be higher than long MA when volume is increasing
|
|
Assert.True(vo.Last.Value > 0,
|
|
$"VO should be positive with increasing volume, got {vo.Last.Value}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_DecreasingVolume_NegativeOscillator()
|
|
{
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
// Feed bars with steadily decreasing volume
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
double volume = 10000 - i * 100; // decreasing
|
|
var bar = new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
100, 101, 99, 100, volume);
|
|
vo.Update(bar, isNew: true);
|
|
}
|
|
|
|
// Short MA should be lower than long MA when volume is decreasing
|
|
Assert.True(vo.Last.Value < 0,
|
|
$"VO should be negative with decreasing volume, got {vo.Last.Value}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_Signal_IsFinite()
|
|
{
|
|
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
vo.Update(bars[i], isNew: true);
|
|
Assert.True(double.IsFinite(vo.Signal),
|
|
$"Signal must be finite at bar {i}, got {vo.Signal}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_ConstantVolume_SignalAlsoZero()
|
|
{
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var bar = new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
100, 101, 99, 100, 1000);
|
|
vo.Update(bar, isNew: true);
|
|
}
|
|
|
|
// Signal is SMA of VO values, all of which are zero
|
|
Assert.Equal(0.0, vo.Signal, precision: 8);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_BatchAndStreaming_ProduceSameResults()
|
|
{
|
|
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(TestDataLength, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
|
|
// Batch
|
|
var batchResults = Vo.Batch(bars, DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
// Streaming
|
|
var streamVo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
var streamResults = new double[bars.Count];
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
var result = streamVo.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 Vo_DifferentPeriods_ProduceDifferentResults()
|
|
{
|
|
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
|
|
var vo1 = new Vo(3, 7, 5);
|
|
var vo2 = new Vo(10, 30, 15);
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
vo1.Update(bars[i], isNew: true);
|
|
vo2.Update(bars[i], isNew: true);
|
|
}
|
|
|
|
Assert.NotEqual(vo1.Last.Value, vo2.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_BarCorrection_IsNewFalse_RestoresState()
|
|
{
|
|
var bars = new GBM(sigma: 0.5, seed: 123).Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
vo.Update(bars[i], isNew: true);
|
|
}
|
|
|
|
vo.Update(bars[30], isNew: true);
|
|
double afterNew = vo.Last.Value;
|
|
|
|
vo.Update(bars[30], isNew: false);
|
|
double afterCorrection = vo.Last.Value;
|
|
|
|
Assert.Equal(afterNew, afterCorrection, precision: 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vo_IsHot_AfterLongPeriod()
|
|
{
|
|
var vo = new Vo(DefaultShortPeriod, DefaultLongPeriod, DefaultSignalPeriod);
|
|
|
|
for (int i = 0; i < DefaultLongPeriod - 1; i++)
|
|
{
|
|
var bar = new TBar(DateTime.UtcNow.AddMinutes(i), 100, 101, 99, 100, 1000);
|
|
vo.Update(bar, isNew: true);
|
|
Assert.False(vo.IsHot, $"Should not be hot at bar {i}");
|
|
}
|
|
|
|
// Bar at index longPeriod-1 should make it hot (Index becomes longPeriod)
|
|
var finalBar = new TBar(DateTime.UtcNow.AddMinutes(DefaultLongPeriod), 100, 101, 99, 100, 1000);
|
|
vo.Update(finalBar, isNew: true);
|
|
Assert.True(vo.IsHot, "Should be hot after longPeriod bars");
|
|
}
|
|
}
|