1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Bollinger Bands indicator.
//!
//! Bollinger Bands are a volatility envelope placed above and below a
//! simple moving average. The width of the bands is determined by the
//! standard deviation of closing prices, so they automatically widen
//! during volatile markets and contract during quiet markets.
//!
//! # Components
//!
//! 1. **Upper band** = SMA + (K * standard deviation)
//! 2. **Middle band** = SMA(close, period)
//! 3. **Lower band** = SMA - (K * standard deviation)
//!
//! where *K* is the `std_dev` multiplier (typically 2.0).
//!
//! # Default parameters
//!
//! `BollingerBands::new(20, 2.0)` -- 20-period SMA with 2 standard deviations.
//!
//! # Interpretation
//!
//! - Price touching the upper band: potentially overbought.
//! - Price touching the lower band: potentially oversold.
//! - Band squeeze (narrow bands): low volatility, often precedes a breakout.
//! - Walking the band: strong trend when price hugs one band.
//!
//! # Default colours
//!
//! Upper/middle/lower sourced from `indicators.bb_upper`, `bb_middle`, `bb_lower`.
//!
//! # Example
//!
//! ```rust,ignore
//! use egui_charts::studies::{BollingerBands, Indicator, IndicatorValue};
//!
//! let mut bb = BollingerBands::new(20, 2.0);
//! bb.calculate(&bars);
//!
//! if let IndicatorValue::Multiple(vals) = &bb.values()[30] {
//! let (upper, middle, lower) = (vals[0], vals[1], vals[2]);
//! }
//! ```
use crate::model::Bar;
use crate::studies::{Indicator, IndicatorValue};
use crate::tokens::DESIGN_TOKENS;
use egui::Color32;
/// Bollinger Bands indicator.
///
/// A three-line volatility overlay: upper band, middle SMA, and lower band.
/// The bands expand and contract with market volatility.
#[derive(Clone)]
pub struct BollingerBands {
period: usize,
std_dev: f64,
values: Vec<IndicatorValue>,
colors: Vec<Color32>,
visible: bool,
}
impl BollingerBands {
pub fn new(period: usize, std_dev: f64) -> Self {
Self {
period,
std_dev,
values: Vec::new(),
colors: vec![
DESIGN_TOKENS.semantic.indicators.bb_upper, // Upper
DESIGN_TOKENS.semantic.indicators.bb_middle, // Middle
DESIGN_TOKENS.semantic.indicators.bb_lower, // Lower
],
visible: true,
}
}
}
/// Construct with the conventional default parameters.
impl Default for BollingerBands {
fn default() -> Self {
Self::new(20, 2.0)
}
}
impl Indicator for BollingerBands {
fn name(&self) -> &str {
"BB"
}
fn desc(&self) -> &str {
"Bollinger Bands - Volatility indicator with upper and lower bands"
}
fn calculate(&mut self, data: &[Bar]) {
self.values.clear();
if data.len() < self.period {
return;
}
for i in 0..data.len() {
if i < self.period - 1 {
self.values.push(IndicatorValue::None);
} else {
let prices: Vec<f64> = data[i + 1 - self.period..=i]
.iter()
.map(|bar| bar.close)
.collect();
let sma = prices.iter().sum::<f64>() / self.period as f64;
let variance =
prices.iter().map(|p| (p - sma).powi(2)).sum::<f64>() / self.period as f64;
let std = variance.sqrt();
let upper = sma + (std * self.std_dev);
let lower = sma - (std * self.std_dev);
self.values
.push(IndicatorValue::Multiple(vec![upper, sma, lower]));
}
}
}
fn values(&self) -> &[IndicatorValue] {
&self.values
}
fn colors(&self) -> Vec<Color32> {
self.colors.clone()
}
fn set_colors(&mut self, colors: Vec<Color32>) {
if colors.len() >= 3 {
self.colors = colors;
} else if !colors.is_empty() {
// If less than 3 colors provided, use the first color for all bands
self.colors = vec![colors[0], colors[0], colors[0]];
}
}
fn is_overlay(&self) -> bool {
true
}
fn line_cnt(&self) -> usize {
3
}
fn line_names(&self) -> Vec<String> {
vec![
format!("BB Upper({}, {})", self.period, self.std_dev),
format!("BB Middle({}, {})", self.period, self.std_dev),
format!("BB Lower({}, {})", self.period, self.std_dev),
]
}
fn is_visible(&self) -> bool {
self.visible
}
fn set_visible(&mut self, visible: bool) {
self.visible = visible;
}
fn clone_box(&self) -> Box<dyn Indicator> {
Box::new(self.clone())
}
}