Ehlers Detrending Filter

Parameters: length = 10

Overview

Ehlers Detrending Filter is designed to strip out slower trend content so the remaining oscillatory structure is easier to inspect. The implementation applies an Ehlers-style detrending stage first, then smooths that detrended series with cosine-weighted averaging. The main edf output is therefore not the raw difference from price, but a filtered residual that emphasizes shorter swings once the broader trend is removed.

The second output is a discrete slope-state signal derived from the filtered line. Rising states are encoded as 1 or 2, falling states as -1 or -2, and flat transitions as 0. That gives the page both a continuous detrended waveform and a compact categorical read on whether that waveform is accelerating or decelerating. Candle mode defaults to `hlcc4`, but the builder can point the filter at any supported candle source.

Defaults: Ehlers Detrending Filter uses `length = 10` and defaults candle input to `hlcc4`.

Implementation Examples

Compute the detrended filter line and its discrete slope signal from a slice or a candle source.

use vector_ta::indicators::ehlers_detrending_filter::{
    ehlers_detrending_filter,
    EhlersDetrendingFilterInput,
    EhlersDetrendingFilterParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = ehlers_detrending_filter(&EhlersDetrendingFilterInput::from_slice(
    &values,
    EhlersDetrendingFilterParams { length: Some(10) },
))?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = ehlers_detrending_filter(
    &EhlersDetrendingFilterInput::with_default_candles(&candles)
)?;

println!("edf = {:?}", output.edf.last());
println!("signal = {:?}", candle_output.signal.last());

API Reference

Input Methods
// From candles and a named source field
EhlersDetrendingFilterInput::from_candles(
    &Candles,
    &str,
    EhlersDetrendingFilterParams,
) -> EhlersDetrendingFilterInput

// From a raw slice
EhlersDetrendingFilterInput::from_slice(&[f64], EhlersDetrendingFilterParams)
    -> EhlersDetrendingFilterInput

// From candles with default parameters
EhlersDetrendingFilterInput::with_default_candles(&Candles)
    -> EhlersDetrendingFilterInput
Parameters Structure
pub struct EhlersDetrendingFilterParams {
    pub length: Option<usize>, // default 10
}
Output Structure
pub struct EhlersDetrendingFilterOutput {
    pub edf: Vec<f64>,
    pub signal: Vec<f64>,
}
Validation, Warmup & NaNs
  • The input slice must be non-empty and contain at least one finite source value.
  • length must be greater than zero and no larger than the input length.
  • The implementation requires at least length consecutive finite values from the first valid source point onward.
  • Warmup lasts through first_valid + length - 1 in the standard output path.
  • Streaming resets on non-finite input and returns None until the full weighted window is rebuilt.
  • The signal line is a categorical slope-state read, not a second smoothed waveform.
  • Batch mode sweeps only length and rejects unsupported kernels.
Builder, Streaming & Batch APIs
// Builder
EhlersDetrendingFilterBuilder::new()
    .length(usize)
    .source(String)
    .kernel(Kernel)
    .apply_slice(&[f64])

EhlersDetrendingFilterBuilder::new()
    .apply(&Candles)

EhlersDetrendingFilterBuilder::new()
    .into_stream()

// Stream
EhlersDetrendingFilterStream::try_new(EhlersDetrendingFilterParams)
EhlersDetrendingFilterStream::update(f64) -> Option<(f64, f64)>
EhlersDetrendingFilterStream::reset()

// Batch
EhlersDetrendingFilterBatchBuilder::new()
    .length_range(start, end, step)
    .length_static(usize)
    .kernel(Kernel)
    .apply_slice(&[f64])

EhlersDetrendingFilterBatchBuilder::new()
    .apply_candles(&Candles, &str)
Error Handling
pub enum EhlersDetrendingFilterError {
    EmptyInputData,
    AllValuesNaN,
    InvalidLength { length: usize, data_len: usize },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

Python Bindings

Python exposes a two-array single-run function, a streaming class, and a batch function. The single-run binding returns the detrended filter array together with the signal array. Batch returns both matrices plus the tested lengths and the rows and cols shape.

import numpy as np
from vector_ta import (
    ehlers_detrending_filter,
    ehlers_detrending_filter_batch,
    EhlersDetrendingFilterStream,
)

data = np.asarray(values, dtype=np.float64)

edf, signal = ehlers_detrending_filter(
    data,
    length=10,
    kernel="auto",
)

stream = EhlersDetrendingFilterStream(length=10)
print(stream.update(data[-1]))

batch = ehlers_detrending_filter_batch(
    data,
    length_range=(10, 20, 5),
    kernel="auto",
)

print(batch["lengths"], batch["rows"], batch["cols"])

JavaScript/WASM Bindings

The WASM layer exposes an object-returning single-run wrapper, a batch wrapper, and pointer-oriented in-place helpers. The single-run helper returns named edf and signal arrays, while the batch helper adds combos, rows, and cols.

import init, {
  ehlers_detrending_filter_js,
  ehlers_detrending_filter_batch_js,
} from "/pkg/vector_ta.js";

await init();

const data = new Float64Array(values);

const single = ehlers_detrending_filter_js(data, 10);
console.log(single.edf, single.signal);

const batch = ehlers_detrending_filter_batch_js(data, {
  length_range: [10, 20, 5],
});

console.log(batch.edf, batch.signal, batch.rows, batch.cols);

CUDA Bindings (Rust)

Additional details for the CUDA bindings can be found inside the VectorTA repository.

Performance Analysis

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators