Vortex Indicator (VI)

Parameters: period = 14

Overview

The Vortex Indicator captures directional movement by measuring the relationship between current price extremes and prior period highs and lows, revealing the strength of upward versus downward vortex flows. The indicator produces two lines: VI+ quantifies positive vortex movement by comparing each high to the previous low, while VI- measures negative movement by comparing each low to the previous high. Both lines are normalized by the rolling sum of True Range to create ratio values that remain comparable across different price levels and volatility regimes. Crossovers between VI+ and VI- signal potential trend changes, with VI+ crossing above VI- indicating emerging upward momentum and the reverse suggesting downward pressure. The indicator proves particularly effective at identifying the start of new trends, as vortex movements often accelerate before price itself makes decisive directional moves. Traders also watch for divergences where vortex strength weakens while price continues trending, often foreshadowing reversals. Default period is 14 bars, providing balanced sensitivity to both short term movements and underlying directional flow.

Implementation Examples

Compute VI from slices or candles:

use vectorta::indicators::vi::{vi, ViInput, ViParams, ViOutput};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From high/low/close slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = ViParams { period: Some(14) }; // default 14
let input = ViInput::from_slices(&high, &low, &close, params);
let ViOutput { plus, minus } = vi(&input)?;

// From Candles with default params (period=14)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = ViInput::with_default_candles(&candles);
let ViOutput { plus, minus } = vi(&input)?;

API Reference

Input Methods
// From H/L/C slices
ViInput::from_slices(&[f64], &[f64], &[f64], ViParams) -> ViInput

// From candles (uses sources "high", "low", "close")
ViInput::from_candles(&Candles, ViParams) -> ViInput

// With defaults (period=14)
ViInput::with_default_candles(&Candles) -> ViInput
Parameters Structure
pub struct ViParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
pub struct ViOutput {
    pub plus: Vec<f64>,  // VI+
    pub minus: Vec<f64>, // VI-
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise ViError::InvalidPeriod.
  • Inputs must be non-empty and equal length; otherwise ViError::EmptyData.
  • First valid index is the earliest where H/L/C are all finite; if none, ViError::AllValuesNaN.
  • If len - first_valid < period, returns ViError::NotEnoughValidData.
  • Warmup: elements before first_valid + period - 1 are NaN. First defined outputs occur at that index.
  • Streaming returns None until the window fills. Streaming update requires previous bar values (Ht-1/Lt-1/Ct-1).
Error Handling
use vectorta::indicators::vi::{vi, ViError};

match vi(&input) {
    Ok(out) => {
        process(out.plus);
        process(out.minus);
    }
    Err(ViError::EmptyData) => eprintln!("no data"),
    Err(ViError::InvalidPeriod { period, data_len }) => {
        eprintln!("invalid period: {} (len={})", period, data_len)
    }
    Err(ViError::NotEnoughValidData { needed, valid }) => {
        eprintln!("need {} valid bars, have {}", needed, valid)
    }
    Err(ViError::AllValuesNaN) => eprintln!("all values NaN"),
}

Python Bindings

Basic Usage
import numpy as np
from vectorta import vi

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

res = vi(high, low, close, period=14, kernel="auto")
plus = res["plus"]
minus = res["minus"]
Streaming
from vectorta import ViStream

stream = ViStream(period=14)
vi_points = []
for h, l, c in zip(high, low, close):
    out = stream.update(h, l, c)  # returns (vi_plus, vi_minus) or None until warmed up
    if out is not None:
        vi_points.append(out)
Batch Processing
import numpy as np
from vectorta import vi_batch

# Sweep period from 7 to 28 by 7
out = vi_batch(high, low, close, period_range=(7, 28, 7), kernel="auto")

# Shapes: plus/minus are (rows, cols)
plus_matrix = out["plus"]
minus_matrix = out["minus"]
periods = out["periods"]

# Select the row for period=14
row = list(periods).index(14)
vi_plus_14 = plus_matrix[row]
vi_minus_14 = minus_matrix[row]
CUDA Acceleration

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

# Coming soon: CUDA-accelerated VI computations
# Pattern: vi_cuda_batch(...) and vi_cuda_many_series_one_param(...)
# matching other indicators in this library.

JavaScript/WASM Bindings

Basic Usage

Calculate VI in JS/TS:

import { vi_js } from 'vectorta-wasm';

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

const { plus, minus } = vi_js(high, low, close, 14);
console.log('VI+:', plus);
console.log('VI-:', minus);
Memory-Efficient Operations

Use zero-copy style with manual memory management:

import { vi_alloc, vi_free, vi_into, memory } from 'vectorta-wasm';

const n = high.length;

// Allocate a single buffer for both outputs and split it
const basePtr = vi_alloc(n);
const plusPtr = basePtr;              // first n slots
const minusPtr = basePtr + n * 8;     // second n slots (Float64 = 8 bytes)

// Input arrays can be passed directly when using vi_into
vi_into(highPtr, lowPtr, closePtr, plusPtr, minusPtr, n, 14);

const viPlus = new Float64Array(memory.buffer, plusPtr, n).slice();
const viMinus = new Float64Array(memory.buffer, minusPtr, n).slice();

vi_free(basePtr, n);
Batch Processing

Sweep period values and reshape results:

import { vi_batch_js } from 'vectorta-wasm';

const cfg = { period_range: [7, 28, 7] };
const result = vi_batch_js(high, low, close, cfg);

const { plus, minus, periods, rows, cols } = result;

// plus/minus are flat arrays (rows * cols)
const plusMatrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  plusMatrix.push(plus.slice(start, start + cols));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators