quantwave_core/indicators/incremental/
macd.rs1use crate::traits::Next;
4
5const NAN_TRIPLE: (f64, f64, f64) = (f64::NAN, f64::NAN, f64::NAN);
6
7#[derive(Debug, Clone)]
9#[allow(non_camel_case_types)]
10pub struct MACD {
11 pub fastperiod: usize,
12 pub slowperiod: usize,
13 pub signalperiod: usize,
14 fp: usize,
15 sp: usize,
16 k_fast: f64,
17 k_slow: f64,
18 k_signal: f64,
19 out_start: usize,
20 bars_seen: usize,
21 seed_closes: Vec<f64>,
22 slow_ema: f64,
23 fast_ema: f64,
24 macd_values: Vec<f64>,
25 signal_ema: f64,
26}
27
28impl MACD {
29 pub fn new(fastperiod: usize, slowperiod: usize, signalperiod: usize) -> Self {
30 let (fp, sp) = if fastperiod < slowperiod {
31 (fastperiod, slowperiod)
32 } else {
33 (slowperiod, fastperiod)
34 };
35 Self {
36 fastperiod,
37 slowperiod,
38 signalperiod,
39 fp,
40 sp,
41 k_fast: 2.0 / (fp as f64 + 1.0),
42 k_slow: 2.0 / (sp as f64 + 1.0),
43 k_signal: 2.0 / (signalperiod as f64 + 1.0),
44 out_start: sp - 1 + signalperiod - 1,
45 bars_seen: 0,
46 seed_closes: Vec::with_capacity(sp),
47 slow_ema: 0.0,
48 fast_ema: 0.0,
49 macd_values: Vec::new(),
50 signal_ema: 0.0,
51 }
52 }
53
54 #[inline]
55 fn update_emas(&mut self, input: f64) {
56 self.slow_ema = self.k_slow.mul_add(input - self.slow_ema, self.slow_ema);
57 self.fast_ema = self.k_fast.mul_add(input - self.fast_ema, self.fast_ema);
58 self.macd_values.push(self.fast_ema - self.slow_ema);
59 }
60}
61
62impl Next<f64> for MACD {
63 type Output = (f64, f64, f64);
64
65 fn next(&mut self, input: f64) -> Self::Output {
66 let i = self.bars_seen;
67 self.bars_seen += 1;
68
69 if i < self.sp - 1 {
70 self.seed_closes.push(input);
71 return NAN_TRIPLE;
72 }
73
74 if i == self.sp - 1 {
75 self.seed_closes.push(input);
76 let slow_seed: f64 =
77 self.seed_closes.iter().sum::<f64>() / self.sp as f64;
78 let fast_seed: f64 = self.seed_closes[self.sp - self.fp..self.sp]
79 .iter()
80 .sum::<f64>()
81 / self.fp as f64;
82 self.slow_ema = slow_seed;
83 self.fast_ema = fast_seed;
84 let macd0 = fast_seed - slow_seed;
85 self.macd_values.push(macd0);
86 if self.out_start == self.sp - 1 {
87 let signal_seed = macd0;
88 self.signal_ema = signal_seed;
89 return (macd0, signal_seed, 0.0);
90 }
91 return NAN_TRIPLE;
92 }
93
94 if i < self.out_start {
95 self.update_emas(input);
96 return NAN_TRIPLE;
97 }
98
99 if i == self.out_start {
100 if i >= self.sp {
101 self.update_emas(input);
102 }
103 let signal_seed: f64 = self.macd_values[..self.signalperiod]
104 .iter()
105 .sum::<f64>()
106 / self.signalperiod as f64;
107 self.signal_ema = signal_seed;
108 let macd = self.macd_values[self.signalperiod - 1];
109 return (macd, signal_seed, macd - signal_seed);
110 }
111
112 self.update_emas(input);
113 let macd = *self.macd_values.last().unwrap_or(&f64::NAN);
114 self.signal_ema = self
115 .k_signal
116 .mul_add(macd - self.signal_ema, self.signal_ema);
117 (macd, self.signal_ema, macd - self.signal_ema)
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use proptest::prelude::*;
125
126 proptest! {
127 #[test]
128 fn test_macd_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
129 let fast = 12;
130 let slow = 26;
131 let signal = 9;
132 let mut macd = MACD::new(fast, slow, signal);
133 let streaming_results: Vec<(f64, f64, f64)> =
134 input.iter().map(|&x| macd.next(x)).collect();
135 let (b_macd, b_signal, b_hist) = talib_rs::momentum::macd(&input, fast, slow, signal)
136 .unwrap_or_else(|_| {
137 (
138 vec![f64::NAN; input.len()],
139 vec![f64::NAN; input.len()],
140 vec![f64::NAN; input.len()],
141 )
142 });
143
144 for (i, (s_macd, s_signal, s_hist)) in streaming_results.into_iter().enumerate() {
145 if s_macd.is_nan() {
146 assert!(b_macd[i].is_nan());
147 } else {
148 approx::assert_relative_eq!(s_macd, b_macd[i], epsilon = 1e-6);
149 }
150 if s_signal.is_nan() {
151 assert!(b_signal[i].is_nan());
152 } else {
153 approx::assert_relative_eq!(s_signal, b_signal[i], epsilon = 1e-6);
154 }
155 if s_hist.is_nan() {
156 assert!(b_hist[i].is_nan());
157 } else {
158 approx::assert_relative_eq!(s_hist, b_hist[i], epsilon = 1e-6);
159 }
160 }
161 }
162 }
163}