mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 02:28:05 +00:00
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:
@@ -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.
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user