1use crate::indicators::patterns::candle_settings::*;
2use crate::traits::Next;
3
4macro_rules! real_body_gap_up {
5 ($curr:expr, $prev:expr) => {
6 $curr.open.min($curr.close) > $prev.open.max($prev.close)
7 };
8}
9
10macro_rules! real_body_gap_down {
11 ($curr:expr, $prev:expr) => {
12 $curr.open.max($curr.close) < $prev.open.min($prev.close)
13 };
14}
15
16#[derive(Debug, Clone)]
18#[allow(non_camel_case_types)]
19pub struct CDLENGULFING {
20 bars_seen: usize,
21 window: CandleWindow,
22}
23impl CDLENGULFING {
24 pub fn new() -> Self {
25 Self::default()
26 }
27}
28impl Default for CDLENGULFING {
29 fn default() -> Self {
30 Self {
31 bars_seen: 0,
32 window: CandleWindow::new(2),
33 }
34 }
35}
36impl Next<(f64, f64, f64, f64)> for CDLENGULFING {
37 type Output = f64;
38 #[inline]
39 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
40 self.window.push(open, high, low, close);
41 self.bars_seen += 1;
42 if self.bars_seen <= 1 {
43 return 0.0;
44 }
45
46 let curr = self.window.bar(0);
47 let prev = self.window.bar(1);
48
49 let bull = candle_color(prev.open, prev.close) == -1
50 && candle_color(curr.open, curr.close) == 1
51 && curr.close >= prev.open
52 && curr.open <= prev.close;
53
54 let bear = candle_color(prev.open, prev.close) == 1
55 && candle_color(curr.open, curr.close) == -1
56 && curr.open >= prev.close
57 && curr.close <= prev.open;
58
59 (bull as i32 * 100 - bear as i32 * 100) as f64
60 }
61}
62
63#[derive(Debug, Clone)]
65#[allow(non_camel_case_types)]
66pub struct CDLCOUNTERATTACK {
67 bars_seen: usize,
68 window: CandleWindow,
69 equal: RollingCandleAvg,
70 body_long: RollingCandleAvg,
71}
72impl CDLCOUNTERATTACK {
73 pub fn new() -> Self {
74 Self::default()
75 }
76}
77impl Default for CDLCOUNTERATTACK {
78 fn default() -> Self {
79 Self {
80 bars_seen: 0,
81 window: CandleWindow::new(2),
82 equal: RollingCandleAvg::new(EQUAL),
83 body_long: RollingCandleAvg::new(BODY_LONG),
84 }
85 }
86}
87impl Next<(f64, f64, f64, f64)> for CDLCOUNTERATTACK {
88 type Output = f64;
89 #[inline]
90 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
91 self.window.push(open, high, low, close);
92 self.equal.push(open, high, low, close);
93 self.body_long.push(open, high, low, close);
94 self.bars_seen += 1;
95
96 let lookback = EQUAL.avg_period.max(BODY_LONG.avg_period) + 1;
97 if self.bars_seen <= lookback {
98 return 0.0;
99 }
100
101 let curr = self.window.bar(0);
102 let prev = self.window.bar(1);
103
104 let is_counterattack = candle_color(prev.open, prev.close)
105 != candle_color(curr.open, curr.close)
106 && real_body(prev.open, prev.close) > self.body_long.val(1)
107 && real_body(curr.open, curr.close) > self.body_long.val(0)
108 && (curr.close - prev.close).abs() <= self.equal.val(1);
109
110 (is_counterattack as i32 * candle_color(curr.open, curr.close) * 100) as f64
111 }
112}
113
114#[derive(Debug, Clone)]
116#[allow(non_camel_case_types)]
117pub struct CDLDARKCLOUDCOVER {
118 bars_seen: usize,
119 window: CandleWindow,
120 body_long: RollingCandleAvg,
121}
122impl CDLDARKCLOUDCOVER {
123 pub fn new() -> Self {
124 Self::default()
125 }
126}
127impl Default for CDLDARKCLOUDCOVER {
128 fn default() -> Self {
129 Self {
130 bars_seen: 0,
131 window: CandleWindow::new(2),
132 body_long: RollingCandleAvg::new(BODY_LONG),
133 }
134 }
135}
136impl Next<(f64, f64, f64, f64)> for CDLDARKCLOUDCOVER {
137 type Output = f64;
138 #[inline]
139 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
140 self.window.push(open, high, low, close);
141 self.body_long.push(open, high, low, close);
142 self.bars_seen += 1;
143
144 let lookback = BODY_LONG.avg_period + 1;
145 if self.bars_seen <= lookback {
146 return 0.0;
147 }
148
149 let curr = self.window.bar(0);
150 let prev = self.window.bar(1);
151 let penetration = 0.5;
152
153 let is_dark_cloud_cover = candle_color(prev.open, prev.close) == 1
154 && real_body(prev.open, prev.close) > self.body_long.val(1)
155 && candle_color(curr.open, curr.close) == -1
156 && curr.open > prev.high
157 && curr.close > prev.open
158 && curr.close < prev.close - real_body(prev.open, prev.close) * penetration;
159
160 (is_dark_cloud_cover as i32 * -100) as f64
161 }
162}
163
164#[derive(Debug, Clone)]
166#[allow(non_camel_case_types)]
167pub struct CDLDOJISTAR {
168 bars_seen: usize,
169 window: CandleWindow,
170 body_long: RollingCandleAvg,
171 body_doji: RollingCandleAvg,
172}
173impl CDLDOJISTAR {
174 pub fn new() -> Self {
175 Self::default()
176 }
177}
178impl Default for CDLDOJISTAR {
179 fn default() -> Self {
180 Self {
181 bars_seen: 0,
182 window: CandleWindow::new(2),
183 body_long: RollingCandleAvg::new(BODY_LONG),
184 body_doji: RollingCandleAvg::new(BODY_DOJI),
185 }
186 }
187}
188impl Next<(f64, f64, f64, f64)> for CDLDOJISTAR {
189 type Output = f64;
190 #[inline]
191 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
192 self.window.push(open, high, low, close);
193 self.body_long.push(open, high, low, close);
194 self.body_doji.push(open, high, low, close);
195 self.bars_seen += 1;
196
197 let lookback = BODY_DOJI.avg_period.max(BODY_LONG.avg_period) + 1;
198 if self.bars_seen <= lookback {
199 return 0.0;
200 }
201
202 let curr = self.window.bar(0);
203 let prev = self.window.bar(1);
204
205 let base = real_body(prev.open, prev.close) > self.body_long.val(1)
206 && real_body(curr.open, curr.close) <= self.body_doji.val(0);
207
208 let bear =
209 base && candle_color(prev.open, prev.close) == 1 && real_body_gap_up!(curr, prev);
210 let bull =
211 base && candle_color(prev.open, prev.close) == -1 && real_body_gap_down!(curr, prev);
212
213 (bull as i32 * 100 - bear as i32 * 100) as f64
214 }
215}
216
217#[derive(Debug, Clone)]
219#[allow(non_camel_case_types)]
220pub struct CDLHANGINGMAN {
221 bars_seen: usize,
222 window: CandleWindow,
223 body_short: RollingCandleAvg,
224 shadow_long: RollingCandleAvg,
225 shadow_very_short: RollingCandleAvg,
226 near: RollingCandleAvg,
227}
228impl CDLHANGINGMAN {
229 pub fn new() -> Self {
230 Self::default()
231 }
232}
233impl Default for CDLHANGINGMAN {
234 fn default() -> Self {
235 Self {
236 bars_seen: 0,
237 window: CandleWindow::new(2),
238 body_short: RollingCandleAvg::new(BODY_SHORT),
239 shadow_long: RollingCandleAvg::new(SHADOW_LONG),
240 shadow_very_short: RollingCandleAvg::new(SHADOW_VERY_SHORT),
241 near: RollingCandleAvg::new(NEAR),
242 }
243 }
244}
245impl Next<(f64, f64, f64, f64)> for CDLHANGINGMAN {
246 type Output = f64;
247 #[inline]
248 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
249 self.window.push(open, high, low, close);
250 self.body_short.push(open, high, low, close);
251 self.shadow_long.push(open, high, low, close);
252 self.shadow_very_short.push(open, high, low, close);
253 self.near.push(open, high, low, close);
254 self.bars_seen += 1;
255
256 let lookback = BODY_SHORT
258 .avg_period
259 .max(SHADOW_VERY_SHORT.avg_period)
260 .max(NEAR.avg_period)
261 + 1;
262 if self.bars_seen <= lookback {
263 return 0.0;
264 }
265
266 let curr = self.window.bar(0);
267 let prev = self.window.bar(1);
268
269 let is_hanging_man = real_body(curr.open, curr.close) < self.body_short.val(0)
270 && lower_shadow(curr.open, curr.low, curr.close) > self.shadow_long.val(0)
271 && upper_shadow(curr.open, curr.high, curr.close) < self.shadow_very_short.val(0)
272 && curr.open.min(curr.close) >= prev.high - self.near.val(1);
273
274 (is_hanging_man as i32 * -100) as f64
275 }
276}
277
278#[derive(Debug, Clone)]
280#[allow(non_camel_case_types)]
281pub struct CDLHARAMI {
282 bars_seen: usize,
283 window: CandleWindow,
284 body_long: RollingCandleAvg,
285 body_short: RollingCandleAvg,
286}
287impl CDLHARAMI {
288 pub fn new() -> Self {
289 Self::default()
290 }
291}
292impl Default for CDLHARAMI {
293 fn default() -> Self {
294 Self {
295 bars_seen: 0,
296 window: CandleWindow::new(2),
297 body_long: RollingCandleAvg::new(BODY_LONG),
298 body_short: RollingCandleAvg::new(BODY_SHORT),
299 }
300 }
301}
302impl Next<(f64, f64, f64, f64)> for CDLHARAMI {
303 type Output = f64;
304 #[inline]
305 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
306 self.window.push(open, high, low, close);
307 self.body_long.push(open, high, low, close);
308 self.body_short.push(open, high, low, close);
309 self.bars_seen += 1;
310
311 let lookback = BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1;
312 if self.bars_seen <= lookback {
313 return 0.0;
314 }
315
316 let curr = self.window.bar(0);
317 let prev = self.window.bar(1);
318
319 let is_harami = real_body(prev.open, prev.close) > self.body_long.val(1)
320 && real_body(curr.open, curr.close) <= self.body_short.val(0)
321 && curr.open.max(curr.close) < prev.open.max(prev.close)
322 && curr.open.min(curr.close) > prev.open.min(prev.close);
323
324 (is_harami as i32 * -candle_color(prev.open, prev.close) * 100) as f64
325 }
326}
327
328#[derive(Debug, Clone)]
330#[allow(non_camel_case_types)]
331pub struct CDLHARAMICROSS {
332 bars_seen: usize,
333 window: CandleWindow,
334 body_long: RollingCandleAvg,
335 body_doji: RollingCandleAvg,
336}
337impl CDLHARAMICROSS {
338 pub fn new() -> Self {
339 Self::default()
340 }
341}
342impl Default for CDLHARAMICROSS {
343 fn default() -> Self {
344 Self {
345 bars_seen: 0,
346 window: CandleWindow::new(2),
347 body_long: RollingCandleAvg::new(BODY_LONG),
348 body_doji: RollingCandleAvg::new(BODY_DOJI),
349 }
350 }
351}
352impl Next<(f64, f64, f64, f64)> for CDLHARAMICROSS {
353 type Output = f64;
354 #[inline]
355 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
356 self.window.push(open, high, low, close);
357 self.body_long.push(open, high, low, close);
358 self.body_doji.push(open, high, low, close);
359 self.bars_seen += 1;
360
361 let lookback = BODY_DOJI.avg_period.max(BODY_LONG.avg_period) + 1;
362 if self.bars_seen <= lookback {
363 return 0.0;
364 }
365
366 let curr = self.window.bar(0);
367 let prev = self.window.bar(1);
368
369 let is_harami_cross = real_body(prev.open, prev.close) > self.body_long.val(1)
370 && real_body(curr.open, curr.close) <= self.body_doji.val(0)
371 && curr.open.max(curr.close) < prev.open.max(prev.close)
372 && curr.open.min(curr.close) > prev.open.min(prev.close);
373
374 (is_harami_cross as i32 * -candle_color(prev.open, prev.close) * 100) as f64
375 }
376}
377
378#[derive(Debug, Clone)]
380#[allow(non_camel_case_types)]
381pub struct CDLHOMINGPIGEON {
382 bars_seen: usize,
383 window: CandleWindow,
384 body_long: RollingCandleAvg,
385 body_short: RollingCandleAvg,
386}
387impl CDLHOMINGPIGEON {
388 pub fn new() -> Self {
389 Self::default()
390 }
391}
392impl Default for CDLHOMINGPIGEON {
393 fn default() -> Self {
394 Self {
395 bars_seen: 0,
396 window: CandleWindow::new(2),
397 body_long: RollingCandleAvg::new(BODY_LONG),
398 body_short: RollingCandleAvg::new(BODY_SHORT),
399 }
400 }
401}
402impl Next<(f64, f64, f64, f64)> for CDLHOMINGPIGEON {
403 type Output = f64;
404 #[inline]
405 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
406 self.window.push(open, high, low, close);
407 self.body_long.push(open, high, low, close);
408 self.body_short.push(open, high, low, close);
409 self.bars_seen += 1;
410
411 let lookback = BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1;
412 if self.bars_seen <= lookback {
413 return 0.0;
414 }
415
416 let curr = self.window.bar(0);
417 let prev = self.window.bar(1);
418
419 let is_homing_pigeon = candle_color(prev.open, prev.close) == -1
420 && candle_color(curr.open, curr.close) == -1
421 && real_body(prev.open, prev.close) > self.body_long.val(1)
422 && real_body(curr.open, curr.close) <= self.body_short.val(0)
423 && curr.open < prev.open
424 && curr.close > prev.close;
425
426 (is_homing_pigeon as i32 * 100) as f64
427 }
428}
429
430#[derive(Debug, Clone)]
432#[allow(non_camel_case_types)]
433pub struct CDLINNECK {
434 bars_seen: usize,
435 window: CandleWindow,
436 equal: RollingCandleAvg,
437 body_long: RollingCandleAvg,
438}
439impl CDLINNECK {
440 pub fn new() -> Self {
441 Self::default()
442 }
443}
444impl Default for CDLINNECK {
445 fn default() -> Self {
446 Self {
447 bars_seen: 0,
448 window: CandleWindow::new(2),
449 equal: RollingCandleAvg::new(EQUAL),
450 body_long: RollingCandleAvg::new(BODY_LONG),
451 }
452 }
453}
454impl Next<(f64, f64, f64, f64)> for CDLINNECK {
455 type Output = f64;
456 #[inline]
457 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
458 self.window.push(open, high, low, close);
459 self.equal.push(open, high, low, close);
460 self.body_long.push(open, high, low, close);
461 self.bars_seen += 1;
462
463 let lookback = EQUAL.avg_period.max(BODY_LONG.avg_period) + 1;
464 if self.bars_seen <= lookback {
465 return 0.0;
466 }
467
468 let curr = self.window.bar(0);
469 let prev = self.window.bar(1);
470
471 let is_inneck = candle_color(prev.open, prev.close) == -1
472 && real_body(prev.open, prev.close) > self.body_long.val(1)
473 && candle_color(curr.open, curr.close) == 1
474 && curr.open < prev.low
475 && curr.close >= prev.close
476 && curr.close <= prev.close + self.equal.val(1);
477
478 (is_inneck as i32 * -100) as f64
479 }
480}
481
482#[derive(Debug, Clone)]
484#[allow(non_camel_case_types)]
485pub struct CDLINVERTEDHAMMER {
486 bars_seen: usize,
487 window: CandleWindow,
488 body_short: RollingCandleAvg,
489 shadow_long: RollingCandleAvg,
490 shadow_very_short: RollingCandleAvg,
491}
492impl CDLINVERTEDHAMMER {
493 pub fn new() -> Self {
494 Self::default()
495 }
496}
497impl Default for CDLINVERTEDHAMMER {
498 fn default() -> Self {
499 Self {
500 bars_seen: 0,
501 window: CandleWindow::new(2),
502 body_short: RollingCandleAvg::new(BODY_SHORT),
503 shadow_long: RollingCandleAvg::new(SHADOW_LONG),
504 shadow_very_short: RollingCandleAvg::new(SHADOW_VERY_SHORT),
505 }
506 }
507}
508impl Next<(f64, f64, f64, f64)> for CDLINVERTEDHAMMER {
509 type Output = f64;
510 #[inline]
511 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
512 self.window.push(open, high, low, close);
513 self.body_short.push(open, high, low, close);
514 self.shadow_long.push(open, high, low, close);
515 self.shadow_very_short.push(open, high, low, close);
516 self.bars_seen += 1;
517
518 let lookback = BODY_SHORT.avg_period.max(SHADOW_VERY_SHORT.avg_period) + 1;
520 if self.bars_seen <= lookback {
521 return 0.0;
522 }
523
524 let curr = self.window.bar(0);
525 let prev = self.window.bar(1);
526
527 let is_inverted_hammer = real_body(curr.open, curr.close) < self.body_short.val(0)
528 && upper_shadow(curr.open, curr.high, curr.close) > self.shadow_long.val(0)
529 && lower_shadow(curr.open, curr.low, curr.close) < self.shadow_very_short.val(0)
530 && real_body_gap_down!(curr, prev);
531
532 (is_inverted_hammer as i32 * 100) as f64
533 }
534}
535
536#[derive(Debug, Clone)]
538#[allow(non_camel_case_types)]
539pub struct CDLMATCHINGLOW {
540 bars_seen: usize,
541 window: CandleWindow,
542 equal: RollingCandleAvg,
543}
544impl CDLMATCHINGLOW {
545 pub fn new() -> Self {
546 Self::default()
547 }
548}
549impl Default for CDLMATCHINGLOW {
550 fn default() -> Self {
551 Self {
552 bars_seen: 0,
553 window: CandleWindow::new(2),
554 equal: RollingCandleAvg::new(EQUAL),
555 }
556 }
557}
558impl Next<(f64, f64, f64, f64)> for CDLMATCHINGLOW {
559 type Output = f64;
560 #[inline]
561 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
562 self.window.push(open, high, low, close);
563 self.equal.push(open, high, low, close);
564 self.bars_seen += 1;
565
566 let lookback = EQUAL.avg_period + 1;
567 if self.bars_seen <= lookback {
568 return 0.0;
569 }
570
571 let curr = self.window.bar(0);
572 let prev = self.window.bar(1);
573
574 let is_matching_low = candle_color(prev.open, prev.close) == -1
575 && candle_color(curr.open, curr.close) == -1
576 && (curr.close - prev.close).abs() <= self.equal.val(1);
577
578 (is_matching_low as i32 * 100) as f64
579 }
580}
581
582#[derive(Debug, Clone)]
584#[allow(non_camel_case_types)]
585pub struct CDLONNECK {
586 bars_seen: usize,
587 window: CandleWindow,
588 equal: RollingCandleAvg,
589 body_long: RollingCandleAvg,
590}
591impl CDLONNECK {
592 pub fn new() -> Self {
593 Self::default()
594 }
595}
596impl Default for CDLONNECK {
597 fn default() -> Self {
598 Self {
599 bars_seen: 0,
600 window: CandleWindow::new(2),
601 equal: RollingCandleAvg::new(EQUAL),
602 body_long: RollingCandleAvg::new(BODY_LONG),
603 }
604 }
605}
606impl Next<(f64, f64, f64, f64)> for CDLONNECK {
607 type Output = f64;
608 #[inline]
609 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
610 self.window.push(open, high, low, close);
611 self.equal.push(open, high, low, close);
612 self.body_long.push(open, high, low, close);
613 self.bars_seen += 1;
614
615 let lookback = EQUAL.avg_period.max(BODY_LONG.avg_period) + 1;
616 if self.bars_seen <= lookback {
617 return 0.0;
618 }
619
620 let curr = self.window.bar(0);
621 let prev = self.window.bar(1);
622
623 let is_onneck = candle_color(prev.open, prev.close) == -1
624 && real_body(prev.open, prev.close) > self.body_long.val(1)
625 && candle_color(curr.open, curr.close) == 1
626 && curr.open < prev.low
627 && (curr.close - prev.low).abs() <= self.equal.val(1);
628
629 (is_onneck as i32 * -100) as f64
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636 use crate::indicators::patterns::fixtures as fx;
637
638 crate::test_pattern_parity!(
639 test_cdl_engulfing_parity,
640 CDLENGULFING,
641 talib_rs::pattern::cdl_engulfing
642 );
643 crate::test_pattern_parity!(
644 test_cdl_counterattack_parity,
645 CDLCOUNTERATTACK,
646 talib_rs::pattern::cdl_counterattack,
647 fx::counterattack
648 );
649 crate::test_pattern_parity!(
650 test_cdl_darkcloudcover_parity,
651 CDLDARKCLOUDCOVER,
652 talib_rs::pattern::cdl_darkcloudcover,
653 fx::darkcloudcover
654 );
655 crate::test_pattern_parity!(
656 test_cdl_dojistar_parity,
657 CDLDOJISTAR,
658 talib_rs::pattern::cdl_dojistar,
659 fx::dojistar
660 );
661 crate::test_pattern_parity!(
662 test_cdl_hangingman_parity,
663 CDLHANGINGMAN,
664 talib_rs::pattern::cdl_hangingman
665 );
666 crate::test_pattern_parity!(
667 test_cdl_harami_parity,
668 CDLHARAMI,
669 talib_rs::pattern::cdl_harami,
670 fx::harami
671 );
672 crate::test_pattern_parity!(
673 test_cdl_haramicross_parity,
674 CDLHARAMICROSS,
675 talib_rs::pattern::cdl_haramicross,
676 fx::haramicross
677 );
678 crate::test_pattern_parity!(
679 test_cdl_homingpigeon_parity,
680 CDLHOMINGPIGEON,
681 talib_rs::pattern::cdl_homingpigeon,
682 fx::homingpigeon
683 );
684 crate::test_pattern_parity!(
685 test_cdl_inneck_parity,
686 CDLINNECK,
687 talib_rs::pattern::cdl_inneck,
688 fx::inneck
689 );
690 crate::test_pattern_parity!(
691 test_cdl_invertedhammer_parity,
692 CDLINVERTEDHAMMER,
693 talib_rs::pattern::cdl_invertedhammer,
694 fx::invertedhammer
695 );
696 crate::test_pattern_parity!(
697 test_cdl_matchinglow_parity,
698 CDLMATCHINGLOW,
699 talib_rs::pattern::cdl_matchinglow
700 );
701 crate::test_pattern_parity!(
702 test_cdl_onneck_parity,
703 CDLONNECK,
704 talib_rs::pattern::cdl_onneck,
705 fx::onneck
706 );
707}