Ehlers Smoothed Adaptive Momentum

Parameters: alpha = 0.07 (0–1) | cutoff = 8

Overview

Ehlers Smoothed Adaptive Momentum is a one-line momentum study that adjusts its effective lookback dynamically instead of staying locked to a single static period. Internally it smooths the source, tracks adaptive phase and period behavior, and then feeds the result through a recursive filter controlled by the cutoff parameter. The output is therefore a momentum line whose responsiveness changes with the evolving cycle structure of the series.

The tradeoff is warmup depth. This implementation intentionally withholds meaningful output for a long initial window because the adaptive lookback logic needs substantial history before it stabilizes. That makes it a better fit for longer continuous runs of data than for very short excerpts. Candle mode defaults to `hl2`, but the builder and batch APIs can target other candle sources when needed.

Defaults: Ehlers Smoothed Adaptive Momentum uses `alpha = 0.07`, `cutoff = 8.0`, and defaults candle input to `hl2`.

Implementation Examples

Compute the adaptive momentum line from a raw slice or from candle `hl2` data.

use vector_ta::indicators::ehlers_smoothed_adaptive_momentum::{
    ehlers_smoothed_adaptive_momentum,
    EhlersSmoothedAdaptiveMomentumInput,
    EhlersSmoothedAdaptiveMomentumParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = ehlers_smoothed_adaptive_momentum(
    &EhlersSmoothedAdaptiveMomentumInput::from_slice(
        &values,
        EhlersSmoothedAdaptiveMomentumParams {
            alpha: Some(0.07),
            cutoff: Some(8.0),
        },
    )
)?;

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

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

API Reference

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

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

// From candles with default parameters
EhlersSmoothedAdaptiveMomentumInput::with_default_candles(&Candles)
    -> EhlersSmoothedAdaptiveMomentumInput
Parameters Structure
pub struct EhlersSmoothedAdaptiveMomentumParams {
    pub alpha: Option<f64>,  // default 0.07
    pub cutoff: Option<f64>, // default 8.0
}
Output Structure
pub struct EhlersSmoothedAdaptiveMomentumOutput {
    pub values: Vec<f64>,
}
Validation, Warmup & NaNs
  • The input slice must be non-empty and contain at least one finite value.
  • alpha must be finite and valid for the internal filter.
  • cutoff must be finite and strictly greater than zero.
  • The implementation requires at least 76 consecutive valid samples from the first finite value onward.
  • The standard output is NaN-prefixed through first_valid + 75.
  • Streaming returns a single floating-point value on every call, but it remains NaN until the long adaptive warmup is complete.
  • Batch mode enforces the same long-history requirement and sweeps both alpha and cutoff combinations.
Builder, Streaming & Batch APIs
// Builder
EhlersSmoothedAdaptiveMomentumBuilder::new()
    .source(&'static str)
    .alpha(f64)
    .cutoff(f64)
    .kernel(Kernel)
    .apply_slice(&[f64])

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

EhlersSmoothedAdaptiveMomentumBuilder::new()
    .into_stream()

// Stream
EhlersSmoothedAdaptiveMomentumStream::try_new(
    EhlersSmoothedAdaptiveMomentumParams
)
EhlersSmoothedAdaptiveMomentumStream::update(f64) -> f64

// Batch
EhlersSmoothedAdaptiveMomentumBatchBuilder::new()
    .source(&'static str)
    .alpha_range((f64, f64, f64))
    .cutoff_range((f64, f64, f64))
    .kernel(Kernel)
    .apply_slice(&[f64])

EhlersSmoothedAdaptiveMomentumBatchBuilder::new()
    .apply(&Candles)
Error Handling
pub enum EhlersSmoothedAdaptiveMomentumError {
    EmptyInputData,
    AllValuesNaN,
    InvalidAlpha { alpha: f64 },
    InvalidCutoff { cutoff: f64 },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

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 adaptive momentum values. Batch returns the values matrix plus the tested alpha and cutoff grids.

import numpy as np
from vector_ta import (
    ehlers_smoothed_adaptive_momentum,
    ehlers_smoothed_adaptive_momentum_batch,
    EhlersSmoothedAdaptiveMomentumStream,
)

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

values_out = ehlers_smoothed_adaptive_momentum(
    data,
    alpha=0.07,
    cutoff=8.0,
    kernel="auto",
)

stream = EhlersSmoothedAdaptiveMomentumStream(alpha=0.07, cutoff=8.0)
print(stream.update(data[-1]))

batch = ehlers_smoothed_adaptive_momentum_batch(
    data,
    alpha_range=(0.07, 0.14, 0.07),
    cutoff_range=(8.0, 10.0, 2.0),
    kernel="auto",
)

print(batch["alphas"], batch["cutoffs"], batch["rows"], batch["cols"])

JavaScript/WASM Bindings

The WASM layer exposes a single-run array-returning helper, a batch wrapper, and pointer-oriented in-place exports. The batch helper returns the flattened values matrix together with alpha and cutoff metadata.

import init, {
  ehlers_smoothed_adaptive_momentum_js,
  ehlers_smoothed_adaptive_momentum_batch_js,
} from "/pkg/vector_ta.js";

await init();

const data = new Float64Array(values);

const valuesOut = ehlers_smoothed_adaptive_momentum_js(data, 0.07, 8.0);
console.log(valuesOut);

const batch = ehlers_smoothed_adaptive_momentum_batch_js(data, {
  alpha_range: [0.07, 0.14, 0.07],
  cutoff_range: [8.0, 10.0, 2.0],
});

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