Skip to main content

quantwave_core/indicators/
alma.rs

1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
6pub struct ALMA {
7    period: usize,
8    _offset: f64,
9    _sigma: f64,
10    window: VecDeque<f64>,
11    weights: Vec<f64>,
12}
13
14impl ALMA {
15    pub fn new(period: usize, offset: f64, sigma: f64) -> Self {
16        let m = offset * (period as f64 - 1.0);
17        let s = period as f64 / sigma;
18        let mut weights = Vec::with_capacity(period);
19        let mut sum_w = 0.0;
20
21        for i in 0..period {
22            let weight = (-((i as f64 - m).powi(2) / (2.0 * s.powi(2)))).exp();
23            weights.push(weight);
24            sum_w += weight;
25        }
26
27        // Normalize weights
28        for w in weights.iter_mut() {
29            *w /= sum_w;
30        }
31
32        Self {
33            period,
34            _offset: offset,
35            _sigma: sigma,
36            window: VecDeque::with_capacity(period),
37            weights,
38        }
39    }
40}
41
42impl Next<f64> for ALMA {
43    type Output = f64;
44
45    fn next(&mut self, input: f64) -> Self::Output {
46        self.window.push_back(input);
47        if self.window.len() > self.period {
48            self.window.pop_front();
49        }
50
51        if self.window.len() < self.period {
52            let mut sum_w = 0.0;
53            let mut weighted_val_sum = 0.0;
54            for (i, &val) in self.window.iter().enumerate() {
55                let weight = self.weights[i + self.period - self.window.len()];
56                weighted_val_sum += val * weight;
57                sum_w += weight;
58            }
59            if sum_w == 0.0 {
60                0.0
61            } else {
62                weighted_val_sum / sum_w
63            }
64        } else {
65            let mut weighted_val_sum = 0.0;
66            for (i, &val) in self.window.iter().enumerate() {
67                weighted_val_sum += val * self.weights[i];
68            }
69            weighted_val_sum
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::test_utils::{
78        assert_indicator_parity, check_batch_streaming_parity, load_gold_standard,
79    };
80    use proptest::prelude::*;
81
82    #[test]
83    fn test_alma_gold_standard() {
84        let case = load_gold_standard("alma_9_085_6");
85        let alma = ALMA::new(9, 0.85, 6.0);
86        assert_indicator_parity(alma, &case.input, &case.expected);
87    }
88
89    fn alma_batch(data: Vec<f64>, period: usize, offset: f64, sigma: f64) -> Vec<f64> {
90        let mut alma = ALMA::new(period, offset, sigma);
91        data.into_iter().map(|x| alma.next(x)).collect()
92    }
93
94    proptest! {
95        #[test]
96        fn test_alma_parity(input in prop::collection::vec(0.0..1000.0, 1..100)) {
97            let period = 9;
98            let offset = 0.85;
99            let sigma = 6.0;
100            let indicator = ALMA::new(period, offset, sigma);
101            check_batch_streaming_parity(input, indicator, |data| alma_batch(data, period, offset, sigma));
102        }
103    }
104
105    #[test]
106    fn test_alma_basic() {
107        let mut alma = ALMA::new(9, 0.85, 6.0);
108        for i in 1..20 {
109            let val = alma.next(i as f64);
110            if i >= 9 {
111                assert!(val > 0.0);
112            }
113        }
114    }
115}
116
117pub const ALMA_METADATA: IndicatorMetadata = IndicatorMetadata {
118    name: "Arnaud Legoux Moving Average",
119    description: "ALMA is designed to reduce lag while providing high smoothness.",
120    params: &[
121        ParamDef {
122            name: "period",
123            default: "9",
124            description: "Period",
125        },
126        ParamDef {
127            name: "offset",
128            default: "0.85",
129            description: "Offset",
130        },
131        ParamDef {
132            name: "sigma",
133            default: "6.0",
134            description: "Sigma",
135        },
136    ],
137    formula_source: "https://www.prorealcode.com/prorealtime-indicators/arnaud-legoux-moving-average-alma/",
138    formula_latex: r#"
139\[
140ALMA = \sum (W_i \times P_i) / \sum W_i
141\]
142"#,
143    gold_standard_file: "alma.json",
144    category: "Classic",
145};