mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-25 13:58:04 +00:00
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
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
// FRACTALS Validation Tests - Williams Fractals
|
||||
// Cross-validated against Skender.Stock.Indicators GetFractal()
|
||||
//
|
||||
// Important alignment notes:
|
||||
// - Skender reports fractal at the bar where the fractal occurs (bar[2] in our terms)
|
||||
// - Our streaming indicator reports at the current bar (bar[0]) when detection happens
|
||||
// - Therefore: our value at index i corresponds to Skender's value at index (i-2)
|
||||
// - Skender naming: FractalBear = high point (resistance) = our UpFractal
|
||||
// FractalBull = low point (support) = our DownFractal
|
||||
|
||||
using Skender.Stock.Indicators;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class FractalsValidationTests
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
// -- Cross-library: Skender UpFractal (= Skender FractalBear) -----------------
|
||||
|
||||
[Fact]
|
||||
public void StreamingMatchesSkender_UpFractal()
|
||||
{
|
||||
var _data = new ValidationTestData();
|
||||
|
||||
// Skender: FractalBear = high point = our UpFractal
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetFractal()
|
||||
.ToList();
|
||||
|
||||
// QuanTAlib streaming
|
||||
var f = new Fractals();
|
||||
var ourUpValues = new double[_data.Bars.Count];
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
_ = f.Update(_data.Bars[i], isNew: true);
|
||||
ourUpValues[i] = f.UpFractal;
|
||||
}
|
||||
|
||||
// Compare with 2-bar offset: our value at i matches Skender at i-2
|
||||
int matched = 0;
|
||||
for (int i = 4; i < _data.Bars.Count; i++)
|
||||
{
|
||||
int skenderIdx = i - 2;
|
||||
if (skenderIdx < 0 || skenderIdx >= skenderResults.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
decimal? skenderBear = skenderResults[skenderIdx].FractalBear;
|
||||
bool skenderIsNull = !skenderBear.HasValue;
|
||||
bool ourIsNaN = double.IsNaN(ourUpValues[i]);
|
||||
|
||||
if (skenderIsNull && ourIsNaN)
|
||||
{
|
||||
matched++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!skenderIsNull && !ourIsNaN)
|
||||
{
|
||||
Assert.Equal((double)skenderBear!.Value, ourUpValues[i], precision: 6);
|
||||
matched++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(matched > 0, "Should have matched at least one warm value");
|
||||
_data.Dispose();
|
||||
}
|
||||
|
||||
// -- Cross-library: Skender DownFractal (= Skender FractalBull) ---------------
|
||||
|
||||
[Fact]
|
||||
public void StreamingMatchesSkender_DownFractal()
|
||||
{
|
||||
var _data = new ValidationTestData();
|
||||
|
||||
// Skender: FractalBull = low point = our DownFractal
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetFractal()
|
||||
.ToList();
|
||||
|
||||
// QuanTAlib streaming
|
||||
var f = new Fractals();
|
||||
var ourDownValues = new double[_data.Bars.Count];
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
_ = f.Update(_data.Bars[i], isNew: true);
|
||||
ourDownValues[i] = f.DownFractal;
|
||||
}
|
||||
|
||||
// Compare with 2-bar offset: our value at i matches Skender at i-2
|
||||
int matched = 0;
|
||||
for (int i = 4; i < _data.Bars.Count; i++)
|
||||
{
|
||||
int skenderIdx = i - 2;
|
||||
if (skenderIdx < 0 || skenderIdx >= skenderResults.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
decimal? skenderBull = skenderResults[skenderIdx].FractalBull;
|
||||
bool skenderIsNull = !skenderBull.HasValue;
|
||||
bool ourIsNaN = double.IsNaN(ourDownValues[i]);
|
||||
|
||||
if (skenderIsNull && ourIsNaN)
|
||||
{
|
||||
matched++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!skenderIsNull && !ourIsNaN)
|
||||
{
|
||||
Assert.Equal((double)skenderBull!.Value, ourDownValues[i], precision: 6);
|
||||
matched++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(matched > 0, "Should have matched at least one warm value");
|
||||
_data.Dispose();
|
||||
}
|
||||
|
||||
// -- Self-Consistency: Streaming == Batch --------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void StreamingMatchesBatch_UpFractal()
|
||||
{
|
||||
var bars = CreateGbmBars();
|
||||
|
||||
// Streaming
|
||||
var streaming = new Fractals();
|
||||
var streamUp = new double[bars.Count];
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
_ = streaming.Update(bars[i], isNew: true);
|
||||
streamUp[i] = streaming.UpFractal;
|
||||
}
|
||||
|
||||
// Batch
|
||||
var batchResults = Fractals.Batch(bars);
|
||||
|
||||
for (int i = 4; i < bars.Count; i++)
|
||||
{
|
||||
if (double.IsNaN(streamUp[i]))
|
||||
{
|
||||
Assert.True(double.IsNaN(batchResults[i].Value), $"Mismatch at {i}: streaming=NaN, batch={batchResults[i].Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(streamUp[i], batchResults[i].Value, precision: 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Self-Consistency: Streaming == Span ---------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void StreamingMatchesSpan_BothFractals()
|
||||
{
|
||||
var bars = CreateGbmBars();
|
||||
|
||||
// Streaming
|
||||
var streaming = new Fractals();
|
||||
var streamUp = new double[bars.Count];
|
||||
var streamDown = new double[bars.Count];
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
_ = streaming.Update(bars[i], isNew: true);
|
||||
streamUp[i] = streaming.UpFractal;
|
||||
streamDown[i] = streaming.DownFractal;
|
||||
}
|
||||
|
||||
// Span
|
||||
var spanUp = new double[bars.Count];
|
||||
var spanDown = new double[bars.Count];
|
||||
Fractals.Batch(bars.HighValues, bars.LowValues, spanUp, spanDown);
|
||||
|
||||
for (int i = 4; i < bars.Count; i++)
|
||||
{
|
||||
if (double.IsNaN(streamUp[i]))
|
||||
{
|
||||
Assert.True(double.IsNaN(spanUp[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(streamUp[i], spanUp[i], precision: 10);
|
||||
}
|
||||
|
||||
if (double.IsNaN(streamDown[i]))
|
||||
{
|
||||
Assert.True(double.IsNaN(spanDown[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(streamDown[i], spanDown[i], precision: 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Determinism ---------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SameInput_ProducesSameOutput()
|
||||
{
|
||||
var bars = CreateGbmBars(count: 200, seed: 123);
|
||||
|
||||
var f1 = new Fractals();
|
||||
var f2 = new Fractals();
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
_ = f1.Update(bars[i], isNew: true);
|
||||
_ = f2.Update(bars[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(f1.UpFractal, f2.UpFractal);
|
||||
Assert.Equal(f1.DownFractal, f2.DownFractal);
|
||||
}
|
||||
|
||||
// -- Calculate Returns Valid Indicator -----------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Calculate_ReturnsValidIndicatorAndResults()
|
||||
{
|
||||
var bars = CreateGbmBars(count: 100);
|
||||
|
||||
var (results, indicator) = Fractals.Calculate(bars);
|
||||
|
||||
Assert.NotNull(results);
|
||||
Assert.Equal(bars.Count, results.Count);
|
||||
Assert.True(indicator.IsHot);
|
||||
}
|
||||
|
||||
// -- BatchDual Returns Both Fractals ------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void BatchDual_ReturnsBothSeries()
|
||||
{
|
||||
var bars = CreateGbmBars(count: 100);
|
||||
|
||||
var (upSeries, downSeries) = Fractals.BatchDual(bars);
|
||||
|
||||
Assert.Equal(bars.Count, upSeries.Count);
|
||||
Assert.Equal(bars.Count, downSeries.Count);
|
||||
|
||||
// At least some fractals should be detected in 100 bars
|
||||
bool hasUp = false;
|
||||
bool hasDown = false;
|
||||
for (int i = 0; i < upSeries.Count; i++)
|
||||
{
|
||||
if (double.IsFinite(upSeries[i].Value))
|
||||
{
|
||||
hasUp = true;
|
||||
}
|
||||
if (double.IsFinite(downSeries[i].Value))
|
||||
{
|
||||
hasDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(hasUp, "Should detect at least one up fractal in 100 bars");
|
||||
Assert.True(hasDown, "Should detect at least one down fractal in 100 bars");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user