Directional Movement Index (DX)

Parameters: period = 14

Overview

The Directional Movement Index (DX), created by J. Welles Wilder Jr., measures the strength of price movement regardless of direction by analyzing the relationship between positive and negative directional indicators. DX forms the raw foundation for the more commonly used Average Directional Index (ADX). The calculation involves first determining Plus DI and Minus DI from smoothed directional movements and true range. DX then quantifies how far apart these directional indicators are, expressing the difference as a percentage of their sum. This produces values between 0 and 100 that represent the degree of directional movement present in the market.

DX values indicate the presence and strength of trending behavior without specifying direction. Low DX readings near 0 occur when Plus DI and Minus DI are nearly equal, signaling directionless, choppy markets where neither bulls nor bears maintain control. High DX values approaching 100 indicate strong directional movement where one side dominates completely. Values above 25 generally suggest trending conditions suitable for trend following strategies. Because DX measures raw directional strength at each point, it can be quite volatile, jumping dramatically as directional dominance shifts between buyers and sellers.

While DX provides valuable insights into market structure, most traders use its smoothed version, ADX, for practical applications due to DX's erratic nature. However, understanding DX helps interpret ADX more effectively by revealing the underlying directional dynamics. Some advanced traders monitor DX directly for early warning of trend changes, as spikes or drops in DX often precede similar moves in ADX by several periods. DX also proves useful in system development for identifying optimal smoothing parameters. The indicator serves as a building block for custom indicators that require raw directional strength measurements before applying proprietary smoothing or filtering techniques.

Implementation Examples

Compute DX from H/L/C slices or candles:

use vectorta::indicators::dx::{dx, DxInput, DxParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with H/L/C slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = DxParams { period: Some(14) }; // default
let input = DxInput::from_hlc_slices(&high, &low, &close, params);
let result = dx(&input)?;

// Using with Candles (auto-selects high/low/close), default period=14
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = DxInput::with_default_candles(&candles);
let result = dx(&input)?;

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

API Reference

Input Methods
// From high/low/close slices
DxInput::from_hlc_slices(&[f64], &[f64], &[f64], DxParams) -> DxInput

// From candles (uses high/low/close fields)
DxInput::from_candles(&Candles, DxParams) -> DxInput

// From candles with default params (period=14)
DxInput::with_default_candles(&Candles) -> DxInput
Parameters Structure
pub struct DxParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
pub struct DxOutput {
    pub values: Vec<f64>, // DX values in [0, 100]
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise DxError::InvalidPeriod { period, data_len }.
  • Data source: candles require high/low/close; selection failures surface as DxError::SelectCandleFieldError(String).
  • First valid index is the first bar where all H/L/C are finite. If none: DxError::AllValuesNaN.
  • There must be at least period valid bars after the first finite bar; else DxError::NotEnoughValidData { needed, valid }.
  • Warmup: indices [0 .. first + period − 1) are NaN. The first DX value is written at first + period − 1.
  • Runtime NaNs: if any of H/L/C are NaN at an index, that output carries the previous DX value forward.
  • Edge case: when +DI + −DI == 0, the first value becomes 0.0; later values fall back to the last DX.
Error Handling
#[derive(Debug, Error)]
pub enum DxError {
    #[error("dx: Empty data provided for DX.")]
    EmptyData,
    #[error("dx: Could not select candle field: {0}")]
    SelectCandleFieldError(String),
    #[error("dx: Invalid period: period = {period}, data length = {data_len}")]
    InvalidPeriod { period: usize, data_len: usize },
    #[error("dx: Not enough valid data: needed = {needed}, valid = {valid}")]
    NotEnoughValidData { needed: usize, valid: usize },
    #[error("dx: All high, low, and close values are NaN.")]
    AllValuesNaN,
}

// Example handling
match dx(&input) {
    Ok(out) => {/* use out.values */},
    Err(e) => match e {
        DxError::EmptyData => {/* handle */},
        DxError::SelectCandleFieldError(msg) => {/* log msg */},
        DxError::InvalidPeriod { .. } => {/* fix params */},
        DxError::NotEnoughValidData { .. } => {/* supply more data */},
        DxError::AllValuesNaN => {/* clean inputs */},
    }
}

Python Bindings

Basic Usage

Calculate DX from NumPy arrays of H/L/C (default period=14):

import numpy as np
from vectorta import dx

# Prepare H/L/C as NumPy arrays
high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)

# Default period
values = dx(high, low, close, period=14)

# Or specify kernel ("auto", "scalar", "avx2", "avx512")
values = dx(high, low, close, period=20, kernel="avx2")

print(values)
Streaming Real-time Updates

Process H/L/C streams with O(1) updates (first value after warmup):

from vectorta import DxStream

stream = DxStream(period=14)
for h, l, c in hlc_feed:
    dx_val = stream.update(h, l, c)
    if dx_val is not None:
        print("DX:", dx_val)
Batch Parameter Optimization

Test multiple period values efficiently:

import numpy as np
from vectorta import dx_batch

high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)

# (start, end, step)
result = dx_batch(high, low, close, period_range=(10, 30, 5), kernel="auto")

values = result["values"]   # shape: (num_periods, len)
periods = result["periods"]  # list of tested periods
print(values.shape, periods)
CUDA Acceleration

CUDA support for DX is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated DX calculations

JavaScript/WASM Bindings

Basic Usage

Calculate DX in JavaScript/TypeScript:

import { dx_js } from 'vectorta-wasm';

// H/L/C data as Float64Array
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);

// Calculate DX with specified period
const values = dx_js(high, low, close, 14);
console.log('DX values:', values);
Memory-Efficient Operations

Use zero-copy operations with explicit memory:

import { dx_alloc, dx_free, dx_into, memory } from 'vectorta-wasm';

const len = close.length;
const hPtr = dx_alloc(len);
const lPtr = dx_alloc(len);
const cPtr = dx_alloc(len);
const outPtr = dx_alloc(len);

// Copy data into WASM memory
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);

// Compute directly into the output buffer
dx_into(hPtr, lPtr, cPtr, outPtr, len, 14);

// Read back (slice() to copy out of WASM memory)
const dxValues = new Float64Array(memory.buffer, outPtr, len).slice();

// Free memory
dx_free(hPtr, len);
dx_free(lPtr, len);
dx_free(cPtr, len);
dx_free(outPtr, len);
Batch Processing

Test multiple periods in one call:

import { dx_batch } from 'vectorta-wasm';

const config = { period_range: [10, 30, 5] };
const { values, combos, rows, cols } = dx_batch(high, low, close, config);

// values is flat [rows * cols]; reshape as needed
const resultMatrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  resultMatrix.push(values.slice(start, start + cols));
}

// combos[r].period holds the period for row r
console.log('Periods tested:', combos.map(c => c.period));

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