Commodity Channel Index (CCI)
period = 14 Overview
CCI measures the deviation of price from its statistical mean by calculating how many mean absolute deviations the current typical price sits away from its moving average. Donald Lambert developed this indicator to identify cyclical turns in commodities, though it works equally well across all markets. The calculation takes typical price (high plus low plus close divided by three), subtracts its simple moving average over the specified period, then divides by the mean absolute deviation multiplied by 0.015. This constant scaling factor ensures that approximately 70 to 80 percent of CCI values fall between -100 and +100 under normal market conditions. Readings above +100 indicate price sits significantly above its average, signaling potential overbought conditions or strong uptrend momentum depending on context. Values below -100 suggest oversold conditions or powerful downtrends. Traders use CCI to spot divergences when price makes new highs while CCI fails to exceed previous peaks, warning of weakening momentum, and to identify trend emergence when CCI moves from below -100 to above +100 rapidly.
Implementation Examples
Get started with CCI from slices or candles:
use vector_ta::indicators::cci::{cci, CciInput, CciParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price slice (typical price series)
let tp: Vec<f64> = vec![/* ... */]; // ensure tp.len() >= period
let input = CciInput::from_slice(&tp, CciParams { period: Some(14) });
let result = cci(&input)?;
// Using with Candles (defaults: period=14, source="hlc3")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = CciInput::with_default_candles(&candles);
let result = cci(&input)?;
// Access the CCI values
for v in result.values { println!("CCI: {}", v); } API Reference
Input Methods ▼
// From price slice (typical price series)
CciInput::from_slice(&[f64], CciParams) -> CciInput
// From candles with custom source (e.g., "close", "hlc3")
CciInput::from_candles(&Candles, &str, CciParams) -> CciInput
// From candles with default params (source="hlc3", period=14)
CciInput::with_default_candles(&Candles) -> CciInput Parameters Structure ▼
pub struct CciParams {
pub period: Option<usize>, // Default: 14
} Output Structure ▼
pub struct CciOutput {
pub values: Vec<f64>, // CCI values
} Validation, Warmup & NaNs ▼
period > 0; otherwiseCciError::InvalidPeriod.- Input cannot be empty; otherwise
CciError::EmptyInputData. - If all inputs are
NaN, returnsCciError::AllValuesNaN. - There must be at least
periodvalid points after the first finite value; otherwiseCciError::NotEnoughValidData. - Warmup: indices before
first_valid + period − 1areNaN. - If MAD is zero for a window, the corresponding output is
0.0(avoid division by zero). - Streaming:
CciStream::updatereturnsNoneuntil the buffer is filled.
Error Handling ▼
use vector_ta::indicators::cci::{cci, CciError, CciInput, CciParams};
let params = CciParams { period: Some(0) };
let input = CciInput::from_slice(&[], params);
match cci(&input) {
Ok(output) => process(output.values),
Err(CciError::EmptyInputData) => eprintln!("input data is empty"),
Err(CciError::AllValuesNaN) => eprintln!("all values are NaN"),
Err(CciError::InvalidPeriod { period, data_len }) =>
eprintln!("invalid period: period = {}, data_len = {}", period, data_len),
Err(CciError::NotEnoughValidData { needed, valid }) =>
eprintln!("not enough valid data: needed = {}, valid = {}", needed, valid),
} Python Bindings
Basic Usage ▼
Operate on NumPy arrays of typical prices:
import numpy as np
from vector_ta import cci
typical = np.asarray(tp_series, dtype=np.float64) # length >= period
values = cci(typical, period=14)
# Optional: choose a specific kernel ("auto", "scalar")
values_scalar = cci(typical, period=14, kernel="scalar") Streaming Updates ▼
Maintain a rolling CCI value:
from vector_ta import CciStream
stream = CciStream(period=14)
for candle in ohlc_generator():
tp = (candle.high + candle.low + candle.close) / 3.0
val = stream.update(tp)
if val is not None:
handle_stream_value(candle.timestamp, val) Batch Processing ▼
Sweep period ranges in one call:
import numpy as np
from vector_ta import cci_batch
tp = np.asarray(tp_series, dtype=np.float64)
result = cci_batch(tp, period_range=(10, 40, 5), kernel="auto")
values = result['values'] # shape: (num_combinations, len(tp))
periods = result['periods'] # list of periods used (usize)
print(values.shape, periods) CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible). Rust CUDA wrappers are documented below.
import numpy as np
from vector_ta import cci_cuda_batch_dev, cci_cuda_many_series_one_param_dev
tp_f32 = np.asarray(tp_series, dtype=np.float32)
dev = cci_cuda_batch_dev(tp_f32, period_range=(10, 40, 5), device_id=0)
tm_f32 = np.asarray(tp_matrix, dtype=np.float32) # shape: (rows, cols)
dev_tm = cci_cuda_many_series_one_param_dev(
tm_f32.ravel(),
cols=tm_f32.shape[1],
rows=tm_f32.shape[0],
period=14,
device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Calculate CCI in JavaScript/TypeScript:
import { cci_js } from 'vectorta-wasm';
const tp = new Float64Array(loadTypicalPrices()); // length >= period
const values = cci_js(tp, 14);
console.log('CCI values:', values); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { cci_alloc, cci_free, cci_into, memory } from 'vectorta-wasm';
const tp = new Float64Array([/* your data */]);
const len = tp.length;
const inPtr = cci_alloc(len);
const outPtr = cci_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(tp);
cci_into(inPtr, outPtr, len, 14);
const cciValues = new Float64Array(memory.buffer, outPtr, len).slice();
cci_free(inPtr, len);
cci_free(outPtr, len); Batch Processing ▼
Test multiple period choices:
import { cci_batch_js, cci_batch_metadata_js, cci_batch as cci_batch_unified } from 'vectorta-wasm';
const tp = new Float64Array([/* historical typical prices */]);
// Metadata: [period1, period2, ...]
const metadata = cci_batch_metadata_js(10, 40, 5);
// Flat results: concatenate rows
const flat = cci_batch_js(tp, 10, 40, 5);
// Unified API: structured result
const unified = cci_batch_unified(tp, { period_range: [10, 40, 5] });
console.log(unified.rows, unified.cols, unified.combos.length); CUDA Bindings (Rust)
use vector_ta::cuda::CudaCci;
use vector_ta::indicators::cci::CciBatchRange;
let cuda = CudaCci::new(0)?;
let data_f32: [f32] = /* ... */;
let sweep = CciBatchRange::default();
let out = cuda.cci_batch_dev(&data_f32, &sweep)?;
let _ = out; Performance Analysis
Across sizes, Rust CPU runs about 1.02× slower than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28
CUDA note
In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.