use ndarray::ArrayView1;
use crate::traits::FloatExt;
pub fn two_scale_rv<T: FloatExt>(prices: ArrayView1<T>, k: usize) -> T {
let len = prices.len();
assert!(k >= 1, "subsampling factor must be ≥ 1");
assert!(len >= k + 2, "price path too short for k = {k}");
let n = len - 1;
let rv_all = price_rv(prices, 1);
let rv_slow = average_subsample_rv(prices, k);
let nbar = T::from_f64_fast((n as f64 - k as f64 + 1.0) / k as f64);
let nf = T::from_usize_(n);
let factor = T::one() - nbar / nf;
let raw = rv_slow - (nbar / nf) * rv_all;
if factor > T::zero() {
raw / factor
} else {
raw
}
}
pub fn multi_scale_rv<T: FloatExt>(prices: ArrayView1<T>, scales: usize) -> T {
let m = scales.max(2);
assert!(prices.len() >= m + 2, "price path too short for {m} scales");
let h1: f64 = (1..=m).map(|i| 1.0 / i as f64).sum();
let h2: f64 = (1..=m).map(|i| 1.0 / (i as f64).powi(2)).sum();
let mu = h1 / (h1.powi(2) - (m as f64) * h2);
let lambda = -h2 * mu / h1;
let mut acc = T::zero();
for i in 1..=m {
let weight = lambda + mu / i as f64;
let rv_i = average_subsample_rv(prices, i);
acc += T::from_f64_fast(weight) * rv_i;
}
acc
}
fn price_rv<T: FloatExt>(prices: ArrayView1<T>, step: usize) -> T {
let mut acc = T::zero();
let mut i = step;
while i < prices.len() {
let d = prices[i] - prices[i - step];
acc += d * d;
i += step;
}
acc
}
fn average_subsample_rv<T: FloatExt>(prices: ArrayView1<T>, k: usize) -> T {
if k <= 1 {
return price_rv(prices, 1);
}
let mut total = T::zero();
let mut count = 0usize;
for offset in 0..k {
let mut acc = T::zero();
let mut i = offset + k;
while i < prices.len() {
let d = prices[i] - prices[i - k];
acc += d * d;
i += k;
}
total += acc;
count += 1;
}
total / T::from_usize_(count)
}
#[cfg(test)]
mod tests {
use ndarray::Array1;
use stochastic_rs_distributions::normal::SimdNormal;
use super::*;
fn simulate_noisy_path(seed: u64, n: usize, sigma: f64, omega: f64) -> (Array1<f64>, 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 x = vec![0.0_f64; n + 1];
for i in 1..=n {
x[i] = x[i - 1] + steps[i - 1];
}
let y: Vec<f64> = x
.iter()
.zip(noise.iter())
.map(|(&xv, &nv)| xv + nv)
.collect();
let iv = (n as f64) * sigma.powi(2);
(Array1::from(y), iv)
}
#[test]
fn tsrv_corrects_naive_rv_bias() {
let (y, iv) = simulate_noisy_path(211, 20_000, 0.005, 0.003);
let dy = Array1::from_iter((1..y.len()).map(|i| y[i] - y[i - 1]));
let rv: f64 = dy.iter().map(|v| v * v).sum();
let tsrv = two_scale_rv(y.view(), 20);
assert!((tsrv - iv).abs() < (rv - iv).abs());
}
#[test]
fn msrv_corrects_naive_rv_bias() {
let (y, iv) = simulate_noisy_path(229, 20_000, 0.005, 0.003);
let dy = Array1::from_iter((1..y.len()).map(|i| y[i] - y[i - 1]));
let rv: f64 = dy.iter().map(|v| v * v).sum();
let msrv = multi_scale_rv(y.view(), 12);
assert!((msrv - iv).abs() < (rv - iv).abs());
}
}