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 - 1)
}
pub fn midpoint(
input_price: &[TAFloat],
param_period: usize,
output_midpoint: &mut [TAFloat],
output_highest: &mut [TAFloat],
output_lowest: &mut [TAFloat],
) -> Result<(), KandError> {
let len = input_price.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 != output_midpoint.len() || len != output_highest.len() || len != output_lowest.len()
{
return Err(KandError::LengthMismatch);
}
}
#[cfg(feature = "deep-check")]
{
for &price in input_price.iter().take(len) {
if price.is_nan() {
return Err(KandError::NaNDetected);
}
}
}
for i in lookback..len {
let start_idx = i + 1 - param_period;
let mut highest = input_price[start_idx];
let mut lowest = input_price[start_idx];
for &price in input_price.iter().take(i + 1).skip(start_idx + 1) {
highest = highest.max(price);
lowest = lowest.min(price);
}
output_highest[i] = highest;
output_lowest[i] = lowest;
output_midpoint[i] = (highest + lowest) / 2.0;
}
for i in 0..lookback {
output_midpoint[i] = TAFloat::NAN;
output_highest[i] = TAFloat::NAN;
output_lowest[i] = TAFloat::NAN;
}
Ok(())
}
pub fn midpoint_inc(
input_price: TAFloat,
prev_highest: TAFloat,
prev_lowest: 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_price.is_nan() || prev_highest.is_nan() || prev_lowest.is_nan() {
return Err(KandError::NaNDetected);
}
}
let new_highest = input_price.max(prev_highest);
let new_lowest = input_price.min(prev_lowest);
let midpoint = (new_highest + new_lowest) / 2.0;
Ok((midpoint, new_highest, new_lowest))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_midpoint_calculation() {
let input_price = 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_midpoint = vec![0.0; input_price.len()];
let mut output_highest = vec![0.0; input_price.len()];
let mut output_lowest = vec![0.0; input_price.len()];
midpoint(
&input_price,
param_period,
&mut output_midpoint,
&mut output_highest,
&mut output_lowest,
)
.unwrap();
for i in 0..13 {
assert!(output_midpoint[i].is_nan());
assert!(output_highest[i].is_nan());
assert!(output_lowest[i].is_nan());
}
let expected_values = [
35207.65, 35172.45, 35147.90, 35126.95, 35126.95, 35126.95, 35125.60, 35095.70,
35084.70, 35084.70, 35084.70, 35084.70,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_midpoint[i + 13], *expected, epsilon = 0.01);
}
let mut prev_highest = output_highest[13];
let mut prev_lowest = output_lowest[13];
for i in 14..19 {
let (midpoint, new_highest, new_lowest) =
midpoint_inc(input_price[i], prev_highest, prev_lowest, param_period).unwrap();
assert_relative_eq!(midpoint, output_midpoint[i], epsilon = 0.01);
prev_highest = new_highest;
prev_lowest = new_lowest;
}
}
}