mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 04:58:08 +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,133 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class BrarIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void BrarIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new BrarIndicator();
|
||||
|
||||
Assert.Equal(26, indicator.Period);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("BRAR - Bull-Bear Power Ratio", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 26 };
|
||||
|
||||
Assert.Equal(0, BrarIndicator.MinHistoryDepths);
|
||||
IWatchlistIndicator watchlistIndicator = indicator;
|
||||
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_ShortName_IncludesPeriod()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 14 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("BRAR", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("14", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new BrarIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Brar.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_Initialize_CreatesTwoLineSeries()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 26 };
|
||||
indicator.Initialize();
|
||||
|
||||
// BR line + AR line
|
||||
Assert.Equal(2, indicator.LinesSeries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
|
||||
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
double brValue = indicator.LinesSeries[0].GetValue(0);
|
||||
double arValue = indicator.LinesSeries[1].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(brValue));
|
||||
Assert.True(double.IsFinite(arValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_ProcessUpdate_NewBar_UpdatesValue()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
}
|
||||
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(20), 120, 130, 110, 125);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
// After new bar, series should have grown
|
||||
Assert.True(indicator.LinesSeries[0].Count >= 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_Parameters_CanBeChanged()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 14 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Equal(14, indicator.Period);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrarIndicator_DifferentOhlcSource_ComputesValues()
|
||||
{
|
||||
var indicator = new BrarIndicator { Period = 10 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
double basePrice = 100.0 + i * 0.5;
|
||||
indicator.HistoricalData.AddBar(
|
||||
now.AddMinutes(i),
|
||||
open: basePrice,
|
||||
high: basePrice + 3.0,
|
||||
low: basePrice - 2.0,
|
||||
close: basePrice + 1.0);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
}
|
||||
|
||||
// Both lines should have finite values
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[1].GetValue(0)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class BrarTests
|
||||
{
|
||||
private readonly GBM _gbm = new(100.0, 0.05, 0.2, seed: 42);
|
||||
private const double Tolerance = 1e-9;
|
||||
|
||||
// ───── A) Constructor validation ─────
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultPeriod_IsValid()
|
||||
{
|
||||
var brar = new Brar();
|
||||
Assert.Equal("Brar(26)", brar.Name);
|
||||
Assert.Equal(26, brar.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ZeroPeriod_Throws()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Brar(period: 0));
|
||||
Assert.Equal("period", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NegativePeriod_Throws()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Brar(period: -1));
|
||||
Assert.Equal("period", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CustomPeriod_SetsCorrectly()
|
||||
{
|
||||
var brar = new Brar(period: 14);
|
||||
Assert.Equal("Brar(14)", brar.Name);
|
||||
Assert.Equal(14, brar.WarmupPeriod);
|
||||
}
|
||||
|
||||
// ───── B) Basic calculation ─────
|
||||
|
||||
[Fact]
|
||||
public void Update_ReturnsTValue()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
var result = brar.Update(bar);
|
||||
Assert.IsType<TValue>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Last_IsAccessible()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
brar.Update(bar);
|
||||
Assert.True(double.IsFinite(brar.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_BrAndAr_Accessible()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true));
|
||||
}
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
Assert.True(double.IsFinite(brar.Ar));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_BullishBars_BrAbove100()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
// Bars where High is far above PrevClose, PrevClose is above Low
|
||||
// H >> PrevC >> L: strong upside push
|
||||
double price = 100.0;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
double open = price + 1;
|
||||
double high = price + 5;
|
||||
double low = price - 1;
|
||||
brar.Update(new TBar(DateTime.UtcNow.AddMinutes(i), open, high, low, price + 3, 1000), isNew: true);
|
||||
price += 3;
|
||||
}
|
||||
Assert.True(brar.Br > 100.0, $"Expected BR > 100, got {brar.Br}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_SymmetricBars_ArNear100()
|
||||
{
|
||||
var brar = new Brar(period: 10);
|
||||
// Open exactly at midpoint of High-Low → AR numerator == AR denominator → AR = 100
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
double open = 100.0; // midpoint of 95..105
|
||||
double high = 105.0;
|
||||
double low = 95.0;
|
||||
double close = 100.0;
|
||||
brar.Update(new TBar(DateTime.UtcNow.AddMinutes(i), open, high, low, close, 1000), isNew: true);
|
||||
}
|
||||
Assert.Equal(100.0, brar.Ar, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ZeroDenominator_BrReturns100()
|
||||
{
|
||||
var brar = new Brar(period: 3);
|
||||
// PrevClose at or below Low for every bar → BR denominator = 0 → returns 100
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
// low=95 > prevClose=90 → max(0, prevClose-low)=0 every time
|
||||
brar.Update(new TBar(DateTime.UtcNow.AddMinutes(i), 100.0, 110.0, 95.0, 90.0, 1000), isNew: true);
|
||||
}
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
}
|
||||
|
||||
// ───── C) State + bar correction ─────
|
||||
|
||||
[Fact]
|
||||
public void Update_IsNew_True_AdvancesState()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
Assert.True(double.IsFinite(brar.Ar));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IsNew_False_RollsBack()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
|
||||
// Two corrections with same data must yield same result
|
||||
var bar = new TBar(DateTime.UtcNow, 105, 110, 100, 107, 1000);
|
||||
brar.Update(bar, isNew: false);
|
||||
double corrected1 = brar.Br;
|
||||
double arCorrected1 = brar.Ar;
|
||||
|
||||
brar.Update(bar, isNew: false);
|
||||
double corrected2 = brar.Br;
|
||||
|
||||
Assert.Equal(corrected1, corrected2, Tolerance);
|
||||
Assert.Equal(arCorrected1, brar.Ar, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IterativeCorrections_Restore()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
var bars = new TBar[15];
|
||||
for (int i = 0; i < bars.Length; i++)
|
||||
{
|
||||
bars[i] = _gbm.Next(isNew: true);
|
||||
}
|
||||
|
||||
foreach (var b in bars)
|
||||
{
|
||||
brar.Update(b, isNew: true);
|
||||
}
|
||||
|
||||
double baselineBr = brar.Br;
|
||||
double baselineAr = brar.Ar;
|
||||
|
||||
// Corrupt and restore
|
||||
brar.Update(new TBar(DateTime.UtcNow, 200, 250, 150, 220, 5000), isNew: false);
|
||||
brar.Update(new TBar(DateTime.UtcNow, 999, 1050, 900, 1000, 9999), isNew: false);
|
||||
brar.Update(bars[^1], isNew: false);
|
||||
|
||||
Assert.Equal(baselineBr, brar.Br, Tolerance);
|
||||
Assert.Equal(baselineAr, brar.Ar, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
|
||||
brar.Reset();
|
||||
|
||||
Assert.False(brar.IsHot);
|
||||
// After reset, single bar should give 100 for both (only 1 bar, symmetric or equilibrium)
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
brar.Update(bar, isNew: true);
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
Assert.True(double.IsFinite(brar.Ar));
|
||||
}
|
||||
|
||||
// ───── D) Warmup / IsHot ─────
|
||||
|
||||
[Fact]
|
||||
public void IsHot_FlipsAfterPeriodBars()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
Assert.False(brar.IsHot);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
Assert.False(brar.IsHot);
|
||||
}
|
||||
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
Assert.True(brar.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmupPeriod_MatchesPeriod()
|
||||
{
|
||||
Assert.Equal(10, new Brar(period: 10).WarmupPeriod);
|
||||
Assert.Equal(26, new Brar(period: 26).WarmupPeriod);
|
||||
}
|
||||
|
||||
// ───── E) Robustness (NaN/Infinity) ─────
|
||||
|
||||
[Fact]
|
||||
public void Update_NaN_High_DoesNotPropagate()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
|
||||
var nanBar = new TBar(DateTime.UtcNow, 100, double.NaN, 95, 102, 1000);
|
||||
brar.Update(nanBar, isNew: true);
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
Assert.True(double.IsFinite(brar.Ar));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_InfinityClose_DoesNotPropagate()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
|
||||
var infBar = new TBar(DateTime.UtcNow, 100, 110, 90, double.PositiveInfinity, 1000);
|
||||
brar.Update(infBar, isNew: true);
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
Assert.True(double.IsFinite(brar.Ar));
|
||||
}
|
||||
|
||||
// ───── F) Consistency (all modes match) ─────
|
||||
|
||||
[Fact]
|
||||
[SkipLocalsInit]
|
||||
public void Consistency_Streaming_Vs_Batch_Match()
|
||||
{
|
||||
const int N = 100;
|
||||
const int period = 14;
|
||||
|
||||
var gbm = new GBM(100.0, 0.05, 0.2, seed: 123);
|
||||
var bars = new TBar[N];
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
bars[i] = gbm.Next(isNew: true);
|
||||
}
|
||||
|
||||
// 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 opens = new double[N];
|
||||
var highs = new double[N];
|
||||
var lows = new double[N];
|
||||
var closes = new double[N];
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
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);
|
||||
|
||||
Assert.Equal(streamBr, brBatch[N - 1], Tolerance);
|
||||
Assert.Equal(streamAr, arBatch[N - 1], Tolerance);
|
||||
}
|
||||
|
||||
// ───── G) Span API validation ─────
|
||||
|
||||
[Fact]
|
||||
public void Batch_ZeroPeriod_Throws()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
Brar.Batch(
|
||||
new double[5], new double[5], new double[5], new double[5],
|
||||
new double[5], new double[5], period: 0));
|
||||
Assert.Equal("period", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_MismatchedHighLength_Throws()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
Brar.Batch(
|
||||
new double[5], new double[6], new double[5], new double[5],
|
||||
new double[5], new double[5], period: 3));
|
||||
Assert.Equal("high", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_MismatchedOutputLength_Throws()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
Brar.Batch(
|
||||
new double[5], new double[5], new double[5], new double[5],
|
||||
new double[4], new double[5], period: 3));
|
||||
Assert.Equal("brOutput", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_EmptyInputs_NoThrow()
|
||||
{
|
||||
// Should not throw or write anything — just verify no exception
|
||||
var ex = Record.Exception(() =>
|
||||
Brar.Batch(
|
||||
ReadOnlySpan<double>.Empty, ReadOnlySpan<double>.Empty,
|
||||
ReadOnlySpan<double>.Empty, ReadOnlySpan<double>.Empty,
|
||||
Span<double>.Empty, Span<double>.Empty, period: 5));
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_LargePeriod_UsesArrayPool()
|
||||
{
|
||||
// period > 256 forces ArrayPool path
|
||||
const int period = 300;
|
||||
const int N = 500;
|
||||
var gbm = new GBM(100.0, 0.05, 0.2, seed: 99);
|
||||
var opens = new double[N]; var highs = new double[N];
|
||||
var lows = new double[N]; var closes = new double[N];
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var b = gbm.Next(isNew: true);
|
||||
opens[i] = b.Open; highs[i] = b.High;
|
||||
lows[i] = b.Low; closes[i] = b.Close;
|
||||
}
|
||||
var brOut = new double[N];
|
||||
var arOut = new double[N];
|
||||
Brar.Batch(opens, highs, lows, closes, brOut, arOut, period);
|
||||
|
||||
Assert.True(double.IsFinite(brOut[N - 1]));
|
||||
Assert.True(double.IsFinite(arOut[N - 1]));
|
||||
}
|
||||
|
||||
// ───── H) Chainability / events ─────
|
||||
|
||||
[Fact]
|
||||
public void Pub_EventFires_OnUpdate()
|
||||
{
|
||||
var brar = new Brar(period: 5);
|
||||
int fired = 0;
|
||||
brar.Pub += (_, in _) => fired++;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
brar.Update(_gbm.Next(isNew: true), isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(5, fired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_TBarSeries_Chains()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var brar = new Brar(series, period: 3);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
series.Add(_gbm.Next(isNew: true));
|
||||
}
|
||||
|
||||
Assert.True(brar.IsHot);
|
||||
Assert.True(double.IsFinite(brar.Br));
|
||||
}
|
||||
|
||||
// ───── Known-value tests ─────
|
||||
|
||||
[Fact]
|
||||
public void KnownValue_SingleBar_FirstBarBootstrap()
|
||||
{
|
||||
var brar = new Brar(period: 3);
|
||||
// First bar: prevClose = open = 100, high = 110, low = 90
|
||||
// brNum = max(0, 110 - 100) = 10
|
||||
// brDen = max(0, 100 - 90) = 10 → BR = 100
|
||||
// arNum = max(0, 110 - 100) = 10
|
||||
// arDen = max(0, 100 - 90) = 10 → AR = 100
|
||||
var bar = new TBar(DateTime.UtcNow, 100.0, 110.0, 90.0, 105.0, 1000);
|
||||
brar.Update(bar, isNew: true);
|
||||
Assert.Equal(100.0, brar.Br, Tolerance);
|
||||
Assert.Equal(100.0, brar.Ar, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KnownValue_TwoBars_CorrectRatios()
|
||||
{
|
||||
var brar = new Brar(period: 3);
|
||||
// Bar 1: O=100, H=110, L=90, C=105 → prevC used as open=100
|
||||
// brNum=10, brDen=10, arNum=10, arDen=10
|
||||
brar.Update(new TBar(DateTime.UtcNow, 100.0, 110.0, 90.0, 105.0, 1000), isNew: true);
|
||||
|
||||
// Bar 2: O=106, H=115, L=100, C=110, prevC=105
|
||||
// brNum = max(0, 115-105) = 10
|
||||
// brDen = max(0, 105-100) = 5
|
||||
// arNum = max(0, 115-106) = 9
|
||||
// arDen = max(0, 106-100) = 6
|
||||
// Running sums (period=3, only 2 bars):
|
||||
// brNumSum=10+10=20, brDenSum=10+5=15 → BR = 20/15*100 ≈ 133.333...
|
||||
// arNumSum=10+9=19, arDenSum=10+6=16 → AR = 19/16*100 = 118.75
|
||||
brar.Update(new TBar(DateTime.UtcNow.AddMinutes(1), 106.0, 115.0, 100.0, 110.0, 1000), isNew: true);
|
||||
|
||||
Assert.Equal(20.0 / 15.0 * 100.0, brar.Br, Tolerance);
|
||||
Assert.Equal(19.0 / 16.0 * 100.0, brar.Ar, Tolerance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user