use crate::error::TaError;
#[inline]
pub fn real_body(open: f64, close: f64) -> f64 {
(close - open).abs()
}
#[inline]
pub fn upper_shadow(open: f64, high: f64, close: f64) -> f64 {
high - open.max(close)
}
#[inline]
pub fn lower_shadow(open: f64, low: f64, close: f64) -> f64 {
open.min(close) - low
}
#[inline]
pub fn high_low_range(high: f64, low: f64) -> f64 {
high - low
}
#[inline]
pub fn candle_color(open: f64, close: f64) -> f64 {
if close >= open {
1.0
} else {
-1.0
}
}
#[inline]
pub fn body_high(open: f64, close: f64) -> f64 {
open.max(close)
}
#[inline]
pub fn body_low(open: f64, close: f64) -> f64 {
open.min(close)
}
#[inline]
pub fn body_center(open: f64, close: f64) -> f64 {
(open + close) * 0.5
}
#[inline]
pub fn real_body_gap_up(
o_cur: f64,
c_cur: f64,
o_prev: f64,
c_prev: f64,
) -> bool {
o_cur.min(c_cur) > o_prev.max(c_prev)
}
#[inline]
pub fn real_body_gap_down(
o_cur: f64,
c_cur: f64,
o_prev: f64,
c_prev: f64,
) -> bool {
o_cur.max(c_cur) < o_prev.min(c_prev)
}
#[inline]
pub fn candle_gap_up(low_cur: f64, high_prev: f64) -> bool {
low_cur > high_prev
}
#[inline]
pub fn candle_gap_down(high_cur: f64, low_prev: f64) -> bool {
high_cur < low_prev
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RangeType {
RealBody,
HighLow,
Shadows,
}
#[derive(Clone, Copy, Debug)]
pub struct CandleSetting {
pub range_type: RangeType,
pub avg_period: usize,
pub factor: f64,
}
pub const BODY_LONG: CandleSetting =
CandleSetting { range_type: RangeType::RealBody, avg_period: 10, factor: 1.0 };
pub const BODY_VERY_LONG: CandleSetting =
CandleSetting { range_type: RangeType::RealBody, avg_period: 10, factor: 3.0 };
pub const BODY_SHORT: CandleSetting =
CandleSetting { range_type: RangeType::RealBody, avg_period: 10, factor: 1.0 };
pub const BODY_DOJI: CandleSetting =
CandleSetting { range_type: RangeType::HighLow, avg_period: 10, factor: 0.1 };
pub const SHADOW_LONG: CandleSetting =
CandleSetting { range_type: RangeType::RealBody, avg_period: 0, factor: 1.0 };
pub const SHADOW_VERY_LONG: CandleSetting =
CandleSetting { range_type: RangeType::RealBody, avg_period: 0, factor: 2.0 };
pub const SHADOW_SHORT: CandleSetting =
CandleSetting { range_type: RangeType::Shadows, avg_period: 10, factor: 1.0 };
pub const SHADOW_VERY_SHORT: CandleSetting =
CandleSetting { range_type: RangeType::HighLow, avg_period: 10, factor: 0.1 };
pub const NEAR: CandleSetting =
CandleSetting { range_type: RangeType::HighLow, avg_period: 5, factor: 0.2 };
pub const FAR: CandleSetting =
CandleSetting { range_type: RangeType::HighLow, avg_period: 5, factor: 0.6 };
pub const EQUAL: CandleSetting =
CandleSetting { range_type: RangeType::HighLow, avg_period: 5, factor: 0.05 };
#[inline]
fn range_of(rt: RangeType, open: f64, high: f64, low: f64, close: f64) -> f64 {
match rt {
RangeType::RealBody => real_body(open, close),
RangeType::HighLow => high_low_range(high, low),
RangeType::Shadows => upper_shadow(open, high, close) + lower_shadow(open, low, close),
}
}
pub struct CandleAvg {
setting: CandleSetting,
total: f64,
trailing: usize,
off: usize,
}
impl CandleAvg {
pub fn new(
setting: CandleSetting,
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
start_idx: usize,
off: usize,
) -> Self {
let p = setting.avg_period;
let begin = start_idx - off - p;
let end = start_idx - off; let mut total = 0.0_f64;
let mut k = begin;
while k < end {
total += range_of(setting.range_type, open[k], high[k], low[k], close[k]);
k += 1;
}
CandleAvg {
setting,
total,
trailing: begin,
off,
}
}
#[inline(always)]
pub fn value(&self, i: usize, open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> f64 {
let rt = self.setting.range_type;
let idx = i - self.off;
let raw = if self.setting.avg_period == 0 {
range_of(rt, open[idx], high[idx], low[idx], close[idx])
} else {
self.total / self.setting.avg_period as f64
};
raw * self.setting.factor / if rt == RangeType::Shadows { 2.0 } else { 1.0 }
}
#[inline(always)]
pub fn advance(&mut self, i: usize, open: &[f64], high: &[f64], low: &[f64], close: &[f64]) {
let rt = self.setting.range_type;
let idx = i - self.off;
self.total += range_of(rt, open[idx], high[idx], low[idx], close[idx])
- range_of(
rt,
open[self.trailing],
high[self.trailing],
low[self.trailing],
close[self.trailing],
);
self.trailing += 1;
}
}
#[allow(unused_assignments, unused_variables)]
pub mod batch_1;
#[allow(unused_assignments, unused_variables)]
pub mod batch_2;
#[allow(unused_assignments, unused_variables)]
pub mod batch_3;
#[allow(unused_assignments, unused_variables)]
pub mod batch_4;
#[allow(unused_assignments, unused_variables)]
pub mod batch_5;
#[allow(unused_assignments, unused_variables)]
pub mod batch_6;
#[allow(unused_assignments, unused_variables)]
pub mod batch_7;
#[allow(unused_assignments, unused_variables)]
pub mod batch_8;
pub use batch_1::*;
pub use batch_2::*;
pub use batch_3::*;
pub use batch_4::*;
pub use batch_5::*;
pub use batch_6::*;
pub use batch_7::*;
pub use batch_8::*;
#[inline]
fn check_ohlc(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
name: &str,
) -> Result<(), TaError> {
let n = open.len();
if high.len() != n || low.len() != n || close.len() != n {
return Err(TaError::BadParam(format!(
"{name}: open/high/low/close must share the same length"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candle_primitives() {
assert_eq!(real_body(10.0, 20.0), 10.0);
assert_eq!(upper_shadow(10.0, 21.0, 20.0), 1.0);
assert_eq!(lower_shadow(10.0, 9.0, 20.0), 1.0);
assert_eq!(high_low_range(21.0, 9.0), 12.0);
assert_eq!(candle_color(10.0, 20.0), 1.0);
assert_eq!(candle_color(20.0, 10.0), -1.0);
assert_eq!(body_high(10.0, 20.0), 20.0);
assert_eq!(body_low(10.0, 20.0), 10.0);
assert_eq!(body_center(10.0, 20.0), 15.0);
assert!(real_body_gap_up(5.0, 6.0, 1.0, 2.0));
assert!(real_body_gap_down(1.0, 2.0, 5.0, 6.0));
assert!(candle_gap_up(5.0, 3.0));
assert!(candle_gap_down(3.0, 5.0));
}
fn bodies() -> ([f64; 8], [f64; 8], [f64; 8], [f64; 8]) {
let open = [0.0; 8];
let close = [0.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0];
let high = close;
let low = open;
(open, high, low, close)
}
#[test]
fn candle_avg_running_window() {
let (open, high, low, close) = bodies();
let s = CandleSetting { range_type: RangeType::RealBody, avg_period: 3, factor: 1.0 };
let mut a = CandleAvg::new(s, &open, &high, &low, &close, 5, 0);
assert_eq!(a.value(5, &open, &high, &low, &close), 4.0); a.advance(5, &open, &high, &low, &close);
assert_eq!(a.value(6, &open, &high, &low, &close), 6.0); a.advance(6, &open, &high, &low, &close);
assert_eq!(a.value(7, &open, &high, &low, &close), 8.0); }
#[test]
fn candle_avg_avg_period_zero() {
let (open, high, low, close) = bodies();
let s = CandleSetting { range_type: RangeType::RealBody, avg_period: 0, factor: 2.0 };
let a = CandleAvg::new(s, &open, &high, &low, &close, 5, 0);
assert_eq!(a.value(5, &open, &high, &low, &close), 16.0);
}
#[test]
fn candle_avg_off_one() {
let (open, high, low, close) = bodies();
let s = CandleSetting { range_type: RangeType::RealBody, avg_period: 3, factor: 1.0 };
let mut a = CandleAvg::new(s, &open, &high, &low, &close, 5, 1);
assert_eq!(a.value(5, &open, &high, &low, &close), 2.0);
a.advance(5, &open, &high, &low, &close);
assert_eq!(a.value(6, &open, &high, &low, &close), 4.0);
}
#[test]
fn cdl_doji_detects() {
let o = vec![0.0; 12];
let mut h = vec![0.0; 12];
let l = vec![0.0; 12];
let c = vec![0.0; 12]; for i in 0..12 {
h[i] = 100.0;
}
let out = cdl_doji(&o, &h, &l, &c).unwrap();
assert_eq!(out.len(), 12);
for i in 0..10 {
assert_eq!(out[i], 0.0, "leading index {i} must be 0");
}
assert_eq!(out[10], 100.0);
assert_eq!(out[11], 100.0);
}
#[test]
fn cdl_marubozu_bullish() {
let mut o = vec![0.0; 11];
let mut h = vec![0.0; 11];
let mut l = vec![0.0; 11];
let mut c = vec![0.0; 11];
for i in 0..10 {
h[i] = 100.0; }
o[10] = 10.0;
c[10] = 20.0; h[10] = 21.0; l[10] = 9.0; let out = cdl_marubozu(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 100.0);
}
#[test]
fn cdl_marubozu_bearish() {
let mut o = vec![0.0; 11];
let mut h = vec![0.0; 11];
let mut l = vec![0.0; 11];
let mut c = vec![0.0; 11];
for i in 0..10 {
h[i] = 100.0;
}
o[10] = 20.0;
c[10] = 10.0;
h[10] = 21.0;
l[10] = 9.0;
let out = cdl_marubozu(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], -100.0);
}
#[test]
fn cdl_marubozu_no_trigger_big_shadows() {
let mut o = vec![0.0; 11];
let mut h = vec![0.0; 11];
let mut l = vec![0.0; 11];
let mut c = vec![0.0; 11];
for i in 0..10 {
h[i] = 100.0;
}
o[10] = 10.0;
c[10] = 20.0;
h[10] = 100.0; l[10] = 0.0; let out = cdl_marubozu(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 0.0);
}
#[test]
fn cdl_engulfing_full_bull() {
let o = [0.0, 20.0, 5.0];
let h = [0.0, 20.0, 25.0];
let l = [0.0, 10.0, 5.0];
let c = [0.0, 10.0, 25.0];
let out = cdl_engulfing(&o, &h, &l, &c).unwrap();
assert_eq!(out[0], 0.0);
assert_eq!(out[1], 0.0); assert_eq!(out[2], 100.0);
}
#[test]
fn cdl_engulfing_weak_bull() {
let o = [0.0, 20.0, 10.0];
let h = [0.0, 20.0, 25.0];
let l = [0.0, 10.0, 10.0];
let c = [0.0, 10.0, 25.0];
let out = cdl_engulfing(&o, &h, &l, &c).unwrap();
assert_eq!(out[2], 80.0);
}
#[test]
fn cdl_engulfing_full_bear() {
let o = [0.0, 10.0, 25.0];
let h = [0.0, 20.0, 25.0];
let l = [0.0, 10.0, 5.0];
let c = [0.0, 20.0, 5.0];
let out = cdl_engulfing(&o, &h, &l, &c).unwrap();
assert_eq!(out[2], -100.0);
}
#[test]
fn cdl_engulfing_none() {
let o = [0.0, 10.0, 12.0];
let h = [0.0, 20.0, 25.0];
let l = [0.0, 10.0, 12.0];
let c = [0.0, 20.0, 22.0];
let out = cdl_engulfing(&o, &h, &l, &c).unwrap();
assert_eq!(out[2], 0.0);
}
#[test]
fn cdl_shootingstar_bearish() {
let mut o = vec![0.0; 12];
let mut h = vec![0.0; 12];
let mut l = vec![0.0; 12];
let mut c = vec![0.0; 12];
for i in 0..11 {
o[i] = 0.0;
c[i] = 5.0;
h[i] = 100.0;
l[i] = 0.0;
}
o[11] = 10.0;
c[11] = 11.0; h[11] = 13.0; l[11] = 9.0; let out = cdl_shootingstar(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], -100.0);
}
#[test]
fn cdl_hammer_bullish() {
let mut o = vec![0.0; 12];
let mut h = vec![0.0; 12];
let mut l = vec![0.0; 12];
let mut c = vec![0.0; 12];
for i in 0..11 {
o[i] = 0.0;
c[i] = 5.0;
h[i] = 100.0;
l[i] = 0.0;
}
o[11] = 10.0;
c[11] = 11.0; h[11] = 20.0; l[11] = 8.0; let out = cdl_hammer(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 100.0);
}
#[test]
fn cdl_hammer_no_trigger_long_upper_shadow() {
let mut o = vec![0.0; 12];
let mut h = vec![0.0; 12];
let mut l = vec![0.0; 12];
let mut c = vec![0.0; 12];
for i in 0..11 {
o[i] = 0.0;
c[i] = 5.0;
h[i] = 100.0;
l[i] = 0.0;
}
o[11] = 10.0;
c[11] = 11.0;
h[11] = 100.0; l[11] = 8.0;
let out = cdl_hammer(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 0.0);
}
}