use ndarray::Array1;
pub mod coefficients;
mod engine;
pub use coefficients::convolution_coefficients;
pub use coefficients::fourier_coefficients_dx;
pub use engine::FMVol;
pub fn default_cutting_freq_vol(n: usize) -> (usize, usize) {
let big_n = n / 2;
let big_m = (big_n as f64).sqrt() as usize;
(big_n, big_m)
}
pub fn default_cutting_freq_lev(n: usize) -> (usize, usize, usize) {
let big_n = n / 2;
let big_m = (big_n as f64).sqrt() as usize;
let big_l = (big_n as f64).powf(0.25) as usize;
(big_n, big_m, big_l)
}
pub fn default_cutting_freq_volvol(n: usize) -> (usize, usize, usize) {
let big_n = n / 2;
let big_m = (big_n as f64).powf(0.4) as usize;
let big_l = (big_n as f64).powf(0.2) as usize;
(big_n, big_m, big_l)
}
pub fn default_cutting_freq_noisy(n: usize) -> (usize, usize, usize) {
let big_n = (5.0 * (n as f64).sqrt()) as usize;
let big_m = (0.3 * (big_n as f64).sqrt()) as usize;
let big_l = (big_m as f64).sqrt() as usize;
(big_n, big_m, big_l)
}
pub fn optimal_cutting_frequency(prices: &[f64], times: &[f64]) -> OptimalCuttingResult {
assert_eq!(
prices.len(),
times.len(),
"prices and times must have the same length"
);
let n_obs = prices.len();
assert!(n_obs >= 8, "need at least 8 observations");
let n = n_obs - 1;
let period = times[n_obs - 1] - times[0];
assert!(period > 0.0, "time period must be positive");
let increments = Array1::from_vec((0..n).map(|i| prices[i + 1] - prices[i]).collect());
let mut step = 1usize;
loop {
let sparse_inc = if step == 1 {
increments.clone()
} else {
let sp: Vec<f64> = (0..n_obs).step_by(step).map(|i| prices[i]).collect();
Array1::from_vec((0..sp.len() - 1).map(|i| sp[i + 1] - sp[i]).collect())
};
if sparse_inc.len() < 4 {
break;
}
let acf1 = lag1_autocorrelation(&sparse_inc);
let bound = 1.96 / (sparse_inc.len() as f64).sqrt();
if acf1.abs() < bound || step >= n / 4 {
break;
}
step += 1;
}
let sparse_prices: Vec<f64> = (0..n_obs).step_by(step).map(|i| prices[i]).collect();
let sparse_inc = Array1::from_vec(
(0..sparse_prices.len() - 1)
.map(|i| sparse_prices[i + 1] - sparse_prices[i])
.collect(),
);
let n_sparse = sparse_inc.len();
let rv = sparse_inc.mapv(|r| r * r).sum();
let sum4 = sparse_inc.mapv(|r| r.powi(4)).sum();
let q = sum4 * n_sparse as f64 / (3.0 * period);
let sum_r2 = increments.mapv(|r| r * r).sum();
let sum_r4 = increments.mapv(|r| r.powi(4)).sum();
let e2 = sum_r2 / n as f64 - rv / n as f64;
let e4 = sum_r4 / n as f64 - 6.0 * e2 * rv / n as f64;
let eeta2 = e2 / 2.0;
let eeta4 = e4 / 2.0 - 3.0 * e2 * e2 / 4.0;
let alpha = e2 * e2;
let beta = 4.0 * eeta4;
let gamma = 8.0 * eeta2 * rv + alpha / 2.0 - 2.0 * eeta4;
let noise_variance = eeta2.max(0.0);
let n_max = n / 2;
let h = period / n as f64;
let mut mse_curve = Array1::<f64>::zeros(n_max);
let mut best_mse = f64::INFINITY;
let mut n_opt = 1usize;
for k in 1..=n_max {
let trunc = (n / 2).min(k);
let rd = rescaled_dirichlet_kernel(trunc, h, period);
let rd2 = rd * rd;
let alpha_fe = alpha * (1.0 + rd2 - 2.0 * rd);
let beta_fe = beta * (1.0 + rd2 - 2.0 * rd);
let gamma_fe = gamma
+ 4.0 * (eeta4 + (e2 / 2.0).powi(2)) * (2.0 * rd - rd2)
+ 8.0 * std::f64::consts::PI * q / (2 * trunc + 1) as f64;
let mse = 2.0 * q * h + beta_fe * n as f64 + alpha_fe * (n as f64).powi(2) + gamma_fe;
mse_curve[k - 1] = mse;
if mse < best_mse {
best_mse = mse;
n_opt = k;
}
}
n_opt = n_opt.min(n_max).max(1);
OptimalCuttingResult {
n_opt,
noise_variance,
mse_curve,
}
}
pub struct OptimalCuttingResult {
pub n_opt: usize,
pub noise_variance: f64,
pub mse_curve: Array1<f64>,
}
impl OptimalCuttingResult {
pub fn cutting_freqs(&self) -> (usize, usize, usize) {
let m = (0.3 * (self.n_opt as f64).sqrt()) as usize;
let l = (m as f64).sqrt() as usize;
(self.n_opt, m.max(1), l.max(1))
}
}
fn lag1_autocorrelation(x: &Array1<f64>) -> f64 {
let n = x.len();
if n < 2 {
return 0.0;
}
let mean = x.sum() / n as f64;
let centered = x.mapv(|v| v - mean);
let cov0 = centered.mapv(|v| v * v).sum();
if cov0.abs() < 1e-30 {
return 0.0;
}
let cov1: f64 = (1..n).map(|i| centered[i] * centered[i - 1]).sum();
cov1 / cov0
}
fn rescaled_dirichlet_kernel(big_n: usize, h: f64, period: f64) -> f64 {
let omega = std::f64::consts::TAU / period;
let mut sum = 1.0;
for s in 1..=big_n {
sum += 2.0 * (s as f64 * omega * h).cos();
}
sum / (2 * big_n + 1) as f64
}