CCI Cycle

Parameters: length = 10 | factor = 0.5 (0.1–1)

Overview

CCI Cycle transforms the Commodity Channel Index into a smoothed cyclical oscillator by applying multiple stages of filtering and normalization to isolate market rhythms from noise. The indicator begins with standard CCI calculation, then processes it through double exponential moving averages followed by smoothed moving averages to remove erratic fluctuations. Next, it applies two layers of stochastic normalization that compress the output between 0 and 100, with the factor parameter controlling the final smoothing intensity. Values above 70 indicate cycle peaks where momentum exhausts and reversals become probable, while readings below 30 mark cycle troughs presenting potential bounce opportunities. The double stochastic transformation makes CCI Cycle particularly effective at identifying oversold and overbought conditions within the context of the dominant cycle rather than absolute price levels. Traders use this indicator to time entries at cycle lows and exits at cycle highs, especially in ranging markets where traditional momentum oscillators produce excessive whipsaws.

Implementation Examples

Get started with CCI Cycle in a few lines:

use vector_ta::indicators::cci_cycle::{cci_cycle, CciCycleInput, CciCycleParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

	// From a price slice
	let prices: Vec<f64> = vec![/* your data (needs >= 2*length finite points; stable after ~4*length) */];
	let params = CciCycleParams { length: Some(10), factor: Some(0.5) };
	let input = CciCycleInput::from_slice(&prices, params);
	let out = cci_cycle(&input)?;

// From Candles using defaults (length=10, factor=0.5, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = CciCycleInput::with_default_candles(&candles);
let out = cci_cycle(&input)?;

// Access values
for v in out.values { println!("CCI Cycle: {}", v); }

API Reference

Input Methods
// From price slice
CciCycleInput::from_slice(&[f64], CciCycleParams) -> CciCycleInput

// From candles with custom source
CciCycleInput::from_candles(&Candles, &str, CciCycleParams) -> CciCycleInput

// Candles with VectorTA defaults (length=10, factor=0.5, source="close")
CciCycleInput::with_default_candles(&Candles) -> CciCycleInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct CciCycleParams {
    pub length: Option<usize>, // Default: 10
    pub factor: Option<f64>,   // Default: 0.5
}
Output Structure
pub struct CciCycleOutput {
    pub values: Vec<f64>, // Normalized 0–100 oscillator values
}
Validation, Warmup & NaNs
  • length > 0 and length ≤ data_len; otherwise CciCycleError::InvalidPeriod.
  • Requires at least length * 2 valid points after the first finite value; otherwise CciCycleError::NotEnoughValidData.
  • Empty input ⇒ CciCycleError::EmptyInputData. All NaNCciCycleError::AllValuesNaN.
  • Warmup: conservative prefix of about first_valid + 4 × length is set to NaN.
  • Kernel selection: Kernel::Auto chooses a best-fit kernel; explicit kernels are supported via cci_cycle_with_kernel/cci_cycle_into_slice.
Error Handling
use vector_ta::indicators::cci_cycle::{cci_cycle, CciCycleError, CciCycleInput, CciCycleParams};

let input = CciCycleInput::from_slice(&[], CciCycleParams::default());
match cci_cycle(&input) {
    Ok(out) => consume(out.values),
    Err(CciCycleError::EmptyInputData) => log::error!("no data provided"),
    Err(CciCycleError::AllValuesNaN) => log::error!("all values were NaN"),
    Err(CciCycleError::InvalidPeriod { period, data_len }) =>
        log::warn!("length {period} exceeds data length {data_len}"),
    Err(CciCycleError::NotEnoughValidData { needed, valid }) =>
        log::warn!("need {needed} valid points, only {valid} available"),
    Err(CciCycleError::CciError(e)) => log::error!("cci failed: {e}"),
    Err(CciCycleError::EmaError(e)) => log::error!("ema failed: {e}"),
    Err(CciCycleError::SmmaError(e)) => log::error!("smma failed: {e}"),
}

Python Bindings

Basic Usage
import numpy as np
from vector_ta import cci_cycle

prices = np.asarray(load_prices(), dtype=np.float64)  # needs >= 2*length points; stable after ~4*length
values = cci_cycle(prices, length=10, factor=0.5, kernel="auto")
print(values)

# Force specific kernel during validation
values_scalar = cci_cycle(prices, length=10, factor=0.5, kernel="scalar")
Streaming

Use CciCycleStream for incremental updates. It returns None until it has enough history, and may return NaN during warmup.

import math
from vector_ta import CciCycleStream

stream = CciCycleStream(length=10, factor=0.5)
for px in feed_prices():
    value = stream.update(float(px))
    if value is not None and math.isfinite(value):
        print(value)
Batch Processing
import numpy as np
from vector_ta import cci_cycle_batch

prices = np.asarray(load_prices(), dtype=np.float64)
result = cci_cycle_batch(
    prices,
    length_range=(10, 20, 5),
    factor_range=(0.3, 0.7, 0.2),
    kernel="auto",
)

print(result["values"].shape)   # (rows, len(prices))
print(result["lengths"])        # array of length per row
print(result["factors"])        # array of factor per row
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_cycle_cuda_batch_dev, cci_cycle_cuda_many_series_one_param_dev

prices_f32 = np.asarray(load_prices(), dtype=np.float32)
dev = cci_cycle_cuda_batch_dev(
    prices_f32,
    length_range=(10, 20, 5),
    factor_range=(0.3, 0.7, 0.2),
    device_id=0,
)

tm_f32 = np.asarray(load_time_major_matrix(), dtype=np.float32)  # shape: (rows, cols)
dev_tm = cci_cycle_cuda_many_series_one_param_dev(
    tm_f32.ravel(),
    cols=tm_f32.shape[1],
    rows=tm_f32.shape[0],
    length=10,
    factor=0.5,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage
import { cci_cycle_js } from 'vectorta-wasm';

const prices = new Float64Array(loadPrices()); // needs >= 2*length points; stable after ~4*length
const values = cci_cycle_js(prices, 10, 0.5);
console.log('CCI Cycle:', Array.from(values));
Memory-Efficient Operations
import { cci_cycle_alloc, cci_cycle_free, cci_cycle_into, memory } from 'vectorta-wasm';

const data = new Float64Array(loadPrices());
const len = data.length;
const inPtr = cci_cycle_alloc(len);
const outPtr = cci_cycle_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(data);
// in_ptr, out_ptr, len, length, factor
cci_cycle_into(inPtr, outPtr, len, 10, 0.5);

const out = new Float64Array(memory.buffer, outPtr, len).slice();
cci_cycle_free(inPtr, len);
cci_cycle_free(outPtr, len);
Batch Processing
import { cci_cycle_batch } from 'vectorta-wasm';

const prices = new Float64Array(loadPrices());
const result = cci_cycle_batch(prices, {
  length_range: [10, 20, 5],
  factor_range: [0.3, 0.7, 0.2],
});

console.log(result.rows, result.cols, result.combos.length);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaCciCycle;
use vector_ta::indicators::cci_cycle::CciCycleBatchRange;

let cuda = CudaCciCycle::new(0)?;

let data_f32: [f32] = /* ... */;
let sweep = CciCycleBatchRange::default();

let out = cuda.cci_cycle_batch_dev(&data_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:
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