Empirical Mode Decomposition (EMD) Bands
period = 20 | delta = 0.5 | fraction = 0.1 Overview
Empirical Mode Decomposition bands isolate intrinsic price cycles from market noise by applying adaptive signal processing to the midpoint between highs and lows. The indicator filters price data through a resonant band pass filter, then extracts natural oscillation modes to construct upper, middle, and lower trading bands based on the detected cyclical patterns. When price breaks above the upper band, it signals strong momentum in the current cycle, while drops below the lower band indicate cycle exhaustion or reversal. Traders leverage EMD bands for timing entries at cycle troughs and exits at peaks, particularly in ranging markets where traditional moving averages lag. Unlike fixed period indicators, EMD bands adapt their sensitivity to the dominant frequency in recent price action, making them especially valuable for detecting regime changes between trending and cycling market conditions.
Implementation Examples
Compute EMD bands from candles or slices:
use vectorta::indicators::emd::{emd, EmdInput, EmdParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From Candles with defaults (period=20, delta=0.5, fraction=0.1)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EmdInput::with_default_candles(&candles);
let out = emd(&input)?;
println!("upper={} middle={} lower={}", out.upperband[100], out.middleband[100], out.lowerband[100]);
// From slices (only high/low are used; close/volume can be passthrough data)
let (high, low, close, volume) = (vec![..], vec![..], vec![..], vec![..]);
let params = EmdParams { period: Some(20), delta: Some(0.5), fraction: Some(0.1) };
let input = EmdInput::from_slices(&high, &low, &close, &volume, params);
let out = emd(&input)?; API Reference
Input Methods ▼
// From candles (uses high/low; close/volume present but unused in compute)
EmdInput::from_candles(&Candles, EmdParams) -> EmdInput
// From explicit slices
EmdInput::from_slices(&[f64], &[f64], &[f64], &[f64], EmdParams) -> EmdInput
// Defaults (period=20, delta=0.5, fraction=0.1)
EmdInput::with_default_candles(&Candles) -> EmdInput Parameters Structure ▼
pub struct EmdParams {
pub period: Option<usize>, // Default: 20
pub delta: Option<f64>, // Default: 0.5
pub fraction: Option<f64>, // Default: 0.1
} Output Structure ▼
pub struct EmdOutput {
pub upperband: Vec<f64>,
pub middleband: Vec<f64>,
pub lowerband: Vec<f64>,
} Validation, Warmup & NaNs ▼
- Requires
high.len() == low.len()and non‑empty input; otherwiseEmdError::InvalidInputLength. period > 0andperiod ≤ len; elseEmdError::InvalidPeriod.- Needs at least
max(2×period, 50)valid points after first finite; elseEmdError::NotEnoughValidData. deltaandfractionmust be finite; otherwiseInvalidDelta/InvalidFraction.- Warmup:
upper/lowerfirst finite index +50 − 1;middlefirst finite index +2×period − 1. Earlier values areNaN. emd_into_sliceserrors if output slice lengths mismatch input length.
Error Handling ▼
#[derive(Debug, Error)]
pub enum EmdError {
AllValuesNaN,
InvalidPeriod { period: usize, data_len: usize },
NotEnoughValidData { needed: usize, valid: usize },
InvalidDelta { delta: f64 },
InvalidFraction { fraction: f64 },
InvalidInputLength { expected: usize, actual: usize },
}
// Example handling
match emd(&input) {
Ok(out) => use_bands(out),
Err(EmdError::NotEnoughValidData { needed, valid }) => eprintln!("need {needed}, have {valid}"),
Err(e) => eprintln!("emd error: {e}"),
} Python Bindings
Basic Usage ▾
Compute bands from NumPy arrays:
import numpy as np
from vectorta import emd
high = np.asarray([...], dtype=np.float64)
low = np.asarray([...], dtype=np.float64)
upper, middle, lower = emd(high, low, period=20, delta=0.5, fraction=0.1, kernel="auto")
print(upper.shape, middle.shape, lower.shape) Streaming Updates ▾
from vectorta import EmdStream
stream = EmdStream(period=20, delta=0.5, fraction=0.1)
for (h, l) in high_low_feed():
ub, mb, lb = stream.update(h, l)
if ub is not None and mb is not None and lb is not None:
handle(ub, mb, lb) Batch Sweeps ▾
Evaluate multiple period/phase/fraction configurations:
from vectorta import emd_batch
high = np.asarray([...], dtype=np.float64)
low = np.asarray([...], dtype=np.float64)
result = emd_batch(
high, low,
period_range=(10, 30, 10),
delta_range=(0.3, 0.7, 0.2),
fraction_range=(0.08, 0.12, 0.02),
kernel="auto",
)
upper = result["upper"] # shape: [rows, len]
middle = result["middle"] # shape: [rows, len]
lower = result["lower"] # shape: [rows, len]
periods = result["periods"]
deltas = result["deltas"]
fractions = result["fractions"] CUDA Acceleration ▾
CUDA support for EMD is currently under development. The API will follow the same pattern as other CUDA‑enabled indicators.
# Coming soon: CUDA-accelerated EMD calculations
# from vectorta import emd_cuda_batch
# results = emd_cuda_batch(
# high=high, low=low,
# period_range=(10, 40, 1),
# delta_range=(0.3, 0.7, 0.05),
# fraction_range=(0.05, 0.20, 0.01),
# device_id=0,
# ) JavaScript/WASM Bindings
Basic Usage ▼
Calculate EMD bands in JavaScript/TypeScript:
import { emd_js } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
// close/volume arrays are accepted by the API but unused internally
const result = emd_js(high, low, new Float64Array(), new Float64Array(), 20, 0.5, 0.1);
// Result layout: { values: [upper..., middle..., lower...], rows: 3, cols: len }
const { values, rows, cols } = result as { values: Float64Array, rows: number, cols: number };
const upper = values.slice(0, cols);
const middle = values.slice(cols, 2*cols);
const lower = values.slice(2*cols); Memory‑Efficient Operations ▼
Use zero‑copy operations with pre‑allocated buffers:
import { emd_alloc, emd_free, emd_into, memory } from 'vectorta-wasm';
// Prepare inputs
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const len = high.length;
// Allocate input and output buffers
const hiPtr = emd_alloc(len);
const loPtr = emd_alloc(len);
const clPtr = emd_alloc(len); // unused by kernel but required by API
const voPtr = emd_alloc(len); // unused by kernel but required by API
const upPtr = emd_alloc(len);
const mdPtr = emd_alloc(len);
const lwPtr = emd_alloc(len);
// Copy inputs into WASM memory
new Float64Array(memory.buffer, hiPtr, len).set(high);
new Float64Array(memory.buffer, loPtr, len).set(low);
new Float64Array(memory.buffer, clPtr, len).fill(0);
new Float64Array(memory.buffer, voPtr, len).fill(0);
// Compute directly into WASM memory
emd_into(hiPtr, loPtr, clPtr, voPtr, upPtr, mdPtr, lwPtr, len, 20, 0.5, 0.1);
// Read back
const upper = new Float64Array(memory.buffer, upPtr, len).slice();
const middle = new Float64Array(memory.buffer, mdPtr, len).slice();
const lower = new Float64Array(memory.buffer, lwPtr, len).slice();
// Free buffers
for (const [ptr] of [[hiPtr],[loPtr],[clPtr],[voPtr],[upPtr],[mdPtr],[lwPtr]]) emd_free(ptr, len); Batch Processing ▼
Test multiple parameter combinations:
import { emd_batch as emd_batch_js } from 'vectorta-wasm';
const config = { period_range: [10, 30, 10], delta_range: [0.3, 0.7, 0.2], fraction_range: [0.08, 0.12, 0.02] };
const out = emd_batch_js(high, low, new Float64Array(), new Float64Array(), config);
// out: { upperband, middleband, lowerband, combos, rows, cols }
const { upperband, middleband, lowerband, rows, cols } = out;
const upperRow0 = upperband.slice(0, cols); // first combo bands
const middleRow0 = middleband.slice(0, cols);
const lowerRow0 = lowerband.slice(0, cols); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05