egui_charts/studies/builtin/
relative_vigor.rs1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::stepper::{rolling_mean, update_window, usable_behind};
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7#[derive(Clone)]
11pub struct RelativeVigorIndex {
12 period: usize,
14 values: IndicatorValues,
15 colors: Vec<Color32>,
17 visible: bool,
18}
19
20impl RelativeVigorIndex {
21 pub fn new(period: usize) -> Self {
23 Self {
24 period: period.max(1),
25 values: IndicatorValues::new(),
26 colors: vec![
27 Color32::from_rgb(0, 128, 0), Color32::from_rgb(255, 0, 0), ],
30 visible: true,
31 }
32 }
33
34 pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
36 self.colors = colors;
37 self
38 }
39
40 fn swma(data: &[f64]) -> Vec<f64> {
41 let mut result = Vec::with_capacity(data.len());
43
44 for i in 0..data.len() {
45 if i < 3 {
46 result.push(f64::NAN);
47 } else {
48 let swma = (data[i - 3] + 2.0 * data[i - 2] + 2.0 * data[i - 1] + data[i]) / 6.0;
49 result.push(swma);
50 }
51 }
52
53 result
54 }
55}
56
57impl Default for RelativeVigorIndex {
58 fn default() -> Self {
59 Self::new(10)
60 }
61}
62
63impl RelativeVigorIndex {
64 fn compute(&self, data: &[Bar]) -> IndicatorValues {
66 let mut values = IndicatorValues::new();
67 let close_open: Vec<f64> = data.iter().map(|b| b.close - b.open).collect();
69
70 let high_low: Vec<f64> = data.iter().map(|b| b.high - b.low).collect();
72
73 let smoothed_num = Self::swma(&close_open);
75 let smoothed_den = Self::swma(&high_low);
76
77 let num_sum = rolling_mean(&smoothed_num, self.period);
79 let den_sum = rolling_mean(&smoothed_den, self.period);
80
81 let mut rvi = Vec::with_capacity(data.len());
83 for i in 0..data.len() {
84 if num_sum[i].is_nan() || den_sum[i].is_nan() || den_sum[i] == 0.0 {
85 rvi.push(f64::NAN);
86 } else {
87 rvi.push(num_sum[i] / den_sum[i]);
88 }
89 }
90
91 let signal = Self::swma(&rvi);
93
94 for i in 0..data.len() {
96 if rvi[i].is_nan() {
97 values.push(IndicatorValue::None);
98 } else {
99 values.push_lines(&[rvi[i], signal[i]]);
100 }
101 }
102
103 values
104 }
105}
106
107impl Indicator for RelativeVigorIndex {
108 crate::studies::inputs::study_inputs!(self;
109 period: length("Period", 1, usize::MAX)
110 ; {});
111
112 fn name(&self) -> &str {
113 "RVI"
114 }
115
116 fn desc(&self) -> &str {
117 "Relative Vigor Index - Price movement conviction"
118 }
119
120 fn calculate(&mut self, data: &[Bar]) {
121 self.update(data, 0);
122 }
123
124 fn update(&mut self, data: &[Bar], from: usize) {
125 let mut values = std::mem::take(&mut self.values);
126 update_window(
127 &mut values,
128 data,
129 from,
130 usable_behind(self.period.saturating_add(5), |b| {
131 [b.open, b.high, b.low, b.close]
132 .iter()
133 .all(|x| x.is_finite())
134 }),
135 |data| {
136 over_finite_bars(
137 data,
138 |b| {
139 [b.open, b.high, b.low, b.close]
140 .iter()
141 .all(|x| x.is_finite())
142 },
143 |data| self.compute(data),
144 )
145 },
146 );
147 self.values = values;
148 }
149
150 fn is_incremental(&self) -> bool {
151 true
152 }
153
154 fn values(&self) -> &IndicatorValues {
155 &self.values
156 }
157
158 fn colors(&self) -> Vec<Color32> {
159 self.colors.clone()
160 }
161
162 fn set_colors(&mut self, colors: Vec<Color32>) {
163 if colors.len() >= 2 {
164 self.colors = colors;
165 }
166 }
167
168 fn is_overlay(&self) -> bool {
169 false
170 }
171
172 fn is_visible(&self) -> bool {
173 self.visible
174 }
175
176 fn set_visible(&mut self, visible: bool) {
177 self.visible = visible;
178 }
179
180 fn clone_box(&self) -> Box<dyn Indicator> {
181 Box::new(self.clone())
182 }
183
184 fn line_names(&self) -> Vec<String> {
185 vec![format!("RVI({})", self.period), "Signal".to_string()]
186 }
187}