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

237 lines
6.6 KiB
C#

using Xunit;
namespace QuanTAlib.Tests;
/// <summary>
/// TD_SEQ Validation Tests — self-consistency only (no external library equivalent).
/// Validates: streaming == batch, determinism, NaN safety, direction reversal logic.
/// </summary>
public sealed class TdSeqValidationTests
{
private static TBar[] MakeBars(int count, int seed = 42)
{
var gbm = new GBM(100.0, 0.02, 0.1, seed: seed);
var tbarSeries = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var bars = new TBar[count];
for (int i = 0; i < count; i++)
{
bars[i] = new TBar(
DateTime.UtcNow.AddMinutes(i),
tbarSeries.Close.Values[i],
tbarSeries.High.Values[i],
tbarSeries.Low.Values[i],
tbarSeries.Close.Values[i],
1000);
}
return bars;
}
// ─── Self-consistency: streaming == batch ───
[Fact]
public void Streaming_EqualsBatch_Period4()
{
var bars = MakeBars(500);
var barSeries = new TBarSeries();
foreach (var b in bars) { barSeries.Add(b); }
// Streaming
var streaming = new TdSeq(4);
var streamResults = new double[bars.Length];
for (int i = 0; i < bars.Length; i++)
{
streamResults[i] = streaming.Update(bars[i]).Value;
}
// Batch via Calculate
TSeries batchResults = TdSeq.Calculate(barSeries, 4);
for (int i = 0; i < bars.Length; i++)
{
Assert.Equal(streamResults[i], batchResults.Values[i]);
}
}
[Fact]
public void Streaming_EqualsBatch_Period2()
{
var bars = MakeBars(200, seed: 13);
var barSeries = new TBarSeries();
foreach (var b in bars) { barSeries.Add(b); }
var streaming = new TdSeq(2);
var streamResults = new double[bars.Length];
for (int i = 0; i < bars.Length; i++)
{
streamResults[i] = streaming.Update(bars[i]).Value;
}
TSeries batchResults = TdSeq.Calculate(barSeries, 2);
for (int i = 0; i < bars.Length; i++)
{
Assert.Equal(streamResults[i], batchResults.Values[i]);
}
}
// ─── Determinism: same input → same output ───
[Fact]
public void Determinism_SameSeed_SameResults()
{
var bars1 = MakeBars(100, seed: 99);
var bars2 = MakeBars(100, seed: 99);
var td1 = new TdSeq(4);
var td2 = new TdSeq(4);
for (int i = 0; i < bars1.Length; i++)
{
double v1 = td1.Update(bars1[i]).Value;
double v2 = td2.Update(bars2[i]).Value;
Assert.Equal(v1, v2);
}
}
// ─── Known-value spot check ───
[Fact]
public void SellSetup_PureRising_CountsCorrectly()
{
// Pure monotone rising: bars 0-3 prime, bars 4-12 each qualify as sell setup
// After 9 qualifying bars the setup count clamps to 9
var td = new TdSeq(4);
int maxSetup = 0;
for (int i = 0; i < 20; i++)
{
double p = 100.0 + i;
td.Update(new TBar(DateTime.UtcNow.AddMinutes(i), p, p + 2, p - 2, p, 1000));
if (td.Setup > maxSetup) { maxSetup = td.Setup; }
}
Assert.Equal(9, maxSetup);
}
[Fact]
public void BuySetup_PureFalling_CountsNegativeNine()
{
var td = new TdSeq(4);
int minSetup = 0;
for (int i = 0; i < 20; i++)
{
double p = 200.0 - i;
td.Update(new TBar(DateTime.UtcNow.AddMinutes(i), p, p + 2, p - 2, p, 1000));
if (td.Setup < minSetup) { minSetup = td.Setup; }
}
Assert.Equal(-9, minSetup);
}
// ─── Setup clamp: never exceeds ±9 ───
[Fact]
public void Setup_NeverExceedsNine()
{
var bars = MakeBars(500, seed: 7);
var td = new TdSeq(4);
foreach (var b in bars)
{
td.Update(b);
Assert.True(td.Setup >= -9 && td.Setup <= 9,
$"Setup {td.Setup} out of range");
}
}
// ─── Countdown clamp: never exceeds ±13 ───
[Fact]
public void Countdown_NeverExceedsThirteen()
{
var bars = MakeBars(500, seed: 7);
var td = new TdSeq(4);
foreach (var b in bars)
{
td.Update(b);
Assert.True(td.Countdown >= -13 && td.Countdown <= 13,
$"Countdown {td.Countdown} out of range");
}
}
// ─── Pre-warmup output is zero ───
[Fact]
public void PreWarmup_OutputIsZero()
{
var td = new TdSeq(4);
for (int i = 0; i < 4; i++)
{
double v = td.Update(new TBar(DateTime.UtcNow, 100 + i, 102 + i, 98 + i, 100 + i, 1000)).Value;
Assert.Equal(0.0, v);
}
}
// ─── NaN inputs: output remains finite ───
[Fact]
public void NaN_OutputRemainsFinite()
{
var td = new TdSeq(4);
var bars = MakeBars(20);
foreach (var b in bars) { td.Update(b); }
// Insert NaN bar
td.Update(new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 1000));
Assert.True(double.IsFinite(td.Last.Value));
}
// ─── Event-based matches streaming ───
[Fact]
public void EventBased_MatchesStreaming()
{
var bars = MakeBars(300, seed: 55);
var streaming = new TdSeq(4);
var streamResults = new double[bars.Length];
for (int i = 0; i < bars.Length; i++)
{
streamResults[i] = streaming.Update(bars[i]).Value;
}
var barSource = new TBarSeries();
var eventTd = new TdSeq(barSource, 4);
var eventResults = new double[bars.Length];
for (int i = 0; i < bars.Length; i++)
{
barSource.Add(bars[i]);
eventResults[i] = eventTd.Last.Value;
}
for (int i = 0; i < bars.Length; i++)
{
Assert.Equal(streamResults[i], eventResults[i]);
}
}
// ─── Different periods produce different results ───
[Fact]
public void DifferentPeriods_ProduceDifferentResults()
{
var bars = MakeBars(100);
var td4 = new TdSeq(4);
var td2 = new TdSeq(2);
bool anyDiff = false;
foreach (var b in bars)
{
double v4 = td4.Update(b).Value;
double v2 = td2.Update(b).Value;
if (v4 != v2) { anyDiff = true; }
}
Assert.True(anyDiff, "Period 4 and period 2 should produce different results on real data");
}
}