quantwave_core/indicators/
alligator.rs1use crate::indicators::metadata::IndicatorMetadata;
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
10pub struct Alligator {
11 jaw: SmmaOffset,
12 teeth: SmmaOffset,
13 lips: SmmaOffset,
14}
15
16#[derive(Debug, Clone)]
17struct SmmaOffset {
18 period: usize,
19 offset: usize,
20 prev_smma: Option<f64>,
21 history: VecDeque<f64>,
22 count: usize,
23}
24
25impl SmmaOffset {
26 fn new(period: usize, offset: usize) -> Self {
27 Self {
28 period,
29 offset,
30 prev_smma: None,
31 history: VecDeque::with_capacity(offset + 1),
32 count: 0,
33 }
34 }
35
36 fn next(&mut self, price: f64) -> f64 {
37 self.count += 1;
38
39 let smma = match self.prev_smma {
41 None => {
42 if self.count == self.period {
43 self.prev_smma = Some(price);
49 price
50 } else {
51 0.0
52 }
53 }
54 Some(prev) => {
55 let val = (prev * (self.period as f64 - 1.0) + price) / self.period as f64;
56 self.prev_smma = Some(val);
57 val
58 }
59 };
60
61 if self.count < self.period {
62 return f64::NAN;
63 }
64
65 self.history.push_front(smma);
67 if self.history.len() > self.offset + 1 {
68 self.history.pop_back();
69 }
70
71 if self.history.len() <= self.offset {
72 f64::NAN
73 } else {
74 *self.history.back().unwrap()
75 }
76 }
77}
78
79impl Alligator {
80 pub fn new() -> Self {
81 Self {
82 jaw: SmmaOffset::new(13, 8),
83 teeth: SmmaOffset::new(8, 5),
84 lips: SmmaOffset::new(5, 3),
85 }
86 }
87}
88
89impl Default for Alligator {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl Next<f64> for Alligator {
96 type Output = (f64, f64, f64); fn next(&mut self, input: f64) -> Self::Output {
99 (
100 self.jaw.next(input),
101 self.teeth.next(input),
102 self.lips.next(input),
103 )
104 }
105}
106
107pub const ALLIGATOR_METADATA: IndicatorMetadata = IndicatorMetadata {
108 name: "Bill Williams Alligator",
109 description: "Trend-following indicator using three delayed smoothed moving averages.",
110 usage: "Use to identify trend presence and direction. When the three Alligator lines are separated and fanning, the market is trending; when they converge or intertwine, the market is ranging.",
111 keywords: &["trend", "moving-average", "classic", "williams"],
112 ehlers_summary: "Bill Williams introduced the Alligator in Trading Chaos (1995) as three offset SMAs with periods 13, 8, and 5 and offsets of 8, 5, and 3 bars. The three lines represent the Jaw, Teeth, and Lips of the Alligator. When the Alligator is sleeping (lines intertwined) no trade is taken; when it wakes and opens its mouth a trend trade is entered. — StockCharts ChartSchool",
113 params: &[],
114 formula_source: "https://chartschool.stockcharts.com/table-of-contents/technical-indicators-and-overlays/alligator",
115 formula_latex: r#"
116\[
117\text{Jaw} = \text{SMMA}(13, 8), \text{Teeth} = \text{SMMA}(8, 5), \text{Lips} = \text{SMMA}(5, 3)
118\]
119"#,
120 gold_standard_file: "alligator.json",
121 category: "Classic",
122};
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::traits::Next;
128 use proptest::prelude::*;
129
130 #[test]
131 fn test_alligator_basic() {
132 let mut alligator = Alligator::new();
133 for i in 0..30 {
134 let (jaw, teeth, lips) = alligator.next(100.0 + i as f64);
135 if i > 25 {
136 assert!(!jaw.is_nan());
137 assert!(!teeth.is_nan());
138 assert!(!lips.is_nan());
139 }
140 }
141 }
142
143 proptest! {
144 #[test]
145 fn test_alligator_parity(
146 inputs in prop::collection::vec(1.0..100.0, 50..100),
147 ) {
148 let mut alligator = Alligator::new();
149 let streaming_results: Vec<(f64, f64, f64)> = inputs.iter().map(|&x| alligator.next(x)).collect();
150
151 let mut jaw_smma = SmmaOffset::new(13, 8);
152 let mut teeth_smma = SmmaOffset::new(8, 5);
153 let mut lips_smma = SmmaOffset::new(5, 3);
154
155 for (i, &input) in inputs.iter().enumerate() {
156 let j = jaw_smma.next(input);
157 let t = teeth_smma.next(input);
158 let l = lips_smma.next(input);
159
160 let (sj, st, sl) = streaming_results[i];
161 if j.is_nan() { assert!(sj.is_nan()); } else { approx::assert_relative_eq!(sj, j, epsilon = 1e-10); }
162 if t.is_nan() { assert!(st.is_nan()); } else { approx::assert_relative_eq!(st, t, epsilon = 1e-10); }
163 if l.is_nan() { assert!(sl.is_nan()); } else { approx::assert_relative_eq!(sl, l, epsilon = 1e-10); }
164 }
165 }
166 }
167}