finlib_ta/indicators/
commodity_channel_index.rs1use core::fmt;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use crate::errors::Result;
7use crate::indicators::{MeanAbsoluteDeviation, SimpleMovingAverage};
8use crate::{Close, High, Low, Next, Period, Reset};
9
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[derive(Debug, Clone)]
33pub struct CommodityChannelIndex {
34 sma: SimpleMovingAverage,
35 mad: MeanAbsoluteDeviation,
36}
37
38impl CommodityChannelIndex {
39 pub fn new(period: usize) -> Result<Self> {
40 Ok(Self {
41 sma: SimpleMovingAverage::new(period)?,
42 mad: MeanAbsoluteDeviation::new(period)?,
43 })
44 }
45}
46
47impl Period for CommodityChannelIndex {
48 fn period(&self) -> usize {
49 self.sma.period()
50 }
51}
52
53impl<T: Close + High + Low> Next<&T> for CommodityChannelIndex {
54 type Output = f64;
55
56 fn next(&mut self, input: &T) -> Self::Output {
57 let tp = (input.close() + input.high() + input.low()) / 3.0;
58 let sma = self.sma.next(tp);
59 let mad = self.mad.next(input);
60
61 if mad == 0.0 {
62 return 0.0;
63 }
64
65 (tp - sma) / (mad * 0.015)
66 }
67}
68
69impl Reset for CommodityChannelIndex {
70 fn reset(&mut self) {
71 self.sma.reset();
72 self.mad.reset();
73 }
74}
75
76impl Default for CommodityChannelIndex {
77 fn default() -> Self {
78 Self::new(20).unwrap()
79 }
80}
81
82impl fmt::Display for CommodityChannelIndex {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 write!(f, "CCI({})", self.sma.period())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::test_helper::*;
92 use alloc::format;
93
94 #[test]
95 fn test_new() {
96 assert!(CommodityChannelIndex::new(0).is_err());
97 assert!(CommodityChannelIndex::new(1).is_ok());
98 }
99
100 #[test]
101 fn test_next_bar() {
102 let mut cci = CommodityChannelIndex::new(5).unwrap();
103
104 let bar1 = Bar::new().high(2).low(1).close(1.5);
105 assert_eq!(round(cci.next(&bar1)), 0.0);
106
107 let bar2 = Bar::new().high(5).low(3).close(4);
108 assert_eq!(round(cci.next(&bar2)), 66.667);
109
110 let bar3 = Bar::new().high(9).low(7).close(8);
111 assert_eq!(round(cci.next(&bar3)), 100.0);
112
113 let bar4 = Bar::new().high(5).low(3).close(4);
114 assert_eq!(round(cci.next(&bar4)), -13.793);
115
116 let bar5 = Bar::new().high(5).low(3).close(4);
117 assert_eq!(round(cci.next(&bar5)), -13.514);
118
119 let bar6 = Bar::new().high(2).low(1).close(1.5);
120 assert_eq!(round(cci.next(&bar6)), -126.126);
121 }
122
123 #[test]
124 fn test_reset() {
125 let mut cci = CommodityChannelIndex::new(5).unwrap();
126
127 let bar1 = Bar::new().high(2).low(1).close(1.5);
128 let bar2 = Bar::new().high(5).low(3).close(4);
129
130 assert_eq!(round(cci.next(&bar1)), 0.0);
131 assert_eq!(round(cci.next(&bar2)), 66.667);
132
133 cci.reset();
134
135 assert_eq!(round(cci.next(&bar1)), 0.0);
136 assert_eq!(round(cci.next(&bar2)), 66.667);
137 }
138
139 #[test]
140 fn test_default() {
141 CommodityChannelIndex::default();
142 }
143
144 #[test]
145 fn test_display() {
146 let indicator = CommodityChannelIndex::new(10).unwrap();
147 assert_eq!(format!("{}", indicator), "CCI(10)");
148 }
149}