Directional Indicator (DI)

Parameters: period = 14

Overview

The Directional Indicator (DI), developed by J. Welles Wilder Jr., quantifies the strength of upward and downward price movements to identify which side dominates the market. The indicator calculates two lines: Plus DI measures upward directional movement by comparing current highs to previous highs, while Minus DI tracks downward movement through low comparisons. Both values are smoothed using Wilder's averaging method and normalized by Average True Range, creating percentage values between 0 and 100 that represent the proportion of price movement in each direction. This normalization allows comparison across different markets and volatility conditions.

When Plus DI exceeds Minus DI, buyers control the market with upward pressure dominating price action. Conversely, Minus DI above Plus DI indicates sellers have control with downward movement prevailing. The magnitude of separation between the lines reflects trend strength, with wider gaps suggesting more directional conviction. Crossovers between the two lines signal potential trend changes, though these work best when confirmed by other indicators. Values above 25 for either line typically indicate strong directional movement, while both lines below 20 suggest a weak or absent trend.

Traders use DI as a core component of directional movement systems, often combining it with ADX to create complete trend trading strategies. Plus DI crossing above Minus DI generates buy signals, while the opposite suggests selling opportunities. The indicator excels at confirming breakouts, as genuine moves show clear DI separation while false breakouts lack directional strength. Many traders use DI divergence between the lines as an early warning of trend exhaustion. The indicator also helps filter trades, with positions taken only in the direction of the dominant DI line to align with prevailing market forces.

Implementation Examples

Use DI with candles or with separate OHLC slices:

use vectorta::indicators::di::{di, DiInput, DiParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From slices (high/low/close)
let high = vec![..];
let low  = vec![..];
let close= vec![..];
let params = DiParams { period: Some(14) };
let input = DiInput::from_slices(&high, &low, &close, params);
let out = di(&input)?; // out.plus, out.minus

// From Candles with defaults (period = 14, sources = high/low/close)
let candles: Candles = read_candles_from_csv("data/sample_ohlc.csv")?;
let input = DiInput::with_default_candles(&candles);
let out = di(&input)?;

// Access values
for (p, m) in out.plus.iter().zip(out.minus.iter()) {
    println!("+DI={}, -DI={}", p, m);
}

API Reference

Input Methods
// From OHLC slices
DiInput::from_slices(&[f64], &[f64], &[f64], DiParams) -> DiInput

// From Candles (uses high/low/close)
DiInput::from_candles(&Candles, DiParams) -> DiInput

// Default params (period=14) with candles
DiInput::with_default_candles(&Candles) -> DiInput
Parameters Structure
pub struct DiParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
pub struct DiOutput {
    pub plus: Vec<f64>,  // +DI values (0..=100)
    pub minus: Vec<f64>, // -DI values (0..=100)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise DiError::InvalidPeriod.
  • First valid index is the first bar where high/low/close are all finite; if none: DiError::AllValuesNaN.
  • Needs at least period valid bars after the first valid; otherwise DiError::NotEnoughValidData { needed, valid }.
  • Warmup prefix is [0 .. first_idx + period − 1]; these outputs are NaN.
  • If smoothed TR is zero at a bar, both outputs are 0.0 for that bar.
  • Streaming: DiStream::update returns None until period updates have filled the buffer.
Error Handling
use vectorta::indicators::di::{di, DiError};

match di(&input) {
    Ok(output) => use_results(output.plus, output.minus),
    Err(DiError::EmptyData) => println!("Empty data provided"),
    Err(DiError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for data length {}", period, data_len),
    Err(DiError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} valid bars after first finite, only {}", needed, valid),
    Err(DiError::AllValuesNaN) => println!("All values are NaN"),
}

Python Bindings

Basic Usage

Compute ±DI from NumPy arrays. Kernel is optional.

import numpy as np
from vectorta import di

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

plus, minus = di(high, low, close, period=14, kernel=None)

print(plus.shape, minus.shape)
Streaming Real-time Updates
from vectorta import DiStream

stream = DiStream(period=14)
for (h, l, c) in ohlc_feed:
    pair = stream.update(h, l, c)
    if pair is not None:
        plus, minus = pair
        print(plus, minus)
Batch Parameter Sweep
import numpy as np
from vectorta import di_batch

high = np.array([...]); low = np.array([...]); close = np.array([...])

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

# result['plus'] and result['minus'] are shaped (rows, cols)
plus_matrix = result['plus']
minus_matrix = result['minus']
periods = result['periods']

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

CUDA support for DI is coming soon.

# Coming soon: CUDA-accelerated DI batch APIs

JavaScript / WASM

Basic Usage
import { di_js } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low  = new Float64Array([...]);
const close= new Float64Array([...]);
const period = 14;

// di_js returns a flattened matrix with 2 rows: [+DI, -DI]
const { values, rows, cols } = di_js(high, low, close, period);
const plus  = values.slice(0, cols);
const minus = values.slice(cols, 2 * cols);
Memory-Efficient Operations

Use zero-copy buffers for large datasets:

import { di_alloc, di_free, di_into, memory } from 'vectorta-wasm';

const n = 10_000;
const high = new Float64Array(n);
const low  = new Float64Array(n);
const close= new Float64Array(n);

// Allocate input and output buffers in WASM memory
const hPtr = di_alloc(n);
const lPtr = di_alloc(n);
const cPtr = di_alloc(n);
const outPtr = di_alloc(2 * n); // [+DI row][−DI row]

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

// Compute into the output buffer
di_into(hPtr, lPtr, cPtr, outPtr, n, 14);

// Read results
const out = new Float64Array(memory.buffer, outPtr, 2 * n);
const plus  = out.slice(0, n);
const minus = out.slice(n, 2 * n);

// Free memory
di_free(hPtr, n); di_free(lPtr, n); di_free(cPtr, n); di_free(outPtr, 2 * n);
Batch Processing
import { di_batch } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low  = new Float64Array([...]);
const close= new Float64Array([...]);

// di_batch returns an object with flattened rows and metadata
const cfg = { period_range: [5, 20, 5] };
const { values, rows, cols, periods } = di_batch(high, low, close, cfg);

// For each combo, rows contain two lines: plus then minus
function row(i) { return values.slice(i * cols, (i + 1) * cols); }
// Example: first combo's +DI and -DI
const plus0  = row(0);
const minus0 = row(1);

Performance Analysis

Comparison:
View:

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

Loading chart...

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

Related Indicators