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