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 - 1)
}
pub fn midprice(
input_high: &[TAFloat],
input_low: &[TAFloat],
param_period: usize,
output_midprice: &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 <= lookback {
return Err(KandError::InsufficientData);
}
if len != input_low.len()
|| len != output_midprice.len()
|| len != output_highest_high.len()
|| len != output_lowest_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);
}
}
}
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;
output_midprice[i] = (highest_high + lowest_low) / 2.0;
}
for i in 0..lookback {
output_midprice[i] = TAFloat::NAN;
output_highest_high[i] = TAFloat::NAN;
output_lowest_low[i] = TAFloat::NAN;
}
Ok(())
}
pub fn midprice_inc(
input_high: TAFloat,
input_low: TAFloat,
prev_highest_high: TAFloat,
prev_lowest_low: 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_high.is_nan()
|| input_low.is_nan()
|| prev_highest_high.is_nan()
|| prev_lowest_low.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let new_highest_high = input_high.max(prev_highest_high);
let new_lowest_low = input_low.min(prev_lowest_low);
let midprice = (new_highest_high + new_lowest_low) / 2.0;
Ok((midprice, new_highest_high, new_lowest_low))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_midprice_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_midprice = 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()];
midprice(
&input_high,
&input_low,
param_period,
&mut output_midprice,
&mut output_highest_high,
&mut output_lowest_low,
)
.unwrap();
for i in 0..13 {
assert!(output_midprice[i].is_nan());
assert!(output_highest_high[i].is_nan());
assert!(output_lowest_low[i].is_nan());
}
let expected_values = [
35206.1, 35180.8, 35151.3, 35115.8, 35115.8, 35115.8, 35115.8, 35106.55, 35083.5,
35076.0, 35076.0, 35076.0,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_midprice[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 (midprice, new_highest_high, new_lowest_low) = midprice_inc(
input_high[i],
input_low[i],
prev_highest_high,
prev_lowest_low,
param_period,
)
.unwrap();
assert_relative_eq!(midprice, output_midprice[i], epsilon = 0.0001);
prev_highest_high = new_highest_high;
prev_lowest_low = new_lowest_low;
}
}
}