use crate::errors::QlResult;
use crate::handle::Handle;
use crate::math::{array::Array, matrix::Matrix};
use crate::patterns::observable::{AsObservable, Observable};
use crate::quotes::Quote;
use crate::shared::{Shared, shared};
use crate::stochasticprocess::StochasticProcess;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::date::Date;
use crate::types::{Real, Size, Time};
use crate::{fail, require};
use super::HestonProcess;
pub struct BatesProcess {
heston: Shared<HestonProcess>,
lambda: Real,
nu: Real,
delta: Real,
}
impl BatesProcess {
#[allow(clippy::too_many_arguments)]
pub fn new(
risk_free_rate: Handle<dyn YieldTermStructure>,
dividend_yield: Handle<dyn YieldTermStructure>,
s0: Handle<dyn Quote>,
v0: Real,
kappa: Real,
theta: Real,
sigma: Real,
rho: Real,
lambda: Real,
nu: Real,
delta: Real,
) -> QlResult<Self> {
Self::validate_parameters(&Array::from([
theta, kappa, sigma, rho, v0, nu, delta, lambda,
]))?;
require!(
risk_free_rate.current_link()?.reference_date()? != Date::null()
&& dividend_yield.current_link()?.reference_date()? != Date::null(),
"Bates curves require non-null reference dates"
);
let spot = s0.current_link()?.value()?;
require!(
spot.is_finite() && spot > 0.0,
"Bates spot must be finite and positive"
);
Ok(Self {
heston: shared(HestonProcess::new(
risk_free_rate,
dividend_yield,
s0,
v0,
kappa,
theta,
sigma,
rho,
)),
lambda,
nu,
delta,
})
}
pub(crate) fn validate_parameters(params: &Array) -> QlResult<()> {
require!(params.size() == 8, "Bates model requires eight parameters");
require!(
params.iter().all(|p| p.is_finite()),
"Bates parameters must be finite"
);
require!(
[0, 1, 2, 4].iter().all(|&i| params[i] > 0.0),
"Bates theta, kappa, sigma and v0 must be positive"
);
require!(
(-1.0..=1.0).contains(¶ms[3]),
"Bates rho must lie in [-1, 1]"
);
require!(
params[6] >= 0.0 && params[7] >= 0.0,
"Bates delta and lambda must be non-negative"
);
let variance = params[6] * params[6];
let exponent = params[5] + 0.5 * variance;
let compensator = exponent.exp_m1();
let exponential_moment = exponent.exp();
require!(
variance.is_finite()
&& exponent.is_finite()
&& compensator.is_finite()
&& exponential_moment.is_finite()
&& exponential_moment > 0.0
&& (params[7] * compensator).is_finite(),
"Bates jump compensation is not representable"
);
Ok(())
}
pub(crate) fn with_parameters(&self, params: &Array) -> Self {
Self {
heston: shared(HestonProcess::new(
self.risk_free_rate(),
self.dividend_yield(),
self.s0(),
params[4],
params[1],
params[0],
params[2],
params[3],
)),
nu: params[5],
delta: params[6],
lambda: params[7],
}
}
pub fn heston_process(&self) -> Shared<HestonProcess> {
self.heston.clone()
}
pub fn v0(&self) -> Real {
self.heston.v0()
}
pub fn kappa(&self) -> Real {
self.heston.kappa()
}
pub fn theta(&self) -> Real {
self.heston.theta()
}
pub fn sigma(&self) -> Real {
self.heston.sigma()
}
pub fn rho(&self) -> Real {
self.heston.rho()
}
pub fn lambda(&self) -> Real {
self.lambda
}
pub fn nu(&self) -> Real {
self.nu
}
pub fn delta(&self) -> Real {
self.delta
}
pub fn m(&self) -> Real {
(self.nu + 0.5 * self.delta * self.delta).exp_m1()
}
pub fn risk_free_rate(&self) -> Handle<dyn YieldTermStructure> {
self.heston.risk_free_rate()
}
pub fn dividend_yield(&self) -> Handle<dyn YieldTermStructure> {
self.heston.dividend_yield()
}
pub fn s0(&self) -> Handle<dyn Quote> {
self.heston.s0()
}
pub fn size(&self) -> Size {
2
}
pub fn factors(&self) -> Size {
4
}
pub fn initial_values(&self) -> QlResult<Array> {
let spot = self.s0().current_link()?.value()?;
require!(
spot.is_finite() && spot > 0.0,
"Bates spot must be finite and positive"
);
Ok(Array::from([spot, self.v0()]))
}
pub fn time(&self, date: &Date) -> QlResult<Time> {
require!(*date != Date::null(), "Bates time requires a non-null date");
require!(
self.risk_free_rate().current_link()?.reference_date()? != Date::null(),
"Bates risk-free curve requires a non-null reference date"
);
self.heston.time(date)
}
pub fn drift(&self, _time: Time, _state: &Array) -> QlResult<Array> {
fail!("BatesProcess does not provide path drift")
}
pub fn diffusion(&self, _time: Time, _state: &Array) -> QlResult<Matrix> {
fail!("BatesProcess does not provide path diffusion")
}
pub fn apply(&self, _state: &Array, _increment: &Array) -> QlResult<Array> {
fail!("BatesProcess does not provide path apply")
}
pub fn expectation(&self, _time: Time, _state: &Array, _dt: Time) -> QlResult<Array> {
fail!("BatesProcess does not provide Gaussian expectation")
}
pub fn std_deviation(&self, _time: Time, _state: &Array, _dt: Time) -> QlResult<Matrix> {
fail!("BatesProcess does not provide Gaussian standard deviation")
}
pub fn covariance(&self, _time: Time, _state: &Array, _dt: Time) -> QlResult<Matrix> {
fail!("BatesProcess does not provide Gaussian covariance")
}
pub fn evolve(&self, _time: Time, _state: &Array, _dt: Time, _dw: &Array) -> QlResult<Array> {
fail!("BatesProcess does not provide Gaussian evolution")
}
}
impl AsObservable for BatesProcess {
fn observable(&self) -> &Observable {
self.heston.observable()
}
}