egui_charts/studies/builtin/
hma.rs1use crate::model::Bar;
19use crate::studies::helpers;
20use crate::studies::palette;
21use crate::studies::stepper::update_window;
22use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
23use egui::Color32;
24
25#[derive(Clone)]
27pub struct HMA {
28 period: usize,
29 values: IndicatorValues,
30 color: Color32,
31 visible: bool,
32}
33
34impl HMA {
35 pub fn new(period: usize) -> Self {
40 Self {
41 period: period.max(2),
42 values: IndicatorValues::new(),
43 color: palette::LIGHT_BLUE,
44 visible: true,
45 }
46 }
47
48 pub fn with_color(mut self, color: Color32) -> Self {
50 self.color = color;
51 self
52 }
53}
54
55impl Default for HMA {
57 fn default() -> Self {
58 Self::new(9)
59 }
60}
61
62impl HMA {
63 fn compute(&self, data: &[Bar]) -> IndicatorValues {
64 let mut values = IndicatorValues::new();
65
66 if data.is_empty() {
67 return values;
68 }
69
70 let half_period = (self.period / 2).max(1);
71 let sqrt_period = (self.period as f64).sqrt() as usize;
72 let sqrt_period = sqrt_period.max(1);
73
74 let closes: Vec<f64> = data.iter().map(|b| b.close).collect();
76
77 let wma_half = helpers::wma(&closes, half_period);
79
80 let wma_full = helpers::wma(&closes, self.period);
82
83 let mut raw_hma = vec![f64::NAN; data.len()];
85 for i in 0..data.len() {
86 if !wma_half[i].is_nan() && !wma_full[i].is_nan() {
87 raw_hma[i] = 2.0 * wma_half[i] - wma_full[i];
88 }
89 }
90
91 let hma = helpers::wma(&raw_hma, sqrt_period);
93
94 let min_period = self.period.saturating_add(sqrt_period) - 1;
96 for i in 0..data.len() {
97 if i < min_period - 1 || hma[i].is_nan() {
98 values.push(IndicatorValue::None);
99 } else {
100 values.push(IndicatorValue::Single(hma[i]));
101 }
102 }
103 values
104 }
105}
106
107impl Indicator for HMA {
108 fn source(&self) -> Option<crate::studies::StudySource> {
109 Some(crate::studies::StudySource::Price(
110 crate::model::PriceSource::Close,
111 ))
112 }
113
114 crate::studies::inputs::study_inputs!(self;
115 period: length("Period", 2, usize::MAX)
116 ; {});
117
118 fn name(&self) -> &str {
119 "HMA"
120 }
121
122 fn desc(&self) -> &str {
123 "Hull Moving Avg - Fast and smooth moving avg"
124 }
125
126 fn calculate(&mut self, data: &[Bar]) {
127 self.update(data, 0);
128 }
129
130 fn update(&mut self, data: &[Bar], from: usize) {
131 let mut values = std::mem::take(&mut self.values);
132 update_window(
133 &mut values,
134 data,
135 from,
136 |_data, from| {
137 from.saturating_sub(
138 self.period
139 .saturating_add(((self.period as f64).sqrt() as usize).max(1) - 1)
140 - 1,
141 )
142 },
143 |data| self.compute(data),
144 );
145 self.values = values;
146 }
147
148 fn is_incremental(&self) -> bool {
149 true
150 }
151
152 fn values(&self) -> &IndicatorValues {
153 &self.values
154 }
155
156 fn colors(&self) -> Vec<Color32> {
157 vec![self.color]
158 }
159
160 fn set_colors(&mut self, colors: Vec<Color32>) {
161 if !colors.is_empty() {
162 self.color = colors[0];
163 }
164 }
165
166 fn is_overlay(&self) -> bool {
167 true
168 }
169
170 fn line_count(&self) -> usize {
171 1
172 }
173
174 fn is_visible(&self) -> bool {
175 self.visible
176 }
177
178 fn set_visible(&mut self, visible: bool) {
179 self.visible = visible;
180 }
181
182 fn clone_box(&self) -> Box<dyn Indicator> {
183 Box::new(self.clone())
184 }
185
186 fn line_names(&self) -> Vec<String> {
187 vec![format!("HMA({})", self.period)]
188 }
189}