Ehlers Undersampled Double Moving Average
fast_length = 6 | slow_length = 12 | sample_length = 5 Overview
Ehlers Undersampled Double Moving Average is a two-line smoother built around a simple idea: do not let every incoming bar immediately change the filter state. Instead, the indicator samples the source only on the chosen cadence, holds that sampled value between sample points, and then feeds the held stream into two Hann-weighted smoothing filters. The result is a fast line and a slow line that can behave more like a deliberate stepwise signal pair than a continuously updated moving-average crossover.
That undersampling stage matters as much as the two smoothing lengths. Shortening the sample cadence makes the lines react more often, while lengthening it causes each accepted sample to persist for more bars before the next update. In candle mode the implementation defaults to `hlcc4`, so the pair is naturally centered on a blended price rather than a raw close.
Defaults: Ehlers Undersampled Double Moving Average uses `fast_length = 6`, `slow_length = 12`, `sample_length = 5`, and defaults candle input to `hlcc4`.
Implementation Examples
Compute the fast and slow lines from a raw slice or from candle `hlcc4` data.
use vector_ta::indicators::moving_averages::ehlers_undersampled_double_moving_average::{
ehlers_undersampled_double_moving_average,
EhlersUndersampledDoubleMovingAverageInput,
EhlersUndersampledDoubleMovingAverageParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let values = vec![100.0, 100.7, 101.2, 100.9, 101.8, 102.1, 101.6, 102.4];
let output = ehlers_undersampled_double_moving_average(
&EhlersUndersampledDoubleMovingAverageInput::from_slice(
&values,
EhlersUndersampledDoubleMovingAverageParams {
fast_length: Some(6),
slow_length: Some(12),
sample_length: Some(5),
},
)
)?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = ehlers_undersampled_double_moving_average(
&EhlersUndersampledDoubleMovingAverageInput::with_default_candles(&candles)
)?;
println!("fast = {:?}", output.fast.last());
println!("slow = {:?}", candle_output.slow.last()); API Reference
Input Methods ▼
// From candles and a named source field
EhlersUndersampledDoubleMovingAverageInput::from_candles(
&Candles,
&str,
EhlersUndersampledDoubleMovingAverageParams,
) -> EhlersUndersampledDoubleMovingAverageInput
// From a raw slice
EhlersUndersampledDoubleMovingAverageInput::from_slice(
&[f64],
EhlersUndersampledDoubleMovingAverageParams,
) -> EhlersUndersampledDoubleMovingAverageInput
// From candles with default parameters
EhlersUndersampledDoubleMovingAverageInput::with_default_candles(&Candles)
-> EhlersUndersampledDoubleMovingAverageInput Parameters Structure ▼
pub struct EhlersUndersampledDoubleMovingAverageParams {
pub fast_length: Option<usize>, // default 6
pub slow_length: Option<usize>, // default 12
pub sample_length: Option<usize>, // default 5
} Output Structure ▼
pub struct EhlersUndersampledDoubleMovingAverageOutput {
pub fast: Vec<f64>,
pub slow: Vec<f64>,
} Validation, Warmup & NaNs ▼
- The input slice must be non-empty and contain at least one finite value or the function returns
EmptyInputDataorAllValuesNaN. fast_length,slow_length, andsample_lengthmust each be in1..=4096.- Batch range definitions validate the same bounds and reject invalid integer sweeps for each axis.
- Single-run output is NaN-prefixed only through the first valid source index; once finite input begins, the filters start emitting values immediately.
- Streaming returns
Noneuntil the first finite update arrives, then returnsSome((fast, slow))on each later call. - Destination slices for in-place APIs must match the input length exactly.
- Batch mode rejects unsupported kernels through
InvalidKernelForBatch.
Builder, Streaming & Batch APIs ▼
// Builder
EhlersUndersampledDoubleMovingAverageBuilder::new()
.fast_length(usize)
.slow_length(usize)
.sample_length(usize)
.kernel(Kernel)
.apply_slice(&[f64])
EhlersUndersampledDoubleMovingAverageBuilder::new()
.apply(&Candles)
EhlersUndersampledDoubleMovingAverageBuilder::new()
.into_stream()
// Stream
EhlersUndersampledDoubleMovingAverageStream::try_new(
EhlersUndersampledDoubleMovingAverageParams
)
EhlersUndersampledDoubleMovingAverageStream::update(f64)
-> Option<(f64, f64)>
EhlersUndersampledDoubleMovingAverageStream::reset()
// Batch
EhlersUndersampledDoubleMovingAverageBatchBuilder::new()
.fast_length_range(start, end, step)
.slow_length_range(start, end, step)
.sample_length_range(start, end, step)
.kernel(Kernel)
.apply_slice(&[f64])
EhlersUndersampledDoubleMovingAverageBatchBuilder::new()
.apply_candles(&Candles)
.apply_candles_source(&Candles, &str) Error Handling ▼
pub enum EhlersUndersampledDoubleMovingAverageError {
EmptyInputData,
AllValuesNaN,
InvalidFastLength { fast_length: usize },
InvalidSlowLength { slow_length: usize },
InvalidSampleLength { sample_length: usize },
OutputLengthMismatch { expected: usize, got: usize },
InvalidFastLengthRange { start: usize, end: usize, step: usize },
InvalidSlowLengthRange { start: usize, end: usize, step: usize },
InvalidSampleLengthRange { start: usize, end: usize, step: usize },
InvalidKernelForBatch(Kernel),
} Python Bindings
Python exposes a tuple-returning single-run function, a streaming class, and a batch function. The single-run binding returns separate NumPy arrays for the fast and slow lines. Batch returns both output matrices plus the tested fast, slow, and sample-length grids.
import numpy as np
from vector_ta import (
ehlers_undersampled_double_moving_average,
ehlers_undersampled_double_moving_average_batch,
EhlersUndersampledDoubleMovingAverageStream,
)
data = np.asarray(close_values, dtype=np.float64)
fast, slow = ehlers_undersampled_double_moving_average(
data,
fast_length=6,
slow_length=12,
sample_length=5,
kernel="auto",
)
stream = EhlersUndersampledDoubleMovingAverageStream(6, 12, 5)
print(stream.update(data[-1]))
batch = ehlers_undersampled_double_moving_average_batch(
data,
fast_length_range=(4, 8, 2),
slow_length_range=(10, 16, 2),
sample_length_range=(3, 5, 1),
kernel="auto",
)
print(batch["fast_values"].shape, batch["slow_values"].shape)
print(batch["fast_lengths"], batch["slow_lengths"], batch["sample_lengths"])
print(batch["rows"], batch["cols"]) JavaScript/WASM Bindings
The WASM layer exposes an object-returning single-run wrapper, an object-returning batch wrapper, and lower-level allocation and in-place exports. The standard JavaScript path returns one object containing separate fast and slow arrays, while batch returns flattened matrices, tested parameter combos, and the rows and cols shape.
import init, {
ehlers_undersampled_double_moving_average_js,
ehlers_undersampled_double_moving_average_batch_js,
} from "/pkg/vector_ta.js";
await init();
const data = new Float64Array(closeValues);
const lines = ehlers_undersampled_double_moving_average_js(data, 6, 12, 5);
console.log(lines.fast, lines.slow);
const batch = ehlers_undersampled_double_moving_average_batch_js(data, {
fast_length_range: [4, 8, 2],
slow_length_range: [10, 16, 2],
sample_length_range: [3, 5, 1],
});
console.log(batch.fast_values, batch.slow_values, batch.combos, batch.rows, batch.cols); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Buff Averages
Technical analysis indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
High-Low Correlation
Technical analysis indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator