wickra_core/indicators/
reflex.rs1#![allow(clippy::doc_markdown)]
3
4use std::collections::VecDeque;
5
6use crate::error::{Error, Result};
7use crate::indicators::super_smoother::SuperSmoother;
8use crate::traits::Indicator;
9
10#[derive(Debug, Clone)]
50pub struct Reflex {
51 period: usize,
52 smoother: SuperSmoother,
53 filt: VecDeque<f64>,
54 ms: f64,
55 last: Option<f64>,
56}
57
58impl Reflex {
59 pub fn new(period: usize) -> Result<Self> {
65 if period == 0 {
66 return Err(Error::PeriodZero);
67 }
68 if period > crate::error::MAX_PERIOD {
69 return Err(Error::InvalidPeriod {
70 message: crate::error::PERIOD_ABOVE_MAX,
71 });
72 }
73 Ok(Self {
74 period,
75 smoother: SuperSmoother::new(period)?,
76 filt: VecDeque::with_capacity(period + 1),
77 ms: 0.0,
78 last: None,
79 })
80 }
81
82 pub const fn period(&self) -> usize {
84 self.period
85 }
86
87 pub const fn value(&self) -> Option<f64> {
89 self.last
90 }
91}
92
93impl Indicator for Reflex {
94 type Input = f64;
95 type Output = f64;
96
97 #[inline]
98 fn update(&mut self, price: f64) -> Option<f64> {
99 if !price.is_finite() {
100 return None;
101 }
102 let filt = self.smoother.update(price)?;
103 if self.filt.len() == self.period + 1 {
104 self.filt.pop_front();
105 }
106 self.filt.push_back(filt);
107 if self.filt.len() < self.period + 1 {
108 return None;
109 }
110 let newest = self.filt[self.period];
112 let oldest = self.filt[0];
113 let slope = (oldest - newest) / self.period as f64;
114 let mut sum = 0.0;
115 for i in 1..=self.period {
116 sum += (newest + i as f64 * slope) - self.filt[self.period - i];
117 }
118 sum /= self.period as f64;
119 self.ms = 0.04 * sum * sum + 0.96 * self.ms;
120 let reflex = if self.ms > 0.0 {
121 sum / self.ms.sqrt()
122 } else {
123 0.0
124 };
125 self.last = Some(reflex);
126 Some(reflex)
127 }
128
129 fn reset(&mut self) {
130 self.smoother.reset();
131 self.filt.clear();
132 self.ms = 0.0;
133 self.last = None;
134 }
135
136 #[inline]
137 fn warmup_period(&self) -> usize {
138 self.period + 1
139 }
140
141 #[inline]
142 fn is_ready(&self) -> bool {
143 self.last.is_some()
144 }
145
146 #[inline]
147 fn name(&self) -> &'static str {
148 "Reflex"
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::traits::BatchExt;
156 use approx::assert_relative_eq;
157
158 #[test]
159 fn rejects_zero_period() {
160 assert!(matches!(Reflex::new(0), Err(Error::PeriodZero)));
161 }
162
163 #[test]
164 fn accessors_and_metadata() {
165 let r = Reflex::new(20).unwrap();
166 assert_eq!(r.period(), 20);
167 assert_eq!(r.warmup_period(), 21);
168 assert_eq!(r.name(), "Reflex");
169 assert!(!r.is_ready());
170 assert_eq!(r.value(), None);
171 }
172
173 #[test]
174 fn first_emission_at_warmup_period() {
175 let mut r = Reflex::new(5).unwrap();
176 let xs: Vec<f64> = (0..12)
177 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 3.0)
178 .collect();
179 let out = r.batch(&xs);
180 for v in out.iter().take(5) {
181 assert!(v.is_none());
182 }
183 assert!(out[5].is_some());
184 }
185
186 #[test]
187 fn constant_input_is_zero() {
188 let mut r = Reflex::new(10).unwrap();
190 for v in r.batch(&[50.0; 100]).into_iter().flatten() {
191 assert_relative_eq!(v, 0.0, epsilon = 1e-9);
192 }
193 }
194
195 #[test]
196 fn cyclic_input_oscillates_around_zero() {
197 let mut r = Reflex::new(20).unwrap();
198 let xs: Vec<f64> = (0..400)
199 .map(|i| 100.0 + (std::f64::consts::TAU * f64::from(i) / 20.0).sin() * 5.0)
200 .collect();
201 let out: Vec<f64> = r.batch(&xs).into_iter().flatten().skip(100).collect();
202 assert!(out.iter().any(|&v| v > 0.5));
203 assert!(out.iter().any(|&v| v < -0.5));
204 }
205
206 #[test]
207 fn ignores_non_finite() {
208 let mut r = Reflex::new(10).unwrap();
209 r.batch(
210 &(0..40)
211 .map(|i| 100.0 + (f64::from(i) * 0.3).sin())
212 .collect::<Vec<_>>(),
213 );
214 let before = r.value();
215 assert_eq!(r.update(f64::NAN), None);
216 assert_eq!(r.value(), before);
218 }
219
220 #[test]
221 fn reset_clears_state() {
222 let mut r = Reflex::new(10).unwrap();
223 r.batch(
224 &(0..40)
225 .map(|i| 100.0 + (f64::from(i) * 0.3).sin())
226 .collect::<Vec<_>>(),
227 );
228 assert!(r.is_ready());
229 r.reset();
230 assert!(!r.is_ready());
231 assert_eq!(r.value(), None);
232 }
233
234 #[test]
235 fn batch_equals_streaming() {
236 let xs: Vec<f64> = (0..120)
237 .map(|i| 100.0 + (f64::from(i) * 0.25).sin() * 9.0)
238 .collect();
239 let batch = Reflex::new(20).unwrap().batch(&xs);
240 let mut b = Reflex::new(20).unwrap();
241 let streamed: Vec<_> = xs.iter().map(|x| b.update(*x)).collect();
242 assert_eq!(batch, streamed);
243 }
244}