egui_charts/studies/builtin/
linear_regression.rs1use crate::model::Bar;
6use crate::studies::palette;
7use crate::studies::stepper::update_window;
8use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
9use egui::Color32;
10
11#[derive(Clone)]
13pub struct LinearRegression {
14 period: usize,
16 values: IndicatorValues,
17 color: Color32,
18 visible: bool,
19}
20
21impl LinearRegression {
22 pub fn new(period: usize) -> Self {
24 Self {
25 period: period.max(1),
26 values: IndicatorValues::new(),
27 color: palette::LIGHT_BLUE,
28 visible: true,
29 }
30 }
31
32 pub fn with_color(mut self, color: Color32) -> Self {
34 self.color = color;
35 self
36 }
37}
38
39impl Default for LinearRegression {
40 fn default() -> Self {
41 Self::new(14)
42 }
43}
44
45impl LinearRegression {
46 fn compute(&self, data: &[Bar]) -> IndicatorValues {
47 let mut values = IndicatorValues::new();
48
49 if data.len() < self.period {
50 for _ in 0..data.len() {
51 values.push(IndicatorValue::None);
52 }
53 return values;
54 }
55
56 for i in 0..data.len() {
57 if i < self.period - 1 {
58 values.push(IndicatorValue::None);
59 } else {
60 let window = &data[i + 1 - self.period..=i];
61 let n = self.period as f64;
62
63 let mut sum_x = 0.0;
64 let mut sum_y = 0.0;
65 let mut sum_xy = 0.0;
66 let mut sum_x2 = 0.0;
67
68 for (j, bar) in window.iter().enumerate() {
69 let x = j as f64;
70 let y = bar.close;
71 sum_x += x;
72 sum_y += y;
73 sum_xy += x * y;
74 sum_x2 += x * x;
75 }
76
77 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
78 let intercept = (sum_y - slope * sum_x) / n;
79
80 let value = intercept + slope * (self.period - 1) as f64;
82 values.push(IndicatorValue::Single(value));
83 }
84 }
85 values
86 }
87}
88
89impl Indicator for LinearRegression {
90 fn source(&self) -> Option<crate::studies::StudySource> {
91 Some(crate::studies::StudySource::Price(
92 crate::model::PriceSource::Close,
93 ))
94 }
95
96 crate::studies::inputs::study_inputs!(self;
97 period: length("Period", 1, usize::MAX)
98 ; {});
99
100 fn name(&self) -> &str {
101 "LinReg"
102 }
103
104 fn desc(&self) -> &str {
105 "Linear Regression Line - Best fit line value"
106 }
107
108 fn calculate(&mut self, data: &[Bar]) {
109 self.update(data, 0);
110 }
111
112 fn update(&mut self, data: &[Bar], from: usize) {
113 let mut values = std::mem::take(&mut self.values);
114 update_window(
115 &mut values,
116 data,
117 from,
118 |_data, from| from.saturating_sub(self.period - 1),
119 |data| self.compute(data),
120 );
121 self.values = values;
122 }
123
124 fn is_incremental(&self) -> bool {
125 true
126 }
127
128 fn values(&self) -> &IndicatorValues {
129 &self.values
130 }
131
132 fn colors(&self) -> Vec<Color32> {
133 vec![self.color]
134 }
135
136 fn set_colors(&mut self, colors: Vec<Color32>) {
137 if !colors.is_empty() {
138 self.color = colors[0];
139 }
140 }
141
142 fn is_overlay(&self) -> bool {
143 true }
145
146 fn is_visible(&self) -> bool {
147 self.visible
148 }
149
150 fn set_visible(&mut self, visible: bool) {
151 self.visible = visible;
152 }
153
154 fn clone_box(&self) -> Box<dyn Indicator> {
155 Box::new(self.clone())
156 }
157
158 fn line_names(&self) -> Vec<String> {
159 vec![format!("LinReg({})", self.period)]
160 }
161}
162
163#[derive(Clone)]
165pub struct LinearRegressionSlope {
166 period: usize,
167 values: IndicatorValues,
168 color: Color32,
169 visible: bool,
170}
171
172impl LinearRegressionSlope {
173 pub fn new(period: usize) -> Self {
175 Self {
176 period: period.max(1),
177 values: IndicatorValues::new(),
178 color: Color32::from_rgb(255, 82, 82),
179 visible: true,
180 }
181 }
182
183 pub fn with_color(mut self, color: Color32) -> Self {
185 self.color = color;
186 self
187 }
188}
189
190impl Default for LinearRegressionSlope {
191 fn default() -> Self {
192 Self::new(14)
193 }
194}
195
196impl LinearRegressionSlope {
197 fn compute(&self, data: &[Bar]) -> IndicatorValues {
198 let mut values = IndicatorValues::new();
199
200 if data.len() < self.period {
201 for _ in 0..data.len() {
202 values.push(IndicatorValue::None);
203 }
204 return values;
205 }
206
207 for i in 0..data.len() {
208 if i < self.period - 1 {
209 values.push(IndicatorValue::None);
210 } else {
211 let window = &data[i + 1 - self.period..=i];
212 let n = self.period as f64;
213
214 let mut sum_x = 0.0;
215 let mut sum_y = 0.0;
216 let mut sum_xy = 0.0;
217 let mut sum_x2 = 0.0;
218
219 for (j, bar) in window.iter().enumerate() {
220 let x = j as f64;
221 let y = bar.close;
222 sum_x += x;
223 sum_y += y;
224 sum_xy += x * y;
225 sum_x2 += x * x;
226 }
227
228 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
229 values.push(IndicatorValue::Single(slope));
230 }
231 }
232 values
233 }
234}
235
236impl Indicator for LinearRegressionSlope {
237 fn source(&self) -> Option<crate::studies::StudySource> {
238 Some(crate::studies::StudySource::Price(
239 crate::model::PriceSource::Close,
240 ))
241 }
242
243 crate::studies::inputs::study_inputs!(self;
244 period: length("Period", 1, usize::MAX)
245 ; {});
246
247 fn name(&self) -> &str {
248 "LinReg Slope"
249 }
250
251 fn desc(&self) -> &str {
252 "Linear Regression Slope - Rate of change"
253 }
254
255 fn calculate(&mut self, data: &[Bar]) {
256 self.update(data, 0);
257 }
258
259 fn update(&mut self, data: &[Bar], from: usize) {
260 let mut values = std::mem::take(&mut self.values);
261 update_window(
262 &mut values,
263 data,
264 from,
265 |_data, from| from.saturating_sub(self.period - 1),
266 |data| self.compute(data),
267 );
268 self.values = values;
269 }
270
271 fn is_incremental(&self) -> bool {
272 true
273 }
274
275 fn values(&self) -> &IndicatorValues {
276 &self.values
277 }
278
279 fn colors(&self) -> Vec<Color32> {
280 vec![self.color]
281 }
282
283 fn set_colors(&mut self, colors: Vec<Color32>) {
284 if !colors.is_empty() {
285 self.color = colors[0];
286 }
287 }
288
289 fn is_overlay(&self) -> bool {
290 false
291 }
292
293 fn is_visible(&self) -> bool {
294 self.visible
295 }
296
297 fn set_visible(&mut self, visible: bool) {
298 self.visible = visible;
299 }
300
301 fn clone_box(&self) -> Box<dyn Indicator> {
302 Box::new(self.clone())
303 }
304
305 fn line_names(&self) -> Vec<String> {
306 vec![format!("Slope({})", self.period)]
307 }
308}