refactor: move Decay/Edecay from trends_IIR to numerics; update filter signatures

- Move lib/trends_IIR/decay/ → lib/numerics/decay/
- Move lib/trends_IIR/edecay/ → lib/numerics/edecay/
- Update Category in Decay.md/Edecay.md from Trends (IIR) to Numerics
- Add DECAY/EDECAY entries to lib/numerics/_index.md and docs/indicators.md
- Update filter signature .md files and .svg assets
- Update trends_IIR signature docs (htit, mama, holt, etc.)
- All 163 tests passing, 0 warnings, 0 errors
This commit is contained in:
Miha Kralj
2026-03-10 20:33:55 -07:00
parent 35a6702b06
commit 7ec79538aa
64 changed files with 183229 additions and 157318 deletions
+2
View File
@@ -433,7 +433,9 @@ Mathematical transformations and derivative indicators. Building blocks for anal
| [**WEIBULLDIST**](../lib/numerics/weibulldist/Weibulldist.md) | Weibull Distribution (CDF) | Rolling Weibull CDF — two-parameter closed-form probability transform via pow + exp |
| [**CHANGE**](../lib/numerics/change/Change.md) | Percentage Change | Relative price movement |
| [**CWT**](../lib/numerics/cwt/Cwt.md) | Continuous Wavelet Transform | Morlet CWT magnitude at a specified scale — time-frequency decomposition |
| [**DECAY**](../lib/numerics/decay/Decay.md) | Linear Decay | Peak envelope with linear degradation |
| [**DWT**](../lib/numerics/dwt/Dwt.md) | Discrete Wavelet Transform | À trous Haar stationary DWT — multi-resolution approximation + detail, WarmupPeriod = 2^levels |
| [**EDECAY**](../lib/numerics/edecay/Edecay.md) | Exponential Decay | Peak envelope with exponential degradation |
| [**EXPTRANS**](../lib/numerics/exptrans/Exptrans.md) | Exponential Transform | e^x for log-space reversal |
| [**HIGHEST**](../lib/numerics/highest/Highest.md) | Rolling Maximum | O(1) via monotonic deque |
| [**MAXINDEX**](../lib/numerics/maxindex/Maxindex.md) | Rolling Maximum Index | Position of max in window |
-1
View File
@@ -8,7 +8,6 @@
| **Outputs** | Single series (BaxterKing) |
| **Output range** | Oscillates around zero |
| **Warmup** | `2K+1` bars (default 25) |
| **Signature** | [baxterking_signature](baxterking_signature.md) |
### TL;DR
@@ -1,3 +0,0 @@
# BAXTERKING Signature
![BAXTERKING Signature](baxterking_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 546 KiB

-1
View File
@@ -8,7 +8,6 @@
| **Outputs** | Single series (BPF) |
| **Output range** | Oscillates around zero |
| **Warmup** | `Math.Max(lowerPeriod, upperPeriod)` bars |
| **Signature** | [bpf_signature](bpf_signature.md) |
### TL;DR
-3
View File
@@ -1,3 +0,0 @@
# BPF Signature
![BPF Signature](bpf_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 553 KiB

-1
View File
@@ -8,7 +8,6 @@
| **Outputs** | Single series (Cfitz) |
| **Output range** | Oscillates around zero |
| **Warmup** | `2` bars |
| **Signature** | [cfitz_signature](cfitz_signature.md) |
### TL;DR
-3
View File
@@ -1,3 +0,0 @@
# CFITZ Signature
![CFITZ Signature](cfitz_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 544 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (Gauss) |
| **Output range** | Tracks input |
| **Warmup** | 1 bar |
| **Signature** | [gauss_signature](gauss_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# ELLIPTIC Signature
![GAUSS signature](gauss_signature.svg)
+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (HP) |
| **Output range** | Tracks input |
| **Warmup** | 1 bar |
| **Signature** | [hp_signature](hp_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# ELLIPTIC Signature
![HP Signature](hp_signature.svg)
-1
View File
@@ -8,7 +8,6 @@
| **Outputs** | Single series (HPF) |
| **Output range** | Tracks input |
| **Warmup** | 1 bar |
| **Signature** | [hpf_signature](hpf_signature.md) |
### TL;DR
-3
View File
@@ -1,3 +0,0 @@
# HPF Signature
![HPF Signature](hpf_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 545 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (Kalman) |
| **Output range** | Tracks input |
| **Warmup** | `10` bars |
| **Signature** | [kalman_signature](kalman_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# ELLIPTIC Signature
![KALMAN Signature](kalman_signature.svg)
+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (Laguerre) |
| **Output range** | Tracks input |
| **Warmup** | `WarmupBars` bars |
| **Signature** | [laguerre_signature](laguerre_signature.md) |
### TL;DR
@@ -0,0 +1,3 @@
# ELLIPTIC Signature
![LAGUERRE Signature](laguerre_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 542 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (OneEuro) |
| **Output range** | Tracks input |
| **Warmup** | `1` bars |
| **Signature** | [oneeuro_signature](oneeuro_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# LOESS Signature
![ONEEURO Signature](oneeuro_signature.svg)
+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (RLS) |
| **Output range** | Tracks input |
| **Warmup** | `order + 1` bars |
| **Signature** | [rls_signature](rls_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# LOESS Signature
![RLS Signature](rls_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 537 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 546 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 548 KiB

-1
View File
@@ -8,7 +8,6 @@
| **Outputs** | Single series (VOSS) |
| **Output range** | Oscillates around zero |
| **Warmup** | `period` bars |
| **Signature** | [voss_signature](voss_signature.md) |
### TL;DR
-3
View File
@@ -1,3 +0,0 @@
# VOSS Signature
![VOSS Signature](voss_signature.svg)
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 539 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (Wavelet) |
| **Output range** | Tracks input |
| **Warmup** | `2^levels` bars (default 16) |
| **Signature** | [wavelet_signature](wavelet_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# SGF Signature
![WAVELET Signature](wavelet_signature.svg)
+2
View File
@@ -10,8 +10,10 @@ Basic mathematical transforms and utility functions for time series. These build
| [BETADIST](betadist/Betadist.md) | Beta Distribution | Beta probability distribution transform. |
| [BINOMDIST](binomdist/Binomdist.md) | Binomial Distribution | Binomial probability distribution transform. |
| [CHANGE](change/Change.md) | Percentage Change | Relative price movement over lookback period. |
| [DECAY](decay/Decay.md) | Linear Decay | Peak envelope with linear degradation; max(input, prev 1/period). |
| [CWT](cwt/Cwt.md) | Continuous Wavelet Transform | Time-frequency decomposition with continuous wavelets. |
| [DWT](dwt/Dwt.md) | Discrete Wavelet Transform | À trous Haar stationary DWT; multi-resolution approximation + detail decomposition. |
| [EDECAY](edecay/Edecay.md) | Exponential Decay | Peak envelope with exponential degradation; max(input, prev × (period1)/period). |
| [EXPDIST](expdist/Expdist.md) | Exponential Distribution | Exponential probability distribution transform. |
| [EXPTRANS](exptrans/Exptrans.md) | Exponential Transform | e^x transform for log-space conversion reversal. |
| [FDIST](fdist/Fdist.md) | F-Distribution | Fisher-Snedecor probability distribution transform. |
@@ -2,7 +2,7 @@
| Property | Value |
| ---------------- | -------------------------------- |
| **Category** | Trends (IIR) |
| **Category** | Numerics |
| **Inputs** | Source (close) |
| **Parameters** | `period` (default 5) |
| **Outputs** | Single series (Decay) |
@@ -2,7 +2,7 @@
| Property | Value |
| ---------------- | -------------------------------- |
| **Category** | Trends (IIR) |
| **Category** | Numerics |
| **Inputs** | Source (close) |
| **Parameters** | `period` (default 5) |
| **Outputs** | Single series (Edecay) |
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 545 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 546 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 546 KiB

+28 -48
View File
@@ -224,11 +224,12 @@ public sealed class Nlma : AbstractBase
// ── Weight computation ─────────────────────────────────────────────
/// <summary>
/// Computes original Igorad two-phase kernel weights.
/// Computes original Igorad two-phase kernel weights (MQL4 NonLagMA v7.1 order).
/// Phase zone (i=0..period-2): t = i/(period-2), g = t≤0.5 ? 1 : 1/(3πt+1), w = g*cos(πt)
/// Cycle zone (i=period-1..flen-2): t = 1 + (i-phase+1)*(2*Cycle-1)/(Cycle*period-1), same g/w
/// Last tap (i=flen-1): weight = 0.
/// weights[0] = newest bar, weights[flen-1] = oldest bar.
/// weights[0] = newest bar (=1.0), weights[flen-1] = oldest bar (=0.0).
/// Matches MQL4 alfa[] order: alfa[0]*Close[0] (newest) .. alfa[Len-1]*Close[Len-1] (oldest).
/// Returns the signed weight sum for normalization.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -238,58 +239,36 @@ public sealed class Nlma : AbstractBase
int phase = period - 1;
double coeff = 3.0 * Math.PI;
// Compute weights in "oldest-first" order (taps[0]=oldest, taps[flen-1]=newest)
// then reverse into weights[] so weights[0]=newest, weights[flen-1]=oldest
const int StackallocThreshold = 256;
double[]? rented = flen > StackallocThreshold ? ArrayPool<double>.Shared.Rent(flen) : null;
Span<double> taps = flen <= StackallocThreshold
? stackalloc double[flen]
: rented!.AsSpan(0, flen);
// Compute weights directly in MQL4 alfa[] order:
// weights[0] = alfa[0] = weight for newest bar (=1.0 at t=0)
// weights[flen-1] = alfa[flen-1] = weight for oldest bar (=0.0)
double wsum = 0.0;
try
for (int i = 0; i < flen - 1; i++)
{
for (int i = 0; i < flen - 1; i++)
double t;
if (i <= phase - 1)
{
double t;
if (i <= phase - 1)
{
// Phase zone: t ramps from 0 to 1
t = phase > 1 ? (double)i / (phase - 1) : 0.0;
}
else
{
// Cycle zone: t continues from 1 upward
double numer = (double)(i - phase + 1) * (2 * Cycle - 1);
double denom = (double)(Cycle * period - 1);
t = 1.0 + (denom > 0 ? numer / denom : 0.0);
}
double beta = Math.Cos(Math.PI * t);
double g = t <= 0.5 ? 1.0 : 1.0 / Math.FusedMultiplyAdd(coeff, t, 1.0);
double w = g * beta;
taps[i] = w;
wsum += w;
// Phase zone: t ramps from 0 to 1
t = phase > 1 ? (double)i / (phase - 1) : 0.0;
}
else
{
// Cycle zone: t continues from 1 upward
double numer = (double)(i - phase + 1) * (2 * Cycle - 1);
double denom = (double)(Cycle * period - 1);
t = 1.0 + (denom > 0 ? numer / denom : 0.0);
}
// Last tap has weight 0 (original MQL4 loop goes to Len-2)
taps[flen - 1] = 0.0;
// Reverse into weights[]: weights[0]=newest=taps[flen-1], weights[flen-1]=oldest=taps[0]
for (int i = 0; i < flen; i++)
{
weights[i] = taps[flen - 1 - i];
}
}
finally
{
if (rented != null)
{
ArrayPool<double>.Shared.Return(rented);
}
double beta = Math.Cos(Math.PI * t);
double g = t <= 0.5 ? 1.0 : 1.0 / Math.FusedMultiplyAdd(coeff, t, 1.0);
weights[i] = g * beta;
wsum += weights[i];
}
// Last tap has weight 0 (original MQL4 loop goes to Len-2)
weights[flen - 1] = 0.0;
return wsum;
}
@@ -297,7 +276,8 @@ public sealed class Nlma : AbstractBase
/// <summary>
/// Convolves when buffer is full (count == flen). Uses precomputed weights.
/// weights[0] = newest, weights[flen-1] = oldest. Normalized by signed weight sum.
/// weights[0] = newest bar weight (=1.0), weights[flen-1] = oldest bar weight (=0.0).
/// Normalized by signed weight sum. Matches MQL4: alfa[0]*Close[0] + ... + alfa[Len-1]*Close[Len-1].
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double ConvolveFull()
@@ -307,7 +287,7 @@ public sealed class Nlma : AbstractBase
int capacity = _buffer.Capacity;
double sum = 0.0;
// Iterate oldest-to-newest: oldest bar gets weights[flen-1], newest gets weights[0]
// Iterate oldest-to-newest: oldest bar gets weights[flen-1] (≈0), newest gets weights[0] (=1.0)
int wi = _flen - 1;
for (int i = head; i < capacity; i++)
{
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 546 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 538 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 545 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 544 KiB

After

Width:  |  Height:  |  Size: 544 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (HTIT) |
| **Output range** | Tracks input |
| **Warmup** | `12` bars |
| **Signature** | [htit_signature](htit_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# HOLT Signature
![HTIT Signature](htit_signature.svg)
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 552 KiB

+2
View File
@@ -8,6 +8,8 @@
| **Outputs** | Single series (Mama) |
| **Output range** | Tracks input |
| **Warmup** | `50` bars |
| **Signature** | [mama_signature](mama_signature.md) |
### TL;DR
+3
View File
@@ -0,0 +1,3 @@
# LTMA Signature
![MAMA Signature](mama_signature.svg)
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 545 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB