quantwave_core/indicators/incremental/
macd_ext.rs1use crate::indicators::incremental::ma_stream::MaStream;
4use crate::indicators::incremental::macd::MACD;
5use crate::indicators::ma_type::MaType;
6use crate::traits::Next;
7
8const NAN_TRIPLE: (f64, f64, f64) = (f64::NAN, f64::NAN, f64::NAN);
9
10#[derive(Debug, Clone)]
12#[allow(non_camel_case_types)]
13pub struct MACDEXT {
14 pub fastperiod: usize,
15 pub fastmatype: MaType,
16 pub slowperiod: usize,
17 pub slowmatype: MaType,
18 pub signalperiod: usize,
19 pub signalmatype: MaType,
20 ema_macd: Option<MACD>,
21 fast_ma: MaStream,
22 slow_ma: MaStream,
23 signal_ma: MaStream,
24 macd_valid_count: usize,
25}
26
27impl MACDEXT {
28 pub fn new(
29 fastperiod: usize,
30 fastmatype: MaType,
31 slowperiod: usize,
32 slowmatype: MaType,
33 signalperiod: usize,
34 signalmatype: MaType,
35 ) -> Self {
36 let ema_macd = if fastmatype == MaType::Ema
37 && slowmatype == MaType::Ema
38 && signalmatype == MaType::Ema
39 {
40 Some(MACD::new(fastperiod, slowperiod, signalperiod))
41 } else {
42 None
43 };
44 Self {
45 fastperiod,
46 fastmatype,
47 slowperiod,
48 slowmatype,
49 signalperiod,
50 signalmatype,
51 ema_macd,
52 fast_ma: MaStream::new(fastperiod, fastmatype),
53 slow_ma: MaStream::new(slowperiod, slowmatype),
54 signal_ma: MaStream::new(signalperiod, signalmatype),
55 macd_valid_count: 0,
56 }
57 }
58}
59
60impl Next<f64> for MACDEXT {
61 type Output = (f64, f64, f64);
62
63 fn next(&mut self, input: f64) -> Self::Output {
64 if let Some(ref mut macd) = self.ema_macd {
65 return macd.next(input);
66 }
67
68 let fast = self.fast_ma.next(input);
69 let slow = self.slow_ma.next(input);
70 if fast.is_nan() || slow.is_nan() {
71 return NAN_TRIPLE;
72 }
73
74 let macd_line = fast - slow;
75 self.macd_valid_count += 1;
76 let signal = self.signal_ma.next(macd_line);
77 if signal.is_nan() {
78 return NAN_TRIPLE;
79 }
80
81 (macd_line, signal, macd_line - signal)
82 }
83}
84
85#[derive(Debug, Clone)]
87#[allow(non_camel_case_types)]
88pub struct MACDFIX {
89 pub signalperiod: usize,
90 fp: usize,
91 sp: usize,
92 k_fast: f64,
93 k_slow: f64,
94 k_signal: f64,
95 out_start: usize,
96 bars_seen: usize,
97 seed_closes: Vec<f64>,
98 slow_ema: f64,
99 fast_ema: f64,
100 macd_values: Vec<f64>,
101 signal_ema: f64,
102}
103
104impl MACDFIX {
105 pub fn new(signalperiod: usize) -> Self {
106 let fp = 12usize;
107 let sp = 26usize;
108 Self {
109 signalperiod,
110 fp,
111 sp,
112 k_fast: 0.15,
113 k_slow: 0.075,
114 k_signal: 2.0 / (signalperiod as f64 + 1.0),
115 out_start: sp - 1 + signalperiod - 1,
116 bars_seen: 0,
117 seed_closes: Vec::with_capacity(sp),
118 slow_ema: 0.0,
119 fast_ema: 0.0,
120 macd_values: Vec::new(),
121 signal_ema: 0.0,
122 }
123 }
124
125 #[inline]
126 fn update_emas(&mut self, input: f64) {
127 self.slow_ema = self.k_slow.mul_add(input - self.slow_ema, self.slow_ema);
128 self.fast_ema = self.k_fast.mul_add(input - self.fast_ema, self.fast_ema);
129 self.macd_values.push(self.fast_ema - self.slow_ema);
130 }
131}
132
133impl Next<f64> for MACDFIX {
134 type Output = (f64, f64, f64);
135
136 fn next(&mut self, input: f64) -> Self::Output {
137 let i = self.bars_seen;
138 self.bars_seen += 1;
139
140 if i < self.sp - 1 {
141 self.seed_closes.push(input);
142 return NAN_TRIPLE;
143 }
144
145 if i == self.sp - 1 {
146 self.seed_closes.push(input);
147 let slow_seed: f64 = self.seed_closes.iter().sum::<f64>() / self.sp as f64;
148 let fast_seed: f64 = self.seed_closes[self.sp - self.fp..self.sp]
149 .iter()
150 .sum::<f64>()
151 / self.fp as f64;
152 self.slow_ema = slow_seed;
153 self.fast_ema = fast_seed;
154 let macd0 = fast_seed - slow_seed;
155 self.macd_values.push(macd0);
156 if self.out_start == self.sp - 1 {
157 let signal_seed = macd0;
158 self.signal_ema = signal_seed;
159 return (macd0, signal_seed, 0.0);
160 }
161 return NAN_TRIPLE;
162 }
163
164 if i < self.out_start {
165 self.update_emas(input);
166 return NAN_TRIPLE;
167 }
168
169 if i == self.out_start {
170 if i >= self.sp {
171 self.update_emas(input);
172 }
173 let signal_seed: f64 = self.macd_values[..self.signalperiod].iter().sum::<f64>()
174 / self.signalperiod as f64;
175 self.signal_ema = signal_seed;
176 let macd = self.macd_values[self.signalperiod - 1];
177 return (macd, signal_seed, macd - signal_seed);
178 }
179
180 self.update_emas(input);
181 let macd = *self.macd_values.last().unwrap_or(&f64::NAN);
182 self.signal_ema = self
183 .k_signal
184 .mul_add(macd - self.signal_ema, self.signal_ema);
185 (macd, self.signal_ema, macd - self.signal_ema)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use proptest::prelude::*;
193
194 type Triples = (Vec<f64>, Vec<f64>, Vec<f64>);
195
196 fn assert_parity(streaming: &[(f64, f64, f64)], batch: &Triples, label: &str) {
197 let (b_macd, b_signal, b_hist) = batch;
198 for (i, &(s_m, s_s, s_h)) in streaming.iter().enumerate() {
199 for (s, b, name) in [
200 (s_m, b_macd[i], "macd"),
201 (s_s, b_signal[i], "signal"),
202 (s_h, b_hist[i], "hist"),
203 ] {
204 if s.is_nan() {
205 assert!(
206 b.is_nan(),
207 "{label} bar {i} {name}: streaming NaN, batch {b}"
208 );
209 } else {
210 assert!(
211 !b.is_nan(),
212 "{label} bar {i} {name}: streaming {s}, batch NaN"
213 );
214 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
215 }
216 }
217 }
218 }
219
220 proptest! {
221 #[test]
222 fn test_macdext_ema_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
223 let fast = 12usize;
224 let slow = 26usize;
225 let signal = 9usize;
226 let matype = MaType::Ema;
227 let mut ext = MACDEXT::new(fast, matype, slow, matype, signal, matype);
228 let streaming: Vec<_> = input.iter().map(|&x| ext.next(x)).collect();
229 let batch = talib_rs::momentum::macd_ext(
230 &input, fast, matype.into(), slow, matype.into(), signal, matype.into(),
231 ).unwrap_or_else(|_| {
232 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
233 });
234 assert_parity(&streaming, &batch, "macdext/ema");
235 }
236
237 #[test]
240 fn test_macdext_parity_all_matypes(
241 input in prop::collection::vec(0.1..100.0, 150..220),
242 idx in 0usize..9,
243 ) {
244 let matype = MaType::ALL[idx];
245 let (fast, slow, signal) = (12usize, 26usize, 9usize);
246 let mut ext = MACDEXT::new(fast, matype, slow, matype, signal, matype);
247 let streaming: Vec<_> = input.iter().map(|&x| ext.next(x)).collect();
248 let batch = talib_rs::momentum::macd_ext(
249 &input, fast, matype.into(), slow, matype.into(), signal, matype.into(),
250 ).unwrap_or_else(|_| {
251 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
252 });
253 assert_parity(&streaming, &batch, &format!("macdext/{matype}"));
254 }
255
256 #[test]
257 fn test_macdfix_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
258 let signal = 9usize;
259 let mut fix = MACDFIX::new(signal);
260 let streaming: Vec<_> = input.iter().map(|&x| fix.next(x)).collect();
261 let batch = talib_rs::momentum::macd_fix(&input, signal)
262 .unwrap_or_else(|_| {
263 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
264 });
265 assert_parity(&streaming, &batch, "macdfix");
266 }
267 }
268}