Skip to main content

egui_charts/studies/builtin/
ma_hamming.rs

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