Exponential Moving Average (EMA)

Parameters: period = 9

Overview

The Exponential Moving Average tracks price trends by applying exponentially decreasing weights to older data points, with the most recent prices receiving the highest influence. Unlike simple averages that weight all values equally, EMA uses a smoothing factor (typically 2/(period+1)) that creates a recursive calculation where each new value depends on the previous EMA and current price. When prices rise above the EMA, it signals potential upward momentum, while prices below suggest bearish pressure. Traders use EMA crossovers as entry and exit signals: faster EMAs crossing above slower ones indicate buying opportunities, while the reverse suggests selling points. The exponential weighting makes EMA particularly effective for tracking trending markets, as it responds quickly to directional moves while maintaining enough smoothing to filter out minor fluctuations.

Implementation Examples

Get started with EMA in just a few lines:

use vector_ta::indicators::ema::{ema, EmaInput, EmaParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = EmaParams { period: Some(9) };
let input = EmaInput::from_slice(&prices, params);
let result = ema(&input)?;

// Using with Candles (default: source="close", period=9)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EmaInput::with_default_candles(&candles);
let result = ema(&input)?;

// Access EMA values
for value in result.values {
    println!("EMA: {}", value);
}

API Reference

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

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

// From candles with default params (source="close", period=9)
EmaInput::with_default_candles(&Candles) -> EmaInput
Parameters Structure
pub struct EmaParams {
    pub period: Option<usize>, // Default: 9
}
Output Structure
pub struct EmaOutput {
    pub values: Vec<f64>, // EMA values
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ data.len(); else EmaError::InvalidPeriod.
  • There must be at least period valid points after the first finite value; otherwise EmaError::NotEnoughValidData.
  • Indices before the first finite input are NaN.
  • Warmup: from first finite value until first + period - 1, output is a running mean; then standard EMA with α = 2/(n+1).
  • Non‑finite inputs during warmup carry forward the current mean; during EMA phase they carry forward the previous EMA value.
  • Streaming: returns None until period valid values have been processed; then returns the current EMA. NaNs do not update state.
Error Handling
use vector_ta::indicators::ema::{ema, EmaError};

match ema(&input) {
    Ok(output) => process_results(output.values),
    Err(EmaError::EmptyInputData) =>
        println!("Input data is empty"),
    Err(EmaError::AllValuesNaN) =>
        println!("All input values are NaN"),
    Err(EmaError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for data length {}", period, data_len),
    Err(EmaError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} valid points, have {}", needed, valid),
}

Python Bindings

Basic EMA Calculation
import numpy as np
from vector_ta import ema

# Price data
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=float)

# Compute EMA (default kernel = auto)
values = ema(prices, period=9)
print(values)
Kernel Selection
# Choose execution kernel (optional)
from vector_ta import ema

values = ema(prices, period=14, kernel="avx2")  # or "neon" on ARM
print(values)
CUDA Acceleration

CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
    from vector_ta import ema_cuda_batch_dev, ema_cuda_many_series_one_param_dev

    # One series (float32)
    data_f32 = np.asarray(load_data(), dtype=np.float32)

    dev = ema_cuda_batch_dev(
    data_f32=data_f32,
    period_range=(5, 30, 5),
    device_id=0,
    )

    # Many series (time-major)
    data_tm_f32 = np.asarray(load_data_time_major_matrix(), dtype=np.float32)

    dev_tm = ema_cuda_many_series_one_param_dev(
    data_tm_f32=data_tm_f32,
    period=14,
    device_id=0,
    )

JavaScript / WASM Bindings

Basic Usage
import { ema_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const period = 9;
const values = ema_js(prices, period);
console.log(values);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { ema_alloc, ema_free, ema_into, memory } from 'vectorta-wasm';

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

// Allocate WASM memory for input and output
const inPtr = ema_alloc(length);
const outPtr = ema_alloc(length);

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

// Calculate EMA directly into allocated memory
// Args: in_ptr, out_ptr, len, period
ema_into(inPtr, outPtr, length, 9);

// Read results from WASM memory (slice() to copy out)
const emaValues = new Float64Array(memory.buffer, outPtr, length).slice();

// Free allocated memory when done
ema_free(inPtr, length);
ema_free(outPtr, length);

console.log('EMA values:', emaValues);
Batch Processing

Test multiple period values efficiently:

import { ema_batch, ema_batch_metadata_js } from 'vectorta-wasm';

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

// Define parameter sweep range
const start = 5, end = 50, step = 5;  // 5, 10, 15, ... 50

// Get metadata about period combinations (single axis)
const metadata = ema_batch_metadata_js(start, end, step);
const numCombos = metadata.length;

// Calculate all combinations using unified batch API
const config = { period_range: [start, end, step] };
const result = ema_batch(prices, config);

// result is an object with values/combos/rows/cols
console.log(result.rows, result.cols);
// Access a specific row (e.g., first period)
const firstRow = result.values.slice(0, prices.length);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaEma;
use vector_ta::indicators::moving_averages::ema::EmaBatchRange;

let cuda = CudaEma::new(0)?;

let data_f32: [f32] = /* ... */;
let sweep = EmaBatchRange::default();

let out = cuda.ema_batch_dev(&data_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:

Across sizes, Rust CPU runs about 1.23× faster than Tulip C in this benchmark.

Loading chart...

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

Related Indicators