egui_charts/studies/builtin/
vwap_auto_anchored.rs1use super::anchor_period::{AnchorPeriod, ExchangeDays};
29use crate::model::{Bar, Instrument, PriceSource, Timeframe};
30use crate::studies::stepper::Stepper;
31use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
32use egui::Color32;
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum AutoAnchor {
38 #[default]
41 Auto,
42 HighestHigh,
44 LowestLow,
46 HighestVolume,
48 Session,
50 Week,
52 Month,
54 Quarter,
56 Year,
58 Decade,
60 Century,
62}
63
64#[derive(Clone)]
72pub struct VwapAutoAnchored {
73 anchor: AutoAnchor,
74 length: usize,
75 source: PriceSource,
76 interval: Timeframe,
77 days: ExchangeDays,
78 values: IndicatorValues,
79 stepper: Stepper<State>,
80 intraday: bool,
81 shown_start: usize,
82 shown_indices: Vec<usize>,
83 colors: Vec<Color32>,
84 visible: bool,
85}
86
87#[derive(Clone, Copy)]
88struct Point {
89 source: usize,
90 high: f64,
91 low: f64,
92 volume: f64,
93 price: f64,
94}
95
96#[derive(Clone, Default)]
97struct State {
98 source: usize,
99 previous_date: Option<chrono::NaiveDate>,
100 intraday: bool,
101 period: Option<(i32, u32)>,
102 start: usize,
103 volume: f64,
104 weighted: f64,
105 candidates: std::collections::VecDeque<Point>,
106}
107
108impl VwapAutoAnchored {
109 pub fn new() -> Self {
111 Self {
112 anchor: AutoAnchor::Auto,
113 length: 20,
114 source: PriceSource::HLC3,
115 interval: Timeframe::Min1,
116 days: ExchangeDays::default(),
117 values: IndicatorValues::new(),
118 stepper: Stepper::default(),
119 intraday: false,
120 shown_start: 0,
121 shown_indices: Vec::new(),
122 colors: vec![Color32::from_rgb(30, 136, 229)],
123 visible: true,
124 }
125 }
126
127 pub fn with_anchor(mut self, anchor: AutoAnchor) -> Self {
129 self.anchor = anchor;
130 self
131 }
132
133 pub fn with_length(mut self, length: usize) -> Self {
136 self.length = length.max(1);
137 self
138 }
139
140 pub fn with_source(mut self, source: PriceSource) -> Self {
142 self.source = source;
143 self
144 }
145
146 fn period(&self) -> Option<AnchorPeriod> {
147 let day = Timeframe::Day1.duration_ms();
148 Some(match self.anchor {
149 AutoAnchor::Auto => match self.interval.duration_ms() {
150 ms if ms < day => AnchorPeriod::Session,
151 ms if ms <= day => AnchorPeriod::Month,
152 ms if ms <= 10 * day => AnchorPeriod::Quarter,
153 ms if ms <= 60 * day => AnchorPeriod::Year,
154 _ => AnchorPeriod::Decade,
155 },
156 AutoAnchor::Session => AnchorPeriod::Session,
157 AutoAnchor::Week => AnchorPeriod::Week,
158 AutoAnchor::Month => AnchorPeriod::Month,
159 AutoAnchor::Quarter => AnchorPeriod::Quarter,
160 AutoAnchor::Year => AnchorPeriod::Year,
161 AutoAnchor::Decade => AnchorPeriod::Decade,
162 AutoAnchor::Century => AnchorPeriod::Century,
163 _ => return None,
164 })
165 }
166}
167
168impl Default for VwapAutoAnchored {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl Indicator for VwapAutoAnchored {
175 crate::studies::inputs::study_inputs!(self;
176 anchor: choice("Anchor Period", [AutoAnchor::Auto, AutoAnchor::HighestHigh, AutoAnchor::LowestLow, AutoAnchor::HighestVolume, AutoAnchor::Session, AutoAnchor::Week, AutoAnchor::Month, AutoAnchor::Quarter, AutoAnchor::Year, AutoAnchor::Decade, AutoAnchor::Century]),
177 length: length("Length", 1, usize::MAX),
178 source: choice("Source", PriceSource::all())
179 ; {});
180
181 fn name(&self) -> &str {
182 "VWAP Auto Anchored"
183 }
184
185 fn desc(&self) -> &str {
186 "VWAP Auto Anchored - Volume weighted average price from an automatically found anchor"
187 }
188
189 fn calculate(&mut self, data: &[Bar]) {
190 self.update(data, 0);
191 }
192
193 fn update(&mut self, data: &[Bar], mut from: usize) {
194 let period = self.period();
195 let price_source = self.source;
196 let usable = |bar: &Bar| {
197 price_source
198 .compute(bar.open, bar.high, bar.low, bar.close)
199 .is_finite()
200 && bar.high.is_finite()
201 && bar.low.is_finite()
202 && bar.volume.is_finite()
203 };
204 let len = self.values.len();
205 if len == 0 || from < len - 1 || data.len() < len {
206 self.shown_start = 0;
207 }
208 loop {
209 self.stepper
210 .run(data, from, &mut self.values, |state, bar| {
211 let source = state.source;
212 state.source += 1;
213 if !usable(bar) {
214 return IndicatorValue::None;
215 }
216 let price = price_source.compute(bar.open, bar.high, bar.low, bar.close);
217 let Some(period) = period else {
218 state.candidates.push_back(Point {
219 source,
220 high: bar.high,
221 low: bar.low,
222 volume: bar.volume,
223 price,
224 });
225 if state.candidates.len() > self.length {
226 state.candidates.pop_front();
227 }
228 return IndicatorValue::None;
229 };
230 let date = bar.time.date_naive();
231 state.intraday |= state.previous_date == Some(date);
232 state.previous_date = Some(date);
233 let key = Some(period.key(self.days.day(bar, !self.intraday)));
234 if key != state.period {
235 state.period = key;
236 state.start = source;
237 state.volume = 0.0;
238 state.weighted = 0.0;
239 }
240 state.volume += bar.volume;
241 state.weighted += price * bar.volume;
242 IndicatorValue::Single(if state.volume == 0.0 {
243 price
244 } else {
245 state.weighted / state.volume
246 })
247 });
248 if period.is_none() || self.intraday == self.stepper.state().intraday {
249 break;
250 }
251 self.intraday = self.stepper.state().intraday;
252 self.shown_start = 0;
253 from = 0;
254 }
255 if period.is_some() {
256 let start = self.stepper.state().start;
257 if start > self.shown_start {
258 for bar in self.shown_start..start {
259 self.values.set(bar, IndicatorValue::None);
260 }
261 } else if start < self.shown_start {
262 let (mut volume, mut weighted) = (0.0, 0.0);
264 for (i, bar) in data.iter().enumerate().take(self.shown_start).skip(start) {
265 if !usable(bar) {
266 continue;
267 }
268 let price = price_source.compute(bar.open, bar.high, bar.low, bar.close);
269 volume += bar.volume;
270 weighted += price * bar.volume;
271 self.values.set_value(
272 0,
273 i,
274 if volume == 0.0 {
275 price
276 } else {
277 weighted / volume
278 },
279 );
280 }
281 }
282 self.shown_start = start;
283 } else {
284 for &bar in &self.shown_indices {
285 if bar < self.values.len() {
286 self.values.set(bar, IndicatorValue::None);
287 }
288 }
289 self.shown_indices.clear();
290 let candidates = &self.stepper.state().candidates;
291 let key = |point: &Point| match self.anchor {
292 AutoAnchor::HighestHigh => point.high,
293 AutoAnchor::LowestLow => -point.low,
294 _ => point.volume,
295 };
296 let start = (0..candidates.len())
297 .max_by(|&a, &b| key(&candidates[a]).total_cmp(&key(&candidates[b])))
298 .unwrap_or(candidates.len());
299 let (mut volume, mut weighted) = (0.0, 0.0);
300 for point in candidates.iter().skip(start) {
301 volume += point.volume;
302 weighted += point.price * point.volume;
303 self.values.set_value(
304 0,
305 point.source,
306 if volume == 0.0 {
307 point.price
308 } else {
309 weighted / volume
310 },
311 );
312 self.shown_indices.push(point.source);
313 }
314 }
315 }
316
317 fn is_incremental(&self) -> bool {
318 true
319 }
320
321 fn set_timeframe(&mut self, timeframe: Timeframe) {
322 self.interval = timeframe;
323 }
324
325 fn set_instrument(&mut self, instrument: Option<&Instrument>) -> bool {
326 self.days.set(instrument)
327 }
328
329 fn values(&self) -> &IndicatorValues {
330 &self.values
331 }
332
333 fn colors(&self) -> Vec<Color32> {
334 self.colors.clone()
335 }
336
337 fn set_colors(&mut self, colors: Vec<Color32>) {
338 if let Some(color) = colors.first() {
339 self.colors = vec![*color];
340 }
341 }
342
343 fn is_overlay(&self) -> bool {
344 true
345 }
346
347 fn is_visible(&self) -> bool {
348 self.visible
349 }
350
351 fn set_visible(&mut self, visible: bool) {
352 self.visible = visible;
353 }
354
355 fn clone_box(&self) -> Box<dyn Indicator> {
356 Box::new(self.clone())
357 }
358
359 fn line_names(&self) -> Vec<String> {
360 vec!["VWAP".to_string()]
361 }
362}