Skip to main content

quantwave_core/indicators/incremental/
dema.rs

1//! Native DEMA — TA-Lib parity via double TalibEma.
2
3use crate::indicators::incremental::talib_ema::TalibEma;
4use crate::traits::Next;
5
6/// Double Exponential Moving Average.
7#[derive(Debug, Clone)]
8#[allow(non_camel_case_types)]
9pub struct DEMA {
10    pub timeperiod: usize,
11    ema1: TalibEma,
12    ema2: TalibEma,
13}
14
15impl DEMA {
16    pub fn new(timeperiod: usize) -> Self {
17        Self {
18            timeperiod,
19            ema1: TalibEma::new(timeperiod),
20            ema2: TalibEma::new(timeperiod),
21        }
22    }
23}
24
25impl Next<f64> for DEMA {
26    type Output = f64;
27
28    fn next(&mut self, input: f64) -> Self::Output {
29        let e1 = self.ema1.next(input);
30        if e1.is_nan() {
31            return f64::NAN;
32        }
33        let e2 = self.ema2.next(e1);
34        if e2.is_nan() {
35            return f64::NAN;
36        }
37        2.0 * e1 - e2
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use proptest::prelude::*;
45
46    proptest! {
47        #[test]
48        fn test_dema_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
49            let period = 10;
50            let mut dema = DEMA::new(period);
51            let streaming: Vec<f64> = input.iter().map(|&x| dema.next(x)).collect();
52            let batch = talib_rs::overlap::dema(&input, period)
53                .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
54            for (s, b) in streaming.iter().zip(batch.iter()) {
55                if s.is_nan() {
56                    assert!(b.is_nan());
57                } else {
58                    approx::assert_relative_eq!(s, b, epsilon = 1e-6);
59                }
60            }
61        }
62    }
63}