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}