mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-31 02:47: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
175 lines
5.5 KiB
C#
175 lines
5.5 KiB
C#
namespace QuanTAlib.Tests;
|
|
|
|
public class DecoValidationTests
|
|
{
|
|
private const double Tolerance = 1e-10;
|
|
|
|
[Fact]
|
|
public void StreamingMatchesBatch_DefaultParams()
|
|
{
|
|
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 42);
|
|
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
TSeries source = bars.Close;
|
|
|
|
// Streaming
|
|
var deco = new Deco(30, 60);
|
|
var streaming = new double[source.Count];
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
streaming[i] = deco.Update(source[i]).Value;
|
|
}
|
|
|
|
// Batch span
|
|
var batch = new double[source.Count];
|
|
Deco.Batch(source.Values, batch, 30, 60);
|
|
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
Assert.Equal(batch[i], streaming[i], Tolerance);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void StreamingMatchesBatch_ShortPeriods()
|
|
{
|
|
var gbm = new GBM(startPrice: 50.0, mu: 0.01, sigma: 0.3, seed: 7);
|
|
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
TSeries source = bars.Close;
|
|
|
|
var deco = new Deco(5, 15);
|
|
var streaming = new double[source.Count];
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
streaming[i] = deco.Update(source[i]).Value;
|
|
}
|
|
|
|
var batch = new double[source.Count];
|
|
Deco.Batch(source.Values, batch, 5, 15);
|
|
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
Assert.Equal(batch[i], streaming[i], Tolerance);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ConstantPrice_OscillatesAtZero()
|
|
{
|
|
var source = new TSeries();
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), 50.0));
|
|
}
|
|
|
|
var deco = new Deco(10, 20);
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
var result = deco.Update(source[i]);
|
|
if (i >= 2)
|
|
{
|
|
Assert.Equal(0.0, result.Value, Tolerance);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Deterministic_SameInputSameOutput()
|
|
{
|
|
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 123);
|
|
var bars = gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
TSeries source = bars.Close;
|
|
|
|
var deco1 = new Deco(10, 30);
|
|
var deco2 = new Deco(10, 30);
|
|
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
var r1 = deco1.Update(source[i]);
|
|
var r2 = deco2.Update(source[i]);
|
|
Assert.Equal(r1.Value, r2.Value, Tolerance);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DirectionalCorrectness_UpTrend()
|
|
{
|
|
// Exponential growth produces non-zero HP output (linear ramp has zero second-difference)
|
|
var deco = new Deco(5, 10);
|
|
double lastVal = 0;
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var result = deco.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 * Math.Exp(0.02 * i)));
|
|
lastVal = result.Value;
|
|
}
|
|
// Exponential uptrend produces non-zero DECO
|
|
Assert.NotEqual(0.0, lastVal);
|
|
}
|
|
|
|
[Fact]
|
|
public void SymmetryCheck_OppositeInputs()
|
|
{
|
|
// Sinusoidal inputs with opposite phase should produce opposite-sign DECO values
|
|
var decoUp = new Deco(5, 10);
|
|
var decoDown = new Deco(5, 10);
|
|
|
|
double lastUp = 0, lastDown = 0;
|
|
for (int i = 0; i < 60; i++)
|
|
{
|
|
double phase = 2.0 * Math.PI * i / 20.0; // period=20 bars
|
|
var rUp = decoUp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + 10.0 * Math.Sin(phase)));
|
|
var rDown = decoDown.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 - 10.0 * Math.Sin(phase)));
|
|
lastUp = rUp.Value;
|
|
lastDown = rDown.Value;
|
|
}
|
|
// Opposite-phase sinusoidal inputs should produce opposite-sign DECO values
|
|
Assert.True(lastUp * lastDown < 0,
|
|
$"Expected opposite signs: up={lastUp}, down={lastDown}");
|
|
}
|
|
|
|
[Fact]
|
|
public void HpFilter_Components_SumCorrectly()
|
|
{
|
|
// Verify that the HP_long and HP_short filters produce sensible output:
|
|
// For constant input, both HP outputs should be zero, hence DECO = 0
|
|
var source = new double[50];
|
|
Array.Fill(source, 42.0);
|
|
var output = new double[50];
|
|
Deco.Batch(source, output, 10, 20);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
Assert.Equal(0.0, output[i], Tolerance);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void LargeDataset_NoOverflow()
|
|
{
|
|
var gbm = new GBM(startPrice: 1000.0, mu: 0.1, sigma: 0.5, seed: 55);
|
|
var bars = gbm.Fetch(5000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
TSeries source = bars.Close;
|
|
|
|
var output = new double[source.Count];
|
|
var ex = Record.Exception(() => Deco.Batch(source.Values, output, 30, 60));
|
|
Assert.Null(ex);
|
|
|
|
for (int i = 0; i < source.Count; i++)
|
|
{
|
|
Assert.True(double.IsFinite(output[i]), $"Non-finite at index {i}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateMethod_ReturnsConsistentResults()
|
|
{
|
|
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 42);
|
|
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
TSeries source = bars.Close;
|
|
|
|
var (results, indicator) = Deco.Calculate(source, 15, 30);
|
|
Assert.Equal(source.Count, results.Count);
|
|
Assert.True(indicator.IsHot);
|
|
Assert.True(double.IsFinite(results.Values[^1]));
|
|
}
|
|
}
|