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