use crate::{TAFloat, error::KandError, helper::period_to_k};
pub const fn lookback(param_period: usize) -> Result<usize, KandError> {
#[cfg(feature = "check")]
{
if param_period < 2 {
return Err(KandError::InvalidParameter);
}
}
Ok(2 * (param_period - 1))
}
pub fn dema(
input: &[TAFloat],
param_period: usize,
output_dema: &mut [TAFloat],
output_ema1: &mut [TAFloat],
output_ema2: &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_dema.len() || len != output_ema1.len() || len != output_ema2.len() {
return Err(KandError::LengthMismatch);
}
}
#[cfg(feature = "deep-check")]
{
for price in input {
if price.is_nan() {
return Err(KandError::NaNDetected);
}
}
}
let alpha = period_to_k(param_period)?;
let mut sum = input[0];
for price in input.iter().take(param_period).skip(1) {
sum += *price;
}
let mut prev_ema1 = sum / param_period as TAFloat;
output_ema1[param_period - 1] = prev_ema1;
for i in param_period..len {
prev_ema1 = input[i].mul_add(alpha, prev_ema1 * (1.0 - alpha));
output_ema1[i] = prev_ema1;
}
let mut sum = output_ema1[param_period - 1];
for value in output_ema1
.iter()
.take(param_period * 2 - 1)
.skip(param_period)
{
sum += *value;
}
let mut prev_ema2 = sum / param_period as TAFloat;
output_ema2[param_period * 2 - 2] = prev_ema2;
for i in param_period * 2 - 1..len {
prev_ema2 = output_ema1[i].mul_add(alpha, prev_ema2 * (1.0 - alpha));
output_ema2[i] = prev_ema2;
}
for i in 0..len {
output_dema[i] = 2.0f64.mul_add(output_ema1[i], -output_ema2[i]);
}
for i in 0..lookback {
output_dema[i] = TAFloat::NAN;
output_ema1[i] = TAFloat::NAN;
output_ema2[i] = TAFloat::NAN;
}
Ok(())
}
pub fn dema_inc(
input_price: TAFloat,
prev_ema1: TAFloat,
prev_ema2: 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_ema1.is_nan() || prev_ema2.is_nan() {
return Err(KandError::NaNDetected);
}
}
let alpha = period_to_k(param_period)?;
let new_ema1 = input_price.mul_add(alpha, prev_ema1 * (1.0 - alpha));
let new_ema2 = new_ema1.mul_add(alpha, prev_ema2 * (1.0 - alpha));
let dema = 2.0f64.mul_add(new_ema1, -new_ema2);
Ok((dema, new_ema1, new_ema2))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn test_dema_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 = 5;
let mut output_dema = vec![0.0; input.len()];
let mut output_ema1 = vec![0.0; input.len()];
let mut output_ema2 = vec![0.0; input.len()];
dema(
&input,
param_period,
&mut output_dema,
&mut output_ema1,
&mut output_ema2,
)
.unwrap();
for value in output_dema.iter().take(8) {
assert!(value.is_nan());
}
let expected_values = [
35_218.829_037_037_04,
35_200.555_187_928_67,
35_185.338_456_332_87,
35_207.882_302_697_76,
35_210.681_534_115_734,
35_183.349_910_955_31,
35_129.574_755_000_09,
35_074.033_046_242_2,
35_022.421_948_322_895,
35_004.747_910_545_1,
35_028.743_014_805_514,
35_019.213_837_276_19,
];
for (i, expected) in expected_values.iter().enumerate() {
assert_relative_eq!(output_dema[i + 8], *expected, epsilon = 0.0001);
}
let mut prev_ema1 = output_ema1[9];
let mut prev_ema2 = output_ema2[9];
for i in 10..15 {
let (dema_val, new_ema1, new_ema2) =
dema_inc(input[i], prev_ema1, prev_ema2, param_period).unwrap();
assert_relative_eq!(dema_val, output_dema[i], epsilon = 0.0001);
assert_relative_eq!(new_ema1, output_ema1[i], epsilon = 0.0001);
assert_relative_eq!(new_ema2, output_ema2[i], epsilon = 0.0001);
prev_ema1 = new_ema1;
prev_ema2 = new_ema2;
}
}
}