Skip to main content

finance_query/indicators/
cci.rs

1//! Commodity Channel Index (CCI) indicator.
2
3use super::{IndicatorError, Result};
4
5/// Calculate Commodity Channel Index (CCI).
6///
7/// CCI measures the variation of a security's price from its statistical mean.
8/// Formula: CCI = (Typical Price - SMA of Typical Price) / (0.015 * Mean Deviation)
9///
10/// Typical Price = (High + Low + Close) / 3
11///
12/// # Arguments
13///
14/// * `highs` - High prices
15/// * `lows` - Low prices
16/// * `closes` - Close prices
17/// * `period` - Number of periods
18///
19/// # Example
20///
21/// ```
22/// use finance_query::indicators::cci;
23///
24/// let highs = vec![10.0, 11.0, 12.0, 13.0];
25/// let lows = vec![8.0, 9.0, 10.0, 11.0];
26/// let closes = vec![9.0, 10.0, 11.0, 12.0];
27/// let result = cci(&highs, &lows, &closes, 3).unwrap();
28/// ```
29pub fn cci(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> Result<Vec<Option<f64>>> {
30    if period == 0 {
31        return Err(IndicatorError::InvalidPeriod(
32            "Period must be greater than 0".to_string(),
33        ));
34    }
35    let len = highs.len();
36    if lows.len() != len || closes.len() != len {
37        return Err(IndicatorError::InvalidPeriod(
38            "Data lengths must match".to_string(),
39        ));
40    }
41    if len < period {
42        return Err(IndicatorError::InsufficientData {
43            need: period,
44            got: len,
45        });
46    }
47
48    let mut typical_prices = Vec::with_capacity(len);
49    for i in 0..len {
50        typical_prices.push((highs[i] + lows[i] + closes[i]) / 3.0);
51    }
52
53    let mut result = vec![None; len];
54
55    for (i, item) in result.iter_mut().enumerate().skip(period - 1) {
56        let start_idx = i + 1 - period;
57        let slice = &typical_prices[start_idx..=i];
58
59        let sum: f64 = slice.iter().sum();
60        let sma = sum / period as f64;
61
62        let deviations_sum: f64 = slice.iter().map(|&tp| (tp - sma).abs()).sum();
63        let mean_deviation = deviations_sum / period as f64;
64
65        if mean_deviation == 0.0 {
66            *item = Some(0.0);
67        } else {
68            let latest_tp = typical_prices[i];
69            let cci_val = (latest_tp - sma) / (0.015 * mean_deviation);
70            *item = Some(cci_val);
71        }
72    }
73
74    Ok(result)
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_cci() {
83        let highs = vec![10.0, 11.0, 12.0, 13.0];
84        let lows = vec![8.0, 9.0, 10.0, 11.0];
85        let closes = vec![9.0, 10.0, 11.0, 12.0];
86        let result = cci(&highs, &lows, &closes, 3).unwrap();
87
88        assert_eq!(result.len(), 4);
89        assert!(result[0].is_none());
90        assert!(result[1].is_none());
91        assert!(result[2].is_some());
92        assert!(result[3].is_some());
93    }
94}