mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 21:18: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,112 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class FramaIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void FramaIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new FramaIndicator();
|
||||
|
||||
Assert.Equal(16, indicator.Period);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("FRAMA - Ehlers Fractal Adaptive Moving Average", indicator.Name);
|
||||
Assert.False(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_MinHistoryDepths_ReturnsZero()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 20 };
|
||||
|
||||
Assert.Equal(0, FramaIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_ShortName_IncludesPeriod()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 21 };
|
||||
|
||||
Assert.Contains("FRAMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("21", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new FramaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Frama.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_Initialize_CreatesLineSeries()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 10 };
|
||||
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Single(indicator.LinesSeries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 4 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
int warmup = indicator.Period % 2 == 0 ? indicator.Period : indicator.Period + 1;
|
||||
for (int i = 0; i < warmup; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
}
|
||||
|
||||
Assert.Equal(warmup, indicator.LinesSeries[0].Count);
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 4 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(1), 102, 108, 100, 106);
|
||||
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramaIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
|
||||
{
|
||||
var indicator = new FramaIndicator { Period = 4 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
int warmup = indicator.Period % 2 == 0 ? indicator.Period : indicator.Period + 1;
|
||||
for (int i = 0; i < warmup; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
}
|
||||
|
||||
double firstValue = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
|
||||
double secondValue = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(firstValue));
|
||||
Assert.True(double.IsFinite(secondValue));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class FramaTests
|
||||
{
|
||||
[Fact]
|
||||
public void Frama_Constructor_ValidatesInput()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Frama(1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Frama(0));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Frama(-5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_BasicCalculation_ReturnsFinite()
|
||||
{
|
||||
var frama = new Frama(16);
|
||||
var series = BuildSeries(40, seed: 42);
|
||||
|
||||
TValue result = default;
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
result = frama.Update(series[i], isNew: true);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
Assert.True(frama.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_IsNewFalse_RestoresState()
|
||||
{
|
||||
var frama = new Frama(16);
|
||||
var series = BuildSeries(20, seed: 7);
|
||||
|
||||
TBar lastBar = default;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
lastBar = series[i];
|
||||
frama.Update(lastBar, isNew: true);
|
||||
}
|
||||
|
||||
double original = frama.Last.Value;
|
||||
|
||||
var corrected = new TBar(lastBar.Time, lastBar.Open, lastBar.High * 1.05, lastBar.Low * 0.95, lastBar.Close, lastBar.Volume);
|
||||
frama.Update(corrected, isNew: false);
|
||||
frama.Update(lastBar, isNew: false);
|
||||
|
||||
Assert.Equal(original, frama.Last.Value, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_NaNFirstBar_RecoversOnValidInput()
|
||||
{
|
||||
var frama = new Frama(10);
|
||||
int warmup = frama.WarmupPeriod;
|
||||
var nanBar = new TBar(DateTime.UtcNow.Ticks, 1, double.NaN, 1, 1, 0);
|
||||
|
||||
TValue first = frama.Update(nanBar, isNew: true);
|
||||
Assert.True(double.IsNaN(first.Value));
|
||||
|
||||
DateTime start = DateTime.UtcNow.AddMinutes(1);
|
||||
TValue next = default;
|
||||
for (int i = 0; i < warmup; i++)
|
||||
{
|
||||
var valid = new TBar(start.AddMinutes(i).Ticks, 100, 110, 90, 105, 1000);
|
||||
next = frama.Update(valid, isNew: true);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(next.Value));
|
||||
Assert.True(frama.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_BatchMatchesStreaming()
|
||||
{
|
||||
int period = 20;
|
||||
var series = BuildSeries(80, seed: 11);
|
||||
|
||||
TSeries batch = FramaBatch(series, period);
|
||||
var frama = new Frama(period);
|
||||
|
||||
var streamValues = new List<double>(series.Count);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamValues.Add(frama.Update(series[i]).Value);
|
||||
}
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(batch[i].Value, streamValues[i], precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_SpanMatchesBatch()
|
||||
{
|
||||
int period = 18;
|
||||
var series = BuildSeries(60, seed: 21);
|
||||
double[] output = new double[series.Count];
|
||||
|
||||
Frama.Batch(series.High.Values, series.Low.Values, period, output);
|
||||
TSeries batch = FramaBatch(series, period);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(batch[i].Value, output[i], precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_Eventing_WorksWithTSeries()
|
||||
{
|
||||
int period = 12;
|
||||
var source = new TSeries();
|
||||
var frama = new Frama(source, period);
|
||||
|
||||
int count = 0;
|
||||
frama.Pub += (object? sender, in TValueEventArgs args) => count++;
|
||||
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 31);
|
||||
for (int i = 0; i < 25; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: true);
|
||||
source.Add(bar.Time, bar.Close);
|
||||
}
|
||||
|
||||
Assert.Equal(25, count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_WarmupPeriod_TransitionsIsHot()
|
||||
{
|
||||
var frama = new Frama(15);
|
||||
int warmup = frama.WarmupPeriod;
|
||||
var series = BuildSeries(warmup, seed: 100);
|
||||
|
||||
for (int i = 0; i < warmup - 1; i++)
|
||||
{
|
||||
frama.Update(series[i], isNew: true);
|
||||
Assert.False(frama.IsHot);
|
||||
}
|
||||
|
||||
frama.Update(series[warmup - 1], isNew: true);
|
||||
Assert.True(frama.IsHot);
|
||||
}
|
||||
|
||||
private static TBarSeries BuildSeries(int count, int seed)
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
private static TSeries FramaBatch(TBarSeries series, int period)
|
||||
{
|
||||
return Frama.Batch(series, period);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
|
||||
using OoplesFinance.StockIndicators;
|
||||
using OoplesFinance.StockIndicators.Models;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class FramaValidationTests
|
||||
{
|
||||
[Fact]
|
||||
public void Frama_Streaming_MatchesReference()
|
||||
{
|
||||
int period = 16;
|
||||
TBarSeries series = BuildSeries(200, seed: 5);
|
||||
double[] reference = new double[series.Count];
|
||||
|
||||
ReferenceFrama(series.High.Values, series.Low.Values, period, reference);
|
||||
|
||||
var frama = new Frama(period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
double actual = frama.Update(series[i], isNew: true).Value;
|
||||
Assert.Equal(reference[i], actual, precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_Batch_MatchesReference()
|
||||
{
|
||||
int period = 20;
|
||||
TBarSeries series = BuildSeries(180, seed: 7);
|
||||
double[] reference = new double[series.Count];
|
||||
|
||||
ReferenceFrama(series.High.Values, series.Low.Values, period, reference);
|
||||
TSeries batch = Frama.Batch(series, period);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(reference[i], batch[i].Value, precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_Span_MatchesReference()
|
||||
{
|
||||
int period = 24;
|
||||
TBarSeries series = BuildSeries(160, seed: 11);
|
||||
double[] output = new double[series.Count];
|
||||
double[] reference = new double[series.Count];
|
||||
|
||||
ReferenceFrama(series.High.Values, series.Low.Values, period, reference);
|
||||
Frama.Batch(series.High.Values, series.Low.Values, period, output);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(reference[i], output[i], precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReferenceFrama(ReadOnlySpan<double> high, ReadOnlySpan<double> low, int period, Span<double> output)
|
||||
{
|
||||
int pe = (period % 2 == 0) ? period : period + 1;
|
||||
int h = pe / 2;
|
||||
|
||||
double lastHigh = double.NaN;
|
||||
double lastLow = double.NaN;
|
||||
double fr = double.NaN;
|
||||
bool hasValue = false;
|
||||
|
||||
for (int i = 0; i < high.Length; i++)
|
||||
{
|
||||
double highVal = high[i];
|
||||
double lowVal = low[i];
|
||||
|
||||
if (!double.IsFinite(highVal) || !double.IsFinite(lowVal))
|
||||
{
|
||||
if (!double.IsFinite(lastHigh) || !double.IsFinite(lastLow))
|
||||
{
|
||||
output[i] = double.NaN;
|
||||
continue;
|
||||
}
|
||||
|
||||
highVal = lastHigh;
|
||||
lowVal = lastLow;
|
||||
}
|
||||
|
||||
lastHigh = highVal;
|
||||
lastLow = lowVal;
|
||||
|
||||
if (i < pe - 1)
|
||||
{
|
||||
output[i] = double.NaN;
|
||||
continue;
|
||||
}
|
||||
|
||||
double maxRecent = double.MinValue;
|
||||
double minRecent = double.MaxValue;
|
||||
double maxPrev = double.MinValue;
|
||||
double minPrev = double.MaxValue;
|
||||
double maxFull = double.MinValue;
|
||||
double minFull = double.MaxValue;
|
||||
|
||||
int startFull = i - pe + 1;
|
||||
int startRecent = i - h + 1;
|
||||
|
||||
for (int j = startFull; j <= i; j++)
|
||||
{
|
||||
double hv = high[j];
|
||||
double lv = low[j];
|
||||
if (!double.IsFinite(hv) || !double.IsFinite(lv))
|
||||
{
|
||||
hv = lastHigh;
|
||||
lv = lastLow;
|
||||
}
|
||||
|
||||
if (hv > maxFull)
|
||||
{
|
||||
maxFull = hv;
|
||||
}
|
||||
|
||||
if (lv < minFull)
|
||||
{
|
||||
minFull = lv;
|
||||
}
|
||||
|
||||
if (j >= startRecent)
|
||||
{
|
||||
if (hv > maxRecent)
|
||||
{
|
||||
maxRecent = hv;
|
||||
}
|
||||
|
||||
if (lv < minRecent)
|
||||
{
|
||||
minRecent = lv;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hv > maxPrev)
|
||||
{
|
||||
maxPrev = hv;
|
||||
}
|
||||
|
||||
if (lv < minPrev)
|
||||
{
|
||||
minPrev = lv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double n1 = (maxRecent - minRecent) / h;
|
||||
double n2 = (maxPrev - minPrev) / h;
|
||||
double n3 = (maxFull - minFull) / pe;
|
||||
|
||||
double alpha = 1.0;
|
||||
if (n1 > 0.0 && n2 > 0.0 && n3 > 0.0)
|
||||
{
|
||||
double dimen = (Math.Log(n1 + n2) - Math.Log(n3)) / 0.693147180559945309417232121458176568;
|
||||
alpha = Math.Exp(-4.6 * (dimen - 1.0));
|
||||
if (alpha < 0.01)
|
||||
{
|
||||
alpha = 0.01;
|
||||
}
|
||||
|
||||
if (alpha > 1.0)
|
||||
{
|
||||
alpha = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
double price = (highVal + lowVal) * 0.5;
|
||||
double prev = hasValue && double.IsFinite(fr) ? fr : price;
|
||||
fr = Math.FusedMultiplyAdd(prev, 1.0 - alpha, alpha * price);
|
||||
hasValue = true;
|
||||
|
||||
output[i] = fr;
|
||||
}
|
||||
}
|
||||
|
||||
private static TBarSeries BuildSeries(int count, int seed)
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: seed);
|
||||
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Frama_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).CalculateEhlersFractalAdaptiveMovingAverage();
|
||||
var values = result.CustomValuesList;
|
||||
int finiteCount = values.Count(v => double.IsFinite(v));
|
||||
Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user