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_long_shadow(
input_open: &[TAFloat],
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
param_shadow_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 == 0 {
return Err(KandError::InvalidData);
}
if len <= lookback {
return Err(KandError::InsufficientData);
}
if len != input_high.len()
|| len != input_low.len()
|| len != input_close.len()
|| len != output_signals.len()
|| len != output_body_avg.len()
{
return Err(KandError::LengthMismatch);
}
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
if param_shadow_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_long_shadow_inc(
input_open[i],
input_high[i],
input_low[i],
input_close[i],
body_avg,
param_period,
param_shadow_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_long_shadow_inc(
input_open: TAFloat,
input_high: TAFloat,
input_low: TAFloat,
input_close: TAFloat,
prev_body_avg: TAFloat,
param_period: usize,
param_shadow_factor: TAFloat,
) -> Result<(TAInt, TAFloat), KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
if param_shadow_factor <= 0.0 {
return Err(KandError::InvalidParameter);
}
}
#[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 total_range = input_high - input_low;
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;
let shadow_threshold = (param_shadow_factor / 100.0) * total_range;
let has_long_upper_shadow = up_shadow >= shadow_threshold;
let has_long_lower_shadow = down_shadow >= shadow_threshold;
let signal = if is_small_body {
if has_long_upper_shadow && !has_long_lower_shadow {
Signal::Bearish.into()
} else if has_long_lower_shadow && !has_long_upper_shadow {
Signal::Bullish.into()
} else {
Signal::Neutral.into()
}
} else {
Signal::Neutral.into()
};
Ok((signal, body_avg))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_cdl_long_shadow() {
let input_open = vec![
96674.3, 96814.9, 96667.3, 96747.9, 96743.4, 96712.4, 96677.7, 96556.3, 96500.0,
96442.8, 96229.7, 96152.2, 96145.7, 96233.1, 96140.1, 95505.0, 95575.1, 95585.2,
95544.0, 95450.0, 95592.4, 95456.0, 95664.2, 95674.1, 95546.9,
];
let input_high = vec![
96911.0, 96831.2, 96754.9, 96875.7, 96793.9, 96725.7, 96802.4, 96646.6, 96526.0,
96470.9, 96229.8, 96245.3, 96341.5, 96233.1, 96140.1, 95582.6, 95856.1, 95585.2,
95702.4, 95729.2, 95633.3, 95720.6, 95698.2, 95682.9, 95794.6,
];
let input_low = vec![
96567.2, 96646.7, 96648.0, 96730.0, 96694.7, 96660.4, 96556.2, 96500.1, 96400.0,
96200.0, 96100.0, 96073.6, 96130.4, 96045.6, 95400.0, 95200.1, 95544.8, 95400.7,
95427.6, 95359.5, 95442.3, 95427.0, 95545.4, 95473.1, 95475.8,
];
let input_close = vec![
96814.9, 96667.4, 96747.9, 96743.4, 96712.5, 96677.7, 96556.2, 96500.1, 96442.7,
96229.7, 96152.2, 96145.7, 96233.1, 96140.0, 95505.2, 95575.1, 95585.3, 95544.0,
95450.1, 95592.5, 95456.0, 95664.2, 95674.1, 95547.0, 95679.4,
];
let param_period = 14;
let param_shadow_factor = 75.0;
let mut output_signals = vec![0; input_open.len()];
let mut output_body_avg = vec![0.0; input_open.len()];
cdl_long_shadow(
&input_open,
&input_high,
&input_low,
&input_close,
param_period,
param_shadow_factor,
&mut output_signals,
&mut output_body_avg,
)
.unwrap();
for i in 0..13 {
assert_eq!(output_signals[i], Signal::Invalid.into());
assert!(output_body_avg[i].is_nan());
}
println!("output_signals: {output_signals:?}");
assert_eq!(output_signals[15], Signal::Bullish.into()); assert_eq!(output_signals[16], Signal::Bearish.into()); assert_eq!(output_signals[17], Signal::Bullish.into()); assert_eq!(output_signals[22], Signal::Bullish.into());
let mut prev_body_avg = output_body_avg[13];
for i in 14..18 {
let (signal, new_body_avg) = cdl_long_shadow_inc(
input_open[i],
input_high[i],
input_low[i],
input_close[i],
prev_body_avg,
param_period,
param_shadow_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;
}
}
}