Ehlers Kaufman Adaptive Moving Average (Ehlers KAMA)

Parameters: period = 20

Overview

The Ehlers Kaufman Adaptive Moving Average combines Perry Kaufman's efficiency ratio concept with John Ehlers' smoothing constants to create an adaptive filter that automatically adjusts to market conditions. The indicator calculates an Efficiency Ratio (ER) by comparing the net price change over a period to the sum of absolute price changes. This ratio measures how efficiently price moves from point A to point B, with values near 1 indicating strong directional movement and values near 0 suggesting choppy, inefficient action. Ehlers' modification applies specific smoothing constants that optimize the balance between responsiveness and stability, creating a more refined adaptation mechanism than the original KAMA.

When markets trend efficiently with high ER values, Ehlers KAMA reduces its smoothing to track price closely and capture the move. During choppy periods with low ER values, the indicator increases smoothing dramatically to filter out whipsaws and false signals. This automatic adjustment happens continuously, with the smoothing factor recalculated at each bar based on current market efficiency. The 20 period default provides a good balance for measuring efficiency across different timeframes. The Ehlers constants ensure smoother transitions between trending and ranging states compared to the original KAMA, reducing the indicator's tendency to overshoot during regime changes.

Traders use Ehlers KAMA as an intelligent trend following tool that adapts to market character without manual intervention. The indicator excels at staying with trends while avoiding whipsaws during consolidations, making it ideal for systematic trading strategies. Crossovers between price and Ehlers KAMA generate trading signals with an excellent risk reward profile, as the adaptive smoothing helps filter out low probability setups. Many traders combine Ehlers KAMA with momentum oscillators, entering positions when both align. The indicator also serves as an adaptive trailing stop, maintaining appropriate distance from price based on current market efficiency rather than fixed parameters.

Implementation Examples

Get started with Ehlers KAMA in a few lines:

use vectorta::indicators::{ehlers_kama, EhlersKamaInput, EhlersKamaParams};
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 = EhlersKamaParams { period: Some(20) };
let input = EhlersKamaInput::from_slice(&prices, params);
let result = ehlers_kama(&input)?;

// Using with Candles (defaults: source="close", period=20)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EhlersKamaInput::with_default_candles(&candles);
let result = ehlers_kama(&input)?;

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

API Reference

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

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

// From candles with defaults (close, period=20)
EhlersKamaInput::with_default_candles(&Candles) -> EhlersKamaInput
Parameters Structure
pub struct EhlersKamaParams {
    pub period: Option<usize>, // Default: 20
}
Output Structure
pub struct EhlersKamaOutput {
    pub values: Vec<f64>, // Adaptive moving average values
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len(data); otherwise EhlersKamaError::InvalidPeriod.
  • Requires at least period valid points after the first finite value; else EhlersKamaError::NotEnoughValidData.
  • Leading NaNs are skipped; indices before first + period - 1 are NaN in output (warmup prefix).
  • Initial seed uses the previous price (Pine-style). Smoothing constant: s = (0.6667·EF + 0.0645)^2.
  • Streaming returns None until period samples are received; then one value per update.
Error Handling
use vectorta::indicators::{ehlers_kama, EhlersKamaInput, EhlersKamaParams};
use vectorta::indicators::moving_averages::ehlers_kama::EhlersKamaError;

match ehlers_kama(&input) {
    Ok(output) => process(output.values),
    Err(EhlersKamaError::EmptyInputData) => eprintln!("input slice is empty"),
    Err(EhlersKamaError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(EhlersKamaError::InvalidPeriod { period, data_len }) =>
        eprintln!("invalid period: {} (len={})", period, data_len),
    Err(EhlersKamaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("need {} valid points, got {}", needed, valid),
}

Python Bindings

Basic Usage

Calculate Ehlers KAMA using NumPy arrays (default period=20):

import numpy as np
from vectorta import ehlers_kama

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

# Default period=20
vals = ehlers_kama(prices, period=20)

# Specify kernel for performance ("auto", "avx2", "avx512", "scalar")
vals = ehlers_kama(prices, period=20, kernel="auto")

print(vals)
Streaming Real-time Updates
from vectorta import EhlersKamaStream

stream = EhlersKamaStream(period=20)
for price in price_feed:
    v = stream.update(price)
    if v is not None:
        print(v)
Batch Parameter Optimization
import numpy as np
from vectorta import ehlers_kama_batch

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

# period_range = (start, end, step)
res = ehlers_kama_batch(
    prices,
    period_range=(10, 50, 5),
    kernel="auto"
)

values = res["values"]     # shape: [num_periods, len(prices)]
periods = res["periods"]   # list of periods used
CUDA Acceleration

CUDA-enabled Python APIs (when built with CUDA support):

from vectorta import (
    ehlers_kama_cuda_batch_dev,
    ehlers_kama_cuda_many_series_one_param_dev,
)
import numpy as np

# One series, many parameters (batch sweep on GPU)
prices = np.array([...], dtype=np.float32)
device_id = 0
gpu_result = ehlers_kama_cuda_batch_dev(
    data_f32=prices,
    period_range=(10, 50, 1),
    device_id=device_id,
)

# Many series, one parameter (time-major [T, N])
portfolio = np.array([...], dtype=np.float32)  # shape [T, N]
gpu_result2 = ehlers_kama_cuda_many_series_one_param_dev(
    data_tm_f32=portfolio,
    period=20,
    device_id=device_id,
)

JavaScript/WASM Bindings

Basic Usage

Calculate Ehlers KAMA with WASM:

import { ehlers_kama_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const values = ehlers_kama_js(prices, 20);
console.log('KAMA values:', values);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { ehlers_kama_alloc, ehlers_kama_free, ehlers_kama_into, memory } from 'vectorta-wasm';

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

const inPtr = ehlers_kama_alloc(n);
const outPtr = ehlers_kama_alloc(n);

// Copy input into WASM memory
new Float64Array(memory.buffer, inPtr, n).set(prices);

// Args: in_ptr, out_ptr, len, period
ehlers_kama_into(inPtr, outPtr, n, 20);

const out = new Float64Array(memory.buffer, outPtr, n).slice();

ehlers_kama_free(inPtr, n);
ehlers_kama_free(outPtr, n);

console.log('KAMA values:', out);
Batch Processing

Sweep multiple period values:

import { ehlers_kama_batch } from 'vectorta-wasm';

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

// Unified batch API returns values, combos, rows, cols
const cfg = { period_range: [10, 50, 5] };
const res = ehlers_kama_batch(prices, cfg);

console.log(res.rows, res.cols);
console.log(res.combos);      // [{ period: 10 }, { period: 15 }, ...]
console.log(res.values);      // flat array: rows * cols

// Reshape if needed
const matrix = [] as number[][];
for (let i = 0; i < res.rows; i++) {
  const start = i * res.cols;
  matrix.push(res.values.slice(start, start + res.cols));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

CUDA note

In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.

Related Indicators