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