use crate::{
TAFloat,
error::KandError,
helper::{highest_bars, lowest_bars},
};
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 aroonosc(
input_high: &[TAFloat],
input_low: &[TAFloat],
param_period: usize,
output_aroonosc: &mut [TAFloat],
output_prev_high: &mut [TAFloat],
output_prev_low: &mut [TAFloat],
output_days_since_high: &mut [usize],
output_days_since_low: &mut [usize],
) -> 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 != output_aroonosc.len()
|| len != output_prev_high.len()
|| len != output_prev_low.len()
|| len != output_days_since_high.len()
|| len != output_days_since_low.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() {
return Err(KandError::NaNDetected);
}
}
}
let param_period_t = param_period as TAFloat;
let hundred_t = 100.0;
for i in lookback..len {
let days_since_high = highest_bars(input_high, i, param_period + 1)?;
let days_since_low = lowest_bars(input_low, i, param_period + 1)?;
output_days_since_high[i] = days_since_high;
output_days_since_low[i] = days_since_low;
output_prev_high[i] = input_high[i - days_since_high];
output_prev_low[i] = input_low[i - days_since_low];
let days_since_high_t = days_since_high as TAFloat;
let days_since_low_t = days_since_low as TAFloat;
let aroon_up = hundred_t - (hundred_t * days_since_high_t / param_period_t);
let aroon_down = hundred_t - (hundred_t * days_since_low_t / param_period_t);
output_aroonosc[i] = aroon_up - aroon_down;
}
for i in 0..lookback {
output_aroonosc[i] = TAFloat::NAN;
output_prev_high[i] = TAFloat::NAN;
output_prev_low[i] = TAFloat::NAN;
output_days_since_high[i] = 0;
output_days_since_low[i] = 0;
}
Ok(())
}
pub fn aroonosc_inc(
input_high: TAFloat,
input_low: TAFloat,
prev_high: TAFloat,
prev_low: TAFloat,
input_days_since_high: usize,
input_days_since_low: usize,
param_period: usize,
) -> Result<(TAFloat, TAFloat, TAFloat, usize, usize), 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() {
return Err(KandError::NaNDetected);
}
}
let mut new_high = prev_high;
let mut new_low = prev_low;
let mut days_since_high = input_days_since_high;
let mut days_since_low = input_days_since_low;
if days_since_high < param_period {
days_since_high += 1;
}
if days_since_low < param_period {
days_since_low += 1;
}
if input_high >= prev_high {
new_high = input_high;
days_since_high = 0;
}
if input_low <= prev_low {
new_low = input_low;
days_since_low = 0;
}
let param_period_t = param_period as TAFloat;
let hundred_t = 100.0;
let days_since_high_t = days_since_high as TAFloat;
let days_since_low_t = days_since_low as TAFloat;
let aroon_up = hundred_t - (hundred_t * days_since_high_t / param_period_t);
let aroon_down = hundred_t - (hundred_t * days_since_low_t / param_period_t);
let aroon_osc = aroon_up - aroon_down;
Ok((
aroon_osc,
new_high,
new_low,
days_since_high,
days_since_low,
))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_aroonosc_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 param_period = 14;
let mut output_aroonosc = vec![0.0; input_high.len()];
let mut output_prev_high = vec![0.0; input_high.len()];
let mut output_prev_low = vec![0.0; input_high.len()];
let mut output_days_since_high = vec![0; input_high.len()];
let mut output_days_since_low = vec![0; input_high.len()];
aroonosc(
&input_high,
&input_low,
param_period,
&mut output_aroonosc,
&mut output_prev_high,
&mut output_prev_low,
&mut output_days_since_high,
&mut output_days_since_low,
)
.unwrap();
for value in output_aroonosc.iter().take(14) {
assert!(value.is_nan());
}
let expected_values = [
-50.0,
-57.142_857_142_857_146,
-64.285_714_285_714_29,
-64.285_714_285_714_29,
-64.285_714_285_714_29,
-64.285_714_285_714_29,
-92.857_142_857_142_86,
-100.0,
-92.857_142_857_142_86,
-64.285_714_285_714_29,
-64.285_714_285_714_29,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_aroonosc[i + 14], *expected, epsilon = 0.0001);
}
let mut prev_high = output_prev_high[14];
let mut prev_low = output_prev_low[14];
let mut days_since_high = output_days_since_high[14];
let mut days_since_low = output_days_since_low[14];
for i in 15..20 {
let (aroon_osc, new_high, new_low, new_days_high, new_days_low) = aroonosc_inc(
input_high[i],
input_low[i],
prev_high,
prev_low,
days_since_high,
days_since_low,
param_period,
)
.unwrap();
assert_relative_eq!(aroon_osc, output_aroonosc[i], epsilon = 0.0001);
prev_high = new_high;
prev_low = new_low;
days_since_high = new_days_high;
days_since_low = new_days_low;
}
}
}