mantis_ta/indicators/trend/
dema.rs1use super::EMA;
2use crate::indicators::Indicator;
3use crate::types::Candle;
4
5#[derive(Debug, Clone)]
33pub struct DEMA {
34 period: usize,
35 ema1: EMA,
36 ema2: EMA,
37}
38
39impl DEMA {
40 pub fn new(period: usize) -> Self {
41 assert!(period > 0, "period must be > 0");
42 Self {
43 period,
44 ema1: EMA::new(period),
45 ema2: EMA::new(period),
46 }
47 }
48
49 #[inline]
50 fn update(&mut self, value: f64) -> Option<f64> {
51 let ema1_val = self.ema1.next(&Candle {
52 timestamp: 0,
53 open: value,
54 high: value,
55 low: value,
56 close: value,
57 volume: 0.0,
58 });
59
60 if let Some(ema1) = ema1_val {
61 let ema2_val = self.ema2.next(&Candle {
62 timestamp: 0,
63 open: ema1,
64 high: ema1,
65 low: ema1,
66 close: ema1,
67 volume: 0.0,
68 });
69
70 if let Some(ema2) = ema2_val {
71 return Some(2.0 * ema1 - ema2);
72 }
73 }
74
75 None
76 }
77}
78
79impl Indicator for DEMA {
80 type Output = f64;
81
82 fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
83 self.update(candle.close)
84 }
85
86 fn reset(&mut self) {
87 self.ema1.reset();
88 self.ema2.reset();
89 }
90
91 fn warmup_period(&self) -> usize {
92 self.period * 2 - 1
93 }
94
95 fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
96 Box::new(self.clone())
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn dema_emits_after_warmup() {
106 let prices = [1.0, 2.0, 3.0, 4.0, 5.0];
107 let candles: Vec<Candle> = prices
108 .iter()
109 .enumerate()
110 .map(|(i, p)| Candle {
111 timestamp: i as i64,
112 open: *p,
113 high: *p,
114 low: *p,
115 close: *p,
116 volume: 0.0,
117 })
118 .collect();
119
120 let out = DEMA::new(3).calculate(&candles);
121 assert!(out.iter().take(4).all(|v| v.is_none()));
122 assert!(out.iter().skip(4).any(|v| v.is_some()));
123 }
124}