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

176 lines
6.2 KiB
C#

// CKSTOP Validation Tests - Chande Kroll Stop
// No external library implements CKSTOP, so validation uses self-consistency checks.
namespace QuanTAlib.Tests;
public sealed class CkstopValidationTests
{
private static TBarSeries CreateGbmBars(int count = 500, int seed = 42)
{
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.20, seed: seed);
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
}
// ── Self-Consistency: Streaming == Batch ──────────────────────────────
[Fact]
public void StreamingMatchesBatch_StopLong()
{
var bars = CreateGbmBars();
int atrPeriod = 10;
double multiplier = 1.0;
int stopPeriod = 9;
// Streaming
var streaming = new Ckstop(atrPeriod, multiplier, stopPeriod);
var streamStopLong = new double[bars.Count];
for (int i = 0; i < bars.Count; i++)
{
_ = streaming.Update(bars[i], isNew: true);
streamStopLong[i] = streaming.StopLong;
}
// Batch
var batchResults = Ckstop.Batch(bars, atrPeriod, multiplier, stopPeriod);
int warmup = atrPeriod + stopPeriod;
for (int i = warmup; i < bars.Count; i++)
{
Assert.Equal(streamStopLong[i], batchResults[i].Value, precision: 10);
}
}
// ── Self-Consistency: Streaming == Span ───────────────────────────────
[Fact]
public void StreamingMatchesSpan_StopLong()
{
var bars = CreateGbmBars();
int atrPeriod = 10;
double multiplier = 1.0;
int stopPeriod = 9;
// Streaming
var streaming = new Ckstop(atrPeriod, multiplier, stopPeriod);
var streamStopLong = new double[bars.Count];
for (int i = 0; i < bars.Count; i++)
{
_ = streaming.Update(bars[i], isNew: true);
streamStopLong[i] = streaming.StopLong;
}
// Span
var spanOutput = new double[bars.Count];
Ckstop.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, bars.CloseValues,
spanOutput, atrPeriod, multiplier, stopPeriod);
int warmup = atrPeriod + stopPeriod;
for (int i = warmup; i < bars.Count; i++)
{
Assert.Equal(streamStopLong[i], spanOutput[i], precision: 10);
}
}
// ── Directional Correctness ──────────────────────────────────────────
[Fact]
public void HigherMultiplier_NarrowsStopGap()
{
var bars = CreateGbmBars(count: 100);
var narrow = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
var wide = new Ckstop(atrPeriod: 10, multiplier: 3.0, stopPeriod: 9);
for (int i = 0; i < bars.Count; i++)
{
_ = narrow.Update(bars[i], isNew: true);
_ = wide.Update(bars[i], isNew: true);
}
// Higher multiplier: StopLong = LowestLow + q*ATR → goes UP
// StopShort = HighestHigh - q*ATR → goes DOWN
// The gap (StopShort - StopLong) narrows with higher multiplier.
double narrowGap = narrow.StopShort - narrow.StopLong;
double wideGap = wide.StopShort - wide.StopLong;
Assert.True(wideGap < narrowGap,
$"Wide gap ({wideGap}) should be < narrow gap ({narrowGap})");
}
// ── Determinism ──────────────────────────────────────────────────────
[Fact]
public void SameInput_ProducesSameOutput()
{
var bars = CreateGbmBars(count: 200, seed: 123);
var ck1 = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
var ck2 = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
for (int i = 0; i < bars.Count; i++)
{
_ = ck1.Update(bars[i], isNew: true);
_ = ck2.Update(bars[i], isNew: true);
}
Assert.Equal(ck1.StopLong, ck2.StopLong);
Assert.Equal(ck1.StopShort, ck2.StopShort);
}
// ── StopLong and StopShort Finite After Warmup ───────────────────────
[Fact]
public void AfterWarmup_BothStopsAreFinite()
{
var bars = CreateGbmBars(count: 100);
var ck = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
for (int i = 0; i < bars.Count; i++)
{
_ = ck.Update(bars[i], isNew: true);
if (ck.IsHot)
{
Assert.True(double.IsFinite(ck.StopLong), $"StopLong should be finite at bar {i}");
Assert.True(double.IsFinite(ck.StopShort), $"StopShort should be finite at bar {i}");
}
}
}
// ── Different Seeds Produce Different Results ─────────────────────────
[Fact]
public void DifferentSeeds_ProduceDifferentStops()
{
var bars1 = CreateGbmBars(count: 100, seed: 42);
var bars2 = CreateGbmBars(count: 100, seed: 99);
var ck1 = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
var ck2 = new Ckstop(atrPeriod: 10, multiplier: 1.0, stopPeriod: 9);
for (int i = 0; i < 100; i++)
{
_ = ck1.Update(bars1[i], isNew: true);
_ = ck2.Update(bars2[i], isNew: true);
}
// Very unlikely to be equal with different random data
Assert.NotEqual(ck1.StopLong, ck2.StopLong);
}
// ── Calculate Returns Valid Indicator ─────────────────────────────────
[Fact]
public void Calculate_ReturnsValidIndicatorAndResults()
{
var bars = CreateGbmBars(count: 100);
var (results, indicator) = Ckstop.Calculate(bars);
Assert.NotNull(results);
Assert.Equal(bars.Count, results.Count);
Assert.True(indicator.IsHot);
Assert.True(double.IsFinite(indicator.StopLong));
Assert.True(double.IsFinite(indicator.StopShort));
}
}