wickra_core/indicators/
rocr.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::traits::Indicator;
7
8#[derive(Debug, Clone)]
30pub struct Rocr {
31 period: usize,
32 window: VecDeque<f64>,
33 last: Option<f64>,
34}
35
36impl Rocr {
37 pub fn new(period: usize) -> Result<Self> {
40 if period == 0 {
41 return Err(Error::PeriodZero);
42 }
43 Ok(Self {
44 period,
45 window: VecDeque::with_capacity(period + 1),
46 last: None,
47 })
48 }
49
50 pub const fn period(&self) -> usize {
52 self.period
53 }
54}
55
56impl Indicator for Rocr {
57 type Input = f64;
58 type Output = f64;
59
60 fn update(&mut self, input: f64) -> Option<f64> {
61 if !input.is_finite() {
62 return self.last;
63 }
64 if self.window.len() == self.period + 1 {
65 self.window.pop_front();
66 }
67 self.window.push_back(input);
68 if self.window.len() < self.period + 1 {
69 return None;
70 }
71 let prev = *self.window.front().expect("non-empty");
72 let rocr = if prev == 0.0 { 0.0 } else { input / prev };
73 self.last = Some(rocr);
74 Some(rocr)
75 }
76
77 fn reset(&mut self) {
78 self.window.clear();
79 self.last = None;
80 }
81
82 fn warmup_period(&self) -> usize {
83 self.period + 1
84 }
85
86 fn is_ready(&self) -> bool {
87 self.window.len() == self.period + 1
88 }
89
90 fn name(&self) -> &'static str {
91 "ROCR"
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!(Rocr::new(0), Err(Error::PeriodZero)));
104 }
105
106 #[test]
107 fn accessors_report_config() {
108 let r = Rocr::new(3).unwrap();
109 assert_eq!(r.period(), 3);
110 assert_eq!(r.name(), "ROCR");
111 assert_eq!(r.warmup_period(), 4);
112 assert!(!r.is_ready());
113 }
114
115 #[test]
116 fn known_value_is_a_ratio() {
117 let mut r = Rocr::new(1).unwrap();
119 let out: Vec<Option<f64>> = r.batch(&[10.0, 11.0]);
120 assert_eq!(out[0], None);
121 assert_relative_eq!(out[1].unwrap(), 1.1, epsilon = 1e-12);
122 assert!(r.is_ready());
123 }
124
125 #[test]
126 fn constant_series_yields_one() {
127 let mut r = Rocr::new(3).unwrap();
128 for v in r.batch(&[10.0_f64; 12]).iter().skip(4).flatten() {
129 assert_relative_eq!(*v, 1.0, epsilon = 1e-12);
130 }
131 }
132
133 #[test]
134 fn zero_reference_price_reports_zero() {
135 let mut r = Rocr::new(1).unwrap();
136 let out: Vec<Option<f64>> = r.batch(&[0.0, 5.0]);
137 assert_relative_eq!(out[1].unwrap(), 0.0, epsilon = 1e-12);
138 }
139
140 #[test]
141 fn non_finite_input_holds_last() {
142 let mut r = Rocr::new(1).unwrap();
143 assert_eq!(r.update(10.0), None);
144 let v = r.update(11.0).unwrap();
145 assert_eq!(r.update(f64::INFINITY), Some(v));
146 }
147
148 #[test]
149 fn reset_clears_state() {
150 let mut r = Rocr::new(1).unwrap();
151 let _ = r.batch(&[10.0, 11.0]);
152 assert!(r.is_ready());
153 r.reset();
154 assert!(!r.is_ready());
155 assert_eq!(r.update(10.0), None);
156 }
157}