quantwave_core/indicators/
mama.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
9pub struct MAMA {
10 fast_limit: f64,
11 slow_limit: f64,
12 price_history: VecDeque<f64>,
13 smooth_history: VecDeque<f64>,
14 detrender_history: VecDeque<f64>,
15 i1_history: VecDeque<f64>,
16 q1_history: VecDeque<f64>,
17 i2_prev: f64,
18 q2_prev: f64,
19 re_prev: f64,
20 im_prev: f64,
21 period_prev: f64,
22 smooth_period_prev: f64,
23 phase_prev: f64,
24 mama_prev: f64,
25 fama_prev: f64,
26 count: usize,
27}
28
29impl MAMA {
30 pub fn new(fast_limit: f64, slow_limit: f64) -> Self {
31 Self {
32 fast_limit,
33 slow_limit,
34 price_history: VecDeque::from(vec![0.0; 4]),
35 smooth_history: VecDeque::from(vec![0.0; 7]),
36 detrender_history: VecDeque::from(vec![0.0; 7]),
37 i1_history: VecDeque::from(vec![0.0; 7]),
38 q1_history: VecDeque::from(vec![0.0; 7]),
39 i2_prev: 0.0,
40 q2_prev: 0.0,
41 re_prev: 0.0,
42 im_prev: 0.0,
43 period_prev: 0.0,
44 smooth_period_prev: 0.0,
45 phase_prev: 0.0,
46 mama_prev: 0.0,
47 fama_prev: 0.0,
48 count: 0,
49 }
50 }
51}
52
53impl Default for MAMA {
54 fn default() -> Self {
55 Self::new(0.5, 0.05)
56 }
57}
58
59impl Next<f64> for MAMA {
60 type Output = (f64, f64);
61
62 fn next(&mut self, price: f64) -> Self::Output {
63 self.count += 1;
64
65 self.price_history.pop_back();
66 self.price_history.push_front(price);
67
68 if self.count < 6 {
69 self.mama_prev = price;
70 self.fama_prev = price;
71 return (price, price);
72 }
73
74 let smooth = (4.0 * self.price_history[0]
76 + 3.0 * self.price_history[1]
77 + 2.0 * self.price_history[2]
78 + self.price_history[3])
79 / 10.0;
80
81 self.smooth_history.pop_back();
82 self.smooth_history.push_front(smooth);
83
84 let detrender = (0.0962 * self.smooth_history[0] + 0.5769 * self.smooth_history[2]
86 - 0.5769 * self.smooth_history[4]
87 - 0.0962 * self.smooth_history[6])
88 * (0.075 * self.period_prev + 0.54);
89
90 self.detrender_history.pop_back();
91 self.detrender_history.push_front(detrender);
92
93 let q1 = (0.0962 * self.detrender_history[0] + 0.5769 * self.detrender_history[2]
95 - 0.5769 * self.detrender_history[4]
96 - 0.0962 * self.detrender_history[6])
97 * (0.075 * self.period_prev + 0.54);
98
99 let i1 = self.detrender_history[3];
101
102 self.i1_history.pop_back();
103 self.i1_history.push_front(i1);
104 self.q1_history.pop_back();
105 self.q1_history.push_front(q1);
106
107 let ji = (0.0962 * self.i1_history[0] + 0.5769 * self.i1_history[2]
109 - 0.5769 * self.i1_history[4]
110 - 0.0962 * self.i1_history[6])
111 * (0.075 * self.period_prev + 0.54);
112
113 let jq = (0.0962 * self.q1_history[0] + 0.5769 * self.q1_history[2]
115 - 0.5769 * self.q1_history[4]
116 - 0.0962 * self.q1_history[6])
117 * (0.075 * self.period_prev + 0.54);
118
119 let mut i2 = i1 - jq;
122 let mut q2 = q1 + ji;
123
124 i2 = 0.2 * i2 + 0.8 * self.i2_prev;
127 q2 = 0.2 * q2 + 0.8 * self.q2_prev;
128 self.i2_prev = i2;
129 self.q2_prev = q2;
130
131 let mut re = i2 * self.i2_prev + q2 * self.q2_prev;
135 let mut im = i2 * self.q2_prev - q2 * self.i2_prev;
136
137 re = 0.2 * re + 0.8 * self.re_prev;
143 im = 0.2 * im + 0.8 * self.im_prev;
144 self.re_prev = re;
145 self.im_prev = im;
146
147 let mut period = self.period_prev;
148 if im != 0.0 && re != 0.0 {
149 period = 360.0 / (im / re).atan().to_degrees();
150 }
151 if period > 1.5 * self.period_prev {
152 period = 1.5 * self.period_prev;
153 }
154 if period < 0.67 * self.period_prev {
155 period = 0.67 * self.period_prev;
156 }
157 if period < 6.0 {
158 period = 6.0;
159 }
160 if period > 50.0 {
161 period = 50.0;
162 }
163 period = 0.2 * period + 0.8 * self.period_prev;
164 self.period_prev = period;
165
166 let _smooth_period = 0.33 * period + 0.67 * self.smooth_period_prev;
167 self.smooth_period_prev = _smooth_period;
168
169 let mut phase = 0.0;
170 if i1 != 0.0 {
171 phase = (q1 / i1).atan().to_degrees();
172 }
173
174 let mut delta_phase = self.phase_prev - phase;
175 self.phase_prev = phase;
176
177 if delta_phase < 1.0 {
178 delta_phase = 1.0;
179 }
180
181 let mut alpha = self.fast_limit / delta_phase;
182 if alpha < self.slow_limit {
183 alpha = self.slow_limit;
184 }
185 if alpha > self.fast_limit {
186 alpha = self.fast_limit;
187 }
188
189 let mama = alpha * price + (1.0 - alpha) * self.mama_prev;
190 let fama = 0.5 * alpha * mama + (1.0 - 0.5 * alpha) * self.fama_prev;
191
192 self.mama_prev = mama;
193 self.fama_prev = fama;
194
195 (mama, fama)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use proptest::prelude::*;
203
204 #[test]
205 fn test_mama_basic() {
206 let mut mama = MAMA::new(0.5, 0.05);
207 let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0];
208 for p in prices {
209 let (m, f) = mama.next(p);
210 assert!(!m.is_nan());
211 assert!(!f.is_nan());
212 }
213 }
214
215 fn mama_batch(data: Vec<f64>, fast: f64, slow: f64) -> Vec<(f64, f64)> {
216 let mut mama = MAMA::new(fast, slow);
217 data.into_iter().map(|x| mama.next(x)).collect()
218 }
219
220 proptest! {
221 #[test]
222 fn test_mama_parity(input in prop::collection::vec(1.0..100.0, 10..100)) {
223 let fast = 0.5;
224 let slow = 0.05;
225 let mut mama = MAMA::new(fast, slow);
226 let mut streaming_results = Vec::with_capacity(input.len());
227 for &val in &input {
228 streaming_results.push(mama.next(val));
229 }
230
231 let batch_results = mama_batch(input, fast, slow);
232
233 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
234 approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-6);
235 approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-6);
236 }
237 }
238 }
239}
240
241pub const MAMA_METADATA: IndicatorMetadata = IndicatorMetadata {
242 name: "MESA Adaptive Moving Average",
243 description: "MAMA adapts to price movement in an entirely new and unique way based on the rate change of phase.",
244 params: &[
245 ParamDef {
246 name: "fast_limit",
247 default: "0.5",
248 description: "Fast limit for alpha",
249 },
250 ParamDef {
251 name: "slow_limit",
252 default: "0.05",
253 description: "Slow limit for alpha",
254 },
255 ],
256 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/implemented/MAMA.pdf",
257 formula_latex: r#"
258\[
259\text{MAMA} = \alpha \cdot \text{Price} + (1 - \alpha) \cdot \text{MAMA}_{1}
260\]
261\[
262\text{FAMA} = 0.5\alpha \cdot \text{MAMA} + (1 - 0.5\alpha) \cdot \text{FAMA}_{1}
263\]
264"#,
265 gold_standard_file: "mama.json",
266 category: "Ehlers DSP",
267};