1use std::collections::VecDeque;
4use std::f64::consts::PI;
5
6use crate::error::{Error, Result};
7use crate::traits::Indicator;
8
9const DELTA: f64 = 0.1;
11
12const PEAK_AVG_LEN: usize = 50;
14
15#[derive(Debug, Clone)]
48pub struct EmpiricalModeDecomposition {
49 period: usize,
50 fraction: f64,
51 beta: f64,
52 alpha: f64,
53 prev_in_1: Option<f64>,
54 prev_in_2: Option<f64>,
55 prev_bp_1: f64,
56 prev_bp_2: f64,
57 peak: f64,
58 valley: f64,
59 bp_window: VecDeque<f64>,
60 bp_sum: f64,
61 peak_window: VecDeque<f64>,
62 peak_sum: f64,
63 valley_window: VecDeque<f64>,
64 valley_sum: f64,
65 upper: f64,
66 lower: f64,
67 last_value: Option<f64>,
68}
69
70impl EmpiricalModeDecomposition {
71 pub fn new(period: usize, fraction: f64) -> Result<Self> {
81 if period == 0 {
82 return Err(Error::PeriodZero);
83 }
84 if period > crate::error::MAX_PERIOD {
85 return Err(Error::InvalidPeriod {
86 message: crate::error::PERIOD_ABOVE_MAX,
87 });
88 }
89 if !fraction.is_finite() || fraction <= 0.0 || fraction > 1.0 {
90 return Err(Error::InvalidPeriod {
91 message: "fraction must be in (0, 1]",
92 });
93 }
94 let beta = (2.0 * PI / period as f64).cos();
95 let gamma = 1.0 / (4.0 * PI * DELTA / period as f64).cos();
96 let alpha = gamma - (gamma * gamma - 1.0).sqrt();
97 Ok(Self {
98 period,
99 fraction,
100 beta,
101 alpha,
102 prev_in_1: None,
103 prev_in_2: None,
104 prev_bp_1: 0.0,
105 prev_bp_2: 0.0,
106 peak: 0.0,
107 valley: 0.0,
108 bp_window: VecDeque::with_capacity(2 * period),
109 bp_sum: 0.0,
110 peak_window: VecDeque::with_capacity(PEAK_AVG_LEN),
111 peak_sum: 0.0,
112 valley_window: VecDeque::with_capacity(PEAK_AVG_LEN),
113 valley_sum: 0.0,
114 upper: 0.0,
115 lower: 0.0,
116 last_value: None,
117 })
118 }
119
120 pub const fn period(&self) -> usize {
122 self.period
123 }
124
125 pub const fn fraction(&self) -> f64 {
127 self.fraction
128 }
129
130 pub const fn value(&self) -> Option<f64> {
132 self.last_value
133 }
134
135 pub const fn upper(&self) -> f64 {
137 self.upper
138 }
139
140 pub const fn lower(&self) -> f64 {
142 self.lower
143 }
144
145 fn push(window: &mut VecDeque<f64>, sum: &mut f64, len: usize, x: f64) {
147 if window.len() == len {
148 *sum -= window.pop_front().expect("window is non-empty");
149 }
150 window.push_back(x);
151 *sum += x;
152 }
153}
154
155impl Indicator for EmpiricalModeDecomposition {
156 type Input = f64;
157 type Output = f64;
158
159 fn update(&mut self, input: f64) -> Option<f64> {
160 if !input.is_finite() {
161 return None;
162 }
163 let bp = if let Some(x2) = self.prev_in_2 {
165 0.5 * (1.0 - self.alpha) * (input - x2)
166 + self.beta * (1.0 + self.alpha) * self.prev_bp_1
167 - self.alpha * self.prev_bp_2
168 } else {
169 0.0
170 };
171 if self.prev_bp_1 > bp && self.prev_bp_1 > self.prev_bp_2 {
173 self.peak = self.prev_bp_1;
174 }
175 if self.prev_bp_1 < bp && self.prev_bp_1 < self.prev_bp_2 {
176 self.valley = self.prev_bp_1;
177 }
178 self.prev_bp_2 = self.prev_bp_1;
179 self.prev_bp_1 = bp;
180 self.prev_in_2 = self.prev_in_1;
181 self.prev_in_1 = Some(input);
182
183 Self::push(&mut self.bp_window, &mut self.bp_sum, 2 * self.period, bp);
184 Self::push(
185 &mut self.peak_window,
186 &mut self.peak_sum,
187 PEAK_AVG_LEN,
188 self.peak,
189 );
190 Self::push(
191 &mut self.valley_window,
192 &mut self.valley_sum,
193 PEAK_AVG_LEN,
194 self.valley,
195 );
196 if self.bp_window.len() < 2 * self.period || self.peak_window.len() < PEAK_AVG_LEN {
197 return None;
198 }
199 let n = PEAK_AVG_LEN as f64;
200 self.upper = self.fraction * self.peak_sum / n;
201 self.lower = self.fraction * self.valley_sum / n;
202 let mean = self.bp_sum / (2 * self.period) as f64;
203 self.last_value = Some(mean);
204 Some(mean)
205 }
206
207 fn reset(&mut self) {
208 self.prev_in_1 = None;
209 self.prev_in_2 = None;
210 self.prev_bp_1 = 0.0;
211 self.prev_bp_2 = 0.0;
212 self.peak = 0.0;
213 self.valley = 0.0;
214 self.bp_window.clear();
215 self.bp_sum = 0.0;
216 self.peak_window.clear();
217 self.peak_sum = 0.0;
218 self.valley_window.clear();
219 self.valley_sum = 0.0;
220 self.upper = 0.0;
221 self.lower = 0.0;
222 self.last_value = None;
223 }
224
225 #[inline]
226 fn warmup_period(&self) -> usize {
227 (2 * self.period).max(PEAK_AVG_LEN)
228 }
229
230 #[inline]
231 fn is_ready(&self) -> bool {
232 self.last_value.is_some()
233 }
234
235 #[inline]
236 fn name(&self) -> &'static str {
237 "EmpiricalModeDecomposition"
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::traits::BatchExt;
245
246 #[test]
247 fn new_rejects_invalid_params() {
248 assert!(matches!(
249 EmpiricalModeDecomposition::new(0, 0.5),
250 Err(Error::PeriodZero)
251 ));
252 assert!(matches!(
253 EmpiricalModeDecomposition::new(20, 0.0),
254 Err(Error::InvalidPeriod { .. })
255 ));
256 assert!(matches!(
257 EmpiricalModeDecomposition::new(20, 1.5),
258 Err(Error::InvalidPeriod { .. })
259 ));
260 assert!(matches!(
261 EmpiricalModeDecomposition::new(20, f64::NAN),
262 Err(Error::InvalidPeriod { .. })
263 ));
264 }
265
266 #[test]
267 fn accessors_and_metadata() {
268 let mut emd = EmpiricalModeDecomposition::new(20, 0.5).unwrap();
269 assert_eq!(emd.period(), 20);
270 assert!((emd.fraction() - 0.5).abs() < 1e-15);
271 assert_eq!(emd.name(), "EmpiricalModeDecomposition");
272 assert!(emd.warmup_period() >= 1);
273 assert!(!emd.is_ready());
274 let prices: Vec<f64> = (0..200)
275 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
276 .collect();
277 emd.batch(&prices);
278 assert!(emd.is_ready());
279 assert!(emd.value().is_some());
280 }
281
282 #[test]
283 fn batch_equals_streaming() {
284 let prices: Vec<f64> = (0..200)
285 .map(|i| 100.0 + (f64::from(i) * 0.2).cos() * 5.0)
286 .collect();
287 let mut a = EmpiricalModeDecomposition::new(20, 0.5).unwrap();
288 let mut b = EmpiricalModeDecomposition::new(20, 0.5).unwrap();
289 let batch = a.batch(&prices);
290 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
291 assert_eq!(batch, streamed);
292 }
293
294 #[test]
295 fn ignores_non_finite_input() {
296 let mut emd = EmpiricalModeDecomposition::new(20, 0.5).unwrap();
297 let prices: Vec<f64> = (0..200)
298 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
299 .collect();
300 emd.batch(&prices);
301 let before = emd.value();
302 assert!(before.is_some());
303 assert_eq!(emd.update(f64::NAN), None);
304 }
305
306 #[test]
307 fn reset_clears_state() {
308 let mut emd = EmpiricalModeDecomposition::new(20, 0.5).unwrap();
309 let prices: Vec<f64> = (0..200)
310 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
311 .collect();
312 emd.batch(&prices);
313 assert!(emd.is_ready());
314 emd.reset();
315 assert!(!emd.is_ready());
316 }
317
318 #[test]
319 fn rejects_period_above_maximum() {
320 assert!(matches!(
321 EmpiricalModeDecomposition::new(crate::error::MAX_PERIOD + 1, 0.5),
322 Err(Error::InvalidPeriod { .. })
323 ));
324 assert!(matches!(
325 EmpiricalModeDecomposition::new(20, -0.1),
326 Err(Error::InvalidPeriod { .. })
327 ));
328 assert!(matches!(
329 EmpiricalModeDecomposition::new(20, f64::INFINITY),
330 Err(Error::InvalidPeriod { .. })
331 ));
332 assert!(EmpiricalModeDecomposition::new(20, 1.0).is_ok());
334 }
335
336 #[test]
337 fn warmup_is_max_of_two_period_and_fifty() {
338 let prices: Vec<f64> = (0..150)
339 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
340 .collect();
341 for (period, expected) in [(1usize, 50usize), (10, 50), (25, 50), (26, 52), (40, 80)] {
342 let mut emd = EmpiricalModeDecomposition::new(period, 0.1).unwrap();
343 assert_eq!(emd.warmup_period(), expected);
344 let out = emd.batch(&prices);
345 assert!(out[..expected - 1].iter().all(Option::is_none));
346 assert!(out[expected - 1].is_some());
347 }
348 }
349
350 #[test]
351 fn constant_series_has_zero_mean_and_thresholds() {
352 let mut emd = EmpiricalModeDecomposition::new(10, 0.5).unwrap();
354 let out = emd.batch(&[42.0; 80]);
355 assert!(out
356 .iter()
357 .flatten()
358 .all(|v| v.to_bits() == 0.0f64.to_bits()));
359 assert_eq!(emd.upper().to_bits(), 0.0f64.to_bits());
360 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
361 }
362
363 #[test]
364 fn hand_computed_step_peak_valley_and_thresholds() {
365 let mut emd = EmpiricalModeDecomposition::new(5, 0.5).unwrap();
368 let (alpha, beta) = (emd.alpha, emd.beta);
369 assert!((beta - 0.309_016_994_374_947_45).abs() < 1e-15);
370 assert!((alpha - 0.775_679_511_049_613_4).abs() < 1e-12);
371 for _ in 0..60 {
373 emd.update(0.0);
374 }
375 assert_eq!(emd.upper().to_bits(), 0.0f64.to_bits());
376 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
377 let c = 0.5 * (1.0 - alpha);
384 let k = beta * (1.0 + alpha);
385 let bp60 = c;
386 let bp61 = c + k * bp60;
387 let bp62 = k * bp61 - alpha * bp60;
388 let bp63 = k * bp62 - alpha * bp61;
389 let bp64 = k * bp63 - alpha * bp62;
390 assert!((bp61 - 0.173_704_269_339_216_5).abs() < 1e-12);
391 assert!((bp63 + 0.130_176_956_775_418_27).abs() < 1e-12);
392 let m60 = emd.update(1.0).unwrap();
394 assert!((m60 - bp60 / 10.0).abs() < 1e-15);
395 assert_eq!(emd.upper().to_bits(), 0.0f64.to_bits());
396 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
397 emd.update(1.0);
398 let m62 = emd.update(1.0).unwrap();
401 assert!((emd.upper() - 0.5 * bp61 / 50.0).abs() < 1e-15);
402 assert!((emd.upper() - 0.001_737_042_693_392_165).abs() < 1e-12);
403 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
404 assert!((m62 - (bp60 + bp61 + bp62) / 10.0).abs() < 1e-15);
406 emd.update(1.0);
408 assert!((emd.upper() - 0.5 * 2.0 * bp61 / 50.0).abs() < 1e-15);
409 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
410 let m64 = emd.update(1.0).unwrap();
413 assert!((emd.upper() - 0.5 * 3.0 * bp61 / 50.0).abs() < 1e-15);
414 assert!((emd.lower() - 0.5 * bp63 / 50.0).abs() < 1e-15);
415 assert!((emd.lower() + 0.001_301_769_567_754_182_7).abs() < 1e-12);
416 assert!((m64 - (bp60 + bp61 + bp62 + bp63 + bp64) / 10.0).abs() < 1e-15);
417 assert_eq!(emd.value(), Some(m64));
418 }
419
420 #[test]
421 fn thresholds_bracket_zero_on_an_oscillation() {
422 let mut emd = EmpiricalModeDecomposition::new(20, 0.3).unwrap();
424 for i in 0..400 {
425 emd.update((f64::from(i) * 2.0 * PI / 20.0).sin() * 10.0 + 100.0);
426 }
427 assert!(emd.upper() > 0.0);
428 assert!(emd.lower() < 0.0);
429 let mean = emd.value().unwrap();
430 assert!(mean.abs() < emd.upper());
431 }
432
433 #[test]
434 fn reset_replays_identically_and_batch_nan_into_matches() {
435 let prices: Vec<f64> = (0..200)
436 .map(|i| 100.0 + (f64::from(i) * 0.17).sin() * 3.0 + f64::from(i) * 0.02)
437 .collect();
438 let mut emd = EmpiricalModeDecomposition::new(12, 0.2).unwrap();
439 let first = emd.batch(&prices);
440 let (up, lo) = (emd.upper(), emd.lower());
441 emd.reset();
442 assert_eq!(emd.upper().to_bits(), 0.0f64.to_bits());
443 assert_eq!(emd.lower().to_bits(), 0.0f64.to_bits());
444 assert_eq!(emd.value(), None);
445 let second = emd.batch(&prices);
446 assert_eq!(first, second);
447 assert_eq!(emd.upper().to_bits(), up.to_bits());
448 assert_eq!(emd.lower().to_bits(), lo.to_bits());
449 let mut fresh = EmpiricalModeDecomposition::new(12, 0.2).unwrap();
450 let mut out = vec![0.0; prices.len()];
451 fresh.batch_nan_into(&prices, &mut out);
452 assert!(out
453 .iter()
454 .zip(&first)
455 .all(|(a, b)| a.to_bits() == b.unwrap_or(f64::NAN).to_bits()));
456 }
457}