Skip to main content

vector_ta/indicators/
pattern_recognition.rs

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