1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::ohlcv::Candle;
7use crate::traits::Indicator;
8
9#[derive(Debug, Clone)]
35pub struct Rvi {
36 period: usize,
37 window: VecDeque<(f64, f64)>,
38 sum_num: f64,
39 sum_den: f64,
40 current: Option<f64>,
41}
42
43impl Rvi {
44 pub fn new(period: usize) -> Result<Self> {
47 if period == 0 {
48 return Err(Error::PeriodZero);
49 }
50 if period > crate::error::MAX_PERIOD {
51 return Err(Error::InvalidPeriod {
52 message: crate::error::PERIOD_ABOVE_MAX,
53 });
54 }
55 Ok(Self {
56 period,
57 window: VecDeque::with_capacity(period),
58 sum_num: 0.0,
59 sum_den: 0.0,
60 current: None,
61 })
62 }
63
64 pub const fn period(&self) -> usize {
66 self.period
67 }
68
69 pub const fn value(&self) -> Option<f64> {
71 self.current
72 }
73}
74
75impl Indicator for Rvi {
76 type Input = Candle;
77 type Output = f64;
78
79 #[inline]
80 fn update(&mut self, candle: Candle) -> Option<f64> {
81 let num = candle.close - candle.open;
82 let den = candle.high - candle.low;
83 if self.window.len() == self.period {
84 let (old_n, old_d) = self.window.pop_front().expect("window is non-empty");
85 self.sum_num -= old_n;
86 self.sum_den -= old_d;
87 }
88 self.window.push_back((num, den));
89 self.sum_num += num;
90 self.sum_den += den;
91 if self.window.len() < self.period {
92 return None;
93 }
94 if self.sum_den <= 0.0 {
95 return self.current;
98 }
99 let value = self.sum_num / self.sum_den;
100 self.current = Some(value);
101 Some(value)
102 }
103
104 fn reset(&mut self) {
105 self.window.clear();
106 self.sum_num = 0.0;
107 self.sum_den = 0.0;
108 self.current = None;
109 }
110
111 #[inline]
112 fn warmup_period(&self) -> usize {
113 self.period
114 }
115
116 #[inline]
117 fn is_ready(&self) -> bool {
118 self.current.is_some()
119 }
120
121 #[inline]
122 fn name(&self) -> &'static str {
123 "RVI"
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::traits::BatchExt;
131 use approx::assert_relative_eq;
132
133 fn candle(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
134 Candle::new(open, high, low, close, 1.0, ts).unwrap()
135 }
136
137 #[test]
138 fn rejects_zero_period() {
139 assert!(matches!(Rvi::new(0), Err(Error::PeriodZero)));
140 }
141
142 #[test]
143 fn accessors_and_metadata() {
144 let mut r = Rvi::new(10).unwrap();
145 assert_eq!(r.period(), 10);
146 assert_eq!(r.warmup_period(), 10);
147 assert_eq!(r.name(), "RVI");
148 assert_eq!(r.value(), None);
149 for i in 0..10 {
150 r.update(candle(10.0, 11.0, 9.0, 10.5, i));
151 }
152 assert!(r.value().is_some());
153 }
154
155 #[test]
156 fn reference_value_period_2() {
157 let mut r = Rvi::new(2).unwrap();
163 assert_eq!(r.update(candle(10.0, 11.0, 9.0, 10.5, 0)), None);
164 let v = r.update(candle(10.5, 11.5, 10.0, 11.0, 1)).unwrap();
165 assert_relative_eq!(v, 1.0 / 3.5, epsilon = 1e-12);
166 }
167
168 #[test]
169 fn warmup_emits_first_value_at_period() {
170 let mut r = Rvi::new(3).unwrap();
171 for i in 0..2 {
172 assert_eq!(r.update(candle(10.0, 11.0, 9.0, 10.5, i)), None);
173 }
174 assert!(r.update(candle(10.5, 11.5, 10.0, 11.0, 2)).is_some());
175 }
176
177 #[test]
178 fn pure_uptrend_is_positive() {
179 let mut r = Rvi::new(5).unwrap();
181 for i in 0..10 {
182 let o = 10.0 + f64::from(i);
183 let c = o + 0.5;
184 r.update(candle(o, c + 0.2, o - 0.2, c, i64::from(i)));
185 }
186 let v = r.value().unwrap();
187 assert!(v > 0.0, "uptrend RVI should be positive: {v}");
188 }
189
190 #[test]
191 fn zero_range_window_holds_value() {
192 let mut r = Rvi::new(3).unwrap();
195 r.update(candle(10.0, 10.0, 10.0, 10.0, 0));
196 r.update(candle(10.0, 10.0, 10.0, 10.0, 1));
197 assert_eq!(r.update(candle(10.0, 10.0, 10.0, 10.0, 2)), None);
198 }
199
200 #[test]
201 fn batch_equals_streaming() {
202 let candles: Vec<Candle> = (0..40_i64)
203 .map(|i| {
204 let o = 100.0 + (i as f64 * 0.3).sin() * 5.0;
205 let c = o + (i as f64 * 0.1).cos();
206 candle(o, o.max(c) + 0.5, o.min(c) - 0.5, c, i)
207 })
208 .collect();
209 let batch = Rvi::new(10).unwrap().batch(&candles);
210 let mut b = Rvi::new(10).unwrap();
211 let streamed: Vec<_> = candles.iter().map(|c| b.update(*c)).collect();
212 assert_eq!(batch, streamed);
213 }
214
215 #[test]
216 fn reset_clears_state() {
217 let mut r = Rvi::new(5).unwrap();
218 for i in 0..10 {
219 r.update(candle(10.0, 11.0, 9.0, 10.5, i));
220 }
221 assert!(r.is_ready());
222 r.reset();
223 assert!(!r.is_ready());
224 assert_eq!(r.update(candle(10.0, 11.0, 9.0, 10.5, 0)), None);
225 }
226}