Skip to main content

egui_charts/studies/builtin/
bollinger_bands.rs

1//! Bollinger Bands indicator.
2//!
3//! Bollinger Bands are a volatility envelope placed above and below a
4//! moving average, a simple one by default. The width of the bands is
5//! determined by the standard deviation of closing prices, so they
6//! automatically widen during volatile markets and contract during quiet
7//! markets.
8//!
9//! # Components
10//!
11//! 1. **Upper band** = basis + (K * standard deviation)
12//! 2. **Middle band** = basis = MA(close, period)
13//! 3. **Lower band** = basis - (K * standard deviation)
14//!
15//! where *K* is the `std_dev` multiplier (typically 2.0) and the standard
16//! deviation is the population deviation of the last `period` closes about
17//! their simple mean, whatever the basis.
18//!
19//! The basis is one of the [`AverageType`] averages, simple by default:
20//! exponential, weighted, smoothed or volume weighted are the others. It
21//! keeps the seed and warm-up of the standalone average, but all three bands
22//! start once a full window of `period` bars exists. A window with a missing
23//! close leaves all three bands missing at that bar.
24//!
25//! # Default parameters
26//!
27//! `BollingerBands::new(20, 2.0)` -- 20-period SMA with 2 standard deviations.
28//!
29//! # Interpretation
30//!
31//! - Price touching the upper band: potentially overbought.
32//! - Price touching the lower band: potentially oversold.
33//! - Band squeeze (narrow bands): low volatility, often precedes a breakout.
34//! - Walking the band: strong trend when price hugs one band.
35//!
36//! # Default colours
37//!
38//! The basis is blue, the upper boundary red and the lower boundary teal.
39//!
40//! # Example
41//!
42//! ```rust
43//! # 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();
44//! use egui_charts::studies::{BollingerBands, Indicator, IndicatorValue};
45//!
46//! let mut bb = BollingerBands::new(20, 2.0);
47//! bb.calculate(&bars);
48//!
49//! if let IndicatorValue::Multiple(vals) = &bb.values().to_rows()[30] {
50//!     let (upper, middle, lower) = (vals[0], vals[1], vals[2]);
51//! }
52//! ```
53
54use super::{AverageType, clamp_param};
55use crate::model::Bar;
56use crate::studies::palette;
57use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
58use egui::Color32;
59
60/// Bollinger Bands indicator.
61///
62/// A three-line volatility overlay: upper band, middle SMA, and lower band.
63/// The bands expand and contract with market volatility.
64#[derive(Clone)]
65pub struct BollingerBands {
66    period: usize,
67    basis: AverageType,
68    std_dev: f64,
69    /// The basis average, built when a calculation starts; `None` for the simple mean.
70    average: Option<Box<dyn Indicator>>,
71    values: IndicatorValues,
72    colors: Vec<Color32>,
73    visible: bool,
74}
75
76impl BollingerBands {
77    /// `std_dev` is clamped into 0 to `f64::MAX`, NaN to 0.
78    pub fn new(period: usize, std_dev: f64) -> Self {
79        Self {
80            period: period.max(1),
81            basis: AverageType::Simple,
82            std_dev: clamp_param(std_dev, 0.0, f64::MAX),
83            average: None,
84            values: IndicatorValues::new(),
85            colors: vec![
86                palette::RED,       // Upper
87                palette::BB_MIDDLE, // Middle
88                palette::TEAL,      // Lower
89                palette::LIGHT_BLUE.gamma_multiply_u8(13),
90            ],
91            visible: true,
92        }
93    }
94
95    /// Use `basis` for the middle band. [`AverageType::Hull`] is not a Bollinger basis and
96    /// leaves the basis unchanged.
97    pub fn with_basis(mut self, basis: AverageType) -> Self {
98        if basis != AverageType::Hull {
99            self.basis = basis;
100        }
101        self
102    }
103
104    /// The bands at bar `i` of `data`.
105    fn row(&self, data: &[Bar], i: usize) -> IndicatorValue {
106        if i + 1 < self.period {
107            return IndicatorValue::None;
108        }
109        let prices: Vec<f64> = data[i + 1 - self.period..=i]
110            .iter()
111            .map(|bar| bar.close)
112            .collect();
113
114        let sma = prices.iter().sum::<f64>() / self.period as f64;
115        let variance = prices.iter().map(|p| (p - sma).powi(2)).sum::<f64>() / self.period as f64;
116        let std = variance.sqrt();
117        let middle = self.average.as_ref().map_or(sma, |average| {
118            average.values().value(0, i).unwrap_or(f64::NAN)
119        });
120        if !std.is_finite() {
121            return IndicatorValue::None;
122        }
123
124        IndicatorValue::Multiple(vec![
125            middle + (std * self.std_dev),
126            middle,
127            middle - (std * self.std_dev),
128        ])
129    }
130}
131
132/// Construct with the conventional default parameters.
133impl Default for BollingerBands {
134    fn default() -> Self {
135        Self::new(20, 2.0)
136    }
137}
138
139impl Indicator for BollingerBands {
140    fn source(&self) -> Option<crate::studies::StudySource> {
141        Some(crate::studies::StudySource::Price(
142            crate::model::PriceSource::Close,
143        ))
144    }
145
146    crate::studies::inputs::study_inputs!(self;
147        period: length("Period", 1, usize::MAX),
148        basis: choice("Basis MA type", [AverageType::Simple, AverageType::Exponential, AverageType::Weighted, AverageType::Smoothed, AverageType::VolumeWeighted]),
149        std_dev: number("Std dev", 0.0, f64::MAX)
150    ; {});
151
152    fn fills(&self) -> Vec<crate::studies::StudyFill> {
153        vec![crate::studies::StudyFill {
154            first: crate::studies::FillBoundary::Plot(0),
155            second: crate::studies::FillBoundary::Plot(2),
156            color: self.colors[3],
157            inverted_color: None,
158        }]
159    }
160
161    fn name(&self) -> &str {
162        "BB"
163    }
164
165    fn desc(&self) -> &str {
166        "Bollinger Bands - Volatility indicator with upper and lower bands"
167    }
168
169    fn calculate(&mut self, data: &[Bar]) {
170        self.update(data, 0);
171    }
172
173    fn update(&mut self, data: &[Bar], from: usize) {
174        let from = from.min(self.values.len()).min(data.len());
175        if from == 0 {
176            self.average =
177                (self.basis != AverageType::Simple).then(|| self.basis.build(self.period));
178        }
179        if let Some(average) = &mut self.average {
180            average.update(data, from);
181        }
182        self.values.truncate(from);
183        for i in from..data.len() {
184            let row = self.row(data, i);
185            self.values.push(row);
186        }
187    }
188
189    fn is_incremental(&self) -> bool {
190        true
191    }
192
193    fn values(&self) -> &IndicatorValues {
194        &self.values
195    }
196
197    fn colors(&self) -> Vec<Color32> {
198        self.colors.clone()
199    }
200
201    fn set_colors(&mut self, colors: Vec<Color32>) {
202        if colors.len() >= 3 {
203            self.colors = colors;
204        } else if !colors.is_empty() {
205            // If less than 3 colors provided, use the first color for all bands
206            self.colors = vec![colors[0], colors[0], colors[0]];
207        }
208        if self.colors.len() == 3 {
209            let color = self.colors[0];
210            self.colors.push(Color32::from_rgba_unmultiplied(
211                color.r(),
212                color.g(),
213                color.b(),
214                16,
215            ));
216        }
217    }
218
219    fn is_overlay(&self) -> bool {
220        true
221    }
222
223    fn line_count(&self) -> usize {
224        3
225    }
226
227    fn line_names(&self) -> Vec<String> {
228        vec![
229            format!("BB Upper({}, {})", self.period, self.std_dev),
230            format!("BB Middle({}, {})", self.period, self.std_dev),
231            format!("BB Lower({}, {})", self.period, self.std_dev),
232        ]
233    }
234
235    fn is_visible(&self) -> bool {
236        self.visible
237    }
238
239    fn set_visible(&mut self, visible: bool) {
240        self.visible = visible;
241    }
242
243    fn clone_box(&self) -> Box<dyn Indicator> {
244        Box::new(self.clone())
245    }
246}