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(
67 last_value,
68 the_average,
69 &self.circ_buf[..timeperiod],
70 timeperiod,
71 );
72 }
73
74 let new_tp = tp;
75 self.running_sum += new_tp - self.circ_buf[self.circ_idx];
76 self.circ_buf[self.circ_idx] = new_tp;
77 let the_average = self.running_sum / timeperiod as f64;
78 let out = cci_value(
79 new_tp,
80 the_average,
81 &self.circ_buf[..timeperiod],
82 timeperiod,
83 );
84 self.circ_idx += 1;
85 if self.circ_idx >= timeperiod {
86 self.circ_idx = 0;
87 }
88 out
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use proptest::prelude::*;
96
97 fn ordered_hlc(h: &[f64], l: &[f64], c: &[f64]) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
98 let len = h.len().min(l.len()).min(c.len());
99 let mut high = Vec::with_capacity(len);
100 let mut low = Vec::with_capacity(len);
101 let mut close = Vec::with_capacity(len);
102 for i in 0..len {
103 let vh = h[i];
104 let vl = l[i];
105 let vc = c[i];
106 high.push(vh.max(vl).max(vc));
107 low.push(vh.min(vl).min(vc));
108 close.push(vc);
109 }
110 (high, low, close)
111 }
112
113 proptest! {
114 #[test]
115 fn test_cci_parity(
116 h in prop::collection::vec(1.0..100.0, 10..100),
117 l in prop::collection::vec(1.0..100.0, 10..100),
118 c in prop::collection::vec(1.0..100.0, 10..100),
119 ) {
120 let (high, low, close) = ordered_hlc(&h, &l, &c);
121 let len = high.len();
122 if len == 0 { return Ok(()); }
123 let period = 14;
124 let mut cci = CCI::new(period);
125 let streaming: Vec<f64> =
126 (0..len).map(|i| cci.next((high[i], low[i], close[i]))).collect();
127 let batch = talib_rs::momentum::cci(&high, &low, &close, period)
128 .unwrap_or_else(|_| vec![f64::NAN; len]);
129 for (s, b) in streaming.iter().zip(batch.iter()) {
130 if s.is_nan() {
131 assert!(b.is_nan());
132 } else if !b.is_nan() {
133 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
134 }
135 }
136 }
137 }
138}