Skip to main content

vector_ta/indicators/
pattern_recognition.rs

1use crate::utilities::data_loader::Candles;
2use crate::utilities::enums::Kernel;
3use std::mem::MaybeUninit;
4use thiserror::Error;
5
6#[derive(Debug, Clone)]
7pub enum PatternData<'a> {
8    Candles {
9        candles: &'a Candles,
10    },
11    Slices {
12        open: &'a [f64],
13        high: &'a [f64],
14        low: &'a [f64],
15        close: &'a [f64],
16    },
17}
18
19impl Default for PatternType {
20    fn default() -> Self {
21        PatternType::Cdl2Crows
22    }
23}
24
25#[derive(Debug, Clone, Default)]
26pub struct PatternParams {
27    pub pattern_type: PatternType,
28    pub penetration: f64,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum PatternType {
33    Cdl2Crows,
34    Cdl3BlackCrows,
35    Cdl3Inside,
36    Cdl3LineStrike,
37    Cdl3Outside,
38    Cdl3StarsInSouth,
39    Cdl3WhiteSoldiers,
40    CdlAbandonedBaby,
41    CdlAdvanceBlock,
42    CdlBeltHold,
43    CdlBreakaway,
44    CdlClosingMarubozu,
45    CdlConcealBabySwall,
46    CdlCounterAttack,
47    CdlDarkCloudCover,
48    CdlDoji,
49    CdlDojiStar,
50    CdlDragonflyDoji,
51    CdlEngulfing,
52    CdlEveningDojiStar,
53    CdlEveningStar,
54    CdlGapSideSideWhite,
55    CdlGravestoneDoji,
56    CdlHammer,
57    CdlHangingMan,
58    CdlHarami,
59    CdlHaramiCross,
60    CdlHighWave,
61    CdlHikkake,
62    CdlHikkakeMod,
63    CdlHomingPigeon,
64    CdlIdentical3Crows,
65    CdlInNeck,
66    CdlInvertedHammer,
67    CdlKicking,
68    CdlKickingByLength,
69    CdlLadderBottom,
70    CdlLongLeggedDoji,
71    CdlLongLine,
72    CdlMarubozu,
73    CdlMatchingLow,
74    CdlMatHold,
75    CdlMorningDojiStar,
76    CdlMorningStar,
77    CdlOnNeck,
78    CdlPiercing,
79    CdlRickshawMan,
80    CdlRiseFall3Methods,
81    CdlSeparatingLines,
82    CdlShootingStar,
83    CdlShortLine,
84    CdlSpinningTop,
85    CdlStalledPattern,
86    CdlStickSandwich,
87    CdlTakuri,
88    CdlTasukiGap,
89    CdlThrusting,
90    CdlTristar,
91    CdlUnique3River,
92    CdlUpsideGap2Crows,
93    CdlXSideGap3Methods,
94}
95
96#[derive(Debug, Clone)]
97pub struct PatternInput<'a> {
98    pub data: PatternData<'a>,
99    pub params: PatternParams,
100}
101
102impl<'a> PatternInput<'a> {
103    pub fn from_candles(candles: &'a Candles, params: PatternParams) -> Self {
104        Self {
105            data: PatternData::Candles { candles },
106            params,
107        }
108    }
109
110    pub fn from_slices(
111        open: &'a [f64],
112        high: &'a [f64],
113        low: &'a [f64],
114        close: &'a [f64],
115        params: PatternParams,
116    ) -> Self {
117        Self {
118            data: PatternData::Slices {
119                open,
120                high,
121                low,
122                close,
123            },
124            params,
125        }
126    }
127
128    pub fn with_default_candles(candles: &'a Candles, pattern_type: PatternType) -> Self {
129        Self {
130            data: PatternData::Candles { candles },
131            params: PatternParams {
132                pattern_type,
133                ..Default::default()
134            },
135        }
136    }
137
138    pub fn with_default_slices(
139        open: &'a [f64],
140        high: &'a [f64],
141        low: &'a [f64],
142        close: &'a [f64],
143        pattern_type: PatternType,
144    ) -> Self {
145        Self {
146            data: PatternData::Slices {
147                open,
148                high,
149                low,
150                close,
151            },
152            params: PatternParams {
153                pattern_type,
154                ..Default::default()
155            },
156        }
157    }
158}
159
160#[derive(Debug, Clone)]
161pub struct PatternOutput {
162    pub values: Vec<i8>,
163}
164
165#[derive(Debug, Clone, Default)]
166pub struct PatternRecognitionParams {}
167
168#[derive(Debug, Clone)]
169pub enum PatternRecognitionData<'a> {
170    Candles {
171        candles: &'a Candles,
172    },
173    Slices {
174        open: &'a [f64],
175        high: &'a [f64],
176        low: &'a [f64],
177        close: &'a [f64],
178    },
179}
180
181#[derive(Debug, Clone)]
182pub struct PatternRecognitionInput<'a> {
183    pub data: PatternRecognitionData<'a>,
184    pub params: PatternRecognitionParams,
185}
186
187impl<'a> PatternRecognitionInput<'a> {
188    pub fn from_candles(candles: &'a Candles, params: PatternRecognitionParams) -> Self {
189        Self {
190            data: PatternRecognitionData::Candles { candles },
191            params,
192        }
193    }
194
195    pub fn from_slices(
196        open: &'a [f64],
197        high: &'a [f64],
198        low: &'a [f64],
199        close: &'a [f64],
200        params: PatternRecognitionParams,
201    ) -> Self {
202        Self {
203            data: PatternRecognitionData::Slices {
204                open,
205                high,
206                low,
207                close,
208            },
209            params,
210        }
211    }
212
213    pub fn with_default_candles(candles: &'a Candles) -> Self {
214        Self::from_candles(candles, PatternRecognitionParams::default())
215    }
216
217    pub fn with_default_slices(
218        open: &'a [f64],
219        high: &'a [f64],
220        low: &'a [f64],
221        close: &'a [f64],
222    ) -> Self {
223        Self::from_slices(open, high, low, close, PatternRecognitionParams::default())
224    }
225}
226
227#[derive(Debug, Clone, Copy)]
228pub struct PatternSpec {
229    pub id: &'static str,
230    pub row_index: usize,
231    pub category: &'static str,
232}
233
234#[derive(Debug, Clone)]
235pub struct PatternRecognitionOutput {
236    pub rows: usize,
237    pub cols: usize,
238    pub values_u8: Vec<u8>,
239    pub pattern_ids: Vec<&'static str>,
240    pub warmup: Option<usize>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct PackedPatternRecognitionOutput {
245    pub rows: usize,
246    pub cols: usize,
247    pub words_per_row: usize,
248    pub words_u64: Vec<u64>,
249    pub pattern_ids: Vec<&'static str>,
250    pub warmup: Option<usize>,
251}
252
253#[derive(Debug, Clone)]
254struct SharedPatternPrimitives {
255    body: Vec<f64>,
256    range: Vec<f64>,
257    upper_shadow: Vec<f64>,
258    lower_shadow: Vec<f64>,
259    direction: Vec<i8>,
260    body_gap_up: Vec<u8>,
261    body_gap_down: Vec<u8>,
262}
263
264fn build_shared_primitives(
265    open: &[f64],
266    high: &[f64],
267    low: &[f64],
268    close: &[f64],
269) -> SharedPatternPrimitives {
270    let len = close.len();
271    let mut body = Vec::with_capacity(len);
272    let mut range = Vec::with_capacity(len);
273    let mut upper_shadow = Vec::with_capacity(len);
274    let mut lower_shadow = Vec::with_capacity(len);
275    let mut direction = Vec::with_capacity(len);
276    let mut body_gap_up = Vec::with_capacity(len);
277    let mut body_gap_down = Vec::with_capacity(len);
278
279    for i in 0..len {
280        let o = open[i];
281        let h = high[i];
282        let l = low[i];
283        let c = close[i];
284
285        body.push((c - o).abs());
286        range.push(h - l);
287        upper_shadow.push(if c >= o { h - c } else { h - o });
288        lower_shadow.push(if c >= o { o - l } else { c - l });
289        direction.push(if c >= o { 1 } else { -1 });
290
291        if i == 0 {
292            body_gap_up.push(0);
293            body_gap_down.push(0);
294        } else {
295            let cur_min = o.min(c);
296            let cur_max = o.max(c);
297            let prev_min = open[i - 1].min(close[i - 1]);
298            let prev_max = open[i - 1].max(close[i - 1]);
299            body_gap_up.push((cur_min > prev_max) as u8);
300            body_gap_down.push((cur_max < prev_min) as u8);
301        }
302    }
303
304    SharedPatternPrimitives {
305        body,
306        range,
307        upper_shadow,
308        lower_shadow,
309        direction,
310        body_gap_up,
311        body_gap_down,
312    }
313}
314
315impl PatternRecognitionOutput {
316    pub fn to_bitmask_u64(&self) -> PackedPatternRecognitionOutput {
317        let words_per_row = self.cols.div_ceil(64);
318        let mut words_u64 = vec![0u64; self.rows.saturating_mul(words_per_row)];
319
320        for row in 0..self.rows {
321            let src_start = row * self.cols;
322            let src = &self.values_u8[src_start..src_start + self.cols];
323            let dst_start = row * words_per_row;
324            let dst = &mut words_u64[dst_start..dst_start + words_per_row];
325
326            for (idx, v) in src.iter().enumerate() {
327                if *v != 0 {
328                    let word = idx / 64;
329                    let bit = idx % 64;
330                    dst[word] |= 1u64 << bit;
331                }
332            }
333        }
334
335        PackedPatternRecognitionOutput {
336            rows: self.rows,
337            cols: self.cols,
338            words_per_row,
339            words_u64,
340            pattern_ids: self.pattern_ids.clone(),
341            warmup: self.warmup,
342        }
343    }
344}
345
346#[derive(Debug, Error)]
347pub enum PatternRecognitionError {
348    #[error(
349        "pattern_recognition: data length mismatch: open={open}, high={high}, low={low}, close={close}"
350    )]
351    DataLengthMismatch {
352        open: usize,
353        high: usize,
354        low: usize,
355        close: usize,
356    },
357
358    #[error("pattern_recognition: output length mismatch for `{pattern_id}`: expected {expected}, got {got}")]
359    OutputLengthMismatch {
360        pattern_id: &'static str,
361        expected: usize,
362        got: usize,
363    },
364
365    #[error(transparent)]
366    Pattern(#[from] PatternError),
367}
368
369#[derive(Clone, Copy)]
370struct PatternRunner {
371    id: &'static str,
372    pattern_type: PatternType,
373    category: &'static str,
374    run: fn(&PatternInput<'_>) -> Result<PatternOutput, PatternError>,
375}
376
377const PATTERN_RUNNERS: [PatternRunner; 61] = [
378    PatternRunner {
379        id: "cdl2crows",
380        pattern_type: PatternType::Cdl2Crows,
381        category: "candlestick",
382        run: cdl2crows,
383    },
384    PatternRunner {
385        id: "cdl3blackcrows",
386        pattern_type: PatternType::Cdl3BlackCrows,
387        category: "candlestick",
388        run: cdl3blackcrows,
389    },
390    PatternRunner {
391        id: "cdl3inside",
392        pattern_type: PatternType::Cdl3Inside,
393        category: "candlestick",
394        run: cdl3inside,
395    },
396    PatternRunner {
397        id: "cdl3linestrike",
398        pattern_type: PatternType::Cdl3LineStrike,
399        category: "candlestick",
400        run: cdl3linestrike,
401    },
402    PatternRunner {
403        id: "cdl3outside",
404        pattern_type: PatternType::Cdl3Outside,
405        category: "candlestick",
406        run: cdl3outside,
407    },
408    PatternRunner {
409        id: "cdl3starsinsouth",
410        pattern_type: PatternType::Cdl3StarsInSouth,
411        category: "candlestick",
412        run: cdl3starsinsouth,
413    },
414    PatternRunner {
415        id: "cdl3whitesoldiers",
416        pattern_type: PatternType::Cdl3WhiteSoldiers,
417        category: "candlestick",
418        run: cdl3whitesoldiers,
419    },
420    PatternRunner {
421        id: "cdlabandonedbaby",
422        pattern_type: PatternType::CdlAbandonedBaby,
423        category: "candlestick",
424        run: cdlabandonedbaby,
425    },
426    PatternRunner {
427        id: "cdladvanceblock",
428        pattern_type: PatternType::CdlAdvanceBlock,
429        category: "candlestick",
430        run: cdladvanceblock,
431    },
432    PatternRunner {
433        id: "cdlbelthold",
434        pattern_type: PatternType::CdlBeltHold,
435        category: "candlestick",
436        run: cdlbelthold,
437    },
438    PatternRunner {
439        id: "cdlbreakaway",
440        pattern_type: PatternType::CdlBreakaway,
441        category: "candlestick",
442        run: cdlbreakaway,
443    },
444    PatternRunner {
445        id: "cdlclosingmarubozu",
446        pattern_type: PatternType::CdlClosingMarubozu,
447        category: "candlestick",
448        run: cdlclosingmarubozu,
449    },
450    PatternRunner {
451        id: "cdlconcealbabyswall",
452        pattern_type: PatternType::CdlConcealBabySwall,
453        category: "candlestick",
454        run: cdlconcealbabyswall,
455    },
456    PatternRunner {
457        id: "cdlcounterattack",
458        pattern_type: PatternType::CdlCounterAttack,
459        category: "candlestick",
460        run: cdlcounterattack,
461    },
462    PatternRunner {
463        id: "cdldarkcloudcover",
464        pattern_type: PatternType::CdlDarkCloudCover,
465        category: "candlestick",
466        run: cdldarkcloudcover,
467    },
468    PatternRunner {
469        id: "cdldoji",
470        pattern_type: PatternType::CdlDoji,
471        category: "candlestick",
472        run: cdldoji,
473    },
474    PatternRunner {
475        id: "cdldojistar",
476        pattern_type: PatternType::CdlDojiStar,
477        category: "candlestick",
478        run: cdldojistar,
479    },
480    PatternRunner {
481        id: "cdldragonflydoji",
482        pattern_type: PatternType::CdlDragonflyDoji,
483        category: "candlestick",
484        run: cdldragonflydoji,
485    },
486    PatternRunner {
487        id: "cdlengulfing",
488        pattern_type: PatternType::CdlEngulfing,
489        category: "candlestick",
490        run: cdlengulfing,
491    },
492    PatternRunner {
493        id: "cdleveningdojistar",
494        pattern_type: PatternType::CdlEveningDojiStar,
495        category: "candlestick",
496        run: cdleveningdojistar,
497    },
498    PatternRunner {
499        id: "cdleveningstar",
500        pattern_type: PatternType::CdlEveningStar,
501        category: "candlestick",
502        run: cdleveningstar,
503    },
504    PatternRunner {
505        id: "cdlmorningstar",
506        pattern_type: PatternType::CdlMorningStar,
507        category: "candlestick",
508        run: cdlmorningstar,
509    },
510    PatternRunner {
511        id: "cdlgravestonedoji",
512        pattern_type: PatternType::CdlGravestoneDoji,
513        category: "candlestick",
514        run: cdlgravestonedoji,
515    },
516    PatternRunner {
517        id: "cdlhammer",
518        pattern_type: PatternType::CdlHammer,
519        category: "candlestick",
520        run: cdlhammer,
521    },
522    PatternRunner {
523        id: "cdlhangingman",
524        pattern_type: PatternType::CdlHangingMan,
525        category: "candlestick",
526        run: cdlhangingman,
527    },
528    PatternRunner {
529        id: "cdlharami",
530        pattern_type: PatternType::CdlHarami,
531        category: "candlestick",
532        run: cdlharami,
533    },
534    PatternRunner {
535        id: "cdlharamicross",
536        pattern_type: PatternType::CdlHaramiCross,
537        category: "candlestick",
538        run: cdlharamicross,
539    },
540    PatternRunner {
541        id: "cdlhighwave",
542        pattern_type: PatternType::CdlHighWave,
543        category: "candlestick",
544        run: cdlhighwave,
545    },
546    PatternRunner {
547        id: "cdlinvertedhammer",
548        pattern_type: PatternType::CdlInvertedHammer,
549        category: "candlestick",
550        run: cdlinvertedhammer,
551    },
552    PatternRunner {
553        id: "cdllongleggeddoji",
554        pattern_type: PatternType::CdlLongLeggedDoji,
555        category: "candlestick",
556        run: cdllongleggeddoji,
557    },
558    PatternRunner {
559        id: "cdllongline",
560        pattern_type: PatternType::CdlLongLine,
561        category: "candlestick",
562        run: cdllongline,
563    },
564    PatternRunner {
565        id: "cdlmarubozu",
566        pattern_type: PatternType::CdlMarubozu,
567        category: "candlestick",
568        run: cdlmarubozu,
569    },
570    PatternRunner {
571        id: "cdlrickshawman",
572        pattern_type: PatternType::CdlRickshawMan,
573        category: "candlestick",
574        run: cdlrickshawman,
575    },
576    PatternRunner {
577        id: "cdlshootingstar",
578        pattern_type: PatternType::CdlShootingStar,
579        category: "candlestick",
580        run: cdlshootingstar,
581    },
582    PatternRunner {
583        id: "cdlshortline",
584        pattern_type: PatternType::CdlShortLine,
585        category: "candlestick",
586        run: cdlshortline,
587    },
588    PatternRunner {
589        id: "cdlspinningtop",
590        pattern_type: PatternType::CdlSpinningTop,
591        category: "candlestick",
592        run: cdlspinningtop,
593    },
594    PatternRunner {
595        id: "cdltakuri",
596        pattern_type: PatternType::CdlTakuri,
597        category: "candlestick",
598        run: cdltakuri,
599    },
600    PatternRunner {
601        id: "cdlhomingpigeon",
602        pattern_type: PatternType::CdlHomingPigeon,
603        category: "candlestick",
604        run: cdlhomingpigeon,
605    },
606    PatternRunner {
607        id: "cdlmatchinglow",
608        pattern_type: PatternType::CdlMatchingLow,
609        category: "candlestick",
610        run: cdlmatchinglow,
611    },
612    PatternRunner {
613        id: "cdlinneck",
614        pattern_type: PatternType::CdlInNeck,
615        category: "candlestick",
616        run: cdlinneck,
617    },
618    PatternRunner {
619        id: "cdlonneck",
620        pattern_type: PatternType::CdlOnNeck,
621        category: "candlestick",
622        run: cdlonneck,
623    },
624    PatternRunner {
625        id: "cdlpiercing",
626        pattern_type: PatternType::CdlPiercing,
627        category: "candlestick",
628        run: cdlpiercing,
629    },
630    PatternRunner {
631        id: "cdlthrusting",
632        pattern_type: PatternType::CdlThrusting,
633        category: "candlestick",
634        run: cdlthrusting,
635    },
636    PatternRunner {
637        id: "cdlmorningdojistar",
638        pattern_type: PatternType::CdlMorningDojiStar,
639        category: "candlestick",
640        run: cdlmorningdojistar,
641    },
642    PatternRunner {
643        id: "cdltristar",
644        pattern_type: PatternType::CdlTristar,
645        category: "candlestick",
646        run: cdltristar,
647    },
648    PatternRunner {
649        id: "cdlidentical3crows",
650        pattern_type: PatternType::CdlIdentical3Crows,
651        category: "candlestick",
652        run: cdlidentical3crows,
653    },
654    PatternRunner {
655        id: "cdlsticksandwich",
656        pattern_type: PatternType::CdlStickSandwich,
657        category: "candlestick",
658        run: cdlsticksandwich,
659    },
660    PatternRunner {
661        id: "cdlseparatinglines",
662        pattern_type: PatternType::CdlSeparatingLines,
663        category: "candlestick",
664        run: cdlseparatinglines,
665    },
666    PatternRunner {
667        id: "cdlgapsidesidewhite",
668        pattern_type: PatternType::CdlGapSideSideWhite,
669        category: "candlestick",
670        run: cdlgapsidesidewhite,
671    },
672    PatternRunner {
673        id: "cdlhikkake",
674        pattern_type: PatternType::CdlHikkake,
675        category: "candlestick",
676        run: cdlhikkake,
677    },
678    PatternRunner {
679        id: "cdlhikkakemod",
680        pattern_type: PatternType::CdlHikkakeMod,
681        category: "candlestick",
682        run: cdlhikkakemod,
683    },
684    PatternRunner {
685        id: "cdlkicking",
686        pattern_type: PatternType::CdlKicking,
687        category: "candlestick",
688        run: cdlkicking,
689    },
690    PatternRunner {
691        id: "cdlkickingbylength",
692        pattern_type: PatternType::CdlKickingByLength,
693        category: "candlestick",
694        run: cdlkickingbylength,
695    },
696    PatternRunner {
697        id: "cdlladderbottom",
698        pattern_type: PatternType::CdlLadderBottom,
699        category: "candlestick",
700        run: cdlladderbottom,
701    },
702    PatternRunner {
703        id: "cdlmathold",
704        pattern_type: PatternType::CdlMatHold,
705        category: "candlestick",
706        run: cdlmathold,
707    },
708    PatternRunner {
709        id: "cdlrisefall3methods",
710        pattern_type: PatternType::CdlRiseFall3Methods,
711        category: "candlestick",
712        run: cdlrisefall3methods,
713    },
714    PatternRunner {
715        id: "cdlstalledpattern",
716        pattern_type: PatternType::CdlStalledPattern,
717        category: "candlestick",
718        run: cdlstalledpattern,
719    },
720    PatternRunner {
721        id: "cdltasukigap",
722        pattern_type: PatternType::CdlTasukiGap,
723        category: "candlestick",
724        run: cdltasukigap,
725    },
726    PatternRunner {
727        id: "cdlunique3river",
728        pattern_type: PatternType::CdlUnique3River,
729        category: "candlestick",
730        run: cdlunique3river,
731    },
732    PatternRunner {
733        id: "cdlupsidegap2crows",
734        pattern_type: PatternType::CdlUpsideGap2Crows,
735        category: "candlestick",
736        run: cdlupsidegap2crows,
737    },
738    PatternRunner {
739        id: "cdlxsidegap3methods",
740        pattern_type: PatternType::CdlXSideGap3Methods,
741        category: "candlestick",
742        run: cdlxsidegap3methods,
743    },
744];
745
746const PATTERN_SPECS: [PatternSpec; 61] = [
747    PatternSpec {
748        id: "cdl2crows",
749        row_index: 0,
750        category: "candlestick",
751    },
752    PatternSpec {
753        id: "cdl3blackcrows",
754        row_index: 1,
755        category: "candlestick",
756    },
757    PatternSpec {
758        id: "cdl3inside",
759        row_index: 2,
760        category: "candlestick",
761    },
762    PatternSpec {
763        id: "cdl3linestrike",
764        row_index: 3,
765        category: "candlestick",
766    },
767    PatternSpec {
768        id: "cdl3outside",
769        row_index: 4,
770        category: "candlestick",
771    },
772    PatternSpec {
773        id: "cdl3starsinsouth",
774        row_index: 5,
775        category: "candlestick",
776    },
777    PatternSpec {
778        id: "cdl3whitesoldiers",
779        row_index: 6,
780        category: "candlestick",
781    },
782    PatternSpec {
783        id: "cdlabandonedbaby",
784        row_index: 7,
785        category: "candlestick",
786    },
787    PatternSpec {
788        id: "cdladvanceblock",
789        row_index: 8,
790        category: "candlestick",
791    },
792    PatternSpec {
793        id: "cdlbelthold",
794        row_index: 9,
795        category: "candlestick",
796    },
797    PatternSpec {
798        id: "cdlbreakaway",
799        row_index: 10,
800        category: "candlestick",
801    },
802    PatternSpec {
803        id: "cdlclosingmarubozu",
804        row_index: 11,
805        category: "candlestick",
806    },
807    PatternSpec {
808        id: "cdlconcealbabyswall",
809        row_index: 12,
810        category: "candlestick",
811    },
812    PatternSpec {
813        id: "cdlcounterattack",
814        row_index: 13,
815        category: "candlestick",
816    },
817    PatternSpec {
818        id: "cdldarkcloudcover",
819        row_index: 14,
820        category: "candlestick",
821    },
822    PatternSpec {
823        id: "cdldoji",
824        row_index: 15,
825        category: "candlestick",
826    },
827    PatternSpec {
828        id: "cdldojistar",
829        row_index: 16,
830        category: "candlestick",
831    },
832    PatternSpec {
833        id: "cdldragonflydoji",
834        row_index: 17,
835        category: "candlestick",
836    },
837    PatternSpec {
838        id: "cdlengulfing",
839        row_index: 18,
840        category: "candlestick",
841    },
842    PatternSpec {
843        id: "cdleveningdojistar",
844        row_index: 19,
845        category: "candlestick",
846    },
847    PatternSpec {
848        id: "cdleveningstar",
849        row_index: 20,
850        category: "candlestick",
851    },
852    PatternSpec {
853        id: "cdlmorningstar",
854        row_index: 21,
855        category: "candlestick",
856    },
857    PatternSpec {
858        id: "cdlgravestonedoji",
859        row_index: 22,
860        category: "candlestick",
861    },
862    PatternSpec {
863        id: "cdlhammer",
864        row_index: 23,
865        category: "candlestick",
866    },
867    PatternSpec {
868        id: "cdlhangingman",
869        row_index: 24,
870        category: "candlestick",
871    },
872    PatternSpec {
873        id: "cdlharami",
874        row_index: 25,
875        category: "candlestick",
876    },
877    PatternSpec {
878        id: "cdlharamicross",
879        row_index: 26,
880        category: "candlestick",
881    },
882    PatternSpec {
883        id: "cdlhighwave",
884        row_index: 27,
885        category: "candlestick",
886    },
887    PatternSpec {
888        id: "cdlinvertedhammer",
889        row_index: 28,
890        category: "candlestick",
891    },
892    PatternSpec {
893        id: "cdllongleggeddoji",
894        row_index: 29,
895        category: "candlestick",
896    },
897    PatternSpec {
898        id: "cdllongline",
899        row_index: 30,
900        category: "candlestick",
901    },
902    PatternSpec {
903        id: "cdlmarubozu",
904        row_index: 31,
905        category: "candlestick",
906    },
907    PatternSpec {
908        id: "cdlrickshawman",
909        row_index: 32,
910        category: "candlestick",
911    },
912    PatternSpec {
913        id: "cdlshootingstar",
914        row_index: 33,
915        category: "candlestick",
916    },
917    PatternSpec {
918        id: "cdlshortline",
919        row_index: 34,
920        category: "candlestick",
921    },
922    PatternSpec {
923        id: "cdlspinningtop",
924        row_index: 35,
925        category: "candlestick",
926    },
927    PatternSpec {
928        id: "cdltakuri",
929        row_index: 36,
930        category: "candlestick",
931    },
932    PatternSpec {
933        id: "cdlhomingpigeon",
934        row_index: 37,
935        category: "candlestick",
936    },
937    PatternSpec {
938        id: "cdlmatchinglow",
939        row_index: 38,
940        category: "candlestick",
941    },
942    PatternSpec {
943        id: "cdlinneck",
944        row_index: 39,
945        category: "candlestick",
946    },
947    PatternSpec {
948        id: "cdlonneck",
949        row_index: 40,
950        category: "candlestick",
951    },
952    PatternSpec {
953        id: "cdlpiercing",
954        row_index: 41,
955        category: "candlestick",
956    },
957    PatternSpec {
958        id: "cdlthrusting",
959        row_index: 42,
960        category: "candlestick",
961    },
962    PatternSpec {
963        id: "cdlmorningdojistar",
964        row_index: 43,
965        category: "candlestick",
966    },
967    PatternSpec {
968        id: "cdltristar",
969        row_index: 44,
970        category: "candlestick",
971    },
972    PatternSpec {
973        id: "cdlidentical3crows",
974        row_index: 45,
975        category: "candlestick",
976    },
977    PatternSpec {
978        id: "cdlsticksandwich",
979        row_index: 46,
980        category: "candlestick",
981    },
982    PatternSpec {
983        id: "cdlseparatinglines",
984        row_index: 47,
985        category: "candlestick",
986    },
987    PatternSpec {
988        id: "cdlgapsidesidewhite",
989        row_index: 48,
990        category: "candlestick",
991    },
992    PatternSpec {
993        id: "cdlhikkake",
994        row_index: 49,
995        category: "candlestick",
996    },
997    PatternSpec {
998        id: "cdlhikkakemod",
999        row_index: 50,
1000        category: "candlestick",
1001    },
1002    PatternSpec {
1003        id: "cdlkicking",
1004        row_index: 51,
1005        category: "candlestick",
1006    },
1007    PatternSpec {
1008        id: "cdlkickingbylength",
1009        row_index: 52,
1010        category: "candlestick",
1011    },
1012    PatternSpec {
1013        id: "cdlladderbottom",
1014        row_index: 53,
1015        category: "candlestick",
1016    },
1017    PatternSpec {
1018        id: "cdlmathold",
1019        row_index: 54,
1020        category: "candlestick",
1021    },
1022    PatternSpec {
1023        id: "cdlrisefall3methods",
1024        row_index: 55,
1025        category: "candlestick",
1026    },
1027    PatternSpec {
1028        id: "cdlstalledpattern",
1029        row_index: 56,
1030        category: "candlestick",
1031    },
1032    PatternSpec {
1033        id: "cdltasukigap",
1034        row_index: 57,
1035        category: "candlestick",
1036    },
1037    PatternSpec {
1038        id: "cdlunique3river",
1039        row_index: 58,
1040        category: "candlestick",
1041    },
1042    PatternSpec {
1043        id: "cdlupsidegap2crows",
1044        row_index: 59,
1045        category: "candlestick",
1046    },
1047    PatternSpec {
1048        id: "cdlxsidegap3methods",
1049        row_index: 60,
1050        category: "candlestick",
1051    },
1052];
1053
1054pub fn list_patterns() -> &'static [PatternSpec] {
1055    &PATTERN_SPECS
1056}
1057
1058pub fn pattern_type_from_id(id: &str) -> Option<PatternType> {
1059    PATTERN_RUNNERS
1060        .iter()
1061        .find(|runner| runner.id.eq_ignore_ascii_case(id))
1062        .map(|runner| runner.pattern_type)
1063}
1064
1065pub fn pattern(input: &PatternInput<'_>) -> Result<PatternOutput, PatternError> {
1066    pattern_with_kernel(input, Kernel::Auto)
1067}
1068
1069pub fn pattern_with_kernel(
1070    input: &PatternInput<'_>,
1071    kernel: Kernel,
1072) -> Result<PatternOutput, PatternError> {
1073    let _ = kernel;
1074    PATTERN_RUNNERS
1075        .iter()
1076        .find(|runner| runner.pattern_type == input.params.pattern_type)
1077        .map(|runner| (runner.run)(input))
1078        .unwrap_or(Err(PatternError::Unknown))
1079}
1080
1081pub fn pattern_recognition(
1082    input: &PatternRecognitionInput<'_>,
1083) -> Result<PatternRecognitionOutput, PatternRecognitionError> {
1084    pattern_recognition_with_kernel(input, Kernel::Auto)
1085}
1086
1087pub fn pattern_recognition_with_kernel(
1088    input: &PatternRecognitionInput<'_>,
1089    kernel: Kernel,
1090) -> Result<PatternRecognitionOutput, PatternRecognitionError> {
1091    let _ = kernel;
1092    let (pattern_data, open, high, low, close, cols) = match &input.data {
1093        PatternRecognitionData::Candles { candles } => {
1094            (
1095                PatternData::Candles { candles },
1096                candles.open.as_slice(),
1097                candles.high.as_slice(),
1098                candles.low.as_slice(),
1099                candles.close.as_slice(),
1100                candles.close.len(),
1101            )
1102        }
1103        PatternRecognitionData::Slices {
1104            open,
1105            high,
1106            low,
1107            close,
1108        } => {
1109            if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
1110                return Err(PatternRecognitionError::DataLengthMismatch {
1111                    open: open.len(),
1112                    high: high.len(),
1113                    low: low.len(),
1114                    close: close.len(),
1115                });
1116            }
1117            (
1118                PatternData::Slices {
1119                    open,
1120                    high,
1121                    low,
1122                    close,
1123                },
1124                *open,
1125                *high,
1126                *low,
1127                *close,
1128                close.len(),
1129            )
1130        }
1131    };
1132
1133    let primitives = build_shared_primitives(open, high, low, close);
1134    debug_assert_eq!(primitives.body.len(), cols);
1135    debug_assert_eq!(primitives.range.len(), cols);
1136    debug_assert_eq!(primitives.upper_shadow.len(), cols);
1137    debug_assert_eq!(primitives.lower_shadow.len(), cols);
1138    debug_assert_eq!(primitives.direction.len(), cols);
1139    debug_assert_eq!(primitives.body_gap_up.len(), cols);
1140    debug_assert_eq!(primitives.body_gap_down.len(), cols);
1141
1142    let rows = PATTERN_RUNNERS.len();
1143    let mut matrix = make_uninit_u8_matrix(rows, cols);
1144
1145    for (row, runner) in PATTERN_RUNNERS.iter().enumerate() {
1146        let pattern_input = PatternInput {
1147            data: pattern_data.clone(),
1148            params: PatternParams {
1149                pattern_type: runner.pattern_type,
1150                penetration: 0.0,
1151            },
1152        };
1153        let out = (runner.run)(&pattern_input)?;
1154        if out.values.len() != cols {
1155            return Err(PatternRecognitionError::OutputLengthMismatch {
1156                pattern_id: runner.id,
1157                expected: cols,
1158                got: out.values.len(),
1159            });
1160        }
1161        let offset = row * cols;
1162        let dst = &mut matrix[offset..offset + cols];
1163        for (cell, value) in dst.iter_mut().zip(out.values.into_iter()) {
1164            cell.write(if value == 0 { 0 } else { 1 });
1165        }
1166    }
1167
1168    let pattern_ids = PATTERN_RUNNERS.iter().map(|x| x.id).collect();
1169    let values_u8 = unsafe { assume_init_u8(matrix) };
1170
1171    Ok(PatternRecognitionOutput {
1172        rows,
1173        cols,
1174        values_u8,
1175        pattern_ids,
1176        warmup: None,
1177    })
1178}
1179
1180pub fn extract_pattern_series<'a>(
1181    output: &'a PatternRecognitionOutput,
1182    pattern_id: &str,
1183) -> Option<&'a [u8]> {
1184    let row = output.pattern_ids.iter().position(|id| *id == pattern_id)?;
1185    let start = row.checked_mul(output.cols)?;
1186    let end = start.checked_add(output.cols)?;
1187    output.values_u8.get(start..end)
1188}
1189
1190pub fn pattern_hit(
1191    output: &PatternRecognitionOutput,
1192    pattern_id: &str,
1193    bar: usize,
1194) -> Option<bool> {
1195    let row = output.pattern_ids.iter().position(|id| *id == pattern_id)?;
1196    if bar >= output.cols {
1197        return None;
1198    }
1199    let idx = row.checked_mul(output.cols)?.checked_add(bar)?;
1200    output.values_u8.get(idx).map(|x| *x != 0)
1201}
1202
1203fn make_uninit_u8_matrix(rows: usize, cols: usize) -> Vec<MaybeUninit<u8>> {
1204    let total = rows
1205        .checked_mul(cols)
1206        .expect("rows * cols overflowed usize");
1207
1208    let mut v: Vec<MaybeUninit<u8>> = Vec::new();
1209    v.try_reserve_exact(total)
1210        .expect("OOM in make_uninit_u8_matrix");
1211
1212    #[cfg(not(debug_assertions))]
1213    unsafe {
1214        v.set_len(total);
1215    }
1216
1217    #[cfg(debug_assertions)]
1218    {
1219        for _ in 0..total {
1220            v.push(MaybeUninit::new(0xCD));
1221        }
1222    }
1223
1224    v
1225}
1226
1227unsafe fn assume_init_u8(mut v: Vec<MaybeUninit<u8>>) -> Vec<u8> {
1228    let ptr = v.as_mut_ptr() as *mut u8;
1229    let len = v.len();
1230    let cap = v.capacity();
1231    std::mem::forget(v);
1232    Vec::from_raw_parts(ptr, len, cap)
1233}
1234
1235#[derive(Debug, Error)]
1236pub enum PatternError {
1237    #[error("pattern_recognition: Not enough data points. Length={len}, pattern={pattern:?}")]
1238    NotEnoughData { len: usize, pattern: PatternType },
1239
1240    #[error("pattern_recognition: Candle field error: {0}")]
1241    CandleFieldError(String),
1242
1243    #[error("pattern_recognition: Unknown error occurred.")]
1244    Unknown,
1245}
1246
1247#[inline(always)]
1248fn candle_color(open: f64, close: f64) -> i32 {
1249    if close >= open {
1250        1
1251    } else {
1252        -1
1253    }
1254}
1255
1256#[inline(always)]
1257fn real_body(open: f64, close: f64) -> f64 {
1258    (close - open).abs()
1259}
1260
1261#[inline(always)]
1262fn candle_range(open: f64, close: f64) -> f64 {
1263    real_body(open, close)
1264}
1265
1266#[inline(always)]
1267fn upper_shadow(open: f64, high: f64, close: f64) -> f64 {
1268    if close >= open {
1269        high - close
1270    } else {
1271        high - open
1272    }
1273}
1274
1275#[inline(always)]
1276fn lower_shadow(open: f64, low: f64, close: f64) -> f64 {
1277    if close >= open {
1278        open - low
1279    } else {
1280        close - low
1281    }
1282}
1283
1284#[inline]
1285fn input_ohlc<'a>(
1286    data: &'a PatternData<'a>,
1287) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64]), PatternError> {
1288    match data {
1289        PatternData::Candles { candles } => {
1290            let open = candles
1291                .select_candle_field("open")
1292                .map_err(|e| PatternError::CandleFieldError(e.to_string()))?;
1293            let high = candles
1294                .select_candle_field("high")
1295                .map_err(|e| PatternError::CandleFieldError(e.to_string()))?;
1296            let low = candles
1297                .select_candle_field("low")
1298                .map_err(|e| PatternError::CandleFieldError(e.to_string()))?;
1299            let close = candles
1300                .select_candle_field("close")
1301                .map_err(|e| PatternError::CandleFieldError(e.to_string()))?;
1302            Ok((open, high, low, close))
1303        }
1304        PatternData::Slices {
1305            open,
1306            high,
1307            low,
1308            close,
1309        } => {
1310            if open.len() != high.len() || open.len() != low.len() || open.len() != close.len() {
1311                return Err(PatternError::CandleFieldError(
1312                    "open/high/low/close length mismatch".to_string(),
1313                ));
1314            }
1315            Ok((open, high, low, close))
1316        }
1317    }
1318}
1319
1320#[inline]
1321pub fn cdl2crows(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1322    const BODY_LONG_PERIOD: usize = 10;
1323
1324    let (open, high, low, close) = input_ohlc(&input.data)?;
1325
1326    let size = open.len();
1327    let lookback_total = 2 + BODY_LONG_PERIOD;
1328
1329    if size < lookback_total {
1330        return Err(PatternError::NotEnoughData {
1331            len: size,
1332            pattern: input.params.pattern_type.clone(),
1333        });
1334    }
1335
1336    let mut out = vec![0i8; size];
1337
1338    let mut body_long_period_total = 0.0;
1339    let body_long_trailing_start = 0;
1340    let body_long_trailing_end = BODY_LONG_PERIOD;
1341    for i in body_long_trailing_start..body_long_trailing_end {
1342        body_long_period_total += candle_range(open[i], close[i]);
1343    }
1344
1345    for i in lookback_total..size {
1346        let first_color = candle_color(open[i - 2], close[i - 2]);
1347        let first_body = real_body(open[i - 2], close[i - 2]);
1348        let body_long_avg = body_long_period_total / (BODY_LONG_PERIOD as f64);
1349
1350        let second_color = candle_color(open[i - 1], close[i - 1]);
1351        let third_color = candle_color(open[i], close[i]);
1352
1353        let second_rb_min = open[i - 1].min(close[i - 1]);
1354        let first_rb_max = open[i - 2].max(close[i - 2]);
1355        let real_body_gap_up = second_rb_min > first_rb_max;
1356
1357        let third_opens_in_2nd_body = open[i] < open[i - 1] && open[i] > close[i - 1];
1358
1359        let third_closes_in_1st_body = close[i] > open[i - 2] && close[i] < close[i - 2];
1360
1361        if (first_color == 1)
1362            && (first_body > body_long_avg)
1363            && (second_color == -1)
1364            && real_body_gap_up
1365            && (third_color == -1)
1366            && third_opens_in_2nd_body
1367            && third_closes_in_1st_body
1368        {
1369            out[i] = -100;
1370        } else {
1371            out[i] = 0;
1372        }
1373
1374        let old_idx = i - lookback_total;
1375        let new_idx = i - 2;
1376        body_long_period_total += candle_range(open[new_idx], close[new_idx])
1377            - candle_range(open[old_idx], close[old_idx]);
1378    }
1379
1380    Ok(PatternOutput { values: out })
1381}
1382
1383#[inline]
1384pub fn cdl3blackcrows(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1385    const SHADOW_VERY_SHORT_PERIOD: usize = 10;
1386
1387    let (open, high, low, close) = input_ohlc(&input.data)?;
1388
1389    let size = open.len();
1390    let lookback_total = 3 + SHADOW_VERY_SHORT_PERIOD;
1391    if size < lookback_total {
1392        return Err(PatternError::NotEnoughData {
1393            len: size,
1394            pattern: input.params.pattern_type.clone(),
1395        });
1396    }
1397
1398    let mut out = vec![0i8; size];
1399
1400    fn candle_color(o: f64, c: f64) -> i8 {
1401        if c >= o {
1402            1
1403        } else {
1404            -1
1405        }
1406    }
1407
1408    fn lower_shadow(o: f64, c: f64, l: f64) -> f64 {
1409        if c < o {
1410            c - l
1411        } else {
1412            o - l
1413        }
1414    }
1415
1416    let mut sum2 = 0.0;
1417    let mut sum1 = 0.0;
1418    let mut sum0 = 0.0;
1419    for i in 0..SHADOW_VERY_SHORT_PERIOD {
1420        sum2 += lower_shadow(open[i], close[i], low[i]);
1421        sum1 += lower_shadow(open[i + 1], close[i + 1], low[i + 1]);
1422        sum0 += lower_shadow(open[i + 2], close[i + 2], low[i + 2]);
1423    }
1424
1425    for i in lookback_total..size {
1426        let avg2 = sum2 / (SHADOW_VERY_SHORT_PERIOD as f64);
1427        let avg1 = sum1 / (SHADOW_VERY_SHORT_PERIOD as f64);
1428        let avg0 = sum0 / (SHADOW_VERY_SHORT_PERIOD as f64);
1429
1430        if candle_color(open[i - 3], close[i - 3]) == 1
1431            && candle_color(open[i - 2], close[i - 2]) == -1
1432            && lower_shadow(open[i - 2], close[i - 2], low[i - 2]) < avg2
1433            && candle_color(open[i - 1], close[i - 1]) == -1
1434            && lower_shadow(open[i - 1], close[i - 1], low[i - 1]) < avg1
1435            && candle_color(open[i], close[i]) == -1
1436            && lower_shadow(open[i], close[i], low[i]) < avg0
1437            && open[i - 1] < open[i - 2]
1438            && open[i - 1] > close[i - 2]
1439            && open[i] < open[i - 1]
1440            && open[i] > close[i - 1]
1441            && high[i - 3] > close[i - 2]
1442            && close[i - 2] > close[i - 1]
1443            && close[i - 1] > close[i]
1444        {
1445            out[i] = -100;
1446        } else {
1447            out[i] = 0;
1448        }
1449
1450        let old_idx2 = i - lookback_total;
1451        let new_idx2 = i - 2;
1452        sum2 += lower_shadow(open[new_idx2], close[new_idx2], low[new_idx2])
1453            - lower_shadow(open[old_idx2], close[old_idx2], low[old_idx2]);
1454
1455        let old_idx1 = i - lookback_total + 1;
1456        let new_idx1 = i - 1;
1457        sum1 += lower_shadow(open[new_idx1], close[new_idx1], low[new_idx1])
1458            - lower_shadow(open[old_idx1], close[old_idx1], low[old_idx1]);
1459
1460        let old_idx0 = i - lookback_total + 2;
1461        let new_idx0 = i;
1462        sum0 += lower_shadow(open[new_idx0], close[new_idx0], low[new_idx0])
1463            - lower_shadow(open[old_idx0], close[old_idx0], low[old_idx0]);
1464    }
1465
1466    Ok(PatternOutput { values: out })
1467}
1468
1469#[inline]
1470pub fn cdl3inside(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1471    const BODY_LONG_PERIOD: usize = 10;
1472    const BODY_SHORT_PERIOD: usize = 10;
1473
1474    let (open, high, low, close) = input_ohlc(&input.data)?;
1475
1476    let size = open.len();
1477    let lookback_total = 2 + BODY_LONG_PERIOD.max(BODY_SHORT_PERIOD);
1478    if size < lookback_total {
1479        return Err(PatternError::NotEnoughData {
1480            len: size,
1481            pattern: input.params.pattern_type.clone(),
1482        });
1483    }
1484
1485    fn candle_color(o: f64, c: f64) -> i8 {
1486        if c >= o {
1487            1
1488        } else {
1489            -1
1490        }
1491    }
1492
1493    fn candle_range(o: f64, c: f64) -> f64 {
1494        (c - o).abs()
1495    }
1496
1497    fn real_body(o: f64, c: f64) -> f64 {
1498        (c - o).abs()
1499    }
1500
1501    fn max2(a: f64, b: f64) -> f64 {
1502        if a > b {
1503            a
1504        } else {
1505            b
1506        }
1507    }
1508
1509    fn min2(a: f64, b: f64) -> f64 {
1510        if a < b {
1511            a
1512        } else {
1513            b
1514        }
1515    }
1516
1517    let mut out = vec![0i8; size];
1518
1519    let mut body_long_period_total = 0.0;
1520    let mut body_short_period_total = 0.0;
1521
1522    for i in 0..BODY_LONG_PERIOD {
1523        body_long_period_total += candle_range(open[i], close[i]);
1524    }
1525    for i in 0..BODY_SHORT_PERIOD {
1526        body_short_period_total += candle_range(open[i], close[i]);
1527    }
1528
1529    for i in lookback_total..size {
1530        let avg_body_long = body_long_period_total / BODY_LONG_PERIOD as f64;
1531        let avg_body_short = body_short_period_total / BODY_SHORT_PERIOD as f64;
1532
1533        if real_body(open[i - 2], close[i - 2]) > avg_body_long
1534            && real_body(open[i - 1], close[i - 1]) <= avg_body_short
1535            && max2(close[i - 1], open[i - 1]) < max2(close[i - 2], open[i - 2])
1536            && min2(close[i - 1], open[i - 1]) > min2(close[i - 2], open[i - 2])
1537            && ((candle_color(open[i - 2], close[i - 2]) == 1
1538                && candle_color(open[i], close[i]) == -1
1539                && close[i] < open[i - 2])
1540                || (candle_color(open[i - 2], close[i - 2]) == -1
1541                    && candle_color(open[i], close[i]) == 1
1542                    && close[i] > open[i - 2]))
1543        {
1544            out[i] = -candle_color(open[i - 2], close[i - 2]) * 100;
1545        } else {
1546            out[i] = 0;
1547        }
1548
1549        let old_idx_long = i - lookback_total;
1550        body_long_period_total += candle_range(open[i - 2], close[i - 2])
1551            - candle_range(open[old_idx_long], close[old_idx_long]);
1552
1553        let old_idx_short = i - lookback_total + 1;
1554        body_short_period_total += candle_range(open[i - 1], close[i - 1])
1555            - candle_range(open[old_idx_short], close[old_idx_short]);
1556    }
1557
1558    Ok(PatternOutput { values: out })
1559}
1560
1561#[inline]
1562pub fn cdl3linestrike(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1563    const NEAR_PERIOD: usize = 10;
1564
1565    let (open, high, low, close) = input_ohlc(&input.data)?;
1566
1567    let size = open.len();
1568    let lookback_total = 3 + NEAR_PERIOD;
1569    if size < lookback_total {
1570        return Err(PatternError::NotEnoughData {
1571            len: size,
1572            pattern: input.params.pattern_type.clone(),
1573        });
1574    }
1575
1576    fn candle_color(o: f64, c: f64) -> i8 {
1577        if c >= o {
1578            1
1579        } else {
1580            -1
1581        }
1582    }
1583
1584    fn candle_range(o: f64, c: f64) -> f64 {
1585        (c - o).abs()
1586    }
1587
1588    fn max2(a: f64, b: f64) -> f64 {
1589        if a > b {
1590            a
1591        } else {
1592            b
1593        }
1594    }
1595
1596    fn min2(a: f64, b: f64) -> f64 {
1597        if a < b {
1598            a
1599        } else {
1600            b
1601        }
1602    }
1603
1604    let mut out = vec![0i8; size];
1605    let mut sum3 = 0.0;
1606    let mut sum2 = 0.0;
1607
1608    for i in 0..NEAR_PERIOD {
1609        sum3 += candle_range(open[i], close[i]);
1610        sum2 += candle_range(open[i + 1], close[i + 1]);
1611    }
1612
1613    for i in lookback_total..size {
1614        let avg3 = sum3 / (NEAR_PERIOD as f64);
1615        let avg2 = sum2 / (NEAR_PERIOD as f64);
1616
1617        if candle_color(open[i - 3], close[i - 3]) == candle_color(open[i - 2], close[i - 2])
1618            && candle_color(open[i - 2], close[i - 2]) == candle_color(open[i - 1], close[i - 1])
1619            && candle_color(open[i], close[i]) == -candle_color(open[i - 1], close[i - 1])
1620            && open[i - 2] >= min2(open[i - 3], close[i - 3]) - avg3
1621            && open[i - 2] <= max2(open[i - 3], close[i - 3]) + avg3
1622            && open[i - 1] >= min2(open[i - 2], close[i - 2]) - avg2
1623            && open[i - 1] <= max2(open[i - 2], close[i - 2]) + avg2
1624            && ((candle_color(open[i - 1], close[i - 1]) == 1
1625                && close[i - 1] > close[i - 2]
1626                && close[i - 2] > close[i - 3]
1627                && open[i] > close[i - 1]
1628                && close[i] < open[i - 3])
1629                || (candle_color(open[i - 1], close[i - 1]) == -1
1630                    && close[i - 1] < close[i - 2]
1631                    && close[i - 2] < close[i - 3]
1632                    && open[i] < close[i - 1]
1633                    && close[i] > open[i - 3]))
1634        {
1635            out[i] = candle_color(open[i - 1], close[i - 1]) * 100;
1636        } else {
1637            out[i] = 0;
1638        }
1639
1640        let old_idx3 = i - lookback_total;
1641        let new_idx3 = i - 3;
1642        sum3 += candle_range(open[new_idx3], close[new_idx3])
1643            - candle_range(open[old_idx3], close[old_idx3]);
1644
1645        let old_idx2 = i - lookback_total + 1;
1646        let new_idx2 = i - 2;
1647        sum2 += candle_range(open[new_idx2], close[new_idx2])
1648            - candle_range(open[old_idx2], close[old_idx2]);
1649    }
1650
1651    Ok(PatternOutput { values: out })
1652}
1653
1654#[inline]
1655pub fn cdl3outside(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1656    let (open, high, low, close) = input_ohlc(&input.data)?;
1657
1658    fn candle_color(o: f64, c: f64) -> i8 {
1659        if c >= o {
1660            1
1661        } else {
1662            -1
1663        }
1664    }
1665
1666    let size = open.len();
1667    let lookback_total = 2;
1668
1669    if size < lookback_total + 1 {
1670        return Err(PatternError::NotEnoughData {
1671            len: size,
1672            pattern: input.params.pattern_type.clone(),
1673        });
1674    }
1675
1676    let mut out = vec![0i8; size];
1677
1678    for i in lookback_total..size {
1679        let second_candle_color = candle_color(open[i - 1], close[i - 1]);
1680        let first_candle_color = candle_color(open[i - 2], close[i - 2]);
1681
1682        let white_engulfs_black = second_candle_color == 1
1683            && first_candle_color == -1
1684            && close[i - 1] > open[i - 2]
1685            && open[i - 1] < close[i - 2]
1686            && close[i] > close[i - 1];
1687
1688        let black_engulfs_white = second_candle_color == -1
1689            && first_candle_color == 1
1690            && open[i - 1] > close[i - 2]
1691            && close[i - 1] < open[i - 2]
1692            && close[i] < close[i - 1];
1693
1694        if white_engulfs_black || black_engulfs_white {
1695            out[i] = second_candle_color * 100;
1696        } else {
1697            out[i] = 0;
1698        }
1699    }
1700
1701    Ok(PatternOutput { values: out })
1702}
1703
1704#[inline]
1705pub fn cdl3starsinsouth(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1706    const BODY_LONG_PERIOD: usize = 10;
1707    const SHADOW_LONG_PERIOD: usize = 10;
1708    const SHADOW_VERY_SHORT_PERIOD: usize = 10;
1709    const BODY_SHORT_PERIOD: usize = 10;
1710
1711    let (open, high, low, close) = input_ohlc(&input.data)?;
1712
1713    fn candle_color(o: f64, c: f64) -> i8 {
1714        if c >= o {
1715            1
1716        } else {
1717            -1
1718        }
1719    }
1720    fn real_body(o: f64, c: f64) -> f64 {
1721        (c - o).abs()
1722    }
1723    fn lower_shadow(o: f64, c: f64, l: f64) -> f64 {
1724        if c < o {
1725            c - l
1726        } else {
1727            o - l
1728        }
1729    }
1730    fn upper_shadow(o: f64, c: f64, h: f64) -> f64 {
1731        if c < o {
1732            h - o
1733        } else {
1734            h - c
1735        }
1736    }
1737    fn candle_range(o: f64, c: f64) -> f64 {
1738        (c - o).abs()
1739    }
1740
1741    let size = open.len();
1742    let lookback_total = 2 + BODY_LONG_PERIOD
1743        .max(SHADOW_LONG_PERIOD)
1744        .max(SHADOW_VERY_SHORT_PERIOD)
1745        .max(BODY_SHORT_PERIOD);
1746
1747    if size < lookback_total {
1748        return Err(PatternError::NotEnoughData {
1749            len: size,
1750            pattern: input.params.pattern_type.clone(),
1751        });
1752    }
1753
1754    let mut out = vec![0i8; size];
1755
1756    let mut body_long_sum = 0.0;
1757    let mut shadow_long_sum = 0.0;
1758    let mut shadow_very_short_sum_1 = 0.0;
1759    let mut shadow_very_short_sum_0 = 0.0;
1760    let mut body_short_sum = 0.0;
1761
1762    let body_long_trail_start = lookback_total - BODY_LONG_PERIOD;
1763    for idx in body_long_trail_start..lookback_total {
1764        let ref_index = if idx >= 2 { idx - 2 } else { 0 };
1765        body_long_sum += candle_range(open[ref_index], close[ref_index]);
1766    }
1767
1768    let shadow_long_trail_start = lookback_total - SHADOW_LONG_PERIOD;
1769    for idx in shadow_long_trail_start..lookback_total {
1770        let ref_index = if idx >= 2 { idx - 2 } else { 0 };
1771        shadow_long_sum += candle_range(open[ref_index], close[ref_index]);
1772    }
1773
1774    let shadow_very_short_trail_start = lookback_total - SHADOW_VERY_SHORT_PERIOD;
1775    for idx in shadow_very_short_trail_start..lookback_total {
1776        let ref_index_1 = if idx >= 1 { idx - 1 } else { 0 };
1777        shadow_very_short_sum_1 +=
1778            lower_shadow(open[ref_index_1], close[ref_index_1], low[ref_index_1]);
1779
1780        shadow_very_short_sum_0 += lower_shadow(open[idx], close[idx], low[idx]);
1781    }
1782
1783    let body_short_trail_start = lookback_total - BODY_SHORT_PERIOD;
1784    for idx in body_short_trail_start..lookback_total {
1785        body_short_sum += candle_range(open[idx], close[idx]);
1786    }
1787    for i in lookback_total..size {
1788        let avg_body_long = body_long_sum / (BODY_LONG_PERIOD as f64);
1789        let avg_shadow_long = shadow_long_sum / (SHADOW_LONG_PERIOD as f64);
1790        let avg_shadow_very_short_1 = shadow_very_short_sum_1 / (SHADOW_VERY_SHORT_PERIOD as f64);
1791        let avg_shadow_very_short_0 = shadow_very_short_sum_0 / (SHADOW_VERY_SHORT_PERIOD as f64);
1792        let avg_body_short = body_short_sum / (BODY_SHORT_PERIOD as f64);
1793
1794        if candle_color(open[i - 2], close[i - 2]) == -1
1795            && candle_color(open[i - 1], close[i - 1]) == -1
1796            && candle_color(open[i], close[i]) == -1
1797            && real_body(open[i - 2], close[i - 2]) > avg_body_long
1798            && lower_shadow(open[i - 2], close[i - 2], low[i - 2]) > avg_shadow_long
1799            && real_body(open[i - 1], close[i - 1]) < real_body(open[i - 2], close[i - 2])
1800            && open[i - 1] > close[i - 2]
1801            && open[i - 1] <= high[i - 2]
1802            && low[i - 1] < close[i - 2]
1803            && low[i - 1] >= low[i - 2]
1804            && lower_shadow(open[i - 1], close[i - 1], low[i - 1]) > avg_shadow_very_short_1
1805            && real_body(open[i], close[i]) < avg_body_short
1806            && lower_shadow(open[i], close[i], low[i]) < avg_shadow_very_short_0
1807            && upper_shadow(open[i], close[i], high[i]) < avg_shadow_very_short_0
1808            && low[i] > low[i - 1]
1809            && high[i] < high[i - 1]
1810        {
1811            out[i] = 100;
1812        } else {
1813            out[i] = 0;
1814        }
1815
1816        let old_idx = i - lookback_total;
1817
1818        {
1819            let old_ref = if old_idx >= 2 { old_idx - 2 } else { 0 };
1820            let new_ref = i - 2;
1821            body_long_sum += candle_range(open[new_ref], close[new_ref])
1822                - candle_range(open[old_ref], close[old_ref]);
1823        }
1824
1825        {
1826            let old_ref = if old_idx >= 2 { old_idx - 2 } else { 0 };
1827            let new_ref = i - 2;
1828            shadow_long_sum += candle_range(open[new_ref], close[new_ref])
1829                - candle_range(open[old_ref], close[old_ref]);
1830        }
1831
1832        {
1833            let old_ref_1 = if old_idx >= 1 { old_idx - 1 } else { 0 };
1834            let new_ref_1 = i - 1;
1835            shadow_very_short_sum_1 +=
1836                lower_shadow(open[new_ref_1], close[new_ref_1], low[new_ref_1])
1837                    - lower_shadow(open[old_ref_1], close[old_ref_1], low[old_ref_1]);
1838        }
1839        {
1840            shadow_very_short_sum_0 += lower_shadow(open[i], close[i], low[i])
1841                - lower_shadow(open[old_idx], close[old_idx], low[old_idx]);
1842        }
1843
1844        {
1845            body_short_sum +=
1846                candle_range(open[i], close[i]) - candle_range(open[old_idx], close[old_idx]);
1847        }
1848    }
1849
1850    Ok(PatternOutput { values: out })
1851}
1852
1853#[inline]
1854pub fn cdl3whitesoldiers(input: &PatternInput) -> Result<PatternOutput, PatternError> {
1855    const SHADOW_VERY_SHORT_PERIOD: usize = 10;
1856    const NEAR_PERIOD: usize = 10;
1857    const FAR_PERIOD: usize = 10;
1858    const BODY_SHORT_PERIOD: usize = 10;
1859
1860    let (open, high, low, close) = input_ohlc(&input.data)?;
1861
1862    fn candle_color(o: f64, c: f64) -> i8 {
1863        if c >= o {
1864            1
1865        } else {
1866            -1
1867        }
1868    }
1869
1870    fn candle_range(o: f64, c: f64) -> f64 {
1871        (c - o).abs()
1872    }
1873
1874    fn real_body(o: f64, c: f64) -> f64 {
1875        (c - o).abs()
1876    }
1877
1878    fn upper_shadow(o: f64, c: f64, h: f64) -> f64 {
1879        if c < o {
1880            h - o
1881        } else {
1882            h - c
1883        }
1884    }
1885
1886    fn max2(a: f64, b: f64) -> f64 {
1887        if a > b {
1888            a
1889        } else {
1890            b
1891        }
1892    }
1893
1894    let size = open.len();
1895    let lookback_total = 2 + SHADOW_VERY_SHORT_PERIOD
1896        .max(NEAR_PERIOD)
1897        .max(FAR_PERIOD)
1898        .max(BODY_SHORT_PERIOD);
1899
1900    if size < lookback_total {
1901        return Err(PatternError::NotEnoughData {
1902            len: size,
1903            pattern: input.params.pattern_type.clone(),
1904        });
1905    }
1906
1907    let mut out = vec![0i8; size];
1908    let mut shadow_very_short_sum = [0.0; 3];
1909    let mut near_sum = [0.0; 3];
1910    let mut far_sum = [0.0; 3];
1911    let mut body_short_sum = 0.0;
1912
1913    for i in 0..SHADOW_VERY_SHORT_PERIOD {
1914        shadow_very_short_sum[2] += upper_shadow(open[i], close[i], high[i]);
1915        if i + 1 < size {
1916            shadow_very_short_sum[1] += upper_shadow(open[i + 1], close[i + 1], high[i + 1]);
1917        }
1918        if i + 2 < size {
1919            shadow_very_short_sum[0] += upper_shadow(open[i + 2], close[i + 2], high[i + 2]);
1920        }
1921    }
1922    for i in 0..NEAR_PERIOD {
1923        if i + 2 < size {
1924            near_sum[2] += candle_range(open[i + 2], close[i + 2]);
1925        }
1926        if i + 1 < size {
1927            near_sum[1] += candle_range(open[i + 1], close[i + 1]);
1928        }
1929    }
1930    for i in 0..FAR_PERIOD {
1931        if i + 2 < size {
1932            far_sum[2] += candle_range(open[i + 2], close[i + 2]);
1933        }
1934        if i + 1 < size {
1935            far_sum[1] += candle_range(open[i + 1], close[i + 1]);
1936        }
1937    }
1938    for i in 0..BODY_SHORT_PERIOD {
1939        body_short_sum += candle_range(open[i], close[i]);
1940    }
1941
1942    for i in lookback_total..size {
1943        let avg_sv_2 = shadow_very_short_sum[2] / (SHADOW_VERY_SHORT_PERIOD as f64);
1944        let avg_sv_1 = shadow_very_short_sum[1] / (SHADOW_VERY_SHORT_PERIOD as f64);
1945        let avg_sv_0 = shadow_very_short_sum[0] / (SHADOW_VERY_SHORT_PERIOD as f64);
1946        let avg_near_2 = near_sum[2] / (NEAR_PERIOD as f64);
1947        let avg_near_1 = near_sum[1] / (NEAR_PERIOD as f64);
1948        let avg_far_2 = far_sum[2] / (FAR_PERIOD as f64);
1949        let avg_far_1 = far_sum[1] / (FAR_PERIOD as f64);
1950        let avg_body_short = body_short_sum / (BODY_SHORT_PERIOD as f64);
1951
1952        if candle_color(open[i - 2], close[i - 2]) == 1
1953            && upper_shadow(open[i - 2], close[i - 2], high[i - 2]) < avg_sv_2
1954            && candle_color(open[i - 1], close[i - 1]) == 1
1955            && upper_shadow(open[i - 1], close[i - 1], high[i - 1]) < avg_sv_1
1956            && candle_color(open[i], close[i]) == 1
1957            && upper_shadow(open[i], close[i], high[i]) < avg_sv_0
1958            && close[i] > close[i - 1]
1959            && close[i - 1] > close[i - 2]
1960            && open[i - 1] > open[i - 2]
1961            && open[i - 1] <= close[i - 2] + avg_near_2
1962            && open[i] > open[i - 1]
1963            && open[i] <= close[i - 1] + avg_near_1
1964            && real_body(open[i - 1], close[i - 1])
1965                > real_body(open[i - 2], close[i - 2]) - avg_far_2
1966            && real_body(open[i], close[i]) > real_body(open[i - 1], close[i - 1]) - avg_far_1
1967            && real_body(open[i], close[i]) > avg_body_short
1968        {
1969            out[i] = 100;
1970        } else {
1971            out[i] = 0;
1972        }
1973
1974        let old_idx = i - lookback_total;
1975        shadow_very_short_sum[2] += upper_shadow(open[i - 2], close[i - 2], high[i - 2])
1976            - upper_shadow(
1977                open[old_idx.saturating_sub(2)],
1978                close[old_idx.saturating_sub(2)],
1979                high[old_idx.saturating_sub(2)],
1980            );
1981        shadow_very_short_sum[1] += upper_shadow(open[i - 1], close[i - 1], high[i - 1])
1982            - upper_shadow(
1983                open[old_idx.saturating_sub(1)],
1984                close[old_idx.saturating_sub(1)],
1985                high[old_idx.saturating_sub(1)],
1986            );
1987        shadow_very_short_sum[0] += upper_shadow(open[i], close[i], high[i])
1988            - upper_shadow(open[old_idx], close[old_idx], high[old_idx]);
1989
1990        far_sum[2] += candle_range(open[i - 2], close[i - 2])
1991            - candle_range(
1992                open[old_idx.saturating_sub(2)],
1993                close[old_idx.saturating_sub(2)],
1994            );
1995        far_sum[1] += candle_range(open[i - 1], close[i - 1])
1996            - candle_range(
1997                open[old_idx.saturating_sub(1)],
1998                close[old_idx.saturating_sub(1)],
1999            );
2000
2001        near_sum[2] += candle_range(open[i - 2], close[i - 2])
2002            - candle_range(
2003                open[old_idx.saturating_sub(2)],
2004                close[old_idx.saturating_sub(2)],
2005            );
2006        near_sum[1] += candle_range(open[i - 1], close[i - 1])
2007            - candle_range(
2008                open[old_idx.saturating_sub(1)],
2009                close[old_idx.saturating_sub(1)],
2010            );
2011
2012        body_short_sum +=
2013            candle_range(open[i], close[i]) - candle_range(open[old_idx], close[old_idx]);
2014    }
2015
2016    Ok(PatternOutput { values: out })
2017}
2018
2019#[inline]
2020pub fn cdlabandonedbaby(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2021    const BODY_LONG_PERIOD: usize = 10;
2022    const BODY_DOJI_PERIOD: usize = 10;
2023    const BODY_SHORT_PERIOD: usize = 10;
2024
2025    let (open, high, low, close) = input_ohlc(&input.data)?;
2026
2027    let penetration = input.params.penetration;
2028
2029    fn candle_color(o: f64, c: f64) -> i8 {
2030        if c >= o {
2031            1
2032        } else {
2033            -1
2034        }
2035    }
2036
2037    fn real_body(o: f64, c: f64) -> f64 {
2038        (c - o).abs()
2039    }
2040
2041    fn candle_range(o: f64, c: f64) -> f64 {
2042        (c - o).abs()
2043    }
2044
2045    fn candle_gap_up(idx1: usize, idx2: usize, low: &[f64], high: &[f64]) -> bool {
2046        low[idx1] > high[idx2]
2047    }
2048
2049    fn candle_gap_down(idx1: usize, idx2: usize, low: &[f64], high: &[f64]) -> bool {
2050        high[idx1] < low[idx2]
2051    }
2052
2053    let size = open.len();
2054    let lookback_total = 2 + BODY_LONG_PERIOD
2055        .max(BODY_DOJI_PERIOD)
2056        .max(BODY_SHORT_PERIOD);
2057
2058    if size < lookback_total {
2059        return Err(PatternError::NotEnoughData {
2060            len: size,
2061            pattern: input.params.pattern_type.clone(),
2062        });
2063    }
2064
2065    let mut out = vec![0i8; size];
2066    let mut body_long_sum = 0.0;
2067    let mut body_doji_sum = 0.0;
2068    let mut body_short_sum = 0.0;
2069
2070    for i in 0..BODY_LONG_PERIOD {
2071        body_long_sum += candle_range(open[i], close[i]);
2072    }
2073    for i in 0..BODY_DOJI_PERIOD {
2074        body_doji_sum += candle_range(open[i], close[i]);
2075    }
2076    for i in 0..BODY_SHORT_PERIOD {
2077        body_short_sum += candle_range(open[i], close[i]);
2078    }
2079
2080    for i in lookback_total..size {
2081        let avg_body_long = body_long_sum / BODY_LONG_PERIOD as f64;
2082        let avg_body_doji = body_doji_sum / BODY_DOJI_PERIOD as f64;
2083        let avg_body_short = body_short_sum / BODY_SHORT_PERIOD as f64;
2084
2085        if real_body(open[i - 2], close[i - 2]) > avg_body_long
2086            && real_body(open[i - 1], close[i - 1]) <= avg_body_doji
2087            && real_body(open[i], close[i]) > avg_body_short
2088            && ((candle_color(open[i - 2], close[i - 2]) == 1
2089                && candle_color(open[i], close[i]) == -1
2090                && close[i] < close[i - 2] - real_body(open[i - 2], close[i - 2]) * penetration
2091                && candle_gap_up(i - 1, i - 2, &low, &high)
2092                && candle_gap_down(i, i - 1, &low, &high))
2093                || (candle_color(open[i - 2], close[i - 2]) == -1
2094                    && candle_color(open[i], close[i]) == 1
2095                    && close[i]
2096                        > close[i - 2] + real_body(open[i - 2], close[i - 2]) * penetration
2097                    && candle_gap_down(i - 1, i - 2, &low, &high)
2098                    && candle_gap_up(i, i - 1, &low, &high)))
2099        {
2100            out[i] = candle_color(open[i], close[i]) * 100;
2101        } else {
2102            out[i] = 0;
2103        }
2104
2105        let old_idx = i - lookback_total;
2106        body_long_sum += candle_range(open[i - 2], close[i - 2])
2107            - candle_range(
2108                open[old_idx.saturating_sub(2)],
2109                close[old_idx.saturating_sub(2)],
2110            );
2111        body_doji_sum += candle_range(open[i - 1], close[i - 1])
2112            - candle_range(
2113                open[old_idx.saturating_sub(1)],
2114                close[old_idx.saturating_sub(1)],
2115            );
2116        body_short_sum +=
2117            candle_range(open[i], close[i]) - candle_range(open[old_idx], close[old_idx]);
2118    }
2119
2120    Ok(PatternOutput { values: out })
2121}
2122
2123#[inline]
2124pub fn cdladvanceblock(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2125    let (open, high, low, close) = input_ohlc(&input.data)?;
2126
2127    let size = open.len();
2128    let shadow_short_period = 10;
2129    let shadow_long_period = 10;
2130    let near_period = 5;
2131    let far_period = 5;
2132    let body_long_period = 10;
2133    let lookback_total = 2 + shadow_short_period
2134        .max(shadow_long_period)
2135        .max(near_period)
2136        .max(far_period)
2137        .max(body_long_period);
2138
2139    if size < lookback_total {
2140        return Err(PatternError::NotEnoughData {
2141            len: size,
2142            pattern: input.params.pattern_type.clone(),
2143        });
2144    }
2145
2146    let mut out = vec![0i8; size];
2147
2148    let mut shadow_short_period_total = [0.0; 3];
2149    let mut shadow_long_period_total = [0.0; 2];
2150    let mut near_period_total = [0.0; 3];
2151    let mut far_period_total = [0.0; 3];
2152    let mut body_long_period_total = 0.0;
2153
2154    #[inline(always)]
2155    fn upper_shadow(o: f64, h: f64, c: f64) -> f64 {
2156        if c >= o {
2157            h - c
2158        } else {
2159            h - o
2160        }
2161    }
2162
2163    #[inline(always)]
2164    fn candle_average(sum: f64, period: usize) -> f64 {
2165        if period == 0 {
2166            0.0
2167        } else {
2168            sum / period as f64
2169        }
2170    }
2171
2172    let start_idx = lookback_total;
2173    let mut shadow_short_trailing_idx = start_idx.saturating_sub(shadow_short_period);
2174    let mut shadow_long_trailing_idx = start_idx.saturating_sub(shadow_long_period);
2175    let mut near_trailing_idx = start_idx.saturating_sub(near_period);
2176    let mut far_trailing_idx = start_idx.saturating_sub(far_period);
2177    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2178
2179    let mut i = shadow_short_trailing_idx;
2180    while i < start_idx {
2181        shadow_short_period_total[2] += upper_shadow(
2182            open[i.saturating_sub(2)],
2183            high[i.saturating_sub(2)],
2184            close[i.saturating_sub(2)],
2185        );
2186        shadow_short_period_total[1] += upper_shadow(
2187            open[i.saturating_sub(1)],
2188            high[i.saturating_sub(1)],
2189            close[i.saturating_sub(1)],
2190        );
2191        shadow_short_period_total[0] += upper_shadow(open[i], high[i], close[i]);
2192        i += 1;
2193    }
2194    i = shadow_long_trailing_idx;
2195    while i < start_idx {
2196        shadow_long_period_total[1] += upper_shadow(
2197            open[i.saturating_sub(1)],
2198            high[i.saturating_sub(1)],
2199            close[i.saturating_sub(1)],
2200        );
2201        shadow_long_period_total[0] += upper_shadow(open[i], high[i], close[i]);
2202        i += 1;
2203    }
2204    i = near_trailing_idx;
2205    while i < start_idx {
2206        near_period_total[2] += real_body(open[i.saturating_sub(2)], close[i.saturating_sub(2)]);
2207        near_period_total[1] += real_body(open[i.saturating_sub(1)], close[i.saturating_sub(1)]);
2208        i += 1;
2209    }
2210    i = far_trailing_idx;
2211    while i < start_idx {
2212        far_period_total[2] += real_body(open[i.saturating_sub(2)], close[i.saturating_sub(2)]);
2213        far_period_total[1] += real_body(open[i.saturating_sub(1)], close[i.saturating_sub(1)]);
2214        i += 1;
2215    }
2216    i = body_long_trailing_idx;
2217    while i < start_idx {
2218        body_long_period_total += real_body(open[i.saturating_sub(2)], close[i.saturating_sub(2)]);
2219        i += 1;
2220    }
2221
2222    let mut idx = start_idx;
2223    while idx < size {
2224        let c1_color = candle_color(open[idx - 2], close[idx - 2]);
2225        let c2_color = candle_color(open[idx - 1], close[idx - 1]);
2226        let c3_color = candle_color(open[idx], close[idx]);
2227
2228        if c1_color == 1
2229            && c2_color == 1
2230            && c3_color == 1
2231            && close[idx] > close[idx - 1]
2232            && close[idx - 1] > close[idx - 2]
2233            && open[idx - 1] > open[idx - 2]
2234            && open[idx - 1] <= close[idx - 2] + candle_average(near_period_total[2], near_period)
2235            && open[idx] > open[idx - 1]
2236            && open[idx] <= close[idx - 1] + candle_average(near_period_total[1], near_period)
2237            && real_body(open[idx - 2], close[idx - 2])
2238                > candle_average(body_long_period_total, body_long_period)
2239            && upper_shadow(open[idx - 2], high[idx - 2], close[idx - 2])
2240                < candle_average(shadow_short_period_total[2], shadow_short_period)
2241            && ((real_body(open[idx - 1], close[idx - 1])
2242                < real_body(open[idx - 2], close[idx - 2])
2243                    - candle_average(far_period_total[2], far_period)
2244                && real_body(open[idx], close[idx])
2245                    < real_body(open[idx - 1], close[idx - 1])
2246                        + candle_average(near_period_total[1], near_period))
2247                || (real_body(open[idx], close[idx])
2248                    < real_body(open[idx - 1], close[idx - 1])
2249                        - candle_average(far_period_total[1], far_period))
2250                || (real_body(open[idx], close[idx]) < real_body(open[idx - 1], close[idx - 1])
2251                    && real_body(open[idx - 1], close[idx - 1])
2252                        < real_body(open[idx - 2], close[idx - 2])
2253                    && (upper_shadow(open[idx], high[idx], close[idx])
2254                        > candle_average(shadow_short_period_total[0], shadow_short_period)
2255                        || upper_shadow(open[idx - 1], high[idx - 1], close[idx - 1])
2256                            > candle_average(shadow_short_period_total[1], shadow_short_period)))
2257                || (real_body(open[idx], close[idx]) < real_body(open[idx - 1], close[idx - 1])
2258                    && upper_shadow(open[idx], high[idx], close[idx])
2259                        > candle_average(shadow_long_period_total[0], shadow_long_period)))
2260        {
2261            out[idx] = -100;
2262        }
2263
2264        for tot_idx in (0..=2).rev() {
2265            if tot_idx < 3 {
2266                shadow_short_period_total[tot_idx] += upper_shadow(
2267                    open[idx.saturating_sub(tot_idx)],
2268                    high[idx.saturating_sub(tot_idx)],
2269                    close[idx.saturating_sub(tot_idx)],
2270                ) - upper_shadow(
2271                    open[shadow_short_trailing_idx.saturating_sub(tot_idx)],
2272                    high[shadow_short_trailing_idx.saturating_sub(tot_idx)],
2273                    close[shadow_short_trailing_idx.saturating_sub(tot_idx)],
2274                );
2275            }
2276        }
2277
2278        for tot_idx in (0..=1).rev() {
2279            shadow_long_period_total[tot_idx] += upper_shadow(
2280                open[idx.saturating_sub(tot_idx)],
2281                high[idx.saturating_sub(tot_idx)],
2282                close[idx.saturating_sub(tot_idx)],
2283            ) - upper_shadow(
2284                open[shadow_long_trailing_idx.saturating_sub(tot_idx)],
2285                high[shadow_long_trailing_idx.saturating_sub(tot_idx)],
2286                close[shadow_long_trailing_idx.saturating_sub(tot_idx)],
2287            );
2288        }
2289
2290        for tot_idx in (1..=2).rev() {
2291            far_period_total[tot_idx] += real_body(
2292                open[idx.saturating_sub(tot_idx)],
2293                close[idx.saturating_sub(tot_idx)],
2294            ) - real_body(
2295                open[far_trailing_idx.saturating_sub(tot_idx)],
2296                close[far_trailing_idx.saturating_sub(tot_idx)],
2297            );
2298            near_period_total[tot_idx] += real_body(
2299                open[idx.saturating_sub(tot_idx)],
2300                close[idx.saturating_sub(tot_idx)],
2301            ) - real_body(
2302                open[near_trailing_idx.saturating_sub(tot_idx)],
2303                close[near_trailing_idx.saturating_sub(tot_idx)],
2304            );
2305        }
2306
2307        body_long_period_total += real_body(open[idx - 2], close[idx - 2])
2308            - real_body(
2309                open[body_long_trailing_idx.saturating_sub(2)],
2310                close[body_long_trailing_idx.saturating_sub(2)],
2311            );
2312
2313        idx += 1;
2314        shadow_short_trailing_idx += 1;
2315        shadow_long_trailing_idx += 1;
2316        near_trailing_idx += 1;
2317        far_trailing_idx += 1;
2318        body_long_trailing_idx += 1;
2319    }
2320
2321    Ok(PatternOutput { values: out })
2322}
2323
2324#[inline]
2325pub fn cdlbelthold(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2326    let (open, high, low, close) = input_ohlc(&input.data)?;
2327
2328    let size = open.len();
2329    let body_long_period = 10;
2330    let shadow_very_short_period = 10;
2331    let lookback_total = body_long_period.max(shadow_very_short_period);
2332
2333    if size < lookback_total {
2334        return Err(PatternError::NotEnoughData {
2335            len: size,
2336            pattern: input.params.pattern_type.clone(),
2337        });
2338    }
2339
2340    let mut out = vec![0i8; size];
2341    let mut body_long_period_total = 0.0;
2342    let mut shadow_very_short_period_total = 0.0;
2343
2344    #[inline(always)]
2345    fn lower_shadow(o: f64, l: f64, c: f64) -> f64 {
2346        if c >= o {
2347            o - l
2348        } else {
2349            c - l
2350        }
2351    }
2352
2353    #[inline(always)]
2354    fn upper_shadow(o: f64, h: f64, c: f64) -> f64 {
2355        if c >= o {
2356            h - c
2357        } else {
2358            h - o
2359        }
2360    }
2361
2362    #[inline(always)]
2363    fn candle_average(sum: f64, period: usize) -> f64 {
2364        if period == 0 {
2365            0.0
2366        } else {
2367            sum / period as f64
2368        }
2369    }
2370
2371    let mut start_idx = lookback_total;
2372    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2373    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
2374
2375    let mut i = body_long_trailing_idx;
2376    while i < start_idx {
2377        body_long_period_total += real_body(open[i], close[i]);
2378        i += 1;
2379    }
2380
2381    i = shadow_very_short_trailing_idx;
2382    while i < start_idx {
2383        let color = candle_color(open[i], close[i]);
2384        shadow_very_short_period_total += if color == 1 {
2385            lower_shadow(open[i], low[i], close[i])
2386        } else {
2387            upper_shadow(open[i], high[i], close[i])
2388        };
2389        i += 1;
2390    }
2391
2392    while start_idx < size {
2393        let color = candle_color(open[start_idx], close[start_idx]);
2394        if real_body(open[start_idx], close[start_idx])
2395            > candle_average(body_long_period_total, body_long_period)
2396            && ((color == 1
2397                && lower_shadow(open[start_idx], low[start_idx], close[start_idx])
2398                    < candle_average(shadow_very_short_period_total, shadow_very_short_period))
2399                || (color == -1
2400                    && upper_shadow(open[start_idx], high[start_idx], close[start_idx])
2401                        < candle_average(shadow_very_short_period_total, shadow_very_short_period)))
2402        {
2403            out[start_idx] = (color as i8) * 100;
2404        }
2405
2406        body_long_period_total += real_body(open[start_idx], close[start_idx])
2407            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
2408
2409        let trailing_color = candle_color(open[start_idx], close[start_idx]);
2410        let new_range = if trailing_color == 1 {
2411            lower_shadow(open[start_idx], low[start_idx], close[start_idx])
2412        } else {
2413            upper_shadow(open[start_idx], high[start_idx], close[start_idx])
2414        };
2415        let old_range_color = candle_color(
2416            open[shadow_very_short_trailing_idx],
2417            close[shadow_very_short_trailing_idx],
2418        );
2419        let old_range = if old_range_color == 1 {
2420            lower_shadow(
2421                open[shadow_very_short_trailing_idx],
2422                low[shadow_very_short_trailing_idx],
2423                close[shadow_very_short_trailing_idx],
2424            )
2425        } else {
2426            upper_shadow(
2427                open[shadow_very_short_trailing_idx],
2428                high[shadow_very_short_trailing_idx],
2429                close[shadow_very_short_trailing_idx],
2430            )
2431        };
2432
2433        shadow_very_short_period_total += new_range - old_range;
2434
2435        start_idx += 1;
2436        body_long_trailing_idx += 1;
2437        shadow_very_short_trailing_idx += 1;
2438    }
2439
2440    Ok(PatternOutput { values: out })
2441}
2442
2443#[inline]
2444pub fn cdlbreakaway(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2445    let (open, high, low, close) = input_ohlc(&input.data)?;
2446
2447    let size = open.len();
2448    let body_long_period = 10;
2449    let lookback_total = 4 + body_long_period;
2450
2451    if size < lookback_total {
2452        return Err(PatternError::NotEnoughData {
2453            len: size,
2454            pattern: input.params.pattern_type.clone(),
2455        });
2456    }
2457
2458    let mut out = vec![0i8; size];
2459    let mut body_long_period_total = 0.0;
2460
2461    #[inline(always)]
2462    fn candle_range(o: f64, c: f64) -> f64 {
2463        (c - o).abs()
2464    }
2465
2466    #[inline(always)]
2467    fn gap_up(op_curr: f64, cl_curr: f64, op_prev: f64, cl_prev: f64) -> bool {
2468        op_curr.min(cl_curr) > op_prev.max(cl_prev)
2469    }
2470
2471    #[inline(always)]
2472    fn gap_down(op_curr: f64, cl_curr: f64, op_prev: f64, cl_prev: f64) -> bool {
2473        op_curr.max(cl_curr) < op_prev.min(cl_prev)
2474    }
2475
2476    let mut start_idx = lookback_total;
2477    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2478
2479    let mut i = body_long_trailing_idx;
2480    while i < start_idx {
2481        body_long_period_total += candle_range(open[i - 4], close[i - 4]);
2482        i += 1;
2483    }
2484
2485    while start_idx < size {
2486        let first_long = (close[start_idx - 4] - open[start_idx - 4]).abs()
2487            > body_long_period_total / body_long_period as f64;
2488        let c1 = candle_color(open[start_idx - 4], close[start_idx - 4]);
2489        let c2 = candle_color(open[start_idx - 3], close[start_idx - 3]);
2490        let c3 = candle_color(open[start_idx - 2], close[start_idx - 2]);
2491        let c4 = candle_color(open[start_idx - 1], close[start_idx - 1]);
2492        let c5 = candle_color(open[start_idx], close[start_idx]);
2493
2494        if first_long
2495            && c1 == c2
2496            && c2 == c4
2497            && c4 == -c5
2498            && ((c1 == -1
2499                && gap_down(
2500                    open[start_idx - 3],
2501                    close[start_idx - 3],
2502                    open[start_idx - 4],
2503                    close[start_idx - 4],
2504                )
2505                && high[start_idx - 2] < high[start_idx - 3]
2506                && low[start_idx - 2] < low[start_idx - 3]
2507                && high[start_idx - 1] < high[start_idx - 2]
2508                && low[start_idx - 1] < low[start_idx - 2]
2509                && close[start_idx] > open[start_idx - 3]
2510                && close[start_idx] < close[start_idx - 4])
2511                || (c1 == 1
2512                    && gap_up(
2513                        open[start_idx - 3],
2514                        close[start_idx - 3],
2515                        open[start_idx - 4],
2516                        close[start_idx - 4],
2517                    )
2518                    && high[start_idx - 2] > high[start_idx - 3]
2519                    && low[start_idx - 2] > low[start_idx - 3]
2520                    && high[start_idx - 1] > high[start_idx - 2]
2521                    && low[start_idx - 1] > low[start_idx - 2]
2522                    && close[start_idx] < open[start_idx - 3]
2523                    && close[start_idx] > close[start_idx - 4]))
2524        {
2525            out[start_idx] = (c5 as i8) * 100;
2526        }
2527
2528        body_long_period_total += candle_range(open[start_idx - 4], close[start_idx - 4])
2529            - candle_range(
2530                open[body_long_trailing_idx - 4],
2531                close[body_long_trailing_idx - 4],
2532            );
2533
2534        start_idx += 1;
2535        body_long_trailing_idx += 1;
2536    }
2537
2538    Ok(PatternOutput { values: out })
2539}
2540
2541#[inline]
2542pub fn cdlclosingmarubozu(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2543    let (open, high, low, close) = input_ohlc(&input.data)?;
2544
2545    let size = open.len();
2546    let body_long_period = 10;
2547    let shadow_very_short_period = 10;
2548    let lookback_total = body_long_period.max(shadow_very_short_period);
2549
2550    if size < lookback_total {
2551        return Err(PatternError::NotEnoughData {
2552            len: size,
2553            pattern: input.params.pattern_type.clone(),
2554        });
2555    }
2556
2557    let mut out = vec![0i8; size];
2558    let mut body_long_period_total = 0.0;
2559    let mut shadow_very_short_period_total = 0.0;
2560
2561    #[inline(always)]
2562    fn lower_shadow(o: f64, l: f64, c: f64) -> f64 {
2563        if c >= o {
2564            o - l
2565        } else {
2566            c - l
2567        }
2568    }
2569
2570    #[inline(always)]
2571    fn upper_shadow(o: f64, h: f64, c: f64) -> f64 {
2572        if c >= o {
2573            h - c
2574        } else {
2575            h - o
2576        }
2577    }
2578
2579    #[inline(always)]
2580    fn candle_average(sum: f64, period: usize) -> f64 {
2581        if period == 0 {
2582            0.0
2583        } else {
2584            sum / period as f64
2585        }
2586    }
2587
2588    let mut start_idx = lookback_total;
2589    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2590    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
2591
2592    let mut i = body_long_trailing_idx;
2593    while i < start_idx {
2594        body_long_period_total += real_body(open[i], close[i]);
2595        i += 1;
2596    }
2597
2598    i = shadow_very_short_trailing_idx;
2599    while i < start_idx {
2600        let color = candle_color(open[i], close[i]);
2601        shadow_very_short_period_total += if color == 1 {
2602            upper_shadow(open[i], high[i], close[i])
2603        } else {
2604            lower_shadow(open[i], low[i], close[i])
2605        };
2606        i += 1;
2607    }
2608
2609    while start_idx < size {
2610        let color = candle_color(open[start_idx], close[start_idx]);
2611        if real_body(open[start_idx], close[start_idx])
2612            > candle_average(body_long_period_total, body_long_period)
2613            && ((color == 1
2614                && upper_shadow(open[start_idx], high[start_idx], close[start_idx])
2615                    < candle_average(shadow_very_short_period_total, shadow_very_short_period))
2616                || (color == -1
2617                    && lower_shadow(open[start_idx], low[start_idx], close[start_idx])
2618                        < candle_average(shadow_very_short_period_total, shadow_very_short_period)))
2619        {
2620            out[start_idx] = (color as i8) * 100;
2621        }
2622
2623        body_long_period_total += real_body(open[start_idx], close[start_idx])
2624            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
2625
2626        let trailing_color = candle_color(open[start_idx], close[start_idx]);
2627        let new_shadow = if trailing_color == 1 {
2628            upper_shadow(open[start_idx], high[start_idx], close[start_idx])
2629        } else {
2630            lower_shadow(open[start_idx], low[start_idx], close[start_idx])
2631        };
2632
2633        let old_color = candle_color(
2634            open[shadow_very_short_trailing_idx],
2635            close[shadow_very_short_trailing_idx],
2636        );
2637        let old_shadow = if old_color == 1 {
2638            upper_shadow(
2639                open[shadow_very_short_trailing_idx],
2640                high[shadow_very_short_trailing_idx],
2641                close[shadow_very_short_trailing_idx],
2642            )
2643        } else {
2644            lower_shadow(
2645                open[shadow_very_short_trailing_idx],
2646                low[shadow_very_short_trailing_idx],
2647                close[shadow_very_short_trailing_idx],
2648            )
2649        };
2650
2651        shadow_very_short_period_total += new_shadow - old_shadow;
2652
2653        start_idx += 1;
2654        body_long_trailing_idx += 1;
2655        shadow_very_short_trailing_idx += 1;
2656    }
2657
2658    Ok(PatternOutput { values: out })
2659}
2660
2661#[inline]
2662pub fn cdlconcealbabyswall(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2663    let (open, high, low, close) = input_ohlc(&input.data)?;
2664
2665    let size = open.len();
2666    let shadow_very_short_period = 10;
2667    let lookback_total = 3 + shadow_very_short_period;
2668
2669    if size < lookback_total {
2670        return Err(PatternError::NotEnoughData {
2671            len: size,
2672            pattern: input.params.pattern_type.clone(),
2673        });
2674    }
2675
2676    let mut out = vec![0i8; size];
2677    let mut shadow_very_short_period_total = [0.0; 4];
2678
2679    #[inline(always)]
2680    fn upper_shadow(o: f64, h: f64, c: f64) -> f64 {
2681        if c >= o {
2682            h - c
2683        } else {
2684            h - o
2685        }
2686    }
2687
2688    #[inline(always)]
2689    fn lower_shadow(o: f64, l: f64, c: f64) -> f64 {
2690        if c >= o {
2691            o - l
2692        } else {
2693            c - l
2694        }
2695    }
2696
2697    #[inline(always)]
2698    fn candle_color(o: f64, c: f64) -> i32 {
2699        if c >= o {
2700            1
2701        } else {
2702            -1
2703        }
2704    }
2705
2706    #[inline(always)]
2707    fn candle_average(sum: f64, period: usize) -> f64 {
2708        if period == 0 {
2709            0.0
2710        } else {
2711            sum / period as f64
2712        }
2713    }
2714
2715    #[inline(always)]
2716    fn real_body_gap_down(o1: f64, c1: f64, o2: f64, c2: f64) -> bool {
2717        o1.max(c1) < o2.min(c2)
2718    }
2719
2720    let mut start_idx = lookback_total;
2721    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
2722
2723    let mut i = shadow_very_short_trailing_idx;
2724    while i < start_idx {
2725        shadow_very_short_period_total[3] += upper_shadow(open[i - 3], high[i - 3], close[i - 3])
2726            .max(lower_shadow(open[i - 3], low[i - 3], close[i - 3]));
2727        shadow_very_short_period_total[2] += upper_shadow(open[i - 2], high[i - 2], close[i - 2])
2728            .max(lower_shadow(open[i - 2], low[i - 2], close[i - 2]));
2729        shadow_very_short_period_total[1] += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
2730            .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]));
2731        i += 1;
2732    }
2733
2734    while start_idx < size {
2735        let c1 = candle_color(open[start_idx - 3], close[start_idx - 3]);
2736        let c2 = candle_color(open[start_idx - 2], close[start_idx - 2]);
2737        let c3 = candle_color(open[start_idx - 1], close[start_idx - 1]);
2738        let c4 = candle_color(open[start_idx], close[start_idx]);
2739
2740        let upper1 = upper_shadow(
2741            open[start_idx - 3],
2742            high[start_idx - 3],
2743            close[start_idx - 3],
2744        );
2745        let lower1 = lower_shadow(
2746            open[start_idx - 3],
2747            low[start_idx - 3],
2748            close[start_idx - 3],
2749        );
2750        let upper2 = upper_shadow(
2751            open[start_idx - 2],
2752            high[start_idx - 2],
2753            close[start_idx - 2],
2754        );
2755        let lower2 = lower_shadow(
2756            open[start_idx - 2],
2757            low[start_idx - 2],
2758            close[start_idx - 2],
2759        );
2760        let upper3 = upper_shadow(
2761            open[start_idx - 1],
2762            high[start_idx - 1],
2763            close[start_idx - 1],
2764        );
2765
2766        if c1 == -1
2767            && c2 == -1
2768            && c3 == -1
2769            && c4 == -1
2770            && lower1 < candle_average(shadow_very_short_period_total[3], shadow_very_short_period)
2771            && upper1 < candle_average(shadow_very_short_period_total[3], shadow_very_short_period)
2772            && lower2 < candle_average(shadow_very_short_period_total[2], shadow_very_short_period)
2773            && upper2 < candle_average(shadow_very_short_period_total[2], shadow_very_short_period)
2774            && real_body_gap_down(
2775                open[start_idx - 1],
2776                close[start_idx - 1],
2777                open[start_idx - 2],
2778                close[start_idx - 2],
2779            )
2780            && upper3 > candle_average(shadow_very_short_period_total[1], shadow_very_short_period)
2781            && high[start_idx - 1] > close[start_idx - 2]
2782            && high[start_idx] > high[start_idx - 1]
2783            && low[start_idx] < low[start_idx - 1]
2784        {
2785            out[start_idx] = 100;
2786        }
2787
2788        for tot_idx in (1..=3).rev() {
2789            let current_upper = upper_shadow(
2790                open[start_idx - tot_idx],
2791                high[start_idx - tot_idx],
2792                close[start_idx - tot_idx],
2793            );
2794            let current_lower = lower_shadow(
2795                open[start_idx - tot_idx],
2796                low[start_idx - tot_idx],
2797                close[start_idx - tot_idx],
2798            );
2799            let new_val = current_upper.max(current_lower);
2800
2801            let old_upper = upper_shadow(
2802                open[shadow_very_short_trailing_idx - tot_idx],
2803                high[shadow_very_short_trailing_idx - tot_idx],
2804                close[shadow_very_short_trailing_idx - tot_idx],
2805            );
2806            let old_lower = lower_shadow(
2807                open[shadow_very_short_trailing_idx - tot_idx],
2808                low[shadow_very_short_trailing_idx - tot_idx],
2809                close[shadow_very_short_trailing_idx - tot_idx],
2810            );
2811            let old_val = old_upper.max(old_lower);
2812
2813            shadow_very_short_period_total[tot_idx] += new_val - old_val;
2814        }
2815
2816        start_idx += 1;
2817        shadow_very_short_trailing_idx += 1;
2818    }
2819
2820    Ok(PatternOutput { values: out })
2821}
2822
2823#[inline]
2824pub fn cdlcounterattack(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2825    let (open, high, low, close) = input_ohlc(&input.data)?;
2826
2827    let size = open.len();
2828    let body_long_period = 10;
2829    let equal_period = 10;
2830    let lookback_total = 1 + body_long_period.max(equal_period);
2831
2832    if size < lookback_total {
2833        return Err(PatternError::NotEnoughData {
2834            len: size,
2835            pattern: input.params.pattern_type.clone(),
2836        });
2837    }
2838
2839    let mut out = vec![0i8; size];
2840    let mut equal_period_total = 0.0;
2841    let mut body_long_period_total = [0.0; 2];
2842
2843    #[inline(always)]
2844    fn real_body(o: f64, c: f64) -> f64 {
2845        (c - o).abs()
2846    }
2847
2848    #[inline(always)]
2849    fn candle_color(o: f64, c: f64) -> i32 {
2850        if c >= o {
2851            1
2852        } else {
2853            -1
2854        }
2855    }
2856
2857    #[inline(always)]
2858    fn candle_average(sum: f64, period: usize) -> f64 {
2859        if period == 0 {
2860            0.0
2861        } else {
2862            sum / period as f64
2863        }
2864    }
2865
2866    let mut start_idx = lookback_total;
2867    let mut equal_trailing_idx = start_idx.saturating_sub(equal_period);
2868    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2869
2870    let mut i = equal_trailing_idx;
2871    while i < start_idx {
2872        equal_period_total += real_body(open[i - 1], close[i - 1]);
2873        i += 1;
2874    }
2875
2876    i = body_long_trailing_idx;
2877    while i < start_idx {
2878        body_long_period_total[1] += real_body(open[i - 1], close[i - 1]);
2879        body_long_period_total[0] += real_body(open[i], close[i]);
2880        i += 1;
2881    }
2882
2883    while start_idx < size {
2884        let c1 = candle_color(open[start_idx - 1], close[start_idx - 1]);
2885        let c2 = candle_color(open[start_idx], close[start_idx]);
2886        let rb1 = real_body(open[start_idx - 1], close[start_idx - 1]);
2887        let rb2 = real_body(open[start_idx], close[start_idx]);
2888        let eq_avg = candle_average(equal_period_total, equal_period);
2889        let body1_avg = candle_average(body_long_period_total[1], body_long_period);
2890        let body2_avg = candle_average(body_long_period_total[0], body_long_period);
2891
2892        if c1 == -c2
2893            && rb1 > body1_avg
2894            && rb2 > body2_avg
2895            && close[start_idx] <= close[start_idx - 1] + eq_avg
2896            && close[start_idx] >= close[start_idx - 1] - eq_avg
2897        {
2898            out[start_idx] = (c2 as i8) * 100;
2899        }
2900
2901        equal_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
2902            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
2903
2904        for tot_idx in (0..=1).rev() {
2905            body_long_period_total[tot_idx] +=
2906                real_body(open[start_idx - tot_idx], close[start_idx - tot_idx])
2907                    - real_body(
2908                        open[body_long_trailing_idx - tot_idx],
2909                        close[body_long_trailing_idx - tot_idx],
2910                    );
2911        }
2912
2913        start_idx += 1;
2914        equal_trailing_idx += 1;
2915        body_long_trailing_idx += 1;
2916    }
2917
2918    Ok(PatternOutput { values: out })
2919}
2920
2921#[inline]
2922pub fn cdldarkcloudcover(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2923    let (open, high, _, close) = input_ohlc(&input.data)?;
2924
2925    let size = open.len();
2926    let body_long_period = 10;
2927    let penetration = if input.params.penetration == 0.0 {
2928        0.5
2929    } else {
2930        input.params.penetration
2931    };
2932    let lookback_total = 1 + body_long_period;
2933
2934    if size < lookback_total {
2935        return Err(PatternError::NotEnoughData {
2936            len: size,
2937            pattern: input.params.pattern_type.clone(),
2938        });
2939    }
2940
2941    let mut out = vec![0i8; size];
2942    let mut body_long_period_total = 0.0;
2943
2944    #[inline(always)]
2945    fn candle_average(sum: f64, period: usize) -> f64 {
2946        if period == 0 {
2947            0.0
2948        } else {
2949            sum / period as f64
2950        }
2951    }
2952
2953    let mut start_idx = lookback_total;
2954    let mut body_long_trailing_idx = start_idx.saturating_sub(body_long_period);
2955
2956    let mut i = body_long_trailing_idx;
2957    while i < start_idx {
2958        body_long_period_total += real_body(open[i - 1], close[i - 1]);
2959        i += 1;
2960    }
2961
2962    while start_idx < size {
2963        if candle_color(open[start_idx - 1], close[start_idx - 1]) == 1
2964            && real_body(open[start_idx - 1], close[start_idx - 1])
2965                > candle_average(body_long_period_total, body_long_period)
2966            && candle_color(open[start_idx], close[start_idx]) == -1
2967            && open[start_idx] > high[start_idx - 1]
2968            && close[start_idx] > open[start_idx - 1]
2969            && close[start_idx]
2970                < close[start_idx - 1]
2971                    - real_body(open[start_idx - 1], close[start_idx - 1]) * penetration
2972        {
2973            out[start_idx] = -100;
2974        }
2975
2976        body_long_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
2977            - real_body(
2978                open[body_long_trailing_idx - 1],
2979                close[body_long_trailing_idx - 1],
2980            );
2981
2982        start_idx += 1;
2983        body_long_trailing_idx += 1;
2984    }
2985
2986    Ok(PatternOutput { values: out })
2987}
2988
2989#[inline]
2990pub fn cdldoji(input: &PatternInput) -> Result<PatternOutput, PatternError> {
2991    let (open, _, _, close) = input_ohlc(&input.data)?;
2992
2993    let size = open.len();
2994    let body_doji_period = 10;
2995    let lookback_total = body_doji_period;
2996
2997    if size < lookback_total {
2998        return Err(PatternError::NotEnoughData {
2999            len: size,
3000            pattern: input.params.pattern_type.clone(),
3001        });
3002    }
3003
3004    let mut out = vec![0i8; size];
3005    let mut body_doji_period_total = 0.0;
3006
3007    #[inline(always)]
3008    fn candle_average(sum: f64, period: usize) -> f64 {
3009        if period == 0 {
3010            0.0
3011        } else {
3012            sum / period as f64
3013        }
3014    }
3015
3016    let mut start_idx = lookback_total;
3017    let mut body_doji_trailing_idx = start_idx.saturating_sub(body_doji_period);
3018
3019    let mut i = body_doji_trailing_idx;
3020    while i < start_idx {
3021        body_doji_period_total += candle_range(open[i], close[i]);
3022        i += 1;
3023    }
3024
3025    while start_idx < size {
3026        let avg_body = candle_average(body_doji_period_total, body_doji_period);
3027        if real_body(open[start_idx], close[start_idx]) <= avg_body {
3028            out[start_idx] = 100;
3029        }
3030
3031        body_doji_period_total += candle_range(open[start_idx], close[start_idx])
3032            - candle_range(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
3033
3034        start_idx += 1;
3035        body_doji_trailing_idx += 1;
3036    }
3037
3038    Ok(PatternOutput { values: out })
3039}
3040
3041#[inline]
3042pub fn cdldojistar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3043    let (open, high, low, close) = input_ohlc(&input.data)?;
3044
3045    let size = open.len();
3046    let body_long_period = 10;
3047    let body_doji_period = 10;
3048    let lookback_total = 1 + body_long_period.max(body_doji_period);
3049
3050    if size < lookback_total {
3051        return Err(PatternError::NotEnoughData {
3052            len: size,
3053            pattern: input.params.pattern_type.clone(),
3054        });
3055    }
3056
3057    let mut out = vec![0i8; size];
3058    let mut body_long_period_total = 0.0;
3059    let mut body_doji_period_total = 0.0;
3060
3061    #[inline(always)]
3062    fn gap_up(current_open: f64, current_close: f64, prev_open: f64, prev_close: f64) -> bool {
3063        current_open.min(current_close) > prev_open.max(prev_close)
3064    }
3065
3066    #[inline(always)]
3067    fn gap_down(current_open: f64, current_close: f64, prev_open: f64, prev_close: f64) -> bool {
3068        current_open.max(current_close) < prev_open.min(prev_close)
3069    }
3070
3071    #[inline(always)]
3072    fn candle_average(sum: f64, period: usize) -> f64 {
3073        if period == 0 {
3074            0.0
3075        } else {
3076            sum / period as f64
3077        }
3078    }
3079
3080    let mut start_idx = lookback_total;
3081    let mut body_long_trailing_idx = start_idx.saturating_sub(1 + body_long_period);
3082    let mut body_doji_trailing_idx = start_idx.saturating_sub(body_doji_period);
3083
3084    let mut i = body_long_trailing_idx;
3085    while i < start_idx - 1 {
3086        body_long_period_total += real_body(open[i], close[i]);
3087        i += 1;
3088    }
3089
3090    i = body_doji_trailing_idx;
3091    while i < start_idx {
3092        body_doji_period_total += real_body(open[i], close[i]);
3093        i += 1;
3094    }
3095
3096    while start_idx < size {
3097        if real_body(open[start_idx - 1], close[start_idx - 1])
3098            > candle_average(body_long_period_total, body_long_period)
3099            && real_body(open[start_idx], close[start_idx])
3100                <= candle_average(body_doji_period_total, body_doji_period)
3101            && ((candle_color(open[start_idx - 1], close[start_idx - 1]) == 1
3102                && gap_up(
3103                    open[start_idx],
3104                    close[start_idx],
3105                    open[start_idx - 1],
3106                    close[start_idx - 1],
3107                ))
3108                || (candle_color(open[start_idx - 1], close[start_idx - 1]) == -1
3109                    && gap_down(
3110                        open[start_idx],
3111                        close[start_idx],
3112                        open[start_idx - 1],
3113                        close[start_idx - 1],
3114                    )))
3115        {
3116            out[start_idx] = -candle_color(open[start_idx - 1], close[start_idx - 1]) as i8 * 100;
3117        }
3118
3119        body_long_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
3120            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
3121
3122        body_doji_period_total += real_body(open[start_idx], close[start_idx])
3123            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
3124
3125        start_idx += 1;
3126        body_long_trailing_idx += 1;
3127        body_doji_trailing_idx += 1;
3128    }
3129
3130    Ok(PatternOutput { values: out })
3131}
3132
3133#[inline]
3134pub fn cdldragonflydoji(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3135    let (open, high, low, close) = input_ohlc(&input.data)?;
3136
3137    let size = open.len();
3138    let body_doji_period = 10;
3139    let shadow_very_short_period = 10;
3140    let lookback_total = body_doji_period.max(shadow_very_short_period);
3141
3142    if size < lookback_total {
3143        return Err(PatternError::NotEnoughData {
3144            len: size,
3145            pattern: input.params.pattern_type.clone(),
3146        });
3147    }
3148
3149    let mut out = vec![0i8; size];
3150    let mut body_doji_period_total = 0.0;
3151    let mut shadow_very_short_period_total = 0.0;
3152
3153    #[inline(always)]
3154    fn upper_shadow(o: f64, h: f64, c: f64) -> f64 {
3155        if c >= o {
3156            h - c
3157        } else {
3158            h - o
3159        }
3160    }
3161
3162    #[inline(always)]
3163    fn lower_shadow(o: f64, l: f64, c: f64) -> f64 {
3164        if c >= o {
3165            o - l
3166        } else {
3167            c - l
3168        }
3169    }
3170
3171    #[inline(always)]
3172    fn candle_average(sum: f64, period: usize) -> f64 {
3173        if period == 0 {
3174            0.0
3175        } else {
3176            sum / period as f64
3177        }
3178    }
3179
3180    let mut start_idx = lookback_total;
3181    let mut body_doji_trailing_idx = start_idx.saturating_sub(body_doji_period);
3182    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
3183
3184    let mut i = body_doji_trailing_idx;
3185    while i < start_idx {
3186        body_doji_period_total += candle_range(open[i], close[i]);
3187        i += 1;
3188    }
3189
3190    i = shadow_very_short_trailing_idx;
3191    while i < start_idx {
3192        shadow_very_short_period_total +=
3193            (upper_shadow(open[i], high[i], close[i])).max(lower_shadow(open[i], low[i], close[i]));
3194        i += 1;
3195    }
3196
3197    while start_idx < size {
3198        let rb = real_body(open[start_idx], close[start_idx]);
3199        let us = upper_shadow(open[start_idx], high[start_idx], close[start_idx]);
3200        let ls = lower_shadow(open[start_idx], low[start_idx], close[start_idx]);
3201        let avg_body_doji = candle_average(body_doji_period_total, body_doji_period);
3202        let avg_shadow_very_short =
3203            candle_average(shadow_very_short_period_total, shadow_very_short_period);
3204
3205        if rb <= avg_body_doji && us < avg_shadow_very_short && ls > avg_shadow_very_short {
3206            out[start_idx] = 100;
3207        }
3208
3209        body_doji_period_total += candle_range(open[start_idx], close[start_idx])
3210            - candle_range(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
3211
3212        let current_shadow_sum = (upper_shadow(open[start_idx], high[start_idx], close[start_idx]))
3213            .max(lower_shadow(
3214                open[start_idx],
3215                low[start_idx],
3216                close[start_idx],
3217            ));
3218        let trailing_shadow_sum = (upper_shadow(
3219            open[shadow_very_short_trailing_idx],
3220            high[shadow_very_short_trailing_idx],
3221            close[shadow_very_short_trailing_idx],
3222        ))
3223        .max(lower_shadow(
3224            open[shadow_very_short_trailing_idx],
3225            low[shadow_very_short_trailing_idx],
3226            close[shadow_very_short_trailing_idx],
3227        ));
3228
3229        shadow_very_short_period_total += current_shadow_sum - trailing_shadow_sum;
3230
3231        start_idx += 1;
3232        body_doji_trailing_idx += 1;
3233        shadow_very_short_trailing_idx += 1;
3234    }
3235
3236    Ok(PatternOutput { values: out })
3237}
3238
3239#[inline]
3240pub fn cdlengulfing(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3241    let (open, _, _, close) = input_ohlc(&input.data)?;
3242
3243    let size = open.len();
3244    if size < 2 {
3245        return Err(PatternError::NotEnoughData {
3246            len: size,
3247            pattern: input.params.pattern_type.clone(),
3248        });
3249    }
3250
3251    let mut out = vec![0i8; size];
3252    for i in 1..size {
3253        let c1 = candle_color(open[i - 1], close[i - 1]);
3254        let c2 = candle_color(open[i], close[i]);
3255        if (c2 == 1
3256            && c1 == -1
3257            && ((close[i] >= open[i - 1] && open[i] < close[i - 1])
3258                || (close[i] > open[i - 1] && open[i] <= close[i - 1])))
3259            || (c2 == -1
3260                && c1 == 1
3261                && ((open[i] >= close[i - 1] && close[i] < open[i - 1])
3262                    || (open[i] > close[i - 1] && close[i] <= open[i - 1])))
3263        {
3264            if (open[i] - close[i - 1]).abs() > f64::EPSILON
3265                && (close[i] - open[i - 1]).abs() > f64::EPSILON
3266            {
3267                out[i] = (c2 as i8) * 100;
3268            } else {
3269                out[i] = (c2 as i8) * 80;
3270            }
3271        }
3272    }
3273
3274    Ok(PatternOutput { values: out })
3275}
3276
3277#[inline]
3278pub fn cdleveningdojistar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3279    let (open, _, _, close) = input_ohlc(&input.data)?;
3280
3281    let size = open.len();
3282    let body_long_period = 10;
3283    let body_doji_period = 10;
3284    let body_short_period = 10;
3285    let penetration = if input.params.penetration == 0.0 {
3286        0.3
3287    } else {
3288        input.params.penetration
3289    };
3290    let lookback_total = 2 + body_long_period
3291        .max(body_doji_period)
3292        .max(body_short_period);
3293
3294    if size < lookback_total {
3295        return Err(PatternError::NotEnoughData {
3296            len: size,
3297            pattern: input.params.pattern_type.clone(),
3298        });
3299    }
3300
3301    let mut out = vec![0i8; size];
3302    let mut body_long_period_total = 0.0;
3303    let mut body_doji_period_total = 0.0;
3304    let mut body_short_period_total = 0.0;
3305
3306    #[inline(always)]
3307    fn candle_average(sum: f64, period: usize) -> f64 {
3308        if period == 0 {
3309            0.0
3310        } else {
3311            sum / period as f64
3312        }
3313    }
3314
3315    #[inline(always)]
3316    fn gap_up(current_open: f64, current_close: f64, prev_open: f64, prev_close: f64) -> bool {
3317        current_open.min(current_close) > prev_open.max(prev_close)
3318    }
3319
3320    let mut start_idx = lookback_total;
3321    let mut body_long_trailing_idx = start_idx.saturating_sub(2 + body_long_period);
3322    let mut body_doji_trailing_idx = start_idx.saturating_sub(1 + body_doji_period);
3323    let mut body_short_trailing_idx = start_idx.saturating_sub(body_short_period);
3324
3325    let mut i = body_long_trailing_idx;
3326    while i < start_idx - 2 {
3327        body_long_period_total += real_body(open[i], close[i]);
3328        i += 1;
3329    }
3330
3331    i = body_doji_trailing_idx;
3332    while i < start_idx - 1 {
3333        body_doji_period_total += real_body(open[i], close[i]);
3334        i += 1;
3335    }
3336
3337    i = body_short_trailing_idx;
3338    while i < start_idx {
3339        body_short_period_total += real_body(open[i], close[i]);
3340        i += 1;
3341    }
3342
3343    while start_idx < size {
3344        if real_body(open[start_idx - 2], close[start_idx - 2])
3345            > candle_average(body_long_period_total, body_long_period)
3346            && candle_color(open[start_idx - 2], close[start_idx - 2]) == 1
3347            && real_body(open[start_idx - 1], close[start_idx - 1])
3348                <= candle_average(body_doji_period_total, body_doji_period)
3349            && gap_up(
3350                open[start_idx - 1],
3351                close[start_idx - 1],
3352                open[start_idx - 2],
3353                close[start_idx - 2],
3354            )
3355            && real_body(open[start_idx], close[start_idx])
3356                > candle_average(body_short_period_total, body_short_period)
3357            && candle_color(open[start_idx], close[start_idx]) == -1
3358            && close[start_idx]
3359                < close[start_idx - 2]
3360                    - real_body(open[start_idx - 2], close[start_idx - 2]) * penetration
3361        {
3362            out[start_idx] = -100;
3363        }
3364
3365        body_long_period_total += real_body(open[start_idx - 2], close[start_idx - 2])
3366            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
3367
3368        body_doji_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
3369            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
3370
3371        body_short_period_total += real_body(open[start_idx], close[start_idx])
3372            - real_body(
3373                open[body_short_trailing_idx],
3374                close[body_short_trailing_idx],
3375            );
3376
3377        start_idx += 1;
3378        body_long_trailing_idx += 1;
3379        body_doji_trailing_idx += 1;
3380        body_short_trailing_idx += 1;
3381    }
3382
3383    Ok(PatternOutput { values: out })
3384}
3385
3386#[inline]
3387pub fn cdleveningstar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3388    let (open, _, _, close) = input_ohlc(&input.data)?;
3389
3390    let size = open.len();
3391    let body_long_period = 10;
3392    let body_short_period = 10;
3393    let penetration = if input.params.penetration == 0.0 {
3394        0.3
3395    } else {
3396        input.params.penetration
3397    };
3398    let lookback_total = 2 + body_long_period.max(body_short_period);
3399
3400    if size < lookback_total {
3401        return Err(PatternError::NotEnoughData {
3402            len: size,
3403            pattern: input.params.pattern_type,
3404        });
3405    }
3406
3407    let mut out = vec![0i8; size];
3408    let mut body_long_period_total = 0.0;
3409    let mut body_short_period_total = 0.0;
3410    let mut body_short_period_total2 = 0.0;
3411
3412    #[inline(always)]
3413    fn candle_average(sum: f64, period: usize) -> f64 {
3414        if period == 0 {
3415            0.0
3416        } else {
3417            sum / period as f64
3418        }
3419    }
3420
3421    #[inline(always)]
3422    fn gap_up(current_open: f64, current_close: f64, prev_open: f64, prev_close: f64) -> bool {
3423        current_open.min(current_close) > prev_open.max(prev_close)
3424    }
3425
3426    let mut start_idx = lookback_total;
3427    let mut body_long_trailing_idx = start_idx.saturating_sub(2 + body_long_period);
3428    let mut body_short_trailing_idx = start_idx.saturating_sub(1 + body_short_period);
3429
3430    let mut i = body_long_trailing_idx;
3431    while i < start_idx - 2 {
3432        body_long_period_total += real_body(open[i], close[i]);
3433        i += 1;
3434    }
3435
3436    i = body_short_trailing_idx;
3437    while i < start_idx - 1 {
3438        body_short_period_total += real_body(open[i], close[i]);
3439        body_short_period_total2 += real_body(open[i + 1], close[i + 1]);
3440        i += 1;
3441    }
3442
3443    while start_idx < size {
3444        if real_body(open[start_idx - 2], close[start_idx - 2])
3445            > candle_average(body_long_period_total, body_long_period)
3446            && candle_color(open[start_idx - 2], close[start_idx - 2]) == 1
3447            && real_body(open[start_idx - 1], close[start_idx - 1])
3448                <= candle_average(body_short_period_total, body_short_period)
3449            && gap_up(
3450                open[start_idx - 1],
3451                close[start_idx - 1],
3452                open[start_idx - 2],
3453                close[start_idx - 2],
3454            )
3455            && real_body(open[start_idx], close[start_idx])
3456                > candle_average(body_short_period_total2, body_short_period)
3457            && candle_color(open[start_idx], close[start_idx]) == -1
3458            && close[start_idx]
3459                < close[start_idx - 2]
3460                    - real_body(open[start_idx - 2], close[start_idx - 2]) * penetration
3461        {
3462            out[start_idx] = -100;
3463        }
3464
3465        body_long_period_total += real_body(open[start_idx - 2], close[start_idx - 2])
3466            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
3467        body_short_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
3468            - real_body(
3469                open[body_short_trailing_idx],
3470                close[body_short_trailing_idx],
3471            );
3472        body_short_period_total2 += real_body(open[start_idx], close[start_idx])
3473            - real_body(
3474                open[body_short_trailing_idx + 1],
3475                close[body_short_trailing_idx + 1],
3476            );
3477
3478        start_idx += 1;
3479        body_long_trailing_idx += 1;
3480        body_short_trailing_idx += 1;
3481    }
3482
3483    Ok(PatternOutput { values: out })
3484}
3485
3486#[inline]
3487pub fn cdlmorningstar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3488    let (open, _, _, close) = input_ohlc(&input.data)?;
3489
3490    let size = open.len();
3491    let body_long_period = 10;
3492    let body_short_period = 10;
3493    let penetration = if input.params.penetration == 0.0 {
3494        0.3
3495    } else {
3496        input.params.penetration
3497    };
3498    let lookback_total = 2 + body_long_period.max(body_short_period);
3499
3500    if size < lookback_total {
3501        return Err(PatternError::NotEnoughData {
3502            len: size,
3503            pattern: input.params.pattern_type,
3504        });
3505    }
3506
3507    let mut out = vec![0i8; size];
3508    let mut body_long_period_total = 0.0;
3509    let mut body_short_period_total = 0.0;
3510    let mut body_short_period_total2 = 0.0;
3511
3512    #[inline(always)]
3513    fn candle_average(sum: f64, period: usize) -> f64 {
3514        if period == 0 {
3515            0.0
3516        } else {
3517            sum / period as f64
3518        }
3519    }
3520
3521    #[inline(always)]
3522    fn gap_down(current_open: f64, current_close: f64, prev_open: f64, prev_close: f64) -> bool {
3523        current_open.max(current_close) < prev_open.min(prev_close)
3524    }
3525
3526    let mut start_idx = lookback_total;
3527    let mut body_long_trailing_idx = start_idx.saturating_sub(2 + body_long_period);
3528    let mut body_short_trailing_idx = start_idx.saturating_sub(1 + body_short_period);
3529
3530    let mut i = body_long_trailing_idx;
3531    while i < start_idx - 2 {
3532        body_long_period_total += real_body(open[i], close[i]);
3533        i += 1;
3534    }
3535
3536    i = body_short_trailing_idx;
3537    while i < start_idx - 1 {
3538        body_short_period_total += real_body(open[i], close[i]);
3539        body_short_period_total2 += real_body(open[i + 1], close[i + 1]);
3540        i += 1;
3541    }
3542
3543    while start_idx < size {
3544        if real_body(open[start_idx - 2], close[start_idx - 2])
3545            > candle_average(body_long_period_total, body_long_period)
3546            && candle_color(open[start_idx - 2], close[start_idx - 2]) == -1
3547            && real_body(open[start_idx - 1], close[start_idx - 1])
3548                <= candle_average(body_short_period_total, body_short_period)
3549            && gap_down(
3550                open[start_idx - 1],
3551                close[start_idx - 1],
3552                open[start_idx - 2],
3553                close[start_idx - 2],
3554            )
3555            && real_body(open[start_idx], close[start_idx])
3556                > candle_average(body_short_period_total2, body_short_period)
3557            && candle_color(open[start_idx], close[start_idx]) == 1
3558            && close[start_idx]
3559                > close[start_idx - 2]
3560                    + real_body(open[start_idx - 2], close[start_idx - 2]) * penetration
3561        {
3562            out[start_idx] = 100;
3563        }
3564
3565        body_long_period_total += real_body(open[start_idx - 2], close[start_idx - 2])
3566            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
3567        body_short_period_total += real_body(open[start_idx - 1], close[start_idx - 1])
3568            - real_body(
3569                open[body_short_trailing_idx],
3570                close[body_short_trailing_idx],
3571            );
3572        body_short_period_total2 += real_body(open[start_idx], close[start_idx])
3573            - real_body(
3574                open[body_short_trailing_idx + 1],
3575                close[body_short_trailing_idx + 1],
3576            );
3577
3578        start_idx += 1;
3579        body_long_trailing_idx += 1;
3580        body_short_trailing_idx += 1;
3581    }
3582
3583    Ok(PatternOutput { values: out })
3584}
3585
3586#[inline]
3587pub fn cdlgravestonedoji(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3588    let (open, high, low, close) = input_ohlc(&input.data)?;
3589
3590    let size = open.len();
3591    let body_doji_period = 10;
3592    let shadow_very_short_period = 10;
3593    let lookback_total = body_doji_period.max(shadow_very_short_period);
3594
3595    if size < lookback_total {
3596        return Err(PatternError::NotEnoughData {
3597            len: size,
3598            pattern: input.params.pattern_type,
3599        });
3600    }
3601
3602    let mut out = vec![0i8; size];
3603    let mut body_doji_period_total = 0.0;
3604    let mut shadow_very_short_period_total = 0.0;
3605
3606    #[inline(always)]
3607    fn candle_average(sum: f64, period: usize) -> f64 {
3608        if period == 0 {
3609            0.0
3610        } else {
3611            sum / period as f64
3612        }
3613    }
3614
3615    let mut start_idx = lookback_total;
3616    let mut body_doji_trailing_idx = start_idx.saturating_sub(body_doji_period);
3617    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
3618
3619    let mut i = body_doji_trailing_idx;
3620    while i < start_idx {
3621        body_doji_period_total += candle_range(open[i], close[i]);
3622        i += 1;
3623    }
3624
3625    i = shadow_very_short_trailing_idx;
3626    while i < start_idx {
3627        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i]);
3628        i += 1;
3629    }
3630
3631    while start_idx < size {
3632        let avg_body_doji = candle_average(body_doji_period_total, body_doji_period);
3633        let avg_shadow_very_short =
3634            candle_average(shadow_very_short_period_total, shadow_very_short_period);
3635        let rb = real_body(open[start_idx], close[start_idx]);
3636        let ls = lower_shadow(open[start_idx], low[start_idx], close[start_idx]);
3637        let us = upper_shadow(open[start_idx], high[start_idx], close[start_idx]);
3638
3639        if rb <= avg_body_doji && ls < avg_shadow_very_short && us > avg_shadow_very_short {
3640            out[start_idx] = 100;
3641        }
3642
3643        body_doji_period_total += candle_range(open[start_idx], close[start_idx])
3644            - candle_range(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
3645        shadow_very_short_period_total +=
3646            upper_shadow(open[start_idx], high[start_idx], close[start_idx])
3647                - upper_shadow(
3648                    open[shadow_very_short_trailing_idx],
3649                    high[shadow_very_short_trailing_idx],
3650                    close[shadow_very_short_trailing_idx],
3651                );
3652
3653        start_idx += 1;
3654        body_doji_trailing_idx += 1;
3655        shadow_very_short_trailing_idx += 1;
3656    }
3657
3658    Ok(PatternOutput { values: out })
3659}
3660
3661#[inline]
3662pub fn cdlhammer(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3663    let (open, high, low, close) = input_ohlc(&input.data)?;
3664
3665    let size = open.len();
3666    let body_short_period = 10;
3667    let shadow_long_period = 10;
3668    let shadow_very_short_period = 10;
3669    let near_period = 10;
3670    let lookback_total = body_short_period
3671        .max(shadow_long_period)
3672        .max(shadow_very_short_period)
3673        .max(near_period)
3674        + 1;
3675
3676    if size < lookback_total {
3677        return Err(PatternError::NotEnoughData {
3678            len: size,
3679            pattern: input.params.pattern_type,
3680        });
3681    }
3682
3683    let mut out = vec![0i8; size];
3684    let mut body_period_total = 0.0;
3685    let mut shadow_long_period_total = 0.0;
3686    let mut shadow_very_short_period_total = 0.0;
3687    let mut near_period_total = 0.0;
3688
3689    #[inline(always)]
3690    fn candle_average(sum: f64, period: usize) -> f64 {
3691        if period == 0 {
3692            0.0
3693        } else {
3694            sum / period as f64
3695        }
3696    }
3697
3698    let mut start_idx = lookback_total;
3699    let mut body_trailing_idx = start_idx.saturating_sub(body_short_period);
3700    let mut shadow_long_trailing_idx = start_idx.saturating_sub(shadow_long_period);
3701    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
3702    let mut near_trailing_idx = start_idx.saturating_sub(1 + near_period);
3703
3704    let mut i = body_trailing_idx;
3705    while i < start_idx {
3706        body_period_total += candle_range(open[i], close[i]);
3707        i += 1;
3708    }
3709    i = shadow_long_trailing_idx;
3710    while i < start_idx {
3711        shadow_long_period_total += lower_shadow(open[i], low[i], close[i]);
3712        i += 1;
3713    }
3714    i = shadow_very_short_trailing_idx;
3715    while i < start_idx {
3716        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i]);
3717        i += 1;
3718    }
3719    i = near_trailing_idx;
3720    while i < start_idx - 1 {
3721        near_period_total += candle_range(open[i], close[i]);
3722        i += 1;
3723    }
3724
3725    while start_idx < size {
3726        let rb = real_body(open[start_idx], close[start_idx]);
3727        let ls = lower_shadow(open[start_idx], low[start_idx], close[start_idx]);
3728        let us = upper_shadow(open[start_idx], high[start_idx], close[start_idx]);
3729        let rb_low = open[start_idx].min(close[start_idx]);
3730        if rb < candle_average(body_period_total, body_short_period)
3731            && ls > candle_average(shadow_long_period_total, shadow_long_period)
3732            && us < candle_average(shadow_very_short_period_total, shadow_very_short_period)
3733            && rb_low <= low[start_idx - 1] + candle_average(near_period_total, near_period)
3734        {
3735            out[start_idx] = 100;
3736        }
3737
3738        body_period_total += candle_range(open[start_idx], close[start_idx])
3739            - candle_range(open[body_trailing_idx], close[body_trailing_idx]);
3740        shadow_long_period_total += lower_shadow(open[start_idx], low[start_idx], close[start_idx])
3741            - lower_shadow(
3742                open[shadow_long_trailing_idx],
3743                low[shadow_long_trailing_idx],
3744                close[shadow_long_trailing_idx],
3745            );
3746        shadow_very_short_period_total +=
3747            upper_shadow(open[start_idx], high[start_idx], close[start_idx])
3748                - upper_shadow(
3749                    open[shadow_very_short_trailing_idx],
3750                    high[shadow_very_short_trailing_idx],
3751                    close[shadow_very_short_trailing_idx],
3752                );
3753        near_period_total += candle_range(open[start_idx - 1], close[start_idx - 1])
3754            - candle_range(open[near_trailing_idx], close[near_trailing_idx]);
3755
3756        start_idx += 1;
3757        body_trailing_idx += 1;
3758        shadow_long_trailing_idx += 1;
3759        shadow_very_short_trailing_idx += 1;
3760        near_trailing_idx += 1;
3761    }
3762
3763    Ok(PatternOutput { values: out })
3764}
3765
3766#[inline]
3767pub fn cdlhangingman(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3768    let (open, high, low, close) = input_ohlc(&input.data)?;
3769
3770    let size = open.len();
3771    let body_short_period = 10;
3772    let shadow_long_period = 10;
3773    let shadow_very_short_period = 10;
3774    let near_period = 10;
3775    let lookback_total = body_short_period
3776        .max(shadow_long_period)
3777        .max(shadow_very_short_period)
3778        .max(near_period)
3779        + 1;
3780
3781    if size < lookback_total {
3782        return Err(PatternError::NotEnoughData {
3783            len: size,
3784            pattern: input.params.pattern_type,
3785        });
3786    }
3787
3788    let mut out = vec![0i8; size];
3789    let mut body_period_total = 0.0;
3790    let mut shadow_long_period_total = 0.0;
3791    let mut shadow_very_short_period_total = 0.0;
3792    let mut near_period_total = 0.0;
3793
3794    #[inline(always)]
3795    fn candle_average(sum: f64, period: usize) -> f64 {
3796        if period == 0 {
3797            0.0
3798        } else {
3799            sum / period as f64
3800        }
3801    }
3802
3803    let mut start_idx = lookback_total;
3804    let mut body_trailing_idx = start_idx.saturating_sub(body_short_period);
3805    let mut shadow_long_trailing_idx = start_idx.saturating_sub(shadow_long_period);
3806    let mut shadow_very_short_trailing_idx = start_idx.saturating_sub(shadow_very_short_period);
3807    let mut near_trailing_idx = start_idx.saturating_sub(1 + near_period);
3808
3809    let mut i = body_trailing_idx;
3810    while i < start_idx {
3811        body_period_total += candle_range(open[i], close[i]);
3812        i += 1;
3813    }
3814    i = shadow_long_trailing_idx;
3815    while i < start_idx {
3816        shadow_long_period_total += lower_shadow(open[i], low[i], close[i]);
3817        i += 1;
3818    }
3819    i = shadow_very_short_trailing_idx;
3820    while i < start_idx {
3821        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i]);
3822        i += 1;
3823    }
3824    i = near_trailing_idx;
3825    while i < start_idx - 1 {
3826        near_period_total += candle_range(open[i], close[i]);
3827        i += 1;
3828    }
3829
3830    while start_idx < size {
3831        let rb = real_body(open[start_idx], close[start_idx]);
3832        let ls = lower_shadow(open[start_idx], low[start_idx], close[start_idx]);
3833        let us = upper_shadow(open[start_idx], high[start_idx], close[start_idx]);
3834        let rb_low = open[start_idx].min(close[start_idx]);
3835        if rb < candle_average(body_period_total, body_short_period)
3836            && ls > candle_average(shadow_long_period_total, shadow_long_period)
3837            && us < candle_average(shadow_very_short_period_total, shadow_very_short_period)
3838            && rb_low >= high[start_idx - 1] - candle_average(near_period_total, near_period)
3839        {
3840            out[start_idx] = -100;
3841        }
3842
3843        body_period_total += candle_range(open[start_idx], close[start_idx])
3844            - candle_range(open[body_trailing_idx], close[body_trailing_idx]);
3845        shadow_long_period_total += lower_shadow(open[start_idx], low[start_idx], close[start_idx])
3846            - lower_shadow(
3847                open[shadow_long_trailing_idx],
3848                low[shadow_long_trailing_idx],
3849                close[shadow_long_trailing_idx],
3850            );
3851        shadow_very_short_period_total +=
3852            upper_shadow(open[start_idx], high[start_idx], close[start_idx])
3853                - upper_shadow(
3854                    open[shadow_very_short_trailing_idx],
3855                    high[shadow_very_short_trailing_idx],
3856                    close[shadow_very_short_trailing_idx],
3857                );
3858        near_period_total += candle_range(open[start_idx - 1], close[start_idx - 1])
3859            - candle_range(open[near_trailing_idx], close[near_trailing_idx]);
3860
3861        start_idx += 1;
3862        body_trailing_idx += 1;
3863        shadow_long_trailing_idx += 1;
3864        shadow_very_short_trailing_idx += 1;
3865        near_trailing_idx += 1;
3866    }
3867
3868    Ok(PatternOutput { values: out })
3869}
3870
3871#[inline]
3872pub fn cdlharami(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3873    let (open, _, _, close) = input_ohlc(&input.data)?;
3874
3875    let size = open.len();
3876    let body_long_period = 10;
3877    let body_short_period = 10;
3878    let lookback_total = body_long_period.max(body_short_period) + 1;
3879
3880    if size < lookback_total {
3881        return Err(PatternError::NotEnoughData {
3882            len: size,
3883            pattern: input.params.pattern_type,
3884        });
3885    }
3886
3887    #[inline(always)]
3888    fn candle_average(sum: f64, period: usize) -> f64 {
3889        if period == 0 {
3890            0.0
3891        } else {
3892            sum / period as f64
3893        }
3894    }
3895
3896    let mut out = vec![0i8; size];
3897    let mut body_long_period_total = 0.0;
3898    let mut body_short_period_total = 0.0;
3899    let mut body_long_trailing_idx = lookback_total - 1 - body_long_period;
3900    let mut body_short_trailing_idx = lookback_total - body_short_period;
3901    let mut i = body_long_trailing_idx;
3902    while i < lookback_total - 1 {
3903        body_long_period_total += real_body(open[i], close[i]);
3904        i += 1;
3905    }
3906    i = body_short_trailing_idx;
3907    while i < lookback_total {
3908        body_short_period_total += real_body(open[i], close[i]);
3909        i += 1;
3910    }
3911    i = lookback_total;
3912    while i < size {
3913        if real_body(open[i - 1], close[i - 1])
3914            > candle_average(body_long_period_total, body_long_period)
3915            && real_body(open[i], close[i])
3916                <= candle_average(body_short_period_total, body_short_period)
3917        {
3918            let hi0 = open[i - 1].max(close[i - 1]);
3919            let lo0 = open[i - 1].min(close[i - 1]);
3920            let hi1 = open[i].max(close[i]);
3921            let lo1 = open[i].min(close[i]);
3922            let sign = -(candle_color(open[i - 1], close[i - 1]) as i8);
3923            if hi1 < hi0 && lo1 > lo0 {
3924                out[i] = sign * 100;
3925            } else if hi1 <= hi0 && lo1 >= lo0 {
3926                out[i] = sign * 80;
3927            }
3928        }
3929
3930        body_long_period_total += real_body(open[i - 1], close[i - 1])
3931            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
3932        body_short_period_total += real_body(open[i], close[i])
3933            - real_body(
3934                open[body_short_trailing_idx],
3935                close[body_short_trailing_idx],
3936            );
3937        i += 1;
3938        body_long_trailing_idx += 1;
3939        body_short_trailing_idx += 1;
3940    }
3941
3942    Ok(PatternOutput { values: out })
3943}
3944
3945#[inline]
3946pub fn cdlharamicross(input: &PatternInput) -> Result<PatternOutput, PatternError> {
3947    let (open, _, _, close) = input_ohlc(&input.data)?;
3948
3949    let size = open.len();
3950    let body_long_period = 10;
3951    let body_doji_period = 10;
3952    let lookback_total = body_long_period.max(body_doji_period) + 1;
3953
3954    if size < lookback_total {
3955        return Err(PatternError::NotEnoughData {
3956            len: size,
3957            pattern: input.params.pattern_type,
3958        });
3959    }
3960
3961    #[inline(always)]
3962    fn candle_average(sum: f64, period: usize) -> f64 {
3963        if period == 0 {
3964            0.0
3965        } else {
3966            sum / period as f64
3967        }
3968    }
3969
3970    let mut out = vec![0i8; size];
3971    let mut body_long_period_total = 0.0;
3972    let mut body_doji_period_total = 0.0;
3973    let mut body_long_trailing_idx = lookback_total - 1 - body_long_period;
3974    let mut body_doji_trailing_idx = lookback_total - body_doji_period;
3975    let mut i = body_long_trailing_idx;
3976    while i < lookback_total - 1 {
3977        body_long_period_total += real_body(open[i], close[i]);
3978        i += 1;
3979    }
3980    i = body_doji_trailing_idx;
3981    while i < lookback_total {
3982        body_doji_period_total += real_body(open[i], close[i]);
3983        i += 1;
3984    }
3985    i = lookback_total;
3986    while i < size {
3987        if real_body(open[i - 1], close[i - 1])
3988            > candle_average(body_long_period_total, body_long_period)
3989            && real_body(open[i], close[i])
3990                <= candle_average(body_doji_period_total, body_doji_period)
3991        {
3992            let hi0 = open[i - 1].max(close[i - 1]);
3993            let lo0 = open[i - 1].min(close[i - 1]);
3994            let hi1 = open[i].max(close[i]);
3995            let lo1 = open[i].min(close[i]);
3996            let sign = -(candle_color(open[i - 1], close[i - 1]) as i8);
3997            if hi1 < hi0 && lo1 > lo0 {
3998                out[i] = sign * 100;
3999            } else if hi1 <= hi0 && lo1 >= lo0 {
4000                out[i] = sign * 80;
4001            }
4002        }
4003
4004        body_long_period_total += real_body(open[i - 1], close[i - 1])
4005            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
4006        body_doji_period_total += real_body(open[i], close[i])
4007            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
4008        i += 1;
4009        body_long_trailing_idx += 1;
4010        body_doji_trailing_idx += 1;
4011    }
4012
4013    Ok(PatternOutput { values: out })
4014}
4015
4016#[inline]
4017pub fn cdlhighwave(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4018    let (open, high, low, close) = input_ohlc(&input.data)?;
4019
4020    let size = open.len();
4021    let body_short_period = 10;
4022    let shadow_very_long_period = 10;
4023    let lookback_total = body_short_period.max(shadow_very_long_period);
4024
4025    if size < lookback_total {
4026        return Err(PatternError::NotEnoughData {
4027            len: size,
4028            pattern: input.params.pattern_type,
4029        });
4030    }
4031
4032    #[inline(always)]
4033    fn candle_average(sum: f64, period: usize) -> f64 {
4034        if period == 0 {
4035            0.0
4036        } else {
4037            sum / period as f64
4038        }
4039    }
4040
4041    let mut out = vec![0i8; size];
4042    let mut body_period_total = 0.0;
4043    let mut shadow_period_total = 0.0;
4044    let mut body_trailing_idx = lookback_total - body_short_period;
4045    let mut shadow_trailing_idx = lookback_total - shadow_very_long_period;
4046
4047    let mut i = body_trailing_idx;
4048    while i < lookback_total {
4049        body_period_total += real_body(open[i], close[i]);
4050        i += 1;
4051    }
4052    i = shadow_trailing_idx;
4053    while i < lookback_total {
4054        shadow_period_total += upper_shadow(open[i], high[i], close[i]);
4055        i += 1;
4056    }
4057
4058    i = lookback_total;
4059    while i < size {
4060        let rb = real_body(open[i], close[i]);
4061        let us = upper_shadow(open[i], high[i], close[i]);
4062        let ls = lower_shadow(open[i], low[i], close[i]);
4063        if rb < candle_average(body_period_total, body_short_period)
4064            && us > candle_average(shadow_period_total, shadow_very_long_period)
4065            && ls > candle_average(shadow_period_total, shadow_very_long_period)
4066        {
4067            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
4068        }
4069
4070        body_period_total += real_body(open[i], close[i])
4071            - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4072        shadow_period_total += upper_shadow(open[i], high[i], close[i])
4073            - upper_shadow(
4074                open[shadow_trailing_idx],
4075                high[shadow_trailing_idx],
4076                close[shadow_trailing_idx],
4077            );
4078        i += 1;
4079        body_trailing_idx += 1;
4080        shadow_trailing_idx += 1;
4081    }
4082
4083    Ok(PatternOutput { values: out })
4084}
4085
4086#[inline]
4087pub fn cdlinvertedhammer(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4088    let (open, high, low, close) = input_ohlc(&input.data)?;
4089
4090    let size = open.len();
4091    let body_short_period = 10;
4092    let shadow_long_period = 10;
4093    let shadow_very_short_period = 10;
4094    let lookback_total = body_short_period
4095        .max(shadow_long_period)
4096        .max(shadow_very_short_period)
4097        + 1;
4098
4099    if size < lookback_total {
4100        return Err(PatternError::NotEnoughData {
4101            len: size,
4102            pattern: input.params.pattern_type,
4103        });
4104    }
4105
4106    #[inline(always)]
4107    fn candle_average(sum: f64, period: usize) -> f64 {
4108        if period == 0 {
4109            0.0
4110        } else {
4111            sum / period as f64
4112        }
4113    }
4114
4115    #[inline(always)]
4116    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
4117        curr_open.max(curr_close) < prev_open.min(prev_close)
4118    }
4119
4120    let mut out = vec![0i8; size];
4121    let mut body_period_total = 0.0;
4122    let mut shadow_long_period_total = 0.0;
4123    let mut shadow_very_short_period_total = 0.0;
4124    let mut body_trailing_idx = lookback_total - 1 - body_short_period;
4125    let mut shadow_long_trailing_idx = lookback_total - 1 - shadow_long_period;
4126    let mut shadow_very_short_trailing_idx = lookback_total - 1 - shadow_very_short_period;
4127
4128    let mut i = body_trailing_idx;
4129    while i < lookback_total - 1 {
4130        body_period_total += real_body(open[i], close[i]);
4131        i += 1;
4132    }
4133    i = shadow_long_trailing_idx;
4134    while i < lookback_total - 1 {
4135        shadow_long_period_total += upper_shadow(open[i], high[i], close[i]);
4136        i += 1;
4137    }
4138    i = shadow_very_short_trailing_idx;
4139    while i < lookback_total - 1 {
4140        shadow_very_short_period_total += lower_shadow(open[i], low[i], close[i]);
4141        i += 1;
4142    }
4143
4144    i = lookback_total;
4145    while i < size {
4146        if real_body(open[i], close[i]) < candle_average(body_period_total, body_short_period)
4147            && upper_shadow(open[i], high[i], close[i])
4148                > candle_average(shadow_long_period_total, shadow_long_period)
4149            && lower_shadow(open[i], low[i], close[i])
4150                < candle_average(shadow_very_short_period_total, shadow_very_short_period)
4151            && real_body_gap_down(open[i], close[i], open[i - 1], close[i - 1])
4152        {
4153            out[i] = 100;
4154        }
4155
4156        body_period_total +=
4157            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4158        shadow_long_period_total += upper_shadow(open[i], high[i], close[i])
4159            - upper_shadow(
4160                open[shadow_long_trailing_idx],
4161                high[shadow_long_trailing_idx],
4162                close[shadow_long_trailing_idx],
4163            );
4164        shadow_very_short_period_total += lower_shadow(open[i], low[i], close[i])
4165            - lower_shadow(
4166                open[shadow_very_short_trailing_idx],
4167                low[shadow_very_short_trailing_idx],
4168                close[shadow_very_short_trailing_idx],
4169            );
4170
4171        i += 1;
4172        body_trailing_idx += 1;
4173        shadow_long_trailing_idx += 1;
4174        shadow_very_short_trailing_idx += 1;
4175    }
4176
4177    Ok(PatternOutput { values: out })
4178}
4179
4180#[inline]
4181pub fn cdllongleggeddoji(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4182    let (open, high, low, close) = input_ohlc(&input.data)?;
4183
4184    let size = open.len();
4185    let body_doji_period = 10;
4186    let shadow_long_period = 10;
4187    let lookback_total = body_doji_period.max(shadow_long_period);
4188
4189    if size < lookback_total {
4190        return Err(PatternError::NotEnoughData {
4191            len: size,
4192            pattern: input.params.pattern_type,
4193        });
4194    }
4195
4196    #[inline(always)]
4197    fn candle_average(sum: f64, period: usize) -> f64 {
4198        if period == 0 {
4199            0.0
4200        } else {
4201            sum / period as f64
4202        }
4203    }
4204
4205    let mut out = vec![0i8; size];
4206    let mut body_doji_period_total = 0.0;
4207    let mut shadow_long_period_total = 0.0;
4208    let mut body_doji_trailing_idx = lookback_total - body_doji_period;
4209    let mut shadow_long_trailing_idx = lookback_total - shadow_long_period;
4210
4211    let mut i = body_doji_trailing_idx;
4212    while i < lookback_total {
4213        body_doji_period_total += real_body(open[i], close[i]);
4214        i += 1;
4215    }
4216    i = shadow_long_trailing_idx;
4217    while i < lookback_total {
4218        shadow_long_period_total += upper_shadow(open[i], high[i], close[i]);
4219        i += 1;
4220    }
4221
4222    i = lookback_total;
4223    while i < size {
4224        let rb = real_body(open[i], close[i]);
4225        let us = upper_shadow(open[i], high[i], close[i]);
4226        let ls = lower_shadow(open[i], low[i], close[i]);
4227        if rb <= candle_average(body_doji_period_total, body_doji_period)
4228            && (ls > candle_average(shadow_long_period_total, shadow_long_period)
4229                || us > candle_average(shadow_long_period_total, shadow_long_period))
4230        {
4231            out[i] = 100;
4232        }
4233
4234        body_doji_period_total += real_body(open[i], close[i])
4235            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
4236        shadow_long_period_total += upper_shadow(open[i], high[i], close[i])
4237            - upper_shadow(
4238                open[shadow_long_trailing_idx],
4239                high[shadow_long_trailing_idx],
4240                close[shadow_long_trailing_idx],
4241            );
4242
4243        i += 1;
4244        body_doji_trailing_idx += 1;
4245        shadow_long_trailing_idx += 1;
4246    }
4247
4248    Ok(PatternOutput { values: out })
4249}
4250
4251#[inline]
4252pub fn cdllongline(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4253    let (open, high, low, close) = input_ohlc(&input.data)?;
4254
4255    let size = open.len();
4256    let body_long_period = 10;
4257    let shadow_short_period = 10;
4258    let lookback_total = body_long_period.max(shadow_short_period);
4259
4260    if size < lookback_total {
4261        return Err(PatternError::NotEnoughData {
4262            len: size,
4263            pattern: input.params.pattern_type,
4264        });
4265    }
4266
4267    #[inline(always)]
4268    fn candle_average(sum: f64, period: usize) -> f64 {
4269        if period == 0 {
4270            0.0
4271        } else {
4272            sum / period as f64
4273        }
4274    }
4275
4276    let mut out = vec![0i8; size];
4277    let mut body_period_total = 0.0;
4278    let mut shadow_period_total = 0.0;
4279    let mut body_trailing_idx = lookback_total - body_long_period;
4280    let mut shadow_trailing_idx = lookback_total - shadow_short_period;
4281
4282    let mut i = body_trailing_idx;
4283    while i < lookback_total {
4284        body_period_total += real_body(open[i], close[i]);
4285        i += 1;
4286    }
4287    i = shadow_trailing_idx;
4288    while i < lookback_total {
4289        shadow_period_total += upper_shadow(open[i], high[i], close[i]);
4290        i += 1;
4291    }
4292
4293    i = lookback_total;
4294    while i < size {
4295        if real_body(open[i], close[i]) > candle_average(body_period_total, body_long_period)
4296            && upper_shadow(open[i], high[i], close[i])
4297                < candle_average(shadow_period_total, shadow_short_period)
4298            && lower_shadow(open[i], low[i], close[i])
4299                < candle_average(shadow_period_total, shadow_short_period)
4300        {
4301            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
4302        }
4303
4304        body_period_total +=
4305            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4306        shadow_period_total += upper_shadow(open[i], high[i], close[i])
4307            - upper_shadow(
4308                open[shadow_trailing_idx],
4309                high[shadow_trailing_idx],
4310                close[shadow_trailing_idx],
4311            );
4312
4313        i += 1;
4314        body_trailing_idx += 1;
4315        shadow_trailing_idx += 1;
4316    }
4317
4318    Ok(PatternOutput { values: out })
4319}
4320
4321#[inline]
4322pub fn cdlmarubozu(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4323    let (open, high, low, close) = input_ohlc(&input.data)?;
4324
4325    let size = open.len();
4326    let body_long_period = 10;
4327    let shadow_very_short_period = 10;
4328    let lookback_total = body_long_period.max(shadow_very_short_period);
4329
4330    if size < lookback_total {
4331        return Err(PatternError::NotEnoughData {
4332            len: size,
4333            pattern: input.params.pattern_type,
4334        });
4335    }
4336
4337    #[inline(always)]
4338    fn candle_average(sum: f64, period: usize) -> f64 {
4339        if period == 0 {
4340            0.0
4341        } else {
4342            sum / period as f64
4343        }
4344    }
4345
4346    let mut out = vec![0i8; size];
4347    let mut body_long_period_total = 0.0;
4348    let mut shadow_very_short_period_total = 0.0;
4349    let mut body_long_trailing_idx = lookback_total - body_long_period;
4350    let mut shadow_very_short_trailing_idx = lookback_total - shadow_very_short_period;
4351
4352    let mut i = body_long_trailing_idx;
4353    while i < lookback_total {
4354        body_long_period_total += real_body(open[i], close[i]);
4355        i += 1;
4356    }
4357    i = shadow_very_short_trailing_idx;
4358    while i < lookback_total {
4359        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i]);
4360        i += 1;
4361    }
4362
4363    i = lookback_total;
4364    while i < size {
4365        if real_body(open[i], close[i]) > candle_average(body_long_period_total, body_long_period)
4366            && upper_shadow(open[i], high[i], close[i])
4367                < candle_average(shadow_very_short_period_total, shadow_very_short_period)
4368            && lower_shadow(open[i], low[i], close[i])
4369                < candle_average(shadow_very_short_period_total, shadow_very_short_period)
4370        {
4371            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
4372        }
4373
4374        body_long_period_total +=
4375            real_body(open[i], close[i]) - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
4376        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i])
4377            - upper_shadow(
4378                open[shadow_very_short_trailing_idx],
4379                high[shadow_very_short_trailing_idx],
4380                close[shadow_very_short_trailing_idx],
4381            );
4382
4383        i += 1;
4384        body_long_trailing_idx += 1;
4385        shadow_very_short_trailing_idx += 1;
4386    }
4387
4388    Ok(PatternOutput { values: out })
4389}
4390
4391#[inline]
4392pub fn cdlrickshawman(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4393    let (open, high, low, close) = input_ohlc(&input.data)?;
4394
4395    let size = open.len();
4396    let body_doji_period = 10;
4397    let shadow_long_period = 10;
4398    let near_period = 5;
4399    let lookback_total = body_doji_period.max(shadow_long_period).max(near_period);
4400
4401    if size < lookback_total {
4402        return Err(PatternError::NotEnoughData {
4403            len: size,
4404            pattern: input.params.pattern_type,
4405        });
4406    }
4407
4408    #[inline(always)]
4409    fn candle_average(sum: f64, period: usize) -> f64 {
4410        if period == 0 {
4411            0.0
4412        } else {
4413            sum / period as f64
4414        }
4415    }
4416
4417    let mut out = vec![0i8; size];
4418    let mut body_doji_period_total = 0.0;
4419    let mut shadow_long_period_total = 0.0;
4420    let mut near_period_total = 0.0;
4421    let mut body_doji_trailing_idx = lookback_total - body_doji_period;
4422    let mut shadow_long_trailing_idx = lookback_total - shadow_long_period;
4423    let mut near_trailing_idx = lookback_total - near_period;
4424
4425    let mut i = body_doji_trailing_idx;
4426    while i < lookback_total {
4427        body_doji_period_total += real_body(open[i], close[i]);
4428        i += 1;
4429    }
4430    i = shadow_long_trailing_idx;
4431    while i < lookback_total {
4432        shadow_long_period_total += upper_shadow(open[i], high[i], close[i]);
4433        i += 1;
4434    }
4435    i = near_trailing_idx;
4436    while i < lookback_total {
4437        near_period_total += candle_range(open[i], close[i]);
4438        i += 1;
4439    }
4440
4441    i = lookback_total;
4442    while i < size {
4443        let rb = real_body(open[i], close[i]);
4444        let us = upper_shadow(open[i], high[i], close[i]);
4445        let ls = lower_shadow(open[i], low[i], close[i]);
4446        let hl_mid = low[i] + (high[i] - low[i]) * 0.5;
4447        let body_low = open[i].min(close[i]);
4448        let body_high = open[i].max(close[i]);
4449        let near_avg = candle_average(near_period_total, near_period);
4450
4451        if rb <= candle_average(body_doji_period_total, body_doji_period)
4452            && ls > candle_average(shadow_long_period_total, shadow_long_period)
4453            && us > candle_average(shadow_long_period_total, shadow_long_period)
4454            && body_low <= hl_mid + near_avg
4455            && body_high >= hl_mid - near_avg
4456        {
4457            out[i] = 100;
4458        }
4459
4460        body_doji_period_total += real_body(open[i], close[i])
4461            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
4462        shadow_long_period_total += upper_shadow(open[i], high[i], close[i])
4463            - upper_shadow(
4464                open[shadow_long_trailing_idx],
4465                high[shadow_long_trailing_idx],
4466                close[shadow_long_trailing_idx],
4467            );
4468        near_period_total += candle_range(open[i], close[i])
4469            - candle_range(open[near_trailing_idx], close[near_trailing_idx]);
4470
4471        i += 1;
4472        body_doji_trailing_idx += 1;
4473        shadow_long_trailing_idx += 1;
4474        near_trailing_idx += 1;
4475    }
4476
4477    Ok(PatternOutput { values: out })
4478}
4479
4480#[inline]
4481pub fn cdlshootingstar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4482    let (open, high, low, close) = input_ohlc(&input.data)?;
4483
4484    let size = open.len();
4485    let body_short_period = 10;
4486    let shadow_long_period = 10;
4487    let shadow_very_short_period = 10;
4488    let lookback_total = body_short_period
4489        .max(shadow_long_period)
4490        .max(shadow_very_short_period)
4491        + 1;
4492
4493    if size < lookback_total {
4494        return Err(PatternError::NotEnoughData {
4495            len: size,
4496            pattern: input.params.pattern_type,
4497        });
4498    }
4499
4500    #[inline(always)]
4501    fn candle_average(sum: f64, period: usize) -> f64 {
4502        if period == 0 {
4503            0.0
4504        } else {
4505            sum / period as f64
4506        }
4507    }
4508
4509    #[inline(always)]
4510    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
4511        curr_open.min(curr_close) > prev_open.max(prev_close)
4512    }
4513
4514    let mut out = vec![0i8; size];
4515    let mut body_period_total = 0.0;
4516    let mut shadow_long_period_total = 0.0;
4517    let mut shadow_very_short_period_total = 0.0;
4518    let mut body_trailing_idx = lookback_total - 1 - body_short_period;
4519    let mut shadow_long_trailing_idx = lookback_total - 1 - shadow_long_period;
4520    let mut shadow_very_short_trailing_idx = lookback_total - 1 - shadow_very_short_period;
4521
4522    let mut i = body_trailing_idx;
4523    while i < lookback_total - 1 {
4524        body_period_total += real_body(open[i], close[i]);
4525        i += 1;
4526    }
4527    i = shadow_long_trailing_idx;
4528    while i < lookback_total - 1 {
4529        shadow_long_period_total += upper_shadow(open[i], high[i], close[i]);
4530        i += 1;
4531    }
4532    i = shadow_very_short_trailing_idx;
4533    while i < lookback_total - 1 {
4534        shadow_very_short_period_total += lower_shadow(open[i], low[i], close[i]);
4535        i += 1;
4536    }
4537
4538    i = lookback_total;
4539    while i < size {
4540        if real_body(open[i], close[i]) < candle_average(body_period_total, body_short_period)
4541            && upper_shadow(open[i], high[i], close[i])
4542                > candle_average(shadow_long_period_total, shadow_long_period)
4543            && lower_shadow(open[i], low[i], close[i])
4544                < candle_average(shadow_very_short_period_total, shadow_very_short_period)
4545            && real_body_gap_up(open[i], close[i], open[i - 1], close[i - 1])
4546        {
4547            out[i] = -100;
4548        }
4549
4550        body_period_total +=
4551            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4552        shadow_long_period_total += upper_shadow(open[i], high[i], close[i])
4553            - upper_shadow(
4554                open[shadow_long_trailing_idx],
4555                high[shadow_long_trailing_idx],
4556                close[shadow_long_trailing_idx],
4557            );
4558        shadow_very_short_period_total += lower_shadow(open[i], low[i], close[i])
4559            - lower_shadow(
4560                open[shadow_very_short_trailing_idx],
4561                low[shadow_very_short_trailing_idx],
4562                close[shadow_very_short_trailing_idx],
4563            );
4564
4565        i += 1;
4566        body_trailing_idx += 1;
4567        shadow_long_trailing_idx += 1;
4568        shadow_very_short_trailing_idx += 1;
4569    }
4570
4571    Ok(PatternOutput { values: out })
4572}
4573
4574#[inline]
4575pub fn cdlshortline(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4576    let (open, high, low, close) = input_ohlc(&input.data)?;
4577
4578    let size = open.len();
4579    let body_short_period = 10;
4580    let shadow_short_period = 10;
4581    let lookback_total = body_short_period.max(shadow_short_period);
4582
4583    if size < lookback_total {
4584        return Err(PatternError::NotEnoughData {
4585            len: size,
4586            pattern: input.params.pattern_type,
4587        });
4588    }
4589
4590    #[inline(always)]
4591    fn candle_average(sum: f64, period: usize) -> f64 {
4592        if period == 0 {
4593            0.0
4594        } else {
4595            sum / period as f64
4596        }
4597    }
4598
4599    let mut out = vec![0i8; size];
4600    let mut body_period_total = 0.0;
4601    let mut shadow_period_total = 0.0;
4602    let mut body_trailing_idx = lookback_total - body_short_period;
4603    let mut shadow_trailing_idx = lookback_total - shadow_short_period;
4604
4605    let mut i = body_trailing_idx;
4606    while i < lookback_total {
4607        body_period_total += real_body(open[i], close[i]);
4608        i += 1;
4609    }
4610    i = shadow_trailing_idx;
4611    while i < lookback_total {
4612        shadow_period_total += upper_shadow(open[i], high[i], close[i]);
4613        i += 1;
4614    }
4615
4616    i = lookback_total;
4617    while i < size {
4618        if real_body(open[i], close[i]) < candle_average(body_period_total, body_short_period)
4619            && upper_shadow(open[i], high[i], close[i])
4620                < candle_average(shadow_period_total, shadow_short_period)
4621            && lower_shadow(open[i], low[i], close[i])
4622                < candle_average(shadow_period_total, shadow_short_period)
4623        {
4624            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
4625        }
4626
4627        body_period_total +=
4628            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4629        shadow_period_total += upper_shadow(open[i], high[i], close[i])
4630            - upper_shadow(
4631                open[shadow_trailing_idx],
4632                high[shadow_trailing_idx],
4633                close[shadow_trailing_idx],
4634            );
4635
4636        i += 1;
4637        body_trailing_idx += 1;
4638        shadow_trailing_idx += 1;
4639    }
4640
4641    Ok(PatternOutput { values: out })
4642}
4643
4644#[inline]
4645pub fn cdlspinningtop(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4646    let (open, high, low, close) = input_ohlc(&input.data)?;
4647
4648    let size = open.len();
4649    let body_short_period = 10;
4650    let lookback_total = body_short_period;
4651
4652    if size < lookback_total {
4653        return Err(PatternError::NotEnoughData {
4654            len: size,
4655            pattern: input.params.pattern_type,
4656        });
4657    }
4658
4659    #[inline(always)]
4660    fn candle_average(sum: f64, period: usize) -> f64 {
4661        if period == 0 {
4662            0.0
4663        } else {
4664            sum / period as f64
4665        }
4666    }
4667
4668    let mut out = vec![0i8; size];
4669    let mut body_period_total = 0.0;
4670    let mut body_trailing_idx = lookback_total - body_short_period;
4671
4672    let mut i = body_trailing_idx;
4673    while i < lookback_total {
4674        body_period_total += real_body(open[i], close[i]);
4675        i += 1;
4676    }
4677
4678    i = lookback_total;
4679    while i < size {
4680        let rb = real_body(open[i], close[i]);
4681        let us = upper_shadow(open[i], high[i], close[i]);
4682        let ls = lower_shadow(open[i], low[i], close[i]);
4683        if rb < candle_average(body_period_total, body_short_period) && us > rb && ls > rb {
4684            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
4685        }
4686
4687        body_period_total +=
4688            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
4689
4690        i += 1;
4691        body_trailing_idx += 1;
4692    }
4693
4694    Ok(PatternOutput { values: out })
4695}
4696
4697#[inline]
4698pub fn cdltakuri(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4699    let (open, high, low, close) = input_ohlc(&input.data)?;
4700
4701    let size = open.len();
4702    let body_doji_period = 10;
4703    let shadow_very_short_period = 10;
4704    let shadow_very_long_period = 10;
4705    let lookback_total = body_doji_period
4706        .max(shadow_very_short_period)
4707        .max(shadow_very_long_period);
4708
4709    if size < lookback_total {
4710        return Err(PatternError::NotEnoughData {
4711            len: size,
4712            pattern: input.params.pattern_type,
4713        });
4714    }
4715
4716    #[inline(always)]
4717    fn candle_average(sum: f64, period: usize) -> f64 {
4718        if period == 0 {
4719            0.0
4720        } else {
4721            sum / period as f64
4722        }
4723    }
4724
4725    let mut out = vec![0i8; size];
4726    let mut body_doji_period_total = 0.0;
4727    let mut shadow_very_short_period_total = 0.0;
4728    let mut shadow_very_long_period_total = 0.0;
4729    let mut body_doji_trailing_idx = lookback_total - body_doji_period;
4730    let mut shadow_very_short_trailing_idx = lookback_total - shadow_very_short_period;
4731    let mut shadow_very_long_trailing_idx = lookback_total - shadow_very_long_period;
4732
4733    let mut i = body_doji_trailing_idx;
4734    while i < lookback_total {
4735        body_doji_period_total += real_body(open[i], close[i]);
4736        i += 1;
4737    }
4738    i = shadow_very_short_trailing_idx;
4739    while i < lookback_total {
4740        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i]);
4741        i += 1;
4742    }
4743    i = shadow_very_long_trailing_idx;
4744    while i < lookback_total {
4745        shadow_very_long_period_total += lower_shadow(open[i], low[i], close[i]);
4746        i += 1;
4747    }
4748
4749    i = lookback_total;
4750    while i < size {
4751        if real_body(open[i], close[i]) <= candle_average(body_doji_period_total, body_doji_period)
4752            && upper_shadow(open[i], high[i], close[i])
4753                < candle_average(shadow_very_short_period_total, shadow_very_short_period)
4754            && lower_shadow(open[i], low[i], close[i])
4755                > candle_average(shadow_very_long_period_total, shadow_very_long_period)
4756        {
4757            out[i] = 100;
4758        }
4759
4760        body_doji_period_total += real_body(open[i], close[i])
4761            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
4762        shadow_very_short_period_total += upper_shadow(open[i], high[i], close[i])
4763            - upper_shadow(
4764                open[shadow_very_short_trailing_idx],
4765                high[shadow_very_short_trailing_idx],
4766                close[shadow_very_short_trailing_idx],
4767            );
4768        shadow_very_long_period_total += lower_shadow(open[i], low[i], close[i])
4769            - lower_shadow(
4770                open[shadow_very_long_trailing_idx],
4771                low[shadow_very_long_trailing_idx],
4772                close[shadow_very_long_trailing_idx],
4773            );
4774
4775        i += 1;
4776        body_doji_trailing_idx += 1;
4777        shadow_very_short_trailing_idx += 1;
4778        shadow_very_long_trailing_idx += 1;
4779    }
4780
4781    Ok(PatternOutput { values: out })
4782}
4783
4784#[inline]
4785pub fn cdlhomingpigeon(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4786    let (open, _high, _low, close) = input_ohlc(&input.data)?;
4787
4788    let size = open.len();
4789    let body_long_period = 10;
4790    let body_short_period = 10;
4791    let lookback_total = body_long_period.max(body_short_period) + 1;
4792
4793    if size < lookback_total {
4794        return Err(PatternError::NotEnoughData {
4795            len: size,
4796            pattern: input.params.pattern_type,
4797        });
4798    }
4799
4800    #[inline(always)]
4801    fn candle_average(sum: f64, period: usize) -> f64 {
4802        if period == 0 {
4803            0.0
4804        } else {
4805            sum / period as f64
4806        }
4807    }
4808
4809    let mut out = vec![0i8; size];
4810    let mut body_long_period_total = 0.0;
4811    let mut body_short_period_total = 0.0;
4812    let mut body_long_trailing_idx = lookback_total - body_long_period;
4813    let mut body_short_trailing_idx = lookback_total - body_short_period;
4814
4815    let mut i = body_long_trailing_idx;
4816    while i < lookback_total {
4817        body_long_period_total += real_body(open[i - 1], close[i - 1]);
4818        i += 1;
4819    }
4820    i = body_short_trailing_idx;
4821    while i < lookback_total {
4822        body_short_period_total += real_body(open[i], close[i]);
4823        i += 1;
4824    }
4825
4826    i = lookback_total;
4827    while i < size {
4828        if candle_color(open[i - 1], close[i - 1]) == -1
4829            && candle_color(open[i], close[i]) == -1
4830            && real_body(open[i - 1], close[i - 1])
4831                > candle_average(body_long_period_total, body_long_period)
4832            && real_body(open[i], close[i]) <= candle_average(body_short_period_total, body_short_period)
4833            && open[i] < open[i - 1]
4834            && close[i] > close[i - 1]
4835        {
4836            out[i] = 100;
4837        }
4838
4839        body_long_period_total += real_body(open[i - 1], close[i - 1])
4840            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
4841        body_short_period_total += real_body(open[i], close[i])
4842            - real_body(open[body_short_trailing_idx], close[body_short_trailing_idx]);
4843
4844        i += 1;
4845        body_long_trailing_idx += 1;
4846        body_short_trailing_idx += 1;
4847    }
4848
4849    Ok(PatternOutput { values: out })
4850}
4851
4852#[inline]
4853pub fn cdlmatchinglow(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4854    let (open, _high, _low, close) = input_ohlc(&input.data)?;
4855
4856    let size = open.len();
4857    let equal_period = 10;
4858    let lookback_total = equal_period + 1;
4859
4860    if size < lookback_total {
4861        return Err(PatternError::NotEnoughData {
4862            len: size,
4863            pattern: input.params.pattern_type,
4864        });
4865    }
4866
4867    #[inline(always)]
4868    fn candle_average(sum: f64, period: usize) -> f64 {
4869        if period == 0 {
4870            0.0
4871        } else {
4872            sum / period as f64
4873        }
4874    }
4875
4876    let mut out = vec![0i8; size];
4877    let mut equal_period_total = 0.0;
4878    let mut equal_trailing_idx = lookback_total - equal_period;
4879
4880    let mut i = equal_trailing_idx;
4881    while i < lookback_total {
4882        equal_period_total += real_body(open[i - 1], close[i - 1]);
4883        i += 1;
4884    }
4885
4886    i = lookback_total;
4887    while i < size {
4888        let eq_avg = candle_average(equal_period_total, equal_period);
4889        if candle_color(open[i - 1], close[i - 1]) == -1
4890            && candle_color(open[i], close[i]) == -1
4891            && close[i] <= close[i - 1] + eq_avg
4892            && close[i] >= close[i - 1] - eq_avg
4893        {
4894            out[i] = 100;
4895        }
4896
4897        equal_period_total += real_body(open[i - 1], close[i - 1])
4898            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
4899        i += 1;
4900        equal_trailing_idx += 1;
4901    }
4902
4903    Ok(PatternOutput { values: out })
4904}
4905
4906#[inline]
4907pub fn cdlinneck(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4908    let (open, _high, low, close) = input_ohlc(&input.data)?;
4909
4910    let size = open.len();
4911    let equal_period = 10;
4912    let body_long_period = 10;
4913    let lookback_total = equal_period.max(body_long_period) + 1;
4914
4915    if size < lookback_total {
4916        return Err(PatternError::NotEnoughData {
4917            len: size,
4918            pattern: input.params.pattern_type,
4919        });
4920    }
4921
4922    #[inline(always)]
4923    fn candle_average(sum: f64, period: usize) -> f64 {
4924        if period == 0 {
4925            0.0
4926        } else {
4927            sum / period as f64
4928        }
4929    }
4930
4931    let mut out = vec![0i8; size];
4932    let mut equal_period_total = 0.0;
4933    let mut body_long_period_total = 0.0;
4934    let mut equal_trailing_idx = lookback_total - equal_period;
4935    let mut body_long_trailing_idx = lookback_total - body_long_period;
4936
4937    let mut i = equal_trailing_idx;
4938    while i < lookback_total {
4939        equal_period_total += real_body(open[i - 1], close[i - 1]);
4940        i += 1;
4941    }
4942    i = body_long_trailing_idx;
4943    while i < lookback_total {
4944        body_long_period_total += real_body(open[i - 1], close[i - 1]);
4945        i += 1;
4946    }
4947
4948    i = lookback_total;
4949    while i < size {
4950        let eq_avg = candle_average(equal_period_total, equal_period);
4951        if candle_color(open[i - 1], close[i - 1]) == -1
4952            && real_body(open[i - 1], close[i - 1])
4953                > candle_average(body_long_period_total, body_long_period)
4954            && candle_color(open[i], close[i]) == 1
4955            && open[i] < low[i - 1]
4956            && close[i] <= close[i - 1] + eq_avg
4957            && close[i] >= close[i - 1]
4958        {
4959            out[i] = -100;
4960        }
4961
4962        equal_period_total += real_body(open[i - 1], close[i - 1])
4963            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
4964        body_long_period_total += real_body(open[i - 1], close[i - 1])
4965            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
4966
4967        i += 1;
4968        equal_trailing_idx += 1;
4969        body_long_trailing_idx += 1;
4970    }
4971
4972    Ok(PatternOutput { values: out })
4973}
4974
4975#[inline]
4976pub fn cdlonneck(input: &PatternInput) -> Result<PatternOutput, PatternError> {
4977    let (open, _high, low, close) = input_ohlc(&input.data)?;
4978
4979    let size = open.len();
4980    let equal_period = 10;
4981    let body_long_period = 10;
4982    let lookback_total = equal_period.max(body_long_period) + 1;
4983
4984    if size < lookback_total {
4985        return Err(PatternError::NotEnoughData {
4986            len: size,
4987            pattern: input.params.pattern_type,
4988        });
4989    }
4990
4991    #[inline(always)]
4992    fn candle_average(sum: f64, period: usize) -> f64 {
4993        if period == 0 {
4994            0.0
4995        } else {
4996            sum / period as f64
4997        }
4998    }
4999
5000    let mut out = vec![0i8; size];
5001    let mut equal_period_total = 0.0;
5002    let mut body_long_period_total = 0.0;
5003    let mut equal_trailing_idx = lookback_total - equal_period;
5004    let mut body_long_trailing_idx = lookback_total - body_long_period;
5005
5006    let mut i = equal_trailing_idx;
5007    while i < lookback_total {
5008        equal_period_total += real_body(open[i - 1], close[i - 1]);
5009        i += 1;
5010    }
5011    i = body_long_trailing_idx;
5012    while i < lookback_total {
5013        body_long_period_total += real_body(open[i - 1], close[i - 1]);
5014        i += 1;
5015    }
5016
5017    i = lookback_total;
5018    while i < size {
5019        let eq_avg = candle_average(equal_period_total, equal_period);
5020        if candle_color(open[i - 1], close[i - 1]) == -1
5021            && real_body(open[i - 1], close[i - 1])
5022                > candle_average(body_long_period_total, body_long_period)
5023            && candle_color(open[i], close[i]) == 1
5024            && open[i] < low[i - 1]
5025            && close[i] <= low[i - 1] + eq_avg
5026            && close[i] >= low[i - 1] - eq_avg
5027        {
5028            out[i] = -100;
5029        }
5030
5031        equal_period_total += real_body(open[i - 1], close[i - 1])
5032            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
5033        body_long_period_total += real_body(open[i - 1], close[i - 1])
5034            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
5035
5036        i += 1;
5037        equal_trailing_idx += 1;
5038        body_long_trailing_idx += 1;
5039    }
5040
5041    Ok(PatternOutput { values: out })
5042}
5043
5044#[inline]
5045pub fn cdlpiercing(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5046    let (open, _high, low, close) = input_ohlc(&input.data)?;
5047
5048    let size = open.len();
5049    let body_long_period = 10;
5050    let lookback_total = body_long_period + 1;
5051
5052    if size < lookback_total {
5053        return Err(PatternError::NotEnoughData {
5054            len: size,
5055            pattern: input.params.pattern_type,
5056        });
5057    }
5058
5059    #[inline(always)]
5060    fn candle_average(sum: f64, period: usize) -> f64 {
5061        if period == 0 {
5062            0.0
5063        } else {
5064            sum / period as f64
5065        }
5066    }
5067
5068    let mut out = vec![0i8; size];
5069    let mut body_long_period_total_prev = 0.0;
5070    let mut body_long_period_total_curr = 0.0;
5071    let mut body_long_trailing_idx = lookback_total - body_long_period;
5072
5073    let mut i = body_long_trailing_idx;
5074    while i < lookback_total {
5075        body_long_period_total_prev += real_body(open[i - 1], close[i - 1]);
5076        body_long_period_total_curr += real_body(open[i], close[i]);
5077        i += 1;
5078    }
5079
5080    i = lookback_total;
5081    while i < size {
5082        if candle_color(open[i - 1], close[i - 1]) == -1
5083            && real_body(open[i - 1], close[i - 1])
5084                > candle_average(body_long_period_total_prev, body_long_period)
5085            && candle_color(open[i], close[i]) == 1
5086            && real_body(open[i], close[i]) > candle_average(body_long_period_total_curr, body_long_period)
5087            && open[i] < low[i - 1]
5088            && close[i] < open[i - 1]
5089            && close[i] > close[i - 1] + real_body(open[i - 1], close[i - 1]) * 0.5
5090        {
5091            out[i] = 100;
5092        }
5093
5094        body_long_period_total_prev += real_body(open[i - 1], close[i - 1])
5095            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
5096        body_long_period_total_curr += real_body(open[i], close[i])
5097            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
5098
5099        i += 1;
5100        body_long_trailing_idx += 1;
5101    }
5102
5103    Ok(PatternOutput { values: out })
5104}
5105
5106#[inline]
5107pub fn cdlthrusting(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5108    let (open, _high, low, close) = input_ohlc(&input.data)?;
5109
5110    let size = open.len();
5111    let equal_period = 10;
5112    let body_long_period = 10;
5113    let lookback_total = equal_period.max(body_long_period) + 1;
5114
5115    if size < lookback_total {
5116        return Err(PatternError::NotEnoughData {
5117            len: size,
5118            pattern: input.params.pattern_type,
5119        });
5120    }
5121
5122    #[inline(always)]
5123    fn candle_average(sum: f64, period: usize) -> f64 {
5124        if period == 0 {
5125            0.0
5126        } else {
5127            sum / period as f64
5128        }
5129    }
5130
5131    let mut out = vec![0i8; size];
5132    let mut equal_period_total = 0.0;
5133    let mut body_long_period_total = 0.0;
5134    let mut equal_trailing_idx = lookback_total - equal_period;
5135    let mut body_long_trailing_idx = lookback_total - body_long_period;
5136
5137    let mut i = equal_trailing_idx;
5138    while i < lookback_total {
5139        equal_period_total += real_body(open[i - 1], close[i - 1]);
5140        i += 1;
5141    }
5142    i = body_long_trailing_idx;
5143    while i < lookback_total {
5144        body_long_period_total += real_body(open[i - 1], close[i - 1]);
5145        i += 1;
5146    }
5147
5148    i = lookback_total;
5149    while i < size {
5150        if candle_color(open[i - 1], close[i - 1]) == -1
5151            && real_body(open[i - 1], close[i - 1])
5152                > candle_average(body_long_period_total, body_long_period)
5153            && candle_color(open[i], close[i]) == 1
5154            && open[i] < low[i - 1]
5155            && close[i] > close[i - 1] + candle_average(equal_period_total, equal_period)
5156            && close[i] <= close[i - 1] + real_body(open[i - 1], close[i - 1]) * 0.5
5157        {
5158            out[i] = -100;
5159        }
5160
5161        equal_period_total += real_body(open[i - 1], close[i - 1])
5162            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
5163        body_long_period_total += real_body(open[i - 1], close[i - 1])
5164            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
5165
5166        i += 1;
5167        equal_trailing_idx += 1;
5168        body_long_trailing_idx += 1;
5169    }
5170
5171    Ok(PatternOutput { values: out })
5172}
5173
5174#[inline]
5175pub fn cdlmorningdojistar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5176    let (open, _high, _low, close) = input_ohlc(&input.data)?;
5177
5178    let size = open.len();
5179    let body_long_period = 10;
5180    let body_doji_period = 10;
5181    let body_short_period = 10;
5182    let penetration = if input.params.penetration == 0.0 {
5183        0.3
5184    } else {
5185        input.params.penetration
5186    };
5187    let lookback_total = 2 + body_long_period
5188        .max(body_doji_period)
5189        .max(body_short_period);
5190
5191    if size < lookback_total {
5192        return Err(PatternError::NotEnoughData {
5193            len: size,
5194            pattern: input.params.pattern_type,
5195        });
5196    }
5197
5198    #[inline(always)]
5199    fn candle_average(sum: f64, period: usize) -> f64 {
5200        if period == 0 {
5201            0.0
5202        } else {
5203            sum / period as f64
5204        }
5205    }
5206
5207    #[inline(always)]
5208    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5209        curr_open.max(curr_close) < prev_open.min(prev_close)
5210    }
5211
5212    let mut out = vec![0i8; size];
5213    let mut body_long_period_total = 0.0;
5214    let mut body_doji_period_total = 0.0;
5215    let mut body_short_period_total = 0.0;
5216    let mut body_long_trailing_idx = lookback_total - 2 - body_long_period;
5217    let mut body_doji_trailing_idx = lookback_total - 1 - body_doji_period;
5218    let mut body_short_trailing_idx = lookback_total - body_short_period;
5219
5220    let mut i = body_long_trailing_idx;
5221    while i < lookback_total - 2 {
5222        body_long_period_total += real_body(open[i], close[i]);
5223        i += 1;
5224    }
5225    i = body_doji_trailing_idx;
5226    while i < lookback_total - 1 {
5227        body_doji_period_total += real_body(open[i], close[i]);
5228        i += 1;
5229    }
5230    i = body_short_trailing_idx;
5231    while i < lookback_total {
5232        body_short_period_total += real_body(open[i], close[i]);
5233        i += 1;
5234    }
5235
5236    i = lookback_total;
5237    while i < size {
5238        if real_body(open[i - 2], close[i - 2]) > candle_average(body_long_period_total, body_long_period)
5239            && candle_color(open[i - 2], close[i - 2]) == -1
5240            && real_body(open[i - 1], close[i - 1]) <= candle_average(body_doji_period_total, body_doji_period)
5241            && real_body_gap_down(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
5242            && real_body(open[i], close[i]) > candle_average(body_short_period_total, body_short_period)
5243            && candle_color(open[i], close[i]) == 1
5244            && close[i] > close[i - 2] + real_body(open[i - 2], close[i - 2]) * penetration
5245        {
5246            out[i] = 100;
5247        }
5248
5249        body_long_period_total += real_body(open[i - 2], close[i - 2])
5250            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
5251        body_doji_period_total += real_body(open[i - 1], close[i - 1])
5252            - real_body(open[body_doji_trailing_idx], close[body_doji_trailing_idx]);
5253        body_short_period_total += real_body(open[i], close[i])
5254            - real_body(open[body_short_trailing_idx], close[body_short_trailing_idx]);
5255
5256        i += 1;
5257        body_long_trailing_idx += 1;
5258        body_doji_trailing_idx += 1;
5259        body_short_trailing_idx += 1;
5260    }
5261
5262    Ok(PatternOutput { values: out })
5263}
5264
5265#[inline]
5266pub fn cdltristar(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5267    let (open, _high, _low, close) = input_ohlc(&input.data)?;
5268
5269    let size = open.len();
5270    let body_doji_period = 10;
5271    let lookback_total = body_doji_period + 2;
5272
5273    if size < lookback_total {
5274        return Err(PatternError::NotEnoughData {
5275            len: size,
5276            pattern: input.params.pattern_type,
5277        });
5278    }
5279
5280    #[inline(always)]
5281    fn candle_average(sum: f64, period: usize) -> f64 {
5282        if period == 0 {
5283            0.0
5284        } else {
5285            sum / period as f64
5286        }
5287    }
5288
5289    #[inline(always)]
5290    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5291        curr_open.min(curr_close) > prev_open.max(prev_close)
5292    }
5293
5294    #[inline(always)]
5295    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5296        curr_open.max(curr_close) < prev_open.min(prev_close)
5297    }
5298
5299    let mut out = vec![0i8; size];
5300    let mut body_period_total = 0.0;
5301    let mut body_trailing_idx = lookback_total - 2 - body_doji_period;
5302
5303    let mut i = body_trailing_idx;
5304    while i < lookback_total - 2 {
5305        body_period_total += real_body(open[i], close[i]);
5306        i += 1;
5307    }
5308
5309    i = lookback_total;
5310    while i < size {
5311        let doji_avg = candle_average(body_period_total, body_doji_period);
5312        if real_body(open[i - 2], close[i - 2]) <= doji_avg
5313            && real_body(open[i - 1], close[i - 1]) <= doji_avg
5314            && real_body(open[i], close[i]) <= doji_avg
5315        {
5316            let mut value = 0i8;
5317            if real_body_gap_up(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
5318                && open[i].max(close[i]) < open[i - 1].max(close[i - 1])
5319            {
5320                value = -100;
5321            }
5322            if real_body_gap_down(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
5323                && open[i].min(close[i]) > open[i - 1].min(close[i - 1])
5324            {
5325                value = 100;
5326            }
5327            out[i] = value;
5328        }
5329
5330        body_period_total += real_body(open[i - 2], close[i - 2])
5331            - real_body(open[body_trailing_idx], close[body_trailing_idx]);
5332        i += 1;
5333        body_trailing_idx += 1;
5334    }
5335
5336    Ok(PatternOutput { values: out })
5337}
5338
5339#[inline]
5340pub fn cdlidentical3crows(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5341    let (open, _high, low, close) = input_ohlc(&input.data)?;
5342
5343    let size = open.len();
5344    let shadow_very_short_period = 10;
5345    let equal_period = 10;
5346    let lookback_total = shadow_very_short_period.max(equal_period) + 2;
5347
5348    if size < lookback_total {
5349        return Err(PatternError::NotEnoughData {
5350            len: size,
5351            pattern: input.params.pattern_type,
5352        });
5353    }
5354
5355    #[inline(always)]
5356    fn candle_average(sum: f64, period: usize) -> f64 {
5357        if period == 0 {
5358            0.0
5359        } else {
5360            sum / period as f64
5361        }
5362    }
5363
5364    let mut out = vec![0i8; size];
5365    let mut shadow_total_2 = 0.0;
5366    let mut shadow_total_1 = 0.0;
5367    let mut shadow_total_0 = 0.0;
5368    let mut equal_total_2 = 0.0;
5369    let mut equal_total_1 = 0.0;
5370    let mut shadow_trailing_idx = lookback_total - shadow_very_short_period;
5371    let mut equal_trailing_idx = lookback_total - equal_period;
5372
5373    let mut i = shadow_trailing_idx;
5374    while i < lookback_total {
5375        shadow_total_2 += lower_shadow(open[i - 2], low[i - 2], close[i - 2]);
5376        shadow_total_1 += lower_shadow(open[i - 1], low[i - 1], close[i - 1]);
5377        shadow_total_0 += lower_shadow(open[i], low[i], close[i]);
5378        i += 1;
5379    }
5380    i = equal_trailing_idx;
5381    while i < lookback_total {
5382        equal_total_2 += real_body(open[i - 2], close[i - 2]);
5383        equal_total_1 += real_body(open[i - 1], close[i - 1]);
5384        i += 1;
5385    }
5386
5387    i = lookback_total;
5388    while i < size {
5389        if candle_color(open[i - 2], close[i - 2]) == -1
5390            && lower_shadow(open[i - 2], low[i - 2], close[i - 2])
5391                < candle_average(shadow_total_2, shadow_very_short_period)
5392            && candle_color(open[i - 1], close[i - 1]) == -1
5393            && lower_shadow(open[i - 1], low[i - 1], close[i - 1])
5394                < candle_average(shadow_total_1, shadow_very_short_period)
5395            && candle_color(open[i], close[i]) == -1
5396            && lower_shadow(open[i], low[i], close[i])
5397                < candle_average(shadow_total_0, shadow_very_short_period)
5398            && close[i - 2] > close[i - 1]
5399            && close[i - 1] > close[i]
5400            && open[i - 1] <= close[i - 2] + candle_average(equal_total_2, equal_period)
5401            && open[i - 1] >= close[i - 2] - candle_average(equal_total_2, equal_period)
5402            && open[i] <= close[i - 1] + candle_average(equal_total_1, equal_period)
5403            && open[i] >= close[i - 1] - candle_average(equal_total_1, equal_period)
5404        {
5405            out[i] = -100;
5406        }
5407
5408        shadow_total_2 += lower_shadow(open[i - 2], low[i - 2], close[i - 2])
5409            - lower_shadow(
5410                open[shadow_trailing_idx - 2],
5411                low[shadow_trailing_idx - 2],
5412                close[shadow_trailing_idx - 2],
5413            );
5414        shadow_total_1 += lower_shadow(open[i - 1], low[i - 1], close[i - 1])
5415            - lower_shadow(
5416                open[shadow_trailing_idx - 1],
5417                low[shadow_trailing_idx - 1],
5418                close[shadow_trailing_idx - 1],
5419            );
5420        shadow_total_0 += lower_shadow(open[i], low[i], close[i])
5421            - lower_shadow(
5422                open[shadow_trailing_idx],
5423                low[shadow_trailing_idx],
5424                close[shadow_trailing_idx],
5425            );
5426
5427        equal_total_2 += real_body(open[i - 2], close[i - 2])
5428            - real_body(open[equal_trailing_idx - 2], close[equal_trailing_idx - 2]);
5429        equal_total_1 += real_body(open[i - 1], close[i - 1])
5430            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
5431
5432        i += 1;
5433        shadow_trailing_idx += 1;
5434        equal_trailing_idx += 1;
5435    }
5436
5437    Ok(PatternOutput { values: out })
5438}
5439
5440#[inline]
5441pub fn cdlsticksandwich(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5442    let (open, _high, low, close) = input_ohlc(&input.data)?;
5443
5444    let size = open.len();
5445    let equal_period = 10;
5446    let lookback_total = equal_period + 2;
5447
5448    if size < lookback_total {
5449        return Err(PatternError::NotEnoughData {
5450            len: size,
5451            pattern: input.params.pattern_type,
5452        });
5453    }
5454
5455    #[inline(always)]
5456    fn candle_average(sum: f64, period: usize) -> f64 {
5457        if period == 0 {
5458            0.0
5459        } else {
5460            sum / period as f64
5461        }
5462    }
5463
5464    let mut out = vec![0i8; size];
5465    let mut equal_period_total = 0.0;
5466    let mut equal_trailing_idx = lookback_total - equal_period;
5467
5468    let mut i = equal_trailing_idx;
5469    while i < lookback_total {
5470        equal_period_total += real_body(open[i - 2], close[i - 2]);
5471        i += 1;
5472    }
5473
5474    i = lookback_total;
5475    while i < size {
5476        let eq_avg = candle_average(equal_period_total, equal_period);
5477        if candle_color(open[i - 2], close[i - 2]) == -1
5478            && candle_color(open[i - 1], close[i - 1]) == 1
5479            && candle_color(open[i], close[i]) == -1
5480            && low[i - 1] > close[i - 2]
5481            && close[i] <= close[i - 2] + eq_avg
5482            && close[i] >= close[i - 2] - eq_avg
5483        {
5484            out[i] = 100;
5485        }
5486
5487        equal_period_total += real_body(open[i - 2], close[i - 2])
5488            - real_body(open[equal_trailing_idx - 2], close[equal_trailing_idx - 2]);
5489        i += 1;
5490        equal_trailing_idx += 1;
5491    }
5492
5493    Ok(PatternOutput { values: out })
5494}
5495
5496#[inline]
5497pub fn cdlseparatinglines(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5498    let (open, high, low, close) = input_ohlc(&input.data)?;
5499
5500    let size = open.len();
5501    let shadow_very_short_period = 10;
5502    let body_long_period = 10;
5503    let equal_period = 10;
5504    let lookback_total = shadow_very_short_period
5505        .max(body_long_period)
5506        .max(equal_period)
5507        + 1;
5508
5509    if size < lookback_total {
5510        return Err(PatternError::NotEnoughData {
5511            len: size,
5512            pattern: input.params.pattern_type,
5513        });
5514    }
5515
5516    #[inline(always)]
5517    fn candle_average(sum: f64, period: usize) -> f64 {
5518        if period == 0 {
5519            0.0
5520        } else {
5521            sum / period as f64
5522        }
5523    }
5524
5525    #[inline(always)]
5526    fn max_shadow(open: f64, high: f64, low: f64, close: f64) -> f64 {
5527        upper_shadow(open, high, close).max(lower_shadow(open, low, close))
5528    }
5529
5530    let mut out = vec![0i8; size];
5531    let mut shadow_very_short_period_total = 0.0;
5532    let mut body_long_period_total = 0.0;
5533    let mut equal_period_total = 0.0;
5534    let mut shadow_very_short_trailing_idx = lookback_total - shadow_very_short_period;
5535    let mut body_long_trailing_idx = lookback_total - body_long_period;
5536    let mut equal_trailing_idx = lookback_total - equal_period;
5537
5538    let mut i = shadow_very_short_trailing_idx;
5539    while i < lookback_total {
5540        shadow_very_short_period_total += max_shadow(open[i], high[i], low[i], close[i]);
5541        i += 1;
5542    }
5543    i = body_long_trailing_idx;
5544    while i < lookback_total {
5545        body_long_period_total += real_body(open[i], close[i]);
5546        i += 1;
5547    }
5548    i = equal_trailing_idx;
5549    while i < lookback_total {
5550        equal_period_total += real_body(open[i - 1], close[i - 1]);
5551        i += 1;
5552    }
5553
5554    i = lookback_total;
5555    while i < size {
5556        if candle_color(open[i - 1], close[i - 1]) == -candle_color(open[i], close[i])
5557            && open[i] <= open[i - 1] + candle_average(equal_period_total, equal_period)
5558            && open[i] >= open[i - 1] - candle_average(equal_period_total, equal_period)
5559            && real_body(open[i], close[i]) > candle_average(body_long_period_total, body_long_period)
5560            && ((candle_color(open[i], close[i]) == 1
5561                && lower_shadow(open[i], low[i], close[i])
5562                    < candle_average(shadow_very_short_period_total, shadow_very_short_period))
5563                || (candle_color(open[i], close[i]) == -1
5564                    && upper_shadow(open[i], high[i], close[i])
5565                        < candle_average(shadow_very_short_period_total, shadow_very_short_period)))
5566        {
5567            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
5568        }
5569
5570        shadow_very_short_period_total += max_shadow(open[i], high[i], low[i], close[i])
5571            - max_shadow(
5572                open[shadow_very_short_trailing_idx],
5573                high[shadow_very_short_trailing_idx],
5574                low[shadow_very_short_trailing_idx],
5575                close[shadow_very_short_trailing_idx],
5576            );
5577        body_long_period_total += real_body(open[i], close[i])
5578            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
5579        equal_period_total += real_body(open[i - 1], close[i - 1])
5580            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
5581
5582        i += 1;
5583        shadow_very_short_trailing_idx += 1;
5584        body_long_trailing_idx += 1;
5585        equal_trailing_idx += 1;
5586    }
5587
5588    Ok(PatternOutput { values: out })
5589}
5590
5591#[inline]
5592pub fn cdlgapsidesidewhite(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5593    let (open, _high, _low, close) = input_ohlc(&input.data)?;
5594
5595    let size = open.len();
5596    let near_period = 10;
5597    let equal_period = 10;
5598    let lookback_total = near_period.max(equal_period) + 2;
5599
5600    if size < lookback_total {
5601        return Err(PatternError::NotEnoughData {
5602            len: size,
5603            pattern: input.params.pattern_type,
5604        });
5605    }
5606
5607    #[inline(always)]
5608    fn candle_average(sum: f64, period: usize) -> f64 {
5609        if period == 0 {
5610            0.0
5611        } else {
5612            sum / period as f64
5613        }
5614    }
5615
5616    #[inline(always)]
5617    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5618        curr_open.min(curr_close) > prev_open.max(prev_close)
5619    }
5620
5621    #[inline(always)]
5622    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5623        curr_open.max(curr_close) < prev_open.min(prev_close)
5624    }
5625
5626    let mut out = vec![0i8; size];
5627    let mut near_period_total = 0.0;
5628    let mut equal_period_total = 0.0;
5629    let mut near_trailing_idx = lookback_total - near_period;
5630    let mut equal_trailing_idx = lookback_total - equal_period;
5631
5632    let mut i = near_trailing_idx;
5633    while i < lookback_total {
5634        near_period_total += real_body(open[i - 1], close[i - 1]);
5635        i += 1;
5636    }
5637    i = equal_trailing_idx;
5638    while i < lookback_total {
5639        equal_period_total += real_body(open[i - 1], close[i - 1]);
5640        i += 1;
5641    }
5642
5643    i = lookback_total;
5644    while i < size {
5645        let gap_up_1 = real_body_gap_up(open[i - 1], close[i - 1], open[i - 2], close[i - 2]);
5646        let gap_down_1 = real_body_gap_down(open[i - 1], close[i - 1], open[i - 2], close[i - 2]);
5647        if ((gap_up_1 && real_body_gap_up(open[i], close[i], open[i - 2], close[i - 2]))
5648            || (gap_down_1 && real_body_gap_down(open[i], close[i], open[i - 2], close[i - 2])))
5649            && candle_color(open[i - 1], close[i - 1]) == 1
5650            && candle_color(open[i], close[i]) == 1
5651            && real_body(open[i], close[i]) >= real_body(open[i - 1], close[i - 1])
5652                - candle_average(near_period_total, near_period)
5653            && real_body(open[i], close[i]) <= real_body(open[i - 1], close[i - 1])
5654                + candle_average(near_period_total, near_period)
5655            && open[i] >= open[i - 1] - candle_average(equal_period_total, equal_period)
5656            && open[i] <= open[i - 1] + candle_average(equal_period_total, equal_period)
5657        {
5658            out[i] = if gap_up_1 { 100 } else { -100 };
5659        }
5660
5661        near_period_total += real_body(open[i - 1], close[i - 1])
5662            - real_body(open[near_trailing_idx - 1], close[near_trailing_idx - 1]);
5663        equal_period_total += real_body(open[i - 1], close[i - 1])
5664            - real_body(open[equal_trailing_idx - 1], close[equal_trailing_idx - 1]);
5665
5666        i += 1;
5667        near_trailing_idx += 1;
5668        equal_trailing_idx += 1;
5669    }
5670
5671    Ok(PatternOutput { values: out })
5672}
5673
5674#[inline]
5675pub fn cdlhikkake(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5676    let (_open, high, low, close) = input_ohlc(&input.data)?;
5677
5678    let size = high.len();
5679    let lookback_total = 5;
5680
5681    if size < lookback_total {
5682        return Err(PatternError::NotEnoughData {
5683            len: size,
5684            pattern: input.params.pattern_type,
5685        });
5686    }
5687
5688    let mut out = vec![0i8; size];
5689    let mut pattern_idx = usize::MAX;
5690    let mut pattern_result = 0i8;
5691
5692    let mut i = lookback_total - 3;
5693    while i < lookback_total {
5694        if high[i - 1] < high[i - 2]
5695            && low[i - 1] > low[i - 2]
5696            && ((high[i] < high[i - 1] && low[i] < low[i - 1])
5697                || (high[i] > high[i - 1] && low[i] > low[i - 1]))
5698        {
5699            pattern_result = if high[i] < high[i - 1] { 100 } else { -100 };
5700            pattern_idx = i;
5701        } else if pattern_idx != usize::MAX
5702            && pattern_idx > 0
5703            && i <= pattern_idx + 3
5704            && ((pattern_result > 0 && close[i] > high[pattern_idx - 1])
5705                || (pattern_result < 0 && close[i] < low[pattern_idx - 1]))
5706        {
5707            pattern_idx = usize::MAX;
5708        }
5709        i += 1;
5710    }
5711
5712    i = lookback_total;
5713    while i < size {
5714        if high[i - 1] < high[i - 2]
5715            && low[i - 1] > low[i - 2]
5716            && ((high[i] < high[i - 1] && low[i] < low[i - 1])
5717                || (high[i] > high[i - 1] && low[i] > low[i - 1]))
5718        {
5719            pattern_result = if high[i] < high[i - 1] { 100 } else { -100 };
5720            pattern_idx = i;
5721            out[i] = pattern_result;
5722        } else if pattern_idx != usize::MAX
5723            && pattern_idx > 0
5724            && i <= pattern_idx + 3
5725            && ((pattern_result > 0 && close[i] > high[pattern_idx - 1])
5726                || (pattern_result < 0 && close[i] < low[pattern_idx - 1]))
5727        {
5728            out[i] = pattern_result;
5729            pattern_idx = usize::MAX;
5730        }
5731
5732        i += 1;
5733    }
5734
5735    Ok(PatternOutput { values: out })
5736}
5737
5738#[inline]
5739pub fn cdlhikkakemod(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5740    let (open, high, low, close) = input_ohlc(&input.data)?;
5741
5742    let size = open.len();
5743    let near_period = 10;
5744    let lookback_total = near_period.max(1) + 5;
5745
5746    if size < lookback_total {
5747        return Err(PatternError::NotEnoughData {
5748            len: size,
5749            pattern: input.params.pattern_type,
5750        });
5751    }
5752
5753    #[inline(always)]
5754    fn candle_average(sum: f64, period: usize) -> f64 {
5755        if period == 0 {
5756            0.0
5757        } else {
5758            sum / period as f64
5759        }
5760    }
5761
5762    let mut out = vec![0i8; size];
5763    let mut near_period_total = 0.0;
5764    let mut near_trailing_idx = lookback_total - 3 - near_period;
5765    let mut pattern_idx = usize::MAX;
5766    let mut pattern_result = 0i8;
5767
5768    let mut i = near_trailing_idx;
5769    while i < lookback_total - 3 {
5770        near_period_total += real_body(open[i - 2], close[i - 2]);
5771        i += 1;
5772    }
5773
5774    i = lookback_total - 3;
5775    while i < lookback_total {
5776        if high[i - 2] < high[i - 3]
5777            && low[i - 2] > low[i - 3]
5778            && high[i - 1] < high[i - 2]
5779            && low[i - 1] > low[i - 2]
5780            && ((high[i] < high[i - 1]
5781                && low[i] < low[i - 1]
5782                && close[i - 2] <= low[i - 2] + candle_average(near_period_total, near_period))
5783                || (high[i] > high[i - 1]
5784                    && low[i] > low[i - 1]
5785                    && close[i - 2] >= high[i - 2] - candle_average(near_period_total, near_period)))
5786        {
5787            pattern_result = if high[i] < high[i - 1] { 100 } else { -100 };
5788            pattern_idx = i;
5789        } else if pattern_idx != usize::MAX
5790            && pattern_idx > 0
5791            && i <= pattern_idx + 3
5792            && ((pattern_result > 0 && close[i] > high[pattern_idx - 1])
5793                || (pattern_result < 0 && close[i] < low[pattern_idx - 1]))
5794        {
5795            pattern_idx = usize::MAX;
5796        }
5797
5798        near_period_total += real_body(open[i - 2], close[i - 2])
5799            - real_body(open[near_trailing_idx - 2], close[near_trailing_idx - 2]);
5800        near_trailing_idx += 1;
5801        i += 1;
5802    }
5803
5804    i = lookback_total;
5805    while i < size {
5806        if high[i - 2] < high[i - 3]
5807            && low[i - 2] > low[i - 3]
5808            && high[i - 1] < high[i - 2]
5809            && low[i - 1] > low[i - 2]
5810            && ((high[i] < high[i - 1]
5811                && low[i] < low[i - 1]
5812                && close[i - 2] <= low[i - 2] + candle_average(near_period_total, near_period))
5813                || (high[i] > high[i - 1]
5814                    && low[i] > low[i - 1]
5815                    && close[i - 2] >= high[i - 2] - candle_average(near_period_total, near_period)))
5816        {
5817            pattern_result = if high[i] < high[i - 1] { 100 } else { -100 };
5818            pattern_idx = i;
5819            out[i] = pattern_result;
5820        } else if pattern_idx != usize::MAX
5821            && pattern_idx > 0
5822            && i <= pattern_idx + 3
5823            && ((pattern_result > 0 && close[i] > high[pattern_idx - 1])
5824                || (pattern_result < 0 && close[i] < low[pattern_idx - 1]))
5825        {
5826            out[i] = pattern_result;
5827            pattern_idx = usize::MAX;
5828        }
5829
5830        near_period_total += real_body(open[i - 2], close[i - 2])
5831            - real_body(open[near_trailing_idx - 2], close[near_trailing_idx - 2]);
5832        near_trailing_idx += 1;
5833        i += 1;
5834    }
5835
5836    Ok(PatternOutput { values: out })
5837}
5838
5839#[inline]
5840pub fn cdlkicking(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5841    let (open, high, low, close) = input_ohlc(&input.data)?;
5842
5843    let size = open.len();
5844    let shadow_very_short_period = 10;
5845    let body_long_period = 10;
5846    let lookback_total = shadow_very_short_period.max(body_long_period) + 1;
5847
5848    if size < lookback_total {
5849        return Err(PatternError::NotEnoughData {
5850            len: size,
5851            pattern: input.params.pattern_type,
5852        });
5853    }
5854
5855    #[inline(always)]
5856    fn candle_average(sum: f64, period: usize) -> f64 {
5857        if period == 0 {
5858            0.0
5859        } else {
5860            sum / period as f64
5861        }
5862    }
5863
5864    #[inline(always)]
5865    fn candle_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5866        curr_open.min(curr_close) > prev_open.max(prev_close)
5867    }
5868
5869    #[inline(always)]
5870    fn candle_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5871        curr_open.max(curr_close) < prev_open.min(prev_close)
5872    }
5873
5874    let mut out = vec![0i8; size];
5875    let mut shadow_total_prev = 0.0;
5876    let mut shadow_total_curr = 0.0;
5877    let mut body_total_prev = 0.0;
5878    let mut body_total_curr = 0.0;
5879    let mut shadow_trailing_idx = lookback_total - shadow_very_short_period;
5880    let mut body_trailing_idx = lookback_total - body_long_period;
5881
5882    let mut i = shadow_trailing_idx;
5883    while i < lookback_total {
5884        shadow_total_prev += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
5885            .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]));
5886        shadow_total_curr += upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]));
5887        i += 1;
5888    }
5889    i = body_trailing_idx;
5890    while i < lookback_total {
5891        body_total_prev += real_body(open[i - 1], close[i - 1]);
5892        body_total_curr += real_body(open[i], close[i]);
5893        i += 1;
5894    }
5895
5896    i = lookback_total;
5897    while i < size {
5898        if candle_color(open[i - 1], close[i - 1]) == -candle_color(open[i], close[i])
5899            && real_body(open[i - 1], close[i - 1]) > candle_average(body_total_prev, body_long_period)
5900            && upper_shadow(open[i - 1], high[i - 1], close[i - 1])
5901                .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]))
5902                < candle_average(shadow_total_prev, shadow_very_short_period)
5903            && real_body(open[i], close[i]) > candle_average(body_total_curr, body_long_period)
5904            && upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]))
5905                < candle_average(shadow_total_curr, shadow_very_short_period)
5906            && ((candle_color(open[i - 1], close[i - 1]) == -1
5907                && candle_gap_up(open[i], close[i], open[i - 1], close[i - 1]))
5908                || (candle_color(open[i - 1], close[i - 1]) == 1
5909                    && candle_gap_down(open[i], close[i], open[i - 1], close[i - 1])))
5910        {
5911            out[i] = (candle_color(open[i], close[i]) as i8) * 100;
5912        }
5913
5914        shadow_total_prev += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
5915            .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]))
5916            - upper_shadow(
5917                open[shadow_trailing_idx - 1],
5918                high[shadow_trailing_idx - 1],
5919                close[shadow_trailing_idx - 1],
5920            )
5921            .max(lower_shadow(
5922                open[shadow_trailing_idx - 1],
5923                low[shadow_trailing_idx - 1],
5924                close[shadow_trailing_idx - 1],
5925            ));
5926        shadow_total_curr += upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]))
5927            - upper_shadow(
5928                open[shadow_trailing_idx],
5929                high[shadow_trailing_idx],
5930                close[shadow_trailing_idx],
5931            )
5932            .max(lower_shadow(
5933                open[shadow_trailing_idx],
5934                low[shadow_trailing_idx],
5935                close[shadow_trailing_idx],
5936            ));
5937        body_total_prev += real_body(open[i - 1], close[i - 1])
5938            - real_body(open[body_trailing_idx - 1], close[body_trailing_idx - 1]);
5939        body_total_curr +=
5940            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
5941
5942        i += 1;
5943        shadow_trailing_idx += 1;
5944        body_trailing_idx += 1;
5945    }
5946
5947    Ok(PatternOutput { values: out })
5948}
5949
5950#[inline]
5951pub fn cdlkickingbylength(input: &PatternInput) -> Result<PatternOutput, PatternError> {
5952    let (open, high, low, close) = input_ohlc(&input.data)?;
5953
5954    let size = open.len();
5955    let shadow_very_short_period = 10;
5956    let body_long_period = 10;
5957    let lookback_total = shadow_very_short_period.max(body_long_period) + 1;
5958
5959    if size < lookback_total {
5960        return Err(PatternError::NotEnoughData {
5961            len: size,
5962            pattern: input.params.pattern_type,
5963        });
5964    }
5965
5966    #[inline(always)]
5967    fn candle_average(sum: f64, period: usize) -> f64 {
5968        if period == 0 {
5969            0.0
5970        } else {
5971            sum / period as f64
5972        }
5973    }
5974
5975    #[inline(always)]
5976    fn candle_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5977        curr_open.min(curr_close) > prev_open.max(prev_close)
5978    }
5979
5980    #[inline(always)]
5981    fn candle_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
5982        curr_open.max(curr_close) < prev_open.min(prev_close)
5983    }
5984
5985    let mut out = vec![0i8; size];
5986    let mut shadow_total_prev = 0.0;
5987    let mut shadow_total_curr = 0.0;
5988    let mut body_total_prev = 0.0;
5989    let mut body_total_curr = 0.0;
5990    let mut shadow_trailing_idx = lookback_total - shadow_very_short_period;
5991    let mut body_trailing_idx = lookback_total - body_long_period;
5992
5993    let mut i = shadow_trailing_idx;
5994    while i < lookback_total {
5995        shadow_total_prev += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
5996            .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]));
5997        shadow_total_curr += upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]));
5998        i += 1;
5999    }
6000    i = body_trailing_idx;
6001    while i < lookback_total {
6002        body_total_prev += real_body(open[i - 1], close[i - 1]);
6003        body_total_curr += real_body(open[i], close[i]);
6004        i += 1;
6005    }
6006
6007    i = lookback_total;
6008    while i < size {
6009        if candle_color(open[i - 1], close[i - 1]) == -candle_color(open[i], close[i])
6010            && real_body(open[i - 1], close[i - 1]) > candle_average(body_total_prev, body_long_period)
6011            && upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6012                .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]))
6013                < candle_average(shadow_total_prev, shadow_very_short_period)
6014            && real_body(open[i], close[i]) > candle_average(body_total_curr, body_long_period)
6015            && upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]))
6016                < candle_average(shadow_total_curr, shadow_very_short_period)
6017            && ((candle_color(open[i - 1], close[i - 1]) == -1
6018                && candle_gap_up(open[i], close[i], open[i - 1], close[i - 1]))
6019                || (candle_color(open[i - 1], close[i - 1]) == 1
6020                    && candle_gap_down(open[i], close[i], open[i - 1], close[i - 1])))
6021        {
6022            out[i] = if real_body(open[i], close[i]) > real_body(open[i - 1], close[i - 1]) {
6023                (candle_color(open[i], close[i]) as i8) * 100
6024            } else {
6025                (candle_color(open[i - 1], close[i - 1]) as i8) * 100
6026            };
6027        }
6028
6029        shadow_total_prev += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6030            .max(lower_shadow(open[i - 1], low[i - 1], close[i - 1]))
6031            - upper_shadow(
6032                open[shadow_trailing_idx - 1],
6033                high[shadow_trailing_idx - 1],
6034                close[shadow_trailing_idx - 1],
6035            )
6036            .max(lower_shadow(
6037                open[shadow_trailing_idx - 1],
6038                low[shadow_trailing_idx - 1],
6039                close[shadow_trailing_idx - 1],
6040            ));
6041        shadow_total_curr += upper_shadow(open[i], high[i], close[i]).max(lower_shadow(open[i], low[i], close[i]))
6042            - upper_shadow(
6043                open[shadow_trailing_idx],
6044                high[shadow_trailing_idx],
6045                close[shadow_trailing_idx],
6046            )
6047            .max(lower_shadow(
6048                open[shadow_trailing_idx],
6049                low[shadow_trailing_idx],
6050                close[shadow_trailing_idx],
6051            ));
6052        body_total_prev += real_body(open[i - 1], close[i - 1])
6053            - real_body(open[body_trailing_idx - 1], close[body_trailing_idx - 1]);
6054        body_total_curr +=
6055            real_body(open[i], close[i]) - real_body(open[body_trailing_idx], close[body_trailing_idx]);
6056
6057        i += 1;
6058        shadow_trailing_idx += 1;
6059        body_trailing_idx += 1;
6060    }
6061
6062    Ok(PatternOutput { values: out })
6063}
6064
6065#[inline]
6066pub fn cdlladderbottom(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6067    let (open, high, _low, close) = input_ohlc(&input.data)?;
6068
6069    let size = open.len();
6070    let shadow_very_short_period = 10;
6071    let lookback_total = shadow_very_short_period + 4;
6072
6073    if size < lookback_total {
6074        return Err(PatternError::NotEnoughData {
6075            len: size,
6076            pattern: input.params.pattern_type,
6077        });
6078    }
6079
6080    #[inline(always)]
6081    fn candle_average(sum: f64, period: usize) -> f64 {
6082        if period == 0 {
6083            0.0
6084        } else {
6085            sum / period as f64
6086        }
6087    }
6088
6089    let mut out = vec![0i8; size];
6090    let mut shadow_very_short_total = 0.0;
6091    let mut shadow_trailing_idx = lookback_total - shadow_very_short_period;
6092
6093    let mut i = shadow_trailing_idx;
6094    while i < lookback_total {
6095        shadow_very_short_total += upper_shadow(open[i - 1], high[i - 1], close[i - 1]);
6096        i += 1;
6097    }
6098
6099    i = lookback_total;
6100    while i < size {
6101        if candle_color(open[i - 4], close[i - 4]) == -1
6102            && candle_color(open[i - 3], close[i - 3]) == -1
6103            && candle_color(open[i - 2], close[i - 2]) == -1
6104            && open[i - 4] > open[i - 3]
6105            && open[i - 3] > open[i - 2]
6106            && close[i - 4] > close[i - 3]
6107            && close[i - 3] > close[i - 2]
6108            && candle_color(open[i - 1], close[i - 1]) == -1
6109            && upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6110                > candle_average(shadow_very_short_total, shadow_very_short_period)
6111            && candle_color(open[i], close[i]) == 1
6112            && open[i] > open[i - 1]
6113            && close[i] > high[i - 1]
6114        {
6115            out[i] = 100;
6116        }
6117
6118        shadow_very_short_total += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6119            - upper_shadow(
6120                open[shadow_trailing_idx - 1],
6121                high[shadow_trailing_idx - 1],
6122                close[shadow_trailing_idx - 1],
6123            );
6124        i += 1;
6125        shadow_trailing_idx += 1;
6126    }
6127
6128    Ok(PatternOutput { values: out })
6129}
6130
6131#[inline]
6132pub fn cdlmathold(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6133    let (open, high, _low, close) = input_ohlc(&input.data)?;
6134
6135    let size = open.len();
6136    let body_short_period = 10;
6137    let body_long_period = 10;
6138    let penetration = if input.params.penetration == 0.0 {
6139        0.5
6140    } else {
6141        input.params.penetration
6142    };
6143    let lookback_total = body_short_period.max(body_long_period) + 4;
6144
6145    if size < lookback_total {
6146        return Err(PatternError::NotEnoughData {
6147            len: size,
6148            pattern: input.params.pattern_type,
6149        });
6150    }
6151
6152    #[inline(always)]
6153    fn candle_average(sum: f64, period: usize) -> f64 {
6154        if period == 0 {
6155            0.0
6156        } else {
6157            sum / period as f64
6158        }
6159    }
6160
6161    #[inline(always)]
6162    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6163        curr_open.min(curr_close) > prev_open.max(prev_close)
6164    }
6165
6166    let mut out = vec![0i8; size];
6167    let mut body_total_4 = 0.0;
6168    let mut body_total_3 = 0.0;
6169    let mut body_total_2 = 0.0;
6170    let mut body_total_1 = 0.0;
6171    let mut body_short_trailing_idx = lookback_total - body_short_period;
6172    let mut body_long_trailing_idx = lookback_total - body_long_period;
6173
6174    let mut i = body_short_trailing_idx;
6175    while i < lookback_total {
6176        body_total_3 += real_body(open[i - 3], close[i - 3]);
6177        body_total_2 += real_body(open[i - 2], close[i - 2]);
6178        body_total_1 += real_body(open[i - 1], close[i - 1]);
6179        i += 1;
6180    }
6181    i = body_long_trailing_idx;
6182    while i < lookback_total {
6183        body_total_4 += real_body(open[i - 4], close[i - 4]);
6184        i += 1;
6185    }
6186
6187    i = lookback_total;
6188    while i < size {
6189        if real_body(open[i - 4], close[i - 4]) > candle_average(body_total_4, body_long_period)
6190            && real_body(open[i - 3], close[i - 3]) < candle_average(body_total_3, body_short_period)
6191            && real_body(open[i - 2], close[i - 2]) < candle_average(body_total_2, body_short_period)
6192            && real_body(open[i - 1], close[i - 1]) < candle_average(body_total_1, body_short_period)
6193            && candle_color(open[i - 4], close[i - 4]) == 1
6194            && candle_color(open[i - 3], close[i - 3]) == -1
6195            && candle_color(open[i], close[i]) == 1
6196            && real_body_gap_up(open[i - 3], close[i - 3], open[i - 4], close[i - 4])
6197            && open[i - 2].min(close[i - 2]) < close[i - 4]
6198            && open[i - 1].min(close[i - 1]) < close[i - 4]
6199            && open[i - 2].min(close[i - 2]) > close[i - 4] - real_body(open[i - 4], close[i - 4]) * penetration
6200            && open[i - 1].min(close[i - 1]) > close[i - 4] - real_body(open[i - 4], close[i - 4]) * penetration
6201            && open[i - 2].max(close[i - 2]) < open[i - 3]
6202            && open[i - 1].max(close[i - 1]) < open[i - 2].max(close[i - 2])
6203            && open[i] > close[i - 1]
6204            && close[i] > high[i - 3].max(high[i - 2]).max(high[i - 1])
6205        {
6206            out[i] = 100;
6207        }
6208
6209        body_total_4 +=
6210            real_body(open[i - 4], close[i - 4]) - real_body(open[body_long_trailing_idx - 4], close[body_long_trailing_idx - 4]);
6211        body_total_3 +=
6212            real_body(open[i - 3], close[i - 3]) - real_body(open[body_short_trailing_idx - 3], close[body_short_trailing_idx - 3]);
6213        body_total_2 +=
6214            real_body(open[i - 2], close[i - 2]) - real_body(open[body_short_trailing_idx - 2], close[body_short_trailing_idx - 2]);
6215        body_total_1 +=
6216            real_body(open[i - 1], close[i - 1]) - real_body(open[body_short_trailing_idx - 1], close[body_short_trailing_idx - 1]);
6217
6218        i += 1;
6219        body_short_trailing_idx += 1;
6220        body_long_trailing_idx += 1;
6221    }
6222
6223    Ok(PatternOutput { values: out })
6224}
6225
6226#[inline]
6227pub fn cdlrisefall3methods(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6228    let (open, high, low, close) = input_ohlc(&input.data)?;
6229
6230    let size = open.len();
6231    let body_short_period = 10;
6232    let body_long_period = 10;
6233    let lookback_total = body_short_period.max(body_long_period) + 4;
6234
6235    if size < lookback_total {
6236        return Err(PatternError::NotEnoughData {
6237            len: size,
6238            pattern: input.params.pattern_type,
6239        });
6240    }
6241
6242    #[inline(always)]
6243    fn candle_average(sum: f64, period: usize) -> f64 {
6244        if period == 0 {
6245            0.0
6246        } else {
6247            sum / period as f64
6248        }
6249    }
6250
6251    let mut out = vec![0i8; size];
6252    let mut body_total_4 = 0.0;
6253    let mut body_total_3 = 0.0;
6254    let mut body_total_2 = 0.0;
6255    let mut body_total_1 = 0.0;
6256    let mut body_total_0 = 0.0;
6257    let mut body_short_trailing_idx = lookback_total - body_short_period;
6258    let mut body_long_trailing_idx = lookback_total - body_long_period;
6259
6260    let mut i = body_short_trailing_idx;
6261    while i < lookback_total {
6262        body_total_3 += real_body(open[i - 3], close[i - 3]);
6263        body_total_2 += real_body(open[i - 2], close[i - 2]);
6264        body_total_1 += real_body(open[i - 1], close[i - 1]);
6265        i += 1;
6266    }
6267    i = body_long_trailing_idx;
6268    while i < lookback_total {
6269        body_total_4 += real_body(open[i - 4], close[i - 4]);
6270        body_total_0 += real_body(open[i], close[i]);
6271        i += 1;
6272    }
6273
6274    i = lookback_total;
6275    while i < size {
6276        let c4 = candle_color(open[i - 4], close[i - 4]);
6277        if real_body(open[i - 4], close[i - 4]) > candle_average(body_total_4, body_long_period)
6278            && real_body(open[i - 3], close[i - 3]) < candle_average(body_total_3, body_short_period)
6279            && real_body(open[i - 2], close[i - 2]) < candle_average(body_total_2, body_short_period)
6280            && real_body(open[i - 1], close[i - 1]) < candle_average(body_total_1, body_short_period)
6281            && real_body(open[i], close[i]) > candle_average(body_total_0, body_long_period)
6282            && c4 == -candle_color(open[i - 3], close[i - 3])
6283            && candle_color(open[i - 3], close[i - 3]) == candle_color(open[i - 2], close[i - 2])
6284            && candle_color(open[i - 2], close[i - 2]) == candle_color(open[i - 1], close[i - 1])
6285            && candle_color(open[i - 1], close[i - 1]) == -candle_color(open[i], close[i])
6286            && open[i - 3].min(close[i - 3]) < high[i - 4]
6287            && open[i - 3].max(close[i - 3]) > low[i - 4]
6288            && open[i - 2].min(close[i - 2]) < high[i - 4]
6289            && open[i - 2].max(close[i - 2]) > low[i - 4]
6290            && open[i - 1].min(close[i - 1]) < high[i - 4]
6291            && open[i - 1].max(close[i - 1]) > low[i - 4]
6292            && close[i - 2] * (c4 as f64) < close[i - 3] * (c4 as f64)
6293            && close[i - 1] * (c4 as f64) < close[i - 2] * (c4 as f64)
6294            && open[i] * (c4 as f64) > close[i - 1] * (c4 as f64)
6295            && close[i] * (c4 as f64) > close[i - 4] * (c4 as f64)
6296        {
6297            out[i] = (c4 as i8) * 100;
6298        }
6299
6300        body_total_4 +=
6301            real_body(open[i - 4], close[i - 4]) - real_body(open[body_long_trailing_idx - 4], close[body_long_trailing_idx - 4]);
6302        body_total_3 +=
6303            real_body(open[i - 3], close[i - 3]) - real_body(open[body_short_trailing_idx - 3], close[body_short_trailing_idx - 3]);
6304        body_total_2 +=
6305            real_body(open[i - 2], close[i - 2]) - real_body(open[body_short_trailing_idx - 2], close[body_short_trailing_idx - 2]);
6306        body_total_1 +=
6307            real_body(open[i - 1], close[i - 1]) - real_body(open[body_short_trailing_idx - 1], close[body_short_trailing_idx - 1]);
6308        body_total_0 +=
6309            real_body(open[i], close[i]) - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
6310
6311        i += 1;
6312        body_short_trailing_idx += 1;
6313        body_long_trailing_idx += 1;
6314    }
6315
6316    Ok(PatternOutput { values: out })
6317}
6318
6319#[inline]
6320pub fn cdlstalledpattern(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6321    let (open, high, _low, close) = input_ohlc(&input.data)?;
6322
6323    let size = open.len();
6324    let body_long_period = 10;
6325    let body_short_period = 10;
6326    let shadow_very_short_period = 10;
6327    let near_period = 10;
6328    let lookback_total = body_long_period
6329        .max(body_short_period)
6330        .max(shadow_very_short_period)
6331        .max(near_period)
6332        + 2;
6333
6334    if size < lookback_total {
6335        return Err(PatternError::NotEnoughData {
6336            len: size,
6337            pattern: input.params.pattern_type,
6338        });
6339    }
6340
6341    #[inline(always)]
6342    fn candle_average(sum: f64, period: usize) -> f64 {
6343        if period == 0 {
6344            0.0
6345        } else {
6346            sum / period as f64
6347        }
6348    }
6349
6350    let mut out = vec![0i8; size];
6351    let mut body_long_total_2 = 0.0;
6352    let mut body_long_total_1 = 0.0;
6353    let mut body_short_total = 0.0;
6354    let mut shadow_vs_total = 0.0;
6355    let mut near_total_2 = 0.0;
6356    let mut near_total_1 = 0.0;
6357    let mut body_long_trailing_idx = lookback_total - body_long_period;
6358    let mut body_short_trailing_idx = lookback_total - body_short_period;
6359    let mut shadow_vs_trailing_idx = lookback_total - shadow_very_short_period;
6360    let mut near_trailing_idx = lookback_total - near_period;
6361
6362    let mut i = body_long_trailing_idx;
6363    while i < lookback_total {
6364        body_long_total_2 += real_body(open[i - 2], close[i - 2]);
6365        body_long_total_1 += real_body(open[i - 1], close[i - 1]);
6366        i += 1;
6367    }
6368    i = body_short_trailing_idx;
6369    while i < lookback_total {
6370        body_short_total += real_body(open[i], close[i]);
6371        i += 1;
6372    }
6373    i = shadow_vs_trailing_idx;
6374    while i < lookback_total {
6375        shadow_vs_total += upper_shadow(open[i - 1], high[i - 1], close[i - 1]);
6376        i += 1;
6377    }
6378    i = near_trailing_idx;
6379    while i < lookback_total {
6380        near_total_2 += real_body(open[i - 2], close[i - 2]);
6381        near_total_1 += real_body(open[i - 1], close[i - 1]);
6382        i += 1;
6383    }
6384
6385    i = lookback_total;
6386    while i < size {
6387        if candle_color(open[i - 2], close[i - 2]) == 1
6388            && candle_color(open[i - 1], close[i - 1]) == 1
6389            && candle_color(open[i], close[i]) == 1
6390            && close[i] > close[i - 1]
6391            && close[i - 1] > close[i - 2]
6392            && real_body(open[i - 2], close[i - 2]) > candle_average(body_long_total_2, body_long_period)
6393            && real_body(open[i - 1], close[i - 1]) > candle_average(body_long_total_1, body_long_period)
6394            && upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6395                < candle_average(shadow_vs_total, shadow_very_short_period)
6396            && open[i - 1] > open[i - 2]
6397            && open[i - 1] <= close[i - 2] + candle_average(near_total_2, near_period)
6398            && real_body(open[i], close[i]) < candle_average(body_short_total, body_short_period)
6399            && open[i] >= close[i - 1]
6400                - real_body(open[i], close[i])
6401                - candle_average(near_total_1, near_period)
6402        {
6403            out[i] = -100;
6404        }
6405
6406        body_long_total_2 += real_body(open[i - 2], close[i - 2])
6407            - real_body(open[body_long_trailing_idx - 2], close[body_long_trailing_idx - 2]);
6408        body_long_total_1 += real_body(open[i - 1], close[i - 1])
6409            - real_body(open[body_long_trailing_idx - 1], close[body_long_trailing_idx - 1]);
6410        body_short_total +=
6411            real_body(open[i], close[i]) - real_body(open[body_short_trailing_idx], close[body_short_trailing_idx]);
6412        shadow_vs_total += upper_shadow(open[i - 1], high[i - 1], close[i - 1])
6413            - upper_shadow(
6414                open[shadow_vs_trailing_idx - 1],
6415                high[shadow_vs_trailing_idx - 1],
6416                close[shadow_vs_trailing_idx - 1],
6417            );
6418        near_total_2 += real_body(open[i - 2], close[i - 2])
6419            - real_body(open[near_trailing_idx - 2], close[near_trailing_idx - 2]);
6420        near_total_1 += real_body(open[i - 1], close[i - 1])
6421            - real_body(open[near_trailing_idx - 1], close[near_trailing_idx - 1]);
6422
6423        i += 1;
6424        body_long_trailing_idx += 1;
6425        body_short_trailing_idx += 1;
6426        shadow_vs_trailing_idx += 1;
6427        near_trailing_idx += 1;
6428    }
6429
6430    Ok(PatternOutput { values: out })
6431}
6432
6433#[inline]
6434pub fn cdltasukigap(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6435    let (open, _high, _low, close) = input_ohlc(&input.data)?;
6436
6437    let size = open.len();
6438    let near_period = 10;
6439    let lookback_total = near_period + 2;
6440
6441    if size < lookback_total {
6442        return Err(PatternError::NotEnoughData {
6443            len: size,
6444            pattern: input.params.pattern_type,
6445        });
6446    }
6447
6448    #[inline(always)]
6449    fn candle_average(sum: f64, period: usize) -> f64 {
6450        if period == 0 {
6451            0.0
6452        } else {
6453            sum / period as f64
6454        }
6455    }
6456
6457    #[inline(always)]
6458    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6459        curr_open.min(curr_close) > prev_open.max(prev_close)
6460    }
6461
6462    #[inline(always)]
6463    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6464        curr_open.max(curr_close) < prev_open.min(prev_close)
6465    }
6466
6467    let mut out = vec![0i8; size];
6468    let mut near_period_total = 0.0;
6469    let mut near_trailing_idx = lookback_total - near_period;
6470
6471    let mut i = near_trailing_idx;
6472    while i < lookback_total {
6473        near_period_total += real_body(open[i - 1], close[i - 1]);
6474        i += 1;
6475    }
6476
6477    i = lookback_total;
6478    while i < size {
6479        if (real_body_gap_up(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
6480            && candle_color(open[i - 1], close[i - 1]) == 1
6481            && candle_color(open[i], close[i]) == -1
6482            && open[i] < close[i - 1]
6483            && open[i] > open[i - 1]
6484            && close[i] < open[i - 1]
6485            && close[i] > close[i - 2].max(open[i - 2])
6486            && (real_body(open[i - 1], close[i - 1]) - real_body(open[i], close[i])).abs()
6487                < candle_average(near_period_total, near_period))
6488            || (real_body_gap_down(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
6489                && candle_color(open[i - 1], close[i - 1]) == -1
6490                && candle_color(open[i], close[i]) == 1
6491                && open[i] < open[i - 1]
6492                && open[i] > close[i - 1]
6493                && close[i] > open[i - 1]
6494                && close[i] < close[i - 2].min(open[i - 2])
6495                && (real_body(open[i - 1], close[i - 1]) - real_body(open[i], close[i])).abs()
6496                    < candle_average(near_period_total, near_period))
6497        {
6498            out[i] = (candle_color(open[i - 1], close[i - 1]) as i8) * 100;
6499        }
6500
6501        near_period_total += real_body(open[i - 1], close[i - 1])
6502            - real_body(open[near_trailing_idx - 1], close[near_trailing_idx - 1]);
6503        i += 1;
6504        near_trailing_idx += 1;
6505    }
6506
6507    Ok(PatternOutput { values: out })
6508}
6509
6510#[inline]
6511pub fn cdlunique3river(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6512    let (open, _high, low, close) = input_ohlc(&input.data)?;
6513
6514    let size = open.len();
6515    let body_short_period = 10;
6516    let body_long_period = 10;
6517    let lookback_total = body_short_period.max(body_long_period) + 2;
6518
6519    if size < lookback_total {
6520        return Err(PatternError::NotEnoughData {
6521            len: size,
6522            pattern: input.params.pattern_type,
6523        });
6524    }
6525
6526    #[inline(always)]
6527    fn candle_average(sum: f64, period: usize) -> f64 {
6528        if period == 0 {
6529            0.0
6530        } else {
6531            sum / period as f64
6532        }
6533    }
6534
6535    let mut out = vec![0i8; size];
6536    let mut body_long_total = 0.0;
6537    let mut body_short_total = 0.0;
6538    let mut body_long_trailing_idx = lookback_total - 2 - body_long_period;
6539    let mut body_short_trailing_idx = lookback_total - body_short_period;
6540
6541    let mut i = body_long_trailing_idx;
6542    while i < lookback_total - 2 {
6543        body_long_total += real_body(open[i], close[i]);
6544        i += 1;
6545    }
6546    i = body_short_trailing_idx;
6547    while i < lookback_total {
6548        body_short_total += real_body(open[i], close[i]);
6549        i += 1;
6550    }
6551
6552    i = lookback_total;
6553    while i < size {
6554        if real_body(open[i - 2], close[i - 2]) > candle_average(body_long_total, body_long_period)
6555            && candle_color(open[i - 2], close[i - 2]) == -1
6556            && candle_color(open[i - 1], close[i - 1]) == -1
6557            && close[i - 1] > close[i - 2]
6558            && open[i - 1] <= open[i - 2]
6559            && low[i - 1] < low[i - 2]
6560            && real_body(open[i], close[i]) < candle_average(body_short_total, body_short_period)
6561            && candle_color(open[i], close[i]) == 1
6562            && open[i] > low[i - 1]
6563        {
6564            out[i] = 100;
6565        }
6566
6567        body_long_total += real_body(open[i - 2], close[i - 2])
6568            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
6569        body_short_total +=
6570            real_body(open[i], close[i]) - real_body(open[body_short_trailing_idx], close[body_short_trailing_idx]);
6571        i += 1;
6572        body_long_trailing_idx += 1;
6573        body_short_trailing_idx += 1;
6574    }
6575
6576    Ok(PatternOutput { values: out })
6577}
6578
6579#[inline]
6580pub fn cdlupsidegap2crows(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6581    let (open, _high, _low, close) = input_ohlc(&input.data)?;
6582
6583    let size = open.len();
6584    let body_short_period = 10;
6585    let body_long_period = 10;
6586    let lookback_total = body_short_period.max(body_long_period) + 2;
6587
6588    if size < lookback_total {
6589        return Err(PatternError::NotEnoughData {
6590            len: size,
6591            pattern: input.params.pattern_type,
6592        });
6593    }
6594
6595    #[inline(always)]
6596    fn candle_average(sum: f64, period: usize) -> f64 {
6597        if period == 0 {
6598            0.0
6599        } else {
6600            sum / period as f64
6601        }
6602    }
6603
6604    #[inline(always)]
6605    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6606        curr_open.min(curr_close) > prev_open.max(prev_close)
6607    }
6608
6609    let mut out = vec![0i8; size];
6610    let mut body_long_total = 0.0;
6611    let mut body_short_total = 0.0;
6612    let mut body_long_trailing_idx = lookback_total - 2 - body_long_period;
6613    let mut body_short_trailing_idx = lookback_total - 1 - body_short_period;
6614
6615    let mut i = body_long_trailing_idx;
6616    while i < lookback_total - 2 {
6617        body_long_total += real_body(open[i], close[i]);
6618        i += 1;
6619    }
6620    i = body_short_trailing_idx;
6621    while i < lookback_total - 1 {
6622        body_short_total += real_body(open[i], close[i]);
6623        i += 1;
6624    }
6625
6626    i = lookback_total;
6627    while i < size {
6628        if candle_color(open[i - 2], close[i - 2]) == 1
6629            && real_body(open[i - 2], close[i - 2]) > candle_average(body_long_total, body_long_period)
6630            && candle_color(open[i - 1], close[i - 1]) == -1
6631            && real_body(open[i - 1], close[i - 1]) <= candle_average(body_short_total, body_short_period)
6632            && real_body_gap_up(open[i - 1], close[i - 1], open[i - 2], close[i - 2])
6633            && candle_color(open[i], close[i]) == -1
6634            && open[i] > open[i - 1]
6635            && close[i] < close[i - 1]
6636            && close[i] > close[i - 2]
6637        {
6638            out[i] = -100;
6639        }
6640
6641        body_long_total += real_body(open[i - 2], close[i - 2])
6642            - real_body(open[body_long_trailing_idx], close[body_long_trailing_idx]);
6643        body_short_total += real_body(open[i - 1], close[i - 1])
6644            - real_body(open[body_short_trailing_idx], close[body_short_trailing_idx]);
6645        i += 1;
6646        body_long_trailing_idx += 1;
6647        body_short_trailing_idx += 1;
6648    }
6649
6650    Ok(PatternOutput { values: out })
6651}
6652
6653#[inline]
6654pub fn cdlxsidegap3methods(input: &PatternInput) -> Result<PatternOutput, PatternError> {
6655    let (open, _high, _low, close) = input_ohlc(&input.data)?;
6656
6657    let size = open.len();
6658    let lookback_total = 2;
6659
6660    if size < lookback_total {
6661        return Err(PatternError::NotEnoughData {
6662            len: size,
6663            pattern: input.params.pattern_type,
6664        });
6665    }
6666
6667    #[inline(always)]
6668    fn real_body_gap_up(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6669        curr_open.min(curr_close) > prev_open.max(prev_close)
6670    }
6671
6672    #[inline(always)]
6673    fn real_body_gap_down(curr_open: f64, curr_close: f64, prev_open: f64, prev_close: f64) -> bool {
6674        curr_open.max(curr_close) < prev_open.min(prev_close)
6675    }
6676
6677    let mut out = vec![0i8; size];
6678    let mut i = lookback_total;
6679    while i < size {
6680        if candle_color(open[i - 2], close[i - 2]) == candle_color(open[i - 1], close[i - 1])
6681            && candle_color(open[i - 1], close[i - 1]) == -candle_color(open[i], close[i])
6682            && open[i] < close[i - 1].max(open[i - 1])
6683            && open[i] > close[i - 1].min(open[i - 1])
6684            && close[i] < close[i - 2].max(open[i - 2])
6685            && close[i] > close[i - 2].min(open[i - 2])
6686            && ((candle_color(open[i - 2], close[i - 2]) == 1
6687                && real_body_gap_up(open[i - 1], close[i - 1], open[i - 2], close[i - 2]))
6688                || (candle_color(open[i - 2], close[i - 2]) == -1
6689                    && real_body_gap_down(open[i - 1], close[i - 1], open[i - 2], close[i - 2])))
6690        {
6691            out[i] = (candle_color(open[i - 2], close[i - 2]) as i8) * 100;
6692        }
6693
6694        i += 1;
6695    }
6696
6697    Ok(PatternOutput { values: out })
6698}
6699
6700#[cfg(test)]
6701mod tests {
6702    use super::*;
6703
6704    fn synthetic_candles(len: usize) -> Candles {
6705        let mut timestamp = Vec::with_capacity(len);
6706        let mut open = Vec::with_capacity(len);
6707        let mut high = Vec::with_capacity(len);
6708        let mut low = Vec::with_capacity(len);
6709        let mut close = Vec::with_capacity(len);
6710        let mut volume = Vec::with_capacity(len);
6711
6712        for i in 0..len {
6713            let base = 100.0 + (i as f64 * 0.01) + (((i % 11) as f64) - 5.0) * 0.12;
6714            let o = base + (((i % 5) as f64) - 2.0) * 0.05;
6715            let c = base + (((i % 7) as f64) - 3.0) * 0.04;
6716            let h = o.max(c) + 0.08 + ((i % 3) as f64) * 0.01;
6717            let l = o.min(c) - 0.08 - ((i % 4) as f64) * 0.01;
6718
6719            timestamp.push(i as i64);
6720            open.push(o);
6721            high.push(h);
6722            low.push(l);
6723            close.push(c);
6724            volume.push(1000.0 + i as f64);
6725        }
6726
6727        Candles::new(timestamp, open, high, low, close, volume)
6728    }
6729
6730    fn adversarial_candles(len: usize) -> Candles {
6731        let mut timestamp = Vec::with_capacity(len);
6732        let mut open = Vec::with_capacity(len);
6733        let mut high = Vec::with_capacity(len);
6734        let mut low = Vec::with_capacity(len);
6735        let mut close = Vec::with_capacity(len);
6736        let mut volume = Vec::with_capacity(len);
6737
6738        let mut prev_close: f64 = 100.0;
6739        for i in 0..len {
6740            let band = i % 12;
6741            let (o, c, hi_pad, lo_pad): (f64, f64, f64, f64) = match band {
6742                0 => (prev_close + 5.0, prev_close + 5.0, 0.0, 0.0),
6743                1 => (prev_close + 1.2, prev_close - 0.8, 0.3, 0.7),
6744                2 => (prev_close - 3.5, prev_close + 2.2, 1.4, 1.1),
6745                3 => (prev_close, prev_close, 0.02, 0.02),
6746                4 => (prev_close + 0.01, prev_close - 0.01, 3.5, 3.5),
6747                5 => (prev_close + 8.0, prev_close + 9.5, 0.5, 0.2),
6748                6 => (prev_close - 7.0, prev_close - 8.2, 0.6, 0.3),
6749                7 => (prev_close + 0.5, prev_close + 0.6, 2.2, 0.1),
6750                8 => (prev_close - 0.6, prev_close - 0.5, 0.1, 2.2),
6751                9 => (prev_close + 0.8, prev_close + 0.8, 0.01, 0.01),
6752                10 => (prev_close - 0.8, prev_close - 0.8, 0.01, 0.01),
6753                _ => (prev_close + 1.5, prev_close - 1.5, 0.4, 0.4),
6754            };
6755
6756            let h = o.max(c) + hi_pad;
6757            let l = o.min(c) - lo_pad;
6758
6759            timestamp.push(i as i64);
6760            open.push(o);
6761            high.push(h);
6762            low.push(l);
6763            close.push(c);
6764            volume.push(1000.0 + ((i * 17) % 113) as f64);
6765            prev_close = c;
6766        }
6767
6768        Candles::new(timestamp, open, high, low, close, volume)
6769    }
6770
6771    #[test]
6772    fn pattern_specs_align_with_runners() {
6773        assert_eq!(PATTERN_SPECS.len(), PATTERN_RUNNERS.len());
6774        for (idx, spec) in PATTERN_SPECS.iter().enumerate() {
6775            assert_eq!(spec.row_index, idx);
6776            assert_eq!(spec.id, PATTERN_RUNNERS[idx].id);
6777            assert_eq!(spec.category, PATTERN_RUNNERS[idx].category);
6778            assert_eq!(pattern_type_from_id(spec.id), Some(PATTERN_RUNNERS[idx].pattern_type));
6779        }
6780    }
6781
6782    #[test]
6783    fn shared_primitive_pass_matches_scalar_helpers() {
6784        let candles = adversarial_candles(128);
6785        let prim = build_shared_primitives(
6786            &candles.open,
6787            &candles.high,
6788            &candles.low,
6789            &candles.close,
6790        );
6791
6792        assert_eq!(prim.body.len(), candles.close.len());
6793        assert_eq!(prim.range.len(), candles.close.len());
6794        assert_eq!(prim.upper_shadow.len(), candles.close.len());
6795        assert_eq!(prim.lower_shadow.len(), candles.close.len());
6796        assert_eq!(prim.direction.len(), candles.close.len());
6797        assert_eq!(prim.body_gap_up.len(), candles.close.len());
6798        assert_eq!(prim.body_gap_down.len(), candles.close.len());
6799
6800        for i in 0..candles.close.len() {
6801            let o = candles.open[i];
6802            let h = candles.high[i];
6803            let l = candles.low[i];
6804            let c = candles.close[i];
6805
6806            assert!((prim.body[i] - real_body(o, c)).abs() <= 1e-12);
6807            assert!((prim.range[i] - (h - l)).abs() <= 1e-12);
6808            assert!((prim.upper_shadow[i] - upper_shadow(o, h, c)).abs() <= 1e-12);
6809            assert!((prim.lower_shadow[i] - lower_shadow(o, l, c)).abs() <= 1e-12);
6810            assert_eq!(prim.direction[i], candle_color(o, c) as i8);
6811
6812            if i == 0 {
6813                assert_eq!(prim.body_gap_up[i], 0);
6814                assert_eq!(prim.body_gap_down[i], 0);
6815            } else {
6816                let cur_min = o.min(c);
6817                let cur_max = o.max(c);
6818                let prev_min = candles.open[i - 1].min(candles.close[i - 1]);
6819                let prev_max = candles.open[i - 1].max(candles.close[i - 1]);
6820                assert_eq!(prim.body_gap_up[i], (cur_min > prev_max) as u8);
6821                assert_eq!(prim.body_gap_down[i], (cur_max < prev_min) as u8);
6822            }
6823        }
6824    }
6825
6826    #[test]
6827    fn pattern_dynamic_route_matches_direct_function() {
6828        let candles = synthetic_candles(256);
6829        let input = PatternInput::with_default_candles(&candles, PatternType::CdlDoji);
6830        let direct = cdldoji(&input).unwrap();
6831        let routed = pattern(&input).unwrap();
6832        let routed_kernel = pattern_with_kernel(&input, Kernel::Scalar).unwrap();
6833
6834        assert_eq!(direct.values, routed.values);
6835        assert_eq!(direct.values, routed_kernel.values);
6836    }
6837
6838    #[test]
6839    fn pattern_recognition_output_contract_holds() {
6840        let candles = synthetic_candles(320);
6841        let input = PatternRecognitionInput::with_default_candles(&candles);
6842        let out = pattern_recognition(&input).unwrap();
6843
6844        assert_eq!(out.rows, PATTERN_RUNNERS.len());
6845        assert_eq!(out.cols, candles.close.len());
6846        assert_eq!(out.values_u8.len(), out.rows * out.cols);
6847        assert_eq!(out.pattern_ids.len(), out.rows);
6848        assert!(out.values_u8.iter().all(|v| *v == 0 || *v == 1));
6849
6850        for spec in list_patterns() {
6851            assert_eq!(out.pattern_ids[spec.row_index], spec.id);
6852        }
6853    }
6854
6855    #[test]
6856    fn pattern_recognition_matches_direct_pattern_functions() {
6857        let candles = synthetic_candles(400);
6858        let input = PatternRecognitionInput::with_default_candles(&candles);
6859        let out = pattern_recognition_with_kernel(&input, Kernel::Scalar).unwrap();
6860
6861        for (row, runner) in PATTERN_RUNNERS.iter().enumerate() {
6862            let direct_input = PatternInput::from_candles(
6863                &candles,
6864                PatternParams {
6865                    pattern_type: runner.pattern_type.clone(),
6866                    penetration: 0.0,
6867                },
6868            );
6869            let direct = (runner.run)(&direct_input).unwrap();
6870            let series = extract_pattern_series(&out, runner.id).unwrap();
6871            for (idx, v) in direct.values.iter().enumerate() {
6872                let mapped = if *v == 0 { 0 } else { 1 };
6873                assert_eq!(series[idx], mapped, "pattern={} bar={}", runner.id, idx);
6874                let hit = pattern_hit(&out, runner.id, idx).unwrap();
6875                assert_eq!(hit, mapped != 0, "pattern={} bar={}", runner.id, idx);
6876            }
6877            assert_eq!(out.pattern_ids[row], runner.id);
6878        }
6879    }
6880
6881    #[test]
6882    fn pattern_recognition_matches_direct_on_adversarial_fixture() {
6883        let candles = adversarial_candles(320);
6884        let input = PatternRecognitionInput::with_default_candles(&candles);
6885        let out = pattern_recognition_with_kernel(&input, Kernel::Scalar).unwrap();
6886
6887        for runner in PATTERN_RUNNERS.iter() {
6888            let direct_input = PatternInput::from_candles(
6889                &candles,
6890                PatternParams {
6891                    pattern_type: runner.pattern_type,
6892                    penetration: 0.0,
6893                },
6894            );
6895            let direct = (runner.run)(&direct_input).unwrap();
6896            let series = extract_pattern_series(&out, runner.id).unwrap();
6897            for (idx, v) in direct.values.iter().enumerate() {
6898                let mapped = if *v == 0 { 0 } else { 1 };
6899                assert_eq!(series[idx], mapped, "pattern={} bar={}", runner.id, idx);
6900            }
6901        }
6902    }
6903
6904    #[test]
6905    fn from_slices_matches_from_candles() {
6906        let candles = synthetic_candles(256);
6907        let from_candles =
6908            pattern_recognition(&PatternRecognitionInput::with_default_candles(&candles)).unwrap();
6909        let from_slices = pattern_recognition(&PatternRecognitionInput::with_default_slices(
6910            &candles.open,
6911            &candles.high,
6912            &candles.low,
6913            &candles.close,
6914        ))
6915        .unwrap();
6916
6917        assert_eq!(from_candles.rows, from_slices.rows);
6918        assert_eq!(from_candles.cols, from_slices.cols);
6919        assert_eq!(from_candles.pattern_ids, from_slices.pattern_ids);
6920        assert_eq!(from_candles.values_u8, from_slices.values_u8);
6921    }
6922
6923    #[test]
6924    fn packed_bitmask_export_matches_dense_values() {
6925        let candles = synthetic_candles(191);
6926        let out = pattern_recognition(&PatternRecognitionInput::with_default_candles(&candles))
6927            .unwrap();
6928        let packed = out.to_bitmask_u64();
6929
6930        assert_eq!(packed.rows, out.rows);
6931        assert_eq!(packed.cols, out.cols);
6932        assert_eq!(packed.pattern_ids, out.pattern_ids);
6933        assert_eq!(packed.warmup, out.warmup);
6934        assert_eq!(packed.words_per_row, out.cols.div_ceil(64));
6935        assert_eq!(packed.words_u64.len(), packed.rows * packed.words_per_row);
6936
6937        for row in 0..out.rows {
6938            for col in 0..out.cols {
6939                let dense_idx = row * out.cols + col;
6940                let word = row * packed.words_per_row + (col / 64);
6941                let bit = col % 64;
6942                let packed_hit = ((packed.words_u64[word] >> bit) & 1) != 0;
6943                assert_eq!(packed_hit, out.values_u8[dense_idx] != 0);
6944            }
6945        }
6946    }
6947
6948    #[test]
6949    fn from_slices_rejects_length_mismatch() {
6950        let candles = synthetic_candles(64);
6951        let res = pattern_recognition(&PatternRecognitionInput::from_slices(
6952            &candles.open[..63],
6953            &candles.high,
6954            &candles.low,
6955            &candles.close,
6956            PatternRecognitionParams::default(),
6957        ));
6958
6959        match res {
6960            Err(PatternRecognitionError::DataLengthMismatch {
6961                open,
6962                high,
6963                low,
6964                close,
6965            }) => {
6966                assert_eq!(open, 63);
6967                assert_eq!(high, 64);
6968                assert_eq!(low, 64);
6969                assert_eq!(close, 64);
6970            }
6971            other => panic!("expected DataLengthMismatch, got {:?}", other),
6972        }
6973    }
6974
6975    #[test]
6976    fn direct_pattern_function_accepts_slice_input() {
6977        let candles = synthetic_candles(256);
6978        let params = PatternParams {
6979            pattern_type: PatternType::CdlEngulfing,
6980            penetration: 0.0,
6981        };
6982        let from_candles =
6983            cdlengulfing(&PatternInput::from_candles(&candles, params.clone())).unwrap();
6984        let from_slices = cdlengulfing(&PatternInput::from_slices(
6985            &candles.open,
6986            &candles.high,
6987            &candles.low,
6988            &candles.close,
6989            params,
6990        ))
6991        .unwrap();
6992        assert_eq!(from_candles.values, from_slices.values);
6993    }
6994}