1use quant_primitives::Candle;
4
5use crate::ema::Ema;
6use crate::error::IndicatorError;
7use crate::indicator::Indicator;
8use crate::series::Series;
9
10#[derive(Debug, Clone)]
42pub struct Macd {
43 fast_period: usize,
44 slow_period: usize,
45 name: String,
46}
47
48impl Macd {
49 pub fn new(fast_period: usize, slow_period: usize) -> Result<Self, IndicatorError> {
60 if fast_period == 0 || slow_period == 0 {
61 return Err(IndicatorError::InvalidParameter {
62 message: "MACD periods must be > 0".to_string(),
63 });
64 }
65 if fast_period >= slow_period {
66 return Err(IndicatorError::InvalidParameter {
67 message: format!(
68 "MACD fast period ({}) must be < slow period ({})",
69 fast_period, slow_period
70 ),
71 });
72 }
73 Ok(Self {
74 fast_period,
75 slow_period,
76 name: format!("MACD({},{})", fast_period, slow_period),
77 })
78 }
79
80 pub fn standard() -> Result<Self, IndicatorError> {
82 Self::new(12, 26)
83 }
84}
85
86impl Indicator for Macd {
87 fn name(&self) -> &str {
88 &self.name
89 }
90
91 fn warmup_period(&self) -> usize {
92 self.slow_period
93 }
94
95 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
96 if candles.len() < self.slow_period {
97 return Err(IndicatorError::InsufficientData {
98 required: self.slow_period,
99 actual: candles.len(),
100 });
101 }
102
103 let fast_ema = Ema::new(self.fast_period)?;
104 let slow_ema = Ema::new(self.slow_period)?;
105
106 let fast_series = fast_ema.compute(candles)?;
107 let slow_series = slow_ema.compute(candles)?;
108
109 let offset = self.slow_period - self.fast_period;
111 let fast_values = fast_series.values();
112 let slow_values = slow_series.values();
113
114 let mut values = Vec::with_capacity(slow_values.len());
115 for (i, (ts, slow_val)) in slow_values.iter().enumerate() {
116 let fast_val = fast_values[i + offset].1;
117 let macd = fast_val - *slow_val;
118 values.push((*ts, macd));
119 }
120
121 Ok(Series::new(values))
122 }
123}
124
125#[cfg(test)]
126#[path = "macd_tests.rs"]
127mod tests;