Skip to content

Reflex ​

Ehlers' near-zero-lag cycle oscillator — averages how far the SuperSmoothed price deviates from the straight line across the lookback, then self-normalises.

Quick reference ​

FieldValue
FamilyEhlers / Cycle (DSP)
Input typef64
Output typef64
Output rangeself-normalised, roughly [−3, +3]
Default parameters(period = 20) (Python)
Warmup periodperiod + 1
InterpretationZero-crossings ≈ cycle turns; extremes = over-extension.

Formula ​

Filt   = SuperSmoother(price, 0.5·period)   # a1 = exp(−1.414·π / (0.5·period))
slope  = (Filt[period] − Filt[0]) / period
sum    = mean over i=1..period of ( Filt[0] + i·slope − Filt[i] )
ms     = 0.04·sum² + 0.96·ms[−1]
Reflex = sum / sqrt(ms)        (0 if ms == 0)

The SuperSmoother prefilter runs with half the lookback as its critical period (Ehlers' a1 = exp(−1.414·π / (0.5·Length))), which may be fractional for an odd period; the line fit and the deviation average still span the full period. Reflex draws the straight line connecting the SuperSmoothed price at the two ends of the window and averages the curve's deviation from it. Using both endpoints gives it almost no lag, so it crosses zero right at cycle turns. The adaptive mean-square term rescales the output to a roughly ±3 band on any instrument. Source: crates/wickra-core/src/indicators/reflex.rs.

Parameters ​

NameTypeDefaultValid rangeSourceDescription
periodusize20 (Python)>= 1reflex.rs:64Lookback for the line fit; the SuperSmoother prefilter uses half of it (0.5·period) as its critical period. 0 errors with Error::PeriodZero.

The period getter returns the window; value returns the current output if ready.

Inputs / Outputs ​

From crates/wickra-core/src/indicators/reflex.rs:

rust
use wickra::{Indicator, Reflex};
// Reflex: Input = f64, Output = f64
const _: fn(&mut Reflex, f64) -> Option<f64> = <Reflex as Indicator>::update;

An f64 in, an Option<f64> out. The Python binding takes a scalar for update and a 1-D numpy array for batch (NaN warmup); Node takes update(value) and batch(values[]).

Warmup ​

warmup_period() == period + 1. The SuperSmoother feeds a period + 1-deep buffer before the first value (first_emission_at_warmup_period pins this).

Edge cases ​

  • Flat input → 0. A constant equals its own line, so the deviation — and the output — is 0 (constant_input_is_zero pins this).
  • Cyclic input → zero-mean swing. A sine produces a symmetric oscillation (cyclic_input_oscillates_around_zero pins this).
  • Non-finite input. A NaN/∞ input is ignored and the last value returned (ignores_non_finite pins this).
  • Reset. r.reset() clears the SuperSmoother, the filter buffer, the normaliser and the last value (reset_clears_state).

Examples ​

Rust ​

rust
use wickra::{BatchExt, Indicator, Reflex};
use std::f64::consts::TAU;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut r = Reflex::new(20)?;
    let xs: Vec<f64> = (0..200).map(|i| 100.0 + (TAU * f64::from(i) / 20.0).sin() * 5.0).collect();
    println!("last = {:?}", r.batch(&xs).last().unwrap());
    Ok(())
}

Python ​

python
import numpy as np
import wickra as ta

r = ta.REFLEX(20)
x = 100 + np.sin(2 * np.pi * np.arange(200) / 20) * 5
print(r.batch(x)[-5:])  # ~[-3, 3] oscillation

Node ​

javascript
const ta = require('wickra');

const r = new ta.REFLEX(20);
console.log('warmupPeriod:', r.warmupPeriod()); // 21

Streaming ​

rust
use wickra::{Indicator, Reflex};

let mut r = Reflex::new(20).unwrap();
let mut last = None;
for i in 0..120 {
    last = r.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
}
println!("{last:?}");

Streaming update and batch are equivalent tick-for-tick (batch_equals_streaming pins this).

Interpretation ​

  1. Cycle timing. Zero-crossings mark cycle turns with minimal lag — earlier than RSI or stochastic on the same data.
  2. Over-extension. Readings near ±2/±3 flag stretched moves likely to revert.
  3. Pair with Trendflex. Run both: Reflex leads in ranging markets, Trendflex shines in trends.

Common pitfalls ​

  • Not bounded exactly. The normaliser targets ±3 but does not clamp; rare spikes can exceed it.
  • Prefilter is half-period. The SuperSmoother runs with critical period 0.5·period, not period; rebuilding the indicator from a SuperSmoother of the full period will not reproduce Wickra's (or Ehlers') values.
  • Period sets the cycle. Match period to the cycle you want to time.
  • Prefilter lag. The SuperSmoother adds a touch of lag the line-fit largely cancels — but in violent gaps a little remains.

References ​

Ehlers, J. F. (2020), "Reflex: A New Zero-Lag Indicator", Technical Analysis of Stocks & Commodities, February 2020.

See also ​