nautilus_indicators/volatility/
rvi.rs1use std::{
17 collections::VecDeque,
18 fmt::{Debug, Display},
19};
20
21use nautilus_model::data::Bar;
22
23use crate::{
24 average::{MovingAverageFactory, MovingAverageType},
25 indicator::{Indicator, MovingAverage},
26 momentum::bb::fast_std_with_mean,
27};
28
29#[repr(C)]
31#[derive(Debug)]
32#[cfg_attr(
33 feature = "python",
34 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators", unsendable)
35)]
36pub struct RelativeVolatilityIndex {
37 pub period: usize,
38 pub scalar: f64,
39 pub ma_type: MovingAverageType,
40 pub value: f64,
41 pub initialized: bool,
42 prices: VecDeque<f64>,
43 ma: Box<dyn MovingAverage + Send + 'static>,
44 pos_ma: Box<dyn MovingAverage + Send + 'static>,
45 neg_ma: Box<dyn MovingAverage + Send + 'static>,
46 previous_close: f64,
47 std: f64,
48 has_inputs: bool,
49}
50
51impl Display for RelativeVolatilityIndex {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(
54 f,
55 "{}({},{},{})",
56 self.name(),
57 self.period,
58 self.scalar,
59 self.ma_type,
60 )
61 }
62}
63
64impl Indicator for RelativeVolatilityIndex {
65 fn name(&self) -> String {
66 stringify!(RelativeVolatilityIndex).to_string()
67 }
68
69 fn has_inputs(&self) -> bool {
70 self.has_inputs
71 }
72
73 fn initialized(&self) -> bool {
74 self.initialized
75 }
76
77 fn handle_bar(&mut self, bar: &Bar) {
78 self.update_raw((&bar.close).into());
79 }
80
81 fn reset(&mut self) {
82 self.previous_close = 0.0;
83 self.value = 0.0;
84 self.has_inputs = false;
85 self.initialized = false;
86 self.std = 0.0;
87 self.prices.clear();
88 self.ma.reset();
89 self.pos_ma.reset();
90 self.neg_ma.reset();
91 }
92}
93
94impl RelativeVolatilityIndex {
95 #[must_use]
97 pub fn new(period: usize, scalar: Option<f64>, ma_type: Option<MovingAverageType>) -> Self {
98 Self {
99 period,
100 scalar: scalar.unwrap_or(100.0),
101 ma_type: ma_type.unwrap_or(MovingAverageType::Simple),
102 value: 0.0,
103 initialized: false,
104 prices: VecDeque::with_capacity(period),
105 ma: MovingAverageFactory::create(ma_type.unwrap_or(MovingAverageType::Simple), period),
106 pos_ma: MovingAverageFactory::create(
107 ma_type.unwrap_or(MovingAverageType::Simple),
108 period,
109 ),
110 neg_ma: MovingAverageFactory::create(
111 ma_type.unwrap_or(MovingAverageType::Simple),
112 period,
113 ),
114 previous_close: 0.0,
115 std: 0.0,
116 has_inputs: false,
117 }
118 }
119
120 pub fn update_raw(&mut self, close: f64) {
121 self.prices.push_back(close);
122 self.ma.update_raw(close);
123
124 self.std = fast_std_with_mean(self.prices.clone(), self.ma.value());
125 self.std = self.std * (self.period as f64).sqrt() / ((self.period - 1) as f64).sqrt();
126
127 if self.ma.initialized() {
128 if close > self.previous_close {
129 self.pos_ma.update_raw(self.std);
130 self.neg_ma.update_raw(0.0);
131 } else if close < self.previous_close {
132 self.pos_ma.update_raw(0.0);
133 self.neg_ma.update_raw(self.std);
134 } else {
135 self.pos_ma.update_raw(0.0);
136 self.neg_ma.update_raw(0.0);
137 }
138
139 self.value = self.scalar * self.pos_ma.value();
140 self.value /= self.pos_ma.value() + self.neg_ma.value();
141 }
142
143 self.previous_close = close;
144
145 if !self.initialized {
147 self.has_inputs = true;
148 if self.pos_ma.initialized() {
149 self.initialized = true;
150 }
151 }
152 }
153}
154
155#[cfg(test)]
159mod tests {
160 use rstest::rstest;
161
162 use super::*;
163 use crate::stubs::rvi_10;
164
165 #[rstest]
166 fn test_name_returns_expected_string(rvi_10: RelativeVolatilityIndex) {
167 assert_eq!(rvi_10.name(), "RelativeVolatilityIndex");
168 }
169
170 #[rstest]
171 fn test_str_repr_returns_expected_string(rvi_10: RelativeVolatilityIndex) {
172 assert_eq!(format!("{rvi_10}"), "RelativeVolatilityIndex(10,10,SIMPLE)");
173 }
174
175 #[rstest]
176 fn test_period_returns_expected_value(rvi_10: RelativeVolatilityIndex) {
177 assert_eq!(rvi_10.period, 10);
178 assert_eq!(rvi_10.scalar, 10.0);
179 assert_eq!(rvi_10.ma_type, MovingAverageType::Simple);
180 }
181
182 #[rstest]
183 fn test_initialized_without_inputs_returns_false(rvi_10: RelativeVolatilityIndex) {
184 assert!(!rvi_10.initialized());
185 }
186
187 #[rstest]
188 fn test_value_with_all_higher_inputs_returns_expected_value(
189 mut rvi_10: RelativeVolatilityIndex,
190 ) {
191 let close_values = [
192 105.25, 107.50, 109.75, 112.00, 114.25, 116.50, 118.75, 121.00, 123.25, 125.50, 127.75,
193 130.00, 132.25, 134.50, 136.75, 139.00, 141.25, 143.50, 145.75, 148.00, 150.25, 152.50,
194 154.75, 157.00, 159.25, 161.50, 163.75, 166.00, 168.25, 170.50,
195 ];
196
197 for close in close_values {
198 rvi_10.update_raw(close);
199 }
200
201 assert!(rvi_10.initialized());
202 assert_eq!(rvi_10.value, 10.0);
203 }
204
205 #[rstest]
206 fn test_reset_successfully_returns_indicator_to_fresh_state(
207 mut rvi_10: RelativeVolatilityIndex,
208 ) {
209 rvi_10.update_raw(1.00020);
210 rvi_10.update_raw(1.00030);
211 rvi_10.update_raw(1.00070);
212
213 rvi_10.reset();
214
215 assert!(!rvi_10.initialized());
216 assert_eq!(rvi_10.value, 0.0);
217 assert!(!rvi_10.initialized);
218 assert!(!rvi_10.has_inputs);
219 assert_eq!(rvi_10.std, 0.0);
220 assert_eq!(rvi_10.prices.len(), 0);
221 assert_eq!(rvi_10.ma.value(), 0.0);
222 assert_eq!(rvi_10.pos_ma.value(), 0.0);
223 assert_eq!(rvi_10.neg_ma.value(), 0.0);
224 }
225}