quantaxis_rs/indicators/
moving_average_convergence_divergence.rs

1use std::fmt;
2
3use crate::errors::*;
4use crate::indicators::ExponentialMovingAverage as Ema;
5use crate::{Close, Next, Reset};
6
7/// Moving average converge divergence (MACD).
8///
9/// The MACD indicator (or "oscillator") is a collection of three time series
10/// calculated from historical price data, most often the closing price.
11/// These three series are:
12///
13/// * The MACD series proper
14/// * The "signal" or "average" series
15/// * The "divergence" series which is the difference between the two
16///
17/// The MACD series is the difference between a "fast" (short period) exponential
18/// moving average (EMA), and a "slow" (longer period) EMA of the price series.
19/// The average series is an EMA of the MACD series itself.
20///
21/// # Formula
22///
23/// # Parameters
24///
25/// * _fast_length_ - length for the fast EMA. Default is 12.
26/// * _slow_length_ - length for the slow EMA. Default is 26.
27/// * _signal_length_ - length for the signal EMA. Default is 9.
28///
29/// # Example
30///
31/// ```
32/// use quantaxis_rs::indicators::MovingAverageConvergenceDivergence as Macd;
33/// use quantaxis_rs::Next;
34///
35/// let mut macd = Macd::new(3, 6, 4).unwrap();
36///
37/// assert_eq!(round(macd.next(2.0)), (0.0, 0.0, 0.0));
38/// assert_eq!(round(macd.next(3.0)), (0.21, 0.09, 0.13));
39/// assert_eq!(round(macd.next(4.2)), (0.52, 0.26, 0.26));
40/// assert_eq!(round(macd.next(7.0)), (1.15, 0.62, 0.54));
41/// assert_eq!(round(macd.next(6.7)), (1.15, 0.83, 0.32));
42/// assert_eq!(round(macd.next(6.5)), (0.94, 0.87, 0.07));
43///
44/// fn round(nums: (f64, f64, f64)) -> (f64, f64, f64) {
45///     let n0 = (nums.0 * 100.0).round() / 100.0;
46///     let n1 = (nums.1 * 100.0).round() / 100.0;
47///     let n2 = (nums.2 * 100.0).round() / 100.0;
48///     (n0, n1, n2)
49/// }
50/// ```
51#[derive(Debug, Clone)]
52pub struct MovingAverageConvergenceDivergence {
53    fast_ema: Ema,
54    slow_ema: Ema,
55    signal_ema: Ema,
56}
57
58impl MovingAverageConvergenceDivergence {
59    pub fn new(fast_length: u32, slow_length: u32, signal_length: u32) -> Result<Self> {
60        let indicator = Self {
61            fast_ema: Ema::new(fast_length)?,
62            slow_ema: Ema::new(slow_length)?,
63            signal_ema: Ema::new(signal_length)?,
64        };
65        Ok(indicator)
66    }
67}
68
69impl Next<f64> for MovingAverageConvergenceDivergence {
70    type Output = (f64, f64, f64);
71
72    fn next(&mut self, input: f64) -> Self::Output {
73        let fast_val = self.fast_ema.next(input);
74        let slow_val = self.slow_ema.next(input);
75
76        let macd = fast_val - slow_val;
77        let signal = self.signal_ema.next(macd);
78        let histogram = macd - signal;
79
80        (macd, signal, histogram)
81    }
82}
83
84impl<'a, T: Close> Next<&'a T> for MovingAverageConvergenceDivergence {
85    type Output = (f64, f64, f64);
86
87    fn next(&mut self, input: &'a T) -> Self::Output {
88        self.next(input.close())
89    }
90}
91
92impl Reset for MovingAverageConvergenceDivergence {
93    fn reset(&mut self) {
94        self.fast_ema.reset();
95        self.slow_ema.reset();
96        self.signal_ema.reset();
97    }
98}
99
100impl Default for MovingAverageConvergenceDivergence {
101    fn default() -> Self {
102        Self::new(12, 26, 9).unwrap()
103    }
104}
105
106impl fmt::Display for MovingAverageConvergenceDivergence {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        write!(
109            f,
110            "MACD({}, {}, {})",
111            self.fast_ema.length(),
112            self.slow_ema.length(),
113            self.signal_ema.length()
114        )
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::test_helper::*;
122    type Macd = MovingAverageConvergenceDivergence;
123    macro_rules! test_indicator {
124        ($i:tt) => {
125            #[test]
126            fn test_indicator() {
127                let bar = Bar::new();
128
129                // ensure Default trait is implemented
130                let mut indicator = $i::default();
131
132                // ensure Next<f64> is implemented
133                let first_output = indicator.next(12.3);
134
135                // ensure next accepts &DataItem as well
136                indicator.next(&bar);
137
138                // ensure Reset is implemented and works correctly
139                indicator.reset();
140                assert_eq!(indicator.next(12.3), first_output);
141
142                // ensure Display is implemented
143                format!("{}", indicator);
144            }
145        };
146    }
147    test_indicator!(Macd);
148
149    fn round(nums: (f64, f64, f64)) -> (f64, f64, f64) {
150        let n0 = (nums.0 * 100.0).round() / 100.0;
151        let n1 = (nums.1 * 100.0).round() / 100.0;
152        let n2 = (nums.2 * 100.0).round() / 100.0;
153        (n0, n1, n2)
154    }
155
156    #[test]
157    fn test_new() {
158        assert!(Macd::new(0, 1, 1).is_err());
159        assert!(Macd::new(1, 0, 1).is_err());
160        assert!(Macd::new(1, 1, 0).is_err());
161        assert!(Macd::new(1, 1, 1).is_ok());
162    }
163
164    #[test]
165    fn test_macd() {
166        let mut macd = Macd::new(3, 6, 4).unwrap();
167
168        assert_eq!(round(macd.next(2.0)), (0.0, 0.0, 0.0));
169        assert_eq!(round(macd.next(3.0)), (0.21, 0.09, 0.13));
170        assert_eq!(round(macd.next(4.2)), (0.52, 0.26, 0.26));
171        assert_eq!(round(macd.next(7.0)), (1.15, 0.62, 0.54));
172        assert_eq!(round(macd.next(6.7)), (1.15, 0.83, 0.32));
173        assert_eq!(round(macd.next(6.5)), (0.94, 0.87, 0.07));
174    }
175
176    #[test]
177    fn test_reset() {
178        let mut macd = Macd::new(3, 6, 4).unwrap();
179
180        assert_eq!(round(macd.next(2.0)), (0.0, 0.0, 0.0));
181        assert_eq!(round(macd.next(3.0)), (0.21, 0.09, 0.13));
182
183        macd.reset();
184
185        assert_eq!(round(macd.next(2.0)), (0.0, 0.0, 0.0));
186        assert_eq!(round(macd.next(3.0)), (0.21, 0.09, 0.13));
187    }
188
189    #[test]
190    fn test_default() {
191        Macd::default();
192    }
193
194    #[test]
195    fn test_display() {
196        let indicator = Macd::new(13, 30, 10).unwrap();
197        assert_eq!(format!("{}", indicator), "MACD(13, 30, 10)");
198    }
199}