Cyber Cycle
John Ehlers' bandpass-style cycle oscillator — isolates short-term cyclic component with dramatically less lag than classic momentum tools.
Visual Example

Synthetic cycle with Cyber Cycle and trigger line. Generated via docs/generate_all_previews.py; maps to core Next<f64> → (cycle, trigger).
Description
Cyber Cycle applies a symmetrical 4-bar FIR smoother and a second-order IIR bandpass to extract the cyclic component of price. The trigger line is the cycle delayed one bar — crossovers produce timing signals with less derivative noise than MACD-style constructions.
Use Cyber Cycle when you need:
- Cycle timing — entries/exits around cycle turns in mean-reverting regimes
- Regime gating — suppress cycle trades when Hurst or trend tools show persistence
- ML features — rich struct output via
.ta.features.cyber_cycle()(cycle, trigger, momentum, signal) - Ehlers stacks — chain with Roofing Filter, SuperSmoother, Instantaneous Trendline
QuantWave sources the math from Ehlers' Cybernetic Analysis for Stocks and Futures (2004), Chapter 4. The streaming indicator returns (cycle, trigger); the feature extractor adds momentum and signal fields for ML pipelines.
Formula / Specification
Source: John Ehlers, Cybernetic Analysis for Stocks and Futures (2004), Ch. 4
Let length control \(\alpha = 2 / (length + 1)\). Four-bar smooth:
Cyber Cycle recurrence (bandpass isolation):
Implementation: quantwave-core/src/indicators/cyber_cycle.rs
Feature struct: quantwave-core/src/features/cyber_cycle.rs (momentum, signal fields)
Parameters
| Parameter | Default | Description |
|---|---|---|
length |
14 | Controls \(\alpha\); higher = smoother, more lag |
Ehlers examples often use 10–20 on daily data; intraday may need shorter lengths with Roofing pre-filtering.
Usage Examples
Polars features (ML / multi-output)
import polars as pl
import quantwave # registers LazyFrame.ta.features
df = (
pl.read_csv("ohlcv.csv")
.lazy()
.ta.features()
.cyber_cycle(14)
.collect()
)
# Struct column "cyber_cycle" — unnest for cycle, trigger, momentum, signal
Streaming indicator (cycle + trigger)
import quantwave as qw
cc = qw.streaming_class("cyber_cycle")(length=14)
for price in closes:
out = cc.next(price)
# out.cycle, out.trigger (or tuple depending on binding)
Streaming (Rust)
use quantwave_core::indicators::cyber_cycle::CyberCycle;
use quantwave_core::traits::Next;
let mut cc = CyberCycle::new(14);
for price in &closes {
let (cycle, trigger) = cc.next(*price);
}
Feature matrix (batch research)
import quantwave as qw
matrix = qw.build_feature_matrix(df, specs=[
qw.FeatureSpec("cyber_cycle", {"length": 30}),
qw.FeatureSpec("hurst", {"window": 100}),
])
See ML Features → Backtest E2E for parity-proof pipeline.
Edge Cases & Limitations
- Trending markets: Bandpass assumes cyclic component exists — trending data produces drift; gate with trend/regime filters.
- Warm-up: FIR + IIR state needs several bars; early outputs are unstable.
- Noisy inputs: Pre-filter with Roofing or SuperSmoother on very choppy series.
- Not a standalone system: Pair with structure (Market Structure) or regime tools.
Boundary Behavior
| Condition | Behavior |
|---|---|
| Warm-up | Leading bars reflect partial filter state. |
length > series length |
Insufficient data for stable cycle extraction. |
| NaN in close | NaN propagates through filter chain. |
| Invalid params | Non-positive length raises ValueError. |
Related Indicators & See Also
- Ehlers DSP guide
- Instantaneous Trendline — complementary trend/cycle separator
- Trendflex — adaptive trend/cycle decomposition
- Roofing Filter — recommended pre-filter
- ML Feature Stability notebook
Sources & References
Primary source: Ehlers (2004) Cybernetic Analysis for Stocks and Futures, Chapter 4
Implementation: quantwave-core/src/indicators/cyber_cycle.rs (CyberCycle / CYBER_CYCLE_METADATA)
Parity: quantwave-core/tests/test_ml_feature_validation.rs — batch vs streaming, no look-ahead