Chaikin's Volatility (CVI)
period = 10 Overview
Chaikin's Volatility (CVI), developed by Marc Chaikin, measures the rate of change in trading range to identify volatility expansion and contraction patterns. The indicator calculates an exponential moving average of the daily trading range (high minus low), then compares the current EMA value to its value from the same number of periods ago. This percentage change reveals whether volatility is increasing or decreasing relative to recent history. The formula essentially tracks how quickly the average trading range is expanding or contracting over time.
CVI oscillates above and below zero, with positive values indicating expanding volatility and negative values showing volatility contraction. Sharp increases in CVI often occur at market bottoms when fear drives wider trading ranges. Conversely, CVI typically declines during quiet accumulation phases or market tops when complacency reduces daily price swings. The indicator reaches extreme positive readings during panic selling or volatile corrections. Extreme negative readings suggest unusually quiet market conditions that often precede significant moves.
Traders use CVI to anticipate potential breakouts and adjust their strategies to current volatility conditions. Rising CVI values warn of increasing market uncertainty, prompting traders to widen stops or reduce position sizes. Declining CVI suggests consolidation periods where range trading strategies may work better than trend following approaches. Many traders combine CVI with price patterns, watching for volatility expansion to confirm breakouts from consolidation areas. The indicator also helps options traders time their strategies, as expanding volatility typically increases option premiums while contracting volatility creates opportunities for selling premium.
Implementation Examples
Get started with CVI in a few lines:
use vectorta::indicators::cvi::{cvi, CviInput, CviParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with high/low slices
let high = vec![10.0, 12.0, 11.5, 13.0, 12.8];
let low = vec![ 9.0, 10.5, 10.8, 12.1, 11.9];
let params = CviParams { period: Some(10) };
let input = CviInput::from_slices(&high, &low, params);
let result = cvi(&input)?;
// Using with Candles (defaults: period=10)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = CviInput::with_default_candles(&candles);
let result = cvi(&input)?;
// Access CVI values (percentage change)
for value in result.values {
println!("CVI: {}", value);
} API Reference
Input Methods ▼
// From high/low slices
CviInput::from_slices(&[f64], &[f64], CviParams) -> CviInput
// From candles with custom params
CviInput::from_candles(&Candles, CviParams) -> CviInput
// From candles with default params (period=10)
CviInput::with_default_candles(&Candles) -> CviInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct CviParams {
pub period: Option<usize>, // Default: 10
} Output Structure ▼
#[derive(Debug, Clone)]
pub struct CviOutput {
pub values: Vec<f64>, // CVI values (% change of EMA(range) vs lagged)
} Validation, Warmup & NaNs ▼
high.len() == low.len()and both non-empty; otherwiseCviError::EmptyData.period > 0andperiod ≤ len; otherwiseCviError::InvalidPeriod.- Finds the first index where both inputs are finite; if none:
CviError::AllValuesNaN. - Needs at least
2*period - 1valid points after the first finite value; elseCviError::NotEnoughValidData. - Outputs are
NaNup to indexfirst_valid_idx + 2*period - 1. - Streaming:
CviStream::updatereturnsNoneuntil its internal lag buffer fills, then returnsSome(f64).
Error Handling ▼
use vectorta::indicators::cvi::{cvi, CviError, CviInput, CviParams};
let input = CviInput::from_slices(&high, &low, CviParams { period: Some(10) });
match cvi(&input) {
Ok(output) => handle(output.values),
Err(CviError::EmptyData) => eprintln!("no data or length mismatch"),
Err(CviError::InvalidPeriod { period, data_len }) => {
eprintln!("invalid period: {} (len={})", period, data_len)
}
Err(CviError::NotEnoughValidData { needed, valid }) => {
eprintln!("need {} valid points, only {}", needed, valid)
}
Err(CviError::AllValuesNaN) => eprintln!("all inputs are NaN"),
} Python Bindings
Basic Usage ▼
Calculate CVI from NumPy arrays of highs and lows:
import numpy as np
from vectorta import cvi
high = np.array([10.0, 12.0, 11.5, 13.0, 12.8])
low = np.array([ 9.0, 10.5, 10.8, 12.1, 11.9])
# Required: period
values = cvi(high, low, period=10, kernel="auto")
print("CVI:", values) Streaming Real-time Updates ▼
Stream per-candle updates using the first candle to seed state:
from vectorta import CviStream
stream = CviStream(period=10, initial_high=first_high, initial_low=first_low)
for (h, l) in hl_feed:
val = stream.update(h, l)
if val is not None:
print("CVI:", val) Batch Parameter Sweep ▼
Test many period settings and compare results:
import numpy as np
from vectorta import cvi_batch
high = np.array([...])
low = np.array([...])
res = cvi_batch(high, low, period_range=(5, 20, 5), kernel="auto")
# Results
values = res["values"] # shape: (num_periods, len(series))
periods = res["periods"] # list of periods tested
print(values.shape, periods) CUDA Acceleration ▼
CUDA support for CVI is coming soon. The API will follow the same pattern as other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated CVI calculations
# Pattern will mirror other CUDA-enabled indicators in this library. JavaScript/WASM Bindings
Basic Usage ▼
Calculate CVI in JavaScript/TypeScript:
import { cvi_js } from 'vectorta-wasm';
const high = new Float64Array([10.0, 12.0, 11.5, 13.0, 12.8]);
const low = new Float64Array([ 9.0, 10.5, 10.8, 12.1, 11.9]);
// Calculate CVI with period=10
const values = cvi_js(high, low, 10);
console.log('CVI:', values); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { cvi_alloc, cvi_free, cvi_into, memory } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const len = high.length;
const highPtr = cvi_alloc(len);
const lowPtr = cvi_alloc(len);
const outPtr = cvi_alloc(len);
new Float64Array(memory.buffer, highPtr, len).set(high);
new Float64Array(memory.buffer, lowPtr, len).set(low);
// Write results directly into outPtr
cvi_into(highPtr, lowPtr, outPtr, len, 10);
const values = new Float64Array(memory.buffer, outPtr, len).slice();
// Free allocated memory
cvi_free(highPtr, len);
cvi_free(lowPtr, len);
cvi_free(outPtr, len); Batch Processing ▼
Test multiple periods efficiently:
import { cvi_batch } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
// Unified API: returns values, combos, rows, cols
const cfg = { period_range: [5, 20, 5] } as const;
const out = cvi_batch(high, low, cfg);
console.log(out.rows, out.cols);
console.log(out.combos); // Array of { period }
`}
Performance Analysis
Across sizes, Rust CPU runs about 1.09× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05