use crate::{
EPSILON,
KandError,
TAFloat,
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 - 1)
}
pub fn willr(
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
output: &mut [TAFloat],
output_highest_high: &mut [TAFloat],
output_lowest_low: &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 != input_low.len()
|| len != input_close.len()
|| len != output.len()
|| len != output_highest_high.len()
|| len != output_lowest_low.len()
{
return Err(KandError::LengthMismatch);
}
if len <= lookback {
return Err(KandError::InsufficientData);
}
}
#[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);
}
}
}
for i in lookback..len {
let highest_idx = highest_bars(input_high, i, param_period)?;
let lowest_idx = lowest_bars(input_low, i, param_period)?;
let highest_high = input_high[i - highest_idx];
let lowest_low = input_low[i - lowest_idx];
output_highest_high[i] = highest_high;
output_lowest_low[i] = lowest_low;
let denom = highest_high - lowest_low;
if denom == 0.0 {
output[i] = 0.0;
} else {
output[i] = (highest_high - input_close[i]) / denom * -100.0;
}
}
for i in 0..lookback {
output[i] = TAFloat::NAN;
output_highest_high[i] = TAFloat::NAN;
output_lowest_low[i] = TAFloat::NAN;
}
Ok(())
}
pub fn willr_inc(
prev_highest_high: TAFloat,
prev_lowest_low: TAFloat,
prev_high: TAFloat,
prev_low: TAFloat,
input_close: TAFloat,
input_high: TAFloat,
input_low: TAFloat,
) -> Result<(TAFloat, TAFloat, TAFloat), KandError> {
#[cfg(feature = "deep-check")]
{
if prev_highest_high.is_nan()
|| prev_lowest_low.is_nan()
|| prev_high.is_nan()
|| prev_low.is_nan()
|| input_close.is_nan()
|| input_high.is_nan()
|| input_low.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let new_highest_high = if input_high > prev_highest_high {
input_high
} else if (prev_high - prev_highest_high).abs() < EPSILON {
input_high.max(prev_highest_high)
} else {
prev_highest_high
};
let new_lowest_low = if input_low < prev_lowest_low {
input_low
} else if (prev_low - prev_lowest_low).abs() < EPSILON {
input_low.min(prev_lowest_low)
} else {
prev_lowest_low
};
let denom = new_highest_high - new_lowest_low;
let willr = if denom == 0.0 {
0.0
} else {
(new_highest_high - input_close) / denom * -100.0
};
Ok((willr, new_highest_high, new_lowest_low))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_willr_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 = vec![0.0; input_high.len()];
let mut output_highest_high = vec![0.0; input_high.len()];
let mut output_lowest_low = vec![0.0; input_high.len()];
willr(
&input_high,
&input_low,
&input_close,
param_period,
&mut output,
&mut output_highest_high,
&mut output_lowest_low,
)
.unwrap();
for i in 0..13 {
assert!(output[i].is_nan());
assert!(output_highest_high[i].is_nan());
assert!(output_lowest_low[i].is_nan());
}
let expected_values = [
-80.106_100_795_756_35,
-94.935_451_837_137_89,
-92.281_105_990_784,
-85.153_892_576_945_03,
-80.899_215_449_606_93,
-64.121_907_060_953_25,
-77.519_613_759_806_97,
-97.742_212_060_588_33,
-87.942_028_985_507_66,
-73.030_303_030_303_03,
-60.363_636_363_635_486,
-48.787_878_787_878_79,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output[i + 13], *expected, epsilon = 0.0001);
}
let mut prev_highest_high = output_highest_high[13];
let mut prev_lowest_low = output_lowest_low[13];
for i in 14..19 {
let (result, highest_high, lowest_low) = willr_inc(
prev_highest_high,
prev_lowest_low,
input_high[i - 1],
input_low[i - 1],
input_close[i],
input_high[i],
input_low[i],
)
.unwrap();
assert_relative_eq!(result, output[i], epsilon = 0.0001);
prev_highest_high = highest_high;
prev_lowest_low = lowest_low;
}
}
}