finlib_ta/indicators/
commodity_channel_index.rs

1use 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/// Commodity Channel Index (CCI)
11///
12/// The commodity channel index is an oscillator originally introduced by Donald Lambert in 1980.
13///
14/// Since its introduction, the indicator has grown in popularity and is now a very common tool for
15/// traders in identifying cyclical trends not only in commodities but also equities and currencies.
16/// The CCI can be adjusted to the timeframe of the market traded on by changing the averaging period.
17///
18/// # Formula
19///
20/// CCI(_period_) = (TP - SMA(_period_) of TP) / (MAD(_period_) * 0.015)
21///
22/// # Parameters
23///
24/// * _period_ - number of periods (integer greater than 0). Default is 20.
25///
26/// # Links
27///
28/// * [Commodity Channel Index, Wikipedia](https://en.wikipedia.org/wiki/Commodity_channel_index)
29/// * [Commodity Channel Index, StockCharts](https://school.stockcharts.com/doku.php?id=technical_indicators:commodity_channel_index_cci)
30///
31#[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}