1#![allow(
3 clippy::doc_markdown,
4 clippy::doc_lazy_continuation,
5 clippy::struct_field_names,
6 clippy::manual_clamp
7)]
8
9use std::f64::consts::PI;
10
11use crate::error::{Error, Result};
12use crate::traits::Indicator;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct MamaOutput {
17 pub mama: f64,
19 pub fama: f64,
21}
22
23#[derive(Debug, Clone)]
51pub struct Mama {
52 fast_limit: f64,
53 slow_limit: f64,
54 price_buf: Vec<f64>,
56 smooth_buf: Vec<f64>,
58 detrender_buf: Vec<f64>,
59 q1_buf: Vec<f64>,
60 i1_buf: Vec<f64>,
61 prev_i2: f64,
62 prev_q2: f64,
63 prev_re: f64,
64 prev_im: f64,
65 prev_period: f64,
66 prev_phase: f64,
67 prev_mama: f64,
68 prev_fama: f64,
69 count: usize,
70 last_value: Option<MamaOutput>,
71}
72
73impl Mama {
74 pub fn new(fast_limit: f64, slow_limit: f64) -> Result<Self> {
81 if !fast_limit.is_finite()
82 || !slow_limit.is_finite()
83 || fast_limit <= 0.0
84 || fast_limit > 1.0
85 || slow_limit <= 0.0
86 || slow_limit > 1.0
87 || slow_limit > fast_limit
88 {
89 return Err(Error::InvalidPeriod {
90 message: "fast_limit, slow_limit must satisfy 0 < slow_limit <= fast_limit <= 1",
91 });
92 }
93 Ok(Self {
94 fast_limit,
95 slow_limit,
96 price_buf: Vec::with_capacity(4),
97 smooth_buf: Vec::with_capacity(7),
98 detrender_buf: Vec::with_capacity(7),
99 q1_buf: Vec::with_capacity(7),
100 i1_buf: Vec::with_capacity(7),
101 prev_i2: 0.0,
102 prev_q2: 0.0,
103 prev_re: 0.0,
104 prev_im: 0.0,
105 prev_period: 0.0,
106 prev_phase: 0.0,
107 prev_mama: 0.0,
108 prev_fama: 0.0,
109 count: 0,
110 last_value: None,
111 })
112 }
113
114 pub fn classic() -> Self {
116 Self::new(0.5, 0.05).expect("classic MAMA limits are valid")
117 }
118
119 pub const fn limits(&self) -> (f64, f64) {
121 (self.fast_limit, self.slow_limit)
122 }
123
124 pub const fn value(&self) -> Option<MamaOutput> {
126 self.last_value
127 }
128
129 fn push_front(buf: &mut Vec<f64>, v: f64, cap: usize) {
130 buf.insert(0, v);
131 if buf.len() > cap {
132 buf.truncate(cap);
133 }
134 }
135}
136
137impl Indicator for Mama {
138 type Input = f64;
139 type Output = MamaOutput;
140
141 fn update(&mut self, input: f64) -> Option<MamaOutput> {
142 if !input.is_finite() {
143 return None;
144 }
145 self.count += 1;
146
147 Self::push_front(&mut self.price_buf, input, 4);
148 if self.price_buf.len() < 4 {
149 return None;
150 }
151 let smooth = (4.0 * self.price_buf[0]
152 + 3.0 * self.price_buf[1]
153 + 2.0 * self.price_buf[2]
154 + self.price_buf[3])
155 / 10.0;
156 Self::push_front(&mut self.smooth_buf, smooth, 7);
157
158 let period = self.prev_period.max(6.0).min(50.0);
159 let adj = 0.075 * period + 0.54;
160
161 if self.smooth_buf.len() < 7 {
162 self.prev_mama = smooth;
165 self.prev_fama = smooth;
166 return None;
167 }
168 let s0 = smooth;
169 let s2 = self.smooth_buf[2];
170 let s4 = self.smooth_buf[4];
171 let s6 = self.smooth_buf[6];
172 let detrender = (0.0962 * s0 + 0.5769 * s2 - 0.5769 * s4 - 0.0962 * s6) * adj;
173 Self::push_front(&mut self.detrender_buf, detrender, 7);
174 if self.detrender_buf.len() < 7 {
175 return None;
176 }
177
178 let q1 = (0.0962 * self.detrender_buf[0] + 0.5769 * self.detrender_buf[2]
179 - 0.5769 * self.detrender_buf[4]
180 - 0.0962 * self.detrender_buf[6])
181 * adj;
182 let i1 = self.detrender_buf[3];
183 Self::push_front(&mut self.q1_buf, q1, 7);
184 Self::push_front(&mut self.i1_buf, i1, 7);
185 if self.q1_buf.len() < 7 || self.i1_buf.len() < 7 {
186 return None;
187 }
188
189 let ji = (0.0962 * self.i1_buf[0] + 0.5769 * self.i1_buf[2]
190 - 0.5769 * self.i1_buf[4]
191 - 0.0962 * self.i1_buf[6])
192 * adj;
193 let jq = (0.0962 * self.q1_buf[0] + 0.5769 * self.q1_buf[2]
194 - 0.5769 * self.q1_buf[4]
195 - 0.0962 * self.q1_buf[6])
196 * adj;
197
198 let mut i2 = i1 - jq;
199 let mut q2 = q1 + ji;
200 i2 = 0.2 * i2 + 0.8 * self.prev_i2;
201 q2 = 0.2 * q2 + 0.8 * self.prev_q2;
202
203 let mut re = i2 * self.prev_i2 + q2 * self.prev_q2;
204 let mut im = i2 * self.prev_q2 - q2 * self.prev_i2;
205 re = 0.2 * re + 0.8 * self.prev_re;
206 im = 0.2 * im + 0.8 * self.prev_im;
207
208 self.prev_i2 = i2;
209 self.prev_q2 = q2;
210 self.prev_re = re;
211 self.prev_im = im;
212
213 let mut new_period = if im.abs() > f64::EPSILON && re.abs() > f64::EPSILON {
214 2.0 * PI / im.atan2(re)
215 } else {
216 self.prev_period
217 };
218 new_period = new_period.min(1.5 * self.prev_period);
219 new_period = new_period.max(0.67 * self.prev_period);
220 new_period = new_period.clamp(6.0, 50.0);
221 self.prev_period = 0.2 * new_period + 0.8 * self.prev_period;
222
223 let phase = if i1.abs() > f64::EPSILON {
225 (q1 / i1).atan().to_degrees()
226 } else {
227 self.prev_phase
228 };
229 let mut delta_phase = self.prev_phase - phase;
230 self.prev_phase = phase;
231 if delta_phase < 1.0 {
232 delta_phase = 1.0;
233 }
234 let mut alpha = self.fast_limit / delta_phase;
237 if alpha < self.slow_limit {
238 alpha = self.slow_limit;
239 }
240
241 self.prev_mama = alpha * input + (1.0 - alpha) * self.prev_mama;
242 let fama_alpha = 0.5 * alpha;
243 self.prev_fama = fama_alpha * self.prev_mama + (1.0 - fama_alpha) * self.prev_fama;
244
245 if self.count < 33 {
246 return None;
247 }
248 let out = MamaOutput {
249 mama: self.prev_mama,
250 fama: self.prev_fama,
251 };
252 self.last_value = Some(out);
253 Some(out)
254 }
255
256 fn reset(&mut self) {
257 self.price_buf.clear();
258 self.smooth_buf.clear();
259 self.detrender_buf.clear();
260 self.q1_buf.clear();
261 self.i1_buf.clear();
262 self.prev_i2 = 0.0;
263 self.prev_q2 = 0.0;
264 self.prev_re = 0.0;
265 self.prev_im = 0.0;
266 self.prev_period = 0.0;
267 self.prev_phase = 0.0;
268 self.prev_mama = 0.0;
269 self.prev_fama = 0.0;
270 self.count = 0;
271 self.last_value = None;
272 }
273
274 #[inline]
275 fn warmup_period(&self) -> usize {
276 33
277 }
278
279 #[inline]
280 fn is_ready(&self) -> bool {
281 self.last_value.is_some()
282 }
283
284 #[inline]
285 fn name(&self) -> &'static str {
286 "MAMA"
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::traits::BatchExt;
294
295 #[test]
296 fn rejects_invalid_limits() {
297 assert!(matches!(
298 Mama::new(0.0, 0.05),
299 Err(Error::InvalidPeriod { .. })
300 ));
301 assert!(matches!(
302 Mama::new(0.5, 0.0),
303 Err(Error::InvalidPeriod { .. })
304 ));
305 assert!(matches!(
306 Mama::new(0.05, 0.5),
307 Err(Error::InvalidPeriod { .. })
308 ));
309 assert!(matches!(
310 Mama::new(1.5, 0.05),
311 Err(Error::InvalidPeriod { .. })
312 ));
313 assert!(matches!(
314 Mama::new(f64::NAN, 0.05),
315 Err(Error::InvalidPeriod { .. })
316 ));
317 }
318
319 #[test]
320 fn accessors_and_metadata() {
321 let mut mama = Mama::classic();
322 assert_eq!(mama.limits(), (0.5, 0.05));
323 assert_eq!(mama.warmup_period(), 33);
324 assert_eq!(mama.name(), "MAMA");
325 assert!(!mama.is_ready());
326 for i in 0..60 {
327 mama.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
328 }
329 assert!(mama.is_ready());
330 assert!(mama.value().is_some());
331 }
332
333 #[test]
334 fn fama_lags_or_equals_mama_on_constant_series() {
335 let mut mama = Mama::classic();
336 let out = mama.batch(&[100.0_f64; 200]);
337 let last = out.iter().flatten().last().unwrap();
338 assert!((last.mama - 100.0).abs() < 1.0);
340 assert!((last.fama - 100.0).abs() < 1.0);
341 }
342
343 #[test]
344 fn batch_equals_streaming() {
345 let prices: Vec<f64> = (0..120)
346 .map(|i| 100.0 + (f64::from(i) * 0.25).sin() * 5.0)
347 .collect();
348 let mut a = Mama::classic();
349 let mut b = Mama::classic();
350 let batch = a.batch(&prices);
351 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
352 assert_eq!(batch, streamed);
353 }
354
355 #[test]
356 fn ignores_non_finite_input() {
357 let mut mama = Mama::classic();
358 let prices: Vec<f64> = (0..100)
359 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
360 .collect();
361 mama.batch(&prices);
362 let before = mama.value();
363 assert!(before.is_some());
364 assert_eq!(mama.update(f64::NAN), None);
365 }
366
367 #[test]
368 fn reset_clears_state() {
369 let mut mama = Mama::classic();
370 let prices: Vec<f64> = (0..100)
371 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
372 .collect();
373 mama.batch(&prices);
374 assert!(mama.is_ready());
375 mama.reset();
376 assert!(!mama.is_ready());
377 }
378
379 #[test]
380 fn flat_input_uses_phase_fallback() {
381 let mut mama = Mama::classic();
387 let out = mama.batch(&[0.0_f64; 200]);
388 assert!(out.iter().flatten().count() > 100);
389 }
390
391 use approx::assert_relative_eq;
392
393 fn sine_prices(n: u32) -> Vec<f64> {
394 (0..n)
395 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
396 .collect()
397 }
398
399 #[test]
400 fn rejects_non_finite_and_out_of_range_limits() {
401 assert!(matches!(
402 Mama::new(f64::INFINITY, 0.05),
403 Err(Error::InvalidPeriod { .. })
404 ));
405 assert!(matches!(
406 Mama::new(0.5, f64::NAN),
407 Err(Error::InvalidPeriod { .. })
408 ));
409 assert!(matches!(
410 Mama::new(1.0, 1.5),
411 Err(Error::InvalidPeriod { .. })
412 ));
413 assert!(matches!(
414 Mama::new(-0.5, -0.6),
415 Err(Error::InvalidPeriod { .. })
416 ));
417 assert_eq!(Mama::new(1.0, 1.0).unwrap().limits(), (1.0, 1.0));
419 }
420
421 #[test]
422 fn first_value_lands_exactly_at_warmup() {
423 let mut mama = Mama::classic();
424 let out = mama.batch(&sine_prices(80));
425 let warmup = mama.warmup_period();
426 assert!(out[..warmup - 1].iter().all(Option::is_none));
427 assert!(out[warmup - 1].is_some());
428 }
429
430 #[test]
431 fn reset_replays_identically() {
432 let prices = sine_prices(150);
433 let fresh = Mama::classic().batch(&prices);
434 let mut mama = Mama::classic();
435 let first = mama.batch(&prices);
436 mama.reset();
437 let second = mama.batch(&prices);
438 assert_eq!(first, fresh);
439 assert_eq!(second, fresh);
440 }
441
442 #[test]
443 fn wma_of_raw_inputs_seeds_lines_and_feeds_detrender_taps() {
444 let mut mama = Mama::classic();
445 for p in [10.0, 20.0, 30.0, 40.0] {
448 assert_eq!(mama.update(p), None);
449 }
450 assert_eq!(mama.smooth_buf, vec![30.0]);
451 assert_eq!(mama.prev_mama, 30.0);
452 assert_eq!(mama.prev_fama, 30.0);
453
454 let mut mama = Mama::classic();
459 let mut series = [0.0; 10];
460 series[7] = 10.0;
461 let _ = mama.batch(&series);
462 assert_eq!(mama.smooth_buf, vec![2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0]);
463 assert_eq!(mama.detrender_buf.len(), 1);
464 assert_relative_eq!(mama.detrender_buf[0], 2.475, epsilon = 1e-12);
465 }
466
467 #[test]
468 fn alpha_bounds_hand_computed() {
469 let mut mama = Mama::new(1.0, 1.0).unwrap();
472 let prices = sine_prices(60);
473 let out = mama.batch(&prices);
474 let first = out[32].unwrap();
475 let second = out[33].unwrap();
476 assert_eq!(first.mama, prices[32]);
477 assert_eq!(second.mama, prices[33]);
478 assert_relative_eq!(
479 second.fama,
480 0.5 * prices[33] + 0.5 * first.fama,
481 epsilon = 1e-12
482 );
483 }
484
485 #[test]
486 fn slow_limit_floor_binds_on_fast_phase_rotation() {
487 let mut mama = Mama::new(0.1, 0.09).unwrap();
492 let prices: Vec<f64> = (0..200)
493 .map(|i| 100.0 + (f64::from(i) * 2.5).sin() * 5.0)
494 .collect();
495 let out = mama.batch(&prices);
496 assert!(out
497 .iter()
498 .flatten()
499 .all(|o| (90.0..=110.0).contains(&o.mama) && (90.0..=110.0).contains(&o.fama)));
500 }
501}