Triple Exponential Moving Average (TEMA)

Parameters: period = 9

Overview

The Triple Exponential Moving Average (TEMA) achieves superior lag reduction through a clever mathematical combination of three exponential moving averages. Rather than simply stacking EMAs, TEMA applies the formula 3×EMA1 - 3×EMA2 + EMA3, where EMA1 operates on price, EMA2 smooths EMA1, and EMA3 smooths EMA2. This weighted combination effectively cancels much of the inherent smoothing lag while maintaining the noise reduction benefits of exponential averaging. The result tracks price movements more closely than a single EMA of the same period, responding faster to genuine trend changes while still filtering random fluctuations. Traders favor TEMA for entries and exits in trending markets where traditional moving averages lag too far behind price action. The indicator remains smooth enough to avoid excessive whipsaw signals yet responsive enough to capture trend inflection points with minimal delay. The default 9-period setting provides a practical balance, though shorter periods increase aggressiveness and longer periods prioritize smoothness over responsiveness.

Implementation Examples

Use TEMA with slices or candles:

use vectorta::indicators::moving_averages::tema::{tema, TemaInput, TemaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = TemaParams { period: Some(9) };
let input = TemaInput::from_slice(&prices, params);
let out = tema(&input)?;

// From candles (defaults: period=9, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TemaInput::with_default_candles(&candles);
let out = tema(&input)?;

for v in out.values { println!("TEMA: {}", v); }

API Reference

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

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

// From candles with default params (close, period=9)
TemaInput::with_default_candles(&Candles) -> TemaInput
Parameters Structure
pub struct TemaParams {
    pub period: Option<usize>, // Default: 9
}
Output Structure
pub struct TemaOutput {
    pub values: Vec<f64>, // TEMA values
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len(data) or TemaError::InvalidPeriod.
  • Requires at least period valid points after the first finite value; else TemaError::NotEnoughValidData.
  • All-NaN input yields TemaError::AllValuesNaN; empty slice yields TemaError::EmptyInputData.
  • Warmup: outputs are NaN until first + (period−1)×3. For period=1, values equal price from first finite onward.
Error Handling
use vectorta::indicators::moving_averages::tema::{tema, TemaError, TemaInput, TemaParams};

let input = TemaInput::from_slice(&prices, TemaParams { period: Some(9) });
match tema(&input) {
    Ok(output) => process(output.values),
    Err(TemaError::EmptyInputData) => eprintln!("Input data is empty"),
    Err(TemaError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(TemaError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data length {}", period, data_len),
    Err(TemaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points after first finite; have {}", needed, valid),
}

Python Bindings

Basic Usage

Compute TEMA with NumPy arrays:

import numpy as np
from vectorta import tema

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0], dtype=np.float64)

# period (>=1), optional kernel: 'auto' | 'scalar' | 'avx2' | 'avx512'
values = tema(prices, 9, kernel='auto')
print(values.shape)
Streaming Real-time Updates

Process real-time TEMA values:

from vectorta import TemaStream

stream = TemaStream(period=9)
for price in price_feed:
    val = stream.update(price)
    if val is not None:
        handle(val)
Batch Parameter Optimization

Test multiple period values and get structured outputs:

import numpy as np
from vectorta import tema_batch

prices = np.array([...], dtype=np.float64)

results = tema_batch(
    prices,
    period_range=(5, 30, 5),
    kernel='auto'
)

# results is a dict with 2D array and metadata
print(results['values'].shape)  # (num_combinations, len(prices))
print(results['periods'])       # np.array of periods tested
CUDA Acceleration

CUDA-backed TEMA APIs are available when built with Python+CUDA features.

from vectorta import tema_cuda_batch_dev, tema_cuda_many_series_one_param_dev
import numpy as np

# 1) One series, many period values (parameter sweep)
prices_f32 = np.array([...], dtype=np.float32)
dev_out = tema_cuda_batch_dev(prices_f32, period_range=(5, 30, 1), device_id=0)

# 2) Many series (time-major), one parameter set
tm = np.array([...], dtype=np.float32)  # shape [T, N]
grid_dev = tema_cuda_many_series_one_param_dev(prices_tm_f32=tm, period=9, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Calculate TEMA in JavaScript/TypeScript:

import { tema_js } from 'vectorta-wasm';

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

Use zero-copy operations for large arrays:

import { tema_alloc, tema_free, tema_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* data */]);
const len = prices.length;
const inPtr = tema_alloc(len);
const outPtr = tema_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(prices);
tema_into(inPtr, outPtr, len, 9);
const values = new Float64Array(memory.buffer, outPtr, len).slice();

tema_free(inPtr, len);
tema_free(outPtr, len);
Batch Processing

Test multiple period values using the unified API:

import { tema_batch } from 'vectorta-wasm';

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

// Unified batch API with config
const out = tema_batch(prices, { period_range: [5, 30, 5] });
// out: { values: Float64Array, combos: { period?: number }[], rows: number, cols: number }

console.log(out.rows, out.cols);
// Deprecated legacy variant also exists: tema_batch_js(prices, start, end, step)
// which returns a flat Float64Array of length rows * cols.

Performance Analysis

Comparison:
View:

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

Loading chart...

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

Related Indicators