use crate::{
KandError,
TAFloat,
TAInt,
helper::{lower_shadow_length, period_to_k, real_body_length, upper_shadow_length},
types::Signal,
};
pub const fn lookback(param_period: usize) -> Result<usize, KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
Ok(param_period - 1)
}
pub fn cdl_inverted_hammer(
input_open: &[TAFloat],
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
param_factor: TAFloat,
output_signals: &mut [TAInt],
output_body_avg: &mut [TAFloat],
) -> Result<(), KandError> {
let len = input_open.len();
let lookback = lookback(param_period)?;
#[cfg(feature = "check")]
{
if len != input_high.len() || len != input_low.len() || len != input_close.len() {
return Err(KandError::LengthMismatch);
}
if len != output_signals.len() || len != output_body_avg.len() {
return Err(KandError::LengthMismatch);
}
if len <= lookback {
return Err(KandError::InsufficientData);
}
if param_factor <= 0.0 {
return Err(KandError::InvalidParameter);
}
}
#[cfg(feature = "deep-check")]
{
for i in 0..len {
if input_open[i].is_nan()
|| input_high[i].is_nan()
|| input_low[i].is_nan()
|| input_close[i].is_nan()
{
return Err(KandError::NaNDetected);
}
}
}
let mut sum = 0.0;
for i in 0..param_period {
sum += real_body_length(input_open[i], input_close[i]);
}
let mut body_avg = sum / param_period as TAFloat;
output_body_avg[lookback] = body_avg;
for i in lookback..len {
let (signal, new_body_avg) = cdl_inverted_hammer_inc(
input_open[i],
input_high[i],
input_low[i],
input_close[i],
body_avg,
param_period,
param_factor,
)?;
output_signals[i] = signal;
output_body_avg[i] = new_body_avg;
body_avg = new_body_avg;
}
for i in 0..lookback {
output_signals[i] = Signal::Invalid.into();
output_body_avg[i] = TAFloat::NAN;
}
Ok(())
}
pub fn cdl_inverted_hammer_inc(
input_open: TAFloat,
input_high: TAFloat,
input_low: TAFloat,
input_close: TAFloat,
prev_body_avg: TAFloat,
param_period: usize,
param_factor: TAFloat,
) -> Result<(TAInt, TAFloat), KandError> {
#[cfg(feature = "deep-check")]
{
if input_open.is_nan()
|| input_high.is_nan()
|| input_low.is_nan()
|| input_close.is_nan()
|| prev_body_avg.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let body = real_body_length(input_open, input_close);
let up_shadow = upper_shadow_length(input_high, input_open, input_close);
let down_shadow = lower_shadow_length(input_low, input_open, input_close);
let k = period_to_k(param_period)?;
let body_avg = (body - prev_body_avg).mul_add(k, prev_body_avg);
let is_small_body = body <= body_avg && body > 0.0;
let has_long_upper_shadow = up_shadow >= param_factor * body;
let has_minimal_lower_shadow = down_shadow <= body;
let body_in_lower_half = TAFloat::max(input_open, input_close) < (input_high + input_low) / 2.0;
let signal =
if is_small_body && has_long_upper_shadow && has_minimal_lower_shadow && body_in_lower_half
{
Signal::Bullish.into()
} else {
Signal::Neutral.into()
};
Ok((signal, body_avg))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_cdl_inverted_hammer() {
let input_open = vec![
96470.4, 96415.2, 96386.1, 96259.3, 96290.1, 96307.4, 96266.7, 96200.0, 96078.4,
96175.2, 96123.1, 96242.9, 96149.3, 96104.0, 96191.9, 96236.3, 96278.1, 96200.6,
96164.9, 96113.6, 96095.8, 96051.1, 96085.9, 96074.0, 96092.5, 96052.5, 96067.5,
96100.0, 96067.1, 96054.1, 95951.3,
];
let input_high = vec![
96470.5, 96450.0, 96386.1, 96344.7, 96374.1, 96312.5, 96300.0, 96244.8, 96183.1,
96198.5, 96242.9, 96275.6, 96156.9, 96211.7, 96240.5, 96323.0, 96300.0, 96200.7,
96165.0, 96162.2, 96118.1, 96107.6, 96086.0, 96183.8, 96095.7, 96102.0, 96125.7,
96120.5, 96110.5, 96054.1, 96043.7,
];
let input_low = vec![
96388.0, 96370.4, 96168.8, 96217.2, 96286.2, 96261.5, 96111.0, 96061.2, 96078.4,
96070.4, 96079.1, 96138.3, 96008.7, 96052.1, 96169.4, 96236.2, 96200.6, 96137.0,
96050.0, 96095.7, 96050.0, 96045.9, 96006.1, 96074.0, 96032.1, 96047.0, 96050.0,
96060.3, 96054.0, 95835.8, 95900.0,
];
let input_close = vec![
96415.2, 96386.1, 96259.3, 96290.2, 96307.3, 96266.8, 96200.0, 96078.3, 96175.3,
96123.2, 96242.9, 96149.3, 96104.1, 96192.0, 96236.3, 96278.1, 96200.6, 96165.0,
96113.6, 96095.8, 96051.2, 96086.0, 96074.0, 96092.6, 96052.5, 96067.6, 96100.0,
96067.0, 96054.0, 95951.4, 95951.5,
];
let param_period = 14;
let param_factor = 2.0;
let mut output_signals = vec![0i64; input_open.len()];
let mut output_body_avg = vec![0.0; input_open.len()];
cdl_inverted_hammer(
&input_open,
&input_high,
&input_low,
&input_close,
param_period,
param_factor,
&mut output_signals,
&mut output_body_avg,
)
.unwrap();
for i in 0..13 {
assert_eq!(output_signals[i], -1);
assert!(output_body_avg[i].is_nan());
}
println!("output_signals: {output_signals:?}");
println!("output_body_avg: {output_body_avg:?}");
assert_eq!(output_signals[19], Signal::Bullish.into()); assert_eq!(output_signals[23], Signal::Bullish.into()); assert_eq!(output_signals[25], Signal::Bullish.into()); assert_eq!(output_signals[28], Signal::Bullish.into());
let mut prev_body_avg = output_body_avg[13];
for i in 14..18 {
let (signal, new_body_avg) = cdl_inverted_hammer_inc(
input_open[i],
input_high[i],
input_low[i],
input_close[i],
prev_body_avg,
param_period,
param_factor,
)
.unwrap();
assert_eq!(signal, output_signals[i]);
assert_relative_eq!(new_body_avg, output_body_avg[i], epsilon = 0.00001);
prev_body_avg = new_body_avg;
}
}
}