Skip to main content

quantwave_core/indicators/incremental/
rolling.rs

1//! Native O(1) rolling MAX / MIN / SUM / MAXINDEX / MININDEX (fixed period).
2
3use 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}