Skip to main content

egui_charts/studies/builtin/
wma.rs

1//! Weighted Moving Avg (WMA) Indicator
2//!
3//! WMA assigns greater weight to more recent data points, making it more
4//! responsive to recent price changes than SMA.
5//!
6//! # Formula
7//! WMA = (P1 * n + P2 * (n-1) + ... + Pn * 1) / (n * (n+1) / 2)
8//!
9//! # Example
10//! ```
11//! # 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();
12//! use egui_charts::studies::{WMA, Indicator};
13//!
14//! let mut wma = WMA::new(14);
15//! wma.calculate(&bars);
16//! ```
17
18use crate::model::Bar;
19use crate::studies::palette;
20use crate::studies::stepper::update_window;
21use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
22use egui::Color32;
23
24/// Weighted Moving Avg indicator
25#[derive(Clone)]
26pub struct WMA {
27    period: usize,
28    values: IndicatorValues,
29    color: Color32,
30    visible: bool,
31}
32
33impl WMA {
34    /// Create a new WMA indicator
35    ///
36    /// # Arguments
37    /// * `period` - Number of periods for calculation (typically 14, 20, or 50)
38    pub fn new(period: usize) -> Self {
39        Self {
40            period: period.max(1),
41            values: IndicatorValues::new(),
42            color: palette::LIGHT_BLUE,
43            visible: true,
44        }
45    }
46
47    /// Set the line color
48    pub fn with_color(mut self, color: Color32) -> Self {
49        self.color = color;
50        self
51    }
52}
53
54/// Construct with the conventional default parameters.
55impl Default for WMA {
56    fn default() -> Self {
57        Self::new(9)
58    }
59}
60
61impl WMA {
62    fn compute(&self, data: &[Bar]) -> IndicatorValues {
63        let mut values = IndicatorValues::new();
64
65        if data.is_empty() {
66            return values;
67        }
68
69        // Weight sum: n*(n+1)/2
70        let weight_sum = self.period as f64 * (self.period as f64 + 1.0) / 2.0;
71
72        for i in 0..data.len() {
73            if i < self.period - 1 {
74                values.push(IndicatorValue::None);
75            } else {
76                let mut weighted_sum = 0.0;
77                for j in 0..self.period {
78                    let weight = (self.period - j) as f64;
79                    weighted_sum += data[i - j].close * weight;
80                }
81                let wma = weighted_sum / weight_sum;
82                values.push(IndicatorValue::Single(wma));
83            }
84        }
85        values
86    }
87}
88
89impl Indicator for WMA {
90    fn source(&self) -> Option<crate::studies::StudySource> {
91        Some(crate::studies::StudySource::Price(
92            crate::model::PriceSource::Close,
93        ))
94    }
95
96    crate::studies::inputs::study_inputs!(self;
97        period: length("Period", 1, usize::MAX)
98    ; {});
99
100    fn name(&self) -> &str {
101        "WMA"
102    }
103
104    fn desc(&self) -> &str {
105        "Weighted Moving Avg - Gives more weight to recent prices"
106    }
107
108    fn calculate(&mut self, data: &[Bar]) {
109        self.update(data, 0);
110    }
111
112    fn update(&mut self, data: &[Bar], from: usize) {
113        let mut values = std::mem::take(&mut self.values);
114        update_window(
115            &mut values,
116            data,
117            from,
118            |_data, from| from.saturating_sub(self.period - 1),
119            |data| self.compute(data),
120        );
121        self.values = values;
122    }
123
124    fn is_incremental(&self) -> bool {
125        true
126    }
127
128    fn values(&self) -> &IndicatorValues {
129        &self.values
130    }
131
132    fn colors(&self) -> Vec<Color32> {
133        vec![self.color]
134    }
135
136    fn set_colors(&mut self, colors: Vec<Color32>) {
137        if !colors.is_empty() {
138            self.color = colors[0];
139        }
140    }
141
142    fn is_overlay(&self) -> bool {
143        true
144    }
145
146    fn line_count(&self) -> usize {
147        1
148    }
149
150    fn is_visible(&self) -> bool {
151        self.visible
152    }
153
154    fn set_visible(&mut self, visible: bool) {
155        self.visible = visible;
156    }
157
158    fn clone_box(&self) -> Box<dyn Indicator> {
159        Box::new(self.clone())
160    }
161
162    fn line_names(&self) -> Vec<String> {
163        vec![format!("WMA({})", self.period)]
164    }
165}