quantwave_core/indicators/incremental/
cci.rs1use crate::traits::Next;
6
7#[inline]
8fn cci_value(tp: f64, average: f64, circ_buf: &[f64], timeperiod: usize) -> f64 {
9 let tp_f = timeperiod as f64;
10 let mut mean_dev_sum = 0.0_f64;
11 for j in 0..timeperiod {
12 mean_dev_sum += (circ_buf[j] - average).abs();
13 }
14 let mean_dev = mean_dev_sum / tp_f;
15 if mean_dev > 0.0 {
16 (tp - average) / (0.015 * mean_dev)
17 } else {
18 0.0
19 }
20}
21
22#[derive(Debug, Clone)]
24#[allow(non_camel_case_types)]
25pub struct CCI {
26 pub timeperiod: usize,
27 circ_buf: Vec<f64>,
28 circ_idx: usize,
29 running_sum: f64,
30 bars_seen: usize,
31}
32
33impl CCI {
34 pub fn new(timeperiod: usize) -> Self {
35 Self {
36 timeperiod,
37 circ_buf: vec![0.0; timeperiod.max(1)],
38 circ_idx: 0,
39 running_sum: 0.0,
40 bars_seen: 0,
41 }
42 }
43}
44
45impl Next<(f64, f64, f64)> for CCI {
46 type Output = f64;
47
48 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
49 let timeperiod = self.timeperiod;
50 if timeperiod < 2 {
51 return f64::NAN;
52 }
53 let lookback = timeperiod - 1;
54 let tp = (high + low + close) / 3.0;
55 self.bars_seen += 1;
56 let i = self.bars_seen - 1;
57
58 if i < timeperiod {
59 self.circ_buf[i] = tp;
60 self.running_sum += tp;
61 if i < lookback {
62 return f64::NAN;
63 }
64 let last_value = self.circ_buf[lookback];
65 let the_average = self.running_sum / timeperiod as f64;
66 return cci_value(last_value, the_average, &self.circ_buf[..timeperiod], timeperiod);
67 }
68
69 let new_tp = tp;
70 self.running_sum += new_tp - self.circ_buf[self.circ_idx];
71 self.circ_buf[self.circ_idx] = new_tp;
72 let the_average = self.running_sum / timeperiod as f64;
73 let out = cci_value(new_tp, the_average, &self.circ_buf[..timeperiod], timeperiod);
74 self.circ_idx += 1;
75 if self.circ_idx >= timeperiod {
76 self.circ_idx = 0;
77 }
78 out
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use proptest::prelude::*;
86
87 fn ordered_hlc(
88 h: &[f64],
89 l: &[f64],
90 c: &[f64],
91 ) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
92 let len = h.len().min(l.len()).min(c.len());
93 let mut high = Vec::with_capacity(len);
94 let mut low = Vec::with_capacity(len);
95 let mut close = Vec::with_capacity(len);
96 for i in 0..len {
97 let vh = h[i];
98 let vl = l[i];
99 let vc = c[i];
100 high.push(vh.max(vl).max(vc));
101 low.push(vh.min(vl).min(vc));
102 close.push(vc);
103 }
104 (high, low, close)
105 }
106
107 proptest! {
108 #[test]
109 fn test_cci_parity(
110 h in prop::collection::vec(1.0..100.0, 10..100),
111 l in prop::collection::vec(1.0..100.0, 10..100),
112 c in prop::collection::vec(1.0..100.0, 10..100),
113 ) {
114 let (high, low, close) = ordered_hlc(&h, &l, &c);
115 let len = high.len();
116 if len == 0 { return Ok(()); }
117 let period = 14;
118 let mut cci = CCI::new(period);
119 let streaming: Vec<f64> =
120 (0..len).map(|i| cci.next((high[i], low[i], close[i]))).collect();
121 let batch = talib_rs::momentum::cci(&high, &low, &close, period)
122 .unwrap_or_else(|_| vec![f64::NAN; len]);
123 for (s, b) in streaming.iter().zip(batch.iter()) {
124 if s.is_nan() {
125 assert!(b.is_nan());
126 } else if !b.is_nan() {
127 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
128 }
129 }
130 }
131 }
132}