wickra_core/indicators/
generalized_dema.rs1use crate::error::{Error, Result};
4use crate::indicators::ema::Ema;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
41pub struct GeneralizedDema {
42 ema1: Ema,
43 ema2: Ema,
44 period: usize,
45 v: f64,
46}
47
48impl GeneralizedDema {
49 pub fn new(period: usize, v: f64) -> Result<Self> {
57 if period == 0 {
58 return Err(Error::PeriodZero);
59 }
60 if period > crate::error::MAX_PERIOD {
61 return Err(Error::InvalidPeriod {
62 message: crate::error::PERIOD_ABOVE_MAX,
63 });
64 }
65 if !v.is_finite() || !(0.0..=1.0).contains(&v) {
66 return Err(Error::InvalidPeriod {
67 message: "GD volume factor must be a finite value in [0.0, 1.0]",
68 });
69 }
70 Ok(Self {
71 ema1: Ema::new(period)?,
72 ema2: Ema::new(period)?,
73 period,
74 v,
75 })
76 }
77
78 pub const fn period(&self) -> usize {
80 self.period
81 }
82
83 pub const fn volume_factor(&self) -> f64 {
85 self.v
86 }
87}
88
89impl Indicator for GeneralizedDema {
90 type Input = f64;
91 type Output = f64;
92
93 #[inline]
94 fn update(&mut self, input: f64) -> Option<f64> {
95 let e1 = self.ema1.update(input)?;
96 let e2 = self.ema2.update(e1)?;
97 Some((1.0 + self.v) * e1 - self.v * e2)
98 }
99
100 fn reset(&mut self) {
101 self.ema1.reset();
102 self.ema2.reset();
103 }
104
105 #[inline]
106 fn warmup_period(&self) -> usize {
107 2 * self.period - 1
109 }
110
111 #[inline]
112 fn is_ready(&self) -> bool {
113 self.ema2.is_ready()
114 }
115
116 #[inline]
117 fn name(&self) -> &'static str {
118 "GD"
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::indicators::Dema;
126 use crate::traits::BatchExt;
127 use approx::assert_relative_eq;
128
129 #[test]
130 fn rejects_zero_period() {
131 assert!(matches!(
132 GeneralizedDema::new(0, 0.7),
133 Err(Error::PeriodZero)
134 ));
135 }
136
137 #[test]
138 fn rejects_invalid_volume_factor() {
139 assert!(matches!(
140 GeneralizedDema::new(5, -0.1),
141 Err(Error::InvalidPeriod { .. })
142 ));
143 assert!(matches!(
144 GeneralizedDema::new(5, 1.5),
145 Err(Error::InvalidPeriod { .. })
146 ));
147 assert!(matches!(
148 GeneralizedDema::new(5, f64::NAN),
149 Err(Error::InvalidPeriod { .. })
150 ));
151 assert!(GeneralizedDema::new(5, 0.0).is_ok());
152 assert!(GeneralizedDema::new(5, 1.0).is_ok());
153 }
154
155 #[test]
158 fn accessors_and_metadata() {
159 let gd = GeneralizedDema::new(5, 0.7).unwrap();
160 assert_eq!(gd.period(), 5);
161 assert_relative_eq!(gd.volume_factor(), 0.7, epsilon = 1e-12);
162 assert_eq!(gd.warmup_period(), 9);
164 assert_eq!(gd.name(), "GD");
165 }
166
167 #[test]
168 fn constant_series_yields_constant() {
169 let mut gd = GeneralizedDema::new(5, 0.7).unwrap();
170 let out = gd.batch(&[100.0_f64; 60]);
171 let last = out.iter().rev().flatten().next().unwrap();
172 assert_relative_eq!(*last, 100.0, epsilon = 1e-9);
173 }
174
175 #[test]
176 fn v_one_equals_dema() {
177 let prices: Vec<f64> = (1..=80)
179 .map(|i| (f64::from(i) * 0.3).sin() * 10.0 + 50.0)
180 .collect();
181 let mut gd = GeneralizedDema::new(7, 1.0).unwrap();
182 let mut dema = Dema::new(7).unwrap();
183 let gd_out = gd.batch(&prices);
184 let dema_out = dema.batch(&prices);
185 for (g, d) in gd_out.iter().zip(dema_out.iter()) {
186 assert_eq!(g.is_some(), d.is_some());
187 if let (Some(a), Some(b)) = (g, d) {
188 assert_relative_eq!(*a, *b, epsilon = 1e-9);
189 }
190 }
191 }
192
193 #[test]
194 fn v_zero_equals_ema() {
195 let prices: Vec<f64> = (1..=60).map(|i| f64::from(i) * 0.5).collect();
197 let mut gd = GeneralizedDema::new(6, 0.0).unwrap();
198 let mut ema = Ema::new(6).unwrap();
199 let gd_out = gd.batch(&prices);
200 for (i, (g, p)) in gd_out.iter().zip(prices.iter()).enumerate() {
201 let want = ema.update(*p).filter(|_| i + 1 >= gd.warmup_period());
205 if let (Some(a), Some(b)) = (g, want) {
206 assert_relative_eq!(*a, b, epsilon = 1e-9);
207 }
208 }
209 }
210
211 #[test]
212 fn batch_equals_streaming() {
213 let prices: Vec<f64> = (1..=80).map(|i| f64::from(i) * 0.5).collect();
214 let mut a = GeneralizedDema::new(7, 0.7).unwrap();
215 let mut b = GeneralizedDema::new(7, 0.7).unwrap();
216 assert_eq!(
217 a.batch(&prices),
218 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
219 );
220 }
221
222 #[test]
223 fn reset_clears_state() {
224 let mut gd = GeneralizedDema::new(5, 0.7).unwrap();
225 gd.batch(&(1..=50).map(f64::from).collect::<Vec<_>>());
226 assert!(gd.is_ready());
227 gd.reset();
228 assert!(!gd.is_ready());
229 assert_eq!(gd.update(1.0), None);
230 }
231}