use crate::{KandError, TAFloat, ta::ohlcv::ema};
pub const fn lookback(param_period: usize) -> Result<usize, KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
Ok(3 * (param_period - 1))
}
pub fn tema(
input: &[TAFloat],
param_period: usize,
output_tema: &mut [TAFloat],
output_ema1: &mut [TAFloat],
output_ema2: &mut [TAFloat],
output_ema3: &mut [TAFloat],
) -> Result<(), KandError> {
let len = input.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_tema.len()
|| len != output_ema1.len()
|| len != output_ema2.len()
|| len != output_ema3.len()
{
return Err(KandError::LengthMismatch);
}
}
#[cfg(feature = "deep-check")]
{
for value in input {
if value.is_nan() {
return Err(KandError::NaNDetected);
}
}
}
ema::ema(input, param_period, None, output_ema1)?;
ema::ema(
&output_ema1[param_period - 1..],
param_period,
None,
&mut output_ema2[param_period - 1..],
)?;
ema::ema(
&output_ema2[2 * (param_period - 1)..],
param_period,
None,
&mut output_ema3[2 * (param_period - 1)..],
)?;
for i in lookback..len {
output_tema[i] = 3.0f64.mul_add(output_ema1[i], -(3.0 * output_ema2[i])) + output_ema3[i];
}
for i in 0..lookback {
output_tema[i] = TAFloat::NAN;
output_ema1[i] = TAFloat::NAN;
output_ema2[i] = TAFloat::NAN;
output_ema3[i] = TAFloat::NAN;
}
Ok(())
}
pub fn tema_inc(
input: TAFloat,
prev_ema1: TAFloat,
prev_ema2: TAFloat,
prev_ema3: TAFloat,
param_period: usize,
) -> Result<(TAFloat, TAFloat, TAFloat, TAFloat), KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
#[cfg(feature = "deep-check")]
{
if input.is_nan() || prev_ema1.is_nan() || prev_ema2.is_nan() || prev_ema3.is_nan() {
return Err(KandError::NaNDetected);
}
}
let ema1 = ema::ema_inc(input, prev_ema1, param_period, None)?;
let ema2 = ema::ema_inc(ema1, prev_ema2, param_period, None)?;
let ema3 = ema::ema_inc(ema2, prev_ema3, param_period, None)?;
let tema = 3.0f64.mul_add(ema1, -(3.0 * ema2)) + ema3;
Ok((tema, ema1, ema2, ema3))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_tema_calculation() {
let input = 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,
];
let param_period = 3;
let mut output_tema = vec![0.0; input.len()];
let mut ema1 = vec![0.0; input.len()];
let mut ema2 = vec![0.0; input.len()];
let mut ema3 = vec![0.0; input.len()];
tema(
&input,
param_period,
&mut output_tema,
&mut ema1,
&mut ema2,
&mut ema3,
)
.unwrap();
for value in output_tema.iter().take(6) {
assert!(value.is_nan());
}
let expected_values = [
35_209.883_333_333_34,
35_245.566_666_666_68,
35_206.030_208_333_33,
35_184.880_729_166_66,
35_173.019_270_833_32,
35_220.059_635_416_67,
35_216.397_591_145_84,
35_168.941_569_010_41,
35_096.534_114_583_344,
35_039.869_694_010_4,
34_995.421_651_204_42,
35_003.259_470_621_76,
35_058.344_179_280_6,
35_033.424_372_355_13,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_tema[i + 6], *expected, epsilon = 0.0001);
}
let mut prev_ema1 = ema1[10];
let mut prev_ema2 = ema2[10];
let mut prev_ema3 = ema3[10];
for i in 11..15 {
let (tema_val, new_ema1, new_ema2, new_ema3) =
tema_inc(input[i], prev_ema1, prev_ema2, prev_ema3, param_period).unwrap();
assert_relative_eq!(tema_val, output_tema[i], epsilon = 0.0001);
assert_relative_eq!(new_ema1, ema1[i], epsilon = 0.0001);
assert_relative_eq!(new_ema2, ema2[i], epsilon = 0.0001);
assert_relative_eq!(new_ema3, ema3[i], epsilon = 0.0001);
prev_ema1 = new_ema1;
prev_ema2 = new_ema2;
prev_ema3 = new_ema3;
}
}
}