Ehlers FM Demodulator

Parameters: period = 30

Overview

Ehlers FM Demodulator works from the intrabar difference between close and open rather than from a single source price alone. The raw open-close move is first scaled and clamped, then passed through a recursive filter governed by the chosen period. The result is a smoothed oscillatory waveform intended to behave more like a demodulated cycle component than a classic trend-following line.

Because the initial step clamps the scaled open-close move into a fixed range, very large single-bar differences do not dominate the output indefinitely. That makes the indicator more focused on recurrent waveform shape than on raw magnitude spikes. The output is a single continuous series, so this page stays closer to a filtered signal tool than to a multi-line confirmation study.

Defaults: Ehlers FM Demodulator uses `period = 30` and reads candle `open` plus `close`.

Implementation Examples

Compute the demodulated waveform from open/close slices or from candle open/close fields.

use vector_ta::indicators::ehlers_fm_demodulator::{
    ehlers_fm_demodulator,
    EhlersFmDemodulatorInput,
    EhlersFmDemodulatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = ehlers_fm_demodulator(&EhlersFmDemodulatorInput::from_slices(
    &open,
    &close,
    EhlersFmDemodulatorParams { period: Some(30) },
))?;

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

println!("value = {:?}", output.values.last());
println!("series length = {}", candle_output.values.len());

API Reference

Input Methods
// From candles using named open and close sources
EhlersFmDemodulatorInput::from_candles(
    &Candles,
    &str,
    &str,
    EhlersFmDemodulatorParams,
) -> EhlersFmDemodulatorInput

// From open/close slices
EhlersFmDemodulatorInput::from_slices(
    &[f64],
    &[f64],
    EhlersFmDemodulatorParams,
) -> EhlersFmDemodulatorInput

// From candles with default parameters
EhlersFmDemodulatorInput::with_default_candles(&Candles)
    -> EhlersFmDemodulatorInput
Parameters Structure
pub struct EhlersFmDemodulatorParams {
    pub period: Option<usize>, // default 30
}
Output Structure
pub struct EhlersFmDemodulatorOutput {
    pub values: Vec<f64>,
}
Validation, Warmup & NaNs
  • Open and close inputs must both be non-empty and have identical lengths.
  • period must be greater than zero and no larger than the input length.
  • The required valid run after the first finite open/close pair is period - 2 bars, with a minimum of one valid bar.
  • The standard output is NaN-prefixed through first_valid + period - 3.
  • Streaming resets on any non-finite open or close input and returns None until warmup is rebuilt.
  • The raw open-close derivative is internally scaled and clamped, so very large single-bar differences saturate before recursive smoothing.
  • Batch mode rejects invalid integer ranges and unsupported kernels.
Builder, Streaming & Batch APIs
// Builder
EhlersFmDemodulatorBuilder::new()
    .period(usize)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64])

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

EhlersFmDemodulatorBuilder::new()
    .into_stream()

// Stream
EhlersFmDemodulatorStream::try_new(EhlersFmDemodulatorParams)
EhlersFmDemodulatorStream::update(open: f64, close: f64) -> Option<f64>
EhlersFmDemodulatorStream::reset()
EhlersFmDemodulatorStream::period() -> usize

// Batch
EhlersFmDemodulatorBatchBuilder::new()
    .period_range(start, end, step)
    .period_static(usize)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64])

EhlersFmDemodulatorBatchBuilder::new()
    .apply_candles(&Candles)
Error Handling
pub enum EhlersFmDemodulatorError {
    EmptyInputData,
    DataLengthMismatch { open_len: usize, close_len: usize },
    AllValuesNaN,
    InvalidPeriod { period: usize, data_len: usize },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidKernelForBatch(Kernel),
    InvalidRange { start: usize, end: usize, step: usize },
}

Python Bindings

Python exposes an array-returning single-run function, a streaming class, and a batch function. The single-run binding returns one NumPy array of demodulated values. Batch returns the values matrix plus the tested periods.

import numpy as np
from vector_ta import (
    ehlers_fm_demodulator,
    ehlers_fm_demodulator_batch,
    EhlersFmDemodulatorStream,
)

open_ = np.asarray(open_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)

values = ehlers_fm_demodulator(
    open_,
    close,
    period=30,
    kernel="auto",
)

stream = EhlersFmDemodulatorStream(30)
print(stream.update(open_[-1], close[-1]))

batch = ehlers_fm_demodulator_batch(
    open_,
    close,
    period_range=(20, 40, 10),
    kernel="auto",
)

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

JavaScript/WASM Bindings

The WASM layer exposes a single-run array-returning helper, a unified batch helper, and pointer-oriented in-place exports. The batch export is published to JavaScript as ehlers_fm_demodulator_batch even though the underlying Rust function name is ehlers_fm_demodulator_batch_unified_js.

import init, {
  ehlers_fm_demodulator_js,
  ehlers_fm_demodulator_batch,
} from "/pkg/vector_ta.js";

await init();

const open_ = new Float64Array(openValues);
const close = new Float64Array(closeValues);

const values = ehlers_fm_demodulator_js(open_, close, 30);
console.log(values);

const batch = ehlers_fm_demodulator_batch(open_, close, {
  period_range: [20, 40, 10],
});

console.log(batch.values, batch.periods, 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