use crate::candle_indicators::candle_patterns::CandlePattern;
use crate::candle_indicators::candle_types::{CDLBasic, CDLDoji, CDLMarubozu, CDLSpinningTop};
use crate::candle_indicators::common::{
cdl_gap, BODY_GAP_DOWN, BODY_GAP_UP, NO_GAP, WICK_GAP_DOWN, WICK_GAP_UP,
};
use crate::candle_indicators::pattern_test::EmaState;
use crate::candle_indicators::types::{CandleStick, CandleTypePattern, CandleTypes, ForecastType};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct CandleBits {
pub mandatory: u32, pub lazy_value: u16, pub lazy_computed: u16, }
impl CandleBits {
pub const BASIC_OFFSET: u32 = tulip_rs_shared::BASIC_OFFSET;
pub const DOJI_OFFSET: u32 = tulip_rs_shared::DOJI_OFFSET;
pub const MARUBOZU_OFFSET: u32 = tulip_rs_shared::MARUBOZU_OFFSET;
pub const SPINNING_TOP_OFFSET: u32 = tulip_rs_shared::SPINNING_TOP_OFFSET;
pub const OTHER_BIT: u32 = tulip_rs_shared::OTHER_BIT;
pub const COLOUR_BIT: u32 = tulip_rs_shared::COLOUR_BIT;
pub const FILL_BIT: u32 = tulip_rs_shared::FILL_BIT;
pub const TREND_BIT: u32 = tulip_rs_shared::TREND_BIT;
pub const LINE_HEIGHT_BIT: u32 = tulip_rs_shared::LINE_HEIGHT_BIT;
pub const LOWER_WICK_LT_BODY_BIT: u32 = tulip_rs_shared::LOWER_WICK_LT_BODY_BIT;
pub const UPPER_WICK_LT_BODY_BIT: u32 = tulip_rs_shared::UPPER_WICK_LT_BODY_BIT;
pub const BODY_HEIGHT_BIT: u32 = tulip_rs_shared::BODY_HEIGHT_BIT;
pub const OPEN_ABOVE_PREV_BODY_MID_BIT: u32 = tulip_rs_shared::OPEN_ABOVE_PREV_BODY_MID_BIT;
pub const OPEN_IN_PREV_BODY_BIT: u32 = tulip_rs_shared::OPEN_IN_PREV_BODY_BIT;
pub const CLOSE_ABOVE_PREV_BODY_MID_BIT: u32 = tulip_rs_shared::CLOSE_ABOVE_PREV_BODY_MID_BIT;
pub const CLOSE_IN_PREV_BODY_BIT: u32 = tulip_rs_shared::CLOSE_IN_PREV_BODY_BIT;
pub const HIGH_ABOVE_PREV_BODY_MID_BIT: u32 = tulip_rs_shared::HIGH_ABOVE_PREV_BODY_MID_BIT;
pub const HIGH_IN_PREV_BODY_BIT: u32 = tulip_rs_shared::HIGH_IN_PREV_BODY_BIT;
pub const HIGH_IN_PREV_LINE_BIT: u32 = tulip_rs_shared::HIGH_IN_PREV_LINE_BIT;
pub const LOW_ABOVE_PREV_BODY_MID_BIT: u32 = tulip_rs_shared::LOW_ABOVE_PREV_BODY_MID_BIT;
pub const LOW_IN_PREV_BODY_BIT: u32 = tulip_rs_shared::LOW_IN_PREV_BODY_BIT;
pub const LOW_IN_PREV_LINE_BIT: u32 = tulip_rs_shared::LOW_IN_PREV_LINE_BIT;
pub const I_ENGULF_PREV_BODY_BIT: u32 = tulip_rs_shared::I_ENGULF_PREV_BODY_BIT;
pub const PREV_HIGH_IN_MY_BODY_BIT: u32 = tulip_rs_shared::PREV_HIGH_IN_MY_BODY_BIT;
pub const PREV_LOW_IN_MY_BODY_BIT: u32 = tulip_rs_shared::PREV_LOW_IN_MY_BODY_BIT;
pub const LOWER_WICK_LONG_2X_BIT: u32 = tulip_rs_shared::LOWER_WICK_LONG_2X_BIT;
pub const UPPER_WICK_LONG_2X_BIT: u32 = tulip_rs_shared::UPPER_WICK_LONG_2X_BIT;
pub const BODY_GT_PREV_BODY_BIT: u32 = tulip_rs_shared::BODY_GT_PREV_BODY_BIT;
pub const BASIC_MASK: u32 = tulip_rs_shared::BASIC_MASK;
pub const DOJI_MASK: u32 = tulip_rs_shared::DOJI_MASK;
pub const MARUBOZU_MASK: u32 = tulip_rs_shared::MARUBOZU_MASK;
pub const SPINNING_TOP_MASK: u32 = tulip_rs_shared::SPINNING_TOP_MASK;
pub const CANDLE_TYPE_MASK: u32 = tulip_rs_shared::CANDLE_TYPE_MASK;
pub const COMPULSORY_MASK: u32 = tulip_rs_shared::COMPULSORY_MASK;
pub const LAZY_MASK: u16 = tulip_rs_shared::LAZY_MASK;
pub const SHORT_WHITE_CANDLE: u32 = tulip_rs_shared::SHORT_WHITE_CANDLE;
pub const WHITE_CANDLE: u32 = tulip_rs_shared::WHITE_CANDLE;
pub const LONG_WHITE_CANDLE: u32 = tulip_rs_shared::LONG_WHITE_CANDLE;
pub const SHORT_BLACK_CANDLE: u32 = tulip_rs_shared::SHORT_BLACK_CANDLE;
pub const BLACK_CANDLE: u32 = tulip_rs_shared::BLACK_CANDLE;
pub const LONG_BLACK_CANDLE: u32 = tulip_rs_shared::LONG_BLACK_CANDLE;
pub const DOJI: u32 = tulip_rs_shared::DOJI;
pub const LONG_LEGGED_DOJI: u32 = tulip_rs_shared::LONG_LEGGED_DOJI;
pub const DRAGONFLY_DOJI: u32 = tulip_rs_shared::DRAGONFLY_DOJI;
pub const GRAVESTONE_DOJI: u32 = tulip_rs_shared::GRAVESTONE_DOJI;
pub const FOUR_PRICE_DOJI: u32 = tulip_rs_shared::FOUR_PRICE_DOJI;
pub const WHITE_MARUBOZU: u32 = tulip_rs_shared::WHITE_MARUBOZU;
pub const OPENING_WHITE_MARUBOZU: u32 = tulip_rs_shared::OPENING_WHITE_MARUBOZU;
pub const CLOSING_WHITE_MARUBOZU: u32 = tulip_rs_shared::CLOSING_WHITE_MARUBOZU;
pub const BLACK_MARUBOZU: u32 = tulip_rs_shared::BLACK_MARUBOZU;
pub const OPENING_BLACK_MARUBOZU: u32 = tulip_rs_shared::OPENING_BLACK_MARUBOZU;
pub const CLOSING_BLACK_MARUBOZU: u32 = tulip_rs_shared::CLOSING_BLACK_MARUBOZU;
pub const WHITE_SPINNING_TOP: u32 = tulip_rs_shared::WHITE_SPINNING_TOP;
pub const BLACK_SPINNING_TOP: u32 = tulip_rs_shared::BLACK_SPINNING_TOP;
pub const HIGH_WAVE: u32 = tulip_rs_shared::HIGH_WAVE;
pub const OTHER: u32 = tulip_rs_shared::OTHER;
pub const COLOUR_GREEN: u32 = tulip_rs_shared::COLOUR_GREEN;
pub const COLOUR_RED: u32 = tulip_rs_shared::COLOUR_RED;
pub const FILL_HALLOW: u32 = tulip_rs_shared::FILL_HOLLOW; pub const FILL_FILLED: u32 = tulip_rs_shared::FILL_FILLED;
pub const TREND_UP: u32 = tulip_rs_shared::TREND_UP;
pub const TREND_DOWN: u32 = tulip_rs_shared::TREND_DOWN;
pub const BODY_HEIGHT_LONG: u32 = tulip_rs_shared::BODY_HEIGHT_LONG;
pub const BODY_HEIGHT_SHORT: u32 = tulip_rs_shared::BODY_HEIGHT_SHORT;
pub const LINE_HEIGHT_LONG: u32 = tulip_rs_shared::LINE_HEIGHT_LONG;
pub const LINE_HEIGHT_SHORT: u32 = tulip_rs_shared::LINE_HEIGHT_SHORT;
pub const LOWER_WICK_LT_BODY: u32 = tulip_rs_shared::LOWER_WICK_LT_BODY;
pub const UPPER_WICK_LT_BODY: u32 = tulip_rs_shared::UPPER_WICK_LT_BODY;
pub const OPEN_ABOVE_PREV_BODY_MID: u16 = tulip_rs_shared::OPEN_ABOVE_PREV_BODY_MID;
pub const OPEN_IN_PREV_BODY: u16 = tulip_rs_shared::OPEN_IN_PREV_BODY;
pub const CLOSE_ABOVE_PREV_BODY_MID: u16 = tulip_rs_shared::CLOSE_ABOVE_PREV_BODY_MID;
pub const CLOSE_IN_PREV_BODY: u16 = tulip_rs_shared::CLOSE_IN_PREV_BODY;
pub const HIGH_ABOVE_PREV_BODY_MID: u16 = tulip_rs_shared::HIGH_ABOVE_PREV_BODY_MID;
pub const HIGH_IN_PREV_BODY: u16 = tulip_rs_shared::HIGH_IN_PREV_BODY;
pub const HIGH_IN_PREV_LINE: u16 = tulip_rs_shared::HIGH_IN_PREV_LINE;
pub const LOW_ABOVE_PREV_BODY_MID: u16 = tulip_rs_shared::LOW_ABOVE_PREV_BODY_MID;
pub const LOW_IN_PREV_BODY: u16 = tulip_rs_shared::LOW_IN_PREV_BODY;
pub const LOW_IN_PREV_LINE: u16 = tulip_rs_shared::LOW_IN_PREV_LINE;
pub const I_ENGULF_PREV_BODY: u16 = tulip_rs_shared::I_ENGULF_PREV_BODY;
pub const PREV_HIGH_IN_MY_BODY: u16 = tulip_rs_shared::PREV_HIGH_IN_MY_BODY;
pub const PREV_LOW_IN_MY_BODY: u16 = tulip_rs_shared::PREV_LOW_IN_MY_BODY;
pub const LOWER_WICK_LONG_2X: u16 = tulip_rs_shared::LOWER_WICK_LONG_2X;
pub const UPPER_WICK_LONG_2X: u16 = tulip_rs_shared::UPPER_WICK_LONG_2X;
pub const BODY_GT_PREV_BODY: u16 = tulip_rs_shared::BODY_GT_PREV_BODY;
#[inline(always)]
pub fn new(
candle_type: &CandleTypes,
colour: bool,
fill: bool,
trend: bool,
line_height: bool,
lower_wick_lt_body: bool,
upper_wick_lt_body: bool,
body_height: bool,
) -> Self {
let mut mandatory: u32 = 0;
match candle_type {
CandleTypes::Basic(variant) => {
mandatory |= tulip_rs_shared::encode_basic_variant(variant.discriminant() as u32);
}
CandleTypes::Doji(variant) => {
mandatory |= tulip_rs_shared::encode_doji_variant(variant.discriminant() as u32);
}
CandleTypes::Marubozu(variant) => {
mandatory |=
tulip_rs_shared::encode_marubozu_variant(variant.discriminant() as u32);
}
CandleTypes::SpinningTop(variant) => {
mandatory |=
tulip_rs_shared::encode_spinning_top_variant(variant.discriminant() as u32);
}
CandleTypes::Other => {
mandatory |= 1u32 << Self::OTHER_BIT;
}
}
if colour {
mandatory |= 1u32 << Self::COLOUR_BIT;
}
if fill {
mandatory |= 1u32 << Self::FILL_BIT;
}
if trend {
mandatory |= 1u32 << Self::TREND_BIT;
}
if line_height {
mandatory |= 1u32 << Self::LINE_HEIGHT_BIT;
}
if lower_wick_lt_body {
mandatory |= 1u32 << Self::LOWER_WICK_LT_BODY_BIT;
}
if upper_wick_lt_body {
mandatory |= 1u32 << Self::UPPER_WICK_LT_BODY_BIT;
}
if body_height {
mandatory |= 1u32 << Self::BODY_HEIGHT_BIT;
}
let mut lazy_computed: u16 = 0;
if lower_wick_lt_body {
lazy_computed |= 1u16 << Self::LOWER_WICK_LONG_2X_BIT;
}
if upper_wick_lt_body {
lazy_computed |= 1u16 << Self::UPPER_WICK_LONG_2X_BIT;
}
CandleBits {
mandatory,
lazy_value: 0,
lazy_computed,
}
}
#[inline(always)]
pub fn apply_gap(&mut self, prev: (f64, f64, f64, f64), current: (f64, f64, f64, f64)) -> i8 {
let (prev_open, prev_high, prev_low, prev_close) = prev;
let (cur_open, cur_high, cur_low, cur_close) = current;
let gap = cdl_gap(prev, current);
self.set_gap(gap);
if gap != WICK_GAP_UP && gap != WICK_GAP_DOWN {
self.set_high_in_line(cur_high >= prev_low && cur_high <= prev_high);
self.set_low_in_line(cur_low >= prev_low && cur_low <= prev_high);
if gap == NO_GAP {
let prev_body_bot = prev_open.min(prev_close);
let prev_body_top = prev_open.max(prev_close);
let prev_mid = (prev_body_bot + prev_body_top) / 2.0;
self.set_open_above_mid(cur_open > prev_mid);
self.set_open_in_body(cur_open >= prev_body_bot && cur_open <= prev_body_top);
self.set_close_above_mid(cur_close > prev_mid);
self.set_close_in_body(cur_close >= prev_body_bot && cur_close <= prev_body_top);
}
}
gap
}
#[inline(always)]
pub fn apply_engulfing(&mut self, prev: (f64, f64, f64, f64), current: (f64, f64, f64, f64)) {
let (prev_open, prev_high, prev_low, prev_close) = prev;
let (cur_open, cur_high, cur_low, cur_close) = current;
let prev_body_top = prev_open.max(prev_close);
let prev_body_bot = prev_open.min(prev_close);
let prev_mid = (prev_body_top + prev_body_bot) / 2.0;
let cur_body_top = cur_open.max(cur_close);
let cur_body_bot = cur_open.min(cur_close);
self.set_open_above_mid(cur_open > prev_mid);
self.set_open_in_body(cur_open >= prev_body_bot && cur_open <= prev_body_top);
self.set_close_above_mid(cur_close > prev_mid);
self.set_close_in_body(cur_close >= prev_body_bot && cur_close <= prev_body_top);
self.set_high_above_mid(cur_high > prev_mid);
self.set_high_in_body(cur_high >= prev_body_bot && cur_high <= prev_body_top);
self.set_high_in_line(cur_high >= prev_low && cur_high <= prev_high);
self.set_low_above_mid(cur_low > prev_mid);
self.set_low_in_body(cur_low >= prev_body_bot && cur_low <= prev_body_top);
self.set_low_in_line(cur_low >= prev_low && cur_low <= prev_high);
let body_engulf = cur_body_top >= prev_body_top
&& cur_body_bot <= prev_body_bot
&& (cur_body_top > prev_body_top || cur_body_bot < prev_body_bot);
self.set_engulfs_prev(body_engulf);
if body_engulf {
self.set_body_gt_prev_body(true);
}
self.set_prev_high_in_my_body(prev_high <= cur_body_top && prev_high >= cur_body_bot);
self.set_prev_low_in_my_body(prev_low <= cur_body_top && prev_low >= cur_body_bot);
}
#[inline(always)]
fn set_gap(&mut self, gap: i8) {
match gap {
WICK_GAP_DOWN => {
self.set_high_above_mid(false);
self.set_low_above_mid(false);
self.set_high_in_body(false);
self.set_high_in_line(false);
self.set_low_in_body(false);
self.set_low_in_line(false);
self.set_open_above_mid(false);
self.set_open_in_body(false);
self.set_close_above_mid(false);
self.set_close_in_body(false);
}
BODY_GAP_DOWN => {
self.set_low_above_mid(false);
self.set_low_in_body(false);
self.set_open_above_mid(false);
self.set_open_in_body(false);
self.set_close_above_mid(false);
self.set_close_in_body(false);
}
WICK_GAP_UP => {
self.set_high_above_mid(true);
self.set_low_above_mid(true);
self.set_high_in_body(false);
self.set_high_in_line(false);
self.set_low_in_body(false);
self.set_low_in_line(false);
self.set_open_above_mid(true);
self.set_open_in_body(false);
self.set_close_above_mid(true);
self.set_close_in_body(false);
}
BODY_GAP_UP => {
self.set_high_above_mid(true);
self.set_high_in_body(false);
self.set_open_above_mid(true);
self.set_open_in_body(false);
self.set_close_above_mid(true);
self.set_close_in_body(false);
}
_ => {}
}
}
#[inline(always)]
pub fn set_body_height(&mut self, is_long: bool) {
if is_long {
self.mandatory |= 1u32 << Self::BODY_HEIGHT_BIT;
} else {
self.mandatory &= !(1u32 << Self::BODY_HEIGHT_BIT);
}
}
#[inline(always)]
pub fn set_open_above_mid(&mut self, above_mid: bool) {
if above_mid {
self.lazy_value |= 1u16 << Self::OPEN_ABOVE_PREV_BODY_MID_BIT;
} else {
self.lazy_value &= !(1u16 << Self::OPEN_ABOVE_PREV_BODY_MID_BIT);
}
self.lazy_computed |= 1u16 << Self::OPEN_ABOVE_PREV_BODY_MID_BIT;
}
#[inline(always)]
pub fn set_open_in_body(&mut self, in_body: bool) {
if in_body {
self.lazy_value |= 1u16 << Self::OPEN_IN_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::OPEN_IN_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::OPEN_IN_PREV_BODY_BIT;
}
#[inline(always)]
pub fn set_open_position(&mut self, above_mid: bool, in_body: bool) {
self.set_open_above_mid(above_mid);
self.set_open_in_body(in_body);
}
#[inline(always)]
pub fn set_close_above_mid(&mut self, above_mid: bool) {
if above_mid {
self.lazy_value |= 1u16 << Self::CLOSE_ABOVE_PREV_BODY_MID_BIT;
} else {
self.lazy_value &= !(1u16 << Self::CLOSE_ABOVE_PREV_BODY_MID_BIT);
}
self.lazy_computed |= 1u16 << Self::CLOSE_ABOVE_PREV_BODY_MID_BIT;
}
#[inline(always)]
pub fn set_close_in_body(&mut self, in_body: bool) {
if in_body {
self.lazy_value |= 1u16 << Self::CLOSE_IN_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::CLOSE_IN_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::CLOSE_IN_PREV_BODY_BIT;
}
#[inline(always)]
pub fn set_close_position(&mut self, above_mid: bool, in_body: bool) {
self.set_close_above_mid(above_mid);
self.set_close_in_body(in_body);
}
#[inline(always)]
pub fn set_high_above_mid(&mut self, above_mid: bool) {
if above_mid {
self.lazy_value |= 1u16 << Self::HIGH_ABOVE_PREV_BODY_MID_BIT;
} else {
self.lazy_value &= !(1u16 << Self::HIGH_ABOVE_PREV_BODY_MID_BIT);
}
self.lazy_computed |= 1u16 << Self::HIGH_ABOVE_PREV_BODY_MID_BIT;
}
#[inline(always)]
pub fn set_high_in_body(&mut self, in_body: bool) {
if in_body {
self.lazy_value |= 1u16 << Self::HIGH_IN_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::HIGH_IN_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::HIGH_IN_PREV_BODY_BIT;
}
#[inline(always)]
pub fn set_high_in_line(&mut self, in_line: bool) {
if in_line {
self.lazy_value |= 1u16 << Self::HIGH_IN_PREV_LINE_BIT;
} else {
self.lazy_value &= !(1u16 << Self::HIGH_IN_PREV_LINE_BIT);
}
self.lazy_computed |= 1u16 << Self::HIGH_IN_PREV_LINE_BIT;
}
#[inline(always)]
pub fn set_low_above_mid(&mut self, above_mid: bool) {
if above_mid {
self.lazy_value |= 1u16 << Self::LOW_ABOVE_PREV_BODY_MID_BIT;
} else {
self.lazy_value &= !(1u16 << Self::LOW_ABOVE_PREV_BODY_MID_BIT);
}
self.lazy_computed |= 1u16 << Self::LOW_ABOVE_PREV_BODY_MID_BIT;
}
#[inline(always)]
pub fn set_low_in_body(&mut self, in_body: bool) {
if in_body {
self.lazy_value |= 1u16 << Self::LOW_IN_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::LOW_IN_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::LOW_IN_PREV_BODY_BIT;
}
#[inline(always)]
pub fn set_low_in_line(&mut self, in_line: bool) {
if in_line {
self.lazy_value |= 1u16 << Self::LOW_IN_PREV_LINE_BIT;
} else {
self.lazy_value &= !(1u16 << Self::LOW_IN_PREV_LINE_BIT);
}
self.lazy_computed |= 1u16 << Self::LOW_IN_PREV_LINE_BIT;
}
#[inline(always)]
pub fn set_high_position(&mut self, above_mid: bool, in_body: bool, in_line: bool) {
self.set_high_above_mid(above_mid);
self.set_high_in_body(in_body);
self.set_high_in_line(in_line);
}
#[inline(always)]
pub fn set_low_position(&mut self, above_mid: bool, in_body: bool, in_line: bool) {
self.set_low_above_mid(above_mid);
self.set_low_in_body(in_body);
self.set_low_in_line(in_line);
}
#[inline(always)]
pub fn set_engulfs_prev(&mut self, does_engulf: bool) {
if does_engulf {
self.lazy_value |= 1u16 << Self::I_ENGULF_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::I_ENGULF_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::I_ENGULF_PREV_BODY_BIT;
}
#[inline(always)]
pub fn set_prev_high_in_my_body(&mut self, is_in: bool) {
if is_in {
self.lazy_value |= 1u16 << Self::PREV_HIGH_IN_MY_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::PREV_HIGH_IN_MY_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::PREV_HIGH_IN_MY_BODY_BIT;
}
#[inline(always)]
pub fn set_prev_low_in_my_body(&mut self, is_in: bool) {
if is_in {
self.lazy_value |= 1u16 << Self::PREV_LOW_IN_MY_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::PREV_LOW_IN_MY_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::PREV_LOW_IN_MY_BODY_BIT;
}
#[inline(always)]
pub fn set_lower_wick_2x(&mut self, is_2x: bool) {
if is_2x {
self.lazy_value |= 1u16 << Self::LOWER_WICK_LONG_2X_BIT;
} else {
self.lazy_value &= !(1u16 << Self::LOWER_WICK_LONG_2X_BIT);
}
self.lazy_computed |= 1u16 << Self::LOWER_WICK_LONG_2X_BIT;
}
#[inline(always)]
pub fn set_upper_wick_2x(&mut self, is_2x: bool) {
if is_2x {
self.lazy_value |= 1u16 << Self::UPPER_WICK_LONG_2X_BIT;
} else {
self.lazy_value &= !(1u16 << Self::UPPER_WICK_LONG_2X_BIT);
}
self.lazy_computed |= 1u16 << Self::UPPER_WICK_LONG_2X_BIT;
}
#[inline(always)]
pub fn set_body_gt_prev_body(&mut self, is_gt: bool) {
if is_gt {
self.lazy_value |= 1u16 << Self::BODY_GT_PREV_BODY_BIT;
} else {
self.lazy_value &= !(1u16 << Self::BODY_GT_PREV_BODY_BIT);
}
self.lazy_computed |= 1u16 << Self::BODY_GT_PREV_BODY_BIT;
}
#[inline(always)]
pub fn ensure_body_height(&mut self, open: f64, close: f64, ema_body: f64) {
let body = (open - close).abs();
self.set_body_height(body >= ema_body);
}
#[inline(always)]
pub fn ensure_body_gt_prev_body(
&mut self,
open: f64,
close: f64,
prev_open: f64,
prev_close: f64,
) {
if (self.lazy_computed & (1u16 << Self::BODY_GT_PREV_BODY_BIT)) != 0 {
return;
}
let body = (open - close).abs();
let prev_body = (prev_open - prev_close).abs();
self.set_body_gt_prev_body(body > prev_body);
}
#[inline(always)]
pub fn ensure_open_position(&mut self, open: f64, prev_open: f64, prev_close: f64) {
let open_in_mask = 1u16 << Self::OPEN_IN_PREV_BODY_BIT;
let open_above_mask = 1u16 << Self::OPEN_ABOVE_PREV_BODY_MID_BIT;
let needs_in = (self.lazy_computed & open_in_mask) == 0;
let needs_above = (self.lazy_computed & open_above_mask) == 0;
if !needs_in && !needs_above {
return;
}
let body_top = prev_open.max(prev_close);
let body_bot = prev_open.min(prev_close);
if needs_in {
self.set_open_in_body(open >= body_bot && open <= body_top);
}
if needs_above {
let body_mid = (body_top + body_bot) / 2.0;
self.set_open_above_mid(open > body_mid);
}
}
#[inline(always)]
pub fn ensure_close_position(&mut self, close: f64, prev_open: f64, prev_close: f64) {
let close_in_mask = 1u16 << Self::CLOSE_IN_PREV_BODY_BIT;
let close_above_mask = 1u16 << Self::CLOSE_ABOVE_PREV_BODY_MID_BIT;
let needs_in = (self.lazy_computed & close_in_mask) == 0;
let needs_above = (self.lazy_computed & close_above_mask) == 0;
if !needs_in && !needs_above {
return;
}
let body_top = prev_open.max(prev_close);
let body_bot = prev_open.min(prev_close);
if needs_in {
self.set_close_in_body(close >= body_bot && close <= body_top);
}
if needs_above {
let body_mid = (body_top + body_bot) / 2.0;
self.set_close_above_mid(close > body_mid);
}
}
#[inline(always)]
pub fn ensure_open_close_position(
&mut self,
open: f64,
close: f64,
prev_open: f64,
prev_close: f64,
) {
let open_in_mask = 1u16 << Self::OPEN_IN_PREV_BODY_BIT;
let open_above_mask = 1u16 << Self::OPEN_ABOVE_PREV_BODY_MID_BIT;
let close_in_mask = 1u16 << Self::CLOSE_IN_PREV_BODY_BIT;
let close_above_mask = 1u16 << Self::CLOSE_ABOVE_PREV_BODY_MID_BIT;
let needs_open_in = (self.lazy_computed & open_in_mask) == 0;
let needs_open_above = (self.lazy_computed & open_above_mask) == 0;
let needs_close_in = (self.lazy_computed & close_in_mask) == 0;
let needs_close_above = (self.lazy_computed & close_above_mask) == 0;
if !needs_open_in && !needs_open_above && !needs_close_in && !needs_close_above {
return;
}
let body_top = prev_open.max(prev_close);
let body_bot = prev_open.min(prev_close);
if needs_open_in {
self.set_open_in_body(open >= body_bot && open <= body_top);
}
if needs_close_in {
self.set_close_in_body(close >= body_bot && close <= body_top);
}
if needs_open_above || needs_close_above {
let body_mid = (body_top + body_bot) / 2.0;
if needs_open_above {
self.set_open_above_mid(open > body_mid);
}
if needs_close_above {
self.set_close_above_mid(close > body_mid);
}
}
}
#[inline(always)]
pub fn ensure_wick_2x(&mut self, open: f64, close: f64, high: f64, low: f64) {
let lower_mask = 1u16 << Self::LOWER_WICK_LONG_2X_BIT;
let upper_mask = 1u16 << Self::UPPER_WICK_LONG_2X_BIT;
let needs_lower = (self.lazy_computed & lower_mask) == 0;
let needs_upper = (self.lazy_computed & upper_mask) == 0;
if !needs_lower && !needs_upper {
return;
}
let body = (open - close).abs();
let body_top = open.max(close);
let body_bot = open.min(close);
if needs_lower {
let lower_wick = body_bot - low;
self.set_lower_wick_2x(lower_wick >= 2.0 * body);
}
if needs_upper {
let upper_wick = high - body_top;
self.set_upper_wick_2x(upper_wick >= 2.0 * body);
}
}
#[inline(always)]
pub fn ensure_low_in_prev_line(&mut self, low: f64, prev_low: f64, prev_high: f64) {
if (self.lazy_computed & (1u16 << Self::LOW_IN_PREV_LINE_BIT)) != 0 {
return;
}
self.set_low_in_line(low >= prev_low && low <= prev_high);
}
#[inline(always)]
pub fn ensure_high_in_prev_line(&mut self, high: f64, prev_low: f64, prev_high: f64) {
if (self.lazy_computed & (1u16 << Self::HIGH_IN_PREV_LINE_BIT)) != 0 {
return;
}
self.set_high_in_line(high >= prev_low && high <= prev_high);
}
pub const fn wildcard() -> Self {
CandleBits {
mandatory: 0,
lazy_value: 0,
lazy_computed: 0,
}
}
pub fn get_candle_type(&self) -> CandleTypes {
let doji_bits = ((self.mandatory & Self::DOJI_MASK) >> Self::DOJI_OFFSET) as u8;
if let Some(doji) = CDLDoji::from_bit(doji_bits) {
return CandleTypes::Doji(doji);
}
let basic_bits = ((self.mandatory & Self::BASIC_MASK) >> Self::BASIC_OFFSET) as u8;
if let Some(basic) = CDLBasic::from_bit(basic_bits) {
return CandleTypes::Basic(basic);
}
let marubozu_bits = ((self.mandatory & Self::MARUBOZU_MASK) >> Self::MARUBOZU_OFFSET) as u8;
if let Some(marubozu) = CDLMarubozu::from_bit(marubozu_bits) {
return CandleTypes::Marubozu(marubozu);
}
let spinning_top_bits =
((self.mandatory & Self::SPINNING_TOP_MASK) >> Self::SPINNING_TOP_OFFSET) as u8;
if let Some(spinning_top) = CDLSpinningTop::from_bit(spinning_top_bits) {
return CandleTypes::SpinningTop(spinning_top);
}
if self.mandatory & (1u32 << Self::OTHER_BIT) != 0 {
return CandleTypes::Other;
}
CandleTypes::Other
}
#[inline(always)]
pub fn get_colour(&self) -> bool {
use crate::candle_indicators::common::{GREEN, RED};
if self.mandatory & Self::COLOUR_GREEN != 0 {
GREEN
} else {
RED
}
}
#[inline(always)]
pub fn get_fill(&self) -> bool {
use crate::candle_indicators::common::{FILL, HALLOW};
if self.mandatory & Self::FILL_HALLOW != 0 {
HALLOW
} else {
FILL
}
}
#[inline(always)]
pub fn get_trend(&self) -> bool {
use crate::candle_indicators::common::{DOWN_TREND, UP_TREND};
if self.mandatory & Self::TREND_UP != 0 {
UP_TREND
} else {
DOWN_TREND
}
}
#[inline(always)]
pub fn get_body_height(&self) -> bool {
use crate::candle_indicators::common::{LONG, SHORT};
if self.mandatory & Self::BODY_HEIGHT_LONG != 0 {
LONG
} else {
SHORT
}
}
#[inline(always)]
pub fn get_line_height(&self) -> bool {
use crate::candle_indicators::common::{LONG, SHORT};
if self.mandatory & Self::LINE_HEIGHT_LONG != 0 {
LONG
} else {
SHORT
}
}
#[inline(always)]
pub fn get_lower_wick_lt_body(&self) -> bool {
self.mandatory & Self::LOWER_WICK_LT_BODY != 0
}
#[inline(always)]
pub fn get_upper_wick_lt_body(&self) -> bool {
self.mandatory & Self::UPPER_WICK_LT_BODY != 0
}
#[inline(always)]
pub fn matches_compulsory_only(&self, pattern: &PatternMask) -> bool {
let candle_type_part = pattern.mandatory_mask & CandleBits::CANDLE_TYPE_MASK;
let candle_match = if candle_type_part != 0 {
(self.mandatory & candle_type_part & pattern.mandatory_value) != 0
} else {
true };
let other_part = pattern.mandatory_mask & !CandleBits::CANDLE_TYPE_MASK;
let mandatory_match =
(self.mandatory & other_part) == (pattern.mandatory_value & other_part);
candle_match && mandatory_match
}
pub fn matches(&self, pattern: &PatternMask) -> bool {
let candle_type_part = pattern.mandatory_mask & CandleBits::CANDLE_TYPE_MASK;
let candle_match = if candle_type_part != 0 {
(self.mandatory & candle_type_part & pattern.mandatory_value) != 0
} else {
true };
let other_mandatory_part = pattern.mandatory_mask & !CandleBits::CANDLE_TYPE_MASK;
let mandatory_match = (self.mandatory & other_mandatory_part)
== (pattern.mandatory_value & other_mandatory_part);
let lazy_part = pattern.lazy_mask & self.lazy_computed;
let lazy_match = (self.lazy_value & lazy_part) == (pattern.lazy_value & lazy_part);
let result = candle_match && mandatory_match && lazy_match;
result
}
#[inline(always)]
pub fn candle_group(&self) -> Option<usize> {
let v = self.mandatory;
if (v & Self::BASIC_MASK) != 0 {
Some(0)
} else if (v & Self::DOJI_MASK) != 0 {
Some(1)
} else if (v & Self::MARUBOZU_MASK) != 0 {
Some(2)
} else if (v & Self::SPINNING_TOP_MASK) != 0 {
Some(3)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PatternMask {
pub mandatory_mask: u32, pub mandatory_value: u32, pub lazy_mask: u16, pub lazy_value: u16, pub has_engulf: bool, pub has_gap: bool, }
impl PatternMask {
pub const fn new(mandatory_mask: u32, mandatory_value: u32) -> Self {
PatternMask {
mandatory_mask,
mandatory_value,
lazy_mask: 0,
lazy_value: 0,
has_engulf: false,
has_gap: false,
}
}
pub const fn wildcard() -> Self {
PatternMask {
mandatory_mask: 0,
mandatory_value: 0,
lazy_mask: 0,
lazy_value: 0,
has_engulf: false,
has_gap: false,
}
}
pub const fn with_colour(mut self, colour: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::COLOUR_BIT;
if colour {
self.mandatory_value |= 1u32 << CandleBits::COLOUR_BIT;
}
self
}
pub const fn with_fill(mut self, fill: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::FILL_BIT;
if fill {
self.mandatory_value |= 1u32 << CandleBits::FILL_BIT;
}
self
}
pub const fn with_candle_type(mut self, candle_type_pattern: CandleTypePattern) -> Self {
match candle_type_pattern {
CandleTypePattern::Basic(variant_mask) => {
self.mandatory_mask |= CandleBits::BASIC_MASK;
self.mandatory_value |= (variant_mask as u32) << CandleBits::BASIC_OFFSET;
}
CandleTypePattern::Doji(variant_mask) => {
self.mandatory_mask |= CandleBits::DOJI_MASK;
self.mandatory_value |= (variant_mask as u32) << CandleBits::DOJI_OFFSET;
}
CandleTypePattern::Marubozu(variant_mask) => {
self.mandatory_mask |= CandleBits::MARUBOZU_MASK;
self.mandatory_value |= (variant_mask as u32) << CandleBits::MARUBOZU_OFFSET;
}
CandleTypePattern::SpinningTop(variant_mask) => {
self.mandatory_mask |= CandleBits::SPINNING_TOP_MASK;
self.mandatory_value |= (variant_mask as u32) << CandleBits::SPINNING_TOP_OFFSET;
}
CandleTypePattern::Any => {
}
}
self
}
pub const fn with_negated_candle_type(
mut self,
candle_type_pattern: CandleTypePattern,
) -> Self {
const ALL_BASIC_VARIANTS: u8 = 0x3F; const ALL_DOJI_VARIANTS: u8 = 0x1F; const ALL_MARUBOZU_VARIANTS: u8 = 0x3F; const ALL_SPINNING_TOP_VARIANTS: u8 = 0x07;
match candle_type_pattern {
CandleTypePattern::Basic(variant_mask) => {
if variant_mask == ALL_BASIC_VARIANTS {
self.mandatory_mask |= CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
} else {
let inverted_mask = ALL_BASIC_VARIANTS & !variant_mask;
self.mandatory_mask |= CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_mask |= CandleBits::BASIC_MASK;
self.mandatory_value |= (inverted_mask as u32) << CandleBits::BASIC_OFFSET;
}
}
CandleTypePattern::Doji(variant_mask) => {
if variant_mask == ALL_DOJI_VARIANTS {
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
} else {
let inverted_mask = ALL_DOJI_VARIANTS & !variant_mask;
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_mask |= CandleBits::DOJI_MASK;
self.mandatory_value |= (inverted_mask as u32) << CandleBits::DOJI_OFFSET;
}
}
CandleTypePattern::Marubozu(variant_mask) => {
if variant_mask == ALL_MARUBOZU_VARIANTS {
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
} else {
let inverted_mask = ALL_MARUBOZU_VARIANTS & !variant_mask;
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_mask |= CandleBits::MARUBOZU_MASK;
self.mandatory_value |= (inverted_mask as u32) << CandleBits::MARUBOZU_OFFSET;
}
}
CandleTypePattern::SpinningTop(variant_mask) => {
if variant_mask == ALL_SPINNING_TOP_VARIANTS {
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| (1u32 << CandleBits::OTHER_BIT);
} else {
let inverted_mask = ALL_SPINNING_TOP_VARIANTS & !variant_mask;
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_value |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| (1u32 << CandleBits::OTHER_BIT);
self.mandatory_mask |= CandleBits::SPINNING_TOP_MASK;
self.mandatory_value |=
(inverted_mask as u32) << CandleBits::SPINNING_TOP_OFFSET;
}
}
CandleTypePattern::Any => {
self.mandatory_mask |= CandleBits::BASIC_MASK
| CandleBits::DOJI_MASK
| CandleBits::MARUBOZU_MASK
| CandleBits::SPINNING_TOP_MASK
| (1u32 << CandleBits::OTHER_BIT);
}
}
self
}
pub const fn with_multiple_negated_candle_types(
mut self,
patterns: &[CandleTypePattern], ) -> Self {
const ALL_BASIC_VARIANTS: u8 = 0x3F; const ALL_DOJI_VARIANTS: u8 = 0x1F; const ALL_MARUBOZU_VARIANTS: u8 = 0x3F; const ALL_SPINNING_TOP_VARIANTS: u8 = 0x07;
let mut allowed_basic: u8 = ALL_BASIC_VARIANTS;
let mut allowed_doji: u8 = ALL_DOJI_VARIANTS;
let mut allowed_marubozu: u8 = ALL_MARUBOZU_VARIANTS;
let mut allowed_spinning: u8 = ALL_SPINNING_TOP_VARIANTS;
let mut allowed_other: bool = true;
let mut i = 0;
while i < patterns.len() {
match patterns[i] {
CandleTypePattern::Basic(mask) => {
allowed_basic &= !mask;
}
CandleTypePattern::Doji(mask) => {
allowed_doji &= !mask;
}
CandleTypePattern::Marubozu(mask) => {
allowed_marubozu &= !mask;
}
CandleTypePattern::SpinningTop(mask) => {
allowed_spinning &= !mask;
}
CandleTypePattern::Any => {
allowed_basic = 0;
allowed_doji = 0;
allowed_marubozu = 0;
allowed_spinning = 0;
allowed_other = false;
}
}
i += 1;
}
if allowed_basic != 0 {
self.mandatory_mask |= CandleBits::BASIC_MASK;
self.mandatory_value |= (allowed_basic as u32) << CandleBits::BASIC_OFFSET;
}
if allowed_doji != 0 {
self.mandatory_mask |= CandleBits::DOJI_MASK;
self.mandatory_value |= (allowed_doji as u32) << CandleBits::DOJI_OFFSET;
}
if allowed_marubozu != 0 {
self.mandatory_mask |= CandleBits::MARUBOZU_MASK;
self.mandatory_value |= (allowed_marubozu as u32) << CandleBits::MARUBOZU_OFFSET;
}
if allowed_spinning != 0 {
self.mandatory_mask |= CandleBits::SPINNING_TOP_MASK;
self.mandatory_value |= (allowed_spinning as u32) << CandleBits::SPINNING_TOP_OFFSET;
}
if allowed_other {
self.mandatory_mask |= 1u32 << CandleBits::OTHER_BIT;
self.mandatory_value |= 1u32 << CandleBits::OTHER_BIT;
}
self
}
pub const fn with_trend(mut self, trend: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::TREND_BIT;
if trend {
self.mandatory_value |= 1u32 << CandleBits::TREND_BIT;
}
self
}
pub const fn with_body_height(mut self, is_long: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::BODY_HEIGHT_BIT;
if is_long {
self.mandatory_value |= 1u32 << CandleBits::BODY_HEIGHT_BIT;
}
self
}
pub const fn with_line_height(mut self, is_long: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::LINE_HEIGHT_BIT;
if is_long {
self.mandatory_value |= 1u32 << CandleBits::LINE_HEIGHT_BIT;
}
self
}
pub const fn with_lower_wick_lt_body(mut self, is_lt: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::LOWER_WICK_LT_BODY_BIT;
if is_lt {
self.mandatory_value |= 1u32 << CandleBits::LOWER_WICK_LT_BODY_BIT;
}
self
}
pub const fn with_upper_wick_lt_body(mut self, is_lt: bool) -> Self {
self.mandatory_mask |= 1u32 << CandleBits::UPPER_WICK_LT_BODY_BIT;
if is_lt {
self.mandatory_value |= 1u32 << CandleBits::UPPER_WICK_LT_BODY_BIT;
}
self
}
pub const fn with_body_gap(mut self, gap: i8) -> Self {
self.has_gap = true;
self.lazy_mask |= (1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT);
self.lazy_value &= !((1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT));
if gap > 0 {
self.lazy_value |= (1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT);
} else {
self.lazy_value &= !((1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT));
}
self
}
pub const fn with_wick_gap(mut self, gap: i8) -> Self {
self.has_gap = true;
self.lazy_mask |= (1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT)
| (1u16 << CandleBits::LOW_IN_PREV_LINE_BIT)
| (1u16 << CandleBits::HIGH_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::LOW_ABOVE_PREV_BODY_MID_BIT);
self.lazy_value &= !((1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT)
| (1u16 << CandleBits::LOW_IN_PREV_LINE_BIT));
if gap > 0 {
self.lazy_value |= (1u16 << CandleBits::HIGH_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::LOW_ABOVE_PREV_BODY_MID_BIT);
} else {
self.lazy_value &= !((1u16 << CandleBits::HIGH_ABOVE_PREV_BODY_MID_BIT)
| (1u16 << CandleBits::LOW_ABOVE_PREV_BODY_MID_BIT));
}
self
}
pub const fn with_open_above_prev_mid(mut self, is_above: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT;
if is_above {
self.lazy_value |= 1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT;
}
self
}
pub const fn with_open_in_prev_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT;
}
self
}
pub const fn with_close_above_prev_mid(mut self, is_above: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT;
if is_above {
self.lazy_value |= 1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT;
}
self
}
pub const fn with_close_in_prev_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT;
}
self
}
pub const fn with_high_above_prev_mid(mut self, is_above: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::HIGH_ABOVE_PREV_BODY_MID_BIT;
if is_above {
self.lazy_value |= 1u16 << CandleBits::HIGH_ABOVE_PREV_BODY_MID_BIT;
}
self
}
pub const fn with_high_in_prev_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::HIGH_IN_PREV_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::HIGH_IN_PREV_BODY_BIT;
}
self
}
pub const fn with_high_in_prev_line(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT;
}
self
}
pub const fn with_low_above_prev_mid(mut self, is_above: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::LOW_ABOVE_PREV_BODY_MID_BIT;
if is_above {
self.lazy_value |= 1u16 << CandleBits::LOW_ABOVE_PREV_BODY_MID_BIT;
}
self
}
pub const fn with_low_in_prev_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::LOW_IN_PREV_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::LOW_IN_PREV_BODY_BIT;
}
self
}
pub const fn with_low_in_prev_line(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::LOW_IN_PREV_LINE_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::LOW_IN_PREV_LINE_BIT;
}
self
}
pub const fn with_engulfs_prev(mut self, does_engulf: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::I_ENGULF_PREV_BODY_BIT;
if does_engulf {
self.lazy_value |= 1u16 << CandleBits::I_ENGULF_PREV_BODY_BIT;
}
self
}
pub const fn with_prev_in_my_body(mut self, engulfs: bool) -> Self {
self.lazy_mask |= (1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT)
| (1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT);
if engulfs {
self.lazy_value |= (1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT)
| (1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT);
}
self
}
pub const fn with_prev_high_in_my_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT;
}
self
}
pub const fn with_prev_low_in_my_body(mut self, is_in: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT;
if is_in {
self.lazy_value |= 1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT;
}
self
}
pub const fn with_engulf_prev(mut self, kind: i8) -> Self {
self.has_engulf = true;
if kind >= 2 {
self.lazy_mask |= (1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT)
| (1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT);
self.lazy_value |= (1u16 << CandleBits::PREV_HIGH_IN_MY_BODY_BIT)
| (1u16 << CandleBits::PREV_LOW_IN_MY_BODY_BIT);
} else {
self.lazy_mask |= 1u16 << CandleBits::I_ENGULF_PREV_BODY_BIT;
self.lazy_value |= 1u16 << CandleBits::I_ENGULF_PREV_BODY_BIT;
}
self
}
pub const fn with_inside_prev(mut self, kind: i8) -> Self {
self.has_engulf = true;
if kind >= 2 {
self.lazy_mask |= (1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT)
| (1u16 << CandleBits::LOW_IN_PREV_LINE_BIT);
self.lazy_value |= (1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT)
| (1u16 << CandleBits::LOW_IN_PREV_LINE_BIT);
} else {
self.lazy_mask |= (1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT);
self.lazy_value |= (1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT);
}
self
}
pub const fn with_lower_wick_2x(mut self, is_2x: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::LOWER_WICK_LONG_2X_BIT;
if is_2x {
self.lazy_value |= 1u16 << CandleBits::LOWER_WICK_LONG_2X_BIT;
}
self
}
pub const fn with_upper_wick_2x(mut self, is_2x: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::UPPER_WICK_LONG_2X_BIT;
if is_2x {
self.lazy_value |= 1u16 << CandleBits::UPPER_WICK_LONG_2X_BIT;
}
self
}
pub const fn with_body_gt_prev_body(mut self, is_gt: bool) -> Self {
self.lazy_mask |= 1u16 << CandleBits::BODY_GT_PREV_BODY_BIT;
if is_gt {
self.lazy_value |= 1u16 << CandleBits::BODY_GT_PREV_BODY_BIT;
}
self
}
pub const fn with_has_engulf(mut self) -> Self {
self.has_engulf = true;
self
}
pub const fn with_has_gap(mut self) -> Self {
self.has_gap = true;
self
}
#[inline(always)]
pub fn matches(&self, bar: &CandleBits) -> bool {
bar.matches(self)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PatternDefinition<const N: usize> {
pub pattern: CandlePattern,
pub forecast: ForecastType,
pub bars: [PatternMask; N], pub check_prev_bar: bool, pub lazy_bits_mask: u16, }
impl<const N: usize> PatternDefinition<N> {
pub const fn new(
pattern: CandlePattern,
forecast: ForecastType,
bars: [PatternMask; N],
check_prev_bar: bool,
lazy_bits_mask: u16,
) -> Self {
PatternDefinition {
pattern,
forecast,
bars,
check_prev_bar,
lazy_bits_mask,
}
}
pub const fn bar_count(&self) -> usize {
N
}
#[inline(always)]
pub fn matches_bars_compulsory_only(&self, bars: &[CandleBits]) -> bool {
if bars.len() < self.bars.len() {
return false;
}
let start = if self.check_prev_bar { 0 } else { 1 };
for i in (start..self.bars.len()).rev() {
if !bars[i].matches_compulsory_only(&self.bars[i]) {
return false;
}
}
true
}
pub fn matches_bars(&self, bars: &[CandleBits]) -> bool {
if bars.len() < self.bars.len() {
return false;
}
let start = if self.check_prev_bar { 0 } else { 1 };
for i in (start..self.bars.len()).rev() {
if !self.bars[i].matches(&bars[i]) {
return false;
}
}
true
}
}
#[inline(always)]
pub fn ensure_lazy_bits(
masks: &[PatternMask],
bars: &mut [CandleBits],
ohlc: (&[f64], &[f64], &[f64], &[f64]),
state: &EmaState,
) {
let (open, high, low, close) = ohlc;
let _ = state; for i in 0..bars.len() {
let missing = masks[i].lazy_mask & !bars[i].lazy_computed;
if missing == 0 {
continue;
}
if masks[i].has_engulf && i > 0 {
if bars[i].lazy_computed & (1u16 << CandleBits::I_ENGULF_PREV_BODY_BIT) == 0 {
bars[i].apply_engulfing(
(open[i - 1], high[i - 1], low[i - 1], close[i - 1]),
(open[i], high[i], low[i], close[i]),
);
continue; }
}
if masks[i].has_gap && i > 0 {
if bars[i].lazy_computed & (1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT) == 0 {
bars[i].apply_gap(
(open[i - 1], high[i - 1], low[i - 1], close[i - 1]),
(open[i], high[i], low[i], close[i]),
);
}
}
let missing = masks[i].lazy_mask & !bars[i].lazy_computed;
if missing == 0 {
continue;
}
let wick_2x_mask = (1u16 << CandleBits::LOWER_WICK_LONG_2X_BIT)
| (1u16 << CandleBits::UPPER_WICK_LONG_2X_BIT);
if missing & wick_2x_mask != 0 {
bars[i].ensure_wick_2x(open[i], close[i], high[i], low[i]);
}
if i > 0 && missing & (1u16 << CandleBits::BODY_GT_PREV_BODY_BIT) != 0 {
bars[i].ensure_body_gt_prev_body(open[i], close[i], open[i - 1], close[i - 1]);
}
if i > 0 {
let needs_open = missing
& ((1u16 << CandleBits::OPEN_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::OPEN_ABOVE_PREV_BODY_MID_BIT))
!= 0;
let needs_close = missing
& ((1u16 << CandleBits::CLOSE_IN_PREV_BODY_BIT)
| (1u16 << CandleBits::CLOSE_ABOVE_PREV_BODY_MID_BIT))
!= 0;
match (needs_open, needs_close) {
(true, true) => {
bars[i].ensure_open_close_position(open[i], close[i], open[i - 1], close[i - 1])
}
(true, false) => bars[i].ensure_open_position(open[i], open[i - 1], close[i - 1]),
(false, true) => bars[i].ensure_close_position(close[i], open[i - 1], close[i - 1]),
(false, false) => {}
}
if missing & (1u16 << CandleBits::LOW_IN_PREV_LINE_BIT) != 0 {
bars[i].ensure_low_in_prev_line(low[i], low[i - 1], high[i - 1]);
}
if missing & (1u16 << CandleBits::HIGH_IN_PREV_LINE_BIT) != 0 {
bars[i].ensure_high_in_prev_line(high[i], low[i - 1], high[i - 1]);
}
}
}
}
macro_rules! check_bar_group {
(
patterns = $patterns:expr,
min_bars = $min_bars:expr,
window_size = $window_size:expr,
bars = $bars:expr,
inputs = $inputs:expr,
i = $i:expr,
state = $state:expr,
matched = $matched:expr
) => {{
if $bars.len() >= $min_bars {
let window_start = $bars.len() - $window_size;
let window = &mut $bars[window_start..];
for pattern_def in $patterns {
if pattern_def.lazy_bits_mask != 0 {
if !pattern_def.matches_bars_compulsory_only(window) {
continue;
}
let ohlc_start = $i + 1 - $window_size;
let sliced_ohlc = (
&$inputs.0[ohlc_start..=$i],
&$inputs.1[ohlc_start..=$i],
&$inputs.2[ohlc_start..=$i],
&$inputs.3[ohlc_start..=$i],
);
ensure_lazy_bits(&pattern_def.bars, window, sliced_ohlc, $state);
}
if !pattern_def.matches_bars(window) {
continue;
}
if pattern_def.pattern.calc($inputs, $i, $state, window) {
$matched
.get_or_insert_with(|| Vec::with_capacity(1))
.push(pattern_def.pattern);
}
}
}
}};
}
#[derive(Debug)]
pub struct PatternDefinitionRegister<
const N1: usize,
const N2: usize,
const N3: usize,
const N4: usize,
const N5: usize,
> {
pub one_bar: [PatternDefinition<2>; N1], pub two_bar: [PatternDefinition<3>; N2], pub three_bar: [PatternDefinition<4>; N3], pub four_bar: [PatternDefinition<5>; N4], pub five_bar: [PatternDefinition<6>; N5], }
impl<const N1: usize, const N2: usize, const N3: usize, const N4: usize, const N5: usize>
PatternDefinitionRegister<N1, N2, N3, N4, N5>
{
pub const fn new(
one_bar: [PatternDefinition<2>; N1], two_bar: [PatternDefinition<3>; N2], three_bar: [PatternDefinition<4>; N3], four_bar: [PatternDefinition<5>; N4], five_bar: [PatternDefinition<6>; N5], ) -> Self {
PatternDefinitionRegister {
one_bar,
two_bar,
three_bar,
four_bar,
five_bar,
}
}
pub fn get_validated_patterns_with_group_trend_dispatch(
&self,
bars: &mut [CandleBits],
inputs: (&[f64], &[f64], &[f64], &[f64]),
i: usize,
state: &EmaState,
gd: &GroupTrendDispatch,
) -> Option<Vec<CandlePattern>> {
let mut matched_patterns: Option<Vec<CandlePattern>> = None;
let current_bar = bars.last()?;
let group = current_bar.candle_group()?;
let colour = (current_bar.mandatory & CandleBits::COLOUR_GREEN != 0) as usize;
let fill = (current_bar.mandatory & CandleBits::FILL_HALLOW != 0) as usize;
let cf = colour * 2 + fill;
if bars.len() >= 2 {
let is_uptrend = (bars[bars.len() - 2].mandatory & CandleBits::TREND_UP) != 0;
let key = group * 8 + (is_uptrend as usize) * 4 + cf;
let (start, end) = gd.one_bar[key];
check_bar_group!(
patterns = &self.one_bar[start..end],
min_bars = 2,
window_size = 2,
bars = bars,
inputs = inputs,
i = i,
state = state,
matched = matched_patterns
);
}
if bars.len() >= 3 {
let is_uptrend = (bars[bars.len() - 3].mandatory & CandleBits::TREND_UP) != 0;
let key = group * 8 + (is_uptrend as usize) * 4 + cf;
let (start, end) = gd.two_bar[key];
check_bar_group!(
patterns = &self.two_bar[start..end],
min_bars = 3,
window_size = 3,
bars = bars,
inputs = inputs,
i = i,
state = state,
matched = matched_patterns
);
}
if bars.len() >= 4 {
let is_uptrend = (bars[bars.len() - 4].mandatory & CandleBits::TREND_UP) != 0;
let key = group * 8 + (is_uptrend as usize) * 4 + cf;
let (start, end) = gd.three_bar[key];
check_bar_group!(
patterns = &self.three_bar[start..end],
min_bars = 4,
window_size = 4,
bars = bars,
inputs = inputs,
i = i,
state = state,
matched = matched_patterns
);
}
if bars.len() >= 5 {
let is_uptrend = (bars[bars.len() - 5].mandatory & CandleBits::TREND_UP) != 0;
let key = group * 8 + (is_uptrend as usize) * 4 + cf;
let (start, end) = gd.four_bar[key];
check_bar_group!(
patterns = &self.four_bar[start..end],
min_bars = 5,
window_size = 5,
bars = bars,
inputs = inputs,
i = i,
state = state,
matched = matched_patterns
);
}
if bars.len() >= 6 {
let is_uptrend = (bars[bars.len() - 6].mandatory & CandleBits::TREND_UP) != 0;
let key = group * 8 + (is_uptrend as usize) * 4 + cf;
let (start, end) = gd.five_bar[key];
check_bar_group!(
patterns = &self.five_bar[start..end],
min_bars = 6,
window_size = 6,
bars = bars,
inputs = inputs,
i = i,
state = state,
matched = matched_patterns
);
}
matched_patterns
}
}
#[derive(Debug, Clone)]
pub struct GroupTrendDispatch {
pub one_bar: [(usize, usize); 32],
pub two_bar: [(usize, usize); 32],
pub three_bar: [(usize, usize); 32],
pub four_bar: [(usize, usize); 32],
pub five_bar: [(usize, usize); 32],
}
impl GroupTrendDispatch {
pub const fn new(
one_bar: [(usize, usize); 32],
two_bar: [(usize, usize); 32],
three_bar: [(usize, usize); 32],
four_bar: [(usize, usize); 32],
five_bar: [(usize, usize); 32],
) -> Self {
GroupTrendDispatch {
one_bar,
two_bar,
three_bar,
four_bar,
five_bar,
}
}
}
pub struct PatternRegister<
const N1: usize,
const N2: usize,
const N3: usize,
const N4: usize,
const N5: usize,
> {
pub pattern_definitions: PatternDefinitionRegister<N1, N2, N3, N4, N5>,
pub forecast_dispatches: [GroupTrendDispatch; 6],
pub global_dispatch: GroupTrendDispatch,
}
impl<const N1: usize, const N2: usize, const N3: usize, const N4: usize, const N5: usize>
PatternRegister<N1, N2, N3, N4, N5>
{
pub const fn new(
pattern_definitions: PatternDefinitionRegister<N1, N2, N3, N4, N5>,
forecast_dispatches: [GroupTrendDispatch; 6],
global_dispatch: GroupTrendDispatch,
) -> Self {
PatternRegister {
pattern_definitions,
forecast_dispatches,
global_dispatch,
}
}
pub fn get_validated_patterns(
&self,
bars: &mut [CandleBits],
inputs: (&[f64], &[f64], &[f64], &[f64]),
i: usize,
state: &EmaState,
forecast: Option<ForecastType>,
) -> Option<Vec<CandlePattern>> {
let gd = match forecast {
Some(fc) => {
let idx = fc as usize;
if idx < self.forecast_dispatches.len() {
&self.forecast_dispatches[idx]
} else {
return None;
}
}
None => &self.global_dispatch,
};
self.pattern_definitions
.get_validated_patterns_with_group_trend_dispatch(bars, inputs, i, state, gd)
}
}