use crate::core::defaults::{ACCBANDS_PERIOD, DEFAULT_TIME_PERIOD, MAVP_MAX_PERIOD, MAVP_MIN_PERIOD, T3_VFACTOR};
use crate::core::{rolling_max, rolling_mean, rolling_mean_skip, rolling_min};
use crate::error::{check_period, TaError};
use crate::indicator::indicator;
indicator! {
fn sma(values: &[f64], time_period: usize) -> Vec<f64> with sma_with_output
default sma_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn sma_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"sma_with_output: out length must equal values length".into(),
));
}
let res = rolling_mean(values, time_period);
out.copy_from_slice(&res);
Ok(())
}
indicator! {
fn ema(values: &[f64], time_period: usize) -> Vec<f64> with ema_with_output
default ema_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn ema_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"ema_with_output: out length must equal values length".into(),
));
}
crate::core::ema_with_output(values, time_period, out);
Ok(())
}
indicator! {
fn wma(values: &[f64], time_period: usize) -> Vec<f64> with wma_with_output
default wma_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn wma_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"wma_with_output: out length must equal values length".into(),
));
}
crate::core::wma_with_output(values, time_period, out);
Ok(())
}
pub fn dema_with_output(values: &[f64], time_period: usize, out: &mut [f64]) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"dema_with_output: out length must equal values length".into(),
));
}
crate::core::nested_ema_with_output::<2, _>(values, time_period, |e| 2.0 * e[0] - e[1], out);
Ok(())
}
indicator! {
fn dema(values: &[f64], time_period: usize) -> Vec<f64> with dema_with_output
default dema_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn tema_with_output(values: &[f64], time_period: usize, out: &mut [f64]) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"tema_with_output: out length must equal values length".into(),
));
}
crate::core::nested_ema_with_output::<3, _>(values, time_period, |e| 3.0 * e[0] - 3.0 * e[1] + e[2], out);
Ok(())
}
indicator! {
fn tema(values: &[f64], time_period: usize) -> Vec<f64> with tema_with_output
default tema_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
indicator! {
fn midpoint(values: &[f64], time_period: usize) -> Vec<f64> with midpoint_with_output
default midpoint_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn midpoint_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"midpoint_with_output: out length must equal values length".into(),
));
}
#[cfg(feature = "parallel")]
{
if values.len() >= 8192 {
return midpoint_parallel_with_output(values, time_period, out);
}
}
midpoint_serial_with_output(values, time_period, out)
}
fn midpoint_serial_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
let (mx, mn) = crate::core::rolling_minmax(values, time_period);
for (i, (a, b)) in mx.iter().zip(&mn).enumerate() {
out[i] = (a + b) / 2.0;
}
Ok(())
}
pub fn midpoint_serial(values: &[f64], time_period: usize) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
let mut out = vec![f64::NAN; values.len()];
midpoint_serial_with_output(values, time_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
pub fn midpoint_parallel(values: &[f64], time_period: usize) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
let mut out = vec![f64::NAN; values.len()];
midpoint_parallel_with_output(values, time_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
fn midpoint_parallel_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"midpoint_parallel_with_output: out length must equal values length".into(),
));
}
let period = time_period;
crate::parallel::parallel_index_map(values.len(), period - 1, out, |start, end| {
let mut local = vec![f64::NAN; end - start];
let _ = midpoint_serial_with_output(&values[start..end], period, &mut local);
local
});
Ok(())
}
indicator! {
fn midprice(high: &[f64], low: &[f64], time_period: usize) -> Vec<f64> with midprice_with_output;
}
pub fn midprice_with_output(
high: &[f64],
low: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if high.len() != low.len() {
return Err(TaError::BadParam(
"high and low must have equal length".into(),
));
}
if out.len() != high.len() {
return Err(TaError::BadParam(
"midprice_with_output: out length must equal high/low length".into(),
));
}
let mx = rolling_max(high, time_period);
let mn = rolling_min(low, time_period);
for (i, (a, b)) in mx.iter().zip(&mn).enumerate() {
out[i] = (a + b) / 2.0;
}
Ok(())
}
pub fn midprice_default(high: &[f64], low: &[f64]) -> Result<Vec<f64>, TaError> {
midprice(high, low, DEFAULT_TIME_PERIOD)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum MaType {
Sma = 0,
Ema = 1,
Wma = 2,
Dema = 3,
Tema = 4,
Trima = 5,
Kama = 6,
Mama = 7,
}
fn ma_lookback(period: usize, ma_type: MaType) -> usize {
match ma_type {
MaType::Sma | MaType::Ema | MaType::Wma | MaType::Trima => period - 1,
MaType::Dema => 2 * (period - 1),
MaType::Tema => 3 * (period - 1),
MaType::Kama => period,
MaType::Mama => 32,
}
}
pub fn bbands(
values: &[f64],
time_period: usize,
nb_dev_up: f64,
nb_dev_dn: f64,
ma_type: MaType,
) -> Result<Bbands, TaError> {
let n = values.len();
let mut out = Bbands {
upper: vec![f64::NAN; n],
middle: vec![f64::NAN; n],
lower: vec![f64::NAN; n],
};
bbands_with_output(values, time_period, nb_dev_up, nb_dev_dn, ma_type, &mut out)?;
Ok(out)
}
pub fn bbands_with_output(
values: &[f64],
time_period: usize,
nb_dev_up: f64,
nb_dev_dn: f64,
ma_type: MaType,
out: &mut Bbands,
) -> Result<(), TaError> {
if time_period < 2 {
return Err(TaError::BadParam(
"bbands: time_period must be >= 2".into(),
));
}
let n = values.len();
if out.upper.len() != n || out.middle.len() != n || out.lower.len() != n {
return Err(TaError::BadParam(
"bbands_with_output: out bands must have length == values length".into(),
));
}
if ma_type == MaType::Sma {
let (middle, var) = crate::core::rolling_mean_var(values, time_period);
for i in 0..n {
out.middle[i] = middle[i];
if var[i].is_nan() {
out.upper[i] = f64::NAN;
out.lower[i] = f64::NAN;
} else {
let sd = var[i].sqrt();
out.upper[i] = middle[i] + nb_dev_up * sd;
out.lower[i] = middle[i] - nb_dev_dn * sd;
}
}
} else {
let middle = ma(values, time_period, ma_type)?;
let sd = crate::stat::stddev(values, time_period, 1.0)?;
for i in 0..n {
out.middle[i] = middle[i];
if middle[i].is_nan() || sd[i].is_nan() {
out.upper[i] = f64::NAN;
out.lower[i] = f64::NAN;
} else {
out.upper[i] = middle[i] + nb_dev_up * sd[i];
out.lower[i] = middle[i] - nb_dev_dn * sd[i];
}
}
}
Ok(())
}
pub struct Bbands {
pub upper: Vec<f64>,
pub middle: Vec<f64>,
pub lower: Vec<f64>,
}
pub fn bbands_default(values: &[f64]) -> Result<Bbands, TaError> {
use crate::core::defaults::{BBANDS_NB_DEV_DN, BBANDS_NB_DEV_UP, BBANDS_PERIOD};
bbands(
values,
BBANDS_PERIOD,
BBANDS_NB_DEV_UP,
BBANDS_NB_DEV_DN,
MaType::Sma,
)
}
pub fn accbands(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
) -> Result<AccBands, TaError> {
check_period(period)?;
let n = high.len();
let mut out = AccBands {
upper: vec![f64::NAN; n],
middle: vec![f64::NAN; n],
lower: vec![f64::NAN; n],
};
accbands_with_output(high, low, close, period, &mut out)?;
Ok(out)
}
pub fn accbands_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
out: &mut AccBands,
) -> Result<(), TaError> {
check_period(period)?;
if period < 2 {
return Err(TaError::BadParam("accbands: period must be >= 2".into()));
}
let n = high.len();
if n != low.len() || n != close.len() {
return Err(TaError::BadParam(
"accbands: high/low/close must share the same length".into(),
));
}
if out.upper.len() != n || out.middle.len() != n || out.lower.len() != n {
return Err(TaError::BadParam(
"accbands_with_output: out bands must have length == input length".into(),
));
}
let mut upper_map = vec![0.0_f64; n];
let mut lower_map = vec![0.0_f64; n];
for i in 0..n {
let hl = high[i] + low[i];
if hl.abs() >= 1e-8 {
let k = 4.0 * (high[i] - low[i]) / hl;
upper_map[i] = high[i] * (1.0 + k);
lower_map[i] = low[i] * (1.0 - k);
} else {
upper_map[i] = high[i];
lower_map[i] = low[i];
}
}
let upper = rolling_mean(&upper_map, period);
let middle = rolling_mean(close, period);
let lower = rolling_mean(&lower_map, period);
out.upper = upper;
out.middle = middle;
out.lower = lower;
Ok(())
}
pub struct AccBands {
pub upper: Vec<f64>,
pub middle: Vec<f64>,
pub lower: Vec<f64>,
}
pub fn accbands_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<AccBands, TaError> {
accbands(high, low, close, ACCBANDS_PERIOD)
}
indicator! {
fn trima(values: &[f64], time_period: usize) -> Vec<f64> with trima_with_output
default trima_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn trima_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"trima_with_output: out length must equal values length".into(),
));
}
if n < time_period {
return Ok(());
}
let (p1, p2) = if time_period % 2 == 1 {
let h = (time_period + 1) / 2;
(h, h)
} else {
let h = time_period / 2;
(h, h + 1)
};
let inner = rolling_mean(values, p1);
let res = rolling_mean_skip(&inner, p2);
out.copy_from_slice(&res);
Ok(())
}
pub fn t3_with_output(
values: &[f64],
time_period: usize,
v_factor: f64,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"t3_with_output: out length must equal values length".into(),
));
}
let v = v_factor;
let c1 = -v * v * v;
let c2 = 3.0 * (v * v - c1);
let c3 = -6.0 * v * v - 3.0 * (v - c1);
let c4 = (3.0 * v * v + 3.0 * v + 1.0) - c1;
crate::core::nested_ema_with_output::<6, _>(
values,
time_period,
|e| c1 * e[5] + c2 * e[4] + c3 * e[3] + c4 * e[2],
out,
);
Ok(())
}
indicator! {
fn t3(values: &[f64], time_period: usize, v_factor: f64) -> Vec<f64> with t3_with_output
default t3_default(values: &[f64]) => (5, T3_VFACTOR)
;
}
indicator! {
fn ma(values: &[f64], time_period: usize, ma_type: MaType) -> Vec<f64> with ma_with_output
default ma_default(values: &[f64]) => (DEFAULT_TIME_PERIOD, MaType::Sma)
;
}
pub fn ma_with_output(
values: &[f64],
time_period: usize,
ma_type: MaType,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"ma_with_output: out length must equal values length".into(),
));
}
let res = match ma_type {
MaType::Sma => sma(values, time_period)?,
MaType::Ema => ema(values, time_period)?,
MaType::Wma => wma(values, time_period)?,
MaType::Dema => dema(values, time_period)?,
MaType::Tema => tema(values, time_period)?,
MaType::Trima => trima(values, time_period)?,
MaType::Kama => kama(values, time_period)?,
MaType::Mama => crate::cycle::mama_default(values)?.mama,
};
out.copy_from_slice(&res);
Ok(())
}
indicator! {
fn mavp(
values: &[f64],
periods: &[f64],
min_period: usize,
max_period: usize,
ma_type: MaType,
) -> Vec<f64> with mavp_with_output
default mavp_default(values: &[f64], periods: &[f64]) => (MAVP_MIN_PERIOD, MAVP_MAX_PERIOD, MaType::Sma)
;
}
pub fn mavp_with_output(
values: &[f64],
periods: &[f64],
min_period: usize,
max_period: usize,
ma_type: MaType,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(min_period)?;
check_period(max_period)?;
if min_period > max_period {
return Err(TaError::BadParam(
"mavp: min_period must be <= max_period".into(),
));
}
let n = values.len();
if periods.len() != n {
return Err(TaError::BadParam(
"mavp: periods must have the same length as values".into(),
));
}
if out.len() != n {
return Err(TaError::BadParam(
"mavp_with_output: out length must equal values length".into(),
));
}
out.fill(f64::NAN);
let mut p: Vec<usize> = Vec::with_capacity(n);
for &x in periods {
let mut v = x.trunc() as i64;
if v < min_period as i64 {
v = min_period as i64;
} else if v > max_period as i64 {
v = max_period as i64;
}
if v < 1 {
v = 1;
}
p.push(v as usize);
}
let lookback = ma_lookback(max_period, ma_type);
let mut distinct: Vec<usize> = p.iter().copied().collect();
distinct.sort_unstable();
distinct.dedup();
for &pp in &distinct {
let ma_series = ma(values, pp, ma_type)?;
for i in lookback..n {
if p[i] == pp {
out[i] = ma_series[i];
}
}
}
Ok(())
}
indicator! {
fn kama(values: &[f64], time_period: usize) -> Vec<f64> with kama_with_output
default kama_default(values: &[f64]) => (DEFAULT_TIME_PERIOD)
;
}
pub fn kama_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"kama_with_output: out length must equal values length".into(),
));
}
out.fill(f64::NAN);
if time_period == 1 {
out.copy_from_slice(values);
return Ok(());
}
let p = time_period;
if n <= p {
return Ok(());
}
let const_max = 2.0 / 31.0;
let const_diff = 2.0 / 3.0 - const_max;
let mut sum_roc1 = 0.0_f64;
let mut today = 0usize;
let mut trailing_idx = 0usize;
let mut i = p;
while i > 0 {
i -= 1;
let tr = values[today];
let diff = tr - values[today + 1];
sum_roc1 += diff.abs();
today += 1;
}
let mut prev_kama = values[today - 1];
let temp = values[today];
let temp2 = values[trailing_idx];
trailing_idx += 1;
let period_roc = temp - temp2;
let mut trailing_value = temp2;
let er = if sum_roc1 <= period_roc || sum_roc1.abs() < 1e-14 {
1.0
} else {
(period_roc / sum_roc1).abs()
};
let mut sc = er * const_diff + const_max;
sc *= sc;
prev_kama = (values[today] - prev_kama).mul_add(sc, prev_kama);
today += 1;
out[p] = prev_kama;
while today <= n - 1 {
let temp = values[today];
let temp2 = values[trailing_idx];
trailing_idx += 1;
let period_roc = temp - temp2;
sum_roc1 -= (trailing_value - temp2).abs();
sum_roc1 += (temp - values[today - 1]).abs();
trailing_value = temp2;
let er = if sum_roc1 <= period_roc || sum_roc1.abs() < 1e-14 {
1.0
} else {
(period_roc / sum_roc1).abs()
};
let mut sc = er * const_diff + const_max;
sc *= sc;
prev_kama = (values[today] - prev_kama).mul_add(sc, prev_kama);
out[today] = prev_kama;
today += 1;
}
Ok(())
}
indicator! {
fn sar(high: &[f64], low: &[f64], acceleration: f64, maximum: f64) -> Vec<f64>
with sar_with_output;
}
pub fn sar_with_output(
high: &[f64],
low: &[f64],
acceleration: f64,
maximum: f64,
out: &mut [f64],
) -> Result<(), TaError> {
if high.len() != low.len() {
return Err(TaError::BadParam("sar: high and low must have equal length".into()));
}
let n = high.len();
if out.len() != n {
return Err(TaError::BadParam(
"sar_with_output: out length must equal high/low length".into(),
));
}
if n < 2 {
return Ok(());
}
let mut af = acceleration;
if af > maximum {
af = maximum;
}
let up_move = high[1] - high[0];
let down_move = low[0] - low[1];
let mut is_long = !(down_move > up_move);
let mut today = 1usize;
let mut new_high = high[0];
let mut new_low = low[0];
let (mut ep, mut sar);
if is_long {
ep = high[1];
sar = new_low;
} else {
ep = low[1];
sar = new_high;
}
new_low = low[1];
new_high = high[1];
while today <= n - 1 {
let bar = today;
let prev_low = new_low;
let prev_high = new_high;
new_low = low[today];
new_high = high[today];
today += 1;
if is_long {
if new_low <= sar {
is_long = false;
sar = ep;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
out[bar] = sar;
af = acceleration;
ep = new_low;
sar = af * (ep - sar) + sar;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
} else {
out[bar] = sar;
if new_high > ep {
ep = new_high;
af += acceleration;
if af > maximum {
af = maximum;
}
}
sar = af * (ep - sar) + sar;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
}
} else if new_high >= sar {
is_long = true;
sar = ep;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
out[bar] = sar;
af = acceleration;
ep = new_high;
sar = af * (ep - sar) + sar;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
} else {
out[bar] = sar;
if new_low < ep {
ep = new_low;
af += acceleration;
if af > maximum {
af = maximum;
}
}
sar = af * (ep - sar) + sar;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
}
}
Ok(())
}
pub fn sar_default(high: &[f64], low: &[f64]) -> Result<Vec<f64>, TaError> {
use crate::core::defaults::{SAR_ACCELERATION, SAR_MAX};
sar(high, low, SAR_ACCELERATION, SAR_MAX)
}
indicator! {
fn sarext(
high: &[f64],
low: &[f64],
start_value: f64,
offset_on_reverse: f64,
accel_init_long: f64,
accel_long: f64,
accel_max_long: f64,
accel_init_short: f64,
accel_short: f64,
accel_max_short: f64,
) -> Vec<f64> with sarext_with_output;
}
pub fn sarext_with_output(
high: &[f64],
low: &[f64],
start_value: f64,
offset_on_reverse: f64,
accel_init_long: f64,
accel_long: f64,
accel_max_long: f64,
accel_init_short: f64,
accel_short: f64,
accel_max_short: f64,
out: &mut [f64],
) -> Result<(), TaError> {
if high.len() != low.len() {
return Err(TaError::BadParam(
"sarext: high and low must have equal length".into(),
));
}
let n = high.len();
if out.len() != n {
return Err(TaError::BadParam(
"sarext_with_output: out length must equal high/low length".into(),
));
}
if n < 2 {
return Ok(());
}
let mut af_long = accel_init_long;
let mut af_short = accel_init_short;
if af_long > accel_max_long {
af_long = accel_max_long;
}
if af_short > accel_max_short {
af_short = accel_max_short;
}
let mut is_long: bool;
if start_value == 0.0 {
let up_move = high[1] - high[0];
let down_move = low[0] - low[1];
is_long = !(down_move > up_move);
} else if start_value > 0.0 {
is_long = true;
} else {
is_long = false;
}
let mut today = 1usize;
let mut new_high = high[0];
let mut new_low = low[0];
let (mut ep, mut sar);
if start_value == 0.0 {
if is_long {
ep = high[1];
sar = new_low;
} else {
ep = low[1];
sar = new_high;
}
} else if start_value > 0.0 {
ep = high[1];
sar = start_value;
} else {
ep = low[1];
sar = start_value.abs();
}
new_low = low[1];
new_high = high[1];
while today <= n - 1 {
let bar = today;
let prev_low = new_low;
let prev_high = new_high;
new_low = low[today];
new_high = high[today];
today += 1;
if is_long {
if new_low <= sar {
is_long = false;
sar = ep;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
if offset_on_reverse != 0.0 {
sar += sar * offset_on_reverse;
}
out[bar] = 0.0 - sar;
af_short = accel_init_short;
ep = new_low;
sar = af_short * (ep - sar) + sar;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
} else {
out[bar] = sar;
if new_high > ep {
ep = new_high;
af_long += accel_long;
if af_long > accel_max_long {
af_long = accel_max_long;
}
}
sar = af_long * (ep - sar) + sar;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
}
} else if new_high >= sar {
is_long = true;
sar = ep;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
if offset_on_reverse != 0.0 {
sar -= sar * offset_on_reverse;
}
out[bar] = sar;
af_long = accel_init_long;
ep = new_high;
sar = af_long * (ep - sar) + sar;
if sar > prev_low {
sar = prev_low;
}
if sar > new_low {
sar = new_low;
}
} else {
out[bar] = 0.0 - sar;
if new_low < ep {
ep = new_low;
af_short += accel_short;
if af_short > accel_max_short {
af_short = accel_max_short;
}
}
sar = af_short * (ep - sar) + sar;
if sar < prev_high {
sar = prev_high;
}
if sar < new_high {
sar = new_high;
}
}
}
Ok(())
}
pub fn sarext_default(high: &[f64], low: &[f64]) -> Result<Vec<f64>, TaError> {
use crate::core::defaults::*;
sarext(
high,
low,
SAREXT_START_VALUE,
SAREXT_OFFSET_ON_REVERSE,
SAREXT_ACCEL_INIT_LONG,
SAREXT_ACCEL_LONG,
SAREXT_ACCEL_MAX_LONG,
SAREXT_ACCEL_INIT_SHORT,
SAREXT_ACCEL_SHORT,
SAREXT_ACCEL_MAX_SHORT,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sma_basic() {
let v = [1.0, 2.0, 3.0, 4.0, 5.0];
let out = sma(&v, 3).unwrap();
assert!(out[0].is_nan());
assert!(out[1].is_nan());
assert!((out[2] - 2.0).abs() < 1e-12);
assert!((out[3] - 3.0).abs() < 1e-12);
assert!((out[4] - 4.0).abs() < 1e-12);
}
#[test]
fn sma_period_zero_is_error() {
assert!(matches!(sma(&[1.0, 2.0], 0), Err(TaError::BadParam(_))));
}
#[test]
fn sma_short_input_all_nan() {
let out = sma(&[1.0, 2.0], 5).unwrap();
assert!(out.iter().all(|x| x.is_nan()));
}
#[test]
fn ema_seed_and_recursion() {
let v = [1.0, 2.0, 4.0, 8.0, 16.0];
let out = ema(&v, 3).unwrap();
assert!(out[0].is_nan() && out[1].is_nan());
assert!((out[2] - 7.0 / 3.0).abs() < 1e-12);
assert!((out[3] - 31.0 / 6.0).abs() < 1e-12);
}
#[test]
fn wma_weights() {
let v = [1.0, 2.0, 4.0];
let out = wma(&v, 3).unwrap();
assert!((out[2] - 17.0 / 6.0).abs() < 1e-12);
}
#[test]
fn dema_first_valid_index() {
let v: Vec<f64> = (0..12).map(|i| 2f64.powi(i)).collect();
let out = dema(&v, 3).unwrap();
let lookback = 2 * (3 - 1);
for i in 0..lookback {
assert!(out[i].is_nan(), "index {i} should be NaN");
}
assert!(!out[lookback].is_nan());
}
#[test]
fn tema_first_valid_index() {
let v: Vec<f64> = (0..14).map(|i| 2f64.powi(i)).collect();
let out = tema(&v, 3).unwrap();
let lookback = 3 * (3 - 1);
for i in 0..lookback {
assert!(out[i].is_nan(), "index {i} should be NaN");
}
assert!(!out[lookback].is_nan());
}
#[test]
fn midpoint_min_max_half() {
let v = [1.0, 5.0, 3.0, 9.0, 2.0];
let out = midpoint(&v, 3).unwrap();
assert!(out[0].is_nan() && out[1].is_nan());
assert!((out[2] - (5.0 + 1.0) / 2.0).abs() < 1e-12);
assert!((out[3] - (9.0 + 3.0) / 2.0).abs() < 1e-12);
}
#[test]
fn midprice_high_low() {
let high = [5.0, 6.0, 7.0, 8.0];
let low = [1.0, 2.0, 3.0, 4.0];
let out = midprice(&high, &low, 3).unwrap();
assert!(out[0].is_nan() && out[1].is_nan());
assert!((out[2] - (7.0 + 1.0) / 2.0).abs() < 1e-12);
}
#[test]
fn midprice_length_mismatch_is_error() {
assert!(matches!(
midprice(&[1.0, 2.0], &[1.0], 2),
Err(TaError::BadParam(_))
));
}
}