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