wickra_core/indicators/
mid_point.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::traits::Indicator;
7
8#[derive(Debug, Clone)]
33pub struct MidPoint {
34 period: usize,
35 window: VecDeque<f64>,
36}
37
38impl MidPoint {
39 pub fn new(period: usize) -> Result<Self> {
42 if period == 0 {
43 return Err(Error::PeriodZero);
44 }
45 Ok(Self {
46 period,
47 window: VecDeque::with_capacity(period),
48 })
49 }
50
51 pub const fn period(&self) -> usize {
53 self.period
54 }
55}
56
57impl Indicator for MidPoint {
58 type Input = f64;
59 type Output = f64;
60
61 fn update(&mut self, value: f64) -> Option<f64> {
62 if !value.is_finite() {
63 return None;
64 }
65 if self.window.len() == self.period {
66 self.window.pop_front();
67 }
68 self.window.push_back(value);
69 if self.window.len() < self.period {
70 return None;
71 }
72 let highest = self
73 .window
74 .iter()
75 .copied()
76 .fold(f64::NEG_INFINITY, f64::max);
77 let lowest = self.window.iter().copied().fold(f64::INFINITY, f64::min);
78 Some(f64::midpoint(highest, lowest))
79 }
80
81 fn reset(&mut self) {
82 self.window.clear();
83 }
84
85 fn warmup_period(&self) -> usize {
86 self.period
87 }
88
89 fn is_ready(&self) -> bool {
90 self.window.len() == self.period
91 }
92
93 fn name(&self) -> &'static str {
94 "MIDPOINT"
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::traits::BatchExt;
102 use approx::assert_relative_eq;
103
104 #[test]
105 fn rejects_zero_period() {
106 assert!(matches!(MidPoint::new(0), Err(Error::PeriodZero)));
107 }
108
109 #[test]
110 fn accessors_report_config() {
111 let mp = MidPoint::new(7).unwrap();
112 assert_eq!(mp.period(), 7);
113 assert_eq!(mp.name(), "MIDPOINT");
114 assert_eq!(mp.warmup_period(), 7);
115 assert!(!mp.is_ready());
116 }
117
118 #[test]
119 fn averages_window_min_and_max() {
120 let mut mp = MidPoint::new(3).unwrap();
122 let out: Vec<Option<f64>> = mp.batch(&[8.0, 12.0, 10.0]);
123 assert_eq!(out[0], None);
124 assert_eq!(out[1], None);
125 assert_relative_eq!(out[2].unwrap(), 10.0, epsilon = 1e-12);
126 assert!(mp.is_ready());
127 }
128
129 #[test]
130 fn window_slides_and_drops_old_values() {
131 let mut mp = MidPoint::new(3).unwrap();
133 let out: Vec<Option<f64>> = mp.batch(&[30.0, 8.0, 12.0, 10.0]);
134 assert_relative_eq!(out[3].unwrap(), 10.0, epsilon = 1e-12);
136 }
137
138 #[test]
139 fn reset_clears_state() {
140 let mut mp = MidPoint::new(3).unwrap();
141 let _ = mp.batch(&[8.0, 12.0, 10.0]);
142 assert!(mp.is_ready());
143 mp.reset();
144 assert!(!mp.is_ready());
145 assert_eq!(mp.update(8.0), None);
146 }
147}