feat: add new indicators (Decay, Edecay, MinusDi, MinusDm, PlusDi, PlusDm, Maxindex, Minindex, Sarext) and update pine scripts, core libs, validation tests, and python bindings

This commit is contained in:
Miha Kralj
2026-03-09 13:45:46 -07:00
parent 8e43d62cbb
commit 031f1b5fe6
491 changed files with 6156 additions and 5590 deletions
@@ -325,5 +325,33 @@ public class TtmSqueezeValidationTests
Assert.InRange(squeeze.ColorCode, 0, 3);
}
[Fact]
public void TtmSqueeze_Correction_Recomputes()
{
var ind = new TtmSqueeze();
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
// Build state well past warmup
for (int i = 0; i < 100; i++)
{
double p = 100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0);
ind.Update(new TBar(t0.AddMinutes(i), p, p + 2, p - 2, p, 1000), isNew: true);
}
// Anchor bar
var anchorTime = t0.AddMinutes(100);
const double anchorClose = 105.5;
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: true);
double anchorMomentum = ind.Momentum.Value;
// Correction with a dramatically different price — Momentum must change
ind.Update(new TBar(anchorTime, anchorClose * 10, (anchorClose + 2) * 10, (anchorClose - 2) * 10, anchorClose * 10, 1000), isNew: false);
Assert.NotEqual(anchorMomentum, ind.Momentum.Value);
// Correction back to original price — must exactly restore original Momentum
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: false);
Assert.Equal(anchorMomentum, ind.Momentum.Value, 1e-9);
}
#endregion
}
+14 -43
View File
@@ -322,7 +322,7 @@ public sealed class TtmSqueeze : ITValuePublisher
_priceSum -= oldest;
_priceSumSquares -= oldest * oldest;
}
_priceBuffer.Add(close, isNew);
_priceBuffer.Add(close);
_priceSum += close;
_priceSumSquares += close * close;
@@ -365,11 +365,11 @@ public sealed class TtmSqueeze : ITValuePublisher
_prevSqueezeOn = squeezeOn;
// === Donchian Midline ===
_highBuffer.Add(high, isNew);
_lowBuffer.Add(low, isNew);
_highBuffer.Add(high);
_lowBuffer.Add(low);
double donchianHigh = GetMax(_highBuffer);
double donchianLow = GetMin(_lowBuffer);
double donchianHigh = _highBuffer.Max();
double donchianLow = _lowBuffer.Min();
double donchianMid = (donchianHigh + donchianLow) / 2;
// === Momentum (Linear Regression) ===
@@ -383,7 +383,7 @@ public sealed class TtmSqueeze : ITValuePublisher
_momentumSumXY = _momentumSumXY + prevSumY - _momPeriod * oldest;
_momentumSumY -= oldest;
}
_momentumBuffer.Add(deviation, isNew);
_momentumBuffer.Add(deviation);
_momentumSumY += deviation;
// Recalculate sumXY during warmup (non-O(1), but short duration)
@@ -535,6 +535,10 @@ public sealed class TtmSqueeze : ITValuePublisher
_saved_prevMomentum = _prevMomentum;
_saved_prevSqueezeOn = _prevSqueezeOn;
_saved_barCount = _barCount;
_priceBuffer.Snapshot();
_highBuffer.Snapshot();
_lowBuffer.Snapshot();
_momentumBuffer.Snapshot();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -552,43 +556,10 @@ public sealed class TtmSqueeze : ITValuePublisher
_prevMomentum = _saved_prevMomentum;
_prevSqueezeOn = _saved_prevSqueezeOn;
_barCount = _saved_barCount;
_priceBuffer.Restore();
_highBuffer.Restore();
_lowBuffer.Restore();
_momentumBuffer.Restore();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double GetMax(RingBuffer buffer)
{
if (buffer.Count == 0)
{
return 0;
}
var span = buffer.GetSpan();
double max = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] > max)
{
max = span[i];
}
}
return max;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double GetMin(RingBuffer buffer)
{
if (buffer.Count == 0)
{
return 0;
}
var span = buffer.GetSpan();
double min = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] < min)
{
min = span[i];
}
}
return min;
}
}