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 period = period.clamp(0.67 * self.period_prev, 1.5 * self.period_prev);
152 period = period.clamp(6.0, 50.0);
153 period = 0.2 * period + 0.8 * self.period_prev;
154 self.period_prev = period;
155
156 let _smooth_period = 0.33 * period + 0.67 * self.smooth_period_prev;
157 self.smooth_period_prev = _smooth_period;
158
159 let mut phase = 0.0;
160 if i1 != 0.0 {
161 phase = (q1 / i1).atan().to_degrees();
162 }
163
164 let mut delta_phase = self.phase_prev - phase;
165 self.phase_prev = phase;
166
167 if delta_phase < 1.0 {
168 delta_phase = 1.0;
169 }
170
171 let alpha = (self.fast_limit / delta_phase).clamp(self.slow_limit, self.fast_limit);
172
173 let mama = alpha * price + (1.0 - alpha) * self.mama_prev;
174 let fama = 0.5 * alpha * mama + (1.0 - 0.5 * alpha) * self.fama_prev;
175
176 self.mama_prev = mama;
177 self.fama_prev = fama;
178
179 (mama, fama)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use proptest::prelude::*;
187
188 #[test]
189 fn test_mama_basic() {
190 let mut mama = MAMA::new(0.5, 0.05);
191 let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0];
192 for p in prices {
193 let (m, f) = mama.next(p);
194 assert!(!m.is_nan());
195 assert!(!f.is_nan());
196 }
197 }
198
199 fn mama_batch(data: Vec<f64>, fast: f64, slow: f64) -> Vec<(f64, f64)> {
200 let mut mama = MAMA::new(fast, slow);
201 data.into_iter().map(|x| mama.next(x)).collect()
202 }
203
204 proptest! {
205 #[test]
206 fn test_mama_parity(input in prop::collection::vec(1.0..100.0, 10..100)) {
207 let fast = 0.5;
208 let slow = 0.05;
209 let mut mama = MAMA::new(fast, slow);
210 let mut streaming_results = Vec::with_capacity(input.len());
211 for &val in &input {
212 streaming_results.push(mama.next(val));
213 }
214
215 let batch_results = mama_batch(input, fast, slow);
216
217 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
218 approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-6);
219 approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-6);
220 }
221 }
222 }
223}
224
225pub const MAMA_METADATA: IndicatorMetadata = IndicatorMetadata {
226 name: "MESA Adaptive Moving Average",
227 description: "MAMA adapts to price movement in an entirely new and unique way based on the rate change of phase.",
228 usage: "Use as an adaptive trend filter that automatically speeds up in fast markets and slows in choppy ones. The FAMA line crossing MAMA provides high-probability trend change signals.",
229 keywords: &["moving-average", "adaptive", "ehlers", "dsp", "trend"],
230 ehlers_summary: "Presented in Rocket Science for Traders (2001), MAMA adapts its alpha based on the rate of phase change measured by the Hilbert Transform Discriminator. Fast cycles produce large alpha for responsiveness; slow cycles produce small alpha to reduce noise.",
231 params: &[
232 ParamDef {
233 name: "fast_limit",
234 default: "0.5",
235 description: "Fast limit for alpha",
236 },
237 ParamDef {
238 name: "slow_limit",
239 default: "0.05",
240 description: "Slow limit for alpha",
241 },
242 ],
243 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/implemented/MAMA.pdf",
244 formula_latex: r#"
245\[
246\text{MAMA} = \alpha \cdot \text{Price} + (1 - \alpha) \cdot \text{MAMA}_{1}
247\]
248\[
249\text{FAMA} = 0.5\alpha \cdot \text{MAMA} + (1 - 0.5\alpha) \cdot \text{FAMA}_{1}
250\]
251"#,
252 gold_standard_file: "mama.json",
253 category: "Ehlers DSP",
254};