use nalgebra::DVector;
use crate::traits::FloatExt;
#[derive(Clone, Copy, Debug)]
pub struct SsviParams<T: FloatExt> {
pub rho: T,
pub eta: T,
pub gamma: T,
}
impl<T: FloatExt> SsviParams<T> {
pub fn new(rho: T, eta: T, gamma: T) -> Self {
Self { rho, eta, gamma }
}
#[inline]
pub fn phi(&self, theta: T) -> T {
if theta <= T::zero() {
return T::infinity();
}
let one = T::one();
self.eta / (theta.powf(self.gamma) * (one + theta).powf(one - self.gamma))
}
#[inline]
pub fn total_variance(&self, k: T, theta: T) -> T {
let half = T::from_f64_fast(0.5);
let one = T::one();
let phi = self.phi(theta);
let u = phi * k + self.rho;
half * theta * (one + self.rho * phi * k + (u * u + one - self.rho * self.rho).sqrt())
}
#[inline]
pub fn implied_vol(&self, k: T, theta: T, t: T) -> T {
let w = self.total_variance(k, theta);
let zero = T::zero();
if w > zero && t > zero {
(w / t).sqrt()
} else {
T::nan()
}
}
#[inline]
pub fn w_prime_k(&self, k: T, theta: T) -> T {
let half = T::from_f64_fast(0.5);
let one = T::one();
let phi = self.phi(theta);
let u = phi * k + self.rho;
let r = (u * u + one - self.rho * self.rho).sqrt();
half * theta * phi * (self.rho + u / r)
}
#[inline]
pub fn w_double_prime_k(&self, k: T, theta: T) -> T {
let half = T::from_f64_fast(0.5);
let one = T::one();
let phi = self.phi(theta);
let u = phi * k + self.rho;
let one_m_rho2 = one - self.rho * self.rho;
let r = (u * u + one_m_rho2).sqrt();
half * theta * phi * phi * one_m_rho2 / (r * r * r)
}
#[inline]
pub fn phi_prime(&self, theta: T) -> T {
if theta <= T::zero() {
return T::zero();
}
let one = T::one();
let phi = self.phi(theta);
-phi * (self.gamma / theta + (one - self.gamma) / (one + theta))
}
#[inline]
pub fn w_prime_theta(&self, k: T, theta: T) -> T {
let half = T::from_f64_fast(0.5);
let one = T::one();
let phi = self.phi(theta);
let phi_p = self.phi_prime(theta);
let u = phi * k + self.rho;
let r = (u * u + one - self.rho * self.rho).sqrt();
let w = self.total_variance(k, theta);
w / theta + half * theta * phi_p * k * (self.rho + u / r)
}
pub fn satisfies_no_butterfly_condition(&self) -> bool {
let two = T::from_f64_fast(2.0);
let one = T::one();
self.eta * (one + self.rho.abs()) <= two
}
pub fn project(&mut self) {
let zero = T::zero();
let one = T::one();
let two = T::from_f64_fast(2.0);
let bound = T::from_f64_fast(0.9999);
let eps = T::from_f64_fast(1e-8);
self.rho = self.rho.max(-bound).min(bound);
self.eta = self.eta.max(eps);
self.gamma = self.gamma.max(zero).min(one);
let max_eta = two / (one + self.rho.abs());
self.eta = self.eta.min(max_eta);
}
pub fn project_with_theta_range(&mut self, theta_max: T) {
self.project();
if theta_max <= T::zero() || !theta_max.is_finite() {
return;
}
let one = T::one();
let four = T::from_f64_fast(4.0);
let calendar_bound =
four * <T as num_traits::Float>::powf(theta_max, self.gamma) / (one + self.rho.abs());
if self.eta > calendar_bound {
self.eta = calendar_bound;
}
}
pub(super) fn as_f64(self) -> SsviParams<f64> {
SsviParams {
rho: self.rho.to_f64().unwrap_or(0.0),
eta: self.eta.to_f64().unwrap_or(0.0),
gamma: self.gamma.to_f64().unwrap_or(0.0),
}
}
}
impl SsviParams<f64> {
pub(super) fn into_dvector(self) -> DVector<f64> {
DVector::from_vec(vec![self.rho, self.eta, self.gamma])
}
pub(super) fn from_dvector(v: &DVector<f64>) -> Self {
SsviParams {
rho: v[0],
eta: v[1],
gamma: v[2],
}
}
}
#[derive(Clone, Debug)]
pub struct SsviSlice<T: FloatExt> {
pub log_moneyness: Vec<T>,
pub total_variance: Vec<T>,
pub theta: T,
}