Skip to main content

egui_charts/studies/builtin/
bollinger_bars.rs

1//! Bollinger Bars
2//!
3//! John Bollinger's Bollinger Bars redraw each price bar at full candle width, so the whole
4//! range traded in the period reads as a block: the body between open and close is coloured
5//! up (close at or above open) or down, and the parts above and below it, the wicks, are
6//! drawn at body width in a third colour, blue by default.
7//!
8//! [`Indicator::candles`] holds two candles per bar: first a block from the high to the low on
9//! plot 0 ("Wicks"), then the body from the open to the close on plot 1 ("Up") or 2 ("Down"),
10//! painted over the block. The output columns hold each bar's close in the up or down column.
11//!
12//! # Example
13//! ```
14//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
15//! use egui_charts::studies::{BollingerBars, Indicator};
16//!
17//! let mut bars_study = BollingerBars::new();
18//! bars_study.calculate(&bars);
19//! assert_eq!(bars_study.candles().len(), 2 * bars.len());
20//! ```
21
22use crate::model::Bar;
23use crate::studies::palette;
24use crate::studies::{Indicator, IndicatorValue, IndicatorValues, StudyCandle};
25use egui::Color32;
26
27/// Bollinger Bars: full-width wick blocks under up or down bodies.
28#[derive(Clone)]
29pub struct BollingerBars {
30    candles: Vec<StudyCandle>,
31    values: IndicatorValues,
32    colors: [Color32; 3],
33    visible: bool,
34}
35
36impl BollingerBars {
37    /// Create the study with blue wicks and the chart's up and down colours.
38    pub fn new() -> Self {
39        Self {
40            candles: Vec::new(),
41            values: IndicatorValues::new(),
42            colors: [Color32::from_rgb(13, 52, 158), palette::UP, palette::DOWN],
43            visible: true,
44        }
45    }
46}
47
48/// Construct with the conventional colours.
49impl Default for BollingerBars {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Indicator for BollingerBars {
56    fn name(&self) -> &str {
57        "Bollinger Bars"
58    }
59
60    fn desc(&self) -> &str {
61        "Bollinger Bars - Full-width bodies with block wicks"
62    }
63
64    fn calculate(&mut self, data: &[Bar]) {
65        self.update(data, 0);
66    }
67
68    fn update(&mut self, data: &[Bar], from: usize) {
69        let from = from.min(self.values.len()).min(data.len());
70        self.candles.truncate(
71            self.candles
72                .partition_point(|candle| candle.bars.start < from),
73        );
74        self.values.truncate(from);
75        // Every bar has all three columns, so an all-up series still reports three lines.
76        self.values
77            .resize(data.len(), IndicatorValue::Multiple(vec![f64::NAN; 3]));
78        for (i, bar) in data.iter().enumerate().skip(from) {
79            if ![bar.open, bar.high, bar.low, bar.close]
80                .iter()
81                .all(|p| p.is_finite())
82            {
83                continue;
84            }
85            let plot = if bar.close >= bar.open { 1 } else { 2 };
86            self.values.set_value(plot, i, bar.close);
87            let (top, bottom) = (bar.open.max(bar.close), bar.open.min(bar.close));
88            self.candles.push(StudyCandle {
89                bars: i..i + 1,
90                bar: Bar {
91                    open: bar.high,
92                    close: bar.low,
93                    ..bar.clone()
94                },
95                plot: 0,
96            });
97            self.candles.push(StudyCandle {
98                bars: i..i + 1,
99                bar: Bar {
100                    high: top,
101                    low: bottom,
102                    ..bar.clone()
103                },
104                plot,
105            });
106        }
107    }
108
109    fn is_incremental(&self) -> bool {
110        true
111    }
112
113    fn values(&self) -> &IndicatorValues {
114        &self.values
115    }
116
117    fn candles(&self) -> &[StudyCandle] {
118        &self.candles
119    }
120
121    fn colors(&self) -> Vec<Color32> {
122        self.colors.to_vec()
123    }
124
125    fn set_colors(&mut self, colors: Vec<Color32>) {
126        for (old, new) in self.colors.iter_mut().zip(colors) {
127            *old = new;
128        }
129    }
130
131    fn is_visible(&self) -> bool {
132        self.visible
133    }
134
135    fn set_visible(&mut self, visible: bool) {
136        self.visible = visible;
137    }
138
139    fn clone_box(&self) -> Box<dyn Indicator> {
140        Box::new(self.clone())
141    }
142
143    fn line_names(&self) -> Vec<String> {
144        vec!["Wicks".into(), "Up".into(), "Down".into()]
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use chrono::{TimeZone, Utc};
152
153    /// Each bar paints its high-low block before its body, the body spans only open to close,
154    /// an unchanged close counts as up, and a bar with a non-finite price draws nothing.
155    #[test]
156    fn each_bar_paints_a_block_then_its_body() {
157        let bar = |i: i64, [o, h, l, c]: [f64; 4]| {
158            Bar::new(Utc.timestamp_opt(i, 0).unwrap(), o, h, l, c, 1.0)
159        };
160        let data = [
161            bar(0, [10.0, 12.0, 9.0, 11.0]),
162            bar(1, [11.0, 11.5, 8.0, 9.0]),
163            bar(2, [f64::NAN, 11.0, 9.0, 10.0]),
164            bar(3, [10.0, 10.5, 9.5, 10.0]),
165        ];
166        let mut study = BollingerBars::new();
167        study.calculate(&data);
168        let candles: Vec<_> = study
169            .candles()
170            .iter()
171            .map(|c| {
172                (
173                    c.bars.clone(),
174                    c.plot,
175                    [c.bar.open, c.bar.high, c.bar.low, c.bar.close],
176                )
177            })
178            .collect();
179        assert_eq!(
180            candles,
181            [
182                (0..1, 0, [12.0, 12.0, 9.0, 9.0]),
183                (0..1, 1, [10.0, 11.0, 10.0, 11.0]),
184                (1..2, 0, [11.5, 11.5, 8.0, 8.0]),
185                (1..2, 2, [11.0, 11.0, 9.0, 9.0]),
186                (3..4, 0, [10.5, 10.5, 9.5, 9.5]),
187                (3..4, 1, [10.0, 10.0, 10.0, 10.0]),
188            ]
189        );
190    }
191}