Skip to main content

egui_charts/studies/builtin/
vwma.rs

1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::{update_window, usable_behind};
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Volume Weighted Moving Avg (VWMA)
9/// Moving avg weighted by volume - gives more importance to high-volume periods
10#[derive(Clone)]
11pub struct VWMA {
12    period: usize,
13    values: IndicatorValues,
14    color: Color32,
15    visible: bool,
16}
17
18impl VWMA {
19    /// Create with a lookback of `period` bars, clamped to at least 1.
20    pub fn new(period: usize) -> Self {
21        Self {
22            period: period.max(1),
23            values: IndicatorValues::new(),
24            color: palette::BLUE,
25            visible: true,
26        }
27    }
28
29    /// Set the plot color.
30    pub fn with_color(mut self, color: Color32) -> Self {
31        self.color = color;
32        self
33    }
34}
35
36/// Construct with the conventional default parameters.
37impl Default for VWMA {
38    fn default() -> Self {
39        Self::new(20)
40    }
41}
42
43impl VWMA {
44    /// The values over `data`, every bar of which has finite inputs.
45    fn compute(&self, data: &[Bar]) -> IndicatorValues {
46        let mut values = IndicatorValues::new();
47        for i in 0..data.len() {
48            if i < self.period - 1 {
49                values.push(IndicatorValue::None);
50            } else {
51                let window = &data[i + 1 - self.period..=i];
52
53                // VWMA = Sum(Price * Volume) / Sum(Volume)
54                let price_volume_sum: f64 = window.iter().map(|bar| bar.close * bar.volume).sum();
55
56                let volume_sum: f64 = window.iter().map(|bar| bar.volume).sum();
57
58                let vwma = if volume_sum > 0.0 {
59                    price_volume_sum / volume_sum
60                } else {
61                    // Fall back to simple avg if no volume
62                    window.iter().map(|bar| bar.close).sum::<f64>() / self.period as f64
63                };
64
65                values.push(IndicatorValue::Single(vwma));
66            }
67        }
68
69        values
70    }
71}
72
73impl Indicator for VWMA {
74    fn source(&self) -> Option<crate::studies::StudySource> {
75        Some(crate::studies::StudySource::Price(
76            crate::model::PriceSource::Close,
77        ))
78    }
79
80    crate::studies::inputs::study_inputs!(self;
81        period: length("Period", 1, usize::MAX)
82    ; {});
83
84    fn name(&self) -> &str {
85        "VWMA"
86    }
87
88    fn desc(&self) -> &str {
89        "Volume Weighted Moving Avg - MA weighted by volume"
90    }
91
92    fn calculate(&mut self, data: &[Bar]) {
93        self.update(data, 0);
94    }
95
96    fn update(&mut self, data: &[Bar], from: usize) {
97        let mut values = std::mem::take(&mut self.values);
98        update_window(
99            &mut values,
100            data,
101            from,
102            usable_behind(self.period - 1, |b| {
103                b.close.is_finite() && b.volume.is_finite()
104            }),
105            |data| {
106                over_finite_bars(
107                    data,
108                    |b| b.close.is_finite() && b.volume.is_finite(),
109                    |data| self.compute(data),
110                )
111            },
112        );
113        self.values = values;
114    }
115
116    fn is_incremental(&self) -> bool {
117        true
118    }
119
120    fn values(&self) -> &IndicatorValues {
121        &self.values
122    }
123
124    fn colors(&self) -> Vec<Color32> {
125        vec![self.color]
126    }
127
128    fn set_colors(&mut self, colors: Vec<Color32>) {
129        if !colors.is_empty() {
130            self.color = colors[0];
131        }
132    }
133
134    fn is_overlay(&self) -> bool {
135        true // Drawn on price chart
136    }
137
138    fn is_visible(&self) -> bool {
139        self.visible
140    }
141
142    fn set_visible(&mut self, visible: bool) {
143        self.visible = visible;
144    }
145
146    fn clone_box(&self) -> Box<dyn Indicator> {
147        Box::new(self.clone())
148    }
149
150    fn line_names(&self) -> Vec<String> {
151        vec![format!("VWMA({})", self.period)]
152    }
153}