Add TRAMA implementation and comprehensive tests

- Implemented the TRAMA (Trend Regularity Adaptive Moving Average) class with adaptive EMA logic.
- Added unit tests for TRAMA functionality, including constructor validation, basic calculations, state management, and robustness checks.
- Created validation tests to ensure consistency across different modes of operation (streaming, batch, and static calculations).
- Enhanced documentation for TRAMA, including performance profiles and quality metrics.
- Updated workspace configuration by removing unnecessary folder references.
This commit is contained in:
Miha Kralj
2026-02-21 20:45:38 -08:00
parent 90d5638008
commit 7253f61299
199 changed files with 29577 additions and 234 deletions
+103
View File
@@ -0,0 +1,103 @@
# REFLEX: Ehlers Reflex Indicator
> "John Ehlers measured how much a filtered price deviates from its own linear extrapolation. The result is a zero-lag oscillator that catches reversals before they happen, because the deviation is largest precisely when the trend is bending."
REFLEX is a zero-lag oscillator that measures the reversal tendency of price by comparing a Super-Smoother-filtered price against a linear extrapolation from $N$ bars ago. The filter computes the slope of the filtered series over the lookback window, projects a straight line, and sums the deviations of the actual filtered values from this projected line. The sum is normalized by an exponential RMS estimate to produce values in roughly $\pm \sigma$ scale. Values above 0 indicate uptrend, below 0 indicate downtrend; crossovers signal potential reversals.
## Historical Context
John F. Ehlers published REFLEX in "Reflex: A New Zero-Lag Indicator" (*Technical Analysis of Stocks & Commodities*, February 2020). Ehlers' motivation was to create a cycle-based oscillator that responds to trend reversals with zero lag, unlike traditional oscillators (RSI, stochastic) that inherently lag price due to their smoothing components.
The core idea is that linear extrapolation of a smoothed series will overshoot (undershoot) when the trend is decelerating (accelerating). By measuring the sum of these overshoots, REFLEX detects curvature changes — exactly the inflection points where trends reverse. This is mathematically similar to measuring the second derivative (acceleration), but the linear-extrapolation approach is more numerically stable and naturally adapts to the trend's own slope.
The 2-pole Super Smoother pre-filter (at half the specified period) removes high-frequency noise before the reflex computation, preventing false signals from bar-to-bar price noise. The exponential RMS normalization ensures the output has consistent scale regardless of the instrument's volatility.
## Architecture & Physics
### 1. Super Smoother Pre-Filter
A 2-pole IIR low-pass filter with cutoff at half the specified period:
$$
\text{Filt} = c_1 \cdot \frac{x_t + x_{t-1}}{2} + c_2 \cdot \text{Filt}_{t-1} + c_3 \cdot \text{Filt}_{t-2}
$$
where $a_1 = e^{-\sqrt{2}\pi / (N/2)}$, $c_2 = 2a_1\cos(\sqrt{2}\pi/(N/2))$, $c_3 = -a_1^2$, $c_1 = 1-c_2-c_3$.
### 2. Linear Extrapolation Slope
$$
\text{slope} = \frac{\text{Filt}_{t-N} - \text{Filt}_t}{N}
$$
### 3. Deviation Summation
$$
\text{Sum} = \frac{1}{N}\sum_{i=1}^{N}\left[(\text{Filt}_t + i \cdot \text{slope}) - \text{Filt}_{t-i}\right]
$$
### 4. Exponential RMS Normalization
$$
\text{MS} = 0.04 \cdot \text{Sum}^2 + 0.96 \cdot \text{MS}_{t-1}
$$
$$
\text{REFLEX} = \frac{\text{Sum}}{\sqrt{\text{MS}}}
$$
## Mathematical Foundation
**Super Smoother coefficients (half-period cutoff):**
$$
a_1 = e^{-\sqrt{2}\pi / (N/2)}, \quad c_2 = 2a_1\cos\!\left(\frac{\sqrt{2}\pi}{N/2}\right), \quad c_3 = -a_1^2, \quad c_1 = 1-c_2-c_3
$$
**Deviation from linear trend:**
$$
D_i = (\text{Filt}_t + i \cdot \text{slope}) - \text{Filt}_{t-i}, \quad i = 1, \ldots, N
$$
**Mean deviation:**
$$
\text{Sum} = \frac{1}{N}\sum_{i=1}^{N} D_i
$$
**Interpretation:**
- $\text{Sum} > 0$: filtered price is above its linear extrapolation (upward curvature, potential uptrend)
- $\text{Sum} < 0$: filtered price is below its linear extrapolation (downward curvature, potential downtrend)
- Zero crossings signal inflection points (trend reversals)
**Default parameters:** `period = 20`, `minPeriod = 2`. Output is an oscillator (not overlay).
**Pseudo-code (streaming):**
```
// Super Smoother (2-pole IIR)
filt = c1*(price + price[1])/2 + c2*filt[1] + c3*filt[2]
// Store in circular buffer
buf[head] = filt
// Slope from N-bar-ago to current
slope = (filt_lag_N - filt) / N
// Sum deviations from linear extrapolation
sum = 0
for i = 1 to N:
sum += (filt + i*slope) - filt[i]
sum /= N
// Normalize by exponential RMS
ms = 0.04 * sum² + 0.96 * ms[1]
return ms > 0 ? sum / sqrt(ms) : 0
```
## Resources
- Ehlers, J.F. (2020). "Reflex: A New Zero-Lag Indicator." *Technical Analysis of Stocks & Commodities*, February 2020.
- Ehlers, J.F. (2013). *Cycle Analytics for Traders*. Wiley. Chapter 3: Super Smoothers.
+90
View File
@@ -0,0 +1,90 @@
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0
// https://mozilla.org/MPL/2.0/
// © QuanTAlib
//@version=6
indicator("Ehlers Reflex Indicator (REFLEX)", "REFLEX", overlay = false)
//@function Ehlers Reflex — a zero-lag oscillator that measures the reflex (reversal
// tendency) of price by comparing the SSF-filtered price against a linear
// extrapolation from N bars ago. Applies a 2-pole Super Smoother pre-filter
// at half the specified period, then computes slope = (Filt[N] - Filt) / N,
// sums deviations of the extrapolated line from actual filtered values over
// the window, and normalizes by exponential RMS. Values above 0 suggest
// uptrend, below 0 suggest downtrend; crossovers signal reversals.
//@param source Series to analyze
//@param period Lookback window / assumed cycle period (>= 2)
//@returns Reflex oscillator value (normalized, roughly ±σ scale)
//@reference Ehlers, J.F. (2020). "Reflex: A New Zero-Lag Indicator."
// Technical Analysis of Stocks & Commodities, Feb 2020.
//@optimized O(period) per bar for the summation loop; SSF is O(1) IIR
export reflex(series float source, simple int period) =>
if period < 2
runtime.error("Period must be at least 2")
float price = nz(source)
// --- 2-Pole Super Smoother Filter (half-period cutoff) ---
float half_period = period * 0.5
float a1 = math.exp(-1.414 * math.pi / half_period)
float b1 = 2.0 * a1 * math.cos(1.414 * math.pi / half_period)
float c2 = b1
float c3 = -(a1 * a1)
float c1 = 1.0 - c2 - c3
var float filt = 0.0
var float filt1 = 0.0
var float filt2 = 0.0
float src1 = nz(source[1])
filt2 := filt1
filt1 := filt
filt := c1 * (price + src1) * 0.5 + c2 * filt1 + c3 * filt2
// --- Circular buffer to store filtered values for lookback ---
var array<float> buf = array.new_float(period + 1, 0.0)
var int head = 0
array.set(buf, head, filt)
int count = math.min(bar_index + 1, period)
// --- Slope: (Filt[Length] - Filt) / Length ---
int lag_idx = (head - period + period + 1) % (period + 1)
float filt_lag = array.get(buf, lag_idx)
float slope = (filt_lag - filt) / period
// --- Sum the differences ---
// Sum = Σ(i=1..Length) [(Filt + i*Slope) - Filt[i]] / Length
float the_sum = 0.0
if count >= period
for i = 1 to period
int idx = (head - i + period + 1) % (period + 1)
float filt_i = array.get(buf, idx)
the_sum += (filt + float(i) * slope) - filt_i
the_sum /= period
// --- Advance head ---
head := (head + 1) % (period + 1)
// --- Normalize in terms of Standard Deviations ---
// MS = 0.04 * Sum² + 0.96 * MS[1] (exponential RMS)
var float ms = 0.0
ms := 0.04 * the_sum * the_sum + 0.96 * ms
float result = 0.0
if ms > 0.0
result := the_sum / math.sqrt(ms)
result
// ── Inputs ──
int p_period = input.int(20, "Period", minval = 2)
float p_src = input.source(close, "Source")
// ── Calculation ──
float out = reflex(p_src, p_period)
// ── Plot ──
plot(out, "REFLEX", color.yellow, 2)
hline(0, "Zero", color.gray)
hline(1.0, "+1σ", color.new(color.red, 60))
hline(-1.0, "-1σ", color.new(color.green, 60))