egui_charts/studies/builtin/
vwma.rs1use 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#[derive(Clone)]
11pub struct VWMA {
12 period: usize,
13 values: IndicatorValues,
14 color: Color32,
15 visible: bool,
16}
17
18impl VWMA {
19 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 pub fn with_color(mut self, color: Color32) -> Self {
31 self.color = color;
32 self
33 }
34}
35
36impl Default for VWMA {
38 fn default() -> Self {
39 Self::new(20)
40 }
41}
42
43impl VWMA {
44 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 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 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 }
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}