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