use super::bar::Bar;
pub trait CandlePatterns {
fn is_hammer(&self) -> bool;
fn is_inverted_hammer(&self) -> bool;
fn is_shooting_star(&self) -> bool;
fn is_spinning_top(&self) -> bool;
fn is_marubozu(&self) -> bool;
fn is_bullish_engulfing(&self, prev: &Bar) -> bool;
fn is_bearish_engulfing(&self, prev: &Bar) -> bool;
fn is_morning_star(&self, prev1: &Bar, prev2: &Bar) -> bool;
fn is_evening_star(&self, prev1: &Bar, prev2: &Bar) -> bool;
}
impl CandlePatterns for Bar {
fn is_hammer(&self) -> bool {
let body_pct = self.body_percentage();
let lower = self.lower_wick();
let upper = self.upper_wick();
let body = self.body_height();
body_pct < 0.3 && lower > 2.0 * body && upper < body * 0.5
}
fn is_inverted_hammer(&self) -> bool {
let body_pct = self.body_percentage();
let lower = self.lower_wick();
let upper = self.upper_wick();
let body = self.body_height();
body_pct < 0.3 && upper > 2.0 * body && lower < body * 0.5
}
fn is_shooting_star(&self) -> bool {
let body_pct = self.body_percentage();
let lower = self.lower_wick();
let upper = self.upper_wick();
let body = self.body_height();
body_pct < 0.3 && upper > 2.0 * body && lower < body * 0.3
}
fn is_spinning_top(&self) -> bool {
let body_pct = self.body_percentage();
let lower = self.lower_wick();
let upper = self.upper_wick();
if body_pct >= 0.3 {
return false;
}
let wick_ratio = if lower > upper {
upper / lower
} else if upper > 0.0 {
lower / upper
} else {
return false;
};
wick_ratio > 0.5
}
fn is_marubozu(&self) -> bool {
self.body_percentage() > 0.85
}
fn is_bullish_engulfing(&self, prev: &Bar) -> bool {
if !prev.is_bearish() || !self.is_bullish() {
return false;
}
let prev_body_top = prev.open;
let prev_body_bottom = prev.close;
let curr_body_top = self.close;
let curr_body_bottom = self.open;
curr_body_top > prev_body_top && curr_body_bottom < prev_body_bottom
}
fn is_bearish_engulfing(&self, prev: &Bar) -> bool {
if !prev.is_bullish() || !self.is_bearish() {
return false;
}
let prev_body_top = prev.close;
let prev_body_bottom = prev.open;
let curr_body_top = self.open;
let curr_body_bottom = self.close;
curr_body_top > prev_body_top && curr_body_bottom < prev_body_bottom
}
fn is_morning_star(&self, prev1: &Bar, prev2: &Bar) -> bool {
if !prev2.is_bearish() || prev2.body_percentage() < 0.5 {
return false;
}
if prev1.body_percentage() > 0.3 {
return false;
}
if !self.is_bullish() || self.body_percentage() < 0.5 {
return false;
}
self.close > (prev2.open + prev2.close) / 2.0
}
fn is_evening_star(&self, prev1: &Bar, prev2: &Bar) -> bool {
if !prev2.is_bullish() || prev2.body_percentage() < 0.5 {
return false;
}
if prev1.body_percentage() > 0.3 {
return false;
}
if !self.is_bearish() || self.body_percentage() < 0.5 {
return false;
}
self.close < (prev2.open + prev2.close) / 2.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
#[test]
fn test_hammer_pattern() {
let hammer = Bar::new(Utc::now(), 99.0, 100.0, 90.0, 100.0, 1000.0);
assert!(hammer.is_hammer());
let not_hammer = Bar::new(Utc::now(), 95.0, 100.0, 90.0, 95.0, 1000.0);
assert!(!not_hammer.is_hammer());
}
#[test]
fn test_marubozu_pattern() {
let marubozu = Bar::new(Utc::now(), 100.0, 110.0, 100.0, 110.0, 1000.0);
assert!(marubozu.is_marubozu());
let not_marubozu = Bar::new(Utc::now(), 102.0, 110.0, 100.0, 108.0, 1000.0);
assert!(!not_marubozu.is_marubozu());
}
#[test]
fn test_engulfing_patterns() {
let bearish_bar = Bar::new(Utc::now(), 105.0, 107.0, 99.0, 100.0, 1000.0);
let bullish_engulfing = Bar::new(Utc::now(), 98.0, 108.0, 97.0, 106.0, 1000.0);
assert!(bullish_engulfing.is_bullish_engulfing(&bearish_bar));
assert!(!bullish_engulfing.is_bearish_engulfing(&bearish_bar));
}
}