mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-04 12:07:44 +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
330 lines
12 KiB
C#
330 lines
12 KiB
C#
using TradingPlatform.BusinessLayer;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class VovIndicatorTests
|
|
{
|
|
[Fact]
|
|
public void VovIndicator_Constructor_SetsDefaults()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
|
|
Assert.Equal(20, indicator.VolatilityPeriod);
|
|
Assert.Equal(10, indicator.VovPeriod);
|
|
Assert.True(indicator.ShowColdValues);
|
|
Assert.Equal("VOV - Volatility of Volatility", indicator.Name);
|
|
Assert.True(indicator.SeparateWindow);
|
|
Assert.True(indicator.OnBackGround);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ShortName_IncludesParameters()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 30, VovPeriod = 15 };
|
|
Assert.Contains("VOV", indicator.ShortName, StringComparison.Ordinal);
|
|
Assert.Contains("30", indicator.ShortName, StringComparison.Ordinal);
|
|
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_MinHistoryDepths_EqualsZero()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
|
|
Assert.Equal(0, VovIndicator.MinHistoryDepths);
|
|
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_Initialize_CreatesInternalVov()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
|
|
// Initialize should not throw
|
|
indicator.Initialize();
|
|
|
|
// After init, line series should exist
|
|
Assert.Single(indicator.LinesSeries);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 10, VovPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
// Add historical data with varying volatility
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
// Create price movement that generates volatility
|
|
double basePrice = 100 + Math.Sin(i * 0.3) * (5 + i * 0.1);
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 2, basePrice - 2, basePrice, 1000);
|
|
|
|
// Process update for each bar to simulate history loading
|
|
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
|
indicator.ProcessUpdate(args);
|
|
}
|
|
|
|
// Line series should have a value
|
|
double val = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(double.IsFinite(val));
|
|
Assert.True(val >= 0, "VOV should be non-negative");
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ProcessUpdate_NewBar_ComputesValue()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 10, VovPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
double basePrice = 100 + i;
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 2, basePrice - 2, basePrice + 1, 1000);
|
|
}
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
// Add new bar
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(30), 130, 135, 125, 132, 1500);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
|
|
|
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_DifferentPeriods_Work()
|
|
{
|
|
var periodCombos = new[] { (5, 3), (10, 5), (20, 10), (30, 15) };
|
|
|
|
foreach (var (volPeriod, vovPeriod) in periodCombos)
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = volPeriod, VovPeriod = vovPeriod };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 60; i++)
|
|
{
|
|
// Create price movement with varying amplitude
|
|
double basePrice = 100 + Math.Sin(i * 0.2) * 5;
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 2, basePrice - 2, basePrice, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double val = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(double.IsFinite(val), $"Periods ({volPeriod},{vovPeriod}) should produce finite value");
|
|
Assert.True(val >= 0, $"Periods ({volPeriod},{vovPeriod}) should produce non-negative value");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_VolatilityPeriod_CanBeChanged()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
Assert.Equal(20, indicator.VolatilityPeriod);
|
|
|
|
indicator.VolatilityPeriod = 30;
|
|
Assert.Equal(30, indicator.VolatilityPeriod);
|
|
|
|
indicator.VolatilityPeriod = 10;
|
|
Assert.Equal(10, indicator.VolatilityPeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_VovPeriod_CanBeChanged()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
Assert.Equal(10, indicator.VovPeriod);
|
|
|
|
indicator.VovPeriod = 15;
|
|
Assert.Equal(15, indicator.VovPeriod);
|
|
|
|
indicator.VovPeriod = 5;
|
|
Assert.Equal(5, indicator.VovPeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ShowColdValues_CanBeToggled()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
Assert.True(indicator.ShowColdValues);
|
|
|
|
indicator.ShowColdValues = false;
|
|
Assert.False(indicator.ShowColdValues);
|
|
|
|
indicator.ShowColdValues = true;
|
|
Assert.True(indicator.ShowColdValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_SourceCodeLink_IsValid()
|
|
{
|
|
var indicator = new VovIndicator();
|
|
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
|
Assert.Contains("Vov.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ConstantPrice_ProducesZero()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 10, VovPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Constant price - no volatility
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 100.01, 99.99, 100, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double val = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(double.IsFinite(val));
|
|
Assert.True(val < 0.1, "Constant price should produce near-zero VOV");
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_ChangingVolatility_ProducesPositiveValue()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 5, VovPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Low volatility period
|
|
for (int i = 0; i < 15; i++)
|
|
{
|
|
double price = 100 + (i % 2) * 0.5; // Small oscillations
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 0.5, price - 0.5, price, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// High volatility period
|
|
for (int i = 15; i < 30; i++)
|
|
{
|
|
double price = 100 + (i % 2) * 10; // Large oscillations
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 5, price - 5, price, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double val = indicator.LinesSeries[0].GetValue(0);
|
|
Assert.True(double.IsFinite(val));
|
|
Assert.True(val > 0, "Changing volatility should produce positive VOV value");
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_UsesClosePrice_ForCalculation()
|
|
{
|
|
// VOV uses close price for volatility calculation
|
|
var indicator = new VovIndicator { VolatilityPeriod = 5, VovPeriod = 3 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Price with varying close but constant OHLC range
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
double close = 100 + Math.Sin(i * 0.5) * 5; // Varying close
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 110, 90, close, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double val = indicator.LinesSeries[0].GetValue(0);
|
|
|
|
Assert.True(double.IsFinite(val));
|
|
Assert.True(val >= 0, "VOV should be non-negative");
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_LargerVolatilityPeriod_SmootherOutput()
|
|
{
|
|
var indicator1 = new VovIndicator { VolatilityPeriod = 5, VovPeriod = 5 };
|
|
var indicator2 = new VovIndicator { VolatilityPeriod = 20, VovPeriod = 5 };
|
|
indicator1.Initialize();
|
|
indicator2.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
var results1 = new List<double>();
|
|
var results2 = new List<double>();
|
|
|
|
for (int i = 0; i < 60; i++)
|
|
{
|
|
double price = 100 + Math.Sin(i * 0.3) * 5;
|
|
indicator1.HistoricalData.AddBar(now.AddMinutes(i), price, price + 2, price - 2, price, 1000);
|
|
indicator2.HistoricalData.AddBar(now.AddMinutes(i), price, price + 2, price - 2, price, 1000);
|
|
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
if (i >= 25) // After both are fully warmed up
|
|
{
|
|
results1.Add(indicator1.LinesSeries[0].GetValue(0));
|
|
results2.Add(indicator2.LinesSeries[0].GetValue(0));
|
|
}
|
|
}
|
|
|
|
// Calculate variance of changes
|
|
double variance1 = CalculateChangeVariance(results1);
|
|
double variance2 = CalculateChangeVariance(results2);
|
|
|
|
// Longer volatility period should be smoother
|
|
Assert.True(variance2 <= variance1 * 1.5, // Allow some tolerance
|
|
$"Longer period should be smoother: short variance={variance1:F6}, long variance={variance2:F6}");
|
|
}
|
|
|
|
private static double CalculateChangeVariance(List<double> values)
|
|
{
|
|
if (values.Count < 2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var changes = new List<double>();
|
|
for (int i = 1; i < values.Count; i++)
|
|
{
|
|
changes.Add(values[i] - values[i - 1]);
|
|
}
|
|
|
|
double mean = changes.Average();
|
|
double variance = changes.Select(c => (c - mean) * (c - mean)).Average();
|
|
return variance;
|
|
}
|
|
|
|
[Fact]
|
|
public void VovIndicator_VolatilityRegimeChange_RespondsCorrectly()
|
|
{
|
|
var indicator = new VovIndicator { VolatilityPeriod = 5, VovPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Stable volatility regime
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
double price = 100 + Math.Sin(i * 0.5) * 2;
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 1, price - 1, price, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double stableVal = indicator.LinesSeries[0].GetValue(0);
|
|
|
|
// Transition to variable volatility
|
|
for (int i = 20; i < 40; i++)
|
|
{
|
|
double amplitude = 2 + (i - 20) * 0.5; // Increasing amplitude
|
|
double price = 100 + Math.Sin(i * 0.5) * amplitude;
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + amplitude, price - amplitude, price, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double transitionVal = indicator.LinesSeries[0].GetValue(0);
|
|
|
|
Assert.True(double.IsFinite(stableVal));
|
|
Assert.True(double.IsFinite(transitionVal));
|
|
// During volatility regime change, VOV should typically increase
|
|
Assert.True(transitionVal > 0, "Changing volatility regime should produce positive VOV");
|
|
}
|
|
}
|