1use crate::error::{Error, Result};
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone)]
39pub struct Rsx {
40 length: usize,
41 f18: f64,
42 f20: f64,
43 prev: Option<f64>,
44 count: usize,
45 s_a0: f64,
47 s_b0: f64,
48 s_a1: f64,
49 s_b1: f64,
50 s_a2: f64,
51 s_b2: f64,
52 a_a0: f64,
54 a_b0: f64,
55 a_a1: f64,
56 a_b1: f64,
57 a_a2: f64,
58 a_b2: f64,
59 last_value: Option<f64>,
60}
61
62impl Rsx {
63 pub fn new(length: usize) -> Result<Self> {
69 if length == 0 {
70 return Err(Error::PeriodZero);
71 }
72 let f18 = 3.0 / (length as f64 + 2.0);
73 Ok(Self {
74 length,
75 f18,
76 f20: 1.0 - f18,
77 prev: None,
78 count: 0,
79 s_a0: 0.0,
80 s_b0: 0.0,
81 s_a1: 0.0,
82 s_b1: 0.0,
83 s_a2: 0.0,
84 s_b2: 0.0,
85 a_a0: 0.0,
86 a_b0: 0.0,
87 a_a1: 0.0,
88 a_b1: 0.0,
89 a_a2: 0.0,
90 a_b2: 0.0,
91 last_value: None,
92 })
93 }
94
95 pub const fn length(&self) -> usize {
97 self.length
98 }
99
100 pub const fn value(&self) -> Option<f64> {
102 self.last_value
103 }
104
105 fn stage(&self, a: &mut f64, b: &mut f64, input: f64) -> f64 {
108 *a = self.f20 * *a + self.f18 * input;
109 *b = self.f18 * *a + self.f20 * *b;
110 1.5 * *a - 0.5 * *b
111 }
112}
113
114impl Indicator for Rsx {
115 type Input = f64;
116 type Output = f64;
117
118 fn update(&mut self, price: f64) -> Option<f64> {
119 if !price.is_finite() {
120 return self.last_value;
121 }
122 let Some(prev) = self.prev else {
123 self.prev = Some(price);
124 return None;
125 };
126 self.prev = Some(price);
127
128 let change = price - prev;
129
130 let (mut sa0, mut sb0) = (self.s_a0, self.s_b0);
132 let v_c = self.stage(&mut sa0, &mut sb0, change);
133 self.s_a0 = sa0;
134 self.s_b0 = sb0;
135 let (mut sa1, mut sb1) = (self.s_a1, self.s_b1);
136 let v_10 = self.stage(&mut sa1, &mut sb1, v_c);
137 self.s_a1 = sa1;
138 self.s_b1 = sb1;
139 let (mut sa2, mut sb2) = (self.s_a2, self.s_b2);
140 let v_14 = self.stage(&mut sa2, &mut sb2, v_10);
141 self.s_a2 = sa2;
142 self.s_b2 = sb2;
143
144 let abs = change.abs();
146 let (mut aa0, mut ab0) = (self.a_a0, self.a_b0);
147 let v_c1 = self.stage(&mut aa0, &mut ab0, abs);
148 self.a_a0 = aa0;
149 self.a_b0 = ab0;
150 let (mut aa1, mut ab1) = (self.a_a1, self.a_b1);
151 let v_18 = self.stage(&mut aa1, &mut ab1, v_c1);
152 self.a_a1 = aa1;
153 self.a_b1 = ab1;
154 let (mut aa2, mut ab2) = (self.a_a2, self.a_b2);
155 let v_1c = self.stage(&mut aa2, &mut ab2, v_18);
156 self.a_a2 = aa2;
157 self.a_b2 = ab2;
158
159 let v4 = if v_1c > 0.0 {
160 (v_14 / v_1c + 1.0) * 50.0
161 } else {
162 50.0
163 };
164 let rsx = v4.clamp(0.0, 100.0);
165
166 self.count += 1;
167 self.last_value = Some(rsx);
168 if self.count >= self.length {
169 Some(rsx)
170 } else {
171 None
172 }
173 }
174
175 fn reset(&mut self) {
176 *self = Self::new(self.length).expect("length already validated");
177 }
178
179 fn warmup_period(&self) -> usize {
180 self.length + 1
182 }
183
184 fn is_ready(&self) -> bool {
185 self.count >= self.length
186 }
187
188 fn name(&self) -> &'static str {
189 "RSX"
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use crate::traits::BatchExt;
197 use approx::assert_relative_eq;
198
199 #[test]
200 fn rejects_zero_length() {
201 assert!(matches!(Rsx::new(0), Err(Error::PeriodZero)));
202 }
203
204 #[test]
207 fn accessors_and_metadata() {
208 let rsx = Rsx::new(14).unwrap();
209 assert_eq!(rsx.length(), 14);
210 assert_eq!(rsx.value(), None);
211 assert_eq!(rsx.warmup_period(), 15);
212 assert_eq!(rsx.name(), "RSX");
213 }
214
215 #[test]
216 fn warmup_then_emits() {
217 let mut rsx = Rsx::new(3).unwrap();
218 assert_eq!(rsx.update(10.0), None);
220 assert_eq!(rsx.update(11.0), None);
221 assert_eq!(rsx.update(12.0), None);
222 assert!(rsx.update(13.0).is_some());
223 }
224
225 #[test]
226 fn flat_market_is_neutral() {
227 let mut rsx = Rsx::new(5).unwrap();
229 let last = rsx.batch(&[7.0; 40]).into_iter().flatten().last().unwrap();
230 assert_relative_eq!(last, 50.0, epsilon = 1e-12);
231 }
232
233 #[test]
234 fn output_stays_in_range() {
235 let prices: Vec<f64> = (0..120)
236 .map(|i| 100.0 + (f64::from(i) * 0.35).sin() * 12.0)
237 .collect();
238 let mut rsx = Rsx::new(14).unwrap();
239 for v in rsx.batch(&prices).into_iter().flatten() {
240 assert!((0.0..=100.0).contains(&v), "RSX {v} left [0, 100]");
241 }
242 }
243
244 #[test]
245 fn strong_uptrend_is_high() {
246 let prices: Vec<f64> = (1..=60).map(f64::from).collect();
248 let mut rsx = Rsx::new(14).unwrap();
249 let last = rsx.batch(&prices).into_iter().flatten().last().unwrap();
250 assert!(
251 last > 80.0,
252 "strong uptrend should push RSX high, got {last}"
253 );
254 }
255
256 #[test]
257 fn ignores_non_finite_input() {
258 let mut rsx = Rsx::new(3).unwrap();
259 let ready = rsx
260 .batch(&[1.0, 2.0, 3.0, 4.0, 5.0])
261 .into_iter()
262 .flatten()
263 .last()
264 .unwrap();
265 assert_eq!(rsx.update(f64::NAN), Some(ready));
266 assert_eq!(rsx.update(f64::INFINITY), Some(ready));
267 }
268
269 #[test]
270 fn reset_clears_state() {
271 let mut rsx = Rsx::new(5).unwrap();
272 rsx.batch(&(1..=40).map(f64::from).collect::<Vec<_>>());
273 assert!(rsx.is_ready());
274 rsx.reset();
275 assert!(!rsx.is_ready());
276 assert_eq!(rsx.update(1.0), None);
277 }
278
279 #[test]
280 fn batch_equals_streaming() {
281 let prices: Vec<f64> = (1..=60)
282 .map(|i| 50.0 + (f64::from(i) * 0.5).sin() * 10.0)
283 .collect();
284 let mut a = Rsx::new(14).unwrap();
285 let mut b = Rsx::new(14).unwrap();
286 assert_eq!(
287 a.batch(&prices),
288 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
289 );
290 }
291}