fn linreg_fit(data: &[f64], period: usize) -> (Vec<f64>, Vec<f64>) {
let n = data.len();
if period == 0 || period > n {
return (vec![f64::NAN; n], vec![f64::NAN; n]);
}
let p = period as f64;
let sum_x = p * (period - 1) as f64 * 0.5;
let sum_x_sqr = (period * (period - 1) * (2 * period - 1) / 6) as f64;
let divisor = sum_x * sum_x - p * sum_x_sqr;
let mut slope = crate::buf::OutBuf::warmup(n, period - 1);
let mut intercept = crate::buf::OutBuf::warmup(n, period - 1);
let mut emit = |today: usize, sum_y: f64, sum_xy: f64| {
let m = (p * sum_xy - sum_x * sum_y) / divisor;
slope.set(today, m);
intercept.set(today, (sum_y - m * sum_x) / p);
};
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
for i in (0..period).rev() {
let y = data[period - 1 - i];
sum_y += y;
sum_xy += i as f64 * y;
}
emit(period - 1, sum_y, sum_xy);
for today in period..n {
let leaving = data[today - period];
sum_xy += sum_y - p * leaving;
sum_y += data[today] - leaving;
emit(today, sum_y, sum_xy);
}
(slope.finish(), intercept.finish())
}
pub fn linearreg(data: &[f64], period: usize) -> Vec<f64> {
let (m, b) = linreg_fit(data, period);
let x = period.saturating_sub(1) as f64;
b.iter().zip(&m).map(|(b, m)| b + m * x).collect()
}
pub fn linearreg_slope(data: &[f64], period: usize) -> Vec<f64> {
linreg_fit(data, period).0
}
pub fn linearreg_intercept(data: &[f64], period: usize) -> Vec<f64> {
linreg_fit(data, period).1
}
pub fn linearreg_angle(data: &[f64], period: usize) -> Vec<f64> {
let deg = 180.0 / std::f64::consts::PI;
linreg_fit(data, period)
.0
.iter()
.map(|m| m.atan() * deg)
.collect()
}
pub fn tsf(data: &[f64], period: usize) -> Vec<f64> {
let (m, b) = linreg_fit(data, period);
let x = period as f64;
b.iter().zip(&m).map(|(b, m)| b + m * x).collect()
}
fn rolling_var(data: &[f64], period: usize) -> Vec<f64> {
let n = data.len();
if period == 0 || period > n {
return vec![f64::NAN; n];
}
let p = period as f64;
let mut total1 = 0.0; let mut total2 = 0.0; for &x in &data[..period - 1] {
total1 += x;
total2 += x * x;
}
let mut trailing = 0;
let mut out = crate::buf::OutBuf::warmup(n, period - 1);
#[allow(clippy::explicit_counter_loop)] for i in (period - 1)..n {
let x = data[i];
total1 += x;
total2 += x * x;
let mean1 = total1 / p;
let mean2 = total2 / p;
let old = data[trailing];
trailing += 1;
total1 -= old;
total2 -= old * old;
out.set(i, mean2 - mean1 * mean1);
}
out.finish()
}
pub fn var(data: &[f64], period: usize) -> Vec<f64> {
rolling_var(data, period)
}
pub fn stddev(data: &[f64], period: usize, nbdev: f64) -> Vec<f64> {
rolling_var(data, period)
.into_iter()
.map(|v| {
if v.is_nan() {
f64::NAN
} else if v > 0.0 {
v.sqrt() * nbdev
} else {
0.0
}
})
.collect()
}
pub fn correl(x: &[f64], y: &[f64], period: usize) -> Vec<f64> {
let n = x.len();
if period == 0 || period > n {
return vec![f64::NAN; n];
}
let mut out = crate::buf::OutBuf::warmup(n, period - 1);
let pf = period as f64;
let inv_pf = 1.0 / pf;
let (mut sx, mut sy, mut sx2, mut sy2, mut sxy) = (0.0, 0.0, 0.0, 0.0, 0.0);
let x_ptr = x.as_ptr();
let y_ptr = y.as_ptr();
let out_ptr = out.ptr();
for i in 0..period {
let (xi, yi) = unsafe { (*x_ptr.add(i), *y_ptr.add(i)) };
sx += xi;
sy += yi;
sx2 += xi * xi;
sy2 += yi * yi;
sxy += xi * yi;
}
let value = |sx: f64, sy: f64, sx2: f64, sy2: f64, sxy: f64| {
let denom = (sx2 - sx * sx * inv_pf) * (sy2 - sy * sy * inv_pf);
if denom < 1e-14 {
0.0
} else {
(sxy - sx * sy * inv_pf) / denom.sqrt()
}
};
unsafe {
*out_ptr.add(period - 1) = value(sx, sy, sx2, sy2, sxy);
}
let mut trailing = 0;
#[allow(clippy::explicit_counter_loop)] for i in period..n {
let (tx, ty) = unsafe { (*x_ptr.add(trailing), *y_ptr.add(trailing)) };
trailing += 1;
sx -= tx;
sx2 -= tx * tx;
sxy -= tx * ty;
sy -= ty;
sy2 -= ty * ty;
let (xi, yi) = unsafe { (*x_ptr.add(i), *y_ptr.add(i)) };
sx += xi;
sx2 += xi * xi;
sxy += xi * yi;
sy += yi;
sy2 += yi * yi;
unsafe {
*out_ptr.add(i) = value(sx, sy, sx2, sy2, sxy);
}
}
out.finish()
}
pub fn beta(x: &[f64], y: &[f64], period: usize) -> Vec<f64> {
let n = x.len();
if period == 0 || period + 1 > n {
return vec![f64::NAN; n];
}
let mut out = crate::buf::OutBuf::warmup(n, period);
let ret = |arr: &[f64], i: usize| -> f64 {
let prev = arr[i - 1];
if prev.abs() < 1e-14 {
0.0
} else {
(arr[i] - prev) / prev
}
};
let pf = period as f64;
let (mut sx, mut sy, mut sxx, mut sxy) = (0.0, 0.0, 0.0, 0.0);
let mut rx_ring = vec![0.0; period];
let mut ry_ring = vec![0.0; period];
for i in 1..period {
let (rx, ry) = (ret(x, i), ret(y, i));
rx_ring[i] = rx;
ry_ring[i] = ry;
sx += rx;
sy += ry;
sxx += rx * rx;
sxy += rx * ry;
}
for i in period..n {
let (rx, ry) = (ret(x, i), ret(y, i));
rx_ring[i % period] = rx;
ry_ring[i % period] = ry;
sx += rx;
sy += ry;
sxx += rx * rx;
sxy += rx * ry;
let denom = pf * sxx - sx * sx;
out.set(i, if denom.abs() < 1e-14 {
0.0
} else {
(pf * sxy - sx * sy) / denom
});
let leaving = i + 1 - period;
let (tx, ty) = (rx_ring[leaving % period], ry_ring[leaving % period]);
sx -= tx;
sy -= ty;
sxx -= tx * tx;
sxy -= tx * ty;
}
out.finish()
}