mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-21 12:08:05 +00:00
Refactor IndicatorExtensions: Remove unused methods and optimize price retrieval
This commit is contained in:
@@ -25,8 +25,8 @@ public class BilateralIndicatorTests
|
||||
{
|
||||
var indicator = new BilateralIndicator { Period = 20 };
|
||||
|
||||
Assert.Equal(20, indicator.MinHistoryDepths);
|
||||
Assert.Equal(20, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -113,16 +113,6 @@ public class BilateralIndicatorTests
|
||||
Assert.True(double.IsFinite(secondValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BilateralIndicator_OnPaintChart_DoesNotThrow()
|
||||
{
|
||||
var indicator = new BilateralIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var method = indicator.GetType().GetMethod("OnPaintChart");
|
||||
Assert.NotNull(method);
|
||||
Assert.Equal(typeof(BilateralIndicator), method.DeclaringType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BilateralIndicator_MultipleUpdates_ProducesCorrectSequence()
|
||||
@@ -181,6 +171,6 @@ public class BilateralIndicatorTests
|
||||
Assert.Equal(20, indicator.Period);
|
||||
Assert.Equal(1.0, indicator.SigmaSRatio);
|
||||
Assert.Equal(2.0, indicator.SigmaRMult);
|
||||
Assert.Equal(20, indicator.MinHistoryDepths);
|
||||
Assert.Equal(0, BilateralIndicator.MinHistoryDepths);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public class BilateralIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
|
||||
@@ -23,9 +25,9 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
|
||||
private Bilateral? _bilateral;
|
||||
protected LineSeries? Series;
|
||||
protected string? SourceName;
|
||||
private int _warmupBarIndex = -1;
|
||||
private Func<IHistoryItem, double>? _priceSelector;
|
||||
|
||||
public int MinHistoryDepths => Period;
|
||||
public static int MinHistoryDepths => 0;
|
||||
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
|
||||
|
||||
public override string ShortName => $"Bilateral {Period}:{SourceName}";
|
||||
@@ -46,30 +48,17 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
_bilateral = new Bilateral(Period, SigmaSRatio, SigmaRMult);
|
||||
SourceName = Source.ToString();
|
||||
_warmupBarIndex = -1;
|
||||
_priceSelector = Source.GetPriceSelector();
|
||||
base.OnInit();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
TValue input = this.GetInputValue(args, Source);
|
||||
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
|
||||
TValue result = _bilateral!.Update(input, isNew);
|
||||
Series!.SetValue(result.Value);
|
||||
Series!.SetMarker(0, Color.Transparent);
|
||||
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
|
||||
|
||||
TValue result = _bilateral!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar());
|
||||
|
||||
if (_warmupBarIndex < 0 && _bilateral!.IsHot)
|
||||
_warmupBarIndex = Count;
|
||||
}
|
||||
|
||||
public override void OnPaintChart(PaintChartEventArgs args)
|
||||
{
|
||||
var savedColor = Series!.Color;
|
||||
Series.Color = Color.Transparent;
|
||||
base.OnPaintChart(args);
|
||||
Series.Color = savedColor;
|
||||
|
||||
int warmupPeriod = _warmupBarIndex > 0 ? _warmupBarIndex : Count;
|
||||
this.PaintLine(args, Series!, warmupPeriod, showColdValues: ShowColdValues);
|
||||
Series!.SetValue(result.Value, _bilateral.IsHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ namespace QuanTAlib;
|
||||
|
||||
public class BilateralTests
|
||||
{
|
||||
private readonly GBM _gbm;
|
||||
|
||||
public BilateralTests()
|
||||
{
|
||||
_gbm = new GBM();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidatesInput()
|
||||
{
|
||||
@@ -101,22 +108,43 @@ public class BilateralTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_Update_Matches_Iterative()
|
||||
public void AllModes_ProduceSameResult()
|
||||
{
|
||||
var indicator = new Bilateral(5);
|
||||
var series = new TSeries();
|
||||
for (int i = 0; i < 20; i++)
|
||||
int period = 10;
|
||||
var bars = _gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var series = bars.Close;
|
||||
|
||||
// 1. Batch Mode
|
||||
var batchSeries = new Bilateral(period).Update(series);
|
||||
double expected = batchSeries.Last.Value;
|
||||
|
||||
// 2. Span Mode
|
||||
var tValues = series.Values.ToArray();
|
||||
var spanInput = new ReadOnlySpan<double>(tValues);
|
||||
var spanOutput = new double[tValues.Length];
|
||||
Bilateral.Calculate(spanInput, spanOutput, period);
|
||||
double spanResult = spanOutput[^1];
|
||||
|
||||
// 3. Streaming Mode
|
||||
var streamingInd = new Bilateral(period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), i));
|
||||
streamingInd.Update(series[i]);
|
||||
}
|
||||
|
||||
var resultSeries = indicator.Update(series);
|
||||
|
||||
var indicatorIterative = new Bilateral(5);
|
||||
for (int i = 0; i < 20; i++)
|
||||
double streamingResult = streamingInd.Last.Value;
|
||||
|
||||
// 4. Eventing Mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingInd = new Bilateral(pubSource, period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
indicatorIterative.Update(series[i]);
|
||||
Assert.Equal(indicatorIterative.Last.Value, resultSeries[i].Value);
|
||||
pubSource.Add(series[i]);
|
||||
}
|
||||
double eventingResult = eventingInd.Last.Value;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, spanResult, 1e-9);
|
||||
Assert.Equal(expected, streamingResult, 1e-9);
|
||||
Assert.Equal(expected, eventingResult, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,4 +260,106 @@ public sealed class Bilateral : AbstractBase
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
|
||||
{
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
// Precalculate spatial weights
|
||||
double sigmaS = Math.Max(period * sigmaSRatio, 1e-10);
|
||||
double twoSigmaSSq = 2.0 * sigmaS * sigmaS;
|
||||
Span<double> spatialWeights = period <= 256 ? stackalloc double[period] : new double[period];
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
double diffSpatial = i;
|
||||
spatialWeights[i] = Math.Exp(-(diffSpatial * diffSpatial) / twoSigmaSSq);
|
||||
}
|
||||
|
||||
// Handle NaNs by tracking last valid value
|
||||
double lastValid = double.NaN;
|
||||
// Find initial valid value
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
if (double.IsFinite(source[i]))
|
||||
{
|
||||
lastValid = source[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If all NaNs, fill with NaN
|
||||
if (double.IsNaN(lastValid))
|
||||
{
|
||||
destination.Fill(double.NaN);
|
||||
return;
|
||||
}
|
||||
|
||||
Span<double> window = period <= 256 ? stackalloc double[period] : new double[period];
|
||||
int windowIdx = 0;
|
||||
int count = 0;
|
||||
double sum = 0;
|
||||
double sumSq = 0;
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
|
||||
// Add to window
|
||||
double removed = 0;
|
||||
if (count >= period)
|
||||
{
|
||||
removed = window[windowIdx];
|
||||
sum -= removed;
|
||||
sumSq -= removed * removed;
|
||||
}
|
||||
|
||||
window[windowIdx] = val;
|
||||
sum += val;
|
||||
sumSq += val * val;
|
||||
|
||||
int currentNewestIdx = windowIdx;
|
||||
windowIdx = (windowIdx + 1) % period;
|
||||
if (count < period) count++;
|
||||
|
||||
// Calculate StDev
|
||||
double variance = Math.Max(0, (sumSq - (sum * sum) / count) / count);
|
||||
double stdev = Math.Sqrt(variance);
|
||||
|
||||
double sigmaR = Math.Max(stdev * sigmaRMult, 1e-10);
|
||||
double twoSigmaRSq = 2.0 * sigmaR * sigmaR;
|
||||
|
||||
double sumWeights = 0.0;
|
||||
double sumWeightedSrc = 0.0;
|
||||
double centerVal = val; // Newest value
|
||||
|
||||
// Iterate backwards through the window
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
// k=0 is newest (currentNewestIdx)
|
||||
// k=1 is previous...
|
||||
int idx = currentNewestIdx - k;
|
||||
if (idx < 0) idx += period;
|
||||
|
||||
double wVal = window[idx];
|
||||
double diffRange = centerVal - wVal;
|
||||
|
||||
double weightRange = Math.Exp(-(diffRange * diffRange) / twoSigmaRSq);
|
||||
double weight = spatialWeights[k] * weightRange;
|
||||
|
||||
sumWeights += weight;
|
||||
sumWeightedSrc += weight * wVal;
|
||||
}
|
||||
|
||||
destination[i] = sumWeights == 0.0 ? centerVal : sumWeightedSrc / sumWeights;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user