finance_query/indicators/
cci.rs1use super::{IndicatorError, Result};
4
5pub 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 let period_f = period as f64;
58 let mut window_sum: f64 = typical_prices[..period].iter().sum();
59
60 for i in (period - 1)..len {
61 if i > period - 1 {
62 window_sum += typical_prices[i] - typical_prices[i - period];
63 }
64 let sma = window_sum / period_f;
65 let start_idx = i + 1 - period;
66 let slice = &typical_prices[start_idx..=i];
67 let deviations_sum: f64 = slice.iter().map(|&tp| (tp - sma).abs()).sum();
68 let mean_deviation = deviations_sum / period_f;
69
70 result[i] = Some(if mean_deviation == 0.0 {
71 0.0
72 } else {
73 (typical_prices[i] - sma) / (0.015 * mean_deviation)
74 });
75 }
76
77 Ok(result)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_cci() {
86 let highs = vec![10.0, 11.0, 12.0, 13.0];
87 let lows = vec![8.0, 9.0, 10.0, 11.0];
88 let closes = vec![9.0, 10.0, 11.0, 12.0];
89 let result = cci(&highs, &lows, &closes, 3).unwrap();
90
91 assert_eq!(result.len(), 4);
92 assert!(result[0].is_none());
93 assert!(result[1].is_none());
94 assert!(result[2].is_some());
95 assert!(result[3].is_some());
96 }
97}