use super::atr;
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 natr(
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
output_natr: &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_natr.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 atr_values = vec![0.0; len];
atr::atr(
input_high,
input_low,
input_close,
param_period,
&mut atr_values,
)?;
for i in lookback..len {
output_natr[i] = (atr_values[i] / input_close[i]) * 100.0;
}
for value in output_natr.iter_mut().take(lookback) {
*value = TAFloat::NAN;
}
Ok(())
}
pub fn natr_inc(
input_high: TAFloat,
input_low: TAFloat,
input_close: TAFloat,
prev_close: TAFloat,
prev_atr: TAFloat,
param_period: usize,
) -> Result<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()
|| input_close.is_nan()
|| prev_close.is_nan()
|| prev_atr.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let output_atr = atr::atr_inc(input_high, input_low, prev_close, prev_atr, param_period)?;
Ok((output_atr / input_close) * 100.0)
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_natr_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,
35069.0, 35024.6, 34939.5, 34952.6, 35000.0, 35041.8, 35080.0, 35114.5, 35097.2,
35092.0, 35073.2, 35139.3,
];
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,
35013.4, 34950.1, 34900.0, 34920.0, 34952.6, 35000.0, 35041.8, 35080.0, 35080.0,
35070.0, 35050.0, 35092.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, 35114.5, 35097.2,
35092.0, 35073.2, 35139.3,
];
let param_period = 14;
let mut output_natr = vec![0.0; input_high.len()];
natr(
&input_high,
&input_low,
&input_close,
param_period,
&mut output_natr,
)
.unwrap();
for value in output_natr.iter().take(param_period) {
assert!(value.is_nan());
}
let expected_values = [
0.180_066_041_856_908_33,
0.189_412_602_820_658_08,
0.196_012_458_358_289_65,
0.192_852_460_649_046_9,
0.192_338_093_664_248_45,
0.191_633_390_505_224_85,
0.199_333_290_479_994_7,
0.200_025_731_859_381_86,
0.197_384_596_204_178_14,
0.197_172_664_957_892_62,
0.193_559_020_569_269_05,
0.195_301_199_830_777_3,
0.195_462_787_749_759_28,
0.186_820_266_737_109_55,
0.180_106_296_880_325_17,
0.188_494_189_518_896_63,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_natr[i + param_period], *expected, epsilon = 0.1);
}
for i in param_period + 1..input_high.len() {
let output_natr_inc = natr_inc(
input_high[i],
input_low[i],
input_close[i],
input_close[i - 1],
output_natr[i - 1] * input_close[i - 1] / 100.0,
param_period,
)
.unwrap();
assert_relative_eq!(output_natr_inc, output_natr[i], epsilon = 0.00001);
}
}
}