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