use crate::core::defaults::ATR_PERIOD;
use crate::core::{check_eq_len, ema_wilder, true_range};
use crate::error::{check_period, TaError};
use crate::indicator::indicator;
indicator! {
fn trange(high: &[f64], low: &[f64], close: &[f64]) -> Vec<f64> with trange_with_output;
}
pub fn trange_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
out: &mut [f64],
) -> Result<(), TaError> {
check_eq_len(&[high, low, close], "trange")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"trange_with_output: out length must equal high length".into(),
));
}
let tr = true_range(high, low, close);
out.copy_from_slice(&tr);
Ok(())
}
indicator! {
fn atr(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with atr_with_output
default atr_default(high: &[f64], low: &[f64], close: &[f64]) => (ATR_PERIOD)
;
}
pub fn atr_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "atr")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"atr_with_output: out length must equal high length".into(),
));
}
let tr = true_range(high, low, close);
let atr_line = ema_wilder(&tr, time_period);
out.copy_from_slice(&atr_line);
Ok(())
}
indicator! {
fn natr(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with natr_with_output
default natr_default(high: &[f64], low: &[f64], close: &[f64]) => (ATR_PERIOD)
;
}
pub fn natr_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "natr")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"natr_with_output: out length must equal close length".into(),
));
}
let tr = true_range(high, low, close);
let atr_line = ema_wilder(&tr, time_period);
let n = close.len();
for i in 0..n {
if atr_line[i].is_nan() {
continue;
}
out[i] = if close[i] == 0.0 {
0.0
} else {
100.0 * atr_line[i] / close[i]
};
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trange_basic() {
let high = [10.0, 11.0, 12.0];
let low = [9.0, 9.5, 11.0];
let close = [9.5, 10.5, 11.5];
let tr = trange(&high, &low, &close).unwrap();
assert!(tr[0].is_nan());
assert!((tr[1] - 1.5).abs() < 1e-12);
assert!((tr[2] - 1.5).abs() < 1e-12);
}
#[test]
fn atr_wilder_seed() {
let high = [10.0, 11.0, 12.0, 13.0, 14.0];
let low = [9.0, 9.5, 11.0, 12.0, 13.0];
let close = [9.5, 10.5, 11.5, 12.5, 13.5];
let out = atr(&high, &low, &close, 3).unwrap();
assert!(out[0].is_nan() && out[1].is_nan() && out[2].is_nan());
let seed = (1.5 + 1.5 + 1.5) / 3.0;
assert!((out[3] - seed).abs() < 1e-12);
let exp = seed + (1.5 - seed) / 3.0;
assert!((out[4] - exp).abs() < 1e-12);
}
#[test]
fn natr_ratio() {
let high: Vec<f64> = (0..40).map(|i| 10.0 + i as f64 * 0.1).collect();
let low: Vec<f64> = (0..40).map(|i| 9.0 + i as f64 * 0.1).collect();
let close: Vec<f64> = (0..40).map(|i| 9.5 + i as f64 * 0.1).collect();
let a = atr(&high, &low, &close, 14).unwrap();
let n = natr(&high, &low, &close, 14).unwrap();
let i = 30;
assert!((n[i] - 100.0 * a[i] / close[i]).abs() < 1e-9);
}
#[test]
fn trange_length_mismatch_is_error() {
assert!(matches!(
trange(&[1.0, 2.0], &[1.0], &[1.0, 2.0]),
Err(TaError::BadParam(_))
));
}
}