wickra_core/indicators/
dema.rs1use crate::error::Result;
4use crate::indicators::ema::Ema;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
25pub struct Dema {
26 ema1: Ema,
27 ema2: Ema,
28 period: usize,
29}
30
31impl Dema {
32 pub fn new(period: usize) -> Result<Self> {
35 Ok(Self {
36 ema1: Ema::new(period)?,
37 ema2: Ema::new(period)?,
38 period,
39 })
40 }
41
42 pub const fn period(&self) -> usize {
44 self.period
45 }
46}
47
48impl Indicator for Dema {
49 type Input = f64;
50 type Output = f64;
51
52 #[inline]
53 fn update(&mut self, input: f64) -> Option<f64> {
54 let e1 = self.ema1.update(input)?;
55 let e2 = self.ema2.update(e1)?;
56 Some(2.0 * e1 - e2)
57 }
58
59 fn reset(&mut self) {
60 self.ema1.reset();
61 self.ema2.reset();
62 }
63
64 #[inline]
65 fn warmup_period(&self) -> usize {
66 2 * self.period - 1
68 }
69
70 #[inline]
71 fn is_ready(&self) -> bool {
72 self.ema2.is_ready()
73 }
74
75 #[inline]
76 fn name(&self) -> &'static str {
77 "DEMA"
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::traits::BatchExt;
85 use approx::assert_relative_eq;
86
87 #[test]
88 fn constant_series_yields_constant_dema() {
89 let mut dema = Dema::new(5).unwrap();
90 let out = dema.batch(&[100.0_f64; 60]);
91 let last = out.iter().rev().flatten().next().unwrap();
92 assert_relative_eq!(*last, 100.0, epsilon = 1e-9);
93 }
94
95 #[test]
96 fn linear_uptrend_dema_above_ema_eventually() {
97 let prices: Vec<f64> = (1..=200).map(f64::from).collect();
100 let mut dema = Dema::new(20).unwrap();
101 let mut ema = Ema::new(20).unwrap();
102 let dema_out = dema.batch(&prices);
103 let ema_out = ema.batch(&prices);
104 let d = dema_out.last().unwrap().unwrap();
106 let e = ema_out.last().unwrap().unwrap();
107 assert!(d > e, "DEMA={d} should exceed EMA={e} on uptrend");
108 }
109
110 #[test]
111 fn batch_equals_streaming() {
112 let prices: Vec<f64> = (1..=80).map(|i| f64::from(i) * 0.5).collect();
113 let mut a = Dema::new(7).unwrap();
114 let mut b = Dema::new(7).unwrap();
115 assert_eq!(
116 a.batch(&prices),
117 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
118 );
119 }
120
121 #[test]
122 fn reset_clears_state() {
123 let mut dema = Dema::new(5).unwrap();
124 dema.batch(&(1..=50).map(f64::from).collect::<Vec<_>>());
125 assert!(dema.is_ready());
126 dema.reset();
127 assert!(!dema.is_ready());
128 }
129
130 #[test]
131 fn rejects_zero_period() {
132 assert!(Dema::new(0).is_err());
133 }
134
135 #[test]
139 fn accessors_and_metadata() {
140 let dema = Dema::new(5).unwrap();
141 assert_eq!(dema.period(), 5);
142 assert_eq!(dema.warmup_period(), 9);
145 assert_eq!(dema.name(), "DEMA");
146 }
147}