# CCI

> Commodity Channel Index — measures how far the current typical price
> deviates from its rolling mean, in units of mean absolute deviation
> scaled by Lambert's constant.

## Quick reference

| Field | Value |
|-------|-------|
| Family | Momentum Oscillators |
| Input type | `Candle` |
| Output type | `f64` |
| Output range | unbounded (typically `[−200, +200]` thanks to the 0.015 factor) |
| Default parameters | `period = 20` (Python) |
| Warmup period | `period` (20 for `period = 20`) |
| Interpretation | `> +100` overbought, `< −100` oversold (Lambert) |

## Formula

For each candle, compute the typical price `TP = (high + low + close) / 3`,
then over the rolling `period`-bar window:

```
SMA_TP_t = (TP_{t-period+1} + … + TP_t) / period
MAD_t    = (1 / period) · Σ |TP_i − SMA_TP_t|    for i = t-period+1 … t

CCI_t    = (TP_t − SMA_TP_t) / (factor · MAD_t)
```

The default `factor` is Lambert's `0.015`, chosen empirically so that
roughly 70–80 % of values fall inside `[−100, +100]`. The implementation
exposes the factor through `Cci::with_factor(period, factor)` if you want
to retune it for an asset with very different volatility characteristics.

When `MAD == 0` (a perfectly flat window), the implementation returns `0`
rather than dividing by zero.

## Parameters

| Name | Type | Default (Python) | Valid range | Description |
|------|------|------------------|-------------|-------------|
| `period` | `usize` | `20` | `>= 1` | Rolling window length for both the SMA of typical price and the MAD. |
| `factor` | `f64` | `0.015` (`Cci::new`) | `> 0`, finite | Lambert's scaling constant; configurable via `Cci::with_factor`. |

`Cci::new(0)` returns `Error::PeriodZero`. `Cci::with_factor(_, factor)`
returns `Error::NonPositiveMultiplier` when `factor <= 0` or non-finite.

## Inputs / Outputs

From `impl Indicator for Cci`:

```rust
type Input  = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64>;
```

Python's `CCI.batch(high, low, close)` returns a 1-D `float64` `np.ndarray`
with `NaN` during warmup. Node's `CCI.batch(high, low, close)` returns a
flat `number[]` (also `NaN` during warmup); the Node binding does not
expose a streaming `update()` (`bindings/node/index.d.ts` lists only
`constructor` and `batch`).

## Warmup

`warmup_period()` returns exactly `period`. CCI does not consume diffs —
it only needs `period` typical-price samples to populate its rolling
window before it can compute an SMA and MAD. In streaming terms, calls
`1..period` return `None`; the `period`-th call returns the first value.

## Edge cases

- **Flat input.** Every `TP` is the SMA, so `MAD == 0` and the
  implementation returns `0.0` (test `flat_candles_yield_zero`). This
  avoids the divide-by-zero that would otherwise produce `NaN` /
  `±∞`.
- **Custom factor.** `Cci::with_factor(period, factor)` lets you replace
  Lambert's `0.015`. Picking a smaller factor widens the typical range
  of CCI values; picking a larger one compresses them.
- **Reset.** `reset()` clears the rolling window and the running sum,
  returning the indicator to the freshly-constructed state.

## Examples

### Rust

```rust
use wickra::{BatchExt, Candle, Cci, Indicator};

let candles: Vec<Candle> = (0..25)
    .map(|i| {
        let m = 50.0 + i as f64;
        Candle::new(m, m + 1.0, m - 1.0, m, 1.0, 0).unwrap()
    })
    .collect();
let mut cci = Cci::new(20)?;
let out = cci.batch(&candles);
println!("row 19 = {}", out[19].unwrap());
println!("row 24 = {}", out[24].unwrap());
# Ok::<(), wickra::Error>(())
```

Verified output:

```
row 19 = 126.66666666666667
row 24 = 126.66666666666667
```

### Python

```python
import numpy as np
import wickra as ta

i = np.arange(25, dtype=float)
m = 50.0 + i
high  = m + 1.0
low   = m - 1.0
close = m
cci = ta.CCI(20)
out = cci.batch(high, low, close)
print('row 19:', out[19])
print('row 24:', out[24])
```

Verified output:

```
row 19: 126.66666666666667
row 24: 126.66666666666667
```

### Node

```javascript
const wickra = require('wickra');

const n = 25;
const high = [], low = [], close = [];
for (let i = 0; i < n; i++) {
  const m = 50 + i;
  high.push(m + 1);
  low.push(m - 1);
  close.push(m);
}
const cci = new wickra.CCI(20);
const out = cci.batch(high, low, close);
console.log('row 19:', out[19]);
console.log('row 24:', out[24]);
```

Verified output:

```
row 19: 126.66666666666667
row 24: 126.66666666666667
```

## Interpretation

- **±100 threshold.** Lambert's published convention is to treat values
  above `+100` as overbought and below `−100` as oversold. The choice
  of `0.015` for the divisor is what makes the threshold meaningful;
  changing the factor changes the threshold.
- **Zero-line cross.** `CCI` crossing zero says the typical price has
  moved through its `period`-bar mean — sometimes used as a
  trend-direction filter.
- **Divergence.** As with RSI/Stochastic, a price making a new high
  while CCI makes a lower high is a classic bearish divergence.

## Common pitfalls

- **CCI is unbounded.** Unlike RSI or Stochastic, CCI can spike well
  outside `±100` in volatile markets. Threshold-based rules should be
  paired with a maximum-absolute-value guard, or you will mis-classify
  legitimate breakouts as "extreme overbought".
- **The 0.015 factor is empirical, not derived.** It was chosen by
  Lambert in 1980 for commodity futures markets. Modern equities and
  crypto have wider distributions; if your `|CCI|` distribution sits
  almost entirely outside `±100`, retune via `Cci::with_factor` rather
  than rewriting downstream thresholds.

## References

- Donald Lambert, "Commodity Channel Index: Tools for Trading Cyclical
  Trends", *Commodities Magazine*, October 1980 — the original
  publication, including the empirical choice of `0.015`.

## See also

- [Indicator: Rsi](../momentum-oscillators/Indicator-Rsi.md) — bounded sibling for comparison.
- [Indicator: WilliamsR](../momentum-oscillators/Indicator-WilliamsR.md) — another candle-input
  oscillator, range-based rather than deviation-based.
- [Indicator: Mfi](../momentum-oscillators/Indicator-Mfi.md) — volume-weighted RSI; useful as a
  confirmation alongside CCI.
- [Warmup Periods](../../Warmup-Periods.md) — `period` (no off-by-one).
