use ndarray::ArrayView1;
use crate::realized::variance::realized_variance;
use crate::traits::FloatExt;
pub fn pre_averaged_variance<T: FloatExt>(returns: ArrayView1<T>, theta: T) -> T {
let n = returns.len();
if n < 4 {
return T::zero();
}
let theta_f = theta.to_f64().unwrap();
assert!(theta_f > 0.0, "theta must be positive");
let nf = n as f64;
let kn = (theta_f * nf.sqrt()).floor() as usize;
let kn = kn.max(2).min(n);
pre_averaged_variance_with_block(returns, kn)
}
pub fn pre_averaged_variance_with_block<T: FloatExt>(returns: ArrayView1<T>, kn: usize) -> T {
let n = returns.len();
if n < kn || kn < 2 {
return T::zero();
}
let kn_f = T::from_usize_(kn);
let mut sum_bar2 = T::zero();
for i in 0..=(n - kn) {
let mut bar = T::zero();
for j in 1..kn {
let x = T::from_usize_(j) / kn_f;
let g = triangular_weight(x);
bar += g * returns[i + j - 1];
}
sum_bar2 += bar * bar;
}
let rv = realized_variance(returns);
let twelve = T::from_f64_fast(12.0);
let six = T::from_f64_fast(6.0);
twelve / kn_f * sum_bar2 - six * rv / (kn_f * kn_f)
}
#[inline]
fn triangular_weight<T: FloatExt>(x: T) -> T {
let half = T::from_f64_fast(0.5);
if x < half { x } else { T::one() - x }
}
#[cfg(test)]
mod tests {
use ndarray::Array1;
use stochastic_rs_distributions::normal::SimdNormal;
use super::*;
fn iid_normal(seed: u64, n: usize, std: f64) -> Array1<f64> {
let dist = SimdNormal::<f64>::with_seed(0.0, std, seed);
let mut out = Array1::<f64>::zeros(n);
dist.fill_slice_fast(out.as_slice_mut().unwrap());
out
}
fn noisy_returns(seed: u64, n: usize, sigma: f64, omega: f64) -> Array1<f64> {
let dx = SimdNormal::<f64>::with_seed(0.0, sigma, seed);
let dn = SimdNormal::<f64>::with_seed(0.0, omega, seed.wrapping_add(1));
let mut steps = vec![0.0_f64; n];
dx.fill_slice_fast(&mut steps);
let mut noise = vec![0.0_f64; n + 1];
dn.fill_slice_fast(&mut noise);
let mut dy = Array1::<f64>::zeros(n);
for i in 0..n {
dy[i] = steps[i] + noise[i + 1] - noise[i];
}
dy
}
#[test]
fn pav_close_to_iv_under_no_noise() {
let n = 5_000;
let r = iid_normal(101, n, 0.005);
let iv = (n as f64) * 0.005_f64.powi(2);
let pav = pre_averaged_variance(r.view(), 1.0_f64 / 3.0);
let rv: f64 = r.iter().map(|v| v * v).sum();
let pav_err = (pav - iv).abs();
let rv_err = (rv - iv).abs();
let tol = pav_err.max(rv_err) * 5.0;
assert!(pav_err <= tol);
}
#[test]
fn pav_corrects_microstructure_noise_better_than_rv() {
let n = 20_000;
let sigma = 0.005_f64;
let omega = 0.003_f64;
let dy = noisy_returns(103, n, sigma, omega);
let iv = (n as f64) * sigma.powi(2);
let rv: f64 = dy.iter().map(|v| v * v).sum();
let pav = pre_averaged_variance(dy.view(), 1.0 / 3.0);
assert!((pav - iv).abs() < (rv - iv).abs());
}
}