1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
44pub struct HikkakeModified {
45 prev: Option<Candle>,
46 prev_prev: Option<Candle>,
47 has_emitted: bool,
48}
49
50impl HikkakeModified {
51 pub const fn new() -> Self {
53 Self {
54 prev: None,
55 prev_prev: None,
56 has_emitted: false,
57 }
58 }
59}
60
61impl Indicator for HikkakeModified {
62 type Input = Candle;
63 type Output = f64;
64
65 #[inline]
66 fn update(&mut self, candle: Candle) -> Option<f64> {
67 let bar1 = self.prev_prev;
68 let bar2 = self.prev;
69 self.prev_prev = self.prev;
70 self.prev = Some(candle);
71 let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
72 return None;
73 };
74 self.has_emitted = true;
75 if !(bar2.high < bar1.high && bar2.low > bar1.low) {
76 return Some(0.0);
77 }
78 if candle.high < bar2.high && candle.low < bar2.low && candle.close > bar2.low {
80 return Some(1.0);
81 }
82 if candle.high > bar2.high && candle.low > bar2.low && candle.close < bar2.high {
84 return Some(-1.0);
85 }
86 Some(0.0)
87 }
88
89 fn reset(&mut self) {
90 self.prev = None;
91 self.prev_prev = None;
92 self.has_emitted = false;
93 }
94
95 #[inline]
96 fn warmup_period(&self) -> usize {
97 3
98 }
99
100 #[inline]
101 fn is_ready(&self) -> bool {
102 self.has_emitted
103 }
104
105 #[inline]
106 fn name(&self) -> &'static str {
107 "HikkakeModified"
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::traits::BatchExt;
115
116 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
117 Candle::new(open, high, low, close, 1.0, ts).unwrap()
118 }
119
120 #[test]
121 fn accessors_and_metadata() {
122 let t = HikkakeModified::new();
123 assert_eq!(t.name(), "HikkakeModified");
124 assert_eq!(t.warmup_period(), 3);
125 assert!(!t.is_ready());
126 }
127
128 #[test]
129 fn bullish_modified_hikkake_is_plus_one() {
130 let mut t = HikkakeModified::new();
131 assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), None);
132 assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), None);
133 assert_eq!(t.update(c(9.0, 12.0, 6.0, 9.0, 2)), Some(1.0));
134 }
135
136 #[test]
137 fn bearish_modified_hikkake_is_minus_one() {
138 let mut t = HikkakeModified::new();
139 assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), None);
140 assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), None);
141 assert_eq!(t.update(c(13.0, 14.0, 9.0, 10.0, 2)), Some(-1.0));
142 }
143
144 #[test]
145 fn break_without_close_recovery_yields_zero() {
146 let mut t = HikkakeModified::new();
147 t.update(c(10.0, 15.0, 5.0, 12.0, 0));
148 t.update(c(11.0, 13.0, 8.0, 12.0, 1));
149 assert_eq!(t.update(c(9.0, 12.0, 6.0, 7.0, 2)), Some(0.0));
152 }
153
154 #[test]
155 fn not_inside_bar_yields_zero() {
156 let mut t = HikkakeModified::new();
157 t.update(c(10.0, 15.0, 5.0, 12.0, 0));
158 t.update(c(11.0, 16.0, 8.0, 12.0, 1));
159 assert_eq!(t.update(c(9.0, 12.0, 6.0, 9.0, 2)), Some(0.0));
160 }
161
162 #[test]
163 fn first_two_bars_return_zero() {
164 let mut t = HikkakeModified::new();
165 assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), None);
166 assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), None);
167 }
168
169 #[test]
170 fn batch_equals_streaming() {
171 let candles: Vec<Candle> = (0..40)
172 .map(|i| {
173 let base = 100.0 + i as f64;
174 match i % 3 {
175 0 => c(base, base + 6.0, base - 6.0, base, i),
176 1 => c(base, base + 2.0, base - 2.0, base, i),
177 _ => c(base, base + 1.0, base - 5.0, base, i),
178 }
179 })
180 .collect();
181 let mut a = HikkakeModified::new();
182 let mut b = HikkakeModified::new();
183 assert_eq!(
184 a.batch(&candles),
185 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
186 );
187 }
188
189 #[test]
190 fn reset_clears_state() {
191 let mut t = HikkakeModified::new();
192 t.update(c(10.0, 15.0, 5.0, 12.0, 0));
193 t.update(c(11.0, 13.0, 8.0, 12.0, 1));
194 t.update(c(9.0, 12.0, 6.0, 9.0, 2));
195 assert!(t.is_ready());
196 t.reset();
197 assert!(!t.is_ready());
198 assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), None);
199 }
200}