egui_charts/studies/builtin/
rainbow_oscillator.rs1use crate::model::Bar;
2use crate::studies::helpers;
3use crate::studies::palette;
4use crate::studies::stepper::update_window;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8#[derive(Clone)]
17pub struct RainbowOscillator {
18 levels: usize,
20 base_period: usize,
22 values: IndicatorValues,
23 color: Color32,
24 visible: bool,
25}
26
27impl RainbowOscillator {
28 pub fn new(levels: usize, base_period: usize) -> Self {
30 Self {
31 levels: levels.max(1),
32 base_period: base_period.max(1),
33 values: IndicatorValues::new(),
34 color: palette::PURPLE,
35 visible: true,
36 }
37 }
38
39 pub fn with_color(mut self, color: Color32) -> Self {
41 self.color = color;
42 self
43 }
44}
45
46impl Default for RainbowOscillator {
47 fn default() -> Self {
48 Self::new(10, 2)
49 }
50}
51
52impl RainbowOscillator {
53 fn compute(&self, data: &[Bar]) -> IndicatorValues {
54 let mut values = IndicatorValues::new();
55
56 if data.is_empty() {
57 return values;
58 }
59
60 let closes: Vec<f64> = data.iter().map(|b| b.close).collect();
62
63 let mut level = helpers::sma(&closes, self.base_period);
66 let mut sums = level.clone();
67 let mut missing: Vec<bool> = level.iter().map(|v| v.is_nan()).collect();
68 let mut level_cnt = 1;
69 while level_cnt < self.levels && self.base_period > 1 && missing.contains(&false) {
72 level = helpers::sma(&level, self.base_period);
73 for ((sum, m), v) in sums.iter_mut().zip(missing.iter_mut()).zip(&level) {
74 *sum += v;
75 *m |= v.is_nan();
76 }
77 level_cnt += 1;
78 }
79
80 for i in 0..data.len() {
82 if missing[i] || i + 1 < self.levels {
83 values.push(IndicatorValue::None);
84 continue;
85 }
86 let window = &closes[i + 1 - self.levels..=i];
87 let highest = window.iter().copied().fold(f64::NEG_INFINITY, f64::max);
88 let lowest = window.iter().copied().fold(f64::INFINITY, f64::min);
89 let range = highest - lowest;
90 if range > 0.0 {
91 let osc = 100.0 * (closes[i] - sums[i] / level_cnt as f64) / range;
92 values.push(IndicatorValue::Single(osc));
93 } else {
94 values.push(IndicatorValue::None);
95 }
96 }
97 values
98 }
99}
100
101impl Indicator for RainbowOscillator {
102 fn source(&self) -> Option<crate::studies::StudySource> {
103 Some(crate::studies::StudySource::Price(
104 crate::model::PriceSource::Close,
105 ))
106 }
107
108 crate::studies::inputs::study_inputs!(self;
109 levels: length("Levels", 1, usize::MAX),
110 base_period: length("Base period", 1, usize::MAX)
111 ; {});
112
113 fn name(&self) -> &str {
114 "Rainbow"
115 }
116
117 fn desc(&self) -> &str {
118 "Rainbow Oscillator - Multi-MA distance"
119 }
120
121 fn calculate(&mut self, data: &[Bar]) {
122 self.update(data, 0);
123 }
124
125 fn update(&mut self, data: &[Bar], from: usize) {
126 let mut values = std::mem::take(&mut self.values);
127 update_window(
128 &mut values,
129 data,
130 from,
131 |_data, from| {
132 from.saturating_sub(
133 (self.base_period - 1)
134 .saturating_mul(self.levels)
135 .max(self.levels - 1),
136 )
137 },
138 |data| self.compute(data),
139 );
140 self.values = values;
141 }
142
143 fn is_incremental(&self) -> bool {
144 true
145 }
146
147 fn values(&self) -> &IndicatorValues {
148 &self.values
149 }
150
151 fn colors(&self) -> Vec<Color32> {
152 vec![self.color]
153 }
154
155 fn set_colors(&mut self, colors: Vec<Color32>) {
156 if !colors.is_empty() {
157 self.color = colors[0];
158 }
159 }
160
161 fn is_overlay(&self) -> bool {
162 false
163 }
164
165 fn is_visible(&self) -> bool {
166 self.visible
167 }
168
169 fn set_visible(&mut self, visible: bool) {
170 self.visible = visible;
171 }
172
173 fn clone_box(&self) -> Box<dyn Indicator> {
174 Box::new(self.clone())
175 }
176
177 fn line_names(&self) -> Vec<String> {
178 vec![format!("Rainbow({},{})", self.levels, self.base_period)]
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::studies::test_support::make_bar;
186
187 #[test]
191 fn rainbow_matches_published_definition() {
192 let mut closes = vec![10.0, 11.0, 13.0, 12.0, 14.0, 15.0, 13.0, 16.0, 17.0, 15.0];
193 closes.extend([18.0; 11]);
194 closes.extend([20.0, 19.0, 21.0]);
195 let expected: [Option<f64>; 24] = [
196 None,
197 None,
198 None,
199 None,
200 None,
201 None,
202 None,
203 None,
204 None,
205 None,
206 Some(36.67689732142857),
207 Some(30.81705729166669),
208 Some(20.88704427083332),
209 Some(15.666015624999972),
210 Some(8.736328124999986),
211 Some(4.130859375),
212 Some(2.5781250000000235),
213 Some(0.6998697916666666),
214 Some(0.12044270833335702),
215 None,
217 None,
218 Some(90.00976562500007),
219 Some(25.1220703125),
220 Some(70.48502604166664),
221 ];
222
223 let mut rainbow = RainbowOscillator::default();
224 rainbow.calculate(&closes.iter().map(|&c| make_bar(c)).collect::<Vec<_>>());
225
226 assert_eq!(rainbow.values.len(), expected.len());
227 for (i, (got, want)) in rainbow.values.to_rows().iter().zip(expected).enumerate() {
228 match (got, want) {
229 (IndicatorValue::None, None) => {}
230 (IndicatorValue::Single(g), Some(w)) => {
231 assert!((g - w).abs() < 1e-12, "bar {i}: got {g}, want {w}")
232 }
233 _ => panic!("bar {i}: got {got:?}, want {want:?}"),
234 }
235 }
236 }
237}