Files
QuanTAlib/lib/trends_IIR/yzvama/Yzvama.Validation.Tests.cs
T
2026-01-25 16:01:45 -08:00

98 lines
3.4 KiB
C#

namespace QuanTAlib.Tests;
/// <summary>
/// Validation tests for YZVAMA (Yang-Zhang Volatility Adjusted Moving Average).
/// Validates mathematical properties rather than comparing against an external library.
/// </summary>
public class YzvamaValidationTests
{
private const double Tolerance = 1e-10;
[Fact]
public void Yzvama_ZeroVolatility_EqualsMaxLength_SMA()
{
const int maxLength = 30;
var yzvama = new Yzvama(yzvShortPeriod: 3, yzvLongPeriod: 50, percentileLookback: 50, minLength: 5, maxLength: maxLength);
var sma = new Sma(maxLength);
// Using close-only input creates synthetic bars with O=H=L=C which yields yzv_short=0,
// thus percentile ~ 0 and adjusted length ~= maxLength.
var values = Enumerable.Range(1, 300).Select(i => (double)i).ToArray();
foreach (var val in values)
{
var tv = new TValue(DateTime.UtcNow, val);
yzvama.Update(tv, isNew: true);
sma.Update(tv, isNew: true);
}
Assert.Equal(sma.Last.Value, yzvama.Last.Value, 1.0);
}
[Fact]
public void Yzvama_ConstantInput_OutputEqualsInput()
{
var yzvama = new Yzvama();
const double constantValue = 42.5;
for (int i = 0; i < 300; i++)
{
yzvama.Update(new TValue(DateTime.UtcNow, constantValue), isNew: true);
}
Assert.Equal(constantValue, yzvama.Last.Value, Tolerance);
}
[Fact]
public void Yzvama_OutputWithinInputRange()
{
var yzvama = new Yzvama();
var gbm = new GBM(startPrice: 100, mu: 0.02, sigma: 0.2, seed: 123);
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
double minInput = double.MaxValue;
double maxInput = double.MinValue;
var outputs = new List<double>();
foreach (var bar in bars)
{
minInput = Math.Min(minInput, bar.Close);
maxInput = Math.Max(maxInput, bar.Close);
outputs.Add(yzvama.Update(bar, isNew: true).Value);
}
var hotOutputs = outputs.Skip(150).ToList();
foreach (var output in hotOutputs)
{
Assert.True(output >= minInput - 1 && output <= maxInput + 1,
$"Output {output} should be within input range [{minInput}, {maxInput}]");
}
}
[Fact]
public void Yzvama_VolatilitySpike_DrivesTowardMinLength()
{
// Prime with a stable low-volatility regime (yzv_short ~ 0 => percentile low => maxLength)
const int percentileLookback = 20;
const int minLength = 5;
const int maxLength = 50;
var yzvama = new Yzvama(yzvShortPeriod: 3, yzvLongPeriod: 50, percentileLookback: percentileLookback, minLength: minLength, maxLength: maxLength);
long t = DateTime.UtcNow.Ticks;
for (int i = 0; i < percentileLookback; i++)
{
var bar = new TBar(t + i, 100, 100, 100, 100, 0);
yzvama.Update(bar, isNew: true);
}
// One large-range bar should rank at the top of the volatility buffer (percentile ~= 100),
// which maps adjusted length to minLength. The SMA then uses the last minLength closes.
var spike = new TBar(t + percentileLookback, 100, 200, 50, 200, 0);
var result = yzvama.Update(spike, isNew: true);
// Last 5 closes: 100,100,100,100,200 => average = 120
Assert.Equal(120.0, result.Value, 1e-6);
}
}