1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6fn black_marubozu(candle: Candle) -> bool {
9 let range = candle.high - candle.low;
10 if range <= 0.0 {
11 return false;
12 }
13 let upper = candle.high - candle.open;
14 let lower = candle.close - candle.low;
15 candle.open > candle.close && upper <= 0.05 * range && lower <= 0.05 * range
16}
17
18#[derive(Debug, Clone, Default)]
58pub struct ConcealingBabySwallow {
59 c1: Option<Candle>,
60 c2: Option<Candle>,
61 c3: Option<Candle>,
62 has_emitted: bool,
63}
64
65impl ConcealingBabySwallow {
66 pub const fn new() -> Self {
68 Self {
69 c1: None,
70 c2: None,
71 c3: None,
72 has_emitted: false,
73 }
74 }
75}
76
77impl Indicator for ConcealingBabySwallow {
78 type Input = Candle;
79 type Output = f64;
80
81 #[inline]
82 fn update(&mut self, candle: Candle) -> Option<f64> {
83 let bar1 = self.c1;
84 let bar2 = self.c2;
85 let bar3 = self.c3;
86 self.c1 = self.c2;
87 self.c2 = self.c3;
88 self.c3 = Some(candle);
89 let (Some(bar1), Some(bar2), Some(bar3)) = (bar1, bar2, bar3) else {
90 return None;
91 };
92 self.has_emitted = true;
93 if !black_marubozu(bar1) || !black_marubozu(bar2) {
95 return Some(0.0);
96 }
97 if bar3.open <= bar3.close {
99 return Some(0.0);
100 }
101 if bar3.open >= bar2.close {
102 return Some(0.0); }
104 if bar3.high <= bar2.close {
105 return Some(0.0); }
107 if candle.open <= candle.close {
109 return Some(0.0);
110 }
111 if candle.open > bar3.high && candle.close < bar3.low {
112 return Some(1.0);
113 }
114 Some(0.0)
115 }
116
117 fn reset(&mut self) {
118 self.c1 = None;
119 self.c2 = None;
120 self.c3 = None;
121 self.has_emitted = false;
122 }
123
124 #[inline]
125 fn warmup_period(&self) -> usize {
126 4
127 }
128
129 #[inline]
130 fn is_ready(&self) -> bool {
131 self.has_emitted
132 }
133
134 #[inline]
135 fn name(&self) -> &'static str {
136 "ConcealingBabySwallow"
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::traits::BatchExt;
144
145 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
146 Candle::new(open, high, low, close, 1.0, ts).unwrap()
147 }
148
149 #[test]
150 fn accessors_and_metadata() {
151 let t = ConcealingBabySwallow::new();
152 assert_eq!(t.name(), "ConcealingBabySwallow");
153 assert_eq!(t.warmup_period(), 4);
154 assert!(!t.is_ready());
155 }
156
157 #[test]
158 fn concealing_baby_swallow_is_plus_one() {
159 let mut t = ConcealingBabySwallow::new();
160 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), None);
161 assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 1)), None);
162 assert_eq!(t.update(c(11.0, 13.0, 9.9, 10.0, 2)), None);
163 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(1.0));
164 }
165
166 #[test]
167 fn warmup_returns_zero() {
168 let mut t = ConcealingBabySwallow::new();
169 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), None);
170 assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 1)), None);
171 assert_eq!(t.update(c(11.0, 13.0, 9.9, 10.0, 2)), None);
172 }
173
174 #[test]
175 fn first_bar_not_marubozu_yields_zero() {
176 let mut t = ConcealingBabySwallow::new();
177 t.update(c(15.0, 20.1, 14.9, 20.0, 0));
179 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
180 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
181 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
182 }
183
184 #[test]
185 fn first_bar_zero_range_yields_zero() {
186 let mut t = ConcealingBabySwallow::new();
187 t.update(c(15.0, 15.0, 15.0, 15.0, 0));
189 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
190 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
191 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
192 }
193
194 #[test]
195 fn second_bar_not_marubozu_yields_zero() {
196 let mut t = ConcealingBabySwallow::new();
197 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
198 t.update(c(12.0, 16.1, 11.9, 16.0, 1));
200 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
201 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
202 }
203
204 #[test]
205 fn third_bar_not_black_yields_zero() {
206 let mut t = ConcealingBabySwallow::new();
207 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
208 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
209 t.update(c(11.0, 13.0, 9.9, 12.5, 2));
211 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
212 }
213
214 #[test]
215 fn third_bar_no_gap_yields_zero() {
216 let mut t = ConcealingBabySwallow::new();
217 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
218 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
219 t.update(c(12.5, 13.0, 9.9, 10.0, 2));
221 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
222 }
223
224 #[test]
225 fn third_bar_no_upper_shadow_yields_zero() {
226 let mut t = ConcealingBabySwallow::new();
227 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
228 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
229 t.update(c(11.0, 11.5, 9.9, 10.0, 2));
231 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
232 }
233
234 #[test]
235 fn fourth_bar_not_black_yields_zero() {
236 let mut t = ConcealingBabySwallow::new();
237 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
238 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
239 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
240 assert_eq!(t.update(c(14.0, 14.1, 8.9, 14.05, 3)), Some(0.0));
242 }
243
244 #[test]
245 fn fourth_bar_not_engulfing_yields_zero() {
246 let mut t = ConcealingBabySwallow::new();
247 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
248 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
249 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
250 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 3)), Some(0.0));
252 }
253
254 #[test]
255 fn batch_equals_streaming() {
256 let candles: Vec<Candle> = (0..40)
257 .map(|i| {
258 let base = 200.0 - i as f64;
259 c(base, base + 0.05, base - 5.0, base - 5.0, i)
260 })
261 .collect();
262 let mut a = ConcealingBabySwallow::new();
263 let mut b = ConcealingBabySwallow::new();
264 assert_eq!(
265 a.batch(&candles),
266 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
267 );
268 }
269
270 #[test]
271 fn reset_clears_state() {
272 let mut t = ConcealingBabySwallow::new();
273 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
274 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
275 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
276 t.update(c(14.0, 14.1, 8.9, 9.0, 3));
277 assert!(t.is_ready());
278 t.reset();
279 assert!(!t.is_ready());
280 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), None);
281 }
282}