mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 00:58:04 +00:00
Merge dev into main: v0.8.7 Kahan compensated summation
This commit is contained in:
+34
-17
@@ -38,13 +38,12 @@ public sealed class Bbb : 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 BBB with specified period and multiplier.
|
||||
/// </summary>
|
||||
@@ -113,25 +112,44 @@ public sealed class Bbb : 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();
|
||||
// Warmup: Kahan addition
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -312,7 +330,6 @@ public sealed class Bbb : AbstractBase
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
_tickCount = 0;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-32
@@ -53,6 +53,9 @@ public sealed class Bbs : ITValuePublisher
|
||||
double BbSum,
|
||||
double BbSumSq,
|
||||
double KcSum,
|
||||
double BbSumComp,
|
||||
double BbSumSqComp,
|
||||
double KcSumComp,
|
||||
double AtrRaw,
|
||||
double AtrE,
|
||||
double PrevClose,
|
||||
@@ -65,10 +68,6 @@ public sealed class Bbs : ITValuePublisher
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
private int _tickCount;
|
||||
private int _p_tickCount;
|
||||
|
||||
// Saved squeeze state for SqueezeFired detection
|
||||
private bool _prevSqueezeOn;
|
||||
private bool _p_prevSqueezeOn;
|
||||
@@ -169,7 +168,7 @@ public sealed class Bbs : ITValuePublisher
|
||||
_bbBuffer = new RingBuffer(bbPeriod);
|
||||
_kcBuffer = new RingBuffer(kcPeriod);
|
||||
|
||||
_state = new State(0, 0, 0, 0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, 0, false);
|
||||
_state = new State(0, 0, 0, 0, 0, 0, 0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, 0, false);
|
||||
_p_state = _state;
|
||||
}
|
||||
|
||||
@@ -234,13 +233,11 @@ public sealed class Bbs : ITValuePublisher
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
_p_tickCount = _tickCount;
|
||||
_p_prevSqueezeOn = _prevSqueezeOn;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
_tickCount = _p_tickCount;
|
||||
_prevSqueezeOn = _p_prevSqueezeOn;
|
||||
}
|
||||
|
||||
@@ -251,23 +248,46 @@ public sealed class Bbs : ITValuePublisher
|
||||
_state = _state with { Bars = _state.Bars + 1 };
|
||||
}
|
||||
|
||||
// === Bollinger Bands: SMA + population stddev via rolling sum/sumSq ===
|
||||
// === Bollinger Bands: Kahan compensated SMA + population stddev ===
|
||||
if (_bbBuffer.IsFull)
|
||||
{
|
||||
double oldest = _bbBuffer.Oldest;
|
||||
double bbDelta = close - oldest;
|
||||
double bbSqDelta = (close * close) - (oldest * oldest);
|
||||
{
|
||||
double y = bbDelta - _state.BbSumComp;
|
||||
double t = _state.BbSum + y;
|
||||
double newComp = (t - _state.BbSum) - y;
|
||||
double y2 = bbSqDelta - _state.BbSumSqComp;
|
||||
double t2 = _state.BbSumSq + y2;
|
||||
double newSqComp = (t2 - _state.BbSumSq) - y2;
|
||||
_state = _state with
|
||||
{
|
||||
BbSum = t,
|
||||
BbSumComp = newComp,
|
||||
BbSumSq = t2,
|
||||
BbSumSqComp = newSqComp
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double y = close - _state.BbSumComp;
|
||||
double t = _state.BbSum + y;
|
||||
double newComp = (t - _state.BbSum) - y;
|
||||
double y2 = (close * close) - _state.BbSumSqComp;
|
||||
double t2 = _state.BbSumSq + y2;
|
||||
double newSqComp = (t2 - _state.BbSumSq) - y2;
|
||||
_state = _state with
|
||||
{
|
||||
BbSum = _state.BbSum - oldest,
|
||||
BbSumSq = _state.BbSumSq - (oldest * oldest)
|
||||
BbSum = t,
|
||||
BbSumComp = newComp,
|
||||
BbSumSq = t2,
|
||||
BbSumSqComp = newSqComp
|
||||
};
|
||||
}
|
||||
|
||||
_bbBuffer.Add(close, isNew);
|
||||
_state = _state with
|
||||
{
|
||||
BbSum = _state.BbSum + close,
|
||||
BbSumSq = _state.BbSumSq + (close * close)
|
||||
};
|
||||
|
||||
int bbCount = _bbBuffer.Count;
|
||||
double bbMean = bbCount > 0 ? _state.BbSum / bbCount : close;
|
||||
@@ -277,15 +297,24 @@ public sealed class Bbs : ITValuePublisher
|
||||
double bbUpper = bbMean + (_bbMult * bbStdDev);
|
||||
double bbLower = bbMean - (_bbMult * bbStdDev);
|
||||
|
||||
// === Keltner Channel: SMA middle + EMA-smoothed ATR ===
|
||||
// === Keltner Channel: Kahan compensated SMA middle + EMA-smoothed ATR ===
|
||||
if (_kcBuffer.IsFull)
|
||||
{
|
||||
double oldest = _kcBuffer.Oldest;
|
||||
_state = _state with { KcSum = _state.KcSum - oldest };
|
||||
double kcDelta = close - oldest;
|
||||
double y = kcDelta - _state.KcSumComp;
|
||||
double t = _state.KcSum + y;
|
||||
_state = _state with { KcSum = t, KcSumComp = (t - _state.KcSum) - y };
|
||||
// Fix: need to use pre-update KcSum for comp calc
|
||||
}
|
||||
else
|
||||
{
|
||||
double y = close - _state.KcSumComp;
|
||||
double t = _state.KcSum + y;
|
||||
_state = _state with { KcSum = t, KcSumComp = (t - _state.KcSum) - y };
|
||||
}
|
||||
|
||||
_kcBuffer.Add(close, isNew);
|
||||
_state = _state with { KcSum = _state.KcSum + close };
|
||||
|
||||
int kcCount = _kcBuffer.Count;
|
||||
double kcMid = kcCount > 0 ? _state.KcSum / kcCount : close;
|
||||
@@ -331,16 +360,6 @@ public sealed class Bbs : ITValuePublisher
|
||||
// === Bandwidth ===
|
||||
double bandwidth = bbMean != 0.0 ? ((bbUpper - bbLower) / bbMean) * 100.0 : 0.0; // skipcq: CS-R1077 - Exact-zero div guard: price avg
|
||||
|
||||
// === Resync for floating-point drift ===
|
||||
if (isNew)
|
||||
{
|
||||
_tickCount++;
|
||||
if (_bbBuffer.IsFull && _tickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
RecalculateSums();
|
||||
}
|
||||
}
|
||||
|
||||
// === IsHot ===
|
||||
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
||||
@@ -644,7 +663,7 @@ public sealed class Bbs : ITValuePublisher
|
||||
kcSum += _kcBuffer[i];
|
||||
}
|
||||
|
||||
_state = _state with { BbSum = bbSum, BbSumSq = bbSumSq, KcSum = kcSum };
|
||||
_state = _state with { BbSum = bbSum, BbSumSq = bbSumSq, KcSum = kcSum, BbSumComp = 0, BbSumSqComp = 0, KcSumComp = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -656,10 +675,8 @@ public sealed class Bbs : ITValuePublisher
|
||||
_bbBuffer.Clear();
|
||||
_kcBuffer.Clear();
|
||||
|
||||
_state = new State(0, 0, 0, 0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, 0, false);
|
||||
_state = new State(0, 0, 0, 0, 0, 0, 0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, 0, false);
|
||||
_p_state = _state;
|
||||
_tickCount = 0;
|
||||
_p_tickCount = 0;
|
||||
_prevSqueezeOn = false;
|
||||
_p_prevSqueezeOn = false;
|
||||
|
||||
|
||||
+36
-17
@@ -32,14 +32,13 @@ public sealed class Cfo : AbstractBase
|
||||
private record struct State(
|
||||
double SumY,
|
||||
double SumXY,
|
||||
double SumYComp,
|
||||
double SumXYComp,
|
||||
int Count,
|
||||
double LastValid);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates CFO with specified period.
|
||||
/// </summary>
|
||||
@@ -100,29 +99,50 @@ public sealed class Cfo : AbstractBase
|
||||
{
|
||||
_p_state = _state;
|
||||
|
||||
// O(1) incremental sumXY maintenance (PineScript algorithm)
|
||||
// Kahan compensated O(1) incremental sumXY maintenance
|
||||
if (_buffer.Count == _buffer.Capacity)
|
||||
{
|
||||
double oldest = _buffer.Oldest;
|
||||
_state.SumY -= oldest;
|
||||
_state.SumXY -= _state.SumY;
|
||||
_state.SumXY += (_period - 1) * value;
|
||||
// Kahan delta for SumY
|
||||
{
|
||||
double delta = value - oldest;
|
||||
double y = delta - _state.SumYComp;
|
||||
double t = _state.SumY + y;
|
||||
_state.SumYComp = (t - _state.SumY) - y;
|
||||
_state.SumY = t;
|
||||
}
|
||||
// SumXY: net delta = -(SumY_old - oldest) + (period-1)*value
|
||||
// Since SumY already updated: SumY_old - oldest = SumY_new - value
|
||||
// So net delta = -(SumY_new - value) + (period-1)*value = -SumY_new + period*value
|
||||
{
|
||||
double netDelta = -_state.SumY + (_period * value);
|
||||
double y = netDelta - _state.SumXYComp;
|
||||
double t = _state.SumXY + y;
|
||||
_state.SumXYComp = (t - _state.SumXY) - y;
|
||||
_state.SumXY = t;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.SumXY += _state.Count * value;
|
||||
// Warmup: Kahan addition for SumY
|
||||
{
|
||||
double y = value - _state.SumYComp;
|
||||
double t = _state.SumY + y;
|
||||
_state.SumYComp = (t - _state.SumY) - y;
|
||||
_state.SumY = t;
|
||||
}
|
||||
// Kahan addition for SumXY
|
||||
{
|
||||
double addXY = _state.Count * value;
|
||||
double y = addXY - _state.SumXYComp;
|
||||
double t = _state.SumXY + y;
|
||||
_state.SumXYComp = (t - _state.SumXY) - y;
|
||||
_state.SumXY = t;
|
||||
}
|
||||
_state.Count++;
|
||||
}
|
||||
|
||||
_state.SumY += value;
|
||||
_buffer.Add(value);
|
||||
|
||||
_tickCount++;
|
||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
RecalculateSums();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -199,7 +219,6 @@ public sealed class Cfo : AbstractBase
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
_tickCount = 0;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
|
||||
+49
-20
@@ -39,13 +39,13 @@ public sealed class Cti : AbstractBase
|
||||
double SumY,
|
||||
double SumY2,
|
||||
double SumXY,
|
||||
double SumYComp,
|
||||
double SumY2Comp,
|
||||
double SumXYComp,
|
||||
int Count,
|
||||
double LastValid);
|
||||
private State _s, _ps;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates CTI with the specified lookback period.
|
||||
/// </summary>
|
||||
@@ -102,30 +102,60 @@ public sealed class Cti : AbstractBase
|
||||
|
||||
if (_buffer.Count == _buffer.Capacity)
|
||||
{
|
||||
// Full window: O(1) incremental update
|
||||
// Full window: Kahan compensated O(1) update
|
||||
double oldest = _buffer.Oldest;
|
||||
_s.SumY -= oldest;
|
||||
_s.SumY2 -= oldest * oldest;
|
||||
_s.SumXY -= _s.SumY; // shift all indices down by 1
|
||||
_s.SumXY += (_period - 1) * value; // new value at position (n-1)
|
||||
// Kahan delta for SumY
|
||||
{
|
||||
double delta = value - oldest;
|
||||
double y = delta - _s.SumYComp;
|
||||
double t = _s.SumY + y;
|
||||
_s.SumYComp = (t - _s.SumY) - y;
|
||||
_s.SumY = t;
|
||||
}
|
||||
// Kahan delta for SumY2
|
||||
{
|
||||
double deltaSq = (value * value) - (oldest * oldest);
|
||||
double y = deltaSq - _s.SumY2Comp;
|
||||
double t = _s.SumY2 + y;
|
||||
_s.SumY2Comp = (t - _s.SumY2) - y;
|
||||
_s.SumY2 = t;
|
||||
}
|
||||
// SumXY net delta = -SumY_new + period*value
|
||||
{
|
||||
double netDelta = -_s.SumY + (_period * value);
|
||||
double y = netDelta - _s.SumXYComp;
|
||||
double t = _s.SumXY + y;
|
||||
_s.SumXYComp = (t - _s.SumXY) - y;
|
||||
_s.SumXY = t;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Growing window during warmup
|
||||
_s.SumXY += _s.Count * value;
|
||||
// Growing window: Kahan additions
|
||||
{
|
||||
double y = value - _s.SumYComp;
|
||||
double t = _s.SumY + y;
|
||||
_s.SumYComp = (t - _s.SumY) - y;
|
||||
_s.SumY = t;
|
||||
}
|
||||
{
|
||||
double sq = value * value;
|
||||
double y = sq - _s.SumY2Comp;
|
||||
double t = _s.SumY2 + y;
|
||||
_s.SumY2Comp = (t - _s.SumY2) - y;
|
||||
_s.SumY2 = t;
|
||||
}
|
||||
{
|
||||
double addXY = _s.Count * value;
|
||||
double y = addXY - _s.SumXYComp;
|
||||
double t = _s.SumXY + y;
|
||||
_s.SumXYComp = (t - _s.SumXY) - y;
|
||||
_s.SumXY = t;
|
||||
}
|
||||
_s.Count++;
|
||||
}
|
||||
|
||||
_s.SumY += value;
|
||||
_s.SumY2 = Math.FusedMultiplyAdd(value, value, _s.SumY2);
|
||||
_buffer.Add(value);
|
||||
|
||||
_tickCount++;
|
||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
Resync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -213,7 +243,6 @@ public sealed class Cti : AbstractBase
|
||||
_buffer.Clear();
|
||||
_s = default;
|
||||
_ps = default;
|
||||
_tickCount = 0;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace QuanTAlib.Tests;
|
||||
public sealed class CtiTests
|
||||
{
|
||||
private const int DefaultPeriod = 20;
|
||||
private const double Tolerance = 1e-10;
|
||||
private const double Tolerance = 1e-7;
|
||||
|
||||
// ───── A) Constructor validation ─────
|
||||
|
||||
|
||||
@@ -34,14 +34,13 @@ public sealed class Inertia : AbstractBase
|
||||
private record struct State(
|
||||
double SumY,
|
||||
double SumXY,
|
||||
double SumYComp,
|
||||
double SumXYComp,
|
||||
int Count,
|
||||
double LastValid);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates Inertia with specified period.
|
||||
/// </summary>
|
||||
@@ -102,29 +101,48 @@ public sealed class Inertia : AbstractBase
|
||||
{
|
||||
_p_state = _state;
|
||||
|
||||
// O(1) incremental sumXY maintenance (PineScript algorithm)
|
||||
// Kahan compensated O(1) incremental maintenance
|
||||
if (_buffer.Count == _buffer.Capacity)
|
||||
{
|
||||
double oldest = _buffer.Oldest;
|
||||
_state.SumY -= oldest;
|
||||
_state.SumXY -= _state.SumY;
|
||||
_state.SumXY += (_period - 1) * value;
|
||||
// Kahan delta for SumY
|
||||
{
|
||||
double delta = value - oldest;
|
||||
double y = delta - _state.SumYComp;
|
||||
double t = _state.SumY + y;
|
||||
_state.SumYComp = (t - _state.SumY) - y;
|
||||
_state.SumY = t;
|
||||
}
|
||||
// SumXY net delta = -SumY_new + period*value
|
||||
{
|
||||
double netDelta = -_state.SumY + (_period * value);
|
||||
double y = netDelta - _state.SumXYComp;
|
||||
double t = _state.SumXY + y;
|
||||
_state.SumXYComp = (t - _state.SumXY) - y;
|
||||
_state.SumXY = t;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.SumXY += _state.Count * value;
|
||||
// Warmup: Kahan addition for SumY
|
||||
{
|
||||
double y = value - _state.SumYComp;
|
||||
double t = _state.SumY + y;
|
||||
_state.SumYComp = (t - _state.SumY) - y;
|
||||
_state.SumY = t;
|
||||
}
|
||||
// Kahan addition for SumXY
|
||||
{
|
||||
double addXY = _state.Count * value;
|
||||
double y = addXY - _state.SumXYComp;
|
||||
double t = _state.SumXY + y;
|
||||
_state.SumXYComp = (t - _state.SumXY) - y;
|
||||
_state.SumXY = t;
|
||||
}
|
||||
_state.Count++;
|
||||
}
|
||||
|
||||
_state.SumY += value;
|
||||
_buffer.Add(value);
|
||||
|
||||
_tickCount++;
|
||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
RecalculateSums();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,7 +219,6 @@ public sealed class Inertia : AbstractBase
|
||||
_buffer.Clear();
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
_tickCount = 0;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user