Relative Volatility Index (RVI)
period = 10 | ma_len = 14 | matype = 1 | devtype = 0 Overview
Donald Dorsey created the Relative Volatility Index in 1993 as a confirming indicator that measures volatility direction rather than price momentum. The indicator divides standard deviation calculations into separate up and down components based on whether prices are rising or falling, then smooths these values to produce an oscillator between 0 and 100. When RVI reads above 50, volatility expands more during upward price movements, while readings below 50 indicate greater volatility during declines. Dorsey specifically designed RVI to complement other indicators rather than generate standalone signals, believing that confirmation between multiple tools provided more reliable trading decisions. The indicator excels at validating breakouts and trend strength because it reveals whether increasing volatility aligns with the price direction. Unlike the similar RSI which tracks price changes directly, RVI focuses purely on volatility characteristics, making it particularly useful for filtering signals during choppy or uncertain market conditions.
See also
- Relative Strength Index (RSI) — similar 0–100 oscillator using price magnitude.
- Standard Deviation — deviation option devtype=0.
- Mean Absolute Deviation — deviation option devtype=1.
- Median Absolute Deviation — deviation option devtype=2.
Interpretation & Use
- Scale and bias: Values near 50 imply balanced volatility; above 50 indicates more volatility on up moves; below 50 on down moves.
- Smoothing control: matype=0 uses SMA; matype=1 uses EMA. ma_len controls responsiveness.
- Deviation choice: devtype selects StdDev/MeanAbsDev/MedianAbsDev for period-length windows.
- Warmup: Outputs are NaN until first_non_nan + (period−1) + (ma_len−1). If both smoothed components are zero at a bar, result is NaN.
Implementation Examples
Get started with RVI using slice or candles:
use vectorta::indicators::rvi::{rvi, RviInput, RviParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = RviParams { period: Some(10), ma_len: Some(14), matype: Some(1), devtype: Some(0) };
let input = RviInput::from_slice(&prices, params);
let result = rvi(&input)?;
// From candles with default params (period=10, ma_len=14, matype=EMA, devtype=StdDev; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = RviInput::with_default_candles(&candles);
let result = rvi(&input)?;
for v in result.values { println!("rvi: {}", v); } API Reference
Input Methods ▼
// From price slice
RviInput::from_slice(&[f64], RviParams) -> RviInput
// From candles with custom source field (e.g., "close")
RviInput::from_candles(&Candles, &str, RviParams) -> RviInput
// From candles with defaults (period=10, ma_len=14, matype=EMA, devtype=StdDev; source="close")
RviInput::with_default_candles(&Candles) -> RviInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct RviParams {
pub period: Option<usize>, // Default: 10
pub ma_len: Option<usize>, // Default: 14
pub matype: Option<usize>, // Default: 1 (EMA); 0=SMA
pub devtype: Option<usize>, // Default: 0 (StdDev); 1=MAD; 2=MEDAD
} Output Structure ▼
#[derive(Debug, Clone)]
pub struct RviOutput {
pub values: Vec<f64>, // RVI values in [0, 100]; NaN during warmup
} Validation, Warmup & NaNs ▼
- Empty input →
RviError::EmptyData; allNaN→RviError::AllValuesNaN. period > 0andma_len > 0; each must be<= data_len, elseRviError::InvalidPeriod { period, ma_len, data_len }.- Require at least
(period−1) + (ma_len−1) + 1valid points after the first finite, elseRviError::NotEnoughValidData { needed, valid }. - Warmup: indices before
first_non_nan + (period−1) + (ma_len−1)set toNaN. rvi_into_slice: destination length must equal input length, elseRviError::OutputLenMismatch { dst_len, data_len }.
Error Handling ▼
use vectorta::indicators::rvi::{rvi, RviError};
match rvi(&input) {
Ok(out) => process(out.values),
Err(RviError::EmptyData) => eprintln!("Empty data provided"),
Err(RviError::AllValuesNaN) => eprintln!("All values are NaN"),
Err(RviError::InvalidPeriod { period, ma_len, data_len }) =>
eprintln!("Invalid period/ma_len: period={}, ma_len={}, data_len={}", period, ma_len, data_len),
Err(RviError::NotEnoughValidData { needed, valid }) =>
eprintln!("Not enough valid data: need {}, have {}", needed, valid),
Err(RviError::OutputLenMismatch { dst_len, data_len }) =>
eprintln!("Output len mismatch: dst={}, data_len={}", dst_len, data_len),
} Python Bindings
Basic Usage ▼
Calculate RVI from NumPy arrays with explicit parameters:
import numpy as np
from vectorta import rvi, RviStream, rvi_batch
prices = np.array([100.0, 102.0, 101.5, 103.0], dtype=np.float64)
# Basic calculation (period=10, ma_len=14, matype=1=EMA, devtype=0=StdDev)
values = rvi(prices, period=10, ma_len=14, matype=1, devtype=0)
# Streaming API currently returns None for all updates
stream = RviStream(period=10, ma_len=14, matype=1, devtype=0)
for x in prices:
assert stream.update(x) is None Batch Sweep ▼
Sweep parameter ranges and reshape the results:
import numpy as np
from vectorta import rvi_batch
prices = np.random.rand(1000).astype(np.float64)
out = rvi_batch(
prices,
period_range=(5, 20, 5),
ma_len_range=(10, 20, 5),
matype_range=(1, 1, 0),
devtype_range=(0, 2, 1),
kernel="auto",
)
# out: {'values': ndarray[rows, cols], 'periods': [...], 'ma_lens': [...], 'matypes': [...], 'devtypes': [...]}
print(out['values'].shape, len(out['periods'])) CUDA Acceleration ▼
CUDA support for RVI is coming soon.
JavaScript/WASM Bindings
Basic Usage ▼
Compute RVI directly from a Float64Array:
import { rvi_js } from 'vectorta-wasm';
const prices = new Float64Array([/* data */]);
const values = rvi_js(prices, /*period=*/10, /*ma_len=*/14, /*matype=*/1, /*devtype=*/0);
console.log(values); Memory-Efficient Operations ▼
Use zero-copy alloc/into helpers:
import { rvi_alloc, rvi_free, rvi_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const n = prices.length;
const inPtr = rvi_alloc(n);
const outPtr = rvi_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);
// into: (in_ptr, out_ptr, len, period, ma_len, matype, devtype)
rvi_into(inPtr, outPtr, n, 10, 14, 1, 0);
const rvi = new Float64Array(memory.buffer, outPtr, n).slice();
rvi_free(inPtr, n);
rvi_free(outPtr, n); Batch Processing ▼
Use the unified batch API with range config:
import { rvi_batch } from 'vectorta-wasm';
const prices = new Float64Array([/* historical prices */]);
const cfg = {
period_range: [5, 20, 5],
ma_len_range: [10, 20, 5],
matype_range: [1, 1, 0],
devtype_range: [0, 2, 1],
};
const out = rvi_batch(prices, cfg);
// out: { values: Float64Array, periods: number[], ma_lens: number[], matypes: number[], devtypes: number[], rows: number, cols: number }
// Reshape into matrix
const rows = out.rows, cols = out.cols;
const matrix: number[][] = [];
for (let r = 0; r < rows; r++) {
const start = r * cols;
matrix.push(Array.from(out.values.slice(start, start + cols)));
}
// Access a specific combo row
const i = out.periods.findIndex((p, idx) => p === 10 && out.ma_lens[idx] === 14 && out.matypes[idx] === 1 && out.devtypes[idx] === 0);
const rvi10_14 = i >= 0 ? matrix[i] : undefined; Export Code
use vectorta::indicators::rvi;
// Calculate RVI with custom parameters
let result = rvi(&data, 10, 14, 1, 0);
// Or using the builder pattern
let result = indicators::rvi::new()
.period(10)
.ma_len(14)
.matype(1)
.devtype(0)
.calculate(&data);This code snippet shows how to use the RVI indicator with your current parameter settings.
Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05