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:
Miha Kralj
2026-03-12 12:34:16 -07:00
parent 8937b0c0fa
commit 060649192f
1149 changed files with 1780 additions and 3316 deletions
@@ -0,0 +1,110 @@
using TradingPlatform.BusinessLayer;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class AcIndicatorTests
{
[Fact]
public void AcIndicator_Constructor_SetsDefaults()
{
var indicator = new AcIndicator();
Assert.Equal(5, indicator.FastPeriod);
Assert.Equal(34, indicator.SlowPeriod);
Assert.Equal(5, indicator.AcPeriod);
Assert.True(indicator.ShowColdValues);
Assert.Equal("AC - Acceleration Oscillator", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void AcIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AcIndicator { SlowPeriod = 20 };
Assert.Equal(0, AcIndicator.MinHistoryDepths);
IWatchlistIndicator watchlistIndicator = indicator;
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
}
[Fact]
public void AcIndicator_ShortName_IncludesParameters()
{
var indicator = new AcIndicator { FastPeriod = 10, SlowPeriod = 40, AcPeriod = 7 };
indicator.Initialize();
Assert.Contains("AC", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
Assert.Contains("7", indicator.ShortName, StringComparison.Ordinal);
}
[Fact]
public void AcIndicator_SourceCodeLink_IsValid()
{
var indicator = new AcIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
Assert.Contains("Ac.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
}
[Fact]
public void AcIndicator_Initialize_CreatesInternalAc()
{
var indicator = new AcIndicator { FastPeriod = 5, SlowPeriod = 34, AcPeriod = 5 };
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Up and Down)
Assert.Equal(2, indicator.LinesSeries.Count);
}
[Fact]
public void AcIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new AcIndicator { FastPeriod = 2, SlowPeriod = 5, AcPeriod = 3 };
indicator.Initialize();
// Add historical data
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);
}
// Line series should have a value (either Up or Down)
double up = indicator.LinesSeries[0].GetValue(0);
double down = indicator.LinesSeries[1].GetValue(0);
Assert.True(double.IsFinite(up) || double.IsFinite(down));
}
[Fact]
public void AcIndicator_ProcessUpdate_NewBar_UpdatesValue()
{
var indicator = new AcIndicator { FastPeriod = 2, SlowPeriod = 5, AcPeriod = 3 };
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 reason = i < 19 ? UpdateReason.HistoricalBar : UpdateReason.NewBar;
var args = new UpdateArgs(reason);
indicator.ProcessUpdate(args);
}
// Verify line series has values
double up = indicator.LinesSeries[0].GetValue(0);
double down = indicator.LinesSeries[1].GetValue(0);
Assert.True(double.IsFinite(up) || double.IsFinite(down));
}
}
+387
View File
@@ -0,0 +1,387 @@
using Xunit;
namespace QuanTAlib.Tests;
public sealed class AcTests
{
private readonly GBM _gbm = new(1000.0, 0.05, 0.3, seed: 42);
// ── A) Constructor validation ──
[Fact]
public void Constructor_FastPeriodZero_Throws()
{
var ex = Assert.Throws<ArgumentException>(() => new Ac(fastPeriod: 0));
Assert.Equal("fastPeriod", ex.ParamName);
}
[Fact]
public void Constructor_SlowPeriodZero_Throws()
{
var ex = Assert.Throws<ArgumentException>(() => new Ac(slowPeriod: 0));
Assert.Equal("slowPeriod", ex.ParamName);
}
[Fact]
public void Constructor_FastGeSlow_Throws()
{
var ex = Assert.Throws<ArgumentException>(() => new Ac(fastPeriod: 34, slowPeriod: 5));
Assert.Equal("fastPeriod", ex.ParamName);
}
[Fact]
public void Constructor_AcPeriodZero_Throws()
{
var ex = Assert.Throws<ArgumentException>(() => new Ac(acPeriod: 0));
Assert.Equal("acPeriod", ex.ParamName);
}
[Fact]
public void Constructor_Defaults_NameCorrect()
{
var ac = new Ac();
Assert.Equal("Ac(5,34,5)", ac.Name);
}
[Fact]
public void Constructor_Custom_WarmupPeriod()
{
var ac = new Ac(5, 34, 5);
Assert.Equal(38, ac.WarmupPeriod); // 34 + 5 - 1
}
// ── B) Basic calculation ──
[Fact]
public void Update_SingleBar_ReturnsValue()
{
var ac = new Ac();
var bar = _gbm.Next(isNew: true);
var result = ac.Update(bar);
Assert.True(double.IsFinite(result.Value));
}
[Fact]
public void Update_Last_IsAccessible()
{
var ac = new Ac();
var bar = _gbm.Next(isNew: true);
_ = ac.Update(bar);
Assert.True(double.IsFinite(ac.Last.Value));
}
[Fact]
public void Update_ConstantPrice_ConvergesToZero()
{
var ac = new Ac();
for (int i = 0; i < 100; i++)
{
var bar = new TBar(DateTime.UtcNow.AddMinutes(i), 100.0, 100.0, 100.0, 100.0, 1000.0);
_ = ac.Update(bar, isNew: true);
}
Assert.True(ac.IsHot);
Assert.Equal(0.0, ac.Last.Value, 1e-10);
}
// ── C) State + bar correction ──
[Fact]
public void Update_IsNew_True_AdvancesState()
{
var ac = new Ac();
// Feed enough bars so the values diverge from zero
for (int i = 0; i < 40; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
var bar1 = _gbm.Next(isNew: true);
var result1 = ac.Update(bar1, isNew: true);
var bar2 = _gbm.Next(isNew: true);
var result2 = ac.Update(bar2, isNew: true);
Assert.NotEqual(result1.Value, result2.Value);
}
[Fact]
public void Update_IsNew_False_Rewrites()
{
var ac = new Ac();
for (int i = 0; i < 40; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
var bar = _gbm.Next(isNew: true);
var first = ac.Update(bar, isNew: true);
var correctionBar = new TBar(bar.Time, bar.Open * 1.01, bar.High * 1.01, bar.Low * 1.01, bar.Close * 1.01, bar.Volume);
var corrected = ac.Update(correctionBar, isNew: false);
Assert.NotEqual(first.Value, corrected.Value);
}
[Fact]
public void Update_IterativeCorrections_Restore()
{
var ac = new Ac();
for (int i = 0; i < 40; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
var bar = _gbm.Next(isNew: true);
var first = ac.Update(bar, isNew: true);
// Apply corrections multiple times
for (int i = 0; i < 5; i++)
{
_ = ac.Update(bar, isNew: false);
}
var final = ac.Update(bar, isNew: false);
Assert.Equal(first.Value, final.Value, 1e-10);
}
[Fact]
public void Reset_ClearsState()
{
var ac = new Ac();
for (int i = 0; i < 50; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
Assert.True(ac.IsHot);
ac.Reset();
Assert.False(ac.IsHot);
Assert.Equal(0.0, ac.Last.Value);
}
// ── D) Warmup / convergence ──
[Fact]
public void IsHot_FlipsAfterSufficientData()
{
var ac = new Ac(5, 34, 5);
// Feed just 1 bar — should not be hot yet
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
// May already become hot if inner SMA sees enough values
// After feeding enough bars, must be hot
for (int i = 1; i < 50; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
Assert.True(ac.IsHot);
}
// ── E) Robustness ──
[Fact]
public void Update_NaN_KeepsLastValid()
{
var ac = new Ac();
for (int i = 0; i < 40; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
var lastBefore = ac.Last;
var nanInput = new TValue(DateTime.UtcNow, double.NaN);
var result = ac.Update(nanInput, isNew: true);
Assert.Equal(lastBefore.Value, result.Value, 1e-10);
}
[Fact]
public void Update_Infinity_KeepsLastValid()
{
var ac = new Ac();
for (int i = 0; i < 40; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
var lastBefore = ac.Last;
var infInput = new TValue(DateTime.UtcNow, double.PositiveInfinity);
var result = ac.Update(infInput, isNew: true);
Assert.Equal(lastBefore.Value, result.Value, 1e-10);
}
// ── F) Consistency (batch == streaming == span == eventing) ──
[Fact]
public void BatchCalc_Matches_Streaming()
{
var gbm = new GBM(500.0, 0.05, 0.3, seed: 99);
var series = new TBarSeries();
for (int i = 0; i < 100; i++)
{
series.Add(gbm.Next(isNew: true));
}
// Streaming
var streaming = new Ac();
for (int i = 0; i < series.Count; i++)
{
_ = streaming.Update(series[i], isNew: true);
}
// Batch via Update(TBarSeries)
var batchAc = new Ac();
var batchResult = batchAc.Update(series);
// Compare last values
Assert.Equal(streaming.Last.Value, batchResult[^1].Value, 4);
}
[Fact]
public void SpanBatch_Matches_Streaming()
{
var gbm = new GBM(500.0, 0.05, 0.3, seed: 99);
var series = new TBarSeries();
for (int i = 0; i < 100; i++)
{
series.Add(gbm.Next(isNew: true));
}
// Streaming
var streaming = new Ac();
for (int i = 0; i < series.Count; i++)
{
_ = streaming.Update(series[i], isNew: true);
}
// Span batch
var output = new double[series.Count];
Ac.Batch(series.High.Values, series.Low.Values, output);
Assert.Equal(streaming.Last.Value, output[^1], 4);
}
[Fact]
public void EventPub_FiresOnUpdate()
{
var ac = new Ac();
int pubCount = 0;
ac.Pub += (object? sender, in TValueEventArgs e) => pubCount++;
for (int i = 0; i < 5; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
Assert.Equal(5, pubCount);
}
// ── G) Span API tests ──
[Fact]
public void Batch_Span_MismatchedLengths_Throws()
{
var high = new double[10];
var low = new double[10];
var dest = new double[5]; // wrong length
var ex = Assert.Throws<ArgumentException>(() => Ac.Batch(high, low, dest));
Assert.Equal("destination", ex.ParamName);
}
[Fact]
public void Batch_Span_Empty_NoException()
{
var output = Array.Empty<double>();
Ac.Batch(ReadOnlySpan<double>.Empty, ReadOnlySpan<double>.Empty, output);
Assert.Empty(output);
}
// ── H) Chainability ──
[Fact]
public void EventChaining_Works()
{
var ac = new Ac();
var values = new List<double>();
ac.Pub += (object? sender, in TValueEventArgs e) => values.Add(e.Value.Value);
for (int i = 0; i < 50; i++)
{
_ = ac.Update(_gbm.Next(isNew: true), isNew: true);
}
Assert.Equal(50, values.Count);
}
// ── Additional: TValue Update path ──
[Fact]
public void TValueUpdate_Works()
{
var ac = new Ac();
for (int i = 0; i < 50; i++)
{
var val = new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i * 0.1);
_ = ac.Update(val, isNew: true);
}
Assert.True(ac.IsHot);
Assert.True(double.IsFinite(ac.Last.Value));
}
[Fact]
public void Prime_SetsState()
{
var gbm = new GBM(500.0, 0.05, 0.3, seed: 77);
var series = new TBarSeries();
for (int i = 0; i < 60; i++)
{
series.Add(gbm.Next(isNew: true));
}
var ac = new Ac();
ac.Prime(series);
Assert.True(ac.IsHot);
Assert.True(double.IsFinite(ac.Last.Value));
}
[Fact]
public void Calculate_ReturnsResultAndIndicator()
{
var gbm = new GBM(500.0, 0.05, 0.3, seed: 88);
var series = new TBarSeries();
for (int i = 0; i < 60; i++)
{
series.Add(gbm.Next(isNew: true));
}
var (results, indicator) = Ac.Calculate(series);
Assert.Equal(60, results.Count);
Assert.True(indicator.IsHot);
}
[Fact]
public void Batch_TBarSeries_Empty()
{
var result = Ac.Batch(new TBarSeries());
Assert.Empty(result);
}
[Fact]
public void Update_TBarSeries_Empty()
{
var ac = new Ac();
var result = ac.Update(new TBarSeries());
Assert.Empty(result);
}
}
@@ -0,0 +1,200 @@
using Xunit;
using OoplesFinance.StockIndicators;
using OoplesFinance.StockIndicators.Models;
namespace QuanTAlib.Tests;
/// <summary>
/// Self-consistency validation for AC. No external library implements AC with
/// identical SMA-based methodology, so we validate AC = AO - SMA(AO, acPeriod)
/// identity, determinism, and cross-mode consistency.
/// </summary>
public sealed class AcValidationTests
{
private static TBarSeries GenerateSeries(int count, int seed = 42)
{
var gbm = new GBM(500.0, 0.05, 0.3, seed: seed);
var series = new TBarSeries();
for (int i = 0; i < count; i++)
{
series.Add(gbm.Next(isNew: true));
}
return series;
}
[Fact]
public void AC_Equals_AO_Minus_SMA_AO()
{
var series = GenerateSeries(200);
// Compute AO
var ao = new Ao();
var aoValues = new List<double>();
for (int i = 0; i < series.Count; i++)
{
var r = ao.Update(series[i], isNew: true);
aoValues.Add(r.Value);
}
// Compute SMA(AO, 5)
var smaAo = new Sma(5);
var smaAoValues = new List<double>();
for (int i = 0; i < aoValues.Count; i++)
{
var r = smaAo.Update(new TValue(DateTime.UtcNow.AddMinutes(i), aoValues[i]), isNew: true);
smaAoValues.Add(r.Value);
}
// Compute AC via streaming
var ac = new Ac();
var acValues = new List<double>();
for (int i = 0; i < series.Count; i++)
{
var r = ac.Update(series[i], isNew: true);
acValues.Add(r.Value);
}
// Verify AC = AO - SMA(AO, 5) once all are hot
int start = 38; // slowPeriod(34) + acPeriod(5) - 1
for (int i = start; i < series.Count; i++)
{
double expected = aoValues[i] - smaAoValues[i];
Assert.Equal(expected, acValues[i], 1e-10);
}
}
[Fact]
public void BatchAndStreaming_Match()
{
var series = GenerateSeries(200);
// Streaming
var streaming = new Ac();
var streamValues = new List<double>();
for (int i = 0; i < series.Count; i++)
{
var r = streaming.Update(series[i], isNew: true);
streamValues.Add(r.Value);
}
// Batch
var batchResult = Ac.Batch(series);
for (int i = 0; i < series.Count; i++)
{
Assert.Equal(streamValues[i], batchResult[i].Value, 4);
}
}
[Fact]
public void Determinism_SameSeedProducesSameResults()
{
var series1 = GenerateSeries(100, seed: 123);
var series2 = GenerateSeries(100, seed: 123);
var ac1 = new Ac();
var ac2 = new Ac();
for (int i = 0; i < series1.Count; i++)
{
var r1 = ac1.Update(series1[i], isNew: true);
var r2 = ac2.Update(series2[i], isNew: true);
Assert.Equal(r1.Value, r2.Value, 1e-12);
}
}
[Fact]
public void SpanBatch_Matches_TBarSeriesBatch()
{
var series = GenerateSeries(150);
var batchResult = Ac.Batch(series);
var output = new double[series.Count];
Ac.Batch(series.High.Values, series.Low.Values, output);
for (int i = 0; i < series.Count; i++)
{
Assert.Equal(batchResult[i].Value, output[i], 1e-10);
}
}
[Fact]
public void ParameterSensitivity_DifferentPeriods_DifferentResults()
{
var series = GenerateSeries(100);
var ac1 = new Ac(5, 34, 5);
var ac2 = new Ac(3, 20, 5);
for (int i = 0; i < series.Count; i++)
{
_ = ac1.Update(series[i], isNew: true);
_ = ac2.Update(series[i], isNew: true);
}
Assert.NotEqual(ac1.Last.Value, ac2.Last.Value);
}
[Fact]
public void LargeDataset_Stability()
{
var series = GenerateSeries(5000, seed: 55);
var ac = new Ac();
for (int i = 0; i < series.Count; i++)
{
var result = ac.Update(series[i], isNew: true);
Assert.True(double.IsFinite(result.Value), $"Non-finite at bar {i}");
}
Assert.True(ac.IsHot);
}
[Fact]
public void MonotonicConvergence_ConstantInput()
{
var ac = new Ac();
double prevAbsValue = double.MaxValue;
bool convergenceStarted = false;
for (int i = 0; i < 200; i++)
{
var bar = new TBar(DateTime.UtcNow.AddMinutes(i), 50.0, 50.0, 50.0, 50.0, 1000.0);
var result = ac.Update(bar, isNew: true);
if (ac.IsHot && i > 50)
{
double absVal = Math.Abs(result.Value);
if (convergenceStarted)
{
Assert.True(absVal <= prevAbsValue + 1e-10, $"Not converging at bar {i}: {absVal} > {prevAbsValue}");
}
convergenceStarted = true;
prevAbsValue = absVal;
}
}
Assert.True(convergenceStarted);
}
[Fact]
public void Ac_MatchesOoples_Structural()
{
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.15, seed: 42);
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var ooplesData = bars.Select(b => new TickerData
{
Date = new DateTime(b.Time, DateTimeKind.Utc),
Open = b.Open, High = b.High, Low = b.Low,
Close = b.Close, Volume = b.Volume
}).ToList();
var result = new StockData(ooplesData).CalculateAcceleratorOscillator();
var values = result.CustomValuesList;
int finiteCount = values.Count(v => double.IsFinite(v));
Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}");
}
}