wickra_core/indicators/
alligator.rs1use crate::error::{Error, Result};
4use crate::indicators::smma::Smma;
5use crate::ohlcv::Candle;
6use crate::traits::Indicator;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct AlligatorOutput {
12 pub jaw: f64,
14 pub teeth: f64,
16 pub lips: f64,
18}
19
20#[derive(Debug, Clone)]
47pub struct Alligator {
48 jaw_period: usize,
49 teeth_period: usize,
50 lips_period: usize,
51 jaw: Smma,
52 teeth: Smma,
53 lips: Smma,
54}
55
56impl Alligator {
57 pub fn new(jaw_period: usize, teeth_period: usize, lips_period: usize) -> Result<Self> {
60 if jaw_period == 0 || teeth_period == 0 || lips_period == 0 {
61 return Err(Error::PeriodZero);
62 }
63 Ok(Self {
64 jaw_period,
65 teeth_period,
66 lips_period,
67 jaw: Smma::new(jaw_period)?,
68 teeth: Smma::new(teeth_period)?,
69 lips: Smma::new(lips_period)?,
70 })
71 }
72
73 pub fn classic() -> Self {
75 Self::new(13, 8, 5).expect("classic Alligator parameters are valid")
76 }
77
78 pub const fn periods(&self) -> (usize, usize, usize) {
80 (self.jaw_period, self.teeth_period, self.lips_period)
81 }
82}
83
84impl Indicator for Alligator {
85 type Input = Candle;
86 type Output = AlligatorOutput;
87
88 fn update(&mut self, candle: Candle) -> Option<AlligatorOutput> {
89 let median = f64::midpoint(candle.high, candle.low);
90 let lips = self.lips.update(median);
94 let teeth = self.teeth.update(median);
95 let jaw = self.jaw.update(median);
96 Some(AlligatorOutput {
97 jaw: jaw?,
98 teeth: teeth?,
99 lips: lips?,
100 })
101 }
102
103 fn reset(&mut self) {
104 self.jaw.reset();
105 self.teeth.reset();
106 self.lips.reset();
107 }
108
109 fn warmup_period(&self) -> usize {
110 self.jaw_period.max(self.teeth_period).max(self.lips_period)
113 }
114
115 fn is_ready(&self) -> bool {
116 self.jaw.is_ready() && self.teeth.is_ready() && self.lips.is_ready()
117 }
118
119 fn name(&self) -> &'static str {
120 "Alligator"
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::traits::BatchExt;
128 use approx::assert_relative_eq;
129
130 fn candle(high: f64, low: f64, ts: i64) -> Candle {
131 let close = f64::midpoint(high, low);
132 Candle::new(close, high, low, close, 1.0, ts).unwrap()
133 }
134
135 #[test]
136 fn rejects_zero_period() {
137 assert!(matches!(Alligator::new(0, 8, 5), Err(Error::PeriodZero)));
138 assert!(matches!(Alligator::new(13, 0, 5), Err(Error::PeriodZero)));
139 assert!(matches!(Alligator::new(13, 8, 0), Err(Error::PeriodZero)));
140 }
141
142 #[test]
143 fn accessors_and_metadata() {
144 let alligator = Alligator::classic();
145 assert_eq!(alligator.periods(), (13, 8, 5));
146 assert_eq!(alligator.warmup_period(), 13);
147 assert_eq!(alligator.name(), "Alligator");
148 }
149
150 #[test]
151 fn constant_series_yields_the_constant() {
152 let mut alligator = Alligator::classic();
154 let candles: Vec<Candle> = (0..40).map(|i| candle(11.0, 9.0, i)).collect();
155 let out = alligator.batch(&candles);
156 for v in out.iter().skip(12).flatten() {
157 assert_relative_eq!(v.jaw, 10.0, epsilon = 1e-12);
158 assert_relative_eq!(v.teeth, 10.0, epsilon = 1e-12);
159 assert_relative_eq!(v.lips, 10.0, epsilon = 1e-12);
160 }
161 }
162
163 #[test]
164 fn warmup_emits_first_value_at_longest_period() {
165 let mut alligator = Alligator::new(5, 3, 2).unwrap();
166 let candles: Vec<Candle> = (0..6).map(|i| candle(11.0, 9.0, i)).collect();
167 let out = alligator.batch(&candles);
168 for v in out.iter().take(4) {
169 assert!(v.is_none());
170 }
171 assert!(out[4].is_some());
172 }
173
174 #[test]
175 fn pure_uptrend_ordering() {
176 let mut alligator = Alligator::classic();
179 let candles: Vec<Candle> = (0_i64..80)
180 .map(|i| candle(10.0 + i as f64, 9.0 + i as f64, i))
181 .collect();
182 let out = alligator.batch(&candles);
183 let last = out.last().unwrap().unwrap();
184 assert!(
185 last.lips > last.teeth,
186 "lips {} > teeth {}",
187 last.lips,
188 last.teeth
189 );
190 assert!(
191 last.teeth > last.jaw,
192 "teeth {} > jaw {}",
193 last.teeth,
194 last.jaw
195 );
196 }
197
198 #[test]
199 fn batch_equals_streaming() {
200 let candles: Vec<Candle> = (0..80_i64)
201 .map(|i| {
202 let base = 100.0 + (i as f64 * 0.2).sin() * 5.0;
203 candle(base + 1.0, base - 1.0, i)
204 })
205 .collect();
206 let mut a = Alligator::classic();
207 let mut b = Alligator::classic();
208 assert_eq!(
209 a.batch(&candles),
210 candles.iter().map(|c| b.update(*c)).collect::<Vec<_>>()
211 );
212 }
213
214 #[test]
215 fn reset_clears_state() {
216 let mut alligator = Alligator::classic();
217 let candles: Vec<Candle> = (0..40).map(|i| candle(11.0, 9.0, i)).collect();
218 alligator.batch(&candles);
219 assert!(alligator.is_ready());
220 alligator.reset();
221 assert!(!alligator.is_ready());
222 }
223}