Refactor error handling and calculations in TheilU, Wmape, and TukeyBiweight classes; update buffer handling for consistency

- Updated buffer handling in TheilU and Wmape classes to ensure consistency after adding new values.
- Changed the resync interval constant in TukeyBiweight for better clarity.
- Refactored state structures to record structs in Gauss, Hann, Hp, Hpf, Kalman, Loess, Notch, and other filter classes for improved performance and readability.
- Enhanced numerical stability in Mama class calculations using Fused Multiply-Add (FMA) for precision.
- Added comprehensive tests for Atan2 validation to compare .NET's Math.Atan2 with PineScript's implementation, ensuring accuracy across various edge cases.
- Updated NDepend badges to reflect changes in classes, methods, and lines of code.
This commit is contained in:
Miha Kralj
2026-01-24 23:07:09 -08:00
parent 744d680435
commit 2836f253c4
53 changed files with 1102 additions and 492 deletions
+2 -3
View File
@@ -52,8 +52,7 @@ public class VwapsdIndicator : Indicator, IWatchlistIndicator
protected override void OnUpdate(UpdateArgs args)
{
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
var time = HistoricalData.Time();
var item = HistoricalData[0, SeekOriginHistory.End];
// VWAP requires OHLCV data - using HLC3 for price
double high = item[PriceType.High];
@@ -61,7 +60,7 @@ public class VwapsdIndicator : Indicator, IWatchlistIndicator
double close = item[PriceType.Close];
double volume = item[PriceType.Volume];
TBar bar = new(time, item[PriceType.Open], high, low, close, volume);
TBar bar = new(item.TimeLeft, item[PriceType.Open], high, low, close, volume);
TValue result = vwapsd!.Update(bar, args.IsNewBar());
VwapSeries!.SetValue(result.Value, vwapsd.IsHot, ShowColdValues);
+31
View File
@@ -325,6 +325,37 @@ public class VwapsdTests
Assert.NotEqual(vwapBeforeReset, vwapsd.Vwap.Value);
}
[Fact]
public void Vwapsd_SessionReset_ResetsIsHotGating()
{
var vwapsd = new Vwapsd(1.0);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42);
var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Process bars until IsHot is true (WarmupPeriod = 2)
vwapsd.Update(bars[0]);
Assert.False(vwapsd.IsHot);
vwapsd.Update(bars[1]);
Assert.True(vwapsd.IsHot);
// Process more bars to ensure we're well past warmup
for (int i = 2; i < 10; i++)
{
vwapsd.Update(bars[i]);
}
Assert.True(vwapsd.IsHot);
// Reset session - IsHot should become false
var resetBar1 = new TBar(DateTime.UtcNow, 200, 210, 190, 200, 1000);
vwapsd.Update(resetBar1, isNew: true, reset: true);
Assert.False(vwapsd.IsHot, "IsHot should be false after reset (1 bar accumulated)");
// Process second bar after reset - IsHot should become true
var resetBar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 205, 215, 195, 205, 1100);
vwapsd.Update(resetBar2, isNew: true);
Assert.True(vwapsd.IsHot, "IsHot should be true after 2 bars accumulated post-reset");
}
[Fact]
public void Vwapsd_VwapFormula_MatchesExpected()
{
@@ -106,12 +106,12 @@ public sealed class VwapsdValidationTests : IDisposable
streamingLower.Add(streamingVwapsd.Lower.Value);
}
// Span mode - using HLC3 for price
// Span mode - using bar.HLC3 for price to match streaming mode
double[] price = new double[bars.Count];
double[] volume = new double[bars.Count];
for (int i = 0; i < bars.Count; i++)
{
price[i] = (bars[i].High + bars[i].Low + bars[i].Close) / 3.0;
price[i] = bars[i].HLC3;
volume[i] = bars[i].Volume;
}
+4 -2
View File
@@ -132,8 +132,7 @@ public sealed class Vwapsd : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TBar bar, bool isNew = true, bool reset = false)
{
double hlc3 = (bar.High + bar.Low + bar.Close) / 3.0;
return Update(new TValue(bar.Time, hlc3), bar.Volume, isNew, reset);
return Update(new TValue(bar.Time, bar.HLC3), bar.Volume, isNew, reset);
}
/// <summary>
@@ -168,6 +167,9 @@ public sealed class Vwapsd : AbstractBase
// Handle reset
if (reset || !_state.IsInitialized)
{
// Reset warmup tracking for proper IsHot gating after session reset
_index = 1;
if (vol > 0)
{
_state = _state with