finlib_ta/indicators/
moving_average_convergence_divergence.rs

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