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