finance_query/indicators/
macd.rs1use super::{IndicatorError, Result, ema::ema_raw};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct MacdResult {
9 pub macd_line: Vec<Option<f64>>,
11
12 pub signal_line: Vec<Option<f64>>,
14
15 pub histogram: Vec<Option<f64>>,
17}
18
19pub fn macd(
50 data: &[f64],
51 fast_period: usize,
52 slow_period: usize,
53 signal_period: usize,
54) -> Result<MacdResult> {
55 if fast_period == 0 || slow_period == 0 || signal_period == 0 {
56 return Err(IndicatorError::InvalidPeriod(
57 "All periods must be greater than 0".to_string(),
58 ));
59 }
60
61 if fast_period >= slow_period {
62 return Err(IndicatorError::InvalidPeriod(
63 "Fast period must be less than slow period".to_string(),
64 ));
65 }
66
67 let min_data_points = slow_period + signal_period;
68 if data.len() < min_data_points {
69 return Err(IndicatorError::InsufficientData {
70 need: min_data_points,
71 got: data.len(),
72 });
73 }
74
75 let fast_raw = ema_raw(data, fast_period); let slow_raw = ema_raw(data, slow_period); let shift = slow_period - fast_period;
82 let macd_values: Vec<f64> = slow_raw
83 .iter()
84 .enumerate()
85 .map(|(k, &s)| fast_raw[k + shift] - s)
86 .collect();
87
88 let signal_raw = ema_raw(&macd_values, signal_period);
90
91 let macd_start = slow_period - 1;
93 let signal_start = macd_start + signal_period - 1;
94 let n = data.len();
95
96 let mut macd_line = vec![None; n];
97 let mut signal_line = vec![None; n];
98 let mut histogram = vec![None; n];
99
100 for (k, &mv) in macd_values.iter().enumerate() {
101 let i = k + macd_start;
102 macd_line[i] = Some(mv);
103 }
104 for (k, &sv) in signal_raw.iter().enumerate() {
105 let i = k + signal_start;
106 signal_line[i] = Some(sv);
107 if let Some(mv) = macd_line[i] {
108 histogram[i] = Some(mv - sv);
109 }
110 }
111
112 Ok(MacdResult {
113 macd_line,
114 signal_line,
115 histogram,
116 })
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_macd_basic() {
125 let data: Vec<f64> = (1..=50).map(|x| x as f64).collect();
126 let result = macd(&data, 12, 26, 9).unwrap();
127
128 assert_eq!(result.macd_line.len(), 50);
129 assert_eq!(result.signal_line.len(), 50);
130 assert_eq!(result.histogram.len(), 50);
131
132 assert!(result.macd_line[0].is_none());
134 assert!(result.signal_line[0].is_none());
135 assert!(result.histogram[0].is_none());
136
137 assert!(result.macd_line[40].is_some());
139 }
140
141 #[test]
142 fn test_macd_invalid_periods() {
143 let data: Vec<f64> = (1..=50).map(|x| x as f64).collect();
144
145 let result = macd(&data, 26, 12, 9);
147 assert!(result.is_err());
148
149 let result = macd(&data, 0, 26, 9);
151 assert!(result.is_err());
152 }
153
154 #[test]
155 fn test_macd_insufficient_data() {
156 let data = vec![1.0, 2.0, 3.0];
157 let result = macd(&data, 12, 26, 9);
158
159 assert!(result.is_err());
160 }
161}