Skip to main content

egui_charts/studies/builtin/
reversal.rs

1//! Reversal.
2//!
3//! Marks a bar that makes a new extreme of the last `lookback` bars and closes back beyond the
4//! close of the bar before it, a key reversal.
5//!
6//! # Rule
7//!
8//! With the previous `lookback` bars, the bar before the current one among them:
9//!
10//! ```text
11//! Upward:   low[t]  < lowest low of the previous bars   and  close[t] > close[t - 1]
12//! Downward: high[t] > highest high of the previous bars  and  close[t] < close[t - 1]
13//! ```
14//!
15//! The upward output holds the bar's low and the downward output its high, so the shapes sit
16//! beneath and above the bar. A signal needs `lookback` earlier bars, is known when its bar closes
17//! and never changes with later bars. Bars with a non-finite high, low or close are skipped. The
18//! default lookback is 20 bars.
19
20use crate::model::Bar;
21use crate::studies::palette;
22use crate::studies::stepper::Stepper;
23use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
24use egui::Color32;
25use std::collections::VecDeque;
26
27/// Reversal: bars that make a new extreme and close beyond the previous close.
28#[derive(Clone)]
29pub struct Reversal {
30    lookback: usize,
31    values: IndicatorValues,
32    stepper: Stepper<VecDeque<[f64; 3]>>,
33    up_color: Color32,
34    down_color: Color32,
35    visible: bool,
36}
37
38impl Reversal {
39    /// Reversals against the previous `lookback` bars, raised to at least 1.
40    pub fn new(lookback: usize) -> Self {
41        Self {
42            lookback: lookback.max(1),
43            values: IndicatorValues::new(),
44            stepper: Stepper::default(),
45            up_color: palette::GREEN,
46            down_color: palette::RED,
47            visible: true,
48        }
49    }
50}
51
52/// Construct with the conventional default parameters.
53impl Default for Reversal {
54    fn default() -> Self {
55        Self::new(20)
56    }
57}
58
59impl Indicator for Reversal {
60    crate::studies::inputs::study_inputs!(self;
61        lookback: length("Lookback", 1, usize::MAX)
62    ; {});
63
64    fn name(&self) -> &str {
65        "Reversal"
66    }
67
68    fn desc(&self) -> &str {
69        "Reversal - New extreme of the lookback that closes beyond the previous close"
70    }
71
72    fn calculate(&mut self, data: &[Bar]) {
73        self.update(data, 0);
74    }
75
76    fn update(&mut self, data: &[Bar], from: usize) {
77        let lookback = self.lookback;
78        self.stepper
79            .run(data, from, &mut self.values, |window, bar| {
80                if ![bar.high, bar.low, bar.close].iter().all(|v| v.is_finite()) {
81                    return IndicatorValue::None;
82                }
83                let mut row = [f64::NAN; 2];
84                if let (Some(&[_, _, close]), true) = (window.back(), window.len() == lookback) {
85                    // shortcut: scans the window on every bar, upgrade to running extremes if a
86                    // lookback of thousands of bars is common
87                    let lowest = window.iter().map(|b| b[1]).fold(f64::INFINITY, f64::min);
88                    let highest = window
89                        .iter()
90                        .map(|b| b[0])
91                        .fold(f64::NEG_INFINITY, f64::max);
92                    if bar.low < lowest && bar.close > close {
93                        row[0] = bar.low;
94                    }
95                    if bar.high > highest && bar.close < close {
96                        row[1] = bar.high;
97                    }
98                }
99                window.push_back([bar.high, bar.low, bar.close]);
100                if window.len() > lookback {
101                    window.pop_front();
102                }
103                IndicatorValue::Multiple(row.to_vec())
104            });
105    }
106
107    fn is_incremental(&self) -> bool {
108        true
109    }
110
111    fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
112        crate::studies::PlotStyle {
113            color: if line == 0 {
114                self.up_color
115            } else {
116                self.down_color
117            },
118            width: 1.0,
119            pattern: crate::model::LineStyle::Solid,
120            plot_type: if line == 0 {
121                crate::studies::PlotType::TriangleUp
122            } else {
123                crate::studies::PlotType::TriangleDown
124            },
125            visible: true,
126        }
127    }
128
129    fn values(&self) -> &IndicatorValues {
130        &self.values
131    }
132
133    fn colors(&self) -> Vec<Color32> {
134        vec![self.up_color, self.down_color]
135    }
136
137    fn set_colors(&mut self, colors: Vec<Color32>) {
138        if let Some(&color) = colors.first() {
139            self.up_color = color;
140        }
141        if let Some(&color) = colors.get(1) {
142            self.down_color = color;
143        }
144    }
145
146    fn is_visible(&self) -> bool {
147        self.visible
148    }
149
150    fn set_visible(&mut self, visible: bool) {
151        self.visible = visible;
152    }
153
154    fn clone_box(&self) -> Box<dyn Indicator> {
155        Box::new(self.clone())
156    }
157
158    fn line_names(&self) -> Vec<String> {
159        vec![
160            "Upward Reversal".to_string(),
161            "Downward Reversal".to_string(),
162        ]
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    /// Lookback 3, labelled by hand from (high, low, close). Bar 3 makes a new low but closes at 7
171    /// under the previous close 7.5; bar 4 makes the low 6 under the earlier 6.5 and closes at 8.2
172    /// over the previous 7; bar 6 breaks the high but closes over the previous close; bar 7 breaks
173    /// the high 11 with 12 and closes at 7.5 under the previous 10.5.
174    #[test]
175    fn key_reversals_need_a_new_extreme_and_a_close_beyond_the_previous_close() {
176        let bars = super::super::hlc_bars(&[
177            (10.0, 8.0, 9.0),
178            (9.5, 7.5, 8.0),
179            (8.5, 7.0, 7.5),
180            (8.0, 6.5, 7.0),
181            (8.5, 6.0, 8.2),
182            (9.0, 7.8, 8.8),
183            (11.0, 8.5, 10.5),
184            (12.0, 8.0, 7.5),
185        ]);
186        let mut study = Reversal::new(3);
187        super::super::assert_replay_matches_batch(&study, &bars);
188        study.calculate(&bars);
189        let line = |line: usize| -> Vec<Option<f64>> {
190            (0..bars.len())
191                .map(|at| study.values().value(line, at))
192                .collect()
193        };
194        let none = None;
195        assert_eq!(
196            line(0),
197            [none, none, none, none, Some(6.0), none, none, none]
198        );
199        assert_eq!(
200            line(1),
201            [none, none, none, none, none, none, none, Some(12.0)]
202        );
203    }
204}