Decycler β
John Ehlers' trend extractor β
decycler = input - HighPass(input). Subtracts a 2-pole high-pass filter from the raw price, leaving the slow component: equivalent to a smoothed trend line with no group delay at low frequencies. The natural complement to SuperSmoother for cycle/trend decomposition.
Quick reference β
| Item | Value |
|---|---|
| Family | Ehlers / Cycle (DSP) |
| Input type | f64 |
| Output type | f64 |
| Output range | unbounded (price-units) |
| Default parameters | period is required (typical 20) |
| Warmup period | 2 (uses input directly until recursion fills) |
| Interpretation | Trend line; no group delay at low frequencies |
Formula β
alpha = (cos(.707Β·2Ο/period) + sin(.707Β·2Ο/period) - 1) / cos(.707Β·2Ο/period)
HP_t = (1 - Ξ±/2)Β² Β· (x_t - 2Β·x_{t-1} + x_{t-2})
+ 2Β·(1 - Ξ±) Β· HP_{t-1}
- (1 - Ξ±)Β² Β· HP_{t-2}
decycler_t = x_t - HP_tThe first two outputs simply equal the input (warmup buffering), the conventional Ehlers initialisation. See crates/wickra-core/src/indicators/decycler.rs.
Parameters β
| Name | Type | Default | Constraint | Description |
|---|---|---|---|---|
period | usize | none | > 1 | Critical period of the inner high-pass filter. |
Decycler::new returns Error::PeriodZero for period == 0 and Error::InvalidPeriod for period == 1.
Inputs / Outputs β
Indicator<Input = f64, Output = f64>. Python: Decycler(period).batch(prices) returns a 1-D np.ndarray (the first 2 bars pass through input unchanged). Node: same shape; update(value) returns number.
Warmup β
warmup_period() == 2. The first two inputs pass through unchanged; from input 3 onward the proper high-pass recursion runs. By input ~2 Β· period the initial transient has decayed.
Edge cases β
- Constant input. High-pass output decays to zero; decycler converges to the input.
- Step input. Critically damped β no overshoot. Roughly
periodbars to converge after a step. - Pure cyclical input at exactly
period. Filtered out almost entirely; decycler tracks the long-term level only. - Reset.
reset()clears the recursion state.
Examples β
Rust β
use wickra::{BatchExt, Decycler, Indicator};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let prices: Vec<f64> = (0..100)
.map(|i| {
let trend = 100.0 + f64::from(i) * 0.5;
let cycle = (f64::from(i) * 0.6).sin() * 5.0;
trend + cycle
})
.collect();
let mut dc = Decycler::new(20)?;
let out = dc.batch(&prices);
println!("row 50 decycler={:?} raw={}", out[50], prices[50]);
// decycler β trend component only
Ok(())
}Python β
import numpy as np
import wickra as ta
t = np.arange(100)
trend = 100 + t * 0.5
cycle = np.sin(t * 0.6) * 5
prices = trend + cycle
dc = ta.Decycler(20)
out = dc.batch(prices)
print('row 50:', out[50], 'vs trend:', trend[50])Node β
const wickra = require('wickra');
const dc = new wickra.Decycler(20);
const prices = Array.from({ length: 100 },
(_, i) => 100 + i * 0.5 + Math.sin(i * 0.6) * 5);
console.log('row 50:', dc.batch(prices)[50]);Streaming β
use wickra::{Decycler, Indicator};
let mut dc = Decycler::new(20).unwrap();
let price_stream: Vec<f64> = Vec::new(); // your live price feed
for px in price_stream {
let trend = dc.update(px).unwrap();
// Use `trend` as the de-cycled price reference for slow-moving systems
}Interpretation β
- Trend line. Use Decycler as a low-lag trend reference β it matches an SMA at roughly the same period but with much less group delay at low frequencies.
- Vs SuperSmoother. They are duals: SuperSmoother is a lowpass (passes long cycles), Decycler is
input β highpass(also passes long cycles, but with different phase response). SuperSmoother has tighter cutoff; Decycler has zero phase lag at DC. - Pair with DecyclerOscillator. Subtracting a fast Decycler from a slow one gives the DecyclerOscillator, an MACD-like cycle-band indicator.
Common pitfalls β
- Treating
periodas SMA period. The Decycler's critical period defines the boundary between trend and cycle, not the length of any averaging window.Decycler(20)is faster thanSMA(20)in the trend regime. - Expecting bounded output. Decycler tracks price scale; it's not an oscillator.
- Warmup misconception. The pass-through-input initial condition means the first 2 outputs equal the inputs β they look "correct" but carry no smoothing yet.
References β
- John F. Ehlers, Cycle Analytics for Traders, Wiley (2013), ch. 4 β derives the Decycler from the high-pass complement.
See also β
- DecyclerOscillator β MACD-like oscillator built on the Decycler difference.
- SuperSmoother β lowpass alternative.
- RoofingFilter β bandpass dual.
- Indicators-Overview β full taxonomy.