Skip to main content

egui_charts/studies/builtin/
rainbow_oscillator.rs

1use 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/// Rainbow Oscillator (Mel Widner, "Rainbow Charts", Technical Analysis of Stocks & Commodities,
9/// July 1997): the close's distance from a stack of recursive moving averages, relative to the
10/// recent range of the close.
11///
12/// The first average is the `base_period` SMA of the close and each further one the same SMA of
13/// the one before, `levels` averages in all:
14/// `100 * (close - mean of the averages) / (highest close - lowest close over the last levels bars)`.
15/// A bar whose range is zero has no value, so a single level never gives one.
16#[derive(Clone)]
17pub struct RainbowOscillator {
18    /// Number of MA levels (typically 10)
19    levels: usize,
20    /// Base period for first SMA (typically 2)
21    base_period: usize,
22    values: IndicatorValues,
23    color: Color32,
24    visible: bool,
25}
26
27impl RainbowOscillator {
28    /// Create with `levels` moving averages and a base period in bars, each at least one.
29    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    /// Set the plot color.
40    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        // Get closes
61        let closes: Vec<f64> = data.iter().map(|b| b.close).collect();
62
63        // Sum the recursive SMAs (each applied to the previous) bar by bar, keeping only
64        // the latest level, so memory stays a few series whatever the level count.
65        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        // With a base period of 1 every level equals the closes, and once every bar misses a
70        // level no deeper level can add a value: either way deeper levels change nothing.
71        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        // 100 * (close - mean of the averages) / (highest - lowest close over the last `levels` bars)
81        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    /// Reference values from Mel Widner's published definition ("Rainbow Charts", Technical
188    /// Analysis of Stocks & Commodities, July 1997), computed independently in double precision:
189    /// `100 * (C - mean of ten recursive 2-bar SMAs) / (HHV(C, 10) - LLV(C, 10))`.
190    #[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            // Ten equal closes: the range is zero, so there is no value.
216            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}