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
+3 -4
View File
@@ -60,11 +60,10 @@ public class BbandsIndicator : Indicator, IWatchlistIndicator
protected override void OnUpdate(UpdateArgs args)
{
var priceSelector = Source.GetPriceSelector();
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
var item = HistoricalData[0, SeekOriginHistory.End];
double price = priceSelector(item);
var time = HistoricalData.Time();
TValue input = new(time, price);
TValue input = new(item.TimeLeft, price);
TValue result = bbands!.Update(input, args.IsNewBar());
MiddleSeries!.SetValue(result.Value, bbands.IsHot, ShowColdValues);
+44 -20
View File
@@ -255,40 +255,64 @@ public sealed class Bbands : AbstractBase
// Calculate SMA using static batch method
Sma.Batch(source, middle, period);
// Calculate standard deviation and bands
for (int i = 0; i < len; i++)
// Calculate standard deviation and bands using O(n) rolling sums
// Instead of O(n²) nested loop, maintain running sum and sumSq
double rollingSum = 0.0;
double rollingSumSq = 0.0;
// Initialize rolling sums for first window
for (int i = 0; i < Math.Min(period, len); i++)
{
double val = source[i];
if (double.IsFinite(val))
{
rollingSum += val;
rollingSumSq += val * val;
}
if (i < period - 1)
{
upper[i] = double.NaN;
lower[i] = double.NaN;
continue;
}
}
// Calculate standard deviation for the current window
double sum = 0.0;
double sumSq = 0.0;
int count = 0;
// Process first complete window
if (len >= period)
{
double mean = rollingSum / period;
double variance = (rollingSumSq / period) - (mean * mean);
variance = Math.Max(0.0, variance); // Guard against negative due to floating point
double stdDev = Math.Sqrt(variance);
double offset = multiplier * stdDev;
upper[period - 1] = middle[period - 1] + offset;
lower[period - 1] = middle[period - 1] - offset;
}
for (int j = i - period + 1; j <= i; j++)
// Process remaining bars with O(1) rolling update
for (int i = period; i < len; i++)
{
// Remove outgoing value (leftmost of previous window)
double outgoing = source[i - period];
if (double.IsFinite(outgoing))
{
double val = source[j];
if (double.IsFinite(val))
{
sum += val;
sumSq += val * val;
count++;
}
rollingSum -= outgoing;
rollingSumSq -= outgoing * outgoing;
}
double variance = 0.0;
if (count > 0)
// Add incoming value (current)
double incoming = source[i];
if (double.IsFinite(incoming))
{
double mean = sum / count;
variance = (sumSq / count) - (mean * mean);
variance = Math.Max(0.0, variance); // Guard against negative due to floating point
rollingSum += incoming;
rollingSumSq += incoming * incoming;
}
// Calculate variance from rolling sums: Var = E[X²] - E[X]²
double mean = rollingSum / period;
double variance = (rollingSumSq / period) - (mean * mean);
variance = Math.Max(0.0, variance); // Guard against negative due to floating point
double stdDev = Math.Sqrt(variance);
double offset = multiplier * stdDev;