kestrel_chartkit/indicator/
alligator.rs1use super::smoothing::Rma;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4use std::collections::{HashMap, VecDeque};
5
6#[derive(Debug, Clone)]
9pub struct AlligatorEngine {
10 jaw_rma: Rma,
11 teeth_rma: Rma,
12 lips_rma: Rma,
13 jaw_window: VecDeque<f64>,
14 teeth_window: VecDeque<f64>,
15 lips_window: VecDeque<f64>,
16}
17
18impl AlligatorEngine {
19 pub fn new() -> Self {
20 Self {
21 jaw_rma: Rma::new(13),
22 teeth_rma: Rma::new(8),
23 lips_rma: Rma::new(5),
24 jaw_window: VecDeque::with_capacity(9),
25 teeth_window: VecDeque::with_capacity(6),
26 lips_window: VecDeque::with_capacity(4),
27 }
28 }
29}
30
31impl Default for AlligatorEngine {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Indicator for AlligatorEngine {
38 fn name(&self) -> &str {
39 "alligator"
40 }
41
42 fn warmup_period(&self) -> usize {
43 13 + 8
44 }
45
46 fn reset(&mut self) {
47 self.jaw_rma.reset();
48 self.teeth_rma.reset();
49 self.lips_rma.reset();
50 self.jaw_window.clear();
51 self.teeth_window.clear();
52 self.lips_window.clear();
53 }
54
55 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
56 let hl2 = (bar.high + bar.low) / 2.0;
57
58 let raw_jaw = self.jaw_rma.update(hl2);
59 let raw_teeth = self.teeth_rma.update(hl2);
60 let raw_lips = self.lips_rma.update(hl2);
61
62 if let (Some(j), Some(t), Some(l)) = (raw_jaw, raw_teeth, raw_lips) {
63 self.jaw_window.push_back(j);
67 if self.jaw_window.len() > 9 {
68 self.jaw_window.pop_front();
69 }
70
71 self.teeth_window.push_back(t);
72 if self.teeth_window.len() > 6 {
73 self.teeth_window.pop_front();
74 }
75
76 self.lips_window.push_back(l);
77 if self.lips_window.len() > 4 {
78 self.lips_window.pop_front();
79 }
80
81 let jaw_val = *self.jaw_window.front().unwrap();
82 let teeth_val = *self.teeth_window.front().unwrap();
83 let lips_val = *self.lips_window.front().unwrap();
84
85 let mut extra = HashMap::new();
86 extra.insert("jaw".to_string(), jaw_val);
87 extra.insert("teeth".to_string(), teeth_val);
88 extra.insert("lips".to_string(), lips_val);
89
90 Some(IndicatorOutput::with_extra(lips_val, extra))
91 } else {
92 None
93 }
94 }
95
96 fn alerts(&self) -> Vec<IndicatorAlert> {
97 Vec::new()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_alligator_basic() {
107 let mut gator = AlligatorEngine::new();
108 let mut out = None;
109 for i in 0..40 {
110 let b = Bar::new(i, 100.0, 105.0, 95.0, 100.0 + i as f64, 1000.0);
111 out = gator.on_bar(&b);
112 }
113 assert!(out.is_some());
114 let o = out.unwrap();
115 assert!(o.extra.contains_key("jaw"));
116 assert!(o.extra.contains_key("teeth"));
117 assert!(o.extra.contains_key("lips"));
118 }
119}