quantwave_core/indicators/incremental/
rolling.rs1use crate::traits::Next;
4use crate::utils::RingBuffer;
5
6#[derive(Debug, Clone)]
7pub struct RollingMax {
8 pub timeperiod: usize,
9 window: RingBuffer<f64>,
10}
11
12impl RollingMax {
13 pub fn new(timeperiod: usize) -> Self {
14 Self {
15 timeperiod,
16 window: RingBuffer::with_capacity(timeperiod),
17 }
18 }
19
20 fn push(&mut self, v: f64) -> f64 {
21 let p = self.timeperiod;
22 if self.window.len() >= p {
23 let _ = self.window.pop_front();
24 }
25 self.window.push_back(v);
26 if self.window.len() < p {
27 return f64::NAN;
28 }
29 let mut m = f64::NEG_INFINITY;
30 for &x in self.window.iter() {
31 if x > m {
32 m = x;
33 }
34 }
35 m
36 }
37}
38
39#[derive(Debug, Clone)]
40#[allow(non_camel_case_types)]
41pub struct MAX {
42 pub timeperiod: usize,
43 inner: RollingMax,
44}
45
46impl MAX {
47 pub fn new(timeperiod: usize) -> Self {
48 Self {
49 timeperiod,
50 inner: RollingMax::new(timeperiod),
51 }
52 }
53}
54
55impl Next<f64> for MAX {
56 type Output = f64;
57 fn next(&mut self, input: f64) -> Self::Output {
58 self.inner.push(input)
59 }
60}
61
62#[derive(Debug, Clone)]
63pub struct RollingMin {
64 pub timeperiod: usize,
65 window: RingBuffer<f64>,
66}
67
68impl RollingMin {
69 pub fn new(timeperiod: usize) -> Self {
70 Self {
71 timeperiod,
72 window: RingBuffer::with_capacity(timeperiod),
73 }
74 }
75
76 fn push(&mut self, v: f64) -> f64 {
77 let p = self.timeperiod;
78 if self.window.len() >= p {
79 let _ = self.window.pop_front();
80 }
81 self.window.push_back(v);
82 if self.window.len() < p {
83 return f64::NAN;
84 }
85 let mut m = f64::INFINITY;
86 for &x in self.window.iter() {
87 if x < m {
88 m = x;
89 }
90 }
91 m
92 }
93}
94
95#[derive(Debug, Clone)]
96#[allow(non_camel_case_types)]
97pub struct MIN {
98 pub timeperiod: usize,
99 inner: RollingMin,
100}
101
102impl MIN {
103 pub fn new(timeperiod: usize) -> Self {
104 Self {
105 timeperiod,
106 inner: RollingMin::new(timeperiod),
107 }
108 }
109}
110
111impl Next<f64> for MIN {
112 type Output = f64;
113 fn next(&mut self, input: f64) -> Self::Output {
114 self.inner.push(input)
115 }
116}
117
118#[derive(Debug, Clone)]
119pub struct RollingSum {
120 pub timeperiod: usize,
121 window: RingBuffer<f64>,
122 sum: f64,
123}
124
125impl RollingSum {
126 pub fn new(timeperiod: usize) -> Self {
127 Self {
128 timeperiod,
129 window: RingBuffer::with_capacity(timeperiod),
130 sum: 0.0,
131 }
132 }
133
134 fn push(&mut self, v: f64) -> f64 {
135 let p = self.timeperiod;
136 if self.window.len() >= p
137 && let Some(old) = self.window.pop_front()
138 {
139 self.sum -= old;
140 }
141 self.window.push_back(v);
142 self.sum += v;
143 if self.window.len() < p {
144 return f64::NAN;
145 }
146 self.sum
147 }
148}
149
150#[derive(Debug, Clone)]
151#[allow(non_camel_case_types)]
152pub struct SUM {
153 pub timeperiod: usize,
154 inner: RollingSum,
155}
156
157impl SUM {
158 pub fn new(timeperiod: usize) -> Self {
159 Self {
160 timeperiod,
161 inner: RollingSum::new(timeperiod),
162 }
163 }
164}
165
166impl Next<f64> for SUM {
167 type Output = f64;
168 fn next(&mut self, input: f64) -> Self::Output {
169 self.inner.push(input)
170 }
171}
172
173#[derive(Debug, Clone)]
174pub struct RollingMaxIndex {
175 pub timeperiod: usize,
176 window: RingBuffer<f64>,
177 bar_index: usize,
178}
179
180impl RollingMaxIndex {
181 pub fn new(timeperiod: usize) -> Self {
182 Self {
183 timeperiod,
184 window: RingBuffer::with_capacity(timeperiod),
185 bar_index: 0,
186 }
187 }
188
189 fn push(&mut self, v: f64) -> f64 {
190 let p = self.timeperiod;
191 if self.window.len() >= p {
192 let _ = self.window.pop_front();
193 }
194 self.window.push_back(v);
195 self.bar_index += 1;
196 if self.window.len() < p {
197 return f64::NAN;
198 }
199 let mut best_idx = 0usize;
200 let mut best_val = f64::NEG_INFINITY;
201 for (i, &x) in self.window.iter().enumerate() {
202 if x > best_val {
203 best_val = x;
204 best_idx = i;
205 }
206 }
207 (self.bar_index - p + best_idx) as f64
208 }
209}
210
211#[derive(Debug, Clone)]
212#[allow(non_camel_case_types)]
213pub struct MAXINDEX {
214 pub timeperiod: usize,
215 inner: RollingMaxIndex,
216}
217
218impl MAXINDEX {
219 pub fn new(timeperiod: usize) -> Self {
220 Self {
221 timeperiod,
222 inner: RollingMaxIndex::new(timeperiod),
223 }
224 }
225}
226
227impl Next<f64> for MAXINDEX {
228 type Output = f64;
229 fn next(&mut self, input: f64) -> Self::Output {
230 self.inner.push(input)
231 }
232}
233
234#[derive(Debug, Clone)]
235pub struct RollingMinIndex {
236 pub timeperiod: usize,
237 window: RingBuffer<f64>,
238 bar_index: usize,
239}
240
241impl RollingMinIndex {
242 pub fn new(timeperiod: usize) -> Self {
243 Self {
244 timeperiod,
245 window: RingBuffer::with_capacity(timeperiod),
246 bar_index: 0,
247 }
248 }
249
250 fn push(&mut self, v: f64) -> f64 {
251 let p = self.timeperiod;
252 if self.window.len() >= p {
253 let _ = self.window.pop_front();
254 }
255 self.window.push_back(v);
256 self.bar_index += 1;
257 if self.window.len() < p {
258 return f64::NAN;
259 }
260 let mut best_idx = 0usize;
261 let mut best_val = f64::INFINITY;
262 for (i, &x) in self.window.iter().enumerate() {
263 if x < best_val {
264 best_val = x;
265 best_idx = i;
266 }
267 }
268 (self.bar_index - p + best_idx) as f64
269 }
270}
271
272#[derive(Debug, Clone)]
273#[allow(non_camel_case_types)]
274pub struct MININDEX {
275 pub timeperiod: usize,
276 inner: RollingMinIndex,
277}
278
279impl MININDEX {
280 pub fn new(timeperiod: usize) -> Self {
281 Self {
282 timeperiod,
283 inner: RollingMinIndex::new(timeperiod),
284 }
285 }
286}
287
288impl Next<f64> for MININDEX {
289 type Output = f64;
290 fn next(&mut self, input: f64) -> Self::Output {
291 self.inner.push(input)
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use proptest::prelude::*;
299
300 proptest! {
301 #[test]
302 fn test_max_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
303 let period = 10;
304 let mut max = MAX::new(period);
305 let streaming: Vec<f64> = input.iter().map(|&x| max.next(x)).collect();
306 let batch = talib_rs::math_operator::max(&input, period)
307 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
308 for (s, b) in streaming.iter().zip(batch.iter()) {
309 if s.is_nan() { assert!(b.is_nan()); }
310 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
311 }
312 }
313 }
314}