egui_charts/studies/builtin/
week52_highlow.rs1use crate::model::Bar;
2use crate::studies::palette;
3use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
4use chrono::Duration;
5use egui::Color32;
6use std::collections::VecDeque;
7
8#[derive(Clone)]
13pub struct Week52HighLow {
14 values: IndicatorValues,
15 state: State,
16 colors: Vec<Color32>,
17 visible: bool,
18}
19
20impl Week52HighLow {
21 pub fn new() -> Self {
23 Self {
24 values: IndicatorValues::new(),
25 state: State::default(),
26 colors: vec![palette::PINK],
27 visible: true,
28 }
29 }
30}
31
32impl Default for Week52HighLow {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl Indicator for Week52HighLow {
39 fn value_format(&self) -> crate::scales::StudyFormat {
40 crate::scales::StudyFormat::Percent
41 }
42
43 fn name(&self) -> &str {
44 "52W HL%"
45 }
46
47 fn desc(&self) -> &str {
48 "52-Week High/Low Percentage - Close position within the last 364 days"
49 }
50
51 fn calculate(&mut self, data: &[Bar]) {
52 self.update(data, 0);
53 }
54
55 fn update(&mut self, data: &[Bar], from: usize) {
56 self.state.run(
57 data,
58 from,
59 &mut self.values,
60 |bar| [bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()),
61 |bar, high, low| {
62 IndicatorValue::Single(if high == low {
63 50.0
64 } else {
65 (bar.close - low) / (high - low) * 100.0
66 })
67 },
68 );
69 }
70
71 fn is_incremental(&self) -> bool {
72 true
73 }
74
75 fn values(&self) -> &IndicatorValues {
76 &self.values
77 }
78
79 fn colors(&self) -> Vec<Color32> {
80 self.colors.clone()
81 }
82
83 fn set_colors(&mut self, colors: Vec<Color32>) {
84 if !colors.is_empty() {
85 self.colors = colors;
86 }
87 }
88
89 fn is_overlay(&self) -> bool {
90 false
91 }
92
93 fn is_visible(&self) -> bool {
94 self.visible
95 }
96
97 fn set_visible(&mut self, visible: bool) {
98 self.visible = visible;
99 }
100
101 fn clone_box(&self) -> Box<dyn Indicator> {
102 Box::new(self.clone())
103 }
104
105 fn line_names(&self) -> Vec<String> {
106 vec!["52W HL%".to_string()]
107 }
108}
109
110#[derive(Clone)]
113pub struct Week52HighLowLevels {
114 values: IndicatorValues,
115 state: State,
116 colors: Vec<Color32>,
117 visible: bool,
118}
119
120impl Week52HighLowLevels {
121 pub fn new() -> Self {
123 Self {
124 values: IndicatorValues::new(),
125 state: State::default(),
126 colors: vec![palette::BLUE, palette::PINK],
127 visible: true,
128 }
129 }
130}
131
132impl Default for Week52HighLowLevels {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl Indicator for Week52HighLowLevels {
139 fn name(&self) -> &str {
140 "52W High/Low"
141 }
142
143 fn desc(&self) -> &str {
144 "52-Week High/Low - Highest high and lowest low within the last 364 days"
145 }
146
147 fn calculate(&mut self, data: &[Bar]) {
148 self.update(data, 0);
149 }
150 fn update(&mut self, data: &[Bar], from: usize) {
151 self.state.run(
152 data,
153 from,
154 &mut self.values,
155 |bar| bar.high.is_finite() && bar.low.is_finite(),
156 |_, high, low| IndicatorValue::Multiple(vec![high, low]),
157 );
158 }
159 fn is_incremental(&self) -> bool {
160 true
161 }
162
163 fn values(&self) -> &IndicatorValues {
164 &self.values
165 }
166
167 fn colors(&self) -> Vec<Color32> {
168 self.colors.clone()
169 }
170
171 fn set_colors(&mut self, colors: Vec<Color32>) {
172 for (kept, color) in self.colors.iter_mut().zip(colors) {
173 *kept = color;
174 }
175 }
176
177 fn is_overlay(&self) -> bool {
178 true
179 }
180
181 fn is_visible(&self) -> bool {
182 self.visible
183 }
184
185 fn set_visible(&mut self, visible: bool) {
186 self.visible = visible;
187 }
188
189 fn clone_box(&self) -> Box<dyn Indicator> {
190 Box::new(self.clone())
191 }
192
193 fn line_names(&self) -> Vec<String> {
194 vec!["52W High".to_string(), "52W Low".to_string()]
195 }
196}
197
198#[derive(Clone, Default)]
199struct State {
200 processed: usize,
201 first: Option<chrono::DateTime<chrono::Utc>>,
202 extremes: [VecDeque<usize>; 2],
203}
204
205impl State {
206 fn run(
207 &mut self,
208 data: &[Bar],
209 from: usize,
210 values: &mut IndicatorValues,
211 usable: impl Fn(&Bar) -> bool,
212 output: impl Fn(&Bar, f64, f64) -> IndicatorValue,
213 ) {
214 let len = values.len();
215 let from = if len > 0 && from >= len - 1 && data.len() >= len {
216 from.min(len)
217 } else {
218 *self = Self::default();
219 0
220 };
221 values.truncate(from);
222 for at in self.processed..data.len() {
224 let bar = &data[at];
225 let last = at + 1 == data.len();
226 let value = if usable(bar) {
227 let first = self.first.unwrap_or(bar.time);
228 let mut range = [bar.high, bar.low];
229 for (line, queue) in self.extremes.iter_mut().enumerate() {
230 let expired = |i: usize| bar.time - data[i].time > Duration::weeks(52);
231 let price = |i: usize| if line == 0 { data[i].high } else { data[i].low };
232 let better = |a: f64, b: f64| if line == 0 { a > b } else { a < b };
233 if !last {
234 while queue.front().is_some_and(|&i| expired(i)) {
235 queue.pop_front();
236 }
237 while queue.back().is_some_and(|&i| !better(price(i), price(at))) {
238 queue.pop_back();
239 }
240 queue.push_back(at);
241 }
242 let start = queue.partition_point(|&i| expired(i));
244 if let Some(&i) = queue.get(start)
245 && better(price(i), range[line])
246 {
247 range[line] = price(i);
248 }
249 }
250 if !last {
251 self.first = Some(first);
252 }
253 if bar.time - first >= Duration::weeks(52) {
254 output(bar, range[0], range[1])
255 } else {
256 IndicatorValue::None
257 }
258 } else {
259 IndicatorValue::None
260 };
261 if at >= from {
262 values.push(value);
263 }
264 if !last {
265 self.processed = at + 1;
266 }
267 }
268 }
269}