Commodity Channel Index (CCI)

Parameters: 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; otherwise CciError::InvalidPeriod.
  • Input cannot be empty; otherwise CciError::EmptyInputData.
  • If all inputs are NaN, returns CciError::AllValuesNaN.
  • There must be at least period valid points after the first finite value; otherwise CciError::NotEnoughValidData.
  • Warmup: indices before first_valid + period − 1 are NaN.
  • If MAD is zero for a window, the corresponding output is 0.0 (avoid division by zero).
  • Streaming: CciStream::update returns None until 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

Comparison:
View:

Across sizes, Rust CPU runs about 1.02× slower than Tulip C in this benchmark.

Loading chart...

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.

Related Indicators