egui_charts/studies/builtin/
bollinger_bars.rs1use crate::model::Bar;
23use crate::studies::palette;
24use crate::studies::{Indicator, IndicatorValue, IndicatorValues, StudyCandle};
25use egui::Color32;
26
27#[derive(Clone)]
29pub struct BollingerBars {
30 candles: Vec<StudyCandle>,
31 values: IndicatorValues,
32 colors: [Color32; 3],
33 visible: bool,
34}
35
36impl BollingerBars {
37 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
48impl 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 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 #[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}