use crate::core::defaults::{MAMA_FAST_LIMIT, MAMA_SLOW_LIMIT};
use crate::error::TaError;
struct Hilbert {
period_wma_sub: f64,
period_wma_sum: f64,
trailing_wma_value: f64,
smoothed_value: f64,
trailing_wma_idx: usize,
a: f64,
b: f64,
rad2deg: f64,
hilbert_idx: usize,
detrender_odd: [f64; 3],
detrender_even: [f64; 3],
detrender: f64,
prev_detrender_odd: f64,
prev_detrender_even: f64,
prev_detrender_input_odd: f64,
prev_detrender_input_even: f64,
q1_odd: [f64; 3],
q1_even: [f64; 3],
q1: f64,
prev_q1_odd: f64,
prev_q1_even: f64,
prev_q1_input_odd: f64,
prev_q1_input_even: f64,
ji_odd: [f64; 3],
ji_even: [f64; 3],
ji: f64,
prev_ji_odd: f64,
prev_ji_even: f64,
prev_ji_input_odd: f64,
prev_ji_input_even: f64,
jq_odd: [f64; 3],
jq_even: [f64; 3],
jq: f64,
prev_jq_odd: f64,
prev_jq_even: f64,
prev_jq_input_odd: f64,
prev_jq_input_even: f64,
period: f64,
q2: f64,
i2: f64,
prev_q2: f64,
prev_i2: f64,
re: f64,
im: f64,
i1_for_odd_prev2: f64,
i1_for_odd_prev3: f64,
i1_for_even_prev2: f64,
i1_for_even_prev3: f64,
prev_phase: f64,
mama: f64,
fama: f64,
smooth_period: f64,
smooth_price: [f64; 50],
smooth_price_idx: usize,
dc_phase: f64,
deg2rad: f64,
const_deg2rad_by360: f64,
sine: f64,
prev_sine: f64,
lead_sine: f64,
prev_lead_sine: f64,
days_in_trend: i32,
trend: i32,
prev_dc_phase: f64,
i_trend1: f64,
i_trend2: f64,
i_trend3: f64,
trendline: f64,
today_value: f64,
}
impl Hilbert {
fn new() -> Self {
Hilbert {
period_wma_sub: 0.0,
period_wma_sum: 0.0,
trailing_wma_value: 0.0,
smoothed_value: 0.0,
trailing_wma_idx: 0,
a: 0.0962,
b: 0.5769,
rad2deg: 180.0 / (4.0 * (1.0_f64).atan()),
hilbert_idx: 0,
detrender_odd: [0.0; 3],
detrender_even: [0.0; 3],
detrender: 0.0,
prev_detrender_odd: 0.0,
prev_detrender_even: 0.0,
prev_detrender_input_odd: 0.0,
prev_detrender_input_even: 0.0,
q1_odd: [0.0; 3],
q1_even: [0.0; 3],
q1: 0.0,
prev_q1_odd: 0.0,
prev_q1_even: 0.0,
prev_q1_input_odd: 0.0,
prev_q1_input_even: 0.0,
ji_odd: [0.0; 3],
ji_even: [0.0; 3],
ji: 0.0,
prev_ji_odd: 0.0,
prev_ji_even: 0.0,
prev_ji_input_odd: 0.0,
prev_ji_input_even: 0.0,
jq_odd: [0.0; 3],
jq_even: [0.0; 3],
jq: 0.0,
prev_jq_odd: 0.0,
prev_jq_even: 0.0,
prev_jq_input_odd: 0.0,
prev_jq_input_even: 0.0,
period: 0.0,
q2: 0.0,
i2: 0.0,
prev_q2: 0.0,
prev_i2: 0.0,
re: 0.0,
im: 0.0,
i1_for_odd_prev2: 0.0,
i1_for_odd_prev3: 0.0,
i1_for_even_prev2: 0.0,
i1_for_even_prev3: 0.0,
prev_phase: 0.0,
mama: 0.0,
fama: 0.0,
smooth_period: 0.0,
smooth_price: [0.0; 50],
smooth_price_idx: 0,
dc_phase: 0.0,
deg2rad: (1.0_f64).atan() / 45.0,
const_deg2rad_by360: (1.0_f64).atan() * 8.0,
sine: 0.0,
prev_sine: 0.0,
lead_sine: 0.0,
prev_lead_sine: 0.0,
days_in_trend: 0,
trend: 0,
prev_dc_phase: 0.0,
i_trend1: 0.0,
i_trend2: 0.0,
i_trend3: 0.0,
trendline: 0.0,
today_value: 0.0,
}
}
fn init(&mut self, values: &[f64], lookback_total: usize, wma_init_iters: usize) -> usize {
let start_idx = if 0 < lookback_total { lookback_total } else { 0 };
self.trailing_wma_idx = start_idx - lookback_total; let mut today = self.trailing_wma_idx;
let mut t = values[today];
today += 1;
self.period_wma_sub = t;
self.period_wma_sum = t;
t = values[today];
today += 1;
self.period_wma_sub += t;
self.period_wma_sum += t * 2.0;
t = values[today];
today += 1;
self.period_wma_sub += t;
self.period_wma_sum += t * 3.0;
self.trailing_wma_value = 0.0;
let mut i = wma_init_iters;
loop {
t = values[today];
today += 1;
self.period_wma_sub += t;
self.period_wma_sub -= self.trailing_wma_value;
self.period_wma_sum += t * 4.0;
self.trailing_wma_value = values[self.trailing_wma_idx];
self.trailing_wma_idx += 1;
self.smoothed_value = self.period_wma_sum * 0.1;
self.period_wma_sum -= self.period_wma_sub;
i -= 1;
if i == 0 {
break;
}
}
today }
fn step(&mut self, values: &[f64], today: usize, today_value: f64) -> f64 {
let adjusted_prev_period = 0.075 * self.period + 0.54;
self.today_value = today_value;
self.period_wma_sub += today_value;
self.period_wma_sub -= self.trailing_wma_value;
self.period_wma_sum += today_value * 4.0;
self.trailing_wma_value = values[self.trailing_wma_idx];
self.trailing_wma_idx += 1;
self.smoothed_value = self.period_wma_sum * 0.1;
self.period_wma_sum -= self.period_wma_sub;
let phase = if today % 2 == 0 {
let mut h = self.a * self.smoothed_value;
self.detrender = 0.0 - self.detrender_even[self.hilbert_idx];
self.detrender_even[self.hilbert_idx] = h;
self.detrender += h;
self.detrender -= self.prev_detrender_even;
self.prev_detrender_even = self.b * self.prev_detrender_input_even;
self.detrender += self.prev_detrender_even;
self.prev_detrender_input_even = self.smoothed_value;
self.detrender *= adjusted_prev_period;
h = self.a * self.detrender;
self.q1 = 0.0 - self.q1_even[self.hilbert_idx];
self.q1_even[self.hilbert_idx] = h;
self.q1 += h;
self.q1 -= self.prev_q1_even;
self.prev_q1_even = self.b * self.prev_q1_input_even;
self.q1 += self.prev_q1_even;
self.prev_q1_input_even = self.detrender;
self.q1 *= adjusted_prev_period;
h = self.a * self.i1_for_even_prev3;
self.ji = 0.0 - self.ji_even[self.hilbert_idx];
self.ji_even[self.hilbert_idx] = h;
self.ji += h;
self.ji -= self.prev_ji_even;
self.prev_ji_even = self.b * self.prev_ji_input_even;
self.ji += self.prev_ji_even;
self.prev_ji_input_even = self.i1_for_even_prev3;
self.ji *= adjusted_prev_period;
h = self.a * self.q1;
self.jq = 0.0 - self.jq_even[self.hilbert_idx];
self.jq_even[self.hilbert_idx] = h;
self.jq += h;
self.jq -= self.prev_jq_even;
self.prev_jq_even = self.b * self.prev_jq_input_even;
self.jq += self.prev_jq_even;
self.prev_jq_input_even = self.q1;
self.jq *= adjusted_prev_period;
self.hilbert_idx += 1;
if self.hilbert_idx == 3 {
self.hilbert_idx = 0;
}
self.q2 = 0.2 * (self.q1 + self.ji) + 0.8 * self.prev_q2;
self.i2 = 0.2 * (self.i1_for_even_prev3 - self.jq) + 0.8 * self.prev_i2;
self.i1_for_odd_prev3 = self.i1_for_odd_prev2;
self.i1_for_odd_prev2 = self.detrender;
let denom = self.i1_for_even_prev3;
if denom != 0.0 {
(self.q1 / denom).atan() * self.rad2deg
} else {
0.0
}
} else {
let mut h = self.a * self.smoothed_value;
self.detrender = 0.0 - self.detrender_odd[self.hilbert_idx];
self.detrender_odd[self.hilbert_idx] = h;
self.detrender += h;
self.detrender -= self.prev_detrender_odd;
self.prev_detrender_odd = self.b * self.prev_detrender_input_odd;
self.detrender += self.prev_detrender_odd;
self.prev_detrender_input_odd = self.smoothed_value;
self.detrender *= adjusted_prev_period;
h = self.a * self.detrender;
self.q1 = 0.0 - self.q1_odd[self.hilbert_idx];
self.q1_odd[self.hilbert_idx] = h;
self.q1 += h;
self.q1 -= self.prev_q1_odd;
self.prev_q1_odd = self.b * self.prev_q1_input_odd;
self.q1 += self.prev_q1_odd;
self.prev_q1_input_odd = self.detrender;
self.q1 *= adjusted_prev_period;
h = self.a * self.i1_for_odd_prev3;
self.ji = 0.0 - self.ji_odd[self.hilbert_idx];
self.ji_odd[self.hilbert_idx] = h;
self.ji += h;
self.ji -= self.prev_ji_odd;
self.prev_ji_odd = self.b * self.prev_ji_input_odd;
self.ji += self.prev_ji_odd;
self.prev_ji_input_odd = self.i1_for_odd_prev3;
self.ji *= adjusted_prev_period;
h = self.a * self.q1;
self.jq = 0.0 - self.jq_odd[self.hilbert_idx];
self.jq_odd[self.hilbert_idx] = h;
self.jq += h;
self.jq -= self.prev_jq_odd;
self.prev_jq_odd = self.b * self.prev_jq_input_odd;
self.jq += self.prev_jq_odd;
self.prev_jq_input_odd = self.q1;
self.jq *= adjusted_prev_period;
self.hilbert_idx += 1;
if self.hilbert_idx == 3 {
self.hilbert_idx = 0;
}
self.q2 = 0.2 * (self.q1 + self.ji) + 0.8 * self.prev_q2;
self.i2 = 0.2 * (self.i1_for_odd_prev3 - self.jq) + 0.8 * self.prev_i2;
self.i1_for_even_prev3 = self.i1_for_even_prev2;
self.i1_for_even_prev2 = self.detrender;
let denom = self.i1_for_odd_prev3;
if denom != 0.0 {
(self.q1 / denom).atan() * self.rad2deg
} else {
0.0
}
};
phase
}
fn update_period(&mut self) {
self.re = 0.8 * self.re + 0.2 * (self.i2 * self.prev_i2 + self.q2 * self.prev_q2);
self.im = 0.8 * self.im + 0.2 * (self.i2 * self.prev_q2 - self.q2 * self.prev_i2);
self.prev_q2 = self.q2;
self.prev_i2 = self.i2;
let temp_real = self.period;
if self.im != 0.0 && self.re != 0.0 {
self.period = 360.0 / ((self.im / self.re).atan() * self.rad2deg);
}
let hi = 1.5 * temp_real;
if self.period > hi {
self.period = hi;
}
let lo = 0.67 * temp_real;
if self.period < lo {
self.period = lo;
}
if self.period < 6.0 {
self.period = 6.0;
} else if self.period > 50.0 {
self.period = 50.0;
}
self.period = 0.2 * self.period + 0.8 * temp_real;
}
fn compute_dc_phase(&mut self) {
let dc_period = self.smooth_period + 0.5;
let dc_period_int = dc_period as i32;
let mut real_part = 0.0_f64;
let mut imag_part = 0.0_f64;
let mut idx = self.smooth_price_idx;
if dc_period_int > 0 {
let p = dc_period_int as f64;
let w = self.const_deg2rad_by360 / p; let cw = w.cos();
let sw = w.sin();
let mut s = 0.0_f64; let mut c = 1.0_f64; let mut i = 0_i32;
while i < dc_period_int {
let temp_real2 = self.smooth_price[idx];
real_part += s * temp_real2;
imag_part += c * temp_real2;
let ns = s * cw + c * sw; let nc = c * cw - s * sw; s = ns;
c = nc;
if idx == 0 {
idx = 49;
} else {
idx -= 1;
}
i += 1;
}
}
let temp_real = imag_part.abs();
if temp_real > 0.0 {
self.dc_phase = (real_part / imag_part).atan() * self.rad2deg;
} else if temp_real <= 0.01 {
if real_part < 0.0 {
self.dc_phase -= 90.0;
} else if real_part > 0.0 {
self.dc_phase += 90.0;
}
}
self.dc_phase += 90.0;
self.dc_phase += 360.0 / self.smooth_period;
if imag_part < 0.0 {
self.dc_phase += 180.0;
}
if self.dc_phase > 315.0 {
self.dc_phase -= 360.0;
}
}
fn advance_full(&mut self, values: &[f64], today: usize, today_value: f64) {
self.step(values, today, today_value);
self.update_period();
self.smooth_period = 0.67 * self.smooth_period + 0.33 * self.period;
self.smooth_price[self.smooth_price_idx] = self.smoothed_value;
self.compute_dc_phase();
self.prev_sine = self.sine;
self.prev_lead_sine = self.lead_sine;
self.sine = (self.dc_phase * self.deg2rad).sin();
self.lead_sine = ((self.dc_phase + 45.0) * self.deg2rad).sin();
self.smooth_price_idx += 1;
if self.smooth_price_idx > 49 {
self.smooth_price_idx = 0;
}
}
fn advance_period_only(&mut self, values: &[f64], today: usize, today_value: f64) {
self.step(values, today, today_value);
self.update_period();
self.smooth_period = 0.67 * self.smooth_period + 0.33 * self.period;
}
fn advance_trend(&mut self, values: &[f64], today: usize) {
self.trend = 1;
let crossed = (self.sine > self.lead_sine && self.prev_sine <= self.prev_lead_sine)
|| (self.sine < self.lead_sine && self.prev_sine >= self.prev_lead_sine);
if crossed {
self.days_in_trend = 0;
self.trend = 0;
}
self.days_in_trend += 1;
if (self.days_in_trend as f64) < 0.5 * self.smooth_period {
self.trend = 0;
}
let temp_real = self.dc_phase - self.prev_dc_phase;
if self.smooth_period != 0.0
&& temp_real > 0.67 * 360.0 / self.smooth_period
&& temp_real < 1.5 * 360.0 / self.smooth_period
{
self.trend = 0;
}
let dc_period = self.smooth_period + 0.5;
let dc_period_int = dc_period as i32;
let mut temp = 0.0_f64;
for j in 0..50 {
if j < dc_period_int && today >= j as usize {
temp += values[today - j as usize];
}
}
if dc_period_int > 0 {
temp /= dc_period_int as f64;
}
self.trendline =
(2.0 * self.i_trend2 + 4.0 * temp + 3.0 * self.i_trend1 + self.i_trend3) / 10.0;
self.i_trend3 = self.i_trend2;
self.i_trend2 = self.i_trend1;
self.i_trend1 = temp;
let latest_smooth = self.smoothed_value;
if self.trendline != 0.0
&& ((latest_smooth - self.trendline) / self.trendline).abs() >= 0.015
{
self.trend = 1;
}
self.prev_dc_phase = self.dc_phase;
}
}
pub struct Mama {
pub mama: Vec<f64>,
pub fama: Vec<f64>,
}
pub fn mama(values: &[f64], fast_limit: f64, slow_limit: f64) -> Result<Mama, TaError> {
if !(0.01..=0.99).contains(&fast_limit) {
return Err(TaError::BadParam(
"mama: fast_limit must be within [0.01, 0.99]".into(),
));
}
if !(0.01..=0.99).contains(&slow_limit) {
return Err(TaError::BadParam(
"mama: slow_limit must be within [0.01, 0.99]".into(),
));
}
let n = values.len();
let lookback = 32;
let mut out_mama = vec![f64::NAN; n];
let mut out_fama = vec![f64::NAN; n];
if n <= lookback {
return Ok(Mama {
mama: out_mama,
fama: out_fama,
});
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 9); let mut today = first_main;
while today <= n - 1 {
let phase = h.step(values, today, values[today]);
let delta_phase = h.prev_phase - phase;
h.prev_phase = phase;
let mut alpha = delta_phase;
if alpha < 1.0 {
alpha = 1.0;
}
if alpha > 1.0 {
alpha = fast_limit / alpha;
if alpha < slow_limit {
alpha = slow_limit;
}
} else {
alpha = fast_limit;
}
h.mama = (1.0 - alpha) * h.mama + alpha * h.today_value;
let alpha2 = alpha * 0.5;
h.fama = (1.0 - alpha2) * h.fama + alpha2 * h.mama;
h.update_period();
if today >= lookback {
out_mama[today] = h.mama;
out_fama[today] = h.fama;
}
today += 1;
}
Ok(Mama {
mama: out_mama,
fama: out_fama,
})
}
pub fn mama_default(values: &[f64]) -> Result<Mama, TaError> {
mama(values, MAMA_FAST_LIMIT, MAMA_SLOW_LIMIT)
}
pub fn ht_trendline(values: &[f64]) -> Result<Vec<f64>, TaError> {
let mut out = vec![f64::NAN; values.len()];
ht_trendline_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_trendline_with_output(values: &[f64], out: &mut [f64]) -> Result<(), TaError> {
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"ht_trendline_with_output: out length must equal values length".into(),
));
}
let lookback = 63;
if n <= lookback {
return Ok(());
}
let mut prefix = vec![0.0_f64; n];
if n > 0 {
prefix[0] = values[0];
for i in 1..n {
prefix[i] = prefix[i - 1] + values[i];
}
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 34); let mut today = first_main;
let mut i_trend1 = 0.0_f64;
let mut i_trend2 = i_trend1;
let mut i_trend3 = i_trend2;
let mut smooth_period = 0.0_f64;
while today <= n - 1 {
h.step(values, today, values[today]);
h.update_period();
smooth_period = 0.67 * smooth_period + 0.33 * h.period;
let dc_period = smooth_period + 0.5;
let dc_period_int = dc_period as i32; let k = if dc_period_int > 50 { 50 } else { dc_period_int };
let terms = if (today as i32) < k - 1 {
today as i32 + 1
} else {
k
} as usize;
let lo = today - terms + 1;
let mut sum = if lo > 0 {
prefix[today] - prefix[lo - 1]
} else {
prefix[today]
};
if dc_period_int > 0 {
sum /= dc_period_int as f64;
}
let trend = (2.0 * i_trend2 + 4.0 * sum + 3.0 * i_trend1 + i_trend3) / 10.0;
i_trend3 = i_trend2;
i_trend2 = i_trend1;
i_trend1 = sum;
if today >= lookback {
out[today] = trend;
}
today += 1;
}
Ok(())
}
pub fn ht_trendline_default(values: &[f64]) -> Result<Vec<f64>, TaError> {
ht_trendline(values)
}
pub fn ht_dcperiod(values: &[f64]) -> Result<Vec<f64>, TaError> {
let mut out = vec![f64::NAN; values.len()];
ht_dcperiod_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_dcperiod_with_output(values: &[f64], out: &mut [f64]) -> Result<(), TaError> {
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"ht_dcperiod_with_output: out length must equal values length".into(),
));
}
let lookback = 32;
if n <= lookback {
return Ok(());
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 9); let mut today = first_main;
while today <= n - 1 {
h.advance_period_only(values, today, values[today]);
if today >= lookback {
out[today] = h.smooth_period;
}
today += 1;
}
Ok(())
}
pub fn ht_dcperiod_default(values: &[f64]) -> Result<Vec<f64>, TaError> {
ht_dcperiod(values)
}
pub fn ht_dcphase(values: &[f64]) -> Result<Vec<f64>, TaError> {
let mut out = vec![f64::NAN; values.len()];
ht_dcphase_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_dcphase_with_output(values: &[f64], out: &mut [f64]) -> Result<(), TaError> {
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"ht_dcphase_with_output: out length must equal values length".into(),
));
}
let lookback = 63;
if n <= lookback {
return Ok(());
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 34); let mut today = first_main;
while today <= n - 1 {
h.advance_full(values, today, values[today]);
if today >= lookback {
out[today] = h.dc_phase;
}
today += 1;
}
Ok(())
}
pub fn ht_dcphase_default(values: &[f64]) -> Result<Vec<f64>, TaError> {
ht_dcphase(values)
}
pub struct HtPhasor {
pub in_phase: Vec<f64>,
pub quadrature: Vec<f64>,
}
pub fn ht_phasor(values: &[f64]) -> Result<HtPhasor, TaError> {
let n = values.len();
let mut out = HtPhasor {
in_phase: vec![f64::NAN; n],
quadrature: vec![f64::NAN; n],
};
ht_phasor_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_phasor_with_output(values: &[f64], out: &mut HtPhasor) -> Result<(), TaError> {
let n = values.len();
if out.in_phase.len() != n || out.quadrature.len() != n {
return Err(TaError::BadParam(
"ht_phasor_with_output: out field lengths must equal values length".into(),
));
}
let lookback = 32;
if n <= lookback {
return Ok(());
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 9); let mut today = first_main;
while today <= n - 1 {
let parity_even = today % 2 == 0;
h.step(values, today, values[today]);
h.update_period();
let quadrature = h.q1;
let in_phase = if parity_even {
h.i1_for_even_prev3
} else {
h.i1_for_odd_prev3
};
if today >= lookback {
out.in_phase[today] = in_phase;
out.quadrature[today] = quadrature;
}
today += 1;
}
Ok(())
}
pub fn ht_phasor_default(values: &[f64]) -> Result<HtPhasor, TaError> {
ht_phasor(values)
}
pub struct HtSine {
pub sine: Vec<f64>,
pub lead_sine: Vec<f64>,
}
pub fn ht_sine(values: &[f64]) -> Result<HtSine, TaError> {
let n = values.len();
let mut out = HtSine {
sine: vec![f64::NAN; n],
lead_sine: vec![f64::NAN; n],
};
ht_sine_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_sine_with_output(values: &[f64], out: &mut HtSine) -> Result<(), TaError> {
let n = values.len();
if out.sine.len() != n || out.lead_sine.len() != n {
return Err(TaError::BadParam(
"ht_sine_with_output: out field lengths must equal values length".into(),
));
}
let lookback = 63;
if n <= lookback {
return Ok(());
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 34); let mut today = first_main;
while today <= n - 1 {
h.advance_full(values, today, values[today]);
if today >= lookback {
out.sine[today] = h.sine;
out.lead_sine[today] = h.lead_sine;
}
today += 1;
}
Ok(())
}
pub fn ht_sine_default(values: &[f64]) -> Result<HtSine, TaError> {
ht_sine(values)
}
pub fn ht_trendmode(values: &[f64]) -> Result<Vec<f64>, TaError> {
let mut out = vec![0.0; values.len()];
ht_trendmode_with_output(values, &mut out)?;
Ok(out)
}
pub fn ht_trendmode_with_output(values: &[f64], out: &mut [f64]) -> Result<(), TaError> {
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"ht_trendmode_with_output: out length must equal values length".into(),
));
}
let lookback = 63;
if n <= lookback {
return Ok(());
}
let mut h = Hilbert::new();
let first_main = h.init(values, lookback, 34); let mut today = first_main;
while today <= n - 1 {
h.advance_full(values, today, values[today]);
h.advance_trend(values, today);
if today >= lookback {
out[today] = h.trend as f64;
}
today += 1;
}
Ok(())
}
pub fn ht_trendmode_default(values: &[f64]) -> Result<Vec<f64>, TaError> {
ht_trendmode(values)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mama_constant_input_converges() {
let v = vec![1.0; 200];
let out = mama_default(&v).unwrap();
assert_eq!(out.mama.len(), 200);
assert_eq!(out.fama.len(), 200);
for i in 0..32 {
assert!(out.mama[i].is_nan(), "mama[{i}] should be NaN");
assert!(out.fama[i].is_nan(), "fama[{i}] should be NaN");
}
for i in 100..200 {
assert!((out.mama[i] - 1.0).abs() < 1e-9, "mama[{i}] = {}", out.mama[i]);
assert!((out.fama[i] - 1.0).abs() < 1e-9, "fama[{i}] = {}", out.fama[i]);
}
}
#[test]
fn mama_invalid_limits() {
let v = vec![1.0; 200];
assert!(matches!(
mama(&v, 0.0, 0.05),
Err(TaError::BadParam(_))
));
assert!(matches!(
mama(&v, 0.5, 1.0),
Err(TaError::BadParam(_))
));
}
#[test]
fn mama_short_input_all_nan() {
let v = vec![1.0, 2.0, 3.0];
let out = mama_default(&v).unwrap();
assert!(out.mama.iter().all(|x| x.is_nan()));
assert!(out.fama.iter().all(|x| x.is_nan()));
}
#[test]
fn ht_trendline_constant_input() {
let v = vec![2.0; 200];
let out = ht_trendline_default(&v).unwrap();
assert_eq!(out.len(), 200);
for i in 0..63 {
assert!(out[i].is_nan(), "ht_trendline[{i}] should be NaN");
}
for i in 120..200 {
assert!((out[i] - 2.0).abs() < 1e-9, "ht_trendline[{i}] = {}", out[i]);
}
}
#[test]
fn ht_trendline_short_input_all_nan() {
let v = vec![1.0, 2.0, 3.0];
let out = ht_trendline_default(&v).unwrap();
assert!(out.iter().all(|x| x.is_nan()));
}
}