Skip to main content

quantwave_core/indicators/patterns/
batch_e.rs

1//! Native O(1) streaming implementations for Batch E patterns.
2//! Matches `talib_rs::pattern::*`.
3
4use crate::indicators::patterns::candle_settings::{
5    BODY_DOJI, BODY_LONG, BODY_SHORT, CandleWindow, EQUAL, NEAR, RollingCandleAvg,
6    SHADOW_VERY_SHORT, candle_color, lower_shadow, real_body, upper_shadow,
7};
8use crate::traits::Next;
9
10#[inline(always)]
11fn real_body_gap_up(window: &CandleWindow, i: usize, j: usize) -> bool {
12    window.bar(i).open.min(window.bar(i).close) > window.bar(j).open.max(window.bar(j).close)
13}
14
15#[inline(always)]
16fn real_body_gap_down(window: &CandleWindow, i: usize, j: usize) -> bool {
17    window.bar(i).open.max(window.bar(i).close) < window.bar(j).open.min(window.bar(j).close)
18}
19
20const CDLGAPSIDESIDEWHITE_LOOKBACK: usize = 5 + 2; // max(NEAR(5), EQUAL(5)) + 2
21
22#[derive(Debug, Clone)]
23#[allow(non_camel_case_types)]
24pub struct CDLGAPSIDESIDEWHITE {
25    bars_seen: usize,
26    window: CandleWindow,
27    near_avg: RollingCandleAvg,
28    equal_avg: RollingCandleAvg,
29}
30impl CDLGAPSIDESIDEWHITE {
31    pub fn new() -> Self {
32        Self {
33            bars_seen: 0,
34            window: CandleWindow::new(3),
35            near_avg: RollingCandleAvg::new(NEAR),
36            equal_avg: RollingCandleAvg::new(EQUAL),
37        }
38    }
39}
40impl Default for CDLGAPSIDESIDEWHITE {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45impl Next<(f64, f64, f64, f64)> for CDLGAPSIDESIDEWHITE {
46    type Output = f64;
47    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
48        self.bars_seen += 1;
49        self.window.push(open, high, low, close);
50        self.near_avg.push(open, high, low, close);
51        self.equal_avg.push(open, high, low, close);
52
53        if self.bars_seen <= CDLGAPSIDESIDEWHITE_LOOKBACK {
54            return 0.0;
55        }
56
57        // Both candles are white, have near-equal real bodies, and open at
58        // near-equal levels. The direction comes solely from which way the
59        // *first* of the pair gapped away from the bar before it.
60        let base = candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
61            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
62            && (real_body(self.window.bar(1).open, self.window.bar(1).close)
63                - real_body(self.window.bar(0).open, self.window.bar(0).close))
64            .abs()
65                < self.near_avg.val(1)
66            && (self.window.bar(1).open - self.window.bar(0).open).abs() < self.equal_avg.val(1);
67
68        let bull = base && real_body_gap_up(&self.window, 1, 2);
69        let bear = base && real_body_gap_down(&self.window, 1, 2);
70
71        if bull {
72            100.0
73        } else if bear {
74            -100.0
75        } else {
76            0.0
77        }
78    }
79}
80
81const CDLIDENTICAL3CROWS_LOOKBACK: usize = 10 + 2; // SHADOW_VERY_SHORT(10), EQUAL(5) + 2 = 12
82
83#[derive(Debug, Clone)]
84#[allow(non_camel_case_types)]
85pub struct CDLIDENTICAL3CROWS {
86    bars_seen: usize,
87    window: CandleWindow,
88    shadow_avg: RollingCandleAvg,
89    equal_avg: RollingCandleAvg,
90}
91impl CDLIDENTICAL3CROWS {
92    pub fn new() -> Self {
93        Self {
94            bars_seen: 0,
95            window: CandleWindow::new(3),
96            shadow_avg: RollingCandleAvg::new(SHADOW_VERY_SHORT),
97            equal_avg: RollingCandleAvg::new(EQUAL),
98        }
99    }
100}
101impl Default for CDLIDENTICAL3CROWS {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106impl Next<(f64, f64, f64, f64)> for CDLIDENTICAL3CROWS {
107    type Output = f64;
108    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
109        self.bars_seen += 1;
110        self.window.push(open, high, low, close);
111        self.shadow_avg.push(open, high, low, close);
112        self.equal_avg.push(open, high, low, close);
113
114        if self.bars_seen <= CDLIDENTICAL3CROWS_LOOKBACK {
115            return 0.0;
116        }
117
118        let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
119            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
120            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
121            && self.window.bar(1).close < self.window.bar(2).close
122            && self.window.bar(0).close < self.window.bar(1).close
123            && lower_shadow(
124                self.window.bar(2).open,
125                self.window.bar(2).low,
126                self.window.bar(2).close,
127            ) < self.shadow_avg.val(2)
128            && lower_shadow(
129                self.window.bar(1).open,
130                self.window.bar(1).low,
131                self.window.bar(1).close,
132            ) < self.shadow_avg.val(1)
133            && lower_shadow(
134                self.window.bar(0).open,
135                self.window.bar(0).low,
136                self.window.bar(0).close,
137            ) < self.shadow_avg.val(0)
138            && (self.window.bar(1).open - self.window.bar(2).close).abs() <= self.equal_avg.val(2)
139            && (self.window.bar(0).open - self.window.bar(1).close).abs() <= self.equal_avg.val(1);
140
141        if cond { -100.0 } else { 0.0 }
142    }
143}
144
145const CDLSTALLEDPATTERN_LOOKBACK: usize = 10 + 2;
146
147#[derive(Debug, Clone)]
148#[allow(non_camel_case_types)]
149pub struct CDLSTALLEDPATTERN {
150    bars_seen: usize,
151    window: CandleWindow,
152    body_long: RollingCandleAvg,
153    body_short: RollingCandleAvg,
154    shadow_vs: RollingCandleAvg,
155    near: RollingCandleAvg,
156}
157impl CDLSTALLEDPATTERN {
158    pub fn new() -> Self {
159        Self {
160            bars_seen: 0,
161            window: CandleWindow::new(3),
162            body_long: RollingCandleAvg::new(BODY_LONG),
163            body_short: RollingCandleAvg::new(BODY_SHORT),
164            shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
165            near: RollingCandleAvg::new(NEAR),
166        }
167    }
168}
169impl Default for CDLSTALLEDPATTERN {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174impl Next<(f64, f64, f64, f64)> for CDLSTALLEDPATTERN {
175    type Output = f64;
176    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
177        self.bars_seen += 1;
178        self.window.push(open, high, low, close);
179        self.body_long.push(open, high, low, close);
180        self.body_short.push(open, high, low, close);
181        self.shadow_vs.push(open, high, low, close);
182        self.near.push(open, high, low, close);
183
184        if self.bars_seen <= CDLSTALLEDPATTERN_LOOKBACK {
185            return 0.0;
186        }
187
188        let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
189            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
190            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
191            && self.window.bar(1).close > self.window.bar(2).close
192            && self.window.bar(0).close > self.window.bar(1).close
193            && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
194            && real_body(self.window.bar(1).open, self.window.bar(1).close) > self.body_long.val(1)
195            && upper_shadow(
196                self.window.bar(1).open,
197                self.window.bar(1).high,
198                self.window.bar(1).close,
199            ) < self.shadow_vs.val(1)
200            && self.window.bar(1).open > self.window.bar(2).open
201            && self.window.bar(1).open <= self.window.bar(2).close + self.near.val(2)
202            && real_body(self.window.bar(0).open, self.window.bar(0).close)
203                < self.body_short.val(0)
204            && self.window.bar(0).open
205                >= self.window.bar(1).close
206                    - real_body(self.window.bar(0).open, self.window.bar(0).close)
207                    - self.near.val(1);
208
209        if cond { -100.0 } else { 0.0 }
210    }
211}
212
213const CDLTASUKIGAP_LOOKBACK: usize = 5 + 2;
214
215#[derive(Debug, Clone)]
216#[allow(non_camel_case_types)]
217pub struct CDLTASUKIGAP {
218    bars_seen: usize,
219    window: CandleWindow,
220    near: RollingCandleAvg,
221}
222impl CDLTASUKIGAP {
223    pub fn new() -> Self {
224        Self {
225            bars_seen: 0,
226            window: CandleWindow::new(3),
227            near: RollingCandleAvg::new(NEAR),
228        }
229    }
230}
231impl Default for CDLTASUKIGAP {
232    fn default() -> Self {
233        Self::new()
234    }
235}
236impl Next<(f64, f64, f64, f64)> for CDLTASUKIGAP {
237    type Output = f64;
238    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
239        self.bars_seen += 1;
240        self.window.push(open, high, low, close);
241        self.near.push(open, high, low, close);
242
243        if self.bars_seen <= CDLTASUKIGAP_LOOKBACK {
244            return 0.0;
245        }
246
247        let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
248        let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
249
250        let near_same = (real_body(self.window.bar(1).open, self.window.bar(1).close)
251            - real_body(self.window.bar(0).open, self.window.bar(0).close))
252        .abs()
253            < self.near.val(1);
254
255        let bull = real_body_gap_up(&self.window, 1, 2)
256            && c1 == 1
257            && c0 == -1
258            && self.window.bar(0).open < self.window.bar(1).close
259            && self.window.bar(0).open > self.window.bar(1).open
260            && self.window.bar(0).close < self.window.bar(1).open
261            && self.window.bar(0).close > self.window.bar(2).open.max(self.window.bar(2).close)
262            && near_same;
263
264        let bear = real_body_gap_down(&self.window, 1, 2)
265            && c1 == -1
266            && c0 == 1
267            && self.window.bar(0).open < self.window.bar(1).open
268            && self.window.bar(0).open > self.window.bar(1).close
269            && self.window.bar(0).close > self.window.bar(1).open
270            && self.window.bar(0).close < self.window.bar(2).open.min(self.window.bar(2).close)
271            && near_same;
272
273        if bull || bear { (c1 * 100) as f64 } else { 0.0 }
274    }
275}
276
277const CDLTRISTAR_LOOKBACK: usize = 10 + 2;
278
279#[derive(Debug, Clone)]
280#[allow(non_camel_case_types)]
281pub struct CDLTRISTAR {
282    bars_seen: usize,
283    window: CandleWindow,
284    body_doji: RollingCandleAvg,
285}
286impl CDLTRISTAR {
287    pub fn new() -> Self {
288        Self {
289            bars_seen: 0,
290            window: CandleWindow::new(3),
291            body_doji: RollingCandleAvg::new(BODY_DOJI),
292        }
293    }
294}
295impl Default for CDLTRISTAR {
296    fn default() -> Self {
297        Self::new()
298    }
299}
300impl Next<(f64, f64, f64, f64)> for CDLTRISTAR {
301    type Output = f64;
302    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
303        self.bars_seen += 1;
304        self.window.push(open, high, low, close);
305        self.body_doji.push(open, high, low, close);
306
307        if self.bars_seen <= CDLTRISTAR_LOOKBACK {
308            return 0.0;
309        }
310
311        let base = real_body(self.window.bar(2).open, self.window.bar(2).close)
312            <= self.body_doji.val(2)
313            && real_body(self.window.bar(1).open, self.window.bar(1).close)
314                <= self.body_doji.val(1)
315            && real_body(self.window.bar(0).open, self.window.bar(0).close)
316                <= self.body_doji.val(0);
317
318        let bear =
319            base && real_body_gap_up(&self.window, 1, 2) && !real_body_gap_up(&self.window, 0, 1);
320        let bull = base
321            && real_body_gap_down(&self.window, 1, 2)
322            && !real_body_gap_down(&self.window, 0, 1);
323
324        (bull as i32 * 100 - bear as i32 * 100) as f64
325    }
326}
327
328const CDLUNIQUE3RIVER_LOOKBACK: usize = 10 + 2;
329
330#[derive(Debug, Clone)]
331#[allow(non_camel_case_types)]
332pub struct CDLUNIQUE3RIVER {
333    bars_seen: usize,
334    window: CandleWindow,
335    body_long: RollingCandleAvg,
336    body_short: RollingCandleAvg,
337}
338impl CDLUNIQUE3RIVER {
339    pub fn new() -> Self {
340        Self {
341            bars_seen: 0,
342            window: CandleWindow::new(3),
343            body_long: RollingCandleAvg::new(BODY_LONG),
344            body_short: RollingCandleAvg::new(BODY_SHORT),
345        }
346    }
347}
348impl Default for CDLUNIQUE3RIVER {
349    fn default() -> Self {
350        Self::new()
351    }
352}
353impl Next<(f64, f64, f64, f64)> for CDLUNIQUE3RIVER {
354    type Output = f64;
355    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
356        self.bars_seen += 1;
357        self.window.push(open, high, low, close);
358        self.body_long.push(open, high, low, close);
359        self.body_short.push(open, high, low, close);
360
361        if self.bars_seen <= CDLUNIQUE3RIVER_LOOKBACK {
362            return 0.0;
363        }
364
365        let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
366            && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
367            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
368            && self.window.bar(1).open.max(self.window.bar(1).close)
369                < self.window.bar(2).open.max(self.window.bar(2).close)
370            && self.window.bar(1).open.min(self.window.bar(1).close)
371                > self.window.bar(2).open.min(self.window.bar(2).close)
372            && self.window.bar(1).low < self.window.bar(2).low
373            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
374            && real_body(self.window.bar(0).open, self.window.bar(0).close)
375                < self.body_short.val(0)
376            && self.window.bar(0).close < self.window.bar(1).close;
377
378        if cond { 100.0 } else { 0.0 }
379    }
380}
381
382const CDLUPSIDEGAP2CROWS_LOOKBACK: usize = 10 + 2;
383
384#[derive(Debug, Clone)]
385#[allow(non_camel_case_types)]
386pub struct CDLUPSIDEGAP2CROWS {
387    bars_seen: usize,
388    window: CandleWindow,
389    body_long: RollingCandleAvg,
390    body_short: RollingCandleAvg,
391}
392impl CDLUPSIDEGAP2CROWS {
393    pub fn new() -> Self {
394        Self {
395            bars_seen: 0,
396            window: CandleWindow::new(3),
397            body_long: RollingCandleAvg::new(BODY_LONG),
398            body_short: RollingCandleAvg::new(BODY_SHORT),
399        }
400    }
401}
402impl Default for CDLUPSIDEGAP2CROWS {
403    fn default() -> Self {
404        Self::new()
405    }
406}
407impl Next<(f64, f64, f64, f64)> for CDLUPSIDEGAP2CROWS {
408    type Output = f64;
409    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
410        self.bars_seen += 1;
411        self.window.push(open, high, low, close);
412        self.body_long.push(open, high, low, close);
413        self.body_short.push(open, high, low, close);
414
415        if self.bars_seen <= CDLUPSIDEGAP2CROWS_LOOKBACK {
416            return 0.0;
417        }
418
419        let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
420            && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
421            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
422            && real_body(self.window.bar(1).open, self.window.bar(1).close)
423                <= self.body_short.val(1)
424            && real_body_gap_up(&self.window, 1, 2)
425            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
426            && self.window.bar(0).open > self.window.bar(1).open
427            && self.window.bar(0).close < self.window.bar(1).close
428            && self.window.bar(0).close > self.window.bar(2).close;
429
430        if cond { -100.0 } else { 0.0 }
431    }
432}
433
434const CDLXSIDEGAP3METHODS_LOOKBACK: usize = 2;
435
436#[derive(Debug, Clone)]
437#[allow(non_camel_case_types)]
438pub struct CDLXSIDEGAP3METHODS {
439    bars_seen: usize,
440    window: CandleWindow,
441}
442impl CDLXSIDEGAP3METHODS {
443    pub fn new() -> Self {
444        Self {
445            bars_seen: 0,
446            window: CandleWindow::new(3),
447        }
448    }
449}
450impl Default for CDLXSIDEGAP3METHODS {
451    fn default() -> Self {
452        Self::new()
453    }
454}
455impl Next<(f64, f64, f64, f64)> for CDLXSIDEGAP3METHODS {
456    type Output = f64;
457    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
458        self.bars_seen += 1;
459        self.window.push(open, high, low, close);
460
461        if self.bars_seen <= CDLXSIDEGAP3METHODS_LOOKBACK {
462            return 0.0;
463        }
464
465        let c2 = candle_color(self.window.bar(2).open, self.window.bar(2).close);
466        let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
467        let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
468
469        let opens_within = self.window.bar(0).open
470            > self.window.bar(1).open.min(self.window.bar(1).close)
471            && self.window.bar(0).open < self.window.bar(1).open.max(self.window.bar(1).close);
472        let closes_within = self.window.bar(0).close
473            > self.window.bar(2).open.min(self.window.bar(2).close)
474            && self.window.bar(0).close < self.window.bar(2).open.max(self.window.bar(2).close);
475
476        let base = c2 == c1 && c0 != c2 && opens_within && closes_within;
477        let bull = base && c2 == 1 && real_body_gap_up(&self.window, 1, 2);
478        let bear = base && c2 == -1 && real_body_gap_down(&self.window, 1, 2);
479
480        (bull as i32 * 100 - bear as i32 * 100) as f64
481    }
482}
483
484const CDLBREAKAWAY_LOOKBACK: usize = 10 + 4;
485
486#[derive(Debug, Clone)]
487#[allow(non_camel_case_types)]
488pub struct CDLBREAKAWAY {
489    bars_seen: usize,
490    window: CandleWindow,
491    body_long: RollingCandleAvg,
492}
493impl CDLBREAKAWAY {
494    pub fn new() -> Self {
495        Self {
496            bars_seen: 0,
497            window: CandleWindow::new(5),
498            body_long: RollingCandleAvg::new(BODY_LONG),
499        }
500    }
501}
502impl Default for CDLBREAKAWAY {
503    fn default() -> Self {
504        Self::new()
505    }
506}
507impl Next<(f64, f64, f64, f64)> for CDLBREAKAWAY {
508    type Output = f64;
509    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
510        self.bars_seen += 1;
511        self.window.push(open, high, low, close);
512        self.body_long.push(open, high, low, close);
513
514        if self.bars_seen <= CDLBREAKAWAY_LOOKBACK {
515            return 0.0;
516        }
517
518        let c4 = candle_color(self.window.bar(4).open, self.window.bar(4).close);
519        let c3 = candle_color(self.window.bar(3).open, self.window.bar(3).close);
520        let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
521        let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
522
523        // A long 1st candle, three candles continuing the move (the 2nd gapping
524        // away on its real body), then a reversal candle of the opposite colour
525        // closing back inside the gap. The 4th candle's colour is not tested.
526        let base = real_body(self.window.bar(4).open, self.window.bar(4).close)
527            > self.body_long.val(4)
528            && c4 == c3
529            && c3 == c1
530            && c1 == -c0;
531
532        let bear_first = base
533            && c4 == -1
534            && real_body_gap_down(&self.window, 3, 4)
535            && self.window.bar(2).high < self.window.bar(3).high
536            && self.window.bar(2).low < self.window.bar(3).low
537            && self.window.bar(1).high < self.window.bar(2).high
538            && self.window.bar(1).low < self.window.bar(2).low
539            && self.window.bar(0).close > self.window.bar(3).open
540            && self.window.bar(0).close < self.window.bar(4).close;
541
542        let bull_first = base
543            && c4 == 1
544            && real_body_gap_up(&self.window, 3, 4)
545            && self.window.bar(2).high > self.window.bar(3).high
546            && self.window.bar(2).low > self.window.bar(3).low
547            && self.window.bar(1).high > self.window.bar(2).high
548            && self.window.bar(1).low > self.window.bar(2).low
549            && self.window.bar(0).close < self.window.bar(3).open
550            && self.window.bar(0).close > self.window.bar(4).close;
551
552        if bear_first || bull_first {
553            (c0 * 100) as f64
554        } else {
555            0.0
556        }
557    }
558}
559
560const CDLCONCEALBABYSWALL_LOOKBACK: usize = 10 + 3;
561
562#[derive(Debug, Clone)]
563#[allow(non_camel_case_types)]
564pub struct CDLCONCEALBABYSWALL {
565    bars_seen: usize,
566    window: CandleWindow,
567    shadow_vs: RollingCandleAvg,
568}
569impl CDLCONCEALBABYSWALL {
570    pub fn new() -> Self {
571        Self {
572            bars_seen: 0,
573            window: CandleWindow::new(4),
574            shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
575        }
576    }
577}
578impl Default for CDLCONCEALBABYSWALL {
579    fn default() -> Self {
580        Self::new()
581    }
582}
583impl Next<(f64, f64, f64, f64)> for CDLCONCEALBABYSWALL {
584    type Output = f64;
585    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
586        self.bars_seen += 1;
587        self.window.push(open, high, low, close);
588        self.shadow_vs.push(open, high, low, close);
589
590        if self.bars_seen <= CDLCONCEALBABYSWALL_LOOKBACK {
591            return 0.0;
592        }
593
594        // Four black candles; the first two are marubozu (both shadows shorter
595        // than the SHADOW_VERY_SHORT average), the third gaps down on its real
596        // body from the *second* and pokes back into it, and the fourth engulfs
597        // the third including its shadows.
598        let cond = candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
599            && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
600            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
601            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
602            && upper_shadow(
603                self.window.bar(3).open,
604                self.window.bar(3).high,
605                self.window.bar(3).close,
606            ) < self.shadow_vs.val(3)
607            && lower_shadow(
608                self.window.bar(3).open,
609                self.window.bar(3).low,
610                self.window.bar(3).close,
611            ) < self.shadow_vs.val(3)
612            && upper_shadow(
613                self.window.bar(2).open,
614                self.window.bar(2).high,
615                self.window.bar(2).close,
616            ) < self.shadow_vs.val(2)
617            && lower_shadow(
618                self.window.bar(2).open,
619                self.window.bar(2).low,
620                self.window.bar(2).close,
621            ) < self.shadow_vs.val(2)
622            && real_body_gap_down(&self.window, 1, 2)
623            && self.window.bar(1).high > self.window.bar(2).close
624            && self.window.bar(0).open >= self.window.bar(1).high
625            && self.window.bar(0).close <= self.window.bar(1).low;
626
627        if cond { 100.0 } else { 0.0 }
628    }
629}
630
631const CDLLADDERBOTTOM_LOOKBACK: usize = 10 + 4;
632
633#[derive(Debug, Clone)]
634#[allow(non_camel_case_types)]
635pub struct CDLLADDERBOTTOM {
636    bars_seen: usize,
637    window: CandleWindow,
638    shadow_vs: RollingCandleAvg,
639}
640impl CDLLADDERBOTTOM {
641    pub fn new() -> Self {
642        Self {
643            bars_seen: 0,
644            window: CandleWindow::new(5),
645            shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
646        }
647    }
648}
649impl Default for CDLLADDERBOTTOM {
650    fn default() -> Self {
651        Self::new()
652    }
653}
654impl Next<(f64, f64, f64, f64)> for CDLLADDERBOTTOM {
655    type Output = f64;
656    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
657        self.bars_seen += 1;
658        self.window.push(open, high, low, close);
659        self.shadow_vs.push(open, high, low, close);
660
661        if self.bars_seen <= CDLLADDERBOTTOM_LOOKBACK {
662            return 0.0;
663        }
664
665        let cond = candle_color(self.window.bar(4).open, self.window.bar(4).close) == -1
666            && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
667            && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
668            && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
669            && self.window.bar(3).close < self.window.bar(4).close
670            && self.window.bar(2).close < self.window.bar(3).close
671            && upper_shadow(
672                self.window.bar(1).open,
673                self.window.bar(1).high,
674                self.window.bar(1).close,
675            ) > self.shadow_vs.val(1)
676            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
677            && self.window.bar(0).open > self.window.bar(1).open
678            && self.window.bar(0).close > self.window.bar(1).high;
679
680        if cond { 100.0 } else { 0.0 }
681    }
682}
683
684const CDLMATHOLD_LOOKBACK: usize = 10 + 4;
685
686#[derive(Debug, Clone)]
687#[allow(non_camel_case_types)]
688pub struct CDLMATHOLD {
689    bars_seen: usize,
690    window: CandleWindow,
691    body_long: RollingCandleAvg,
692    body_short: RollingCandleAvg,
693}
694impl CDLMATHOLD {
695    pub fn new() -> Self {
696        Self {
697            bars_seen: 0,
698            window: CandleWindow::new(5),
699            body_long: RollingCandleAvg::new(BODY_LONG),
700            body_short: RollingCandleAvg::new(BODY_SHORT),
701        }
702    }
703}
704impl Default for CDLMATHOLD {
705    fn default() -> Self {
706        Self::new()
707    }
708}
709impl Next<(f64, f64, f64, f64)> for CDLMATHOLD {
710    type Output = f64;
711    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
712        self.bars_seen += 1;
713        self.window.push(open, high, low, close);
714        self.body_long.push(open, high, low, close);
715        self.body_short.push(open, high, low, close);
716
717        if self.bars_seen <= CDLMATHOLD_LOOKBACK {
718            return 0.0;
719        }
720
721        let penetration = 0.5;
722        let cond = real_body(self.window.bar(4).open, self.window.bar(4).close)
723            > self.body_long.val(4)
724            && real_body(self.window.bar(3).open, self.window.bar(3).close)
725                < self.body_short.val(3)
726            && real_body(self.window.bar(2).open, self.window.bar(2).close)
727                < self.body_short.val(2)
728            && real_body(self.window.bar(1).open, self.window.bar(1).close)
729                < self.body_short.val(1)
730            && candle_color(self.window.bar(4).open, self.window.bar(4).close) == 1
731            && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
732            && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
733            && real_body_gap_up(&self.window, 3, 4)
734            && self.window.bar(2).open.min(self.window.bar(2).close) < self.window.bar(4).close
735            && self.window.bar(1).open.min(self.window.bar(1).close) < self.window.bar(4).close
736            && self.window.bar(2).open.min(self.window.bar(2).close)
737                > self.window.bar(4).close
738                    - real_body(self.window.bar(4).open, self.window.bar(4).close) * penetration
739            && self.window.bar(1).open.min(self.window.bar(1).close)
740                > self.window.bar(4).close
741                    - real_body(self.window.bar(4).open, self.window.bar(4).close) * penetration
742            && self.window.bar(2).open.max(self.window.bar(2).close) < self.window.bar(3).open
743            && self.window.bar(1).open.max(self.window.bar(1).close)
744                < self.window.bar(2).open.max(self.window.bar(2).close)
745            && self.window.bar(0).open > self.window.bar(1).close
746            && self.window.bar(0).close
747                > self
748                    .window
749                    .bar(3)
750                    .high
751                    .max(self.window.bar(2).high)
752                    .max(self.window.bar(1).high);
753
754        if cond { 100.0 } else { 0.0 }
755    }
756}
757
758const CDLRISEFALL3METHODS_LOOKBACK: usize = 10 + 4;
759
760#[derive(Debug, Clone)]
761#[allow(non_camel_case_types)]
762pub struct CDLRISEFALL3METHODS {
763    bars_seen: usize,
764    window: CandleWindow,
765    body_long: RollingCandleAvg,
766    body_short: RollingCandleAvg,
767}
768impl CDLRISEFALL3METHODS {
769    pub fn new() -> Self {
770        Self {
771            bars_seen: 0,
772            window: CandleWindow::new(5),
773            body_long: RollingCandleAvg::new(BODY_LONG),
774            body_short: RollingCandleAvg::new(BODY_SHORT),
775        }
776    }
777}
778impl Default for CDLRISEFALL3METHODS {
779    fn default() -> Self {
780        Self::new()
781    }
782}
783impl Next<(f64, f64, f64, f64)> for CDLRISEFALL3METHODS {
784    type Output = f64;
785    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
786        self.bars_seen += 1;
787        self.window.push(open, high, low, close);
788        self.body_long.push(open, high, low, close);
789        self.body_short.push(open, high, low, close);
790
791        if self.bars_seen <= CDLRISEFALL3METHODS_LOOKBACK {
792            return 0.0;
793        }
794
795        let c4 = candle_color(self.window.bar(4).open, self.window.bar(4).close);
796        let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
797
798        if real_body(self.window.bar(4).open, self.window.bar(4).close) > self.body_long.val(4)
799            && real_body(self.window.bar(0).open, self.window.bar(0).close) > self.body_long.val(0)
800        {
801            let mid_short = real_body(self.window.bar(3).open, self.window.bar(3).close)
802                < self.body_short.val(3)
803                && real_body(self.window.bar(2).open, self.window.bar(2).close)
804                    < self.body_short.val(2)
805                && real_body(self.window.bar(1).open, self.window.bar(1).close)
806                    < self.body_short.val(1);
807
808            let bull = c4 == 1
809                && mid_short
810                && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
811                && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
812                && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
813                && self.window.bar(3).close < self.window.bar(4).close
814                && self.window.bar(2).close < self.window.bar(3).close
815                && self.window.bar(1).close < self.window.bar(2).close
816                && self.window.bar(3).low > self.window.bar(4).low
817                && self.window.bar(2).low > self.window.bar(4).low
818                && self.window.bar(1).low > self.window.bar(4).low
819                && self.window.bar(3).high < self.window.bar(4).high
820                && self.window.bar(2).high < self.window.bar(4).high
821                && self.window.bar(1).high < self.window.bar(4).high
822                && c0 == 1
823                && self.window.bar(0).open > self.window.bar(1).close
824                && self.window.bar(0).close > self.window.bar(4).close;
825
826            let bear = c4 == -1
827                && mid_short
828                && candle_color(self.window.bar(3).open, self.window.bar(3).close) == 1
829                && candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
830                && candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
831                && self.window.bar(3).close > self.window.bar(4).close
832                && self.window.bar(2).close > self.window.bar(3).close
833                && self.window.bar(1).close > self.window.bar(2).close
834                && self.window.bar(3).high < self.window.bar(4).high
835                && self.window.bar(2).high < self.window.bar(4).high
836                && self.window.bar(1).high < self.window.bar(4).high
837                && self.window.bar(3).low > self.window.bar(4).low
838                && self.window.bar(2).low > self.window.bar(4).low
839                && self.window.bar(1).low > self.window.bar(4).low
840                && c0 == -1
841                && self.window.bar(0).open < self.window.bar(1).close
842                && self.window.bar(0).close < self.window.bar(4).close;
843
844            (bull as i32 * 100 - bear as i32 * 100) as f64
845        } else {
846            0.0
847        }
848    }
849}
850
851#[cfg(test)]
852mod tests {
853    use super::*;
854    use crate::indicators::patterns::fixtures as fx;
855
856    crate::test_pattern_parity!(
857        test_cdlgapsidesidewhite,
858        CDLGAPSIDESIDEWHITE,
859        talib_rs::pattern::cdl_gapsidesidewhite,
860        fx::gapsidesidewhite
861    );
862    crate::test_pattern_parity!(
863        test_cdlidentical3crows,
864        CDLIDENTICAL3CROWS,
865        talib_rs::pattern::cdl_identical3crows
866    );
867    crate::test_pattern_parity!(
868        test_cdlstalledpattern,
869        CDLSTALLEDPATTERN,
870        talib_rs::pattern::cdl_stalledpattern
871    );
872    crate::test_pattern_parity!(
873        test_cdltasukigap,
874        CDLTASUKIGAP,
875        talib_rs::pattern::cdl_tasukigap,
876        fx::tasukigap
877    );
878    crate::test_pattern_parity!(
879        test_cdltristar,
880        CDLTRISTAR,
881        talib_rs::pattern::cdl_tristar,
882        fx::tristar
883    );
884    crate::test_pattern_parity!(
885        test_cdlunique3river,
886        CDLUNIQUE3RIVER,
887        talib_rs::pattern::cdl_unique3river,
888        fx::unique3river
889    );
890    crate::test_pattern_parity!(
891        test_cdlupsidegap2crows,
892        CDLUPSIDEGAP2CROWS,
893        talib_rs::pattern::cdl_upsidegap2crows,
894        fx::upsidegap2crows
895    );
896    crate::test_pattern_parity!(
897        test_cdlxsidegap3methods,
898        CDLXSIDEGAP3METHODS,
899        talib_rs::pattern::cdl_xsidegap3methods,
900        fx::xsidegap3methods
901    );
902    crate::test_pattern_parity!(
903        test_cdlbreakaway,
904        CDLBREAKAWAY,
905        talib_rs::pattern::cdl_breakaway,
906        fx::breakaway
907    );
908    crate::test_pattern_parity!(
909        test_cdlconcealbabyswall,
910        CDLCONCEALBABYSWALL,
911        talib_rs::pattern::cdl_concealbabyswall,
912        fx::concealbabyswall
913    );
914    crate::test_pattern_parity!(
915        test_cdlladderbottom,
916        CDLLADDERBOTTOM,
917        talib_rs::pattern::cdl_ladderbottom,
918        fx::ladderbottom
919    );
920    crate::test_pattern_parity!(
921        test_cdlmathold,
922        CDLMATHOLD,
923        talib_rs::pattern::cdl_mathold,
924        fx::mathold
925    );
926    crate::test_pattern_parity!(
927        test_cdlrisefall3methods,
928        CDLRISEFALL3METHODS,
929        talib_rs::pattern::cdl_risefall3methods,
930        fx::risefall3methods
931    );
932}