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 var(
input_prices: &[TAFloat],
param_period: usize,
output_var: &mut [TAFloat],
output_sum: &mut [TAFloat],
output_sum_sq: &mut [TAFloat],
) -> Result<(), KandError> {
let len = input_prices.len();
let lookback = lookback(param_period)?;
#[cfg(feature = "check")]
{
if len == 0 {
return Err(KandError::InvalidData);
}
if output_var.len() != len || output_sum.len() != len || output_sum_sq.len() != len {
return Err(KandError::LengthMismatch);
}
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
if len <= lookback {
return Err(KandError::InsufficientData);
}
}
#[cfg(feature = "deep-check")]
{
for price in input_prices {
if price.is_nan() {
return Err(KandError::NaNDetected);
}
}
}
let mut sum = 0.0;
let mut sum_sq = 0.0;
for val in input_prices.iter().take(param_period) {
sum += *val;
sum_sq += *val * *val;
}
let period_t = param_period as TAFloat;
let mean = sum / period_t;
output_var[lookback] = sum.mul_add(-mean, sum_sq) / period_t;
output_sum[lookback] = sum;
output_sum_sq[lookback] = sum_sq;
for i in param_period..len {
let old_val = input_prices[i - param_period];
let new_val = input_prices[i];
sum = sum - old_val + new_val;
sum_sq = new_val.mul_add(new_val, old_val.mul_add(-old_val, sum_sq));
let mean = sum / period_t;
output_var[i] = sum.mul_add(-mean, sum_sq) / period_t;
output_sum[i] = sum;
output_sum_sq[i] = sum_sq;
}
for i in 0..lookback {
output_var[i] = TAFloat::NAN;
output_sum[i] = TAFloat::NAN;
output_sum_sq[i] = TAFloat::NAN;
}
Ok(())
}
pub fn var_inc(
input_price: TAFloat,
prev_sum: TAFloat,
prev_sum_sq: TAFloat,
input_old_price: 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_sum.is_nan()
|| prev_sum_sq.is_nan()
|| input_old_price.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let new_sum = prev_sum - input_old_price + input_price;
let new_sum_sq = input_price.mul_add(
input_price,
input_old_price.mul_add(-input_old_price, prev_sum_sq),
);
let period_t = param_period as TAFloat;
let mean = new_sum / period_t;
let var = new_sum.mul_add(-mean, new_sum_sq) / period_t;
Ok((var, new_sum, new_sum_sq))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_var_calculation() {
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_var = vec![0.0; input_close.len()];
let mut output_sum = vec![0.0; input_close.len()];
let mut output_sum_sq = vec![0.0; input_close.len()];
var(
&input_close,
param_period,
&mut output_var,
&mut output_sum,
&mut output_sum_sq,
)
.unwrap();
for i in 0..13 {
assert!(output_var[i].is_nan());
assert!(output_sum[i].is_nan());
assert!(output_sum_sq[i].is_nan());
}
let expected_values = [
786.293_724_536_895_8,
1_610.155_357_122_421_3,
3_072.717_398_166_656_5,
5_255.849_234_580_994,
6_837.828_826_189_041,
7_280.654_081_583_023,
7_312.572_448_968_887,
9_261.126_785_755_157,
9_287.239_183_425_903,
8_900.911_019_802_094,
8_078.288_214_445_114,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_var[i + 13], *expected, epsilon = 0.0001);
}
let mut prev_sum = output_sum[13];
let mut prev_sum_sq = output_sum_sq[13];
for i in 14..19 {
let (var, new_sum, new_sum_sq) = var_inc(
input_close[i],
prev_sum,
prev_sum_sq,
input_close[i - param_period],
param_period,
)
.unwrap();
assert_relative_eq!(var, output_var[i], epsilon = 0.0001);
assert_relative_eq!(new_sum, output_sum[i], epsilon = 0.0001);
assert_relative_eq!(new_sum_sq, output_sum_sq[i], epsilon = 0.0001);
prev_sum = new_sum;
prev_sum_sq = new_sum_sq;
}
}
}