egui_charts/studies/builtin/
awesome_oscillator.rs1use crate::model::Bar;
27use crate::studies::stepper::update_window;
28use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
29use egui::Color32;
30
31#[derive(Clone)]
33pub struct AwesomeOscillator {
34 fast_period: usize,
35 slow_period: usize,
36 values: IndicatorValues,
37 positive_color: Color32,
38 negative_color: Color32,
39 visible: bool,
40}
41
42impl AwesomeOscillator {
43 pub fn new(fast_period: usize, slow_period: usize) -> Self {
49 Self {
50 fast_period: fast_period.max(1),
51 slow_period: slow_period.max(1),
52 values: IndicatorValues::new(),
53 positive_color: crate::studies::palette::DEEP_TEAL,
54 negative_color: crate::studies::palette::BRIGHT_RED,
55 visible: true,
56 }
57 }
58
59 pub fn default_params() -> Self {
61 Self::new(5, 34)
62 }
63
64 pub fn with_colors(mut self, positive: Color32, negative: Color32) -> Self {
66 self.positive_color = positive;
67 self.negative_color = negative;
68 self
69 }
70}
71
72impl Default for AwesomeOscillator {
74 fn default() -> Self {
75 Self::new(5, 34)
76 }
77}
78
79impl AwesomeOscillator {
80 fn compute(&self, data: &[Bar]) -> IndicatorValues {
81 let mut values = IndicatorValues::new();
82
83 if data.is_empty() {
84 return values;
85 }
86
87 let min_period = self.slow_period.max(self.fast_period);
88
89 let median_prices: Vec<f64> = data.iter().map(|b| (b.high + b.low) / 2.0).collect();
91
92 for i in 0..data.len() {
93 if i < min_period - 1 {
94 values.push(IndicatorValue::None);
95 continue;
96 }
97
98 let fast_start = i + 1 - self.fast_period;
100 let fast_sma: f64 =
101 median_prices[fast_start..=i].iter().sum::<f64>() / self.fast_period as f64;
102
103 let slow_start = i + 1 - self.slow_period;
105 let slow_sma: f64 =
106 median_prices[slow_start..=i].iter().sum::<f64>() / self.slow_period as f64;
107
108 let ao = fast_sma - slow_sma;
110 values.push(IndicatorValue::Single(ao));
111 }
112 values
113 }
114}
115
116impl Indicator for AwesomeOscillator {
117 fn source(&self) -> Option<crate::studies::StudySource> {
118 Some(crate::studies::StudySource::Price(
119 crate::model::PriceSource::HL2,
120 ))
121 }
122
123 crate::studies::inputs::study_inputs!(self;
124 fast_period: length("Fast period", 1, usize::MAX),
125 slow_period: length("Slow period", 1, usize::MAX)
126 ; {});
127
128 fn plot_style(&self, _line: usize) -> crate::studies::PlotStyle {
129 crate::studies::PlotStyle {
130 color: self.positive_color,
131 width: 1.0,
132 pattern: crate::model::LineStyle::Solid,
133 plot_type: crate::studies::PlotType::Columns,
134 visible: true,
135 }
136 }
137
138 fn plot_color(&self, _line: usize, bar: usize) -> Option<Color32> {
139 let value = self.values.value(0, bar)?;
140 let previous = bar.checked_sub(1).and_then(|bar| self.values.value(0, bar));
141 Some(if previous.is_some_and(|previous| value <= previous) {
142 self.negative_color
143 } else {
144 self.positive_color
145 })
146 }
147
148 fn value_format(&self) -> crate::scales::StudyFormat {
149 crate::scales::StudyFormat::Price
150 }
151
152 fn name(&self) -> &str {
153 "AO"
154 }
155
156 fn desc(&self) -> &str {
157 "Awesome Oscillator - Market momentum indicator"
158 }
159
160 fn calculate(&mut self, data: &[Bar]) {
161 self.update(data, 0);
162 }
163
164 fn update(&mut self, data: &[Bar], from: usize) {
165 let mut values = std::mem::take(&mut self.values);
166 update_window(
167 &mut values,
168 data,
169 from,
170 |_data, from| from.saturating_sub(self.fast_period.max(self.slow_period) - 1),
171 |data| self.compute(data),
172 );
173 self.values = values;
174 }
175
176 fn is_incremental(&self) -> bool {
177 true
178 }
179
180 fn values(&self) -> &IndicatorValues {
181 &self.values
182 }
183
184 fn colors(&self) -> Vec<Color32> {
185 vec![self.positive_color, self.negative_color]
186 }
187
188 fn set_colors(&mut self, colors: Vec<Color32>) {
189 if !colors.is_empty() {
190 self.positive_color = colors[0];
191 }
192 if colors.len() > 1 {
193 self.negative_color = colors[1];
194 }
195 }
196
197 fn is_overlay(&self) -> bool {
198 false }
200
201 fn line_count(&self) -> usize {
202 1 }
204
205 fn is_visible(&self) -> bool {
206 self.visible
207 }
208
209 fn set_visible(&mut self, visible: bool) {
210 self.visible = visible;
211 }
212
213 fn clone_box(&self) -> Box<dyn Indicator> {
214 Box::new(self.clone())
215 }
216
217 fn line_names(&self) -> Vec<String> {
218 vec![format!("AO({}, {})", self.fast_period, self.slow_period)]
219 }
220}