Skip to main content

egui_charts/studies/builtin/
standard_error.rs

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