egui_charts/studies/builtin/
standard_error_bands.rs1use super::clamp_param;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::update_window;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8#[derive(Clone)]
11pub struct StandardErrorBands {
12 period: usize,
13 multiplier: f64,
14 values: IndicatorValues,
15 colors: Vec<Color32>,
16 visible: bool,
17}
18
19impl StandardErrorBands {
20 pub fn new(period: usize, multiplier: f64) -> Self {
22 Self {
23 period: period.max(2),
24 multiplier: clamp_param(multiplier, 0.0, f64::MAX),
25 values: IndicatorValues::new(),
26 colors: vec![palette::LIGHT_BLUE, palette::ORANGE, palette::LIGHT_BLUE],
27 visible: true,
28 }
29 }
30
31 fn linreg_with_se(prices: &[f64]) -> (f64, f64, f64) {
33 let n = prices.len() as f64;
34 let mut sum_x = 0.0;
35 let mut sum_y = 0.0;
36 let mut sum_xy = 0.0;
37 let mut sum_x2 = 0.0;
38
39 for (j, &y) in prices.iter().enumerate() {
40 let x = j as f64;
41 sum_x += x;
42 sum_y += y;
43 sum_xy += x * y;
44 sum_x2 += x * x;
45 }
46
47 let denom = n * sum_x2 - sum_x * sum_x;
48 if denom.abs() < 1e-15 {
49 let mean = sum_y / n;
50 return (0.0, mean, 0.0);
51 }
52
53 let slope = (n * sum_xy - sum_x * sum_y) / denom;
54 let intercept = (sum_y - slope * sum_x) / n;
55
56 let sum_sq_err: f64 = prices
57 .iter()
58 .enumerate()
59 .map(|(j, &y)| {
60 let predicted = intercept + slope * j as f64;
61 (y - predicted).powi(2)
62 })
63 .sum();
64
65 let se = if n > 2.0 {
66 (sum_sq_err / (n - 2.0)).sqrt()
67 } else {
68 0.0
69 };
70
71 (slope, intercept, se)
72 }
73}
74
75impl Default for StandardErrorBands {
76 fn default() -> Self {
77 Self::new(20, 2.0)
78 }
79}
80
81impl StandardErrorBands {
82 fn compute(&self, data: &[Bar]) -> IndicatorValues {
83 let mut values = IndicatorValues::new();
84
85 if data.is_empty() {
86 return values;
87 }
88
89 for i in 0..data.len() {
90 if i + 1 < self.period {
91 values.push(IndicatorValue::None);
92 continue;
93 }
94
95 let start = i + 1 - self.period;
96 let prices: Vec<f64> = data[start..=i].iter().map(|b| b.close).collect();
97 let (slope, intercept, se) = Self::linreg_with_se(&prices);
98
99 let middle = intercept + slope * (self.period - 1) as f64;
101 let upper = middle + self.multiplier * se;
102 let lower = middle - self.multiplier * se;
103
104 values.push_lines(&[upper, middle, lower]);
105 }
106 values
107 }
108}
109
110impl Indicator for StandardErrorBands {
111 fn source(&self) -> Option<crate::studies::StudySource> {
112 Some(crate::studies::StudySource::Price(
113 crate::model::PriceSource::Close,
114 ))
115 }
116
117 crate::studies::inputs::study_inputs!(self;
118 period: length("Period", 2, usize::MAX),
119 multiplier: number("Multiplier", 0.0, f64::MAX)
120 ; {});
121
122 fn name(&self) -> &str {
123 "StdErrBands"
124 }
125
126 fn desc(&self) -> &str {
127 "Standard Error Bands - Linear regression with standard error envelope"
128 }
129
130 fn calculate(&mut self, data: &[Bar]) {
131 self.update(data, 0);
132 }
133
134 fn update(&mut self, data: &[Bar], from: usize) {
135 let mut values = std::mem::take(&mut self.values);
136 update_window(
137 &mut values,
138 data,
139 from,
140 |_data, from| from.saturating_sub(self.period - 1),
141 |data| self.compute(data),
142 );
143 self.values = values;
144 }
145
146 fn is_incremental(&self) -> bool {
147 true
148 }
149
150 fn values(&self) -> &IndicatorValues {
151 &self.values
152 }
153
154 fn colors(&self) -> Vec<Color32> {
155 self.colors.clone()
156 }
157
158 fn set_colors(&mut self, colors: Vec<Color32>) {
159 if colors.len() >= 3 {
160 self.colors = colors;
161 } else if !colors.is_empty() {
162 self.colors = vec![colors[0], colors[0], colors[0]];
163 }
164 }
165
166 fn is_overlay(&self) -> bool {
167 true
168 }
169
170 fn line_count(&self) -> usize {
171 3
172 }
173
174 fn is_visible(&self) -> bool {
175 self.visible
176 }
177
178 fn set_visible(&mut self, visible: bool) {
179 self.visible = visible;
180 }
181
182 fn clone_box(&self) -> Box<dyn Indicator> {
183 Box::new(self.clone())
184 }
185
186 fn line_names(&self) -> Vec<String> {
187 vec![
188 format!("SE Upper({}, {})", self.period, self.multiplier),
189 format!("SE Middle({})", self.period),
190 format!("SE Lower({}, {})", self.period, self.multiplier),
191 ]
192 }
193}