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