Skip to main content

egui_charts/studies/builtin/
awesome_oscillator.rs

1//! Awesome Oscillator (AO) Indicator
2//!
3//! Developed by Bill Williams, the Awesome Oscillator measures market momentum
4//! by comparing recent market momentum to a larger time frame.
5//!
6//! # Formula
7//! AO = SMA(median_price, 5) - SMA(median_price, 34)
8//! Where median_price = (high + low) / 2
9//!
10//! # Interpretation
11//! - AO > 0: Bullish momentum
12//! - AO < 0: Bearish momentum
13//! - Zero line crossovers signal momentum shifts
14//! - Twin peaks (divergence) signals
15//! - Saucer signals (3-bar pattern)
16//!
17//! # Example
18//! ```
19//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
20//! use egui_charts::studies::{AwesomeOscillator, Indicator};
21//!
22//! let mut ao = AwesomeOscillator::new(5, 34);
23//! ao.calculate(&bars);
24//! ```
25
26use crate::model::Bar;
27use crate::studies::stepper::update_window;
28use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
29use egui::Color32;
30
31/// Awesome Oscillator indicator
32#[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    /// Create a new Awesome Oscillator
44    ///
45    /// # Arguments
46    /// * `fast_period` - Fast SMA period (typically 5)
47    /// * `slow_period` - Slow SMA period (typically 34)
48    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    /// Create with default params (5, 34)
60    pub fn default_params() -> Self {
61        Self::new(5, 34)
62    }
63
64    /// Set colors for positive and negative values
65    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
72/// Construct with the conventional default parameters.
73impl 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        // Calculate median prices (HL/2)
90        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            // Calculate fast SMA of median prices
99            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            // Calculate slow SMA of median prices
104            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            // AO = Fast SMA - Slow SMA
109            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 // AO is a separate histogram pane
199    }
200
201    fn line_count(&self) -> usize {
202        1 // Single histogram
203    }
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}