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 fn update(&mut self, candle: Candle) -> Option<f64> {
82 self.has_emitted = true;
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 Some(0.0);
91 };
92 if !black_marubozu(bar1) || !black_marubozu(bar2) {
94 return Some(0.0);
95 }
96 if bar3.open <= bar3.close {
98 return Some(0.0);
99 }
100 if bar3.open >= bar2.close {
101 return Some(0.0); }
103 if bar3.high <= bar2.close {
104 return Some(0.0); }
106 if candle.open <= candle.close {
108 return Some(0.0);
109 }
110 if candle.open > bar3.high && candle.close < bar3.low {
111 return Some(1.0);
112 }
113 Some(0.0)
114 }
115
116 fn reset(&mut self) {
117 self.c1 = None;
118 self.c2 = None;
119 self.c3 = None;
120 self.has_emitted = false;
121 }
122
123 fn warmup_period(&self) -> usize {
124 4
125 }
126
127 fn is_ready(&self) -> bool {
128 self.has_emitted
129 }
130
131 fn name(&self) -> &'static str {
132 "ConcealingBabySwallow"
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::traits::BatchExt;
140
141 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
142 Candle::new(open, high, low, close, 1.0, ts).unwrap()
143 }
144
145 #[test]
146 fn accessors_and_metadata() {
147 let t = ConcealingBabySwallow::new();
148 assert_eq!(t.name(), "ConcealingBabySwallow");
149 assert_eq!(t.warmup_period(), 4);
150 assert!(!t.is_ready());
151 }
152
153 #[test]
154 fn concealing_baby_swallow_is_plus_one() {
155 let mut t = ConcealingBabySwallow::new();
156 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
157 assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 1)), Some(0.0));
158 assert_eq!(t.update(c(11.0, 13.0, 9.9, 10.0, 2)), Some(0.0));
159 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(1.0));
160 }
161
162 #[test]
163 fn warmup_returns_zero() {
164 let mut t = ConcealingBabySwallow::new();
165 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
166 assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 1)), Some(0.0));
167 assert_eq!(t.update(c(11.0, 13.0, 9.9, 10.0, 2)), Some(0.0));
168 }
169
170 #[test]
171 fn first_bar_not_marubozu_yields_zero() {
172 let mut t = ConcealingBabySwallow::new();
173 t.update(c(15.0, 20.1, 14.9, 20.0, 0));
175 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
176 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
177 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
178 }
179
180 #[test]
181 fn first_bar_zero_range_yields_zero() {
182 let mut t = ConcealingBabySwallow::new();
183 t.update(c(15.0, 15.0, 15.0, 15.0, 0));
185 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
186 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
187 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
188 }
189
190 #[test]
191 fn second_bar_not_marubozu_yields_zero() {
192 let mut t = ConcealingBabySwallow::new();
193 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
194 t.update(c(12.0, 16.1, 11.9, 16.0, 1));
196 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
197 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
198 }
199
200 #[test]
201 fn third_bar_not_black_yields_zero() {
202 let mut t = ConcealingBabySwallow::new();
203 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
204 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
205 t.update(c(11.0, 13.0, 9.9, 12.5, 2));
207 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
208 }
209
210 #[test]
211 fn third_bar_no_gap_yields_zero() {
212 let mut t = ConcealingBabySwallow::new();
213 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
214 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
215 t.update(c(12.5, 13.0, 9.9, 10.0, 2));
217 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
218 }
219
220 #[test]
221 fn third_bar_no_upper_shadow_yields_zero() {
222 let mut t = ConcealingBabySwallow::new();
223 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
224 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
225 t.update(c(11.0, 11.5, 9.9, 10.0, 2));
227 assert_eq!(t.update(c(14.0, 14.1, 8.9, 9.0, 3)), Some(0.0));
228 }
229
230 #[test]
231 fn fourth_bar_not_black_yields_zero() {
232 let mut t = ConcealingBabySwallow::new();
233 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
234 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
235 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
236 assert_eq!(t.update(c(14.0, 14.1, 8.9, 14.05, 3)), Some(0.0));
238 }
239
240 #[test]
241 fn fourth_bar_not_engulfing_yields_zero() {
242 let mut t = ConcealingBabySwallow::new();
243 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
244 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
245 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
246 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 3)), Some(0.0));
248 }
249
250 #[test]
251 fn batch_equals_streaming() {
252 let candles: Vec<Candle> = (0..40)
253 .map(|i| {
254 let base = 200.0 - i as f64;
255 c(base, base + 0.05, base - 5.0, base - 5.0, i)
256 })
257 .collect();
258 let mut a = ConcealingBabySwallow::new();
259 let mut b = ConcealingBabySwallow::new();
260 assert_eq!(
261 a.batch(&candles),
262 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
263 );
264 }
265
266 #[test]
267 fn reset_clears_state() {
268 let mut t = ConcealingBabySwallow::new();
269 t.update(c(20.0, 20.1, 14.9, 15.0, 0));
270 t.update(c(16.0, 16.1, 11.9, 12.0, 1));
271 t.update(c(11.0, 13.0, 9.9, 10.0, 2));
272 t.update(c(14.0, 14.1, 8.9, 9.0, 3));
273 assert!(t.is_ready());
274 t.reset();
275 assert!(!t.is_ready());
276 assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
277 }
278}