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