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 self.window.len() == self.period {
63 self.window.pop_front();
64 }
65 self.window.push_back(value);
66 if self.window.len() < self.period {
67 return None;
68 }
69 let highest = self
70 .window
71 .iter()
72 .copied()
73 .fold(f64::NEG_INFINITY, f64::max);
74 let lowest = self.window.iter().copied().fold(f64::INFINITY, f64::min);
75 Some(f64::midpoint(highest, lowest))
76 }
77
78 fn reset(&mut self) {
79 self.window.clear();
80 }
81
82 fn warmup_period(&self) -> usize {
83 self.period
84 }
85
86 fn is_ready(&self) -> bool {
87 self.window.len() == self.period
88 }
89
90 fn name(&self) -> &'static str {
91 "MIDPOINT"
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::traits::BatchExt;
99 use approx::assert_relative_eq;
100
101 #[test]
102 fn rejects_zero_period() {
103 assert!(matches!(MidPoint::new(0), Err(Error::PeriodZero)));
104 }
105
106 #[test]
107 fn accessors_report_config() {
108 let mp = MidPoint::new(7).unwrap();
109 assert_eq!(mp.period(), 7);
110 assert_eq!(mp.name(), "MIDPOINT");
111 assert_eq!(mp.warmup_period(), 7);
112 assert!(!mp.is_ready());
113 }
114
115 #[test]
116 fn averages_window_min_and_max() {
117 let mut mp = MidPoint::new(3).unwrap();
119 let out: Vec<Option<f64>> = mp.batch(&[8.0, 12.0, 10.0]);
120 assert_eq!(out[0], None);
121 assert_eq!(out[1], None);
122 assert_relative_eq!(out[2].unwrap(), 10.0, epsilon = 1e-12);
123 assert!(mp.is_ready());
124 }
125
126 #[test]
127 fn window_slides_and_drops_old_values() {
128 let mut mp = MidPoint::new(3).unwrap();
130 let out: Vec<Option<f64>> = mp.batch(&[30.0, 8.0, 12.0, 10.0]);
131 assert_relative_eq!(out[3].unwrap(), 10.0, epsilon = 1e-12);
133 }
134
135 #[test]
136 fn reset_clears_state() {
137 let mut mp = MidPoint::new(3).unwrap();
138 let _ = mp.batch(&[8.0, 12.0, 10.0]);
139 assert!(mp.is_ready());
140 mp.reset();
141 assert!(!mp.is_ready());
142 assert_eq!(mp.update(8.0), None);
143 }
144}