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