mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 04:58:08 +00:00
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:
+31
-44
@@ -34,18 +34,17 @@ public sealed class Ghla : AbstractBase
|
||||
private record struct State(
|
||||
double HighSum,
|
||||
double LowSum,
|
||||
double HighSumComp,
|
||||
double LowSumComp,
|
||||
int Trend,
|
||||
double LastValidHigh,
|
||||
double LastValidLow,
|
||||
double LastValidClose,
|
||||
int TickCount
|
||||
double LastValidClose
|
||||
);
|
||||
|
||||
private State _s;
|
||||
private State _ps;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Creates GHLA with specified SMA period.
|
||||
/// </summary>
|
||||
@@ -61,7 +60,7 @@ public sealed class Ghla : AbstractBase
|
||||
_lowBuffer = new RingBuffer(period);
|
||||
Name = $"Ghla({period})";
|
||||
WarmupPeriod = period;
|
||||
_s = new State(0, 0, 0, 0, 0, 0, 0);
|
||||
_s = default;
|
||||
_ps = _s;
|
||||
}
|
||||
|
||||
@@ -178,7 +177,7 @@ public sealed class Ghla : AbstractBase
|
||||
{
|
||||
_highBuffer.Clear();
|
||||
_lowBuffer.Clear();
|
||||
_s = new State(0, 0, 0, 0, 0, 0, 0);
|
||||
_s = default;
|
||||
_ps = _s;
|
||||
Last = default;
|
||||
}
|
||||
@@ -291,33 +290,32 @@ public sealed class Ghla : AbstractBase
|
||||
// Update running SMA sums via ring buffers
|
||||
if (isNew)
|
||||
{
|
||||
// High buffer
|
||||
// High buffer — Kahan compensated
|
||||
double highRemoved = _highBuffer.Count == _highBuffer.Capacity ? _highBuffer.Oldest : 0.0;
|
||||
s.HighSum = s.HighSum - highRemoved + high;
|
||||
double hDelta = high - highRemoved - s.HighSumComp;
|
||||
double hNewSum = s.HighSum + hDelta;
|
||||
s.HighSumComp = (hNewSum - s.HighSum) - hDelta;
|
||||
s.HighSum = hNewSum;
|
||||
_highBuffer.Add(high);
|
||||
|
||||
// Low buffer
|
||||
// Low buffer — Kahan compensated
|
||||
double lowRemoved = _lowBuffer.Count == _lowBuffer.Capacity ? _lowBuffer.Oldest : 0.0;
|
||||
s.LowSum = s.LowSum - lowRemoved + low;
|
||||
double lDelta = low - lowRemoved - s.LowSumComp;
|
||||
double lNewSum = s.LowSum + lDelta;
|
||||
s.LowSumComp = (lNewSum - s.LowSum) - lDelta;
|
||||
s.LowSum = lNewSum;
|
||||
_lowBuffer.Add(low);
|
||||
|
||||
// Periodic resync to limit floating-point drift
|
||||
s.TickCount++;
|
||||
if (_highBuffer.IsFull && s.TickCount >= ResyncInterval)
|
||||
{
|
||||
s.TickCount = 0;
|
||||
s.HighSum = _highBuffer.RecalculateSum();
|
||||
s.LowSum = _lowBuffer.RecalculateSum();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bar correction: update newest value in both buffers
|
||||
_highBuffer.UpdateNewest(high);
|
||||
s.HighSum = _highBuffer.Sum;
|
||||
s.HighSumComp = 0;
|
||||
|
||||
_lowBuffer.UpdateNewest(low);
|
||||
s.LowSum = _lowBuffer.Sum;
|
||||
s.LowSumComp = 0;
|
||||
}
|
||||
|
||||
// Compute SMAs
|
||||
@@ -390,7 +388,9 @@ public sealed class Ghla : AbstractBase
|
||||
try
|
||||
{
|
||||
double highSum = 0;
|
||||
double highSumComp = 0;
|
||||
double lowSum = 0;
|
||||
double lowSumComp = 0;
|
||||
double lastValidHigh = 0;
|
||||
double lastValidLow = 0;
|
||||
double lastValidClose = 0;
|
||||
@@ -398,7 +398,6 @@ public sealed class Ghla : AbstractBase
|
||||
int lowIdx = 0;
|
||||
int filled = 0;
|
||||
int trend = 0;
|
||||
int tickCount = 0;
|
||||
|
||||
// Seed lastValid values
|
||||
for (int k = 0; k < len; k++)
|
||||
@@ -459,12 +458,14 @@ public sealed class Ghla : AbstractBase
|
||||
c = lastValidClose;
|
||||
}
|
||||
|
||||
// Update high buffer
|
||||
if (filled >= period)
|
||||
// Kahan-compensated update for high buffer
|
||||
{
|
||||
highSum -= highBuf[highIdx];
|
||||
double deltaH = h - (filled >= period ? highBuf[highIdx] : 0);
|
||||
double yH = deltaH - highSumComp;
|
||||
double tH = highSum + yH;
|
||||
highSumComp = (tH - highSum) - yH;
|
||||
highSum = tH;
|
||||
}
|
||||
highSum += h;
|
||||
highBuf[highIdx] = h;
|
||||
highIdx++;
|
||||
if (highIdx >= period)
|
||||
@@ -472,12 +473,14 @@ public sealed class Ghla : AbstractBase
|
||||
highIdx = 0;
|
||||
}
|
||||
|
||||
// Update low buffer
|
||||
if (filled >= period)
|
||||
// Kahan-compensated update for low buffer
|
||||
{
|
||||
lowSum -= lowBuf[lowIdx];
|
||||
double deltaL = l - (filled >= period ? lowBuf[lowIdx] : 0);
|
||||
double yL = deltaL - lowSumComp;
|
||||
double tL = lowSum + yL;
|
||||
lowSumComp = (tL - lowSum) - yL;
|
||||
lowSum = tL;
|
||||
}
|
||||
lowSum += l;
|
||||
lowBuf[lowIdx] = l;
|
||||
lowIdx++;
|
||||
if (lowIdx >= period)
|
||||
@@ -490,22 +493,6 @@ public sealed class Ghla : AbstractBase
|
||||
filled++;
|
||||
}
|
||||
|
||||
// Resync
|
||||
tickCount++;
|
||||
if (filled >= period && tickCount >= ResyncInterval)
|
||||
{
|
||||
tickCount = 0;
|
||||
double recalcH = 0;
|
||||
double recalcL = 0;
|
||||
for (int k = 0; k < period; k++)
|
||||
{
|
||||
recalcH += highBuf[k];
|
||||
recalcL += lowBuf[k];
|
||||
}
|
||||
highSum = recalcH;
|
||||
lowSum = recalcL;
|
||||
}
|
||||
|
||||
double smaH = highSum / filled;
|
||||
double smaL = lowSum / filled;
|
||||
|
||||
|
||||
+30
-61
@@ -33,16 +33,14 @@ public sealed class Ravi : AbstractBase
|
||||
private record struct State(
|
||||
double ShortSum,
|
||||
double LongSum,
|
||||
double LastValidValue,
|
||||
int ShortTickCount,
|
||||
int LongTickCount
|
||||
double ShortSumComp,
|
||||
double LongSumComp,
|
||||
double LastValidValue
|
||||
);
|
||||
|
||||
private State _s;
|
||||
private State _ps;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Creates RAVI with specified short and long SMA periods.
|
||||
/// </summary>
|
||||
@@ -69,7 +67,7 @@ public sealed class Ravi : AbstractBase
|
||||
_longBuffer = new RingBuffer(longPeriod);
|
||||
Name = $"Ravi({shortPeriod},{longPeriod})";
|
||||
WarmupPeriod = longPeriod;
|
||||
_s = new State(0, 0, 0, 0, 0);
|
||||
_s = default;
|
||||
_ps = _s;
|
||||
}
|
||||
|
||||
@@ -121,38 +119,32 @@ public sealed class Ravi : AbstractBase
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
// Short buffer: remove oldest, add new
|
||||
// Short buffer — Kahan compensated
|
||||
double shortRemoved = _shortBuffer.Count == _shortBuffer.Capacity ? _shortBuffer.Oldest : 0.0;
|
||||
s.ShortSum = s.ShortSum - shortRemoved + val;
|
||||
double sDelta = val - shortRemoved - s.ShortSumComp;
|
||||
double sNewSum = s.ShortSum + sDelta;
|
||||
s.ShortSumComp = (sNewSum - s.ShortSum) - sDelta;
|
||||
s.ShortSum = sNewSum;
|
||||
_shortBuffer.Add(val);
|
||||
|
||||
// Long buffer: remove oldest, add new
|
||||
// Long buffer — Kahan compensated
|
||||
double longRemoved = _longBuffer.Count == _longBuffer.Capacity ? _longBuffer.Oldest : 0.0;
|
||||
s.LongSum = s.LongSum - longRemoved + val;
|
||||
double lDelta = val - longRemoved - s.LongSumComp;
|
||||
double lNewSum = s.LongSum + lDelta;
|
||||
s.LongSumComp = (lNewSum - s.LongSum) - lDelta;
|
||||
s.LongSum = lNewSum;
|
||||
_longBuffer.Add(val);
|
||||
|
||||
// Resync to prevent floating-point drift
|
||||
s.ShortTickCount++;
|
||||
if (_shortBuffer.IsFull && s.ShortTickCount >= ResyncInterval)
|
||||
{
|
||||
s.ShortTickCount = 0;
|
||||
s.ShortSum = _shortBuffer.RecalculateSum();
|
||||
}
|
||||
s.LongTickCount++;
|
||||
if (_longBuffer.IsFull && s.LongTickCount >= ResyncInterval)
|
||||
{
|
||||
s.LongTickCount = 0;
|
||||
s.LongSum = _longBuffer.RecalculateSum();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bar correction: update newest value in both buffers
|
||||
_shortBuffer.UpdateNewest(val);
|
||||
s.ShortSum = _shortBuffer.Sum;
|
||||
s.ShortSumComp = 0;
|
||||
|
||||
_longBuffer.UpdateNewest(val);
|
||||
s.LongSum = _longBuffer.Sum;
|
||||
s.LongSumComp = 0;
|
||||
}
|
||||
|
||||
// Calculate RAVI
|
||||
@@ -335,7 +327,9 @@ public sealed class Ravi : AbstractBase
|
||||
try
|
||||
{
|
||||
double shortSum = 0;
|
||||
double shortSumComp = 0;
|
||||
double longSum = 0;
|
||||
double longSumComp = 0;
|
||||
double lastValid = 0;
|
||||
int shortIdx = 0;
|
||||
int longIdx = 0;
|
||||
@@ -352,9 +346,6 @@ public sealed class Ravi : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
int shortTickCount = 0;
|
||||
int longTickCount = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
@@ -367,12 +358,14 @@ public sealed class Ravi : AbstractBase
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
// Update short buffer
|
||||
if (shortFilled >= shortPeriod)
|
||||
// Kahan-compensated update for short buffer
|
||||
{
|
||||
shortSum -= shortBuf[shortIdx];
|
||||
double deltaS = val - (shortFilled >= shortPeriod ? shortBuf[shortIdx] : 0);
|
||||
double yS = deltaS - shortSumComp;
|
||||
double tS = shortSum + yS;
|
||||
shortSumComp = (tS - shortSum) - yS;
|
||||
shortSum = tS;
|
||||
}
|
||||
shortSum += val;
|
||||
shortBuf[shortIdx] = val;
|
||||
if (shortFilled < shortPeriod)
|
||||
{
|
||||
@@ -384,12 +377,14 @@ public sealed class Ravi : AbstractBase
|
||||
shortIdx = 0;
|
||||
}
|
||||
|
||||
// Update long buffer
|
||||
if (longFilled >= longPeriod)
|
||||
// Kahan-compensated update for long buffer
|
||||
{
|
||||
longSum -= longBuf[longIdx];
|
||||
double deltaL = val - (longFilled >= longPeriod ? longBuf[longIdx] : 0);
|
||||
double yL = deltaL - longSumComp;
|
||||
double tL = longSum + yL;
|
||||
longSumComp = (tL - longSum) - yL;
|
||||
longSum = tL;
|
||||
}
|
||||
longSum += val;
|
||||
longBuf[longIdx] = val;
|
||||
if (longFilled < longPeriod)
|
||||
{
|
||||
@@ -401,32 +396,6 @@ public sealed class Ravi : AbstractBase
|
||||
longIdx = 0;
|
||||
}
|
||||
|
||||
// Resync short
|
||||
shortTickCount++;
|
||||
if (shortFilled >= shortPeriod && shortTickCount >= ResyncInterval)
|
||||
{
|
||||
shortTickCount = 0;
|
||||
double recalc = 0;
|
||||
for (int k = 0; k < shortPeriod; k++)
|
||||
{
|
||||
recalc += shortBuf[k];
|
||||
}
|
||||
shortSum = recalc;
|
||||
}
|
||||
|
||||
// Resync long
|
||||
longTickCount++;
|
||||
if (longFilled >= longPeriod && longTickCount >= ResyncInterval)
|
||||
{
|
||||
longTickCount = 0;
|
||||
double recalc = 0;
|
||||
for (int k = 0; k < longPeriod; k++)
|
||||
{
|
||||
recalc += longBuf[k];
|
||||
}
|
||||
longSum = recalc;
|
||||
}
|
||||
|
||||
// Calculate RAVI
|
||||
if (shortFilled >= shortPeriod && longFilled >= longPeriod)
|
||||
{
|
||||
|
||||
+15
-31
@@ -31,17 +31,15 @@ public sealed class Vhf : AbstractBase
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(
|
||||
double DiffSum,
|
||||
double DiffSumComp,
|
||||
double PrevClose,
|
||||
double LastValidValue,
|
||||
int TickCount,
|
||||
bool HasPrevClose
|
||||
);
|
||||
|
||||
private State _s;
|
||||
private State _ps;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Creates VHF with specified lookback period.
|
||||
/// </summary>
|
||||
@@ -58,7 +56,7 @@ public sealed class Vhf : AbstractBase
|
||||
_diffBuffer = new RingBuffer(period); // period absolute differences
|
||||
Name = $"Vhf({period})";
|
||||
WarmupPeriod = period + 1;
|
||||
_s = new State(0, 0, 0, 0, false);
|
||||
_s = default;
|
||||
_ps = _s;
|
||||
}
|
||||
|
||||
@@ -116,11 +114,14 @@ public sealed class Vhf : AbstractBase
|
||||
absDiff = Math.Abs(val - s.PrevClose);
|
||||
}
|
||||
|
||||
// Update diff buffer running sum
|
||||
// Update diff buffer running sum — Kahan compensated
|
||||
if (s.HasPrevClose)
|
||||
{
|
||||
double diffRemoved = _diffBuffer.Count == _diffBuffer.Capacity ? _diffBuffer.Oldest : 0.0;
|
||||
s.DiffSum = s.DiffSum - diffRemoved + absDiff;
|
||||
double delta = absDiff - diffRemoved - s.DiffSumComp;
|
||||
double newSum = s.DiffSum + delta;
|
||||
s.DiffSumComp = (newSum - s.DiffSum) - delta;
|
||||
s.DiffSum = newSum;
|
||||
_diffBuffer.Add(absDiff);
|
||||
}
|
||||
|
||||
@@ -129,14 +130,6 @@ public sealed class Vhf : AbstractBase
|
||||
|
||||
s.PrevClose = val;
|
||||
s.HasPrevClose = true;
|
||||
|
||||
// Resync to prevent floating-point drift
|
||||
s.TickCount++;
|
||||
if (_diffBuffer.IsFull && s.TickCount >= ResyncInterval)
|
||||
{
|
||||
s.TickCount = 0;
|
||||
s.DiffSum = _diffBuffer.RecalculateSum();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -151,6 +144,7 @@ public sealed class Vhf : AbstractBase
|
||||
double newAbsDiff = Math.Abs(val - prevCloseForDiff);
|
||||
_diffBuffer.UpdateNewest(newAbsDiff);
|
||||
s.DiffSum = _diffBuffer.Sum;
|
||||
s.DiffSumComp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +321,7 @@ public sealed class Vhf : AbstractBase
|
||||
try
|
||||
{
|
||||
double diffSum = 0;
|
||||
double diffSumComp = 0;
|
||||
double lastValid = 0;
|
||||
double prevClose = 0;
|
||||
bool hasPrevClose = false;
|
||||
@@ -334,7 +329,6 @@ public sealed class Vhf : AbstractBase
|
||||
int closeFilled = 0;
|
||||
int diffIdx = 0;
|
||||
int diffFilled = 0;
|
||||
int tickCount = 0;
|
||||
|
||||
// Find first valid value to seed lastValid
|
||||
for (int k = 0; k < len; k++)
|
||||
@@ -363,12 +357,14 @@ public sealed class Vhf : AbstractBase
|
||||
{
|
||||
double absDiff = Math.Abs(val - prevClose);
|
||||
|
||||
// Update diff buffer
|
||||
if (diffFilled >= period)
|
||||
// Kahan-compensated update for diff buffer
|
||||
{
|
||||
diffSum -= diffBuf[diffIdx];
|
||||
double deltaD = absDiff - (diffFilled >= period ? diffBuf[diffIdx] : 0);
|
||||
double yD = deltaD - diffSumComp;
|
||||
double tD = diffSum + yD;
|
||||
diffSumComp = (tD - diffSum) - yD;
|
||||
diffSum = tD;
|
||||
}
|
||||
diffSum += absDiff;
|
||||
diffBuf[diffIdx] = absDiff;
|
||||
if (diffFilled < period)
|
||||
{
|
||||
@@ -396,18 +392,6 @@ public sealed class Vhf : AbstractBase
|
||||
prevClose = val;
|
||||
hasPrevClose = true;
|
||||
|
||||
// Resync diff sum
|
||||
tickCount++;
|
||||
if (diffFilled >= period && tickCount >= ResyncInterval)
|
||||
{
|
||||
tickCount = 0;
|
||||
double recalc = 0;
|
||||
for (int k = 0; k < period; k++)
|
||||
{
|
||||
recalc += diffBuf[k];
|
||||
}
|
||||
diffSum = recalc;
|
||||
}
|
||||
|
||||
// Calculate VHF
|
||||
if (closeFilled >= closeBufSize && diffFilled >= period)
|
||||
|
||||
Reference in New Issue
Block a user