Files
Miha Kralj 060649192f docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- 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
2026-03-12 12:34:16 -07:00

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]));
}
}