egui_charts/studies/builtin/
swma.rs1use crate::model::Bar;
25use crate::studies::palette;
26use crate::studies::stepper::update_window;
27use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
28use egui::Color32;
29
30#[derive(Clone)]
35pub struct SWMA {
36 values: IndicatorValues,
37 color: Color32,
38 visible: bool,
39}
40
41impl Default for SWMA {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl SWMA {
48 pub fn new() -> Self {
50 Self {
51 values: IndicatorValues::new(),
52 color: palette::BLUE,
53 visible: true,
54 }
55 }
56
57 pub fn with_color(mut self, color: Color32) -> Self {
59 self.color = color;
60 self
61 }
62
63 fn swma_val(p1: f64, p2: f64, p3: f64, p4: f64) -> f64 {
67 (p1 + 2.0 * p2 + 2.0 * p3 + p4) / 6.0
68 }
69}
70
71impl SWMA {
72 fn compute(&self, data: &[Bar]) -> IndicatorValues {
73 let mut values = IndicatorValues::new();
74
75 if data.len() < 4 {
77 for _ in 0..data.len() {
78 values.push(IndicatorValue::None);
79 }
80 return values;
81 }
82
83 for _ in 0..3 {
85 values.push(IndicatorValue::None);
86 }
87
88 for i in 3..data.len() {
90 let p1 = data[i - 3].close;
91 let p2 = data[i - 2].close;
92 let p3 = data[i - 1].close;
93 let p4 = data[i].close;
94
95 let swma = Self::swma_val(p1, p2, p3, p4);
96 values.push(IndicatorValue::Single(swma));
97 }
98 values
99 }
100}
101
102impl Indicator for SWMA {
103 fn source(&self) -> Option<crate::studies::StudySource> {
104 Some(crate::studies::StudySource::Price(
105 crate::model::PriceSource::Close,
106 ))
107 }
108
109 fn name(&self) -> &str {
110 "SWMA"
111 }
112
113 fn desc(&self) -> &str {
114 "Symmetrically Weighted Moving Avg - 4-bar symmetric weighted MA"
115 }
116
117 fn calculate(&mut self, data: &[Bar]) {
118 self.update(data, 0);
119 }
120
121 fn update(&mut self, data: &[Bar], from: usize) {
122 let mut values = std::mem::take(&mut self.values);
123 update_window(
124 &mut values,
125 data,
126 from,
127 |_data, from| from.saturating_sub(3),
128 |data| self.compute(data),
129 );
130 self.values = values;
131 }
132
133 fn is_incremental(&self) -> bool {
134 true
135 }
136
137 fn values(&self) -> &IndicatorValues {
138 &self.values
139 }
140
141 fn colors(&self) -> Vec<Color32> {
142 vec![self.color]
143 }
144
145 fn set_colors(&mut self, colors: Vec<Color32>) {
146 if !colors.is_empty() {
147 self.color = colors[0];
148 }
149 }
150
151 fn is_overlay(&self) -> bool {
152 true
153 }
154
155 fn is_visible(&self) -> bool {
156 self.visible
157 }
158
159 fn set_visible(&mut self, visible: bool) {
160 self.visible = visible;
161 }
162
163 fn clone_box(&self) -> Box<dyn Indicator> {
164 Box::new(self.clone())
165 }
166
167 fn line_names(&self) -> Vec<String> {
168 vec!["SWMA".to_string()]
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::studies::test_support::make_bar;
176
177 #[test]
178 fn test_swma_weighted() {
179 let swma = SWMA::swma_val(100.0, 120.0, 120.0, 100.0);
182 assert!((swma - 113.33).abs() < 0.01);
183 }
184
185 #[test]
186 fn test_swma_calculation() {
187 let mut swma = SWMA::new();
188
189 let data = vec![
190 make_bar(100.0),
191 make_bar(102.0),
192 make_bar(101.0),
193 make_bar(103.0),
194 make_bar(104.0),
195 ];
196
197 swma.calculate(&data);
198
199 assert_eq!(swma.values.len(), 5);
200 assert!(matches!(swma.values.to_rows()[0], IndicatorValue::None));
201 assert!(matches!(swma.values.to_rows()[1], IndicatorValue::None));
202 assert!(matches!(swma.values.to_rows()[2], IndicatorValue::None));
203
204 let IndicatorValue::Single(v) = swma.values.to_rows()[3] else {
206 panic!("no value: {:?}", swma.values.to_rows()[3]);
207 };
208 assert!((v - 101.5).abs() < 0.01);
209 }
210}