Files
QuanTAlib/lib/oscillators/deco/Deco.Validation.Tests.cs
Miha Kralj 3dd05f23e4 Refactor indicators to include "Ehlers" in names and descriptions for clarity
- Updated the name and description of the Hilbert Trendline (HTIT) to "Ehlers Hilbert Transform Instantaneous Trend (HTIT)".
- Changed the name and description of the MESA Adaptive Moving Average (MAMA) to "Ehlers MESA Adaptive Moving Average".
- Modified the Center of Gravity (CG) indicator to "Ehlers Center of Gravity (CG)".
- Renamed the Detrended Synthetic Price (DSP) to "Ehlers Detrended Synthetic Price (DSP)".
- Updated the Autocorrelation Periodogram (EACP) to "Ehlers Autocorrelation Periodogram (EACP)".
- Changed the Homodyne Discriminator (HOMOD) to "Ehlers Homodyne Discriminator (HOMOD)".
- Updated the Hilbert Transform Dominant Cycle Period and Phase indicators to include "Ehlers" in their names.
- Renamed the Hilbert Transform Phasor Components to "Ehlers Hilbert Transform Phasor Components (HT_PHASOR)".
- Updated the SineWave indicator to "Ehlers Hilbert Transform SineWave (HT_SINE)".
- Changed the Phasor Analysis indicator to "Ehlers Hilbert Transform Phasor Components (HT_PHASOR)".
- Updated the SSF-Based Detrended Synthetic Price to "Ehlers SSF Detrended Synthetic Price (SSFDSP)".
- Renamed the Ultimate Channel to "Ehlers Ultimate Channel (UCHANNEL)".
- Added new indicators: Moving Average Variable Period (MAVP), Ehlers Predictive Moving Average (PMA), Ehlers Reverse EMA (REVERSEEMA), and Ehlers Trendflex Indicator (TRENDFLEX).
- Updated various SVG badges to reflect changes in classes, comments, source files, lines of code, methods, and public types.
2026-02-18 19:08:15 -08: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]));
}
}