Skip to main content

quantwave_core/indicators/patterns/
batch_b.rs

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/// Native O(1) streaming CDLENGULFING (matches `talib_rs::pattern::cdl_engulfing`).
17#[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/// Native O(1) streaming CDLCOUNTERATTACK (matches `talib_rs::pattern::cdl_counterattack`).
64#[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/// Native O(1) streaming CDLDARKCLOUDCOVER (matches `talib_rs::pattern::cdl_darkcloudcover`).
115#[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/// Native O(1) streaming CDLDOJISTAR (matches `talib_rs::pattern::cdl_dojistar`).
165#[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/// Native O(1) streaming CDLHANGINGMAN (matches `talib_rs::pattern::cdl_hangingman`).
218#[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        // SHADOW_LONG.avg_period is 0 and cannot raise the max.
257        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/// Native O(1) streaming CDLHARAMI (matches `talib_rs::pattern::cdl_harami`).
279#[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/// Native O(1) streaming CDLHARAMICROSS (matches `talib_rs::pattern::cdl_haramicross`).
329#[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/// Native O(1) streaming CDLHOMINGPIGEON (matches `talib_rs::pattern::cdl_homingpigeon`).
379#[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/// Native O(1) streaming CDLINNECK (matches `talib_rs::pattern::cdl_inneck`).
431#[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/// Native O(1) streaming CDLINVERTEDHAMMER (matches `talib_rs::pattern::cdl_invertedhammer`).
483#[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        // SHADOW_LONG.avg_period is 0 and cannot raise the max.
519        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/// Native O(1) streaming CDLMATCHINGLOW (matches `talib_rs::pattern::cdl_matchinglow`).
537#[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/// Native O(1) streaming CDLONNECK (matches `talib_rs::pattern::cdl_onneck`).
583#[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}