1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
43pub struct OnNeck {
44 prev: Option<Candle>,
45 has_emitted: bool,
46}
47
48impl OnNeck {
49 pub const fn new() -> Self {
51 Self {
52 prev: None,
53 has_emitted: false,
54 }
55 }
56}
57
58impl Indicator for OnNeck {
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 if bar1.close < bar1.open
73 && (bar1.open - bar1.close) >= 0.5 * range1
74 && candle.close > candle.open
75 && candle.open < bar1.low
76 && (candle.close - bar1.low).abs() <= 0.05 * range1
77 {
78 return Some(-1.0);
79 }
80 Some(0.0)
81 }
82
83 fn reset(&mut self) {
84 self.prev = None;
85 self.has_emitted = false;
86 }
87
88 #[inline]
89 fn warmup_period(&self) -> usize {
90 2
91 }
92
93 #[inline]
94 fn is_ready(&self) -> bool {
95 self.has_emitted
96 }
97
98 #[inline]
99 fn name(&self) -> &'static str {
100 "OnNeck"
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 = OnNeck::new();
116 assert_eq!(t.name(), "OnNeck");
117 assert_eq!(t.warmup_period(), 2);
118 assert!(!t.is_ready());
119 }
120
121 #[test]
122 fn on_neck_is_minus_one() {
123 let mut t = OnNeck::new();
124 assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), None);
125 assert_eq!(t.update(c(7.0, 9.1, 6.9, 9.0, 1)), Some(-1.0));
126 }
127
128 #[test]
129 fn close_into_body_yields_zero() {
130 let mut t = OnNeck::new();
131 t.update(c(15.0, 15.1, 9.0, 10.0, 0));
132 assert_eq!(t.update(c(7.0, 10.2, 6.9, 10.1, 1)), Some(0.0));
134 }
135
136 #[test]
137 fn second_bar_black_yields_zero() {
138 let mut t = OnNeck::new();
139 t.update(c(15.0, 15.1, 9.0, 10.0, 0));
140 assert_eq!(t.update(c(9.5, 9.6, 6.9, 9.0, 1)), Some(0.0));
141 }
142
143 #[test]
144 fn opens_above_low_yields_zero() {
145 let mut t = OnNeck::new();
146 t.update(c(15.0, 15.1, 9.0, 10.0, 0));
147 assert_eq!(t.update(c(9.5, 10.1, 9.4, 10.0, 1)), Some(0.0));
149 }
150
151 #[test]
152 fn first_bar_returns_zero() {
153 let mut t = OnNeck::new();
154 assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), None);
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 = OnNeck::new();
166 let mut b = OnNeck::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 = OnNeck::new();
176 t.update(c(15.0, 15.1, 9.0, 10.0, 0));
177 t.update(c(7.0, 9.1, 6.9, 9.0, 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)), None);
182 }
183
184 #[test]
185 fn zero_range_first_bar_yields_zero() {
186 let mut t = OnNeck::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}