Ehlers Decycler

Parameters: high_pass_period = 125 | frequency_coefficient = 0.707

Overview

The Ehlers Decycler, created by John Ehlers, extracts the underlying trend from price data by removing cyclical components using digital signal processing techniques. The indicator applies a high pass filter to identify and isolate high frequency oscillations, then subtracts these components from the original price to reveal the smooth trend beneath. This approach differs from traditional smoothing methods that average prices together. Instead, the decycler surgically removes unwanted frequencies while preserving the trend structure. The frequency coefficient parameter, typically set to 0.707, controls the filter's response characteristics and determines how aggressively cycles are removed.

The decycled output represents pure trend movement stripped of short term oscillations and market noise. This filtered series responds to genuine trend changes while remaining stable during sideways chop that would create false signals in momentum indicators. The high pass period parameter, defaulting to 125, defines the cutoff frequency below which cycles are removed. Longer periods create smoother outputs by filtering more frequencies, while shorter periods preserve more price detail. The indicator achieves this noise reduction with minimal lag compared to moving averages of similar smoothness, making it responsive to actual trend shifts.

Traders use the Decycler as a superior alternative to moving averages for trend identification and signal generation. Price crossovers with the decycled line provide cleaner entry and exit signals with fewer whipsaws than traditional moving average systems. The indicator excels as a baseline for other calculations, providing a stable reference for oscillators or bands that would otherwise be distorted by market noise. Many systematic traders employ the Decycler to separate trend from cycle components, allowing different strategies to trade each market aspect. The filtered output also serves as dynamic support and resistance, with price often respecting the decycled line during trending markets.

Implementation Examples

Compute Decycler from a slice or Candles:

use vectorta::indicators::decycler::{decycler, DecyclerInput, DecyclerParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = DecyclerParams { hp_period: Some(125), k: Some(0.707) };
let input = DecyclerInput::from_slice(&prices, params);
let result = decycler(&input)?;

// Using with Candles (defaults: hp_period=125, k=0.707; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = DecyclerInput::with_default_candles(&candles);
let result = decycler(&input)?;

// Access the values
for v in result.values { println!("Decycler: {}", v); }

API Reference

Input Methods
// From price slice
DecyclerInput::from_slice(&[f64], DecyclerParams) -> DecyclerInput

// From candles with custom source
DecyclerInput::from_candles(&Candles, &str, DecyclerParams) -> DecyclerInput

// From candles with defaults (close, hp_period=125, k=0.707)
DecyclerInput::with_default_candles(&Candles) -> DecyclerInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct DecyclerParams {
    pub hp_period: Option<usize>, // Default: 125
    pub k: Option<f64>,           // Default: 0.707
}
Output Structure
#[derive(Debug, Clone)]
pub struct DecyclerOutput {
    pub values: Vec<f64>, // Decycled series (trend component)
}
Validation, Warmup & NaNs
  • hp_period >= 2 and hp_period ≤ data.len(); else DecyclerError::InvalidPeriod.
  • k must be finite and > 0; else DecyclerError::InvalidK.
  • Empty input → DecyclerError::EmptyData; all-NaN input → DecyclerError::AllValuesNaN.
  • Must have at least hp_period valid points after first finite; else DecyclerError::NotEnoughValidData.
  • Warmup: indices before first_valid_index + 2 are NaN to match offline/streaming behavior.
Error Handling
use vectorta::indicators::decycler::{decycler, DecyclerError};

match decycler(&input) {
    Ok(output) => consume(output.values),
    Err(DecyclerError::EmptyData) => eprintln!("Empty data"),
    Err(DecyclerError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid hp_period {} for length {}", period, data_len),
    Err(DecyclerError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, found {}", needed, valid),
    Err(DecyclerError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(DecyclerError::InvalidK { k }) => eprintln!("Invalid k: {}", k),
    Err(DecyclerError::InvalidKernel) => eprintln!("Invalid kernel for batch operation"),
}

Python Bindings

Basic Usage

Calculate Decycler using NumPy arrays (defaults: 125 / 0.707):

import numpy as np
from vectorta import decycler

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])

# Defaults: hp_period=125, k=0.707
result = decycler(prices)

# Custom parameters and kernel selection
result = decycler(prices, hp_period=100, k=0.8, kernel="auto")

print("Decycler:", result)
Streaming Real-time Updates

Use the streaming API to update with O(1) cost:

from vectorta import DecyclerStream

stream = DecyclerStream(hp_period=125, k=0.707)
for price in price_feed:
    value = stream.update(price)
    if value is not None:
        print("Decycler:", value)
Batch Parameter Optimization

Test multiple parameter combinations:

import numpy as np
from vectorta import decycler_batch

prices = np.array([...])

sweep = decycler_batch(
    prices,
    hp_period_range=(50, 150, 25),
    k_range=(0.5, 1.0, 0.25),
    kernel="auto",
)

print(sweep['values'].shape)  # (num_combos, len(prices))
print(sweep['hp_periods'])
print(sweep['k_values'])
CUDA Acceleration

CUDA support for Decycler is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated Decycler calculations

JavaScript/WASM Bindings

Basic Usage

Calculate Decycler in JavaScript/TypeScript:

import { decycler_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
const values = decycler_js(prices, 125, 0.707);
console.log('Decycler values:', values);
Memory-Efficient Operations

Use zero-copy operations with pre-allocated memory:

import { decycler_alloc, decycler_free, decycler_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* data */]);
const len = prices.length;

const inPtr = decycler_alloc(len);
const outPtr = decycler_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);

// Args: in_ptr, out_ptr, len, hp_period, k
decycler_into(inPtr, outPtr, len, 125, 0.707);

const out = new Float64Array(memory.buffer, outPtr, len).slice();
decycler_free(inPtr, len);
decycler_free(outPtr, len);
console.log('Decycler:', out);
Batch Processing

Test multiple parameter combinations efficiently:

import { decycler_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Unified batch API with config object
const cfg = {
  hp_period_range: [50, 150, 25],
  k_range: [0.5, 1.0, 0.25],
};

const res = decycler_batch(prices, cfg);
// res: { values: Float64Array, combos: Array<{hp_period?: number, k?: number}>, rows: number, cols: number }
console.log(res.rows, res.cols);
console.log(res.combos[0]);

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators