Skip to main content

sliding_features/sliding_windows/
my_rsi.rs

1//! John Ehlers MyRSI
2//! from: <http://www.mesasoftware.com/papers/Noise%20Elimination%20Technology.pdf>
3
4use std::{
5    collections::VecDeque,
6    num::NonZeroUsize,
7};
8
9use getset::CopyGetters;
10use num::Float;
11
12use crate::View;
13
14/// John Ehlers MyRSI
15/// from: <http://www.mesasoftware.com/papers/Noise%20Elimination%20Technology.pdf>
16#[derive(Debug, Clone, CopyGetters)]
17pub struct MyRSI<T, V> {
18    view: V,
19    /// The sliding window length.
20    #[getset(get_copy = "pub")]
21    window_len: NonZeroUsize,
22    cu: T,
23    cd: T,
24    out: T,
25    q_vals: VecDeque<T>,
26    last_val: T,
27    oldest_val: T,
28}
29
30impl<T, V> MyRSI<T, V>
31where
32    V: View<T>,
33    T: Float,
34{
35    /// Create a new MyRSI indicator with a chained View and a given window length
36    pub fn new(view: V, window_len: NonZeroUsize) -> Self {
37        MyRSI {
38            view,
39            window_len,
40            cu: T::zero(),
41            cd: T::zero(),
42            out: T::zero(),
43            q_vals: VecDeque::with_capacity(window_len.get()),
44            last_val: T::zero(),
45            oldest_val: T::zero(),
46        }
47    }
48}
49
50impl<T, V> View<T> for MyRSI<T, V>
51where
52    V: View<T>,
53    T: Float,
54{
55    fn update(&mut self, val: T) {
56        debug_assert!(val.is_finite(), "value must be finite");
57        self.view.update(val);
58        let Some(val) = self.view.last() else { return };
59        debug_assert!(val.is_finite(), "value must be finite");
60
61        if self.q_vals.is_empty() {
62            self.oldest_val = val;
63            self.last_val = val;
64        }
65        if self.q_vals.len() >= self.window_len.get() {
66            let old_val = self.q_vals.pop_front().unwrap();
67            if old_val > self.oldest_val {
68                self.cu = self.cu - (old_val - self.oldest_val);
69            } else {
70                self.cd = self.cd - (self.oldest_val - old_val);
71            }
72            self.oldest_val = old_val;
73        }
74        self.q_vals.push_back(val);
75
76        // accumulate 'closes up' and 'closes down'
77        if val > self.last_val {
78            self.cu = self.cu + val - self.last_val;
79        } else {
80            self.cd = self.cd + self.last_val - val;
81        }
82        self.last_val = val;
83
84        if self.cu + self.cd != T::zero() {
85            self.out = (self.cu - self.cd) / (self.cu + self.cd);
86        }
87    }
88
89    #[inline]
90    fn last(&self) -> Option<T> {
91        if self.q_vals.len() < self.window_len.get() {
92            return None;
93        }
94        debug_assert!(self.out.is_finite(), "value must be finite");
95        Some(self.out)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::{
103        plot::plot_values,
104        pure_functions::Echo,
105        test_data::TEST_DATA,
106    };
107
108    #[test]
109    fn my_rsi() {
110        // TODO: don't be so lazy with this test.
111        let mut my_rsi = MyRSI::<f64, _>::new(Echo::new(), NonZeroUsize::new(16).unwrap());
112        for v in &TEST_DATA {
113            my_rsi.update(*v);
114            if let Some(val) = my_rsi.last() {
115                dbg!(val);
116                assert!(val <= 1.0);
117                assert!(val >= -1.0);
118            }
119        }
120    }
121
122    #[test]
123    fn my_rsi_plot() {
124        let mut my_rsi = MyRSI::new(Echo::new(), NonZeroUsize::new(16).unwrap());
125        let mut out: Vec<f64> = Vec::new();
126        for v in &TEST_DATA {
127            my_rsi.update(*v);
128            if let Some(rsi) = my_rsi.last() {
129                out.push(rsi);
130            }
131        }
132        println!("out: {:?}", out);
133        let filename = "img/my_rsi.png";
134        plot_values(out, filename).unwrap();
135    }
136}