mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-03 03:47:42 +00:00
060649192f
- 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
238 lines
7.6 KiB
C#
238 lines
7.6 KiB
C#
using System.Runtime.CompilerServices;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
/// <summary>
|
|
/// Self-consistency validation for BRAR.
|
|
/// BRAR is not implemented by TA-Lib, Skender, Tulip, or Ooples,
|
|
/// so validation uses streaming == batch == span mode consistency
|
|
/// plus mathematical identity checks.
|
|
/// </summary>
|
|
public sealed class BrarValidationTests(ITestOutputHelper output)
|
|
{
|
|
private readonly ITestOutputHelper _output = output;
|
|
private const double Tolerance = 1e-12;
|
|
|
|
// ───── Self-consistency: streaming == batch span ─────
|
|
|
|
[Fact]
|
|
[SkipLocalsInit]
|
|
public void Validate_Streaming_Equals_Batch_Period14()
|
|
{
|
|
const int N = 200;
|
|
const int period = 14;
|
|
|
|
var gbm = new GBM(100.0, 0.05, 0.2, seed: 1001);
|
|
var opens = new double[N]; var highs = new double[N];
|
|
var lows = new double[N]; var closes = new double[N];
|
|
var bars = new TBar[N];
|
|
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
bars[i] = gbm.Next(isNew: true);
|
|
opens[i] = bars[i].Open; highs[i] = bars[i].High;
|
|
lows[i] = bars[i].Low; closes[i] = bars[i].Close;
|
|
}
|
|
|
|
// Streaming
|
|
var brar = new Brar(period);
|
|
for (int i = 0; i < N; i++) { brar.Update(bars[i], isNew: true); }
|
|
double streamBr = brar.Br;
|
|
double streamAr = brar.Ar;
|
|
|
|
// Batch span
|
|
var brBatch = new double[N];
|
|
var arBatch = new double[N];
|
|
Brar.Batch(opens, highs, lows, closes, brBatch, arBatch, period);
|
|
|
|
_output.WriteLine($"Streaming BR={streamBr:F8}, Batch BR={brBatch[N-1]:F8}");
|
|
_output.WriteLine($"Streaming AR={streamAr:F8}, Batch AR={arBatch[N-1]:F8}");
|
|
|
|
Assert.Equal(streamBr, brBatch[N - 1], Tolerance);
|
|
Assert.Equal(streamAr, arBatch[N - 1], Tolerance);
|
|
}
|
|
|
|
[Fact]
|
|
[SkipLocalsInit]
|
|
public void Validate_Streaming_Equals_Batch_Period26()
|
|
{
|
|
const int N = 300;
|
|
const int period = 26;
|
|
|
|
var gbm = new GBM(100.0, 0.05, 0.3, seed: 2002);
|
|
var opens = new double[N]; var highs = new double[N];
|
|
var lows = new double[N]; var closes = new double[N];
|
|
var bars = new TBar[N];
|
|
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
bars[i] = gbm.Next(isNew: true);
|
|
opens[i] = bars[i].Open; highs[i] = bars[i].High;
|
|
lows[i] = bars[i].Low; closes[i] = bars[i].Close;
|
|
}
|
|
|
|
var brar = new Brar(period);
|
|
for (int i = 0; i < N; i++) { brar.Update(bars[i], isNew: true); }
|
|
|
|
var brBatch = new double[N];
|
|
var arBatch = new double[N];
|
|
Brar.Batch(opens, highs, lows, closes, brBatch, arBatch, period);
|
|
|
|
Assert.Equal(brar.Br, brBatch[N - 1], Tolerance);
|
|
Assert.Equal(brar.Ar, arBatch[N - 1], Tolerance);
|
|
}
|
|
|
|
// ───── Mathematical identity checks ─────
|
|
|
|
[Fact]
|
|
public void Validate_SymmetricBars_ArEquals100()
|
|
{
|
|
// Open at center of range → AR = 100 at all times
|
|
const int N = 50;
|
|
const int period = 10;
|
|
|
|
var brar = new Brar(period);
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
brar.Update(new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
open: 100.0, high: 110.0, low: 90.0, close: 100.0, volume: 1000), isNew: true);
|
|
}
|
|
|
|
Assert.Equal(100.0, brar.Ar, Tolerance);
|
|
_output.WriteLine($"Symmetric AR (expect 100): {brar.Ar}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_EqualBrPressure_BrEquals100()
|
|
{
|
|
// High - PrevClose == PrevClose - Low for every bar → BR = 100
|
|
const int N = 50;
|
|
const int period = 10;
|
|
|
|
var brar = new Brar(period);
|
|
double close = 100.0;
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
// high = close + d, low = close - d → symmetric around prevClose
|
|
double d = 5.0;
|
|
double high = close + d;
|
|
double low = close - d;
|
|
brar.Update(new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
open: close, high: high, low: low, close: close, volume: 1000), isNew: true);
|
|
// close stays constant so prevClose = close always
|
|
}
|
|
|
|
Assert.Equal(100.0, brar.Br, Tolerance);
|
|
_output.WriteLine($"Symmetric BR (expect 100): {brar.Br}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_AllUpBars_ArAbove100()
|
|
{
|
|
// Open much closer to Low than to High → arNum >> arDen → AR > 100
|
|
const int N = 50;
|
|
const int period = 10;
|
|
|
|
var brar = new Brar(period);
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
// Open just above low, high far above
|
|
brar.Update(new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
open: 91.0, high: 110.0, low: 90.0, close: 105.0, volume: 1000), isNew: true);
|
|
}
|
|
|
|
Assert.True(brar.Ar > 100.0, $"Expected AR > 100, got {brar.Ar}");
|
|
_output.WriteLine($"Bullish AR: {brar.Ar}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_AllDownBars_ArBelow100()
|
|
{
|
|
// Open just below high, low far below → arDen >> arNum → AR < 100
|
|
const int N = 50;
|
|
const int period = 10;
|
|
|
|
var brar = new Brar(period);
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
brar.Update(new TBar(
|
|
DateTime.UtcNow.AddMinutes(i),
|
|
open: 109.0, high: 110.0, low: 90.0, close: 95.0, volume: 1000), isNew: true);
|
|
}
|
|
|
|
Assert.True(brar.Ar < 100.0, $"Expected AR < 100, got {brar.Ar}");
|
|
_output.WriteLine($"Bearish AR: {brar.Ar}");
|
|
}
|
|
|
|
// ───── Determinism ─────
|
|
|
|
[Fact]
|
|
public void Validate_Deterministic_SameSeed_SameResult()
|
|
{
|
|
const int N = 150;
|
|
const int period = 20;
|
|
|
|
static double ComputeFinalBr(int n, int p, int seed)
|
|
{
|
|
var gbm = new GBM(100.0, 0.05, 0.2, seed: seed);
|
|
var brar = new Brar(p);
|
|
for (int i = 0; i < n; i++) { brar.Update(gbm.Next(isNew: true), isNew: true); }
|
|
return brar.Br;
|
|
}
|
|
|
|
double run1 = ComputeFinalBr(N, period, 777);
|
|
double run2 = ComputeFinalBr(N, period, 777);
|
|
|
|
Assert.Equal(run1, run2, Tolerance);
|
|
_output.WriteLine($"Deterministic BR: {run1}");
|
|
}
|
|
|
|
// ───── Full intermediate series consistency ─────
|
|
|
|
[Fact]
|
|
[SkipLocalsInit]
|
|
public void Validate_AllBars_Streaming_Vs_Batch_Match()
|
|
{
|
|
const int N = 100;
|
|
const int period = 10;
|
|
|
|
var gbm = new GBM(100.0, 0.05, 0.2, seed: 3333);
|
|
var opens = new double[N]; var highs = new double[N];
|
|
var lows = new double[N]; var closes = new double[N];
|
|
var bars = new TBar[N];
|
|
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
bars[i] = gbm.Next(isNew: true);
|
|
opens[i] = bars[i].Open; highs[i] = bars[i].High;
|
|
lows[i] = bars[i].Low; closes[i] = bars[i].Close;
|
|
}
|
|
|
|
var brBatch = new double[N];
|
|
var arBatch = new double[N];
|
|
Brar.Batch(opens, highs, lows, closes, brBatch, arBatch, period);
|
|
|
|
// Compare every bar, not just last
|
|
var brar = new Brar(period);
|
|
int mismatches = 0;
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
brar.Update(bars[i], isNew: true);
|
|
double diff = Math.Abs(brar.Br - brBatch[i]);
|
|
if (diff > Tolerance)
|
|
{
|
|
mismatches++;
|
|
_output.WriteLine($"BR mismatch at i={i}: streaming={brar.Br}, batch={brBatch[i]}, diff={diff:E3}");
|
|
}
|
|
}
|
|
|
|
Assert.Equal(0, mismatches);
|
|
_output.WriteLine($"All {N} bars match between streaming and batch");
|
|
}
|
|
}
|