wickra_core/indicators/
rwi.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::ohlcv::Candle;
7use crate::traits::Indicator;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct RwiOutput {
12 pub high: f64,
14 pub low: f64,
16}
17
18#[derive(Debug, Clone)]
60pub struct Rwi {
61 period: usize,
62 candles: VecDeque<Candle>,
64 trs: VecDeque<f64>,
67 last: Option<RwiOutput>,
68}
69
70impl Rwi {
71 pub fn new(period: usize) -> Result<Self> {
79 if period == 0 {
80 return Err(Error::PeriodZero);
81 }
82 if period < 2 {
83 return Err(Error::InvalidPeriod {
84 message: "RWI requires period >= 2",
85 });
86 }
87 Ok(Self {
88 period,
89 candles: VecDeque::with_capacity(period),
90 trs: VecDeque::with_capacity(period),
91 last: None,
92 })
93 }
94
95 pub const fn period(&self) -> usize {
97 self.period
98 }
99
100 pub const fn value(&self) -> Option<RwiOutput> {
102 self.last
103 }
104}
105
106impl Indicator for Rwi {
107 type Input = Candle;
108 type Output = RwiOutput;
109
110 fn update(&mut self, candle: Candle) -> Option<RwiOutput> {
111 let tr = if let Some(prev) = self.candles.back() {
114 candle.true_range(Some(prev.close))
115 } else {
116 candle.high - candle.low
117 };
118
119 if self.candles.len() == self.period {
120 self.candles.pop_front();
121 }
122 self.candles.push_back(candle);
123
124 if self.candles.len() >= 2 {
128 if self.trs.len() == self.period - 1 {
129 self.trs.pop_front();
130 }
131 self.trs.push_back(tr);
132 }
133
134 if self.candles.len() < self.period {
136 return None;
137 }
138
139 let candles: Vec<&Candle> = self.candles.iter().collect();
141 let trs: Vec<f64> = self.trs.iter().copied().collect();
142 let n = candles.len(); let last_high = candles[n - 1].high;
144 let last_low = candles[n - 1].low;
145
146 let mut rwi_high = 0.0_f64;
147 let mut rwi_low = 0.0_f64;
148 for i in 2..=self.period {
156 let tr_start = n - i;
160 let tr_end = n - 1;
161 let count = tr_end - tr_start;
162 let atr_i: f64 = trs[tr_start..tr_end].iter().sum::<f64>() / (count as f64);
163 let denom = atr_i * (i as f64).sqrt();
164 if denom == 0.0 {
165 continue;
166 }
167 let old_low = candles[n - i].low;
168 let old_high = candles[n - i].high;
169 let h = (last_high - old_low) / denom;
170 let l = (old_high - last_low) / denom;
171 if h > rwi_high {
172 rwi_high = h;
173 }
174 if l > rwi_low {
175 rwi_low = l;
176 }
177 }
178
179 let out = RwiOutput {
180 high: rwi_high,
181 low: rwi_low,
182 };
183 self.last = Some(out);
184 Some(out)
185 }
186
187 fn reset(&mut self) {
188 self.candles.clear();
189 self.trs.clear();
190 self.last = None;
191 }
192
193 fn warmup_period(&self) -> usize {
194 self.period
196 }
197
198 fn is_ready(&self) -> bool {
199 self.last.is_some()
200 }
201
202 fn name(&self) -> &'static str {
203 "RWI"
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::traits::BatchExt;
211
212 fn candle(h: f64, l: f64, c: f64, ts: i64) -> Candle {
213 Candle::new(c, h, l, c, 1.0, ts).unwrap()
214 }
215
216 #[test]
217 fn rejects_zero_period() {
218 assert!(matches!(Rwi::new(0), Err(Error::PeriodZero)));
219 }
220
221 #[test]
222 fn rejects_period_one() {
223 assert!(matches!(Rwi::new(1), Err(Error::InvalidPeriod { .. })));
224 }
225
226 #[test]
227 fn accessors_and_metadata() {
228 let mut r = Rwi::new(14).unwrap();
229 assert_eq!(r.period(), 14);
230 assert_eq!(r.warmup_period(), 14);
231 assert_eq!(r.name(), "RWI");
232 assert!(r.value().is_none());
233 for i in 0..30_i64 {
234 let p = 100.0 + (i as f64);
235 r.update(candle(p + 1.0, p - 1.0, p, i));
236 }
237 assert!(r.value().is_some());
238 }
239
240 #[test]
241 fn first_emission_at_warmup_period() {
242 let candles: Vec<Candle> = (0..40_i64)
243 .map(|i| {
244 let p = 100.0 + ((i as f64) * 0.3).sin() * 5.0;
245 candle(p + 1.0, p - 1.0, p, i)
246 })
247 .collect();
248 let mut r = Rwi::new(5).unwrap();
249 let out = r.batch(&candles);
250 for v in out.iter().take(4) {
251 assert!(v.is_none());
252 }
253 assert!(out[4].is_some());
254 }
255
256 #[test]
257 fn constant_series_yields_zero_outputs() {
258 let candles: Vec<Candle> = (0..30_i64).map(|i| candle(10.0, 10.0, 10.0, i)).collect();
261 let mut r = Rwi::new(5).unwrap();
262 let last = r.batch(&candles).into_iter().flatten().last().unwrap();
263 assert_eq!(last.high, 0.0);
264 assert_eq!(last.low, 0.0);
265 }
266
267 #[test]
268 fn pure_uptrend_high_dominates_low() {
269 let candles: Vec<Candle> = (0..40_i64)
271 .map(|i| {
272 let base = 100.0 + (i as f64) * 2.0;
273 candle(base + 1.0, base - 0.5, base + 0.5, i)
274 })
275 .collect();
276 let mut r = Rwi::new(14).unwrap();
277 let last = r.batch(&candles).into_iter().flatten().last().unwrap();
278 assert!(
279 last.high > last.low,
280 "RWI_High {} should exceed RWI_Low {}",
281 last.high,
282 last.low
283 );
284 assert!(
285 last.high > 1.0,
286 "strong uptrend should exceed 1, got {}",
287 last.high
288 );
289 }
290
291 #[test]
292 fn pure_downtrend_low_dominates_high() {
293 let candles: Vec<Candle> = (0..40_i64)
294 .rev()
295 .map(|i| {
296 let base = 100.0 + (i as f64) * 2.0;
297 candle(base + 0.5, base - 1.0, base - 0.5, 40 - i)
298 })
299 .collect();
300 let mut r = Rwi::new(14).unwrap();
301 let last = r.batch(&candles).into_iter().flatten().last().unwrap();
302 assert!(last.low > last.high);
303 assert!(last.low > 1.0);
304 }
305
306 #[test]
307 fn outputs_non_negative() {
308 let candles: Vec<Candle> = (0..120_i64)
309 .map(|i| {
310 let p = 100.0 + ((i as f64) * 0.25).sin() * 6.0;
311 candle(p + 1.5, p - 1.5, p, i)
312 })
313 .collect();
314 let mut r = Rwi::new(10).unwrap();
315 for v in r.batch(&candles).into_iter().flatten() {
316 assert!(v.high >= 0.0 && v.low >= 0.0);
317 assert!(v.high.is_finite() && v.low.is_finite());
318 }
319 }
320
321 #[test]
322 fn batch_equals_streaming() {
323 let candles: Vec<Candle> = (0..80_i64)
324 .map(|i| {
325 let p = 100.0 + ((i as f64) * 0.3).sin() * 5.0;
326 candle(p + 1.0, p - 1.0, p, i)
327 })
328 .collect();
329 let mut a = Rwi::new(7).unwrap();
330 let mut b = Rwi::new(7).unwrap();
331 assert_eq!(
332 a.batch(&candles),
333 candles.iter().map(|c| b.update(*c)).collect::<Vec<_>>()
334 );
335 }
336
337 #[test]
338 fn reset_clears_state() {
339 let candles: Vec<Candle> = (0..30_i64).map(|i| candle(11.0, 9.0, 10.0, i)).collect();
340 let mut r = Rwi::new(5).unwrap();
341 r.batch(&candles);
342 assert!(r.is_ready());
343 r.reset();
344 assert!(!r.is_ready());
345 assert_eq!(r.update(candles[0]), None);
346 }
347}