use super::{sma, typprice};
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 cci(
input_high: &[TAFloat],
input_low: &[TAFloat],
input_close: &[TAFloat],
param_period: usize,
output_cci: &mut [TAFloat],
output_tp: &mut [TAFloat],
output_tp_sma: &mut [TAFloat],
output_mean_dev: &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 != input_close.len()
|| len != output_cci.len()
|| len != output_tp.len()
|| len != output_tp_sma.len()
|| len != output_mean_dev.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() || input_close[i].is_nan() {
return Err(KandError::NaNDetected);
}
}
}
typprice::typprice(input_high, input_low, input_close, output_tp)?;
sma::sma(output_tp, param_period, output_tp_sma)?;
let factor = 0.015;
for i in lookback..len {
let mut mean_dev = 0.0;
for j in 0..param_period {
mean_dev += (output_tp[i - j] - output_tp_sma[i]).abs();
}
mean_dev /= param_period as TAFloat;
output_mean_dev[i] = mean_dev;
output_cci[i] = if mean_dev == 0.0 {
0.0
} else {
(output_tp[i] - output_tp_sma[i]) / (factor * mean_dev)
};
}
for i in 0..lookback {
output_cci[i] = TAFloat::NAN;
output_tp[i] = TAFloat::NAN;
output_tp_sma[i] = TAFloat::NAN;
output_mean_dev[i] = TAFloat::NAN;
}
Ok(())
}
pub fn cci_inc(
prev_sma_tp: TAFloat,
input_new_high: TAFloat,
input_new_low: TAFloat,
input_new_close: TAFloat,
input_old_high: TAFloat,
input_old_low: TAFloat,
input_old_close: TAFloat,
param_period: usize,
tp_buffer: &mut Vec<TAFloat>,
) -> Result<TAFloat, KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
#[cfg(feature = "deep-check")]
{
if prev_sma_tp.is_nan()
|| input_new_high.is_nan()
|| input_new_low.is_nan()
|| input_new_close.is_nan()
|| input_old_high.is_nan()
|| input_old_low.is_nan()
|| input_old_close.is_nan()
{
return Err(KandError::NaNDetected);
}
}
let new_tp = (input_new_high + input_new_low + input_new_close) / 3.0;
let old_tp = (input_old_high + input_old_low + input_old_close) / 3.0;
let sma_tp = sma::sma_inc(prev_sma_tp, new_tp, old_tp, param_period)?;
if tp_buffer.len() == param_period {
tp_buffer.remove(0);
}
tp_buffer.push(new_tp);
let mut mean_dev = 0.0;
for &tp in tp_buffer.iter() {
mean_dev += (tp - sma_tp).abs();
}
mean_dev /= param_period as TAFloat;
let factor = 0.015;
Ok(if mean_dev.abs() <= TAFloat::EPSILON {
0.0
} else {
(new_tp - sma_tp) / (factor * mean_dev)
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_cci_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_cci = vec![0.0; input_high.len()];
let mut output_tp = vec![0.0; input_high.len()];
let mut output_tp_sma = vec![0.0; input_high.len()];
let mut output_mean_dev = vec![0.0; input_high.len()];
cci(
&input_high,
&input_low,
&input_close,
param_period,
&mut output_cci,
&mut output_tp,
&mut output_tp_sma,
&mut output_mean_dev,
)
.unwrap();
for i in 0..13 {
assert!(output_cci[i].is_nan());
assert!(output_tp[i].is_nan());
assert!(output_tp_sma[i].is_nan());
assert!(output_mean_dev[i].is_nan());
}
let expected_values = [
-94.082_890_723_346_37,
-180.802_792_321_114_62,
-244.063_557_150_198_87,
-243.848_383_823_747_3,
-166.790_215_765_872_72,
-89.041_824_371_64,
-81.225_924_313_890_73,
-119.920_356_473_813_2,
-114.051_248_309_390_3,
-74.418_873_070_067_66,
-41.113_546_460_345_28,
7.295_737_949_004_944,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_cci[i + 13], *expected, epsilon = 0.0001);
}
let mut tp_buffer = Vec::with_capacity(param_period);
for i in 0..param_period {
let tp = (input_high[i] + input_low[i] + input_close[i]) / 3.0;
tp_buffer.push(tp);
}
for i in param_period..input_high.len() {
let result = cci_inc(
output_tp_sma[i - 1],
input_high[i],
input_low[i],
input_close[i],
input_high[i - param_period],
input_low[i - param_period],
input_close[i - param_period],
param_period,
&mut tp_buffer,
)
.unwrap();
assert_relative_eq!(result, output_cci[i], epsilon = 0.00001);
}
}
}