Directional Indicator (DI)
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 > 0andperiod ≤ len; otherwiseDiError::InvalidPeriod.- First valid index is the first bar where
high/low/closeare all finite; if none:DiError::AllValuesNaN. - Needs at least
periodvalid bars after the first valid; otherwiseDiError::NotEnoughValidData { needed, valid }. - Warmup prefix is
[0 .. first_idx + period − 1]; these outputs areNaN. - If smoothed TR is zero at a bar, both outputs are
0.0for that bar. - Streaming:
DiStream::updatereturnsNoneuntilperiodupdates 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
Across sizes, Rust CPU runs about 2.59× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
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