mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 12:38:06 +00:00
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:
@@ -364,5 +364,33 @@ public class CcorValidationTests
|
||||
"Different thresholds should produce different market state classifications");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ccor_Correction_Recomputes()
|
||||
{
|
||||
var ind = new Ccor(period: 20);
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorResult = ind.Last.Value;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorResult, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorResult, ind.Last.Value, Tolerance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ public sealed class Ccor : AbstractBase
|
||||
{
|
||||
foreach (double value in source)
|
||||
{
|
||||
Update(new TValue(DateTime.UtcNow, value));
|
||||
Update(new TValue(DateTime.MinValue, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Correlation Cycle (CCOR)", "CCOR", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Cyber Cycle (CCYC)", "CCYC", overlay=false)
|
||||
|
||||
+1
-1
@@ -295,7 +295,7 @@ public sealed class Cg : AbstractBase
|
||||
int idx = (effectiveStart + j) % period;
|
||||
double price = buffer[idx];
|
||||
int weight = j + 1; // 1-based weighting
|
||||
weightedSum += weight * price;
|
||||
weightedSum = Math.FusedMultiplyAdd(weight, price, weightedSum);
|
||||
sum += price;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Center of Gravity (CG)", "CG", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Detrended Synthetic Price (DSP)", "DSP", overlay=false)
|
||||
|
||||
@@ -418,5 +418,33 @@ public class EacpValidationTests
|
||||
Assert.True(diff > 5, $"Should detect different cycles: {eacp1.DominantCycle} vs {eacp2.DominantCycle}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eacp_Correction_Recomputes()
|
||||
{
|
||||
var ind = new Eacp(8, 48, 3, true);
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorResult = ind.Last.Value;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorResult, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorResult, ind.Last.Value, Tolerance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public sealed class Eacp : AbstractBase
|
||||
private readonly double[] _corr;
|
||||
private readonly double[] _power;
|
||||
private readonly double[] _smooth;
|
||||
private readonly double[] _p_smooth;
|
||||
|
||||
// State for filters and output
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
@@ -122,6 +123,7 @@ public sealed class Eacp : AbstractBase
|
||||
_corr = new double[size];
|
||||
_power = new double[size];
|
||||
_smooth = new double[size];
|
||||
_p_smooth = new double[size];
|
||||
_filtHistory = new RingBuffer(size + maxPeriod);
|
||||
|
||||
Name = $"Eacp({minPeriod},{maxPeriod})";
|
||||
@@ -156,11 +158,13 @@ public sealed class Eacp : AbstractBase
|
||||
{
|
||||
_ps = _s;
|
||||
_filtHistory.Snapshot();
|
||||
Array.Copy(_smooth, _p_smooth, _smooth.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
_s = _ps;
|
||||
_filtHistory.Restore();
|
||||
Array.Copy(_p_smooth, _smooth, _smooth.Length);
|
||||
}
|
||||
|
||||
var s = _s;
|
||||
@@ -407,6 +411,7 @@ public sealed class Eacp : AbstractBase
|
||||
Array.Clear(_corr);
|
||||
Array.Clear(_power);
|
||||
Array.Clear(_smooth);
|
||||
Array.Clear(_p_smooth);
|
||||
Last = default;
|
||||
}
|
||||
|
||||
@@ -414,7 +419,7 @@ public sealed class Eacp : AbstractBase
|
||||
{
|
||||
foreach (double value in source)
|
||||
{
|
||||
Update(new TValue(DateTime.UtcNow, value));
|
||||
Update(new TValue(DateTime.MinValue, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +464,7 @@ public sealed class Eacp : AbstractBase
|
||||
var eacp = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var result = eacp.Update(new TValue(DateTime.UtcNow, source[i]));
|
||||
var result = eacp.Update(new TValue(DateTime.MinValue, source[i]));
|
||||
output[i] = result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Autocorrelation Periodogram (EACP)","EACP",overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Even Better Sinewave (EBSW)", "EBSW", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Homodyne Discriminator (HOMOD)","HOMOD",overlay=false)
|
||||
|
||||
@@ -92,4 +92,32 @@ public sealed class HtDcperiodValidationTests : IDisposable
|
||||
var q = new HtDcperiod();
|
||||
Assert.Equal(talibLookback, q.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcperiod_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtDcperiod();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorResult = ind.Last.Value;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorResult, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorResult, ind.Last.Value, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Hilbert Transform Dominant Cycle Period (HT_DCPERIOD)", "HT_DCPERIOD", overlay=false)
|
||||
|
||||
@@ -139,8 +139,8 @@ public class HtDcphaseTests
|
||||
// First update (new bar)
|
||||
var result1 = ht.Update(new TValue(now.AddMinutes(70), 105), isNew: true);
|
||||
|
||||
// Same bar update
|
||||
var result2 = ht.Update(new TValue(now.AddMinutes(70), 106), isNew: false);
|
||||
// Same bar update with the same price — must yield identical result
|
||||
var result2 = ht.Update(new TValue(now.AddMinutes(70), 105), isNew: false);
|
||||
|
||||
Assert.Equal(result1.Value, result2.Value);
|
||||
}
|
||||
|
||||
@@ -92,4 +92,32 @@ public sealed class HtDcphaseValidationTests : IDisposable
|
||||
var q = new HtDcphase();
|
||||
Assert.Equal(talibLookback, q.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtDcphase_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtDcphase();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorResult = ind.Last.Value;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorResult, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original result
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorResult, ind.Last.Value, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,12 +217,10 @@ public sealed class HtDcphase : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same-bar update: restore previous state and return cached result from Last
|
||||
_state = _p_state;
|
||||
Array.Copy(_p_circBuffer, _circBuffer, CIRC_BUFFER_SIZE);
|
||||
Array.Copy(_p_smoothPrice, _smoothPrice, SMOOTH_PRICE_SIZE);
|
||||
Array.Copy(_p_priceHistory, _priceHistory, PRICE_HISTORY_SIZE);
|
||||
return Last.Value;
|
||||
}
|
||||
|
||||
var s = _state;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Hilbert Transform Dominant Cycle Phase (HT_DCPHASE)", "HT_DCPHASE", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Hilbert Transform Phasor Components (HT_PHASOR)", "HT_PHASOR", overlay=false)
|
||||
|
||||
@@ -122,4 +122,34 @@ public sealed class HtSineValidationTests : IDisposable
|
||||
|
||||
Assert.Equal(talibLookback, htSine.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtSine_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtSine();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorSine = ind.Last.Value;
|
||||
double anchorLeadSine = ind.LeadSine;
|
||||
|
||||
// Correction with a dramatically different price — recompute must yield different results
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorSine, ind.Last.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original results
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorSine, ind.Last.Value, 1e-9);
|
||||
Assert.Equal(anchorLeadSine, ind.LeadSine, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,6 @@ public sealed class HtSine : AbstractBase
|
||||
Array.Copy(_circBuffer, _p_circBuffer, CIRC_BUFFER_SIZE);
|
||||
Array.Copy(_smoothPrice, _p_smoothPrice, SMOOTH_PRICE_SIZE);
|
||||
Array.Copy(_priceHistory, _p_priceHistory, PRICE_HISTORY_SIZE);
|
||||
_state.Today++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -305,6 +304,8 @@ public sealed class HtSine : AbstractBase
|
||||
Array.Copy(_p_priceHistory, _priceHistory, PRICE_HISTORY_SIZE);
|
||||
}
|
||||
|
||||
_state.Today++;
|
||||
|
||||
// Local copy of state for struct promotion (AGENTS.md §2.5)
|
||||
var s = _state;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Hilbert Transform SineWave (HT_SINE)", "HT_SINE", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Lunar Phase (LUNAR)", "LUNAR", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Solar Cycle (SOLAR)", "SOLAR", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers SSF Detrended Synthetic Price (SSFDSP)", "SSF-DSP", overlay=false)
|
||||
|
||||
Reference in New Issue
Block a user