Skip to main content

egui_charts/studies/builtin/
twap.rs

1//! Time Weighted Average Price (TWAP).
2//!
3//! The arithmetic mean of a price source, (open + high + low + close) / 4 by default, over the
4//! bars since the start of the anchor period: a trading session by default, or a week, month,
5//! quarter, year, decade or century of trading days. Each bar weighs the same, so on bars of
6//! equal duration the mean weighs every interval of time equally.
7//!
8//! # Calculation
9//! TWAP = Sum(Source) / Count since the start of the period
10//!
11//! # Example
12//! ```
13//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
14//! use egui_charts::studies::{AnchorPeriod, Indicator, TimeWeightedAvgPrice};
15//!
16//! let mut twap = TimeWeightedAvgPrice::new().with_anchor(AnchorPeriod::Week);
17//! twap.calculate(&bars);
18//! assert_eq!(twap.values().len(), bars.len());
19//! ```
20
21use super::anchor_period::{AnchorPeriod, ExchangeDays};
22use crate::model::{Bar, Instrument, PriceSource};
23use crate::studies::stepper::Stepper;
24use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
25use egui::Color32;
26
27/// Time Weighted Average Price, restarted at each anchor period.
28///
29/// Periods are counted in exchange trading days, as
30/// [`VolumeWeightedAvgPrice`](super::VolumeWeightedAvgPrice) counts them, in the time zone and
31/// session of the chart instrument ([`Indicator::set_instrument`]; UTC until then).
32/// Overlay indicator.
33#[derive(Clone)]
34pub struct TimeWeightedAvgPrice {
35    anchor: AnchorPeriod,
36    source: PriceSource,
37    days: ExchangeDays,
38    values: IndicatorValues,
39    stepper: Stepper<State>,
40    intraday: bool,
41    colors: Vec<Color32>,
42    visible: bool,
43}
44
45#[derive(Clone, Default)]
46struct State {
47    previous_date: Option<chrono::NaiveDate>,
48    intraday: bool,
49    period: Option<(i32, u32)>,
50    sum: f64,
51    count: f64,
52}
53
54impl TimeWeightedAvgPrice {
55    /// The mean of ohlc4 over each session.
56    pub fn new() -> Self {
57        Self {
58            anchor: AnchorPeriod::Session,
59            source: PriceSource::OHLC4,
60            days: ExchangeDays::default(),
61            values: IndicatorValues::new(),
62            stepper: Stepper::default(),
63            intraday: false,
64            colors: vec![Color32::from_rgb(221, 122, 40)],
65            visible: true,
66        }
67    }
68
69    /// Restarts at each selected calendar period.
70    pub fn with_anchor(mut self, anchor: AnchorPeriod) -> Self {
71        self.anchor = anchor;
72        self
73    }
74
75    /// The price averaged.
76    pub fn with_source(mut self, source: PriceSource) -> Self {
77        self.source = source;
78        self
79    }
80}
81
82impl Default for TimeWeightedAvgPrice {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl Indicator for TimeWeightedAvgPrice {
89    fn source(&self) -> Option<crate::studies::StudySource> {
90        Some(crate::studies::StudySource::Price(self.source))
91    }
92
93    crate::studies::inputs::study_inputs!(self;
94        anchor: choice("Anchor Period", [AnchorPeriod::Session, AnchorPeriod::Week, AnchorPeriod::Month, AnchorPeriod::Quarter, AnchorPeriod::Year, AnchorPeriod::Decade, AnchorPeriod::Century]),
95        source: choice("Source", PriceSource::all())
96    ; {});
97
98    fn name(&self) -> &str {
99        "Time Weighted Average Price"
100    }
101
102    fn desc(&self) -> &str {
103        "Time Weighted Average Price - Mean price since the start of each anchor period"
104    }
105
106    fn calculate(&mut self, data: &[Bar]) {
107        self.update(data, 0);
108    }
109
110    fn update(&mut self, data: &[Bar], mut from: usize) {
111        loop {
112            self.stepper
113                .run(data, from, &mut self.values, |state, bar| {
114                    let price = self.source.compute(bar.open, bar.high, bar.low, bar.close);
115                    if !price.is_finite() {
116                        return IndicatorValue::None;
117                    }
118                    let date = bar.time.date_naive();
119                    state.intraday |= state.previous_date == Some(date);
120                    state.previous_date = Some(date);
121                    let key = Some(self.anchor.key(self.days.day(bar, !self.intraday)));
122                    if key != state.period {
123                        (state.period, state.sum, state.count) = (key, 0.0, 0.0);
124                    }
125                    state.sum += price;
126                    state.count += 1.0;
127                    IndicatorValue::Single(state.sum / state.count)
128                });
129            if self.intraday == self.stepper.state().intraday {
130                break;
131            }
132            // Switching the inferred interval changes the exchange dates of earlier bars too.
133            self.intraday = self.stepper.state().intraday;
134            from = 0;
135        }
136    }
137
138    fn is_incremental(&self) -> bool {
139        true
140    }
141
142    fn set_instrument(&mut self, instrument: Option<&Instrument>) -> bool {
143        self.days.set(instrument)
144    }
145
146    fn values(&self) -> &IndicatorValues {
147        &self.values
148    }
149
150    fn colors(&self) -> Vec<Color32> {
151        self.colors.clone()
152    }
153
154    fn set_colors(&mut self, colors: Vec<Color32>) {
155        if let Some(color) = colors.first() {
156            self.colors = vec![*color];
157        }
158    }
159
160    fn is_overlay(&self) -> bool {
161        true
162    }
163
164    fn is_visible(&self) -> bool {
165        self.visible
166    }
167
168    fn set_visible(&mut self, visible: bool) {
169        self.visible = visible;
170    }
171
172    fn clone_box(&self) -> Box<dyn Indicator> {
173        Box::new(self.clone())
174    }
175
176    fn line_names(&self) -> Vec<String> {
177        vec!["TWAP".to_string()]
178    }
179}