Directional Movement (+DM/−DM)
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 > 0andperiod ≤ len; otherwiseDmError::InvalidPeriod.- Requires at least
periodvalid high/low pairs after the first finite pair; elseDmError::NotEnoughValidData. - Leading NaNs are skipped to find the first finite pair; if none found:
DmError::AllValuesNaN. - Indices before
first_valid + period − 1areNaN(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
Across sizes, Rust CPU runs about 2.26× faster than Tulip C in this benchmark.
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
Average Directional Index
Technical analysis indicator
Average Directional Movement Index Rating
Technical analysis indicator
Alligator
Technical analysis indicator
Aroon
Technical analysis indicator
Aroon Oscillator
Technical analysis indicator
Chande Momentum Oscillator
Technical analysis indicator