use ndarray::Array1;
use num_complex::Complex;
use super::coefficients::fourier_coefficients_dx;
use super::coefficients::fourier_coefficients_dx_uniform;
use crate::traits::FloatExt;
mod helpers;
mod integrated;
mod spot;
#[cfg(test)]
mod tests;
pub struct FMVol<T: FloatExt> {
pub(super) dx: Array1<Complex<T>>,
pub(super) period: T,
pub(super) n: usize,
pub(super) n_freq: usize,
pub(super) max_freq: usize,
}
impl<T: FloatExt> FMVol<T> {
pub fn new(prices: &[T], times: &[T], period: T) -> Self {
Self::try_new(prices, times, period)
.expect("FMVol::new precondition violated — call try_new to handle this gracefully")
}
pub fn try_new(prices: &[T], times: &[T], period: T) -> anyhow::Result<Self> {
if prices.len() < 2 {
anyhow::bail!(
"FMVol::try_new requires at least 2 price observations to form increments, got {}",
prices.len()
);
}
if prices.len() != times.len() {
anyhow::bail!(
"FMVol::try_new: prices.len()={} must equal times.len()={}",
prices.len(),
times.len()
);
}
let n = prices.len() - 1;
let big_n = n / 2;
let m_max = (big_n as f64).sqrt() as usize;
let l_max = (big_n as f64).powf(0.25) as usize;
let max_freq = big_n + m_max + l_max;
let dx = fourier_coefficients_dx(prices, times, period, max_freq);
Ok(Self {
dx,
period,
n,
n_freq: big_n,
max_freq,
})
}
pub fn new_uniform(prices: &[T], period: T) -> Self {
Self::try_new_uniform(prices, period).expect(
"FMVol::new_uniform precondition violated — call try_new_uniform to handle this gracefully",
)
}
pub fn try_new_uniform(prices: &[T], period: T) -> anyhow::Result<Self> {
if prices.len() < 2 {
anyhow::bail!(
"FMVol::try_new_uniform requires at least 2 price observations to form increments, got {}",
prices.len()
);
}
let n = prices.len() - 1;
let big_n = n / 2;
let m_max = (big_n as f64).sqrt() as usize;
let l_max = (big_n as f64).powf(0.25) as usize;
let max_freq = big_n + m_max + l_max;
let dx = fourier_coefficients_dx_uniform(prices, period, max_freq);
Ok(Self {
dx,
period,
n,
n_freq: big_n,
max_freq,
})
}
pub fn with_freq(prices: &[T], times: &[T], period: T, n_freq: usize, max_freq: usize) -> Self {
Self::try_with_freq(prices, times, period, n_freq, max_freq).expect(
"FMVol::with_freq precondition violated — call try_with_freq to handle this gracefully",
)
}
pub fn try_with_freq(
prices: &[T],
times: &[T],
period: T,
n_freq: usize,
max_freq: usize,
) -> anyhow::Result<Self> {
if prices.len() < 2 {
anyhow::bail!(
"FMVol::try_with_freq requires at least 2 price observations to form increments, got {}",
prices.len()
);
}
if prices.len() != times.len() {
anyhow::bail!(
"FMVol::try_with_freq: prices.len()={} must equal times.len()={}",
prices.len(),
times.len()
);
}
if max_freq < n_freq {
anyhow::bail!("FMVol::try_with_freq: max_freq={max_freq} must be ≥ n_freq={n_freq}");
}
let n = prices.len() - 1;
let dx = fourier_coefficients_dx(prices, times, period, max_freq);
Ok(Self {
dx,
period,
n,
n_freq,
max_freq,
})
}
pub fn n_freq(&self) -> usize {
self.n_freq
}
pub fn n(&self) -> usize {
self.n
}
pub fn period(&self) -> T {
self.period
}
}