Skip to content

Commit b4b16b0

Browse files
committed
feat: update vol_surface pipeline
1 parent 9966e4e commit b4b16b0

9 files changed

Lines changed: 326 additions & 374 deletions

File tree

src/quant/calibration/heston.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ pub struct HestonParams {
9898
}
9999

100100
impl HestonParams {
101+
/// Convert to a [`HestonFourier`] model for pricing / vol surface generation.
102+
pub fn to_fourier(&self, r: f64, q: f64) -> crate::quant::pricing::fourier::HestonFourier {
103+
crate::quant::pricing::fourier::HestonFourier {
104+
v0: self.v0,
105+
kappa: self.kappa,
106+
theta: self.theta,
107+
sigma: self.sigma,
108+
rho: self.rho,
109+
r,
110+
q,
111+
}
112+
}
113+
101114
fn periodic_map(x: f64, c: f64, d: f64) -> f64 {
102115
if c <= x && x <= d {
103116
x

src/quant/calibration/heston_stoch_corr.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ pub struct HscmCalibrationResult {
5050
pub mae: f64,
5151
}
5252

53+
impl From<HscmCalibrationResult> for crate::quant::pricing::heston_stoch_corr::HscmModel {
54+
fn from(r: HscmCalibrationResult) -> Self {
55+
Self {
56+
v0: r.v0,
57+
kappa_v: r.kappa_v,
58+
theta_v: r.theta_v,
59+
sigma_v: r.sigma_v,
60+
rho0: r.rho0,
61+
kappa_r: r.kappa_r,
62+
mu_r: r.mu_r,
63+
sigma_r: r.sigma_r,
64+
rho2: r.rho2,
65+
}
66+
}
67+
}
68+
5369
impl HscmCalibrationResult {
5470
pub fn to_vec(&self) -> Vec<f64> {
5571
vec![

src/quant/calibration/levy.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,63 @@ pub struct LevyCalibrationResult {
8484
pub iterations: usize,
8585
}
8686

87+
impl LevyCalibrationResult {
88+
/// Convert to a Fourier model for pricing / vol surface generation.
89+
///
90+
/// Returns a boxed [`ModelPricer`] because the concrete type depends
91+
/// on the calibrated model variant.
92+
pub fn to_model(
93+
&self,
94+
r: f64,
95+
q: f64,
96+
) -> Box<dyn crate::traits::ModelPricer> {
97+
use crate::quant::pricing::fourier::*;
98+
let p = &self.params;
99+
match self.model_type {
100+
LevyModelType::VarianceGamma => Box::new(VarianceGammaFourier {
101+
sigma: p[0],
102+
theta: p[1],
103+
nu: p[2],
104+
r,
105+
q,
106+
}),
107+
LevyModelType::NIG => Box::new(CGMYFourier {
108+
c: p[0],
109+
g: p[1],
110+
m: p[2],
111+
y: 0.5,
112+
r,
113+
q,
114+
}),
115+
LevyModelType::CGMY => Box::new(CGMYFourier {
116+
c: p[0],
117+
g: p[1],
118+
m: p[2],
119+
y: p[3],
120+
r,
121+
q,
122+
}),
123+
LevyModelType::MertonJD => Box::new(MertonJDFourier {
124+
sigma: p[0],
125+
lambda: p[1],
126+
mu_j: p[2],
127+
sigma_j: p[3],
128+
r,
129+
q,
130+
}),
131+
LevyModelType::Kou => Box::new(KouFourier {
132+
sigma: p[0],
133+
lambda: p[1],
134+
p_up: p[2],
135+
eta1: p[3],
136+
eta2: p[4],
137+
r,
138+
q,
139+
}),
140+
}
141+
}
142+
}
143+
87144
/// Lévy model calibrator via Fourier pricing + Levenberg-Marquardt.
88145
///
89146
/// Source:

src/quant/calibration/svj.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,24 @@ pub struct SVJCalibrationResult {
181181
pub converged: bool,
182182
}
183183

184+
impl SVJCalibrationResult {
185+
/// Convert to a [`BatesFourier`] model for pricing / vol surface generation.
186+
pub fn to_fourier(&self, r: f64, q: f64) -> crate::quant::pricing::fourier::BatesFourier {
187+
crate::quant::pricing::fourier::BatesFourier {
188+
v0: self.v0,
189+
kappa: self.kappa,
190+
theta: self.theta,
191+
sigma_v: self.sigma_v,
192+
rho: self.rho,
193+
lambda: self.lambda,
194+
mu_j: self.mu_j,
195+
sigma_j: self.sigma_j,
196+
r,
197+
q,
198+
}
199+
}
200+
}
201+
184202
/// SVJ (Bates) least-squares calibrator using Levenberg-Marquardt.
185203
///
186204
/// Source:

src/quant/pricing/fourier.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@ impl LewisPricer {
244244
}
245245
}
246246

247+
/// Blanket implementation: every [`FourierModelExt`] model automatically
248+
/// implements [`ModelPricer`] via Gil-Pelaez quadrature.
249+
impl<T: FourierModelExt> crate::traits::ModelPricer for T {
250+
fn price_call(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64 {
251+
GilPelaezPricer::price_call(self, s, k, r, q, tau)
252+
}
253+
}
254+
247255
/// Black–Scholes–Merton model for Fourier pricing.
248256
pub struct BSMFourier {
249257
pub sigma: f64,

src/quant/pricing/heston_stoch_corr.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,32 @@ impl TimeExt for HestonStochCorrPricer {
261261
}
262262
}
263263

264+
/// Heston stochastic-correlation model parameters (model only, no market data).
265+
///
266+
/// Implements [`ModelPricer`] via the Carr-Madan FFT pricer.
267+
#[derive(Clone, Copy, Debug)]
268+
pub struct HscmModel {
269+
pub v0: f64,
270+
pub kappa_v: f64,
271+
pub theta_v: f64,
272+
pub sigma_v: f64,
273+
pub rho0: f64,
274+
pub kappa_r: f64,
275+
pub mu_r: f64,
276+
pub sigma_r: f64,
277+
pub rho2: f64,
278+
}
279+
280+
impl crate::traits::ModelPricer for HscmModel {
281+
fn price_call(&self, s: f64, k: f64, r: f64, _q: f64, tau: f64) -> f64 {
282+
let pricer = HestonStochCorrPricer::new(
283+
s, r, k, self.v0, self.kappa_v, self.theta_v, self.sigma_v, self.rho0, self.kappa_r,
284+
self.mu_r, self.sigma_r, self.rho2, tau,
285+
);
286+
pricer.price_call_carr_madan()
287+
}
288+
}
289+
264290
#[cfg(test)]
265291
mod tests {
266292
use super::*;

src/quant/pricing/sabr.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,43 @@ impl PricerExt for SabrPricer {
319319
}
320320
}
321321

322+
/// SABR model parameters (model only, no market data).
323+
///
324+
/// Implements [`ModelPricer`] via the Hagan (2002) implied-vol formula
325+
/// plugged into Black-Scholes.
326+
#[derive(Clone, Copy, Debug)]
327+
pub struct SabrModel {
328+
pub alpha: f64,
329+
pub beta: f64,
330+
pub nu: f64,
331+
pub rho: f64,
332+
}
333+
334+
impl crate::traits::ModelPricer for SabrModel {
335+
fn price_call(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64 {
336+
let fwd = s * ((r - q) * tau).exp();
337+
let sigma = hagan_implied_vol(k, fwd, tau, self.alpha, self.beta, self.nu, self.rho);
338+
if !sigma.is_finite() || sigma <= 0.0 {
339+
return 0.0;
340+
}
341+
let pricer = BSMPricer::new(
342+
s,
343+
sigma,
344+
k,
345+
r,
346+
None,
347+
None,
348+
Some(q),
349+
Some(tau),
350+
None,
351+
None,
352+
OptionType::Call,
353+
BSMCoc::Merton1973,
354+
);
355+
pricer.calculate_call_put().0
356+
}
357+
}
358+
322359
#[cfg(test)]
323360
mod tests {
324361
use super::*;

0 commit comments

Comments
 (0)