use crate::core::defaults::{BETA_PERIOD, CORREL_PERIOD, LINEARREG_PERIOD, STDDEV_NB_DEV, STDDEV_PERIOD};
use crate::core::{check_eq_len, rolling_var};
use crate::error::{check_period, TaError};
use crate::indicator::indicator;
indicator! {
fn stddev(values: &[f64], time_period: usize, nb_dev: f64) -> Vec<f64> with stddev_with_output
default stddev_default(values: &[f64]) => (STDDEV_PERIOD, STDDEV_NB_DEV)
;
}
pub fn stddev_with_output(
values: &[f64],
time_period: usize,
nb_dev: f64,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"stddev_with_output: out length must equal values length".into(),
));
}
let var = rolling_var(values, time_period);
let n = values.len();
for i in 0..n {
if !var[i].is_nan() {
out[i] = nb_dev * var[i].sqrt();
}
}
Ok(())
}
indicator! {
fn var(values: &[f64], time_period: usize, _nb_dev: f64) -> Vec<f64> with var_with_output
default var_default(values: &[f64]) => (STDDEV_PERIOD, STDDEV_NB_DEV)
;
}
pub fn var_with_output(
values: &[f64],
time_period: usize,
_nb_dev: f64,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"var_with_output: out length must equal values length".into(),
));
}
let temp = rolling_var(values, time_period);
out.copy_from_slice(&temp);
Ok(())
}
#[cfg(test)]
fn linreg_core(values: &[f64], period: usize, mode: u8) -> Vec<f64> {
let mut out = vec![f64::NAN; values.len()];
linreg_core_with_output(values, period, mode, &mut out);
out
}
fn linreg_core_with_output(values: &[f64], period: usize, mode: u8, out: &mut [f64]) {
let n = values.len();
if n < period {
return;
}
let p = period as f64;
let sx = (period * (period - 1)) as f64 / 2.0; let sxx = (period * (period - 1) * (2 * period - 1)) as f64 / 6.0; let denom = p * sxx - sx * sx;
let mut sy = 0.0_f64;
let mut sxy = 0.0_f64;
for k in 0..period {
let x = values[k];
sy += x;
sxy += (k as f64) * x;
}
let slope = (p * sxy - sx * sy) / denom;
let intercept = (sy - slope * sx) / p;
out[period - 1] = match mode {
1 => slope.atan() * 180.0 / std::f64::consts::PI, 2 => intercept, 3 => slope, 4 => intercept + slope * p, _ => intercept + slope * (p - 1.0), };
for i in period..n {
sy = sy + values[i] - values[i - period];
sxy = sxy + (period as f64) * values[i] - sy;
let slope = (p * sxy - sx * sy) / denom;
let intercept = (sy - slope * sx) / p;
out[i] = match mode {
1 => slope.atan() * 180.0 / std::f64::consts::PI,
2 => intercept,
3 => slope,
4 => intercept + slope * p,
_ => intercept + slope * (p - 1.0),
};
}
}
indicator! {
fn linear_reg(values: &[f64], time_period: usize) -> Vec<f64> with linear_reg_with_output
default linear_reg_default(values: &[f64]) => (LINEARREG_PERIOD)
;
}
indicator! {
fn linear_reg_angle(values: &[f64], time_period: usize) -> Vec<f64> with linear_reg_angle_with_output
default linear_reg_angle_default(values: &[f64]) => (LINEARREG_PERIOD)
;
}
indicator! {
fn linear_reg_intercept(values: &[f64], time_period: usize) -> Vec<f64> with linear_reg_intercept_with_output
default linear_reg_intercept_default(values: &[f64]) => (LINEARREG_PERIOD)
;
}
indicator! {
fn linear_reg_slope(values: &[f64], time_period: usize) -> Vec<f64> with linear_reg_slope_with_output
default linear_reg_slope_default(values: &[f64]) => (LINEARREG_PERIOD)
;
}
indicator! {
fn tsf(values: &[f64], time_period: usize) -> Vec<f64> with tsf_with_output
default tsf_default(values: &[f64]) => (LINEARREG_PERIOD)
;
}
pub fn linear_reg_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(
"linear_reg_with_output: out length must equal values length".into(),
));
}
linreg_core_with_output(values, time_period, 0, out);
Ok(())
}
pub fn linear_reg_angle_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(
"linear_reg_angle_with_output: out length must equal values length".into(),
));
}
linreg_core_with_output(values, time_period, 1, out);
Ok(())
}
pub fn linear_reg_intercept_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(
"linear_reg_intercept_with_output: out length must equal values length".into(),
));
}
linreg_core_with_output(values, time_period, 2, out);
Ok(())
}
pub fn linear_reg_slope_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(
"linear_reg_slope_with_output: out length must equal values length".into(),
));
}
linreg_core_with_output(values, time_period, 3, out);
Ok(())
}
pub fn tsf_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(
"tsf_with_output: out length must equal values length".into(),
));
}
linreg_core_with_output(values, time_period, 4, out);
Ok(())
}
#[inline]
fn ta_is_zero(v: f64) -> bool {
v > -1e-8 && v < 1e-8
}
fn beta_core(real0: &[f64], real1: &[f64], period: usize) -> Vec<f64> {
let n = real0.len();
let mut out = vec![f64::NAN; n];
if n <= period {
return out;
}
let p = period as f64;
let mut rx = vec![0.0_f64; n];
let mut ry = vec![0.0_f64; n];
let mut last_x = real0[0];
let mut last_y = real1[0];
for i in 1..n {
let cur_x = real0[i];
rx[i] = if ta_is_zero(last_x) { 0.0 } else { (cur_x - last_x) / last_x };
last_x = cur_x;
let cur_y = real1[i];
ry[i] = if ta_is_zero(last_y) { 0.0 } else { (cur_y - last_y) / last_y };
last_y = cur_y;
}
let mut s_xx = 0.0_f64;
let mut s_xy = 0.0_f64;
let mut s_x = 0.0_f64;
let mut s_y = 0.0_f64;
for k in 1..=period {
let a = rx[k];
let b = ry[k];
s_xx += a * a;
s_xy += a * b;
s_x += a;
s_y += b;
}
let denom = p * s_xx - s_x * s_x;
out[period] = if ta_is_zero(denom) { 0.0 } else { (p * s_xy - s_x * s_y) / denom };
for i in (period + 1)..n {
let a_new = rx[i];
let b_new = ry[i];
let a_old = rx[i - period];
let b_old = ry[i - period];
s_xx = s_xx + a_new * a_new - a_old * a_old;
s_xy = s_xy + a_new * b_new - a_old * b_old;
s_x = s_x + a_new - a_old;
s_y = s_y + b_new - b_old;
let denom = p * s_xx - s_x * s_x;
out[i] = if ta_is_zero(denom) { 0.0 } else { (p * s_xy - s_x * s_y) / denom };
}
out
}
fn correl_core(real0: &[f64], real1: &[f64], period: usize) -> Vec<f64> {
let mut out = vec![f64::NAN; real0.len()];
correl_core_with_output(real0, real1, period, &mut out);
out
}
fn correl_core_with_output(real0: &[f64], real1: &[f64], period: usize, out: &mut [f64]) {
let n = real0.len();
if n < period {
return;
}
let p = period as f64;
let mut s0 = 0.0_f64;
let mut s1 = 0.0_f64;
let mut s00 = 0.0_f64;
let mut s11 = 0.0_f64;
let mut s01 = 0.0_f64;
for k in 0..period {
let a = real0[period - 1 - k];
let b = real1[period - 1 - k];
s0 += a;
s1 += b;
s00 += a * a;
s11 += b * b;
s01 += a * b;
}
let cov = (s01 - s0 * s1 / p) / p;
let v0 = (s00 - s0 * s0 / p) / p;
let v1 = (s11 - s1 * s1 / p) / p;
out[period - 1] = if ta_is_zero(v0) || ta_is_zero(v1) {
0.0
} else {
cov / (v0 * v1).sqrt()
};
for i in period..n {
s0 = s0 + real0[i] - real0[i - period];
s1 = s1 + real1[i] - real1[i - period];
s00 = s00 + real0[i] * real0[i] - real0[i - period] * real0[i - period];
s11 = s11 + real1[i] * real1[i] - real1[i - period] * real1[i - period];
s01 = s01 + real0[i] * real1[i] - real0[i - period] * real1[i - period];
let cov = (s01 - s0 * s1 / p) / p;
let v0 = (s00 - s0 * s0 / p) / p;
let v1 = (s11 - s1 * s1 / p) / p;
out[i] = if ta_is_zero(v0) || ta_is_zero(v1) {
0.0
} else {
cov / (v0 * v1).sqrt()
};
}
}
pub fn beta(
real0: &[f64],
real1: &[f64],
time_period: usize,
) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
check_eq_len(&[real0, real1], "beta")?;
let mut out = vec![f64::NAN; real0.len()];
beta_with_output(real0, real1, time_period, &mut out)?;
Ok(out)
}
pub fn beta_with_output(
real0: &[f64],
real1: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[real0, real1], "beta")?;
if out.len() != real0.len() {
return Err(TaError::BadParam(
"beta_with_output: out length must equal real0 length".into(),
));
}
let temp = beta_core(real0, real1, time_period);
out.copy_from_slice(&temp);
Ok(())
}
pub fn beta_default(real0: &[f64], real1: &[f64]) -> Result<Vec<f64>, TaError> {
beta(real0, real1, BETA_PERIOD)
}
pub fn correl(
real0: &[f64],
real1: &[f64],
time_period: usize,
) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
check_eq_len(&[real0, real1], "correl")?;
Ok(correl_core(real0, real1, time_period))
}
pub fn correl_with_output(
real0: &[f64],
real1: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[real0, real1], "correl")?;
if out.len() != real0.len() {
return Err(TaError::BadParam(
"correl_with_output: out length must equal real0 length".into(),
));
}
correl_core_with_output(real0, real1, time_period, out);
Ok(())
}
pub fn correl_default(real0: &[f64], real1: &[f64]) -> Result<Vec<f64>, TaError> {
correl(real0, real1, CORREL_PERIOD)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stddev_population() {
let x = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let out = stddev(&x, 8, 1.0).unwrap();
assert!((out[7] - 2.0).abs() < 1e-9);
let out2 = stddev(&x, 8, 2.0).unwrap();
assert!((out2[7] - 4.0).abs() < 1e-9);
}
#[test]
fn var_matches_stddev_squared() {
let x: Vec<f64> = (0..30).map(|i| (i as f64).sin() + 2.0).collect();
let v = var(&x, 10, 1.0).unwrap();
let s = stddev(&x, 10, 1.0).unwrap();
let i = 29;
assert!((v[i] - s[i] * s[i]).abs() < 1e-9);
}
#[test]
fn linear_reg_through_points() {
let y = [1.0, 3.0, 5.0, 7.0, 9.0];
let lr = linear_reg(&y, 3).unwrap();
assert!((lr[2] - 5.0).abs() < 1e-9);
let slope = linear_reg_slope(&y, 3).unwrap();
assert!((slope[2] - 2.0).abs() < 1e-9);
let intercept = linear_reg_intercept(&y, 3).unwrap();
assert!((intercept[2] - 1.0).abs() < 1e-9);
let tsf_out = tsf(&y, 3).unwrap();
assert!((tsf_out[2] - 7.0).abs() < 1e-9);
let angle = linear_reg_angle(&y, 3).unwrap();
assert!((angle[2] - (2.0_f64).atan() * 180.0 / std::f64::consts::PI).abs() < 1e-9);
}
#[test]
fn correl_perfect_positive() {
let a: Vec<f64> = (0..20).map(|i| i as f64).collect();
let b: Vec<f64> = (0..20).map(|i| 2.0 * i as f64 + 1.0).collect();
let c = correl(&a, &b, 10).unwrap();
assert!((c[19] - 1.0).abs() < 1e-9);
}
#[test]
fn beta_of_proportional_series_is_one() {
let a: Vec<f64> = (0..20).map(|i| 10.0 + i as f64).collect();
let b: Vec<f64> = a.iter().map(|&v| 3.0 * v).collect();
let bt = beta(&a, &b, 10).unwrap();
assert!((bt[19] - 1.0).abs() < 1e-9);
}
#[test]
fn beta_length_mismatch_is_error() {
assert!(matches!(
beta(&[1.0, 2.0], &[1.0], 1),
Err(TaError::BadParam(_))
));
}
fn linreg_core_naive(values: &[f64], period: usize, mode: u8) -> Vec<f64> {
let n = values.len();
let mut out = vec![f64::NAN; n];
if n < period {
return out;
}
let p = period as f64;
let sx = (period * (period - 1)) as f64 / 2.0;
let sxx = (period * (period - 1) * (2 * period - 1)) as f64 / 6.0;
let denom = p * sxx - sx * sx;
for i in (period - 1)..n {
let mut sy = 0.0_f64;
let mut sxy = 0.0_f64;
for k in 0..period {
let x = values[i - (period - 1) + k];
sy += x;
sxy += (k as f64) * x;
}
let slope = (p * sxy - sx * sy) / denom;
let intercept = (sy - slope * sx) / p;
out[i] = match mode {
1 => slope.atan() * 180.0 / std::f64::consts::PI,
2 => intercept,
3 => slope,
4 => intercept + slope * p,
_ => intercept + slope * (p - 1.0),
};
}
out
}
fn correl_core_naive(real0: &[f64], real1: &[f64], period: usize) -> Vec<f64> {
let n = real0.len();
let mut out = vec![f64::NAN; n];
if n < period {
return out;
}
let p = period as f64;
for i in (period - 1)..n {
let mut s0 = 0.0_f64;
let mut s1 = 0.0_f64;
let mut s00 = 0.0_f64;
let mut s11 = 0.0_f64;
let mut s01 = 0.0_f64;
for k in 0..period {
let a = real0[i - k];
let b = real1[i - k];
s0 += a;
s1 += b;
s00 += a * a;
s11 += b * b;
s01 += a * b;
}
let cov = (s01 - s0 * s1 / p) / p;
let v0 = (s00 - s0 * s0 / p) / p;
let v1 = (s11 - s1 * s1 / p) / p;
out[i] = if ta_is_zero(v0) || ta_is_zero(v1) {
0.0
} else {
cov / (v0 * v1).sqrt()
};
}
out
}
#[test]
fn linreg_core_matches_naive() {
let close = |a: f64, b: f64| -> bool {
if a.is_nan() && b.is_nan() {
return true;
}
(a - b).abs() <= 1e-9
|| (a.is_finite() && b.is_finite() && (a - b).abs() <= 1e-6 * a.abs().max(b.abs()))
};
let mut x: u64 = 0x1234_5678_9abc_def0;
let mut lcg = || {
x = x
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((x >> 11) as f64 / (1u64 << 53) as f64) * 100.0 - 50.0
};
for &n in &[0usize, 1, 2, 5, 20, 64, 137, 500] {
for &p in &[1usize, 2, 3, 7, 20, 30, 64] {
if n < p || p < 2 {
continue;
}
let v: Vec<f64> = (0..n).map(|_| lcg()).collect();
for mode in 0..5u8 {
let fast = linreg_core(&v, p, mode);
let naive = linreg_core_naive(&v, p, mode);
for i in 0..n {
assert!(
close(fast[i], naive[i]),
"linreg mismatch n={n} p={p} mode={mode} i={i}: {} vs {}",
fast[i],
naive[i]
);
}
}
}
}
}
#[test]
fn correl_core_matches_naive() {
let close = |a: f64, b: f64| -> bool {
if a.is_nan() && b.is_nan() {
return true;
}
(a - b).abs() <= 1e-9
|| (a.is_finite() && b.is_finite() && (a - b).abs() <= 1e-6 * a.abs().max(b.abs()))
};
let mut x: u64 = 0x1357_9bdf_2468_ace0;
let mut lcg = || {
x = x
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((x >> 11) as f64 / (1u64 << 53) as f64) * 100.0 - 50.0
};
for &n in &[0usize, 1, 2, 5, 20, 64, 137, 500] {
for &p in &[1usize, 2, 3, 7, 20, 30, 64] {
if n < p {
continue;
}
let a: Vec<f64> = (0..n).map(|_| lcg()).collect();
let b: Vec<f64> = (0..n).map(|_| lcg()).collect();
let fast = correl_core(&a, &b, p);
let naive = correl_core_naive(&a, &b, p);
for i in 0..n {
assert!(
close(fast[i], naive[i]),
"correl mismatch n={n} p={p} i={i}: {} vs {}",
fast[i],
naive[i]
);
}
}
}
}
}