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 vectorta::indicators::ema::{ema, EmaInput, EmaParams};
use vectorta::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 vectorta::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 vectorta 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 vectorta import ema

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

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);

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-01-05

Related Indicators