Skip to main content

egui_charts/studies/builtin/
williams_alligator.rs

1//! Williams Alligator
2//!
3//! Bill Williams' Alligator (Trading Chaos, 1995) is three smoothed moving averages of the
4//! median price, each displaced into the future, that intertwine while the market ranges and
5//! fan out in order while it trends.
6//!
7//! # Formula
8//! - median = (high + low) / 2
9//! - Jaw (blue) = SMMA(median, 13), drawn 8 bars ahead
10//! - Teeth (red) = SMMA(median, 8), drawn 5 bars ahead
11//! - Lips (green) = SMMA(median, 5), drawn 3 bars ahead
12//!
13//! SMMA(x, n) starts with the simple mean of its first `n` values, then
14//! SMMA = (SMMA[1] * (n - 1) + x) / n. Values stay indexed by their source bar; the
15//! displacement is [`plot_offset`](Indicator::plot_offset), so the last values are drawn in
16//! the future whitespace.
17//!
18//! # Example
19//! ```
20//! # 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();
21//! use egui_charts::studies::{Indicator, WilliamsAlligator};
22//!
23//! let mut alligator = WilliamsAlligator::new(13, 8, 5);
24//! alligator.calculate(&bars);
25//! assert_eq!(alligator.plot_offset(0), 8);
26//! ```
27
28use super::smoothing::Smoothing;
29use crate::model::Bar;
30use crate::studies::palette;
31use crate::studies::stepper::Stepper;
32use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
33use egui::Color32;
34
35/// Williams Alligator: jaw, teeth and lips lines, displaced forward.
36#[derive(Clone)]
37pub struct WilliamsAlligator {
38    jaw_length: usize,
39    teeth_length: usize,
40    lips_length: usize,
41    jaw_offset: usize,
42    teeth_offset: usize,
43    lips_offset: usize,
44    values: IndicatorValues,
45    stepper: Stepper<[Smoothing; 3]>,
46    colors: [Color32; 3],
47    visible: bool,
48}
49
50impl WilliamsAlligator {
51    /// Create an Alligator with the jaw, teeth and lips smoothing lengths (typically 13, 8, 5)
52    /// and the conventional displacements 8, 5 and 3.
53    pub fn new(jaw_length: usize, teeth_length: usize, lips_length: usize) -> Self {
54        Self {
55            jaw_length: jaw_length.max(1),
56            teeth_length: teeth_length.max(1),
57            lips_length: lips_length.max(1),
58            jaw_offset: 8,
59            teeth_offset: 5,
60            lips_offset: 3,
61            values: IndicatorValues::new(),
62            stepper: Stepper::default(),
63            colors: [
64                palette::ACCENT,
65                palette::PINK,
66                Color32::from_rgb(102, 187, 106),
67            ],
68            visible: true,
69        }
70    }
71
72    /// Set the forward displacement of the jaw, teeth and lips in bars.
73    pub fn with_offsets(mut self, jaw: usize, teeth: usize, lips: usize) -> Self {
74        (self.jaw_offset, self.teeth_offset, self.lips_offset) = (jaw, teeth, lips);
75        self
76    }
77}
78
79/// Construct with the conventional default parameters.
80impl Default for WilliamsAlligator {
81    fn default() -> Self {
82        Self::new(13, 8, 5)
83    }
84}
85
86impl Indicator for WilliamsAlligator {
87    fn source(&self) -> Option<crate::studies::StudySource> {
88        Some(crate::studies::StudySource::Price(
89            crate::model::PriceSource::HL2,
90        ))
91    }
92
93    crate::studies::inputs::study_inputs!(self;
94        jaw_length: length("Jaw length", 1, usize::MAX),
95        teeth_length: length("Teeth length", 1, usize::MAX),
96        lips_length: length("Lips length", 1, usize::MAX),
97        jaw_offset: length("Jaw offset", 0, isize::MAX as usize),
98        teeth_offset: length("Teeth offset", 0, isize::MAX as usize),
99        lips_offset: length("Lips offset", 0, isize::MAX as usize)
100    ; {});
101
102    fn name(&self) -> &str {
103        "Williams Alligator"
104    }
105
106    fn desc(&self) -> &str {
107        "Williams Alligator - Displaced smoothed averages of the median price"
108    }
109
110    fn calculate(&mut self, data: &[Bar]) {
111        self.update(data, 0);
112    }
113
114    fn update(&mut self, data: &[Bar], from: usize) {
115        self.stepper
116            .run(data, from, &mut self.values, |state, bar| {
117                if !bar.high.is_finite() || !bar.low.is_finite() {
118                    return IndicatorValue::None;
119                }
120                let median = (bar.high + bar.low) / 2.0;
121                let values = state
122                    .iter_mut()
123                    .zip([self.jaw_length, self.teeth_length, self.lips_length])
124                    .map(|(average, n)| average.next(median, n, 1.0 / n as f64))
125                    .collect();
126                IndicatorValue::Multiple(values)
127            });
128    }
129
130    fn is_incremental(&self) -> bool {
131        true
132    }
133
134    fn plot_offset(&self, line: usize) -> isize {
135        let offset = [self.jaw_offset, self.teeth_offset, self.lips_offset]
136            .get(line)
137            .copied()
138            .unwrap_or(0);
139        offset.min(isize::MAX as usize) as isize
140    }
141
142    fn values(&self) -> &IndicatorValues {
143        &self.values
144    }
145
146    fn colors(&self) -> Vec<Color32> {
147        self.colors.to_vec()
148    }
149
150    fn set_colors(&mut self, colors: Vec<Color32>) {
151        for (old, new) in self.colors.iter_mut().zip(colors) {
152            *old = new;
153        }
154    }
155
156    fn is_visible(&self) -> bool {
157        self.visible
158    }
159
160    fn set_visible(&mut self, visible: bool) {
161        self.visible = visible;
162    }
163
164    fn clone_box(&self) -> Box<dyn Indicator> {
165        Box::new(self.clone())
166    }
167
168    fn line_names(&self) -> Vec<String> {
169        vec!["Jaw".into(), "Teeth".into(), "Lips".into()]
170    }
171}