egui_charts/studies/builtin/
ma_hamming.rs1use crate::model::Bar;
2use crate::studies::palette;
3use crate::studies::stepper::update_window;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7#[derive(Clone)]
9pub struct HammingMA {
10 period: usize,
11 values: IndicatorValues,
12 color: Color32,
13 visible: bool,
14}
15
16impl HammingMA {
17 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 pub fn with_color(mut self, color: Color32) -> Self {
29 self.color = color;
30 self
31 }
32
33 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}