Merge branch 'dev'

This commit is contained in:
Miha Kralj
2026-03-13 13:47:10 -07:00
404 changed files with 2754 additions and 1763 deletions
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [adxvma.pine](adxvma.pine) |
- ADXVMA is an adaptive IIR filter that uses the Average Directional Index (ADX) as its smoothing constant.
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `period * 2` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** ADX for trend strength | **Trading note:** ADX-based Variable MA; adapts smoothing using ADX measurement.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
ADXVMA is an adaptive IIR filter that uses the Average Directional Index (ADX) as its smoothing constant. When ADX is high (strong trend), the smoothing factor approaches 1.0 and the filter tracks price aggressively. When ADX is low (range-bound), the smoothing factor approaches 0.0 and the filter barely moves. This creates a moving average that automatically switches between responsive trend-following and noise-immune range-holding without external regime detection.
@@ -147,4 +145,4 @@ O(1) per bar. State is 4 RMA scalars + OHLC history + VMA output. WarmupPeriod =
| ADX computation | Partial | Vectorizable ratio except for recursive RMA |
| Adaptive VMA | No | Recursive IIR (alpha depends on computed ADX) |
All four RMA passes and the adaptive VMA are recursive IIR — inherently sequential. Batch mode can vectorize TR and DM computation (pure per-bar arithmetic) then run scalar RMA sweeps. Net batch speedup for large series: ~1.5× (TR/DM vectorization only).
All four RMA passes and the adaptive VMA are recursive IIR — inherently sequential. Batch mode can vectorize TR and DM computation (pure per-bar arithmetic) then run scalar RMA sweeps. Net batch speedup for large series: ~1.5× (TR/DM vectorization only).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [ahrens_signature](ahrens_signature.md) |
- AHRENS is a recursive IIR filter that adjusts toward the source price minus the midpoint of its current and lagged (by one period) states.
- Parameterized by `period` (default 9).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../ema/ema.md) | **Complementary:** Trend filters | **Trading note:** Ahrens MA; modified exponential averaging for reduced noise.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
AHRENS is a recursive IIR filter that adjusts toward the source price minus the midpoint of its current and lagged (by one period) states. The formula $\text{AHRENS}_t = \text{AHRENS}_{t-1} + (\text{source} - \frac{\text{AHRENS}_{t-1} + \text{AHRENS}_{t-N}}{2}) / N$ creates a self-dampening feedback loop: the correction term shrinks as the current and lagged states converge, producing a smoother approach to equilibrium than a standard EMA with less tendency to overshoot on reversals.
@@ -122,4 +120,4 @@ O(1) per bar. The ring buffer stores past output values, not input values — a
| Self-referential IIR update | No | AHRENS[t] depends on AHRENS[t-1] and AHRENS[t-N]; both are computed values |
| Correction divide | No | Alpha depends on computed error; scalar only |
AHRENS is strictly sequential — the output at bar t depends on the output at bar t-1 (direct feedback) AND the output at bar t-N (delayed feedback). No vectorization is possible. Batch mode runs the same scalar kernel as streaming.
AHRENS is strictly sequential — the output at bar t depends on the output at bar t-1 (direct feedback) AND the output at bar t-N (delayed feedback). No vectorization is possible. Batch mode runs the same scalar kernel as streaming.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [coral_signature](coral_signature.md) |
- The **Coral** filter is a smooth, low-lag trend indicator that chains six cascaded EMA passes and combines stages 36 using polynomial coefficients...
- Parameterized by `period`, `cd` (default 0.4).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [TEMA](../tema/tema.md) | **Complementary:** Trend direction filters | **Trading note:** Coral trend indicator; smooth, low-lag modified exponential filter.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## Overview
@@ -191,4 +189,4 @@ All 6 EMA stages are recursive IIR — inherently sequential. The polynomial com
- LazyBear, "Coral Trend Indicator" — [TradingView](https://www.tradingview.com/u/LazyBear/)
- Original MT4 implementation (author unknown)
- Related: Tillson, T. "Smoothing Techniques for More Accurate Signals" — TASC, 1998 (T3 cascade technique)
- Related: Tillson, T. "Smoothing Techniques for More Accurate Signals" — TASC, 1998 (T3 cascade technique)
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [decycler_signature](decycler_signature.md) |
- The Ehlers Decycler extracts the trend component from a price series by subtracting a 2-pole Butterworth high-pass filter from the source signal.
- Parameterized by `period` (default 60).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../ema/ema.md), [HTIT](../htit/htit.md) | **Complementary:** Cycle indicators | **Trading note:** Ehlers Decycler; high-pass complement removes cycle components to isolate trend.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Ehlers Decycler extracts the trend component from a price series by subtracting a 2-pole Butterworth high-pass filter from the source signal. Where most moving averages blur the boundary between trend and cycle, the Decycler defines it with a frequency-domain cutoff: cycles shorter than the specified period are removed, everything longer stays. The result is an overlay that hugs price with near-zero lag during trends and rejects short-term oscillations without the smoothing artifacts of convolution-based averages.
@@ -244,4 +242,4 @@ var (results, indicator) = Decycler.Calculate(series, period: 60);
- Ehlers, J. F. (2015). "Decyclers." *Technical Analysis of Stocks & Commodities*, September 2015.
- Ehlers, J. F. (2013). *Cycle Analytics for Traders*. Wiley. Chapter 4.
- Ehlers, J. F. (2004). *Cybernetic Analysis for Stocks and Futures*. Wiley.
- Ehlers, J. F. (2004). *Cybernetic Analysis for Stocks and Futures*. Wiley.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [dsma_signature](dsma_signature.md) |
- DSMA (Deviation-Scaled Moving Average) is a volatility-adaptive trend filter that combines a Super Smoother (2-pole Butterworth IIR filter) with RM...
- Parameterized by `period`, `scalefactor` (default 0.5).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [KAMA](../kama/kama.md) | **Complementary:** ADX for trend confirmation | **Trading note:** Deviation-Scaled MA; adapts smoothing based on price deviation.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
DSMA (Deviation-Scaled Moving Average) is a volatility-adaptive trend filter that combines a Super Smoother (2-pole Butterworth IIR filter) with RMS-based deviation scaling. Unlike fixed-period moving averages that treat all market conditions identically, DSMA adjusts its responsiveness based on measured volatility—accelerating when trends are strong and decelerating when prices consolidate.
@@ -172,4 +170,4 @@ DSMA is not implemented in mainstream libraries (TA-Lib, Skender, Tulip, Ooples)
6. **Bar Correction**: Like all QuanTAlib indicators, DSMA supports bar correction via the `isNew` parameter. When `isNew = false`, it rolls back to the previous state before recalculating. Ensure your data feed correctly signals bar updates versus corrections.
7. **SIMD Limitation**: The recursive nature of the Super Smoother filter and adaptive alpha calculation precludes efficient SIMD vectorization. The `Calculate(Span)` method uses a scalar loop. For bulk backtesting, consider parallelizing across multiple series rather than within a single series.
7. **SIMD Limitation**: The recursive nature of the Super Smoother filter and adaptive alpha calculation precludes efficient SIMD vectorization. The `Calculate(Span)` method uses a scalar loop. For bulk backtesting, consider parallelizing across multiple series rather than within a single series.
+52
View File
@@ -0,0 +1,52 @@
import math
import numpy as np
__all__ = ["ema"]
EPSILON = 1e-10 # bias-compensator cutoff
def ema(source, period: int = 10, *, alpha: float | None = None) -> np.ndarray:
"""Bias-compensated EMA.
Use *period* (default) or explicit *alpha* (keyword-only).
If *alpha* is given, *period* is ignored.
"""
if alpha is not None:
if not (0.0 < alpha <= 1.0):
raise ValueError(f"alpha must be in (0, 1], got {alpha}")
else:
if period <= 0:
raise ValueError(f"period must be > 0, got {period}")
alpha = 2.0 / (period + 1)
src = np.asarray(source, dtype=np.float64)
if src.ndim == 0 or src.size == 0:
raise ValueError("source must not be empty")
src = src.ravel()
out = np.empty(len(src), dtype=np.float64)
decay = 1.0 - alpha
ema_val = 0.0
e = 1.0
last_valid = 0.0
has_valid = False
for i, v in enumerate(src):
if math.isfinite(v):
last_valid = v
has_valid = True
elif has_valid:
v = last_valid
else:
out[i] = math.nan
continue
ema_val = ema_val * decay + alpha * v
if e > EPSILON:
e *= decay
out[i] = ema_val / (1.0 - e)
else:
out[i] = ema_val
return out
+143
View File
@@ -0,0 +1,143 @@
"""Unit tests for pure-Python EMA implementation.
Per PYTHON_FALLBACK_SPEC §5: co-located <indicator>_test.py
Run::
pytest lib/trends_IIR/ema/ema_test.py -v
"""
import math
import sys
from pathlib import Path
import numpy as np
import pytest
# Import from co-located ema.py
sys.path.insert(0, str(Path(__file__).parent))
from ema import ema # noqa: E402
# ── basic correctness ──
def test_known_values():
"""Hand-calculated EMA(3) with bias compensation.
alpha=0.5, decay=0.5:
Bar 0: acc = 0.5*10 = 5, E = 0.5, result = 5/0.5 = 10.0
Bar 1: acc = 5*0.5 + 0.5*20 = 12.5, E = 0.25, result = 12.5/0.75 16.667
Bar 2: acc = 12.5*0.5 + 0.5*30 = 21.25, E = 0.125, result = 21.25/0.875 24.286
"""
data = np.array([10.0, 20.0, 30.0, 40.0, 50.0])
result = ema(data, period=3)
assert result[0] == pytest.approx(10.0, rel=1e-12)
assert result[1] == pytest.approx(16.666666666666668, rel=1e-10)
assert result[2] == pytest.approx(24.285714285714285, rel=1e-10)
def test_period_1():
"""Period=1 → alpha=1.0 → output equals input (no smoothing)."""
data = np.array([10.0, 20.0, 30.0, 40.0, 50.0])
result = ema(data, period=1)
np.testing.assert_array_equal(result, data)
def test_constant_input():
"""Constant 100.0 through any EMA → always 100.0."""
data = np.full(100, 100.0)
result = ema(data, period=20)
np.testing.assert_allclose(result, 100.0, rtol=1e-12)
# ── edge cases ──
def test_empty_array():
"""Empty input → raises ValueError."""
with pytest.raises(ValueError, match="must not be empty"):
ema(np.array([]), period=5)
def test_invalid_period():
"""Period ≤ 0 → raises ValueError."""
with pytest.raises(ValueError, match="period must be > 0"):
ema(np.array([1.0, 2.0, 3.0]), period=0)
with pytest.raises(ValueError, match="period must be > 0"):
ema(np.array([1.0, 2.0, 3.0]), period=-1)
def test_single_element():
"""Single-element input returns that element."""
result = ema([42.0], period=10)
assert len(result) == 1
assert result[0] == pytest.approx(42.0, rel=1e-12)
# ── NaN handling ──
def test_nan_in_input():
"""NaN replaced with last valid value → output stays finite."""
data = np.array([10.0, 20.0, np.nan, 40.0, 50.0])
result = ema(data, period=3)
assert all(math.isfinite(v) for v in result)
def test_all_nan_returns_nan():
"""All-NaN input → all-NaN output."""
data = np.full(5, np.nan)
result = ema(data, period=3)
assert all(math.isnan(v) for v in result)
def test_inf_handled():
"""Inf replaced with last valid value."""
data = np.array([10.0, 20.0, np.inf, 40.0, 50.0])
result = ema(data, period=3)
assert all(math.isfinite(v) for v in result)
# ── output shape & warmup ──
def test_output_length_matches_input():
"""len(output) == len(input)."""
data = np.random.default_rng(42).normal(100, 5, size=200)
result = ema(data, period=14)
assert len(result) == 200
def test_first_bar_always_valid():
"""Bias compensation means EMA produces valid output from bar 0."""
data = np.random.default_rng(42).normal(100, 5, size=50)
result = ema(data, period=50)
assert math.isfinite(result[0])
# ── numerical precision ──
def test_large_values():
"""No overflow with 1e300 values."""
data = np.full(100, 1e300)
result = ema(data, period=10)
np.testing.assert_allclose(result, 1e300, rtol=1e-10)
def test_tiny_values():
"""No underflow with 1e-300 values."""
data = np.full(100, 1e-300)
result = ema(data, period=10)
np.testing.assert_allclose(result, 1e-300, rtol=1e-10)
def test_10k_series_all_finite():
"""Long series stays finite (no drift)."""
data = np.random.default_rng(42).normal(100, 10, size=10_000)
result = ema(data, period=20)
assert np.all(np.isfinite(result))
def test_alpha_from_period_equivalence():
"""ema(period=N) == ema(alpha=2/(N+1))."""
data = np.random.default_rng(42).normal(100, 5, size=200)
r1 = ema(data, period=14)
r2 = ema(data, alpha=2.0 / 15.0)
np.testing.assert_allclose(r1, r2, rtol=1e-14)
+337
View File
@@ -0,0 +1,337 @@
"""Tests for the pure-Python EMA implementation.
Validates that ``ema.py`` produces results matching the C# ``Ema.Batch``
algorithm, including bias compensation, NaN handling, and edge cases.
Run with::
python -m pytest lib/trends_IIR/ema/tests/test_ema.py -v
"""
import math
import sys
from pathlib import Path
import numpy as np
import pytest
# ── ensure the ema module is importable ──────────────────────────────────
# The ema.py lives one directory up from tests/
_EMA_DIR = Path(__file__).resolve().parent.parent
if str(_EMA_DIR) not in sys.path:
sys.path.insert(0, str(_EMA_DIR))
from ema import ema # noqa: E402
# ═════════════════════════════════════════════════════════════════════════
# Fixtures
# ═════════════════════════════════════════════════════════════════════════
@pytest.fixture
def constant_series() -> np.ndarray:
"""100 bars of constant value 50.0."""
return np.full(100, 50.0)
@pytest.fixture
def ramp_series() -> np.ndarray:
"""50 bars ramping 1..50."""
return np.arange(1.0, 51.0)
@pytest.fixture
def short_series() -> np.ndarray:
"""10 bars: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]."""
return np.arange(10.0, 110.0, 10.0)
# ═════════════════════════════════════════════════════════════════════════
# Input validation
# ═════════════════════════════════════════════════════════════════════════
class TestInputValidation:
"""Guard clauses match C# ArgumentOutOfRangeException behavior."""
def test_period_zero_raises(self) -> None:
with pytest.raises(ValueError, match="period must be > 0"):
ema([1.0, 2.0], period=0)
def test_period_negative_raises(self) -> None:
with pytest.raises(ValueError, match="period must be > 0"):
ema([1.0, 2.0], period=-5)
def test_alpha_zero_raises(self) -> None:
with pytest.raises(ValueError, match="alpha must be in"):
ema([1.0, 2.0], alpha=0.0)
def test_alpha_negative_raises(self) -> None:
with pytest.raises(ValueError, match="alpha must be in"):
ema([1.0, 2.0], alpha=-0.1)
def test_alpha_above_one_raises(self) -> None:
with pytest.raises(ValueError, match="alpha must be in"):
ema([1.0, 2.0], alpha=1.01)
def test_alpha_exactly_one_ok(self) -> None:
result = ema([5.0, 10.0, 15.0], alpha=1.0)
# alpha=1 means output == input (no smoothing)
np.testing.assert_array_equal(result, [5.0, 10.0, 15.0])
def test_empty_source_raises(self) -> None:
with pytest.raises(ValueError, match="must not be empty"):
ema([], period=10)
def test_scalar_source_raises(self) -> None:
with pytest.raises(ValueError, match="must not be empty"):
ema(np.float64(5.0), period=10)
# ═════════════════════════════════════════════════════════════════════════
# Output shape
# ═════════════════════════════════════════════════════════════════════════
class TestOutputShape:
"""Output array must match input length exactly."""
def test_length_equals_input(self, ramp_series: np.ndarray) -> None:
result = ema(ramp_series, period=10)
assert len(result) == len(ramp_series)
def test_single_element(self) -> None:
result = ema([42.0], period=5)
assert len(result) == 1
def test_dtype_float64(self, ramp_series: np.ndarray) -> None:
result = ema(ramp_series, period=10)
assert result.dtype == np.float64
def test_accepts_list_input(self) -> None:
result = ema([1.0, 2.0, 3.0], period=2)
assert len(result) == 3
def test_accepts_tuple_input(self) -> None:
result = ema((1.0, 2.0, 3.0), period=2)
assert len(result) == 3
# ═════════════════════════════════════════════════════════════════════════
# Bias compensation correctness
# ═════════════════════════════════════════════════════════════════════════
class TestBiasCompensation:
"""Verify the warmup compensator E = (1-α)^t produces valid early values."""
def test_first_bar_equals_input(self) -> None:
"""EMA(bar_0) must equal the input itself (compensated to 1×input)."""
result = ema([100.0, 200.0, 300.0], period=10)
# After compensation: ema_acc = alpha * 100, E = decay
# result[0] = (alpha * 100) / (1 - decay) = (alpha * 100) / alpha = 100
assert result[0] == pytest.approx(100.0, rel=1e-12)
def test_constant_series_converges_to_constant(
self, constant_series: np.ndarray
) -> None:
"""On constant input, every bar should equal the constant."""
result = ema(constant_series, period=10)
np.testing.assert_allclose(result, 50.0, rtol=1e-12)
def test_compensator_eliminates_zero_bias(self) -> None:
"""Without compensation, starting from ema=0 would bias downward.
With compensation, bar 1 on a constant 100 series must be 100."""
result = ema(np.full(5, 100.0), period=20)
# Every value should be exactly 100.0 (constant input)
np.testing.assert_allclose(result, 100.0, rtol=1e-12)
def test_period_1_passthrough(self) -> None:
"""Period=1 → alpha=1.0 → output equals input."""
data = np.array([10.0, 20.0, 30.0, 40.0, 50.0])
result = ema(data, period=1)
np.testing.assert_array_equal(result, data)
# ═════════════════════════════════════════════════════════════════════════
# EMA mathematical properties
# ═════════════════════════════════════════════════════════════════════════
class TestMathProperties:
"""Verify EMA satisfies known mathematical properties."""
def test_monotone_input_monotone_output(self, ramp_series: np.ndarray) -> None:
"""Strictly increasing input → strictly increasing EMA."""
result = ema(ramp_series, period=10)
diffs = np.diff(result)
assert np.all(diffs > 0), "EMA of monotone-increasing input must increase"
def test_ema_lags_behind_ramp(self, ramp_series: np.ndarray) -> None:
"""EMA of increasing ramp must be ≤ the source (lag property)."""
result = ema(ramp_series, period=10)
# After first bar, EMA should lag behind
assert np.all(result[1:] <= ramp_series[1:] + 1e-10)
def test_ema_between_min_and_max(self, ramp_series: np.ndarray) -> None:
"""EMA output must lie within [min(source), max(source)]."""
result = ema(ramp_series, period=10)
assert np.all(result >= ramp_series.min() - 1e-10)
assert np.all(result <= ramp_series.max() + 1e-10)
def test_alpha_from_period(self) -> None:
"""ema(period=N) must equal ema(alpha=2/(N+1))."""
data = np.random.default_rng(42).normal(100, 5, size=200)
r1 = ema(data, period=14)
r2 = ema(data, alpha=2.0 / 15.0)
np.testing.assert_allclose(r1, r2, rtol=1e-14)
def test_larger_period_smoother(self) -> None:
"""Larger period → less variance in the output."""
data = np.random.default_rng(99).normal(100, 10, size=500)
r5 = ema(data, period=5)
r50 = ema(data, period=50)
# Skip warmup region; use last 300 bars
assert np.std(r50[-300:]) < np.std(r5[-300:])
# ═════════════════════════════════════════════════════════════════════════
# NaN / Inf handling
# ═════════════════════════════════════════════════════════════════════════
class TestNanHandling:
"""NaN/Inf inputs are replaced with last valid value (C# GetValidValue)."""
def test_nan_in_middle(self) -> None:
data = np.array([10.0, 20.0, np.nan, 40.0, 50.0])
result = ema(data, period=3)
assert all(math.isfinite(v) for v in result)
def test_inf_in_middle(self) -> None:
data = np.array([10.0, 20.0, np.inf, 40.0, 50.0])
result = ema(data, period=3)
assert all(math.isfinite(v) for v in result)
def test_neg_inf_in_middle(self) -> None:
data = np.array([10.0, 20.0, -np.inf, 40.0, 50.0])
result = ema(data, period=3)
assert all(math.isfinite(v) for v in result)
def test_nan_at_start_skipped(self) -> None:
"""Leading NaNs use last_valid = 0 until a finite value arrives."""
data = np.array([np.nan, np.nan, 100.0, 200.0, 300.0])
result = ema(data, period=3)
# First two bars use last_valid=0 initially, then seed
# After 100.0 arrives, behavior normalizes
assert math.isfinite(result[2])
assert math.isfinite(result[4])
def test_all_nan_returns_nan(self) -> None:
"""If every value is NaN, output must be all NaN."""
data = np.full(5, np.nan)
result = ema(data, period=3)
assert all(math.isnan(v) for v in result)
def test_all_inf_returns_nan(self) -> None:
"""If every value is Inf, no valid seed → all NaN."""
data = np.full(5, np.inf)
result = ema(data, period=3)
assert all(math.isnan(v) for v in result)
# ═════════════════════════════════════════════════════════════════════════
# Golden values (hand-calculated reference)
# ═════════════════════════════════════════════════════════════════════════
class TestGoldenValues:
"""Verify against hand-calculated EMA with bias compensation.
For period=3, alpha=0.5, decay=0.5:
Bar 0: ema_acc = 0.5*10 = 5, E = 0.5, result = 5/(1-0.5) = 10.0
Bar 1: ema_acc = 5*0.5 + 0.5*20 = 12.5, E = 0.25, result = 12.5/0.75 16.6667
Bar 2: ema_acc = 12.5*0.5 + 0.5*30 = 21.25, E = 0.125, result = 21.25/0.875 24.2857
"""
def test_period_3_first_three_bars(self) -> None:
data = np.array([10.0, 20.0, 30.0, 40.0, 50.0])
result = ema(data, period=3)
assert result[0] == pytest.approx(10.0, rel=1e-12)
assert result[1] == pytest.approx(16.666666666666668, rel=1e-10)
assert result[2] == pytest.approx(24.285714285714285, rel=1e-10)
def test_period_5_constant_100(self) -> None:
"""Constant 100 through EMA(5) → all 100.0."""
data = np.full(20, 100.0)
result = ema(data, period=5)
np.testing.assert_allclose(result, 100.0, rtol=1e-12)
def test_ema_alpha_direct(self) -> None:
"""ema(alpha=0.5) on [10,20,30] → same as period=3."""
data = np.array([10.0, 20.0, 30.0])
r1 = ema(data, period=3)
r2 = ema(data, alpha=0.5)
np.testing.assert_allclose(r1, r2, rtol=1e-14)
# ═════════════════════════════════════════════════════════════════════════
# Stability and performance
# ═════════════════════════════════════════════════════════════════════════
class TestStability:
"""Long series shouldn't drift or produce non-finite values."""
def test_10k_bars_all_finite(self) -> None:
rng = np.random.default_rng(42)
data = rng.normal(100, 10, size=10_000)
result = ema(data, period=20)
assert np.all(np.isfinite(result))
def test_large_values_no_overflow(self) -> None:
data = np.full(100, 1e300)
result = ema(data, period=10)
np.testing.assert_allclose(result, 1e300, rtol=1e-10)
def test_tiny_values_no_underflow(self) -> None:
data = np.full(100, 1e-300)
result = ema(data, period=10)
np.testing.assert_allclose(result, 1e-300, rtol=1e-10)
def test_alternating_sign(self) -> None:
"""Alternating +100 / -100 should converge toward 0 for large period."""
data = np.array([100.0, -100.0] * 500)
result = ema(data, period=100)
# Last few values should be near zero
assert abs(result[-1]) < 20.0
# ═════════════════════════════════════════════════════════════════════════
# Cross-validation with C# native (when available)
# ═════════════════════════════════════════════════════════════════════════
class TestCrossValidation:
"""Compare pure Python EMA against NativeAOT EMA (skipped if unavailable)."""
@pytest.fixture
def native_ema(self):
"""Try to import the native EMA wrapper."""
try:
from quantalib.trends_iir import ema as native_ema_fn
return native_ema_fn
except (ImportError, OSError):
pytest.skip("quantalib native lib not available")
def test_matches_native_random_data(self, native_ema) -> None:
rng = np.random.default_rng(12345)
data = rng.normal(100, 10, size=500)
py_result = ema(data, period=14)
native_result = native_ema(data, period=14)
# Convert native result to numpy if needed
native_arr = np.asarray(native_result, dtype=np.float64)
np.testing.assert_allclose(py_result, native_arr, rtol=1e-10,
err_msg="Python EMA diverges from native")
def test_matches_native_with_nans(self, native_ema) -> None:
data = np.array([10.0, np.nan, 30.0, 40.0, np.nan, 60.0, 70.0])
py_result = ema(data, period=3)
native_result = native_ema(data, period=3)
native_arr = np.asarray(native_result, dtype=np.float64)
np.testing.assert_allclose(py_result, native_arr, rtol=1e-10,
err_msg="NaN handling diverges from native")
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [frama_signature](frama_signature.md) |
- FRAMA is John Ehlers' fractal adaptive moving average.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `pe` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** ADX for trend context | **Trading note:** Fractal Adaptive MA; uses fractal dimension to adjust smoothing.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
FRAMA is John Ehlers' fractal adaptive moving average. It estimates a fractal dimension from high and low ranges, then converts that dimension into a dynamic EMA alpha. The result is a moving average that tightens in trends and relaxes in noise.
@@ -127,4 +125,4 @@ FRAMA is not implemented in the common TA libraries used by QuanTAlib. Validatio
1. **Period parity**: The algorithm requires even `N`. Odd values are rounded up.
2. **Warmup**: Outputs are `NaN` until `N` bars are available.
3. **Range source**: FRAMA uses High and Low ranges. Feeding Close-only data collapses the ranges.
4. **Bar correction**: Use `isNew=false` for corrections so the last bar is recomputed safely.
4. **Bar correction**: Use `isNew=false` for corrections so the last bar is recomputed safely.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [gdema_signature](gdema_signature.md) |
- GDEMA extends the standard DEMA (Double Exponential Moving Average) with a tunable gain factor $v$ that controls the aggressiveness of lag compensa...
- Parameterized by `period` (default 10), `vfactor` (default 1.0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [T3](../t3/t3.md) | **Complementary:** Signal crossovers | **Trading note:** Generalized DEMA; tunable volume factor between EMA and DEMA behavior.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
GDEMA extends the standard DEMA (Double Exponential Moving Average) with a tunable gain factor $v$ that controls the aggressiveness of lag compensation. The formula $\text{GDEMA} = (1+v) \cdot \text{EMA}_1 - v \cdot \text{EMA}_2$ reduces to plain EMA when $v=0$, standard DEMA when $v=1$, and progressively more aggressive lag removal for $v>1$. This parametric flexibility allows traders to dial in the exact smoothness-responsiveness trade-off for their application, rather than being locked into DEMA's fixed 2:1 ratio.
@@ -131,4 +129,4 @@ O(1) per bar. Two FMAs for EMA stages, one FMA for the combination. Fastest of t
| EMA₂ pass (depends on EMA₁ output) | No | Sequential dependency on EMA₁ series |
| Output combination (1+v)×E1 v×E2 | Yes | `VFNMADD231PD` across bar series once EMA passes complete |
Both EMA passes are recursive IIR. The final linear combination is vectorizable after the two EMA sweeps. Net batch speedup: minimal (~1.1×) since combination is only 3 of 18 cycles.
Both EMA passes are recursive IIR. The final linear combination is vectorizable after the two EMA sweeps. Net batch speedup: minimal (~1.1×) since combination is only 3 of 18 cycles.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hema_signature](hema_signature.md) |
- HEMA is a Hull-style moving average built entirely from **exponential smoothers**.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `EstimateWarmupPeriod()` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../ema/ema.md), [DEMA](../dema/dema.md) | **Complementary:** Trend following | **Trading note:** Hull-style EMA; applies Hulls lag-reduction technique to EMA.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## An EMA-domain analog of HMA with WMA-lag-matched alphas
@@ -246,4 +244,4 @@ HEMA is not commonly available in mainstream TA libraries. Validation uses a **r
## References
- Hull, A. "Hull Moving Average." Technical analysis methodology using WMA lag cancellation.
- Wolfram Alpha verification: EMA lag with alpha=3/(N+2) equals (N-1)/3, matching WMA(N) lag exactly.
- Wolfram Alpha verification: EMA lag with alpha=3/(N+2) equals (N-1)/3, matching WMA(N) lag exactly.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [holt_signature](holt_signature.md) |
- Holt's exponential smoothing extends simple exponential smoothing (EMA) by adding a second equation that explicitly tracks the local trend.
- Parameterized by `period`, `gamma` (default 0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [KAMA](../kama/kama.md) | **Complementary:** Forecast accuracy metrics | **Trading note:** Holt exponential smoothing; level + trend components for forecasting.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## Overview
@@ -131,4 +129,4 @@ Both state variables are recursive. Batch mode provides no SIMD opportunity beyo
- [EMA](../ema/Ema.md) — Single exponential smoothing (level only)
- [DEMA](../dema/Dema.md) — Double EMA with algebraic lag correction (different approach)
- [TEMA](../tema/Tema.md) — Triple EMA cascade
- [TEMA](../tema/Tema.md) — Triple EMA cascade
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [htit_signature](htit_signature.md) |
- HTIT (Hilbert Transform Instantaneous Trend) is a trend-following indicator that doesn't rely on simple averaging.
- No configurable parameters; computation is stateless per bar.
- Output range: Tracks input.
- Requires `12` bars of warmup before first valid output (IsHot = true).
- **Similar:** [MAMA](../mama/mama.md), [DEMA](../dema/dema.md) | **Complementary:** HT_DCPeriod | **Trading note:** Hilbert Transform trendline; cycle-adaptive smoothing.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HTIT (Hilbert Transform Instantaneous Trend) is a trend-following indicator that doesn't rely on simple averaging. Instead, it uses the Hilbert Transform to measure the dominant cycle period of the market and then computes a trendline that filters out that specific cycle. It adapts to the market's rhythm rather than imposing a fixed period.
@@ -177,4 +175,4 @@ The differences with Skender and Ooples arise from:
1. **Initialization**: How the first few bars are handled.
2. **Precision**: Hardcoded decimals vs exact fractions.
3. **Period Constraints**: How strictly the [6, 50] bounds are enforced during intermediate steps.
3. **Period Constraints**: How strictly the [6, 50] bounds are enforced during intermediate steps.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hwma_signature](hwma_signature.md) |
- HWMA is an Infinite Impulse Response (IIR) filter that applies triple exponential smoothing with level (F), velocity (V), and acceleration (A) comp...
- Parameterized by `period` (default 10).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [HOLT](../holt/holt.md) | **Complementary:** Seasonal analysis | **Trading note:** Holt-Winters MA; triple exponential smoothing with seasonal component.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HWMA is an Infinite Impulse Response (IIR) filter that applies triple exponential smoothing with level (F), velocity (V), and acceleration (A) components. Unlike simple exponential smoothing which only tracks the current level, HWMA anticipates future values by extrapolating trend and trend changes.
@@ -189,4 +187,4 @@ QuanTAlib validates HWMA against its PineScript reference implementation.
5. **Seasonal Confusion**: "Holt-Winters" often implies seasonal decomposition. This implementation is the non-seasonal variant focusing on level-trend-acceleration only.
6. **Parameter Sensitivity**: Small changes in β and γ significantly affect behavior. Start with the default period-based derivation before experimenting with custom values.
6. **Parameter Sensitivity**: Small changes in β and γ significantly affect behavior. Start with the default period-based derivation before experimenting with custom values.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [jma_signature](jma_signature.md) |
- JMA (Jurik Moving Average) is Mark Jurik's flagship adaptive smoother, recovered through decompilation of his proprietary AmiBroker/MetaTrader bina...
- Parameterized by `period`, `phase` (default 0), `power` (default 0.45).
- Output range: $-100$ to $+100$.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [FRAMA](../frama/frama.md) | **Complementary:** JMA-based bands | **Trading note:** Jurik MA; gold standard for smoothness with minimal lag.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
JMA (Jurik Moving Average) is Mark Jurik's flagship adaptive smoother, recovered through decompilation of his proprietary AmiBroker/MetaTrader binaries. Unlike forum-sourced approximations that use exponential volatility smoothing, this implementation maintains a 128-bar volatility distribution and applies percentile trimming to derive a robust reference. The result: identical behavior to Jurik's commercial software within floating-point tolerance, including spike rejection during 3-sigma events where approximations diverge by 3-4%.
@@ -270,4 +268,4 @@ JMA is proprietary. No open-source library implements it. Validation is performe
## References
- Jurik Research. (1998-2005). "JMA White Papers." *jurikres.com* (archived).
- Kositsin, Nikolay. (2007). "Digital Indicators for MetaTrader 4." *Alpari Forum Archives*.
- Kositsin, Nikolay. (2007). "Digital Indicators for MetaTrader 4." *Alpari Forum Archives*.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [kama_signature](kama_signature.md) |
- KAMA (Kaufman's Adaptive Moving Average) is an intelligent moving average that adjusts its smoothing speed based on market noise.
- Parameterized by `period` (default 10), `fastperiod` (default 2), `slowperiod` (default 30).
- Output range: Tracks input.
- Requires `period + 1` bars of warmup before first valid output (IsHot = true).
- **Similar:** [FRAMA](../frama/frama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** ADX to confirm trend | **Trading note:** Kaufmans Adaptive MA; efficiency ratio adjusts speed.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
KAMA (Kaufman's Adaptive Moving Average) is an intelligent moving average that adjusts its smoothing speed based on market noise. When the price is moving steadily (high signal-to-noise ratio), KAMA speeds up to capture the trend. When the price is chopping sideways (low signal-to-noise ratio), KAMA slows down to filter out the noise.
@@ -110,4 +108,4 @@ Validated against TA-Lib, Skender, Tulip, and Ooples.
| **TA-Lib** | ✅ | Matches `Kama` |
| **Skender** | ✅ | Matches `GetKama` |
| **Tulip** | ✅ | Matches `kama` |
| **Ooples** | ✅ | Matches `CalculateKaufmanAdaptiveMovingAverage` |
| **Ooples** | ✅ | Matches `CalculateKaufmanAdaptiveMovingAverage` |
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [lema_signature](lema_signature.md) |
- LEMA (Leader EMA) adds a smoothed error correction to the standard EMA, creating a moving average that anticipates price movement.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../ema/ema.md) | **Complementary:** Trend following | **Trading note:** Leading EMA; forward-shifted to reduce lag.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
LEMA (Leader EMA) adds a smoothed error correction to the standard EMA, creating a moving average that anticipates price movement. The formula $\text{LEMA} = \text{EMA}(x, N) + \text{EMA}(x - \text{EMA}(x, N), N)$ decomposes price into a smooth component (EMA) and an error component (residual), then re-smooths the error and adds it back. The re-smoothed error represents the systematic part of the EMA's tracking deficit, and adding it back shifts the output toward where the next price is likely to be.
@@ -146,4 +144,4 @@ O(1) per bar. The error-tracking EMA (stage 2) reacts faster than it would as a
| EMA₂ pass (on error series) | No | Recursive IIR on error series |
| Final addition | Yes | `VADDPD` once both EMA series computed |
EMA₁ must complete before the error series can be computed, and EMA₂ must complete before the final addition. Single-pass vectorization is impossible. Batch speedup: error subtraction and final addition are vectorizable but represent <10% of total cost.
EMA₁ must complete before the error series can be computed, and EMA₂ must complete before the final addition. Single-pass vectorization is impossible. Batch speedup: error subtraction and final addition are vectorizable but represent <10% of total cost.
+2 -4
View File
@@ -15,9 +15,7 @@
- MAMA (MESA Adaptive Moving Average) is a unique adaptive moving average that uses the Hilbert Transform to determine the phase rate of change of th...
- Parameterized by `fastlimit` (default 0.5), `slowlimit` (default 0.05).
- Output range: Tracks input.
- Requires `50` bars of warmup before first valid output (IsHot = true).
- **Similar:** [FRAMA](../frama/frama.md), [KAMA](../kama/kama.md) | **Complementary:** FAMA crossover | **Trading note:** MESA Adaptive MA by Ehlers; Hilbert Transform cycle-adaptive smoothing.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
MAMA (MESA Adaptive Moving Average) is a unique adaptive moving average that uses the Hilbert Transform to determine the phase rate of change of the market cycle. It produces two outputs: MAMA (the adaptive average) and FAMA (Following Adaptive Moving Average), which acts as a slower, confirming signal.
@@ -359,4 +357,4 @@ MAMA works best when combined with indicators that cover its blind spots:
5. **Precision Expectations**: Don't expect your MAMA to match TradingView or TA-Lib to the sixth decimal. It won't. Those implementations have accumulated rounding errors from 20 years of cargo-cult porting. Your values will be more accurate but numerically different. If this breaks your backtests, the backtests were fragile.
6. **Ignoring the Alpha Output**: Many traders only look at MAMA and FAMA values. The adaptive alpha itself is valuable information—it tells you how confident MAMA is in its cycle estimate. High alpha (near FastLimit) means rapid phase change and uncertainty. Low alpha (near SlowLimit) means stable, established trend.
6. **Ignoring the Alpha Output**: Many traders only look at MAMA and FAMA values. The adaptive alpha itself is valuable information—it tells you how confident MAMA is in its cycle estimate. High alpha (near FastLimit) means rapid phase change and uncertainty. Low alpha (near SlowLimit) means stable, established trend.
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [mavp.pine](mavp.pine) |
- MAVP applies an EMA-style exponential smoothing where the period -- and therefore the smoothing constant alpha -- changes on every bar.
- Parameterized by `minperiod` (default 2), `maxperiod` (default 30).
- Output range: Tracks input.
- Requires `maxPeriod` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** Adaptive period selection | **Trading note:** MA with Variable Period; dynamically adjusts lookback period.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## Introduction
@@ -170,4 +168,4 @@ TA-Lib's native `MAVP` function uses SMA by default (MAType=0), not EMA. Direct
- Kaufman, P. J. (2013). *Trading Systems and Methods*, 5th Ed. Wiley. Discusses adaptive moving averages.
- TA-Lib documentation: [MAVP - Moving Average with Variable Period](https://ta-lib.org/function.html)
- Ehlers, J. F. (2001). *Rocket Science for Traders*. Wiley. Adaptive smoothing with variable alpha.
- Ehlers, J. F. (2001). *Rocket Science for Traders*. Wiley. Adaptive smoothing with variable alpha.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [mcnma_signature](mcnma_signature.md) |
- MCNMA computes $2 \times \text{TEMA}(x, N) - \text{TEMA}(\text{TEMA}(x, N), N)$, applying the DEMA lag-cancellation technique to TEMA itself.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SMA](../sma/sma.md) | **Complementary:** Trend detection | **Trading note:** McNicholl MA; modified SMA calculation for improved response.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
MCNMA computes $2 \times \text{TEMA}(x, N) - \text{TEMA}(\text{TEMA}(x, N), N)$, applying the DEMA lag-cancellation technique to TEMA itself. This requires six cascaded EMA stages: three for the inner TEMA and three for the outer TEMA of the inner TEMA's output. The result is an extremely responsive moving average that tracks fast trends with minimal lag, at the cost of significant overshoot on reversals. Published by Dennis McNicholl in "Better Bollinger Bands" (*Futures Magazine*, October 1998) as a component for improved volatility band construction.
@@ -150,4 +148,4 @@ O(1) per bar. Six EMA stages plus two TEMA constructions and the final differenc
| TEMA combinations (×2) | Yes | `VFNMADD` after EMA stages; constant coefficients |
| Final 2×TEMA₁ TEMA₂ | Yes | `VFNMADD231PD` across bar series |
All EMA stages must complete sequentially. TEMA combinations and the final subtraction are vectorizable but represent ~28 of 52 cycles — approximately 54% of compute. Batch speedup: ~1.3× (vectorizing only the combination phases).
All EMA stages must complete sequentially. TEMA combinations and the final subtraction are vectorizable but represent ~28 of 52 cycles — approximately 54% of compute. Batch speedup: ~1.3× (vectorizing only the combination phases).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [mgdi_signature](mgdi_signature.md) |
- MGDI (McGinley Dynamic Indicator) looks like a moving average but operates on a fundamentally different principle.
- Parameterized by `period` (default 14), `k` (default 0.6).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [JMA](../jma/jma.md), [KAMA](../kama/kama.md) | **Complementary:** Momentum oscillators | **Trading note:** McGinley Dynamic; self-adjusts to market speed.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
MGDI (McGinley Dynamic Indicator) looks like a moving average but operates on a fundamentally different principle. Rather than using a fixed smoothing factor, it dynamically adjusts based on the ratio between price and the indicator's current value. The result is a filter that accelerates to catch breakouts while decelerating to avoid overshooting reversals—a behavior that fixed-alpha filters cannot achieve.
@@ -165,4 +163,4 @@ MGDI is inherently recursive (each value depends on the previous), limiting SIMD
## References
- McGinley, J.R. (1991). "The McGinley Dynamic." *Market Technicians Association Journal*, Fall 1991.
- McGinley, J.R. (1991). "The McGinley Dynamic." *Market Technicians Association Journal*, Fall 1991.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [mma_signature](mma_signature.md) |
- MMA (Modified Moving Average) uses a **simple mean** as a baseline, then adds a **weighted correction** based on the position of values within the ...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SMMA](../smma/smma.md), [EMA](../ema/ema.md) | **Complementary:** RSI/ATR (use MMA internally) | **Trading note:** Modified MA (identical to SMMA/RMA); Wilders smoothing.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
MMA (Modified Moving Average) uses a **simple mean** as a baseline, then adds a **weighted correction** based on the position of values within the buffer. The weighting tilts toward newer bars without fully discarding older ones, creating a filter that sits between SMA (equal weights) and WMA (linear weights) in both lag and smoothness characteristics.
@@ -175,4 +173,4 @@ The weighted sum computation is vectorizable:
## References
- PineScript reference implementation: `lib/trends_IIR/mma/mma.pine`
- PineScript reference implementation: `lib/trends_IIR/mma/mma.pine`
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [nma_signature](nma_signature.md) |
- NMA is an adaptive IIR filter whose smoothing ratio is derived from a volatility-weighted square-root kernel analysis of log-price movements over a...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** Noise filters | **Trading note:** Noise-elimination MA; adapts to signal-to-noise ratio.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
NMA is an adaptive IIR filter whose smoothing ratio is derived from a volatility-weighted square-root kernel analysis of log-price movements over a lookback window. When volatility concentrates in recent bars, the ratio approaches 1.0 (fast tracking). When volatility is spread uniformly, the ratio approaches $1/\sqrt{N}$ (heavy smoothing). The square-root kernel $(\sqrt{i+1} - \sqrt{i})$ gives a concave-down weighting that gently emphasizes recency, while the log-price transformation normalizes for price level, making the adaptation scale-invariant.
@@ -206,4 +204,4 @@ NMA is a proprietary indicator from Sloman's *Ocean Theory*. No reference implem
- Sloman, J. *Ocean Theory*. Pages 63-70. (Original NMA description.)
- Kaufman, P.J. (2013). *Trading Systems and Methods*, 5th ed. Wiley. Chapter 7: Adaptive Moving Averages.
- Chande, T.S. & Kroll, S. (1994). *The New Technical Trader*. Wiley. (Adaptive filter framework.)
- Chande, T.S. & Kroll, S. (1994). *The New Technical Trader*. Wiley. (Adaptive filter framework.)
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [qema_signature](qema_signature.md) |
- QEMA (Quad Exponential Moving Average) is a zero-lag smoothing filter that cascades four EMAs with geometrically ramped alphas and combines them us...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [TEMA](../tema/tema.md) | **Complementary:** Signal line crossovers | **Trading note:** Quadruple EMA; 4th-order lag reduction with overshoot risk.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
QEMA (Quad Exponential Moving Average) is a zero-lag smoothing filter that cascades four EMAs with geometrically ramped alphas and combines them using minimum-energy weights. Unlike traditional multi-stage EMAs (DEMA, TEMA) that use fixed coefficients, QEMA solves for weights that explicitly eliminate DC lag while minimizing output variance. The result is a filter that tracks linear trends with zero group delay while suppressing high-frequency noise more effectively than standard EMA cascades.
@@ -358,4 +356,4 @@ Run validation: `dotnet test --filter "FullyQualifiedName~QemaValidation"`
6. **Using `isNew` Incorrectly**: For live tick updates within the same bar, use `Update(value, isNew: false)`. Use `isNew: true` (default) only when a new bar opens. Getting this wrong causes the filter to run 4× faster than intended.
7. **Overshoot on Step Changes**: Despite being "zero-lag," QEMA can overshoot on sudden step changes because the weighted sum can extrapolate beyond the input. This is the price of reduced lag. If overshoot is unacceptable, use a filter with monotonic step response (like EMA).
7. **Overshoot on Step Changes**: Despite being "zero-lag," QEMA can overshoot on sudden step changes because the weighted sum can extrapolate beyond the input. This is the price of reduced lag. If overshoot is unacceptable, use a filter with monotonic step response (like EMA).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [rema_signature](rema_signature.md) |
- REMA (Regularized Exponential Moving Average) combines exponential smoothing with a regularization term that penalizes deviations from the previous...
- Parameterized by `period`, `lambda` (default 0.5).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../ema/ema.md), [DEMA](../dema/dema.md) | **Complementary:** Volatility filters | **Trading note:** Regularized EMA; lambda term reduces whipsaws.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
REMA (Regularized Exponential Moving Average) combines exponential smoothing with a regularization term that penalizes deviations from the previous trend direction. The result is a filter that responds to genuine price movements while suppressing noise-induced oscillations. Think of it as an EMA with momentum awareness: it knows where it was heading and applies a penalty for sudden course corrections.
@@ -211,4 +209,4 @@ REMA is ideal when:
REMA is less suitable when:
- You need maximum responsiveness (use EMA instead)
- You're comparing against external libraries that don't implement REMA
- You need predictable, standardized behavior across platforms
- You need predictable, standardized behavior across platforms
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [rgma_signature](rgma_signature.md) |
- RGMA (Recursive Gaussian Moving Average) approximates Gaussian smoothing by cascading multiple identical exponential moving averages.
- Parameterized by `period`, `passes` (default 3).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [TEMA](../tema/tema.md) | **Complementary:** Trend confirmation | **Trading note:** Recursive Gaussian MA; Gaussian IIR approximation for smooth trend.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
RGMA (Recursive Gaussian Moving Average) approximates Gaussian smoothing by cascading multiple identical exponential moving averages. Each pass through an EMA filter smooths the signal further, and the mathematical magic is that cascaded low-pass filters push the impulse response toward a Gaussian-like shape. You get the desirable properties of Gaussian smoothing—smooth frequency roll-off, minimal ringing, symmetric lag—without the computational cost of a true FIR convolution.
@@ -231,4 +229,4 @@ RGMA is less suitable when:
## References
- TradingView reference implementation: `lib/trends_IIR/rgma/rgma.pine`
- Central Limit Theorem and cascaded filter theory: Smith, S.W. *The Scientist and Engineer's Guide to Digital Signal Processing*, Chapter 15
- Central Limit Theorem and cascaded filter theory: Smith, S.W. *The Scientist and Engineer's Guide to Digital Signal Processing*, Chapter 15
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [rma_signature](rma_signature.md) |
- The Running Moving Average (RMA), also known as the Smoothed Moving Average (SMMA) or Wilder's Moving Average, is the backbone of J.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `ema.WarmupPeriod` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SMMA](../smma/smma.md), [MMA](../mma/mma.md) | **Complementary:** RSI/ATR | **Trading note:** Running MA (identical to SMMA); Wilders original smoothing method.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Running Moving Average (RMA), also known as the Smoothed Moving Average (SMMA) or Wilder's Moving Average, is the backbone of J. Welles Wilder's most famous indicators: RSI, ATR, and ADX. It is functionally identical to an Exponential Moving Average (EMA), but with a smoothing factor ($\alpha$) of $1/N$ instead of $2/(N+1)$. This results in a longer "memory" and slower decay than a standard EMA of the same period.
@@ -111,4 +109,4 @@ Validated against Skender and Ooples.
1. **Initialization**: Like EMA, RMA requires a "warmup" period to converge. Wilder often initialized with a Simple Moving Average (SMA) of the first $N$ bars. QuanTAlib follows this convention.
2. **Naming**: Often called SMMA (Smoothed Moving Average) in other libraries.
3. **Period Mismatch**: Using an EMA(14) where an RMA(14) is expected will result in a much faster-moving line (equivalent to RMA(7.5)).
3. **Period Mismatch**: Using an EMA(14) where an RMA(14) is expected will result in a much faster-moving line (equivalent to RMA(7.5)).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [t3_signature](t3_signature.md) |
- The T3 Moving Average is a hyper-smooth, low-lag filter that cascades six Exponential Moving Averages (EMAs).
- Parameterized by `period`, `vfactor` (default 0.7).
- Output range: Tracks input.
- Requires `period * 6` bars of warmup before first valid output (IsHot = true).
- **Similar:** [TEMA](../tema/tema.md), [DEMA](../dema/dema.md) | **Complementary:** Signal line crossover | **Trading note:** Tillsons T3; generalized DEMA with volume factor.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The T3 Moving Average is a hyper-smooth, low-lag filter that cascades six Exponential Moving Averages (EMAs). Unlike standard cascading (which increases lag), T3 uses a "Volume Factor" ($v$) to weight the EMAs in a way that partially cancels out the lag, resulting in a curve that is smoother than an EMA but more responsive than an SMA.
@@ -116,4 +114,4 @@ T3 is inherently recursive due to 6 cascaded EMAs. SIMD parallelization across b
1. **Warmup**: Because it cascades 6 EMAs, T3 takes significantly longer to stabilize than a standard EMA. A T3(10) might need 60+ bars to converge.
2. **Overshoot**: With high $v$ values ($>1$), T3 can overshoot price turns, creating false breakout signals.
3. **Complexity**: It is computationally heavier than SMA or EMA (approx 6x ops), though still negligible on modern CPUs.
3. **Complexity**: It is computationally heavier than SMA or EMA (approx 6x ops), though still negligible on modern CPUs.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [trama_signature](trama_signature.md) |
- TRAMA is an adaptive EMA where the smoothing factor derives from the "trend regularity" of the lookback window, measured as the fraction of bars th...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** Volatility filters | **Trading note:** Triangular Adaptive MA; uses triangular window in adaptive mode.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
TRAMA is an adaptive EMA where the smoothing factor derives from the "trend regularity" of the lookback window, measured as the fraction of bars that produce either a new highest-high (HH) or a new lowest-low (LL). This fraction is squared to create a convex penalty: low regularity (ranging) produces near-zero smoothing (filter barely moves), while high regularity (trending) produces aggressive smoothing (filter tracks closely). Developed by LuxAlgo (TradingView, December 2020).
@@ -155,4 +153,4 @@ Batch implementation uses ArrayPool-rented circular buffers for prices and event
- LuxAlgo (2020). "TRAMA - Trend Regularity Adaptive Moving Average." TradingView. Published December 2020.
- Kaufman, P.J. (1995). *Smarter Trading*. McGraw-Hill. Chapter 7: Adaptive Techniques. (KAMA framework, precursor to adaptive MA design.)
- Chande, T.S. (1997). *Beyond Technical Analysis*, 2nd ed. Wiley. (VIDYA and adaptive smoothing theory.)
- Chande, T.S. (1997). *Beyond Technical Analysis*, 2nd ed. Wiley. (VIDYA and adaptive smoothing theory.)
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [vama.pine](vama.pine) |
- Most moving averages use a fixed lookback period.
- Parameterized by `baselength` (default 20), `shortatrperiod` (default 10), `longatrperiod` (default 50), `minlength` (default 5), `maxlength` (default 100).
- Output range: Tracks input.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [VIDYA](../vidya/vidya.md), [KAMA](../kama/kama.md) | **Complementary:** Volatility analysis | **Trading note:** Volatility-Adjusted MA; scales smoothing by relative volatility.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## The Core Insight
@@ -287,4 +285,4 @@ VAMA's ATR-based approach specifically responds to range expansion/contraction,
## References
- Wilder, J.W. (1978). "New Concepts in Technical Trading Systems" - ATR and RMA foundations
- PineScript reference implementation: `vama.pine`
- PineScript reference implementation: `vama.pine`
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [vidya_signature](vidya_signature.md) |
- The Variable Index Dynamic Average (VIDYA) is an adaptive moving average that automatically adjusts its smoothing speed based on market volatility.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [KAMA](../kama/kama.md), [FRAMA](../frama/frama.md) | **Complementary:** CMO (used internally) | **Trading note:** Chandes Variable Index Dynamic Average; adapts via CMO ratio.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Variable Index Dynamic Average (VIDYA) is an adaptive moving average that automatically adjusts its smoothing speed based on market volatility. When the market is trending (high volatility), VIDYA speeds up to capture the move. When the market is ranging (low volatility), it slows down to filter out the noise.
@@ -117,4 +115,4 @@ VIDYA is an IIR filter with CMO-driven adaptive alpha — not vectorizable acros
1. **Flatlining**: In extremely choppy, sideways markets, CMO can approach 0, causing VIDYA to flatline completely. This is a feature, not a bug.
2. **Sensitivity**: VIDYA is highly sensitive to the period chosen for the CMO. A short period makes it jittery; a long period makes it sluggish.
3. **Comparison**: Often compared to KAMA (Kaufman). KAMA uses Efficiency Ratio (ER); VIDYA uses CMO. They are conceptually similar but mathematically distinct.
3. **Comparison**: Often compared to KAMA (Kaufman). KAMA uses Efficiency Ratio (ER); VIDYA uses CMO. They are conceptually similar but mathematically distinct.
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [yzvama.pine](yzvama.pine) |
- Most adaptive moving averages measure volatility using close-to-close changes (standard deviation) or high-low ranges (ATR).
- Parameterized by `yzvshortperiod` (default 3), `yzvlongperiod` (default 50), `percentilelookback` (default 100), `minlength` (default 5), `maxlength` (default 100).
- Output range: Tracks input.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [VAMA](../vama/vama.md), [VIDYA](../vidya/vidya.md) | **Complementary:** Yang-Zhang volatility | **Trading note:** Yang-Zhang Volatility-Adjusted MA; adapts using YZ volatility estimator.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## The Core Insight
@@ -427,4 +425,4 @@ Percentile ranking solves both:
- Yang, D., & Zhang, Q. (2000). "Drift-Independent Volatility Estimation Based on High, Low, Open, and Close Prices." *Journal of Business*, 73(3), 477-491.
- Rogers, L.C.G., & Satchell, S.E. (1991). "Estimating Variance from High, Low and Closing Prices." *Annals of Applied Probability*, 1(4), 504-512.
- PineScript reference implementation: `yzvama.pine`
- PineScript reference implementation: `yzvama.pine`
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [zldema_signature](zldema_signature.md) |
- ZLDEMA takes a standard DEMA and feeds it a **zero-lag signal**: current price minus a lagged price.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true).
- **Similar:** [ZLEMA](../zlema/zlema.md), [DEMA](../dema/dema.md) | **Complementary:** Signal line crossovers | **Trading note:** Zero-Lag DEMA; combines zero-lag with double-exponential.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## DEMA with lag compensation via a zero-lag signal
@@ -164,4 +162,4 @@ ZLDEMA is validated against a PineScript reference implementation.
5. **DEMA vs ZLDEMA**
ZLDEMA is not simply DEMA with a different alpha. The zero-lag preprocessing fundamentally changes the input signal, making ZLDEMA more responsive but also more prone to overshoot than standard DEMA.
ZLDEMA is not simply DEMA with a different alpha. The zero-lag preprocessing fundamentally changes the input signal, making ZLDEMA more responsive but also more prone to overshoot than standard DEMA.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [zlema_signature](zlema_signature.md) |
- ZLEMA takes a standard EMA and feeds it a **zero-lag signal**: current price minus a lagged price.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../dema/dema.md), [HMA](../../trends_FIR/hma/hma.md) | **Complementary:** Momentum confirmation | **Trading note:** Zero-Lag EMA; pre-adjusts input to remove delay.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## EMA with lag compensation via a zero-lag signal
@@ -143,4 +141,4 @@ ZLEMA is validated against a PineScript reference implementation.
4. **Non-finite data**
NaN or Infinity is replaced with the last valid value. Before the first valid sample, output is `NaN`.
NaN or Infinity is replaced with the last valid value. Before the first valid sample, output is `NaN`.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [zltema_signature](zltema_signature.md) |
- ZLTEMA takes a standard TEMA and feeds it a **zero-lag signal**: current price minus a lagged price.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `Math.Max(lag + 1, EstimateWarmupPeriod(beta))` bars of warmup before first valid output (IsHot = true).
- **Similar:** [ZLEMA](../zlema/zlema.md), [TEMA](../tema/tema.md) | **Complementary:** Signal line crossovers | **Trading note:** Zero-Lag TEMA; combines zero-lag with triple-exponential.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
## TEMA with lag compensation via a zero-lag signal
@@ -173,4 +171,4 @@ ZLTEMA is validated against a PineScript reference implementation.
6. **ZLDEMA vs ZLTEMA**
ZLTEMA adds a third EMA stage over ZLDEMA. This provides additional smoothing at the cost of more overshoot during reversals. Use ZLDEMA when overshoot is more concerning than noise; use ZLTEMA when maximum smoothness is required.
ZLTEMA adds a third EMA stage over ZLDEMA. This provides additional smoothing at the cost of more overshoot during reversals. Use ZLDEMA when overshoot is more concerning than noise; use ZLTEMA when maximum smoothness is required.