use super::trange;
use crate::{KandError, TAFloat};
pub const fn lookback(param_period: usize) -> Result<usize, KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
Ok(param_period)
}
pub fn minus_di(
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
output_minus_di: &mut [TAFloat],
output_smoothed_minus_dm: &mut [TAFloat],
output_smoothed_tr: &mut [TAFloat],
) -> Result<(), KandError> {
let len = input_high.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_low.len()
|| len != input_close.len()
|| len != output_minus_di.len()
|| len != output_smoothed_minus_dm.len()
|| len != output_smoothed_tr.len()
{
return Err(KandError::LengthMismatch);
}
}
#[cfg(feature = "deep-check")]
{
for i in 0..len {
if input_high[i].is_nan() || input_low[i].is_nan() || input_close[i].is_nan() {
return Err(KandError::NaNDetected);
}
}
}
let mut minus_dm_sum = 0.0;
let mut tr_sum = 0.0;
let mut prev_high = input_high[0];
let mut prev_low = input_low[0];
let mut prev_close = input_close[0];
for i in 1..param_period {
let high_diff = input_high[i] - prev_high;
let low_diff = prev_low - input_low[i];
let minus_dm1 = if low_diff > high_diff && low_diff > 0.0 {
low_diff
} else {
0.0
};
minus_dm_sum += minus_dm1;
let tr1 = trange::trange_inc(input_high[i], input_low[i], prev_close)?;
tr_sum += tr1;
prev_high = input_high[i];
prev_low = input_low[i];
prev_close = input_close[i];
}
let hundred = 100.0;
let period_t = param_period as TAFloat;
let mut curr_smoothed_minus_dm = minus_dm_sum;
let mut curr_smoothed_tr = tr_sum;
for i in lookback..len {
let high_diff = input_high[i] - input_high[i - 1];
let low_diff = input_low[i - 1] - input_low[i];
let minus_dm1 = if low_diff > high_diff && low_diff > 0.0 {
low_diff
} else {
0.0
};
let tr1 = trange::trange_inc(input_high[i], input_low[i], input_close[i - 1])?;
curr_smoothed_minus_dm =
curr_smoothed_minus_dm - (curr_smoothed_minus_dm / period_t) + minus_dm1;
curr_smoothed_tr = curr_smoothed_tr - (curr_smoothed_tr / period_t) + tr1;
output_smoothed_minus_dm[i] = curr_smoothed_minus_dm;
output_smoothed_tr[i] = curr_smoothed_tr;
output_minus_di[i] = if curr_smoothed_tr == 0.0 {
0.0
} else {
hundred * curr_smoothed_minus_dm / curr_smoothed_tr
};
}
for i in 0..lookback {
output_minus_di[i] = TAFloat::NAN;
output_smoothed_minus_dm[i] = TAFloat::NAN;
output_smoothed_tr[i] = TAFloat::NAN;
}
Ok(())
}
pub fn minus_di_inc(
input_high: TAFloat,
input_low: TAFloat,
prev_high: TAFloat,
prev_low: TAFloat,
prev_close: TAFloat,
prev_smoothed_minus_dm: TAFloat,
prev_smoothed_tr: TAFloat,
param_period: usize,
) -> Result<(TAFloat, TAFloat, TAFloat), KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
#[cfg(feature = "deep-check")]
{
if input_high.is_nan()
|| input_low.is_nan()
|| prev_high.is_nan()
|| prev_low.is_nan()
|| prev_close.is_nan()
|| prev_smoothed_minus_dm.is_nan()
|| prev_smoothed_tr.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let high_diff = input_high - prev_high;
let low_diff = prev_low - input_low;
let minus_dm = if low_diff > high_diff && low_diff > 0.0 {
low_diff
} else {
0.0
};
let tr = trange::trange_inc(input_high, input_low, prev_close)?;
let period_t = param_period as TAFloat;
let output_smoothed_minus_dm =
prev_smoothed_minus_dm - (prev_smoothed_minus_dm / period_t) + minus_dm;
let output_smoothed_tr = prev_smoothed_tr - (prev_smoothed_tr / period_t) + tr;
let output_minus_di = if output_smoothed_tr == 0.0 {
0.0
} else {
100.0 * output_smoothed_minus_dm / output_smoothed_tr
};
Ok((
output_minus_di,
output_smoothed_minus_dm,
output_smoothed_tr,
))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_minus_di_calculation() {
let input_high = vec![
35266.0, 35247.5, 35235.7, 35190.8, 35182.0, 35258.0, 35262.9, 35281.5, 35256.0,
35210.0, 35185.4, 35230.0, 35241.0, 35218.1, 35212.6, 35128.9, 35047.7, 35019.5,
35078.8, 35085.0, 35034.1, 34984.4, 35010.8, 35047.1, 35091.4,
];
let input_low = vec![
35216.1, 35206.5, 35180.0, 35130.7, 35153.6, 35174.7, 35202.6, 35203.5, 35175.0,
35166.0, 35170.9, 35154.1, 35186.0, 35143.9, 35080.1, 35021.1, 34950.1, 34966.0,
35012.3, 35022.2, 34931.6, 34911.0, 34952.5, 34977.9, 35039.0,
];
let input_close = vec![
35216.1, 35221.4, 35190.7, 35170.0, 35181.5, 35254.6, 35202.8, 35251.9, 35197.6,
35184.7, 35175.1, 35229.9, 35212.5, 35160.7, 35090.3, 35041.2, 34999.3, 35013.4,
35069.0, 35024.6, 34939.5, 34952.6, 35000.0, 35041.8, 35080.0,
];
let param_period = 14;
let mut output_minus_di = vec![0.0; input_high.len()];
let mut output_smoothed_minus_dm = vec![0.0; input_high.len()];
let mut output_smoothed_tr = vec![0.0; input_high.len()];
minus_di(
&input_high,
&input_low,
&input_close,
param_period,
&mut output_minus_di,
&mut output_smoothed_minus_dm,
&mut output_smoothed_tr,
)
.unwrap();
for value in output_minus_di.iter().take(param_period) {
assert!(value.is_nan());
}
assert_relative_eq!(
output_minus_di[14],
26.118_652_373_133_61,
epsilon = 0.00001
);
assert_relative_eq!(
output_minus_di[15],
29.626_333_125_808_358,
epsilon = 0.00001
);
assert_relative_eq!(
output_minus_di[16],
34.230_177_437_536_13,
epsilon = 0.00001
);
assert_relative_eq!(
output_minus_di[17],
32.200_629_859_296_83,
epsilon = 0.00001
);
assert_relative_eq!(
output_minus_di[18],
29.832_869_923_860_61,
epsilon = 0.00001
);
let mut prev_smoothed_minus_dm = output_smoothed_minus_dm[14];
let mut prev_smoothed_tr = output_smoothed_tr[14];
for i in 15..input_high.len() {
let (minus_di, new_smoothed_minus_dm, new_smoothed_tr) = minus_di_inc(
input_high[i],
input_low[i],
input_high[i - 1],
input_low[i - 1],
input_close[i - 1],
prev_smoothed_minus_dm,
prev_smoothed_tr,
param_period,
)
.unwrap();
assert_relative_eq!(minus_di, output_minus_di[i], epsilon = 0.00001);
prev_smoothed_minus_dm = new_smoothed_minus_dm;
prev_smoothed_tr = new_smoothed_tr;
}
}
}