use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use crate::traits::ModelPricer;
#[pyclass(name = "HscmModel", from_py_object, unsendable)]
#[derive(Clone)]
pub struct PyHscmModel {
pub inner: crate::pricing::heston_stoch_corr::HscmModel,
}
#[pymethods]
impl PyHscmModel {
#[new]
#[allow(clippy::too_many_arguments)]
fn new(
v0: f64,
kappa_v: f64,
theta_v: f64,
sigma_v: f64,
rho0: f64,
kappa_r: f64,
mu_r: f64,
sigma_r: f64,
rho2: f64,
) -> PyResult<Self> {
if v0 <= 0.0 || theta_v <= 0.0 || sigma_v <= 0.0 || sigma_r <= 0.0 {
return Err(PyValueError::new_err(
"v0, theta_v, sigma_v, sigma_r must be > 0",
));
}
if rho0.abs() >= 1.0 || rho2.abs() >= 1.0 || mu_r.abs() >= 1.0 {
return Err(PyValueError::new_err("|rho0|, |rho2|, |mu_r| must be < 1"));
}
Ok(Self {
inner: crate::pricing::heston_stoch_corr::HscmModel {
v0,
kappa_v,
theta_v,
sigma_v,
rho0,
kappa_r,
mu_r,
sigma_r,
rho2,
},
})
}
fn price_call(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64 {
self.inner.price_call(s, k, r, q, tau)
}
}
#[pyclass(name = "HscmMarketOption", from_py_object, unsendable)]
#[derive(Clone)]
pub struct PyHscmMarketOption {
pub inner: crate::calibration::heston_stoch_corr::MarketOption,
}
#[pymethods]
impl PyHscmMarketOption {
#[new]
fn new(strike: f64, maturity: f64, price: f64, rate: f64) -> Self {
Self {
inner: crate::calibration::heston_stoch_corr::MarketOption {
strike,
maturity,
price,
rate,
},
}
}
#[getter]
fn strike(&self) -> f64 {
self.inner.strike
}
#[getter]
fn maturity(&self) -> f64 {
self.inner.maturity
}
#[getter]
fn price(&self) -> f64 {
self.inner.price
}
#[getter]
fn rate(&self) -> f64 {
self.inner.rate
}
}
#[pyclass(name = "HscmCalibrator", unsendable)]
pub struct PyHscmCalibrator {
inner: crate::calibration::heston_stoch_corr::HscmCalibrator,
}
#[pymethods]
impl PyHscmCalibrator {
#[new]
#[pyo3(signature = (s0, options, max_iter=500))]
fn new(s0: f64, options: Vec<PyHscmMarketOption>, max_iter: usize) -> Self {
let inner_options: Vec<crate::calibration::heston_stoch_corr::MarketOption> =
options.into_iter().map(|o| o.inner).collect();
Self {
inner: crate::calibration::heston_stoch_corr::HscmCalibrator::new(s0, inner_options)
.with_max_iter(max_iter),
}
}
#[pyo3(signature = (initial_guess=None))]
#[allow(clippy::type_complexity)]
fn calibrate(
&self,
initial_guess: Option<[f64; 9]>,
) -> PyResult<(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, bool)> {
use crate::traits::Calibrator;
let res = self
.inner
.calibrate(initial_guess)
.map_err(|e| PyValueError::new_err(format!("HSCM calibration failed: {e}")))?;
Ok((
res.kappa_v,
res.theta_v,
res.sigma_v,
res.v0,
res.kappa_r,
res.mu_r,
res.sigma_r,
res.rho0,
res.rho2,
res.rmse,
res.converged,
))
}
#[pyo3(signature = (initial_guess=None))]
fn calibrate_to_model(&self, initial_guess: Option<[f64; 9]>) -> PyResult<PyHscmModel> {
use crate::traits::Calibrator;
let res = self
.inner
.calibrate(initial_guess)
.map_err(|e| PyValueError::new_err(format!("HSCM calibration failed: {e}")))?;
Ok(PyHscmModel {
inner: res.to_model(),
})
}
}