Ehlers Instantaneous Trendline

Parameters: warmup_bars = 12 | max_dc_period = 50

Overview

The Ehlers Instantaneous Trendline, developed by John Ehlers, creates an adaptive moving average that automatically adjusts its smoothing period based on the dominant market cycle detected through digital signal processing techniques. The indicator employs Hilbert Transform phase relationships to identify the current dominant cycle period in real time. This cycle measurement then determines the optimal smoothing window, allowing the trendline to adapt continuously to changing market rhythms. Unlike fixed period moving averages, the Instantaneous Trendline becomes more responsive during shorter cycles and smoother during longer cycles, achieving minimal lag while maintaining excellent noise reduction.

The adaptive mechanism makes the Instantaneous Trendline exceptionally effective at tracking trends across varying market conditions. During fast moving markets with short dominant cycles, the indicator tightens its smoothing to stay close to price action. When markets slow down and cycles lengthen, it automatically increases smoothing to filter out noise without manual adjustment. The max dominant cycle period parameter, typically set to 50, prevents excessive smoothing during extremely slow markets. The warmup bars parameter allows the Hilbert Transform calculations to stabilize before the adaptive mechanism engages, ensuring reliable cycle detection from the start.

Traders use the Instantaneous Trendline as a superior alternative to traditional moving averages for trend identification and signal generation. Price crossovers with the trendline generate cleaner signals with fewer whipsaws, as the adaptive smoothing helps distinguish genuine trend changes from temporary fluctuations. The indicator excels as a trailing stop mechanism, automatically adjusting its distance from price based on current market volatility and cycle characteristics. Many systematic traders employ the Instantaneous Trendline as their primary trend filter, only taking positions aligned with its direction. The indicator also works well in pairs, with different max period settings creating adaptive envelope systems that expand and contract with market conditions.

Implementation Examples

Get started with Ehlers iTrend in Rust:

use vectorta::indicators::ehlers_itrend::{ehlers_itrend, EhlersITrendInput, EhlersITrendParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = EhlersITrendParams { warmup_bars: Some(12), max_dc_period: Some(50) };
let input = EhlersITrendInput::from_slice(&prices, params);
let out = ehlers_itrend(&input)?;

// Using Candles with defaults (source = "close", warmup=12, max_dc=50)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EhlersITrendInput::with_default_candles(&candles);
let out = ehlers_itrend(&input)?;

for v in out.values { println!("iTrend: {}", v); }

API Reference

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

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

// From candles with defaults (source="close", warmup=12, max_dc=50)
EhlersITrendInput::with_default_candles(&Candles) -> EhlersITrendInput
Parameters Structure
pub struct EhlersITrendParams {
    pub warmup_bars: Option<usize>,   // Default: 12 (must be > 0)
    pub max_dc_period: Option<usize>, // Default: 50 (must be > 0)
}
Output Structure
pub struct EhlersITrendOutput {
    pub values: Vec<f64>, // iTrend values
}
Validation, Warmup & NaNs
  • warmup_bars > 0, max_dc_period > 0. Otherwise errors EhlersITrendError::InvalidWarmupBars / InvalidMaxDcPeriod.
  • Empty input → EhlersITrendError::EmptyInputData. All NaNs → EhlersITrendError::AllValuesNaN.
  • After the first finite value, must have at least warmup_bars points; else EhlersITrendError::NotEnoughDataForWarmup.
  • Indices before warmup (first + warmup_bars) are NaN. Streaming returns None until warmup completes.
  • ehlers_itrend_into_slice validates the destination length, else EhlersITrendError::InvalidOutputLen.
Error Handling
use vectorta::indicators::ehlers_itrend::{ehlers_itrend, EhlersITrendError};

match ehlers_itrend(&input) {
    Ok(output) => consume(output.values),
    Err(EhlersITrendError::EmptyInputData) => eprintln!("input is empty"),
    Err(EhlersITrendError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(EhlersITrendError::InvalidWarmupBars { warmup_bars }) =>
        eprintln!("invalid warmup_bars: {}", warmup_bars),
    Err(EhlersITrendError::InvalidMaxDcPeriod { max_dc }) =>
        eprintln!("invalid max_dc_period: {}", max_dc),
    Err(EhlersITrendError::NotEnoughDataForWarmup { warmup_bars, length }) =>
        eprintln!("need {} bars after first finite, only {}", warmup_bars, length),
    Err(EhlersITrendError::InvalidOutputLen { expected, got }) =>
        eprintln!("dst length mismatch: expected {}, got {}", expected, got),
    Err(e) => eprintln!("ehlers_itrend error: {}", e),
}

Python Bindings

Basic Usage

Calculate iTrend with NumPy arrays:

import numpy as np
from vectorta import ehlers_itrend

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

# Defaults: warmup_bars=12, max_dc_period=50
result = ehlers_itrend(prices, warmup_bars=12, max_dc_period=50)

# Specify kernel ("auto", "scalar", "avx2", etc.)
result = ehlers_itrend(prices, warmup_bars=12, max_dc_period=50, kernel="avx2")

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

Use the Python stream wrapper:

from vectorta import EhlersITrendStream

stream = EhlersITrendStream(warmup_bars=12, max_dc_period=50)
for price in price_feed:
    value = stream.update(price)
    if value is not None:
        handle(value)
Batch Parameter Optimization

Test multiple parameter combinations efficiently:

import numpy as np
from vectorta import ehlers_itrend_batch

prices = np.array([...], dtype=float)

# (start, end, step)
res = ehlers_itrend_batch(
    prices,
    warmup_range=(8, 16, 2),
    max_dc_range=(30, 60, 10),
    kernel="auto",
)

# res is a dict-like object with:
# - values: 2D matrix [rows x len(prices)]
# - warmups: warmup_bars for each row
# - max_dcs: max_dc_period for each row
print(res.keys())
CUDA Acceleration

CUDA APIs are available for iTrend in this project:

# GPU batch over parameter ranges (f32 path)
from vectorta import ehlers_itrend_cuda_batch_dev

dev_out = ehlers_itrend_cuda_batch_dev(
    data_f32=prices.astype('float32'),
    warmup_range=(12, 12, 0),
    max_dc_range=(50, 50, 0),
    device_id=0,
)

# Many series with one parameter set (time-major input)
from vectorta import ehlers_itrend_cuda_many_series_one_param_dev

tm = portfolio_data.astype('float32')  # shape [T, N]
dev_out = ehlers_itrend_cuda_many_series_one_param_dev(
    data_tm_f32=tm,
    warmup_bars=12,
    max_dc_period=50,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Calculate iTrend in JavaScript/TypeScript:

import { ehlers_itrend_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);

// warmup_bars=12, max_dc_period=50
const result = ehlers_itrend_js(prices, 12, 50);
console.log('iTrend:', result);
Memory-Efficient Operations

Use zero-copy into/alloc functions for large arrays:

import { ehlers_itrend_alloc, ehlers_itrend_free, ehlers_itrend_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([...]);
const len = prices.length;
const inPtr = ehlers_itrend_alloc(len);
const outPtr = ehlers_itrend_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(prices);
// Args: in_ptr, out_ptr, len, warmup_bars, max_dc_period
ehlers_itrend_into(inPtr, outPtr, len, 12, 50);

const itrend = new Float64Array(memory.buffer, outPtr, len).slice();
ehlers_itrend_free(inPtr, len);
ehlers_itrend_free(outPtr, len);
Batch Processing

Sweep parameter ranges via a single API:

import { ehlers_itrend_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const config = {
  warmup_range: [8, 16, 2],     // 8, 10, 12, 14, 16
  max_dc_range: [30, 60, 10],   // 30, 40, 50, 60
};

const out = ehlers_itrend_batch(prices, config);
// out = { values: Float64Array (rows*len), combos: [...], rows: number, cols: number }

// Reshape values by rows
const matrix: number[][] = [];
for (let r = 0; r < out.rows; r++) {
  const start = r * out.cols;
  matrix.push(Array.from(out.values.slice(start, start + out.cols)));
}
console.log(out.combos, matrix.length, out.cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators