N-Order EMA

Parameters: period = 9 | order = 1 (1–64) | ema_style = ema | iir_style = impulse_matched

Overview

N-Order EMA implements a family of recursive moving averages built from an nth-order IIR core. The same engine can emit a standard EMA, a double EMA, a triple EMA, or a Hull-like EMA variant by composing one or more filtered stages. The coefficient generator is also configurable, so the response can be shaped with all-pole, impulse-matched, matched-z, or bilinear coefficients instead of a single fixed recursion.

For candle input, the default source is close. Slice input accepts a single data series. The output is one moving-average series with a style-dependent warmup prefix filled with NaN, and the streaming API resets when it encounters a non-finite input value.

Defaults: period = 9.0, order = 1, ema_style = "ema", iir_style = "impulse_matched", candle source = "close".

Implementation Examples

Compute the indicator from a single series or candle data:

use vector_ta::indicators::moving_averages::n_order_ema::{
    n_order_ema, NOrderEmaInput, NOrderEmaParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let data = vec![100.0, 101.0, 102.5, 101.75, 103.0, 104.2, 105.1];

let input = NOrderEmaInput::from_slice(
    &data,
    NOrderEmaParams {
        period: Some(9.0),
        order: Some(2),
        ema_style: Some("tema".to_string()),
        iir_style: Some("matched_z".to_string()),
    },
);
let out = n_order_ema(&input)?;
println!("{:?}", out.values);

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let default_input = NOrderEmaInput::with_default_candles(&candles); // source = close
let default_out = n_order_ema(&default_input)?;

API Reference

Input Methods v
NOrderEmaInput::from_slice(&[f64], NOrderEmaParams) -> NOrderEmaInput
NOrderEmaInput::from_candles(&Candles, "source", NOrderEmaParams) -> NOrderEmaInput
NOrderEmaInput::with_default_candles(&Candles) -> NOrderEmaInput // source = "close"

n_order_ema(&NOrderEmaInput) -> Result<NOrderEmaOutput, NOrderEmaError>
n_order_ema_into(&NOrderEmaInput, &mut [f64]) -> Result<(), NOrderEmaError>
Parameters and Styles v
pub struct NOrderEmaParams {
    pub period: Option<f64>,
    pub order: Option<usize>,
    pub ema_style: Option<String>,
    pub iir_style: Option<String>,
}

// Defaults
period = 9.0
order = 1
ema_style = "ema"
iir_style = "impulse_matched"

// Accepted ema_style values
"ema" | "dema" | "hema" | "tema"

// Accepted iir_style values
"all_pole"
"impulse_matched"
"matched_z"
"bilinear"
Output and Builders v
pub struct NOrderEmaOutput {
    pub values: Vec<f64>,
}

NOrderEmaBuilder::new()
    .period(f64)
    .order(usize)
    .ema_style(NOrderEmaStyle)
    .iir_style(NOrderEmaIirStyle)
    .kernel(Kernel)
    .apply(&Candles)
    .apply_slice(&[f64])
    .into_stream()

NOrderEmaBatchBuilder::new()
    .period_range((start, end, step))
    .order_range((start, end, step))
    .ema_style(NOrderEmaStyle)
    .iir_style(NOrderEmaIirStyle)
    .kernel(Kernel)
    .apply_slice(&[f64])

pub struct NOrderEmaBatchOutput {
    pub values: Vec<f64>,
    pub combos: Vec<NOrderEmaParams>,
    pub rows: usize,
    pub cols: usize,
}
Validation, Warmup & NaNs v
  • period must be finite, at least 1.0, and ceil(period) <= data.len() for one-shot calls.
  • order must be in 1..=64.
  • Input must be non-empty and contain at least one finite value.
  • The implementation requires one contiguous valid run long enough for the chosen style and order; non-finite samples break the run.
  • Warmup is style dependent: EMA and DEMA use order * (ceil(period) - 1); TEMA uses three times that base lookback; HEMA uses the max of the half-period and base lookbacks plus the sqrt-period lookback.
  • One-shot output prefixes the warmup region with NaN. Streaming returns None until warmup completes, and also returns None after any non-finite input because the stream resets.
  • Batch output is row-major. Each row corresponds to one (period, order) combo, and each row gets its own warmup NaN prefix.
  • Batch kernel validation rejects non-batch kernels with NOrderEmaError::InvalidKernelForBatch.
Error Handling v
use vector_ta::indicators::moving_averages::n_order_ema::NOrderEmaError;

match NOrderEmaBuilder::new().period(20.0).order(2).apply_slice(&data) {
    Ok(out) => println!("{:?}", out.values),
    Err(NOrderEmaError::InvalidPeriod { period, data_len }) =>
        eprintln!("invalid period {} for len {}", period, data_len),
    Err(NOrderEmaError::InvalidOrder { order }) =>
        eprintln!("invalid order {}", order),
    Err(NOrderEmaError::InvalidEmaStyle { value }) =>
        eprintln!("bad ema_style {}", value),
    Err(NOrderEmaError::InvalidIirStyle { value }) =>
        eprintln!("bad iir_style {}", value),
    Err(NOrderEmaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("need {} contiguous valid values, got {}", needed, valid),
    Err(e) => eprintln!("n_order_ema error: {}", e),
}

Python Bindings

Basic Usage v

The Python function returns a single NumPy array of values:

import numpy as np
from vector_ta import n_order_ema

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

values = n_order_ema(
    data,
    period=21.0,
    order=2,
    ema_style="tema",
    iir_style="matched_z",
)

fast_values = n_order_ema(
    data,
    period=9.0,
    order=1,
    ema_style="ema",
    iir_style="impulse_matched",
    kernel="avx2",
)
Streaming Real-time Updates v

The Python stream returns a float and emits NaN until warmup completes:

import numpy as np
from vector_ta import NOrderEmaStream

stream = NOrderEmaStream(
    period=34.0,
    order=3,
    ema_style="hema",
    iir_style="bilinear",
)

for value in live_prices:
    ema_value = stream.update(float(value))
    if not np.isnan(ema_value):
        print("ready:", ema_value)
Batch Processing v

Batch mode returns a dict with the reshaped values matrix and the expanded parameter vectors:

import numpy as np
from vector_ta import n_order_ema_batch

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

result = n_order_ema_batch(
    data,
    period_range=(9.0, 21.0, 4.0),
    order_range=(1, 3, 1),
    ema_style="dema",
    iir_style="all_pole",
)

values = result["values"]      # shape: (rows, cols)
periods = result["periods"]    # one entry per row
orders = result["orders"]      # one entry per row
rows = result["rows"]
cols = result["cols"]

JavaScript/WASM Bindings

Basic Usage v

The high-level WASM helper returns a Float64Array with the same length as the input series:

import { n_order_ema_js } from 'vectorta-wasm';

const data = new Float64Array(closePrices);

const values = n_order_ema_js(
  data,
  21.0,
  2,
  'tema',
  'impulse_matched',
);

console.log(values.length);
console.log(values[0]); // NaN during warmup
Memory-Efficient Operations v

Use the low-level allocation helpers when you want direct writes into WASM memory:

import { memory, n_order_ema_alloc, n_order_ema_free, n_order_ema_into } from 'vectorta-wasm';

const data = new Float64Array(closePrices);
const len = data.length;

const inPtr = n_order_ema_alloc(len);
const outPtr = n_order_ema_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(data);

n_order_ema_into(inPtr, outPtr, len, 34.0, 3, 'hema', 'matched_z');

const values = new Float64Array(memory.buffer, outPtr, len).slice();

n_order_ema_free(inPtr, len);
n_order_ema_free(outPtr, len);
Batch Processing v

The batch API accepts a config object and returns values, combos, rows, and cols:

import { n_order_ema_batch_js } from 'vectorta-wasm';

const data = new Float64Array(closePrices);

const result = n_order_ema_batch_js(data, {
  period_range: [9.0, 21.0, 4.0],
  order_range: [1, 3, 1],
  ema_style: 'dema',
  iir_style: 'bilinear',
});

const { values, combos, rows, cols } = result as {
  values: number[];
  combos: Array<{
    period?: number;
    order?: number;
    ema_style?: string;
    iir_style?: string;
  }>;
  rows: number;
  cols: number;
};

// Low-level batch writes are also available through n_order_ema_batch_into.

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