pub fn trix(closes: &[f64], period: usize) -> Vec<Option<f64>> {
if period == 0 || closes.len() < period * 3 {
return vec![None; closes.len()];
}
fn ema(data: &[f64], period: usize) -> Vec<f64> {
let mut ema = Vec::with_capacity(data.len());
let alpha = 2.0 / (period as f64 + 1.0);
let mut prev_ema = data[0];
for &price in data.iter() {
prev_ema = alpha * price + (1.0 - alpha) * prev_ema;
ema.push(prev_ema);
}
ema
}
let ema1 = ema(closes, period);
let ema2 = ema(&ema1, period);
let ema3 = ema(&ema2, period);
let mut trix = vec![None; closes.len()];
for i in 1..closes.len() {
if i >= period * 3 - 2 {
let prev = ema3[i - 1];
let curr = ema3[i];
if prev != 0.0 {
trix[i] = Some((curr - prev) / prev * 100.0);
} else {
trix[i] = Some(0.0);
}
}
}
trix
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trix_basic() {
let closes: Vec<f64> = (1..=30).map(|x| x as f64).collect();
let period = 15;
let trix_values = trix(&closes, period);
assert_eq!(trix_values.len(), closes.len());
assert!(trix_values.iter().take(period * 3 - 2).all(|v| v.is_none()));
}
#[test]
fn test_trix_short_input() {
let closes = vec![1.0, 2.0, 3.0];
let period = 15;
let trix_values = trix(&closes, period);
assert_eq!(trix_values, vec![None, None, None]);
}
}