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