mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-30 02:27:43 +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
133 lines
4.4 KiB
C#
133 lines
4.4 KiB
C#
namespace QuanTAlib.Validation;
|
|
|
|
/// <summary>
|
|
/// Validation tests for ZTEST indicator.
|
|
/// No direct TA-Lib/Tulip/Skender/Ooples equivalent exists for one-sample t-test.
|
|
/// Validates against manual computation, mathematical properties, and ZSCORE relationship.
|
|
/// </summary>
|
|
public sealed class ZtestValidationTests
|
|
{
|
|
[Fact]
|
|
public void Ztest_ManualComputation_MatchesPineScript()
|
|
{
|
|
// PineScript formula: t = (mean - mu0) / (sampleStdDev / sqrt(n))
|
|
// Data: {10, 20, 30, 40, 50}, period=5, mu0=0
|
|
// mean = 30, popVar = 1000/5 = 200, sampleVar = 200*5/4 = 250
|
|
// sampleStdDev = sqrt(250) ≈ 15.8114
|
|
// SE = sqrt(250)/sqrt(5) = sqrt(50) ≈ 7.0711
|
|
// t = 30 / sqrt(50) = 30*sqrt(2)/10 = 3*sqrt(2) ≈ 4.2426
|
|
var z = new Ztest(5, 0.0);
|
|
double[] data = [10, 20, 30, 40, 50];
|
|
foreach (double d in data)
|
|
{
|
|
z.Update(new TValue(DateTime.UtcNow, d));
|
|
}
|
|
|
|
double expected = 30.0 / Math.Sqrt(50.0);
|
|
Assert.Equal(expected, z.Last.Value, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ztest_GBMData_BoundedRange()
|
|
{
|
|
// For GBM-generated data with mu0=0, t-stats should be far from zero for prices
|
|
// but still finite
|
|
int period = 20;
|
|
var z = new Ztest(period, 0.0);
|
|
var rng = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
|
|
|
|
for (int i = 0; i < 200; i++)
|
|
{
|
|
TBar bar = rng.Next();
|
|
z.Update(new TValue(bar.Time, bar.Close));
|
|
|
|
if (z.IsHot)
|
|
{
|
|
Assert.True(double.IsFinite(z.Last.Value),
|
|
$"t-stat not finite at i={i}");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Ztest_ScalingProperty_Mu0ScalesToo()
|
|
{
|
|
// If we scale data by factor a and mu0 by same factor a,
|
|
// t-statistic should remain the same (scale-invariant when mu0 scales too)
|
|
int period = 10;
|
|
double mu0 = 5.0;
|
|
double scale = 3.0;
|
|
var z1 = new Ztest(period, mu0);
|
|
var z2 = new Ztest(period, mu0 * scale);
|
|
var rng = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 88);
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
double val = rng.Next().Close;
|
|
z1.Update(new TValue(DateTime.UtcNow, val));
|
|
z2.Update(new TValue(DateTime.UtcNow, val * scale));
|
|
|
|
if (z1.IsHot && z2.IsHot)
|
|
{
|
|
Assert.Equal(z1.Last.Value, z2.Last.Value, 1e-4); // scaled values amplify FP accumulation drift
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Ztest_RelationToZscore_CorrectRatio()
|
|
{
|
|
// ZTEST(mu0=mean) = 0 while ZSCORE tests individual value vs mean
|
|
// When mu0=0: t = mean / SE = mean / (s/sqrt(n))
|
|
// zscore = (last_value - mean) / pop_stddev
|
|
// Relationship: t = mean * sqrt(n) / s = mean * sqrt(n) / (pop_sd * sqrt(n/(n-1)))
|
|
// = mean * sqrt(n-1) / pop_sd
|
|
int period = 10;
|
|
var zt = new Ztest(period, 0.0);
|
|
var rng = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 99);
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
double val = rng.Next().Close;
|
|
zt.Update(new TValue(DateTime.UtcNow, val));
|
|
}
|
|
|
|
// Just verify finite and non-zero for prices with mu0=0
|
|
Assert.True(double.IsFinite(zt.Last.Value));
|
|
Assert.NotEqual(0.0, zt.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ztest_SignProperty_MatchesMeanVsMu0()
|
|
{
|
|
// t-stat sign must match sign of (mean - mu0)
|
|
int period = 10;
|
|
var rng = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 77);
|
|
|
|
var source = new TSeries(30);
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
TBar bar = rng.Next();
|
|
source.Add(new TValue(bar.Time, bar.Close), true);
|
|
}
|
|
|
|
// With mu0 = 0 and price data around 100, mean >> mu0, so t should be positive
|
|
var z = new Ztest(period, 0.0);
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
z.Update(source[i]);
|
|
}
|
|
|
|
Assert.True(z.Last.Value > 0, "t-stat should be positive when mean >> mu0=0");
|
|
|
|
// With mu0 = 10000, mean << mu0, so t should be negative
|
|
var z2 = new Ztest(period, 10000.0);
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
z2.Update(source[i]);
|
|
}
|
|
|
|
Assert.True(z2.Last.Value < 0, "t-stat should be negative when mean << mu0=10000");
|
|
}
|
|
}
|