indexes_rs/v1/macd/
main.rs

1//! # MACD (Moving Average Convergence Divergence) Module
2//!
3//! This module implements the MACD indicator using three exponential moving averages:
4//! - A fast EMA (short period)
5//! - A slow EMA (long period)
6//! - A signal EMA (for smoothing the MACD line)
7//!
8//! The MACD line is calculated as the difference between the fast and slow EMAs.
9//! The signal line is calculated as the EMA of the MACD line, and the histogram is the
10//! difference between the MACD line and the signal line. Additionally, a trading signal is
11//! generated based on the relationship between the MACD line and the signal line.
12//!
13//! # Examples
14//!
15//! ```rust
16//! use indexes_rs::v1::macd::main::MACD;
17//! use indexes_rs::v1::macd::types::MACDResult;
18//! use indexes_rs::v1::types::TradingSignal;
19//!
20//! // Create a MACD indicator with fast=12, slow=26, and signal=9 periods
21//! let mut macd = MACD::new(12, 26, 9);
22//!
23//! // Simulate feeding prices
24//! let prices = vec![44.0, 44.5, 45.0, 44.8, 45.2, 45.5, 45.3];
25//! let mut result = None;
26//! for price in prices {
27//!     result = macd.calculate(price);
28//! }
29//!
30//! if let Some(res) = result {
31//!     println!("MACD Line: {:.2}", res.macd_line);
32//!     println!("Signal Line: {:.2}", res.signal_line);
33//!     println!("Histogram: {:.2}", res.histogram);
34//!     println!("Trading Signal: {:?}", res.signal);
35//! }
36//! ```
37
38use super::types::*;
39use crate::v1::{ema::main::ExponentialMovingAverage, types::TradingSignal};
40
41/// MACD (Moving Average Convergence Divergence) indicator.
42pub struct MACD {
43    pub fast_ema: ExponentialMovingAverage,
44    pub slow_ema: ExponentialMovingAverage,
45    pub signal_ema: ExponentialMovingAverage,
46    pub histogram: Vec<f64>,
47}
48
49impl MACD {
50    /// Creates a new MACD indicator.
51    ///
52    /// # Arguments
53    ///
54    /// * `fast_period` - The period for the fast EMA.
55    /// * `slow_period` - The period for the slow EMA.
56    /// * `signal_period` - The period for the signal EMA.
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use indexes_rs::v1::macd::main::MACD;
62    ///
63    /// let macd = MACD::new(12, 26, 9);
64    /// ```
65    pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self {
66        MACD {
67            fast_ema: ExponentialMovingAverage::new(fast_period),
68            slow_ema: ExponentialMovingAverage::new(slow_period),
69            signal_ema: ExponentialMovingAverage::new(signal_period),
70            histogram: Vec::new(),
71        }
72    }
73
74    /// Updates the MACD calculation with a new price and returns the current MACD result.
75    ///
76    /// The method updates the fast and slow EMAs with the new price, calculates the MACD line
77    /// as the difference between the fast and slow EMAs, and then updates the signal EMA using
78    /// the MACD line. The histogram is computed as the difference between the MACD line and the signal line.
79    ///
80    /// # Arguments
81    ///
82    /// * `price` - The latest price.
83    ///
84    /// # Returns
85    ///
86    /// * `Some(MACDResult)` containing the MACD line, signal line, histogram, and trading signal,
87    ///   if the EMAs have been sufficiently initialized.
88    /// * `None` if any of the EMA calculations are not yet available.
89    pub fn calculate(&mut self, price: f64) -> Option<MACDResult> {
90        let fast = self.fast_ema.add_value(price)?;
91        let slow = self.slow_ema.add_value(price)?;
92        let macd_line = fast - slow;
93        let signal_line = self.signal_ema.add_value(macd_line)?;
94        let histogram = macd_line - signal_line;
95
96        self.histogram.push(histogram);
97
98        Some(MACDResult {
99            macd_line,
100            signal_line,
101            histogram,
102            signal: self.determine_signal(macd_line, signal_line),
103        })
104    }
105
106    /// Determines the trading signal based on the MACD line and the signal line.
107    ///
108    /// If the MACD line is above the signal line, returns `Buy`.
109    /// If the MACD line is below the signal line, returns `Sell`.
110    /// Otherwise, returns `Hold`.
111    ///
112    /// # Arguments
113    ///
114    /// * `macd` - The current MACD line value.
115    /// * `signal` - The current signal line value.
116    ///
117    /// # Returns
118    ///
119    /// A `TradingSignal` representing the trading recommendation.
120    pub fn determine_signal(&self, macd: f64, signal: f64) -> TradingSignal {
121        if macd > signal {
122            TradingSignal::Buy
123        } else if macd < signal {
124            TradingSignal::Sell
125        } else {
126            TradingSignal::Hold
127        }
128    }
129}