finance_query/indicators/
macd.rs1use super::{IndicatorError, Result, ema::ema};
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_ema = ema(data, fast_period);
77 let slow_ema = ema(data, slow_period);
78
79 let mut macd_line = Vec::with_capacity(data.len());
81 for i in 0..data.len() {
82 match (fast_ema[i], slow_ema[i]) {
83 (Some(fast), Some(slow)) => macd_line.push(Some(fast - slow)),
84 _ => macd_line.push(None),
85 }
86 }
87
88 let macd_values: Vec<f64> = macd_line.iter().filter_map(|&v| v).collect();
90
91 let signal_ema = ema(&macd_values, signal_period);
93
94 let mut signal_line = vec![None; data.len()];
96 let mut signal_idx = 0;
97 for i in 0..data.len() {
98 if macd_line[i].is_some() {
99 signal_line[i] = signal_ema.get(signal_idx).copied().flatten();
100 signal_idx += 1;
101 }
102 }
103
104 let mut histogram = Vec::with_capacity(data.len());
106 for i in 0..data.len() {
107 match (macd_line[i], signal_line[i]) {
108 (Some(macd), Some(signal)) => histogram.push(Some(macd - signal)),
109 _ => histogram.push(None),
110 }
111 }
112
113 Ok(MacdResult {
114 macd_line,
115 signal_line,
116 histogram,
117 })
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_macd_basic() {
126 let data: Vec<f64> = (1..=50).map(|x| x as f64).collect();
127 let result = macd(&data, 12, 26, 9).unwrap();
128
129 assert_eq!(result.macd_line.len(), 50);
130 assert_eq!(result.signal_line.len(), 50);
131 assert_eq!(result.histogram.len(), 50);
132
133 assert!(result.macd_line[0].is_none());
135 assert!(result.signal_line[0].is_none());
136 assert!(result.histogram[0].is_none());
137
138 assert!(result.macd_line[40].is_some());
140 }
141
142 #[test]
143 fn test_macd_invalid_periods() {
144 let data: Vec<f64> = (1..=50).map(|x| x as f64).collect();
145
146 let result = macd(&data, 26, 12, 9);
148 assert!(result.is_err());
149
150 let result = macd(&data, 0, 26, 9);
152 assert!(result.is_err());
153 }
154
155 #[test]
156 fn test_macd_insufficient_data() {
157 let data = vec![1.0, 2.0, 3.0];
158 let result = macd(&data, 12, 26, 9);
159
160 assert!(result.is_err());
161 }
162}