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