v0.8.7: Replace periodic ResyncInterval with Kahan compensated summation

Comprehensive refactor across all indicators replacing the periodic
ResyncInterval-based drift correction (every 1000 ticks recalculate
from scratch) with Kahan compensated summation for running sums.

Key changes:
- Remove ResyncInterval constants and TickCount fields from all State records
- Add Kahan compensation fields (SumComp, SumSqComp, etc.) to State records
- Replace naive sum += val - removed with Kahan delta pattern
- Remove Resync()/RecalculateSum() methods that did O(N) recalculation
- Update batch/SIMD paths to use Kahan compensation instead of resync loops
- IIR filters (EMA, REMA, RGMA) simplified: inherently self-correcting
- Version bump to 0.8.7
- Build system: README version stamping via Directory.Build.props
- Minor doc/test tolerance adjustments for new numerical characteristics

Affected modules: channels, core, cycles, dynamics, errors, momentum,
oscillators, statistics, trends_FIR, trends_IIR, volatility, volume
This commit is contained in:
Miha Kralj
2026-03-13 22:01:31 -07:00
parent c75135ab14
commit 67ad6f0cba
79 changed files with 2923 additions and 2495 deletions
+33 -17
View File
@@ -40,13 +40,12 @@ public sealed class Bbwp : AbstractBase
private record struct State(
double Sum,
double SumSq,
double SumComp,
double SumSqComp,
double LastValid);
private State _state;
private State _p_state;
private const int ResyncInterval = 1000;
private int _tickCount;
/// <summary>
/// Creates BBWP with specified period, multiplier, and lookback.
/// </summary>
@@ -127,25 +126,43 @@ public sealed class Bbwp : AbstractBase
{
_p_state = _state;
// Remove oldest value contribution if buffer full
// Kahan compensated sliding window update
if (_buffer.Count == _buffer.Capacity)
{
double oldest = _buffer.Oldest;
_state.Sum -= oldest;
_state.SumSq -= oldest * oldest;
double delta = value - oldest;
{
double y = delta - _state.SumComp;
double t = _state.Sum + y;
_state.SumComp = (t - _state.Sum) - y;
_state.Sum = t;
}
{
double deltaSq = (value * value) - (oldest * oldest);
double y = deltaSq - _state.SumSqComp;
double t = _state.SumSq + y;
_state.SumSqComp = (t - _state.SumSq) - y;
_state.SumSq = t;
}
}
// Add new value
_state.Sum += value;
_state.SumSq += value * value;
_buffer.Add(value);
_tickCount++;
if (_buffer.IsFull && _tickCount >= ResyncInterval)
else
{
_tickCount = 0;
RecalculateSums();
{
double y = value - _state.SumComp;
double t = _state.Sum + y;
_state.SumComp = (t - _state.Sum) - y;
_state.Sum = t;
}
{
double sq = value * value;
double y = sq - _state.SumSqComp;
double t = _state.SumSq + y;
_state.SumSqComp = (t - _state.SumSq) - y;
_state.SumSq = t;
}
}
_buffer.Add(value);
}
else
{
@@ -251,7 +268,6 @@ public sealed class Bbwp : AbstractBase
_bbwBuffer.Clear();
_state = default;
_p_state = default;
_tickCount = 0;
Last = default;
}