Double Exponential Moving Average (DEMA)

Parameters: period = 30

Overview

The Double Exponential Moving Average (DEMA), developed by Patrick Mulloy in 1994, reduces the lag inherent in traditional moving averages while maintaining smooth price tracking. The indicator calculates an exponential moving average of prices, then takes an EMA of that first EMA to create a double smoothed series. Rather than using this double smoothed value directly, DEMA combines both EMAs using the formula: 2 times EMA minus EMA of EMA. This mathematical approach cancels out much of the lag introduced by the smoothing process, creating a moving average that responds faster to price changes while filtering noise.

DEMA tracks price more closely than standard EMAs during trending markets, turning faster at inflection points and staying closer to current price action. The indicator maintains the smoothness characteristic of exponential averages while cutting lag approximately in half compared to a standard EMA of the same period. This responsiveness makes DEMA particularly effective for timing entries and exits in trending markets. The period parameter, typically set to 30, determines the lookback window for both EMA calculations. Shorter periods create more responsive averages suitable for scalping, while longer periods provide smoother signals better suited for position trading.

Traders use DEMA as a versatile tool for trend identification, signal generation, and dynamic support and resistance. Price crossovers with DEMA generate earlier signals than traditional moving averages, though with slightly more false signals during choppy markets. The indicator works particularly well in crossover systems where a fast DEMA crosses a slower one, providing timely trend change signals. Many traders also use DEMA as a trailing stop mechanism, as its reduced lag keeps stops closer to price during trends. DEMA serves as an excellent baseline for other indicators, providing a responsive yet stable price proxy for momentum calculations or band construction.

Implementation Examples

Compute DEMA from a slice or candles:

use vectorta::indicators::dema::{dema, DemaInput, DemaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price slice
let prices = vec![100.0, 101.0, 102.5, 101.5, 103.0];
let params = DemaParams { period: Some(30) }; // default is 30
let input = DemaInput::from_slice(&prices, params);
let result = dema(&input)?;

// Using Candles with defaults (period=30, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = DemaInput::with_default_candles(&candles);
let result = dema(&input)?;

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

API Reference

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

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

// From candles with defaults (period=30, source="close")
DemaInput::with_default_candles(&Candles) -> DemaInput
Parameters Structure
pub struct DemaParams {
    pub period: Option<usize>, // Default: 30
}
Output Structure
pub struct DemaOutput {
    pub values: Vec<f64>, // DEMA values (2*EMA1 - EMA2)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len(data).
  • Data length must satisfy needed = 2 × (period − 1); otherwise DemaError::NotEnoughData.
  • After the first finite input, at least needed valid points are required; else DemaError::NotEnoughValidData.
  • Batch output enforces warmup: indices ..(first + period − 1) are NaN. Streaming returns None for the first period − 1 updates (period=1 emits immediately).
Error Handling
use vectorta::indicators::dema::{dema, DemaError};

match dema(&input) {
    Ok(output) => process(output.values),
    Err(DemaError::EmptyInputData) => println!("Input data is empty"),
    Err(DemaError::AllValuesNaN) => println!("All input values are NaN"),
    Err(DemaError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for length {}", period, data_len),
    Err(DemaError::NotEnoughData { needed, valid }) =>
        println!("Need {} points, only {} available", needed, valid),
    Err(DemaError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} valid points after first finite, only {}", needed, valid),
}

Python Bindings

Basic Usage

Calculate DEMA using NumPy arrays (default period=30):

import numpy as np
from vectorta import dema

prices = np.array([100.0, 101.0, 102.5, 101.5, 103.0])

# With explicit period
result = dema(prices, period=30)

# Specify kernel for optimization ("auto" | "avx2" | "avx512" if available)
result = dema(prices, period=30, kernel="auto")

print(result)  # NumPy array
Streaming Real-time Updates

Update an internal state and receive values after warmup:

from vectorta import DemaStream

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

Test multiple period values efficiently:

import numpy as np
from vectorta import dema_batch

prices = np.array([...])
results = dema_batch(
    prices,
    period_range=(10, 50, 10),
    kernel="auto"
)

print(results['values'].shape)  # (num_periods, len(prices))
print(results['periods'])       # list of tested periods
CUDA Acceleration

CUDA helpers are available when built with Python + CUDA features:

# Single-series parameter sweep on GPU (f32)
from vectorta import dema_cuda_batch_dev
res = dema_cuda_batch_dev(
    data_f32=prices.astype('float32'),
    period_range=(10, 50, 10),
    device_id=0
)

# Many series × one parameter (time-major [T, N])
from vectorta import dema_cuda_many_series_one_param_dev
out = dema_cuda_many_series_one_param_dev(
    data_tm_f32=portfolio_tm.astype('float32'),
    period=30,
    device_id=0
)

JavaScript/WASM Bindings

Basic Usage

Compute DEMA in JavaScript/TypeScript:

import { dema_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 101.0, 102.5, 101.5, 103.0]);
const values = dema_js(prices, 30);
console.log('DEMA values:', values);
Memory-Efficient Operations

Use zero-copy APIs for performance:

import { dema_alloc, dema_free, dema_into, memory } from 'vectorta-wasm';

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

try {
  new Float64Array(memory.buffer, inPtr, n).set(prices);
  // Args: in_ptr, out_ptr, len, period
  dema_into(inPtr, outPtr, n, 30);
  const out = new Float64Array(memory.buffer, outPtr, n).slice();
  console.log(out);
} finally {
  dema_free(inPtr, n);
  dema_free(outPtr, n);
}
Batch Processing

Run a unified batch over period values and get metadata:

import { dema_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const { values, combos, rows, cols } = dema_batch(prices, {
  period_range: [10, 50, 10]
});

// Reshape values to a matrix [rows x cols]
const matrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  matrix.push(Array.from(values.slice(start, start + cols)));
}

// Access the period for the first combo
const firstPeriod = (combos[0] as any).period;

Performance Analysis

Comparison:
View:

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

Loading chart...

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

Related Indicators