mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-13 16:18:05 +00:00
Merge dev into main: v0.8.7 Kahan compensated summation
This commit is contained in:
+17
-71
@@ -25,17 +25,11 @@ namespace QuanTAlib;
|
||||
public sealed class Evwma : ITValuePublisher
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double SumVol, double Result, int Index, int Head, int Count, int SyncCounter)
|
||||
private record struct State(double SumVol, double SumVolComp, double Result, int Index, int Head, int Count)
|
||||
{
|
||||
public static State New() => new() { SumVol = 0, Result = double.NaN, Index = 0, Head = 0, Count = 0, SyncCounter = 0 };
|
||||
public static State New() => new() { SumVol = 0, SumVolComp = 0, Result = double.NaN, Index = 0, Head = 0, Count = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resync interval to limit floating-point drift in running volume sum.
|
||||
/// Full recalculation every N bars.
|
||||
/// </summary>
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
private readonly int _period;
|
||||
private readonly double[] _volBuffer;
|
||||
private State _state;
|
||||
@@ -118,25 +112,6 @@ public sealed class Evwma : ITValuePublisher
|
||||
return lastValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates running volume sum from buffer to eliminate accumulated floating-point drift.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ResyncRunningTotals(ref State s)
|
||||
{
|
||||
double sumVol = 0;
|
||||
|
||||
for (int i = 0; i < _period; i++)
|
||||
{
|
||||
double v = _volBuffer[i];
|
||||
if (v > 0)
|
||||
{
|
||||
sumVol += v;
|
||||
}
|
||||
}
|
||||
|
||||
s.SumVol = sumVol;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
@@ -203,6 +178,8 @@ public sealed class Evwma : ITValuePublisher
|
||||
_lastValidVolume = _p_lastValidVolume;
|
||||
// Restore buffer value at head position
|
||||
_volBuffer[s.Head] = _p_bufferVol;
|
||||
// Reset Kahan compensation on re-entry
|
||||
s.SumVolComp = 0;
|
||||
}
|
||||
|
||||
// Get valid values
|
||||
@@ -210,16 +187,13 @@ public sealed class Evwma : ITValuePublisher
|
||||
double currentVol = GetValidValue(volume, ref _lastValidVolume);
|
||||
currentVol = Math.Max(0.0, currentVol);
|
||||
|
||||
// Remove oldest volume from circular buffer
|
||||
// Kahan-compensated delta update for SumVol
|
||||
double oldVol = _volBuffer[s.Head];
|
||||
|
||||
if (s.Count >= _period)
|
||||
{
|
||||
s.SumVol -= oldVol;
|
||||
}
|
||||
|
||||
// Add current volume to running sum
|
||||
s.SumVol += currentVol;
|
||||
double delta = currentVol - (s.Count >= _period ? oldVol : 0);
|
||||
double y = delta - s.SumVolComp;
|
||||
double t = s.SumVol + y;
|
||||
s.SumVolComp = (t - s.SumVol) - y;
|
||||
s.SumVol = t;
|
||||
|
||||
// Store in circular buffer
|
||||
_volBuffer[s.Head] = currentVol;
|
||||
@@ -234,14 +208,6 @@ public sealed class Evwma : ITValuePublisher
|
||||
{
|
||||
s.Count++;
|
||||
}
|
||||
|
||||
// Periodic resync to limit floating-point drift
|
||||
s.SyncCounter++;
|
||||
if (s.SyncCounter >= ResyncInterval && s.Count >= _period)
|
||||
{
|
||||
s.SyncCounter = 0;
|
||||
ResyncRunningTotals(ref s);
|
||||
}
|
||||
}
|
||||
|
||||
// EVWMA calculation
|
||||
@@ -384,6 +350,7 @@ public sealed class Evwma : ITValuePublisher
|
||||
volBuffer.Clear();
|
||||
|
||||
double sumVol = 0;
|
||||
double sumVolComp = 0;
|
||||
double result = double.NaN;
|
||||
double lastValidPrice = 0;
|
||||
double lastValidVolume = 0;
|
||||
@@ -408,8 +375,6 @@ public sealed class Evwma : ITValuePublisher
|
||||
}
|
||||
}
|
||||
|
||||
int syncCounter = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
// Get valid values with NaN substitution
|
||||
@@ -426,16 +391,13 @@ public sealed class Evwma : ITValuePublisher
|
||||
lastValidVolume = volume[i];
|
||||
}
|
||||
|
||||
// Remove oldest volume from circular buffer
|
||||
// Kahan-compensated delta update for SumVol
|
||||
double oldVol = volBuffer[head];
|
||||
|
||||
if (count >= period)
|
||||
{
|
||||
sumVol -= oldVol;
|
||||
}
|
||||
|
||||
// Add current volume to running sum
|
||||
sumVol += currentVol;
|
||||
double delta = currentVol - (count >= period ? oldVol : 0);
|
||||
double y = delta - sumVolComp;
|
||||
double t = sumVol + y;
|
||||
sumVolComp = (t - sumVol) - y;
|
||||
sumVol = t;
|
||||
|
||||
// Store in circular buffer
|
||||
volBuffer[head] = currentVol;
|
||||
@@ -448,22 +410,6 @@ public sealed class Evwma : ITValuePublisher
|
||||
count++;
|
||||
}
|
||||
|
||||
// Periodic resync to limit floating-point drift
|
||||
syncCounter++;
|
||||
if (syncCounter >= ResyncInterval && count >= period)
|
||||
{
|
||||
syncCounter = 0;
|
||||
sumVol = 0;
|
||||
for (int j = 0; j < period; j++)
|
||||
{
|
||||
double vj = volBuffer[j];
|
||||
if (vj > 0)
|
||||
{
|
||||
sumVol += vj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EVWMA calculation
|
||||
if (double.IsNaN(result))
|
||||
{
|
||||
|
||||
+33
-61
@@ -23,17 +23,11 @@ namespace QuanTAlib;
|
||||
public sealed class Vwma : ITValuePublisher
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double SumPV, double SumVol, int Index, int Head, int Count, int SyncCounter)
|
||||
private record struct State(double SumPV, double SumVol, double SumPVComp, double SumVolComp, int Index, int Head, int Count)
|
||||
{
|
||||
public static State New() => new() { SumPV = 0, SumVol = 0, Index = 0, Head = 0, Count = 0, SyncCounter = 0 };
|
||||
public static State New() => new() { SumPV = 0, SumVol = 0, SumPVComp = 0, SumVolComp = 0, Index = 0, Head = 0, Count = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resync interval to limit floating-point drift in running sums.
|
||||
/// Full recalculation every N bars.
|
||||
/// </summary>
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
private readonly int _period;
|
||||
private readonly double[] _priceBuffer;
|
||||
private readonly double[] _volBuffer;
|
||||
@@ -222,18 +216,23 @@ public sealed class Vwma : ITValuePublisher
|
||||
double oldPrice = _priceBuffer[s.Head];
|
||||
double oldVol = _volBuffer[s.Head];
|
||||
|
||||
if (s.Count >= _period && oldVol > 0)
|
||||
{
|
||||
s.SumPV = Math.FusedMultiplyAdd(-oldPrice, oldVol, s.SumPV);
|
||||
s.SumVol -= oldVol;
|
||||
}
|
||||
// Compute net deltas for Kahan compensation
|
||||
double pvRemove = (s.Count >= _period && oldVol > 0) ? oldPrice * oldVol : 0.0;
|
||||
double pvAdd = currentVol > 0 ? currentPrice * currentVol : 0.0;
|
||||
double volRemove = (s.Count >= _period && oldVol > 0) ? oldVol : 0.0;
|
||||
double volAdd = currentVol > 0 ? currentVol : 0.0;
|
||||
|
||||
// Add new values
|
||||
if (currentVol > 0)
|
||||
{
|
||||
s.SumPV = Math.FusedMultiplyAdd(currentPrice, currentVol, s.SumPV);
|
||||
s.SumVol += currentVol;
|
||||
}
|
||||
// Kahan compensated SumPV
|
||||
double pvDelta = pvAdd - pvRemove - s.SumPVComp;
|
||||
double pvNewSum = s.SumPV + pvDelta;
|
||||
s.SumPVComp = (pvNewSum - s.SumPV) - pvDelta;
|
||||
s.SumPV = pvNewSum;
|
||||
|
||||
// Kahan compensated SumVol
|
||||
double volDelta = volAdd - volRemove - s.SumVolComp;
|
||||
double volNewSum = s.SumVol + volDelta;
|
||||
s.SumVolComp = (volNewSum - s.SumVol) - volDelta;
|
||||
s.SumVol = volNewSum;
|
||||
|
||||
// Store in circular buffer
|
||||
_priceBuffer[s.Head] = currentPrice;
|
||||
@@ -249,14 +248,6 @@ public sealed class Vwma : ITValuePublisher
|
||||
{
|
||||
s.Count++;
|
||||
}
|
||||
|
||||
// Periodic resync to limit floating-point drift
|
||||
s.SyncCounter++;
|
||||
if (s.SyncCounter >= ResyncInterval && s.Count >= _period)
|
||||
{
|
||||
s.SyncCounter = 0;
|
||||
ResyncRunningTotals(ref s);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate VWMA
|
||||
@@ -389,7 +380,9 @@ public sealed class Vwma : ITValuePublisher
|
||||
volBuffer.Clear();
|
||||
|
||||
double sumPV = 0;
|
||||
double sumPVComp = 0;
|
||||
double sumVol = 0;
|
||||
double sumVolComp = 0;
|
||||
double lastValidPrice = 0;
|
||||
double lastValidVolume = 0;
|
||||
int head = 0;
|
||||
@@ -413,8 +406,6 @@ public sealed class Vwma : ITValuePublisher
|
||||
}
|
||||
}
|
||||
|
||||
int syncCounter = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
// Get valid values with NaN substitution
|
||||
@@ -430,22 +421,23 @@ public sealed class Vwma : ITValuePublisher
|
||||
lastValidVolume = volume[i];
|
||||
}
|
||||
|
||||
// Remove old values from circular buffer
|
||||
// Kahan-compensated delta updates for SumPV and SumVol
|
||||
double oldPrice = priceBuffer[head];
|
||||
double oldVol = volBuffer[head];
|
||||
|
||||
if (count >= period && oldVol > 0)
|
||||
{
|
||||
sumPV = Math.FusedMultiplyAdd(-oldPrice, oldVol, sumPV);
|
||||
sumVol -= oldVol;
|
||||
}
|
||||
double newPV = currentVol > 0 ? currentPrice * currentVol : 0;
|
||||
double oldPV = (count >= period && oldVol > 0) ? oldPrice * oldVol : 0;
|
||||
double deltaPV = newPV - oldPV;
|
||||
double yPV = deltaPV - sumPVComp;
|
||||
double tPV = sumPV + yPV;
|
||||
sumPVComp = (tPV - sumPV) - yPV;
|
||||
sumPV = tPV;
|
||||
|
||||
// Add new values
|
||||
if (currentVol > 0)
|
||||
{
|
||||
sumPV = Math.FusedMultiplyAdd(currentPrice, currentVol, sumPV);
|
||||
sumVol += currentVol;
|
||||
}
|
||||
double deltaVol = (currentVol > 0 ? currentVol : 0) - (count >= period && oldVol > 0 ? oldVol : 0);
|
||||
double yVol = deltaVol - sumVolComp;
|
||||
double tVol = sumVol + yVol;
|
||||
sumVolComp = (tVol - sumVol) - yVol;
|
||||
sumVol = tVol;
|
||||
|
||||
// Store in circular buffer
|
||||
priceBuffer[head] = currentPrice;
|
||||
@@ -459,26 +451,6 @@ public sealed class Vwma : ITValuePublisher
|
||||
count++;
|
||||
}
|
||||
|
||||
// Periodic resync to limit floating-point drift
|
||||
syncCounter++;
|
||||
if (syncCounter >= ResyncInterval && count >= period)
|
||||
{
|
||||
syncCounter = 0;
|
||||
// Recalculate sums from buffer
|
||||
sumPV = 0;
|
||||
sumVol = 0;
|
||||
for (int j = 0; j < period; j++)
|
||||
{
|
||||
double pj = priceBuffer[j];
|
||||
double vj = volBuffer[j];
|
||||
if (vj > 0)
|
||||
{
|
||||
sumPV = Math.FusedMultiplyAdd(pj, vj, sumPV);
|
||||
sumVol += vj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate VWMA
|
||||
output[i] = sumVol > double.Epsilon ? sumPV / sumVol : currentPrice;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user