Directional Movement (+DM/−DM)

Parameters: period = 14

Overview

Directional Movement, created by J. Welles Wilder Jr., forms the foundation of the complete Directional Movement System by measuring raw upward and downward price movements between consecutive bars. Plus DM captures upward movement when the current high exceeds the previous high, while Minus DM measures downward movement when the current low drops below the previous low. Wilder's rules ensure only the dominant movement counts on each bar. If both upward and downward movements occur, only the larger value is recorded while the smaller is set to zero. These raw values undergo Wilder smoothing to create continuous series that track the persistence of directional movement over time.

The smoothed Plus DM and Minus DM values reveal which direction exhibits greater momentum and how that momentum changes over time. Rising Plus DM indicates strengthening upward price expansion, suggesting bullish pressure building in the market. Increasing Minus DM shows growing downward expansion, warning of bearish forces gaining control. The relationship between these two series provides insights into trend development and potential reversals. When one directional movement consistently dominates the other, it confirms trending behavior, while alternating dominance suggests choppy or transitional markets.

While rarely used in isolation, Directional Movement serves as the essential building block for more sophisticated indicators like DI and ADX. Understanding these raw components helps traders interpret the derived indicators more effectively. Some advanced traders monitor DM directly to spot early trend changes before they appear in normalized indicators. The raw values also prove useful for custom indicator development and system optimization. Directional Movement provides the purest measure of price expansion in each direction, making it valuable for volatility analysis and market structure studies beyond traditional trend following applications.

Implementation Examples

Compute +DM/−DM from high/low arrays or candles:

use vectorta::indicators::dm::{DmParams, DmInput, DmOutput, dm};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From high/low slices
let high = vec![10.0, 10.5, 10.7, 10.4, 10.9];
let low  = vec![ 9.6, 10.0, 10.2, 10.1, 10.2];
let params = DmParams { period: Some(14) }; // default 14
let input = DmInput::from_slices(&high, &low, params);
let DmOutput { plus, minus } = dm(&input)?;

// From candles with defaults (period=14)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = DmInput::with_default_candles(&candles);
let out = dm(&input)?; // out.plus, out.minus

// Inspect values
println!("plus[0]={:?} minus[0]={:?}", out.plus.get(0), out.minus.get(0));

API Reference

Input Methods
// From high/low slices
DmInput::from_slices(&[f64], &[f64], DmParams) -> DmInput

// From candles (uses high/low fields)
DmInput::from_candles(&Candles, DmParams) -> DmInput

// From candles with defaults (period=14)
DmInput::with_default_candles(&Candles) -> DmInput
Parameters Structure
pub struct DmParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
pub struct DmOutput {
    pub plus: Vec<f64>,  // smoothed +DM
    pub minus: Vec<f64>, // smoothed −DM
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise DmError::InvalidPeriod.
  • Requires at least period valid high/low pairs after the first finite pair; else DmError::NotEnoughValidData.
  • Leading NaNs are skipped to find the first finite pair; if none found: DmError::AllValuesNaN.
  • Indices before first_valid + period − 1 are NaN (warmup prefix).
  • Empty inputs or mismatched lengths: DmError::EmptyData.
Error Handling
use vectorta::indicators::dm::{dm, DmError};

match dm(&input) {
    Ok(output) => process_dm(output.plus, output.minus),
    Err(DmError::EmptyData) => println!("Empty or mismatched high/low data"),
    Err(DmError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for length {}", period, data_len),
    Err(DmError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} valid pairs, got {}", needed, valid),
    Err(DmError::AllValuesNaN) => println!("All values are NaN"),
}

Python Bindings

Basic Usage

Compute +DM/−DM from NumPy arrays:

import numpy as np
from vectorta import dm

high = np.array([10.0, 10.5, 10.7, 10.4, 10.9], dtype=np.float64)
low  = np.array([ 9.6, 10.0, 10.2, 10.1, 10.2], dtype=np.float64)

# period defaults to 14 if omitted by Rust defaults; here pass explicitly
plus, minus = dm(high, low, period=14, kernel="auto")

print(plus)
print(minus)
Streaming
import numpy as np
from vectorta import DmStream

stream = DmStream(14)
for h, l in zip(high_series, low_series):
    out = stream.update(h, l)
    if out is not None:
        plus_dm, minus_dm = out
        handle(plus_dm, minus_dm)
Batch Processing
import numpy as np
from vectorta import dm_batch

high = np.asarray(high, dtype=np.float64)
low  = np.asarray(low, dtype=np.float64)

result = dm_batch(high, low, period_range=(5, 20, 5), kernel="auto")

# result is a dict with 'plus' and 'minus' 2D arrays and 'periods'
plus_matrix  = result['plus']   # shape: [num_periods, len]
minus_matrix = result['minus']  # shape: [num_periods, len]
periods      = result['periods']

print(periods)
print(plus_matrix.shape, minus_matrix.shape)
CUDA Acceleration

CUDA support for DM is coming soon.

# Coming soon: CUDA-accelerated DM calculations

JavaScript/WASM Bindings

Basic Usage

Calculate DM in JavaScript/TypeScript:

import { dm } from 'vectorta-wasm';

// High/Low data as Float64Array (same length)
const high = new Float64Array([10.0, 10.5, 10.7, 10.4, 10.9]);
const low  = new Float64Array([ 9.6, 10.0, 10.2, 10.1, 10.2]);

// Returns object with flattened values and shape metadata
const out = dm(high, low, 14);
// out.values = [plus..., minus...], out.rows = 2, out.cols = len
const plus = out.values.slice(0, out.cols);
const minus = out.values.slice(out.cols);

console.log('plus[0..3]=', plus.slice(0, 3));
console.log('minus[0..3]=', minus.slice(0, 3));
Memory-Efficient Operations

Zero-copy into pre-allocated WASM memory:

import { dm_alloc, dm_free, dm_into, memory } from 'vectorta-wasm';

const length = high.length;

// Allocate separate inputs/outputs
const highPtr  = dm_alloc(length);
const lowPtr   = dm_alloc(length);
const plusPtr  = dm_alloc(length);
const minusPtr = dm_alloc(length);

// Copy inputs into WASM
new Float64Array(memory.buffer, highPtr, length).set(high);
new Float64Array(memory.buffer, lowPtr,  length).set(low);

// Compute directly into output buffers
dm_into(highPtr, lowPtr, plusPtr, minusPtr, length, 14);

// Read out results
const plus  = new Float64Array(memory.buffer, plusPtr,  length).slice();
const minus = new Float64Array(memory.buffer, minusPtr, length).slice();

// Free when done
dm_free(highPtr, length);
dm_free(lowPtr,  length);
dm_free(plusPtr, length);
dm_free(minusPtr, length);
Batch Processing

Run many periods in one call:

import { dm_batch } from 'vectorta-wasm';

const cfg = { period_range: [5, 20, 5] };
const out = dm_batch(high, low, cfg);

// out.values layout: [all plus rows..., all minus rows...]
// Shape: rows = 2 * numPeriods, cols = len
const numPeriods = out.rows / 2;
const cols = out.cols;

// Extract first period's plus/minus rows
const plus0  = out.values.slice(0 * cols, 1 * cols);
const minus0 = out.values.slice(numPeriods * cols + 0 * cols, numPeriods * cols + 1 * cols);

console.log('periods:', out.periods);

Performance Analysis

Comparison:
View:

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

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