quantwave_core/indicators/
my_rsi.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
10pub struct MyRSI {
11 length: usize,
12 price_window: VecDeque<f64>,
13}
14
15impl MyRSI {
16 pub fn new(length: usize) -> Self {
17 Self {
18 length,
19 price_window: VecDeque::with_capacity(length + 1),
20 }
21 }
22}
23
24impl Default for MyRSI {
25 fn default() -> Self {
26 Self::new(14)
27 }
28}
29
30impl Next<f64> for MyRSI {
31 type Output = f64;
32
33 fn next(&mut self, input: f64) -> Self::Output {
34 self.price_window.push_front(input);
35 if self.price_window.len() > self.length + 1 {
36 self.price_window.pop_back();
37 }
38
39 if self.price_window.len() < self.length + 1 {
40 return 0.0;
41 }
42
43 let mut cu = 0.0;
44 let mut cd = 0.0;
45
46 for i in 0..self.length {
47 let diff = self.price_window[i] - self.price_window[i + 1];
48 if diff > 0.0 {
49 cu += diff;
50 } else if diff < 0.0 {
51 cd -= diff;
52 }
53 }
54
55 if cu + cd != 0.0 {
56 (cu - cd) / (cu + cd)
57 } else {
58 0.0
59 }
60 }
61}
62
63pub const MY_RSI_METADATA: IndicatorMetadata = IndicatorMetadata {
64 name: "MyRSI",
65 description: "Ehlers' version of RSI that swings between -1 and +1.",
66 params: &[ParamDef {
67 name: "length",
68 default: "14",
69 description: "Smoothing length",
70 }],
71 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/Noise%20Elimination%20Technology.pdf",
72 formula_latex: r#"
73\[
74CU = \sum_{i=0}^{length-1} \max(0, Price_i - Price_{i+1})
75\]
76\[
77CD = \sum_{i=0}^{length-1} \max(0, Price_{i+1} - Price_i)
78\]
79\[
80MyRSI = \frac{CU - CD}{CU + CD}
81\]
82"#,
83 gold_standard_file: "my_rsi.json",
84 category: "Ehlers DSP",
85};
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::traits::Next;
91 use proptest::prelude::*;
92
93 #[test]
94 fn test_my_rsi_basic() {
95 let mut rsi = MyRSI::new(14);
96 let inputs = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0];
97 let mut last_rsi = 0.0;
98 for input in inputs {
99 last_rsi = rsi.next(input);
100 }
101 assert_eq!(last_rsi, 1.0);
102 }
103
104 proptest! {
105 #[test]
106 fn test_my_rsi_parity(
107 inputs in prop::collection::vec(1.0..100.0, 20..100),
108 ) {
109 let length = 14;
110 let mut rsi = MyRSI::new(length);
111 let streaming_results: Vec<f64> = inputs.iter().map(|&x| rsi.next(x)).collect();
112
113 let mut batch_results = Vec::with_capacity(inputs.len());
115 for i in 0..inputs.len() {
116 if i < length {
117 batch_results.push(0.0);
118 continue;
119 }
120
121 let mut cu = 0.0;
122 let mut cd = 0.0;
123 for j in 0..length {
124 let diff = inputs[i - j] - inputs[i - j - 1];
125 if diff > 0.0 {
126 cu += diff;
127 } else if diff < 0.0 {
128 cd -= diff;
129 }
130 }
131
132 if cu + cd != 0.0 {
133 batch_results.push((cu - cd) / (cu + cd));
134 } else {
135 batch_results.push(0.0);
136 }
137 }
138
139 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
140 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
141 }
142 }
143 }
144}