Dickson Moving Average (DMA)

Parameters: hull_length = 7 | ema_length = 20 | ema_gain_limit = 50 | hull_ma_type = WMA

Overview

The Dickson Moving Average (DMA) creates a sophisticated hybrid indicator by combining an adaptive exponential moving average with a Hull style smoother to achieve both responsiveness and stability. The adaptive component dynamically adjusts its gain parameter by testing multiple values and selecting the one that minimizes prediction error at each step. This optimization occurs within a quantized grid of gain values, typically testing increments of 0.1 up to a specified limit. Meanwhile, the Hull component uses either weighted or exponential moving averages in a dual stage configuration to reduce lag while maintaining smoothness. The final DMA value averages these two distinct approaches, balancing the adaptivity of the error minimizing EMA with the stability of the Hull smoother.

DMA excels at tracking price movements with minimal delay while avoiding the overshooting common in highly responsive averages. The adaptive EMA component ensures the indicator adjusts to changing market conditions, becoming more reactive during trends and more stable during consolidation. The Hull smoothing component filters out noise and provides a stable baseline that prevents erratic behavior even when the adaptive component becomes aggressive. This dual nature makes DMA particularly effective in markets that alternate between trending and ranging phases. The gain limit parameter controls how adaptive the indicator can become, with higher limits allowing more aggressive tracking at the cost of potential instability.

Traders use DMA as a versatile tool for trend identification, signal generation, and dynamic support and resistance levels. The indicator generates cleaner crossover signals than traditional moving averages due to its adaptive nature and reduced lag. DMA works particularly well in trend following systems where its responsiveness helps capture moves early while the smoothing component reduces false signals. Many traders employ DMA as a trailing stop mechanism, as it stays close to price during trends but provides adequate distance during volatile periods. The indicator also serves as an excellent baseline for oscillators or bands, providing a responsive yet stable center line that adapts to market conditions.

Implementation Examples

Compute DMA from a slice or Candles:

use vectorta::indicators::moving_averages::dma::{dma, DmaInput, DmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = DmaParams { hull_length: Some(7), ema_length: Some(20), ema_gain_limit: Some(50), hull_ma_type: Some("WMA".into()) };
let input = DmaInput::from_slice(&prices, params);
let result = dma(&input)?; // DmaOutput { values }

// From Candles with defaults (close, 7/20/50, WMA)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = DmaInput::with_default_candles(&candles);
let result = dma(&input)?;

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

API Reference

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

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

// From candles with defaults (close, 7/20/50, "WMA")
DmaInput::with_default_candles(&Candles) -> DmaInput
Parameters Structure
pub struct DmaParams {
    pub hull_length: Option<usize>,     // Default: 7
    pub ema_length: Option<usize>,      // Default: 20
    pub ema_gain_limit: Option<usize>,  // Default: 50
    pub hull_ma_type: Option<String>,   // Default: "WMA" ("EMA" allowed)
}
Output Structure
pub struct DmaOutput {
    pub values: Vec<f64>, // Final DMA values (length = input length)
}
Validation, Warmup & NaNs
  • hull_length > 0 and ema_length > 0; both must not exceed data length.
  • First finite input index = first. Requires at least max(hull_length, ema_length) + ⌊√hull_length⌉ valid points after first; else DmaError::NotEnoughValidData.
  • hull_ma_type ∈ {"WMA", "EMA"}; otherwise DmaError::InvalidHullMAType.
  • Outputs before warmup end are set to NaN (prefix seeded to match batch/stream parity).
Error Handling
#[derive(Debug, Error)]
pub enum DmaError {
    EmptyInputData,
    AllValuesNaN,
    InvalidPeriod { period: usize, data_len: usize },
    NotEnoughValidData { needed: usize, valid: usize },
    InvalidHullMAType { value: String },
}

// Example handling
match dma(&input) {
    Ok(out) => { /* use out.values */ }
    Err(DmaError::EmptyInputData) => eprintln!("empty input"),
    Err(DmaError::AllValuesNaN) => eprintln!("all NaN"),
    Err(DmaError::InvalidPeriod { period, data_len }) => { /* fix params */ }
    Err(DmaError::NotEnoughValidData { needed, valid }) => { /* wait for more data */ }
    Err(DmaError::InvalidHullMAType { value }) => { /* use "WMA" or "EMA" */ }
}~

Python Bindings

Basic Usage

Compute DMA and use the streaming class:

import numpy as np
from vectorta import dma, DmaStream, dma_batch

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

# Vector DMA
values = dma(prices, hull_length=7, ema_length=20, ema_gain_limit=50, hull_ma_type="WMA", kernel=None)

# Streaming DMA
stream = DmaStream(7, 20, 50, "WMA")
for p in prices:
    v = stream.update(p)
    if v is not None:
        print("DMA:", v)
Batch Parameter Optimization

Sweep hull/EMA/gain ranges and inspect combinations:

import numpy as np
from vectorta import dma_batch

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

result = dma_batch(
    prices,
    hull_length_range=(3, 10, 1),
    ema_length_range=(10, 30, 5),
    ema_gain_limit_range=(10, 50, 10),
    hull_ma_type="WMA",
    kernel=None,
)

# result keys: values (rows×cols), hull_lengths, ema_lengths, ema_gain_limits, hull_ma_type, hull_ma_types
values = result["values"]
rows, cols = values.shape
print("rows, cols:", rows, cols)
print("hulls:", result["hull_lengths"])
CUDA Acceleration

CUDA dev APIs are available when built with CUDA + Python features:

# from vectorta import dma_cuda_batch_dev, dma_cuda_many_series_one_param_dev
# 
# # One series, many parameter combinations (device array result)
# out_dev = dma_cuda_batch_dev(
#     data_f32=prices.astype(np.float32),
#     hull_length_range=(3, 8, 1),
#     ema_length_range=(10, 20, 2),
#     ema_gain_limit_range=(10, 50, 10),
#     hull_ma_type="WMA",
#     device_id=0,
# )
# 
# # Many series (T×N), one parameter set (device array result)
# out_dev = dma_cuda_many_series_one_param_dev(
#     data_tm_f32=portfolio_tm.astype(np.float32),
#     hull_length=7,
#     ema_length=20,
#     ema_gain_limit=50,
#     hull_ma_type="WMA",
#     device_id=0,
# )

JavaScript/WASM Bindings

Basic Usage

Calculate DMA in JavaScript/TypeScript:

import { dma_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
const values = dma_js(prices, 7, 20, 50, "WMA");
console.log('DMA values:', values);
Memory-Efficient Operations

Use zero-copy into preallocated WASM buffers:

import { dma_alloc, dma_free, dma_into, memory } from 'vectorta-wasm';

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

new Float64Array(memory.buffer, inPtr, n).set(prices);
dma_into(inPtr, outPtr, n, 7, 20, 50, "WMA");
const out = new Float64Array(memory.buffer, outPtr, n).slice();

dma_free(inPtr, n);
dma_free(outPtr, n);
Batch Processing

Compute many parameter combinations at once:

import { dma_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* ... */]);
const config = {
  hull_length_range: [3, 10, 1],
  ema_length_range: [10, 30, 5],
  ema_gain_limit_range: [10, 50, 10],
  hull_ma_type: "WMA",
};

const out = dma_batch(prices, config);
// out: { values: Float64Array, combos: DmaParams[], rows: number, cols: number }
console.log(out.rows, out.cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

CUDA note

In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.

Related Indicators