Skip to main content

vector_ta/indicators/
pattern_recognition.rs

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