use ndarray::Array1;
use ndarray::Array2;
use ndarray::s;
use stochastic_rs_stochastic::diffusion::gbm::Gbm;
use crate::traits::PricerExt;
use crate::traits::ProcessExt;
use crate::traits::TimeExt;
fn laplace_pdf(x: f64, l: f64) -> f64 {
if l <= 0.0 {
return 0.0;
}
(-(x.abs()) / l).exp() / (2.0 * l)
}
fn laplace_cdf(x: f64, l: f64) -> f64 {
if l <= 0.0 {
return if x < 0.0 { 0.0 } else { 1.0 };
}
0.5 * (1.0 + x.signum() * (1.0 - (-(x.abs()) / l).exp()))
}
#[derive(Debug, Clone)]
pub struct GbmMalliavinPricer {
pub s: f64,
pub v: f64,
pub k: f64,
pub r: f64,
pub q: Option<f64>,
pub tau: Option<f64>,
pub eval: Option<chrono::NaiveDate>,
pub expiration: Option<chrono::NaiveDate>,
pub n_paths: usize,
pub n_steps: usize,
pub t_eval: f64,
}
impl TimeExt for GbmMalliavinPricer {
fn tau(&self) -> Option<f64> {
self.tau
}
fn eval(&self) -> Option<chrono::NaiveDate> {
self.eval
}
fn expiration(&self) -> Option<chrono::NaiveDate> {
self.expiration
}
}
impl PricerExt for GbmMalliavinPricer {
fn calculate_call_put(&self) -> (f64, f64) {
let t = self.t_eval;
let (_s_t, c_t) = self.conditional_call_malliavin(t);
self.call_put_from_conditional(t, &c_t)
}
fn calculate_price(&self) -> f64 {
self.calculate_call_put().0
}
}
impl GbmMalliavinPricer {
fn call_put_from_conditional(&self, t_eval: f64, c_t: &Array1<f64>) -> (f64, f64) {
let T = self.calculate_tau_in_years();
assert!(t_eval > 0.0 && t_eval < T, "t_eval must be in (0, T)");
let disc_0t = (-self.r * t_eval).exp();
let mut sum = 0.0_f64;
let mut count = 0_usize;
for &v in c_t.iter() {
if v.is_finite() {
sum += v;
count += 1;
}
}
let avg_c_t = if count > 0 { sum / count as f64 } else { 0.0 };
let mut call_0 = disc_0t * avg_c_t;
if call_0 < 0.0 {
call_0 = 0.0;
}
let q = self.q.unwrap_or(0.0);
let df_rT = (-self.r * T).exp();
let df_qT = (-q * T).exp();
let mut put_0 = call_0 + self.k * df_rT - self.s * df_qT;
if put_0 < 0.0 {
put_0 = 0.0;
}
(call_0, put_0)
}
pub fn calculate_call_put_localized(&self) -> (f64, f64) {
let t = self.t_eval;
let (_s_t, c_t) = self.conditional_call_malliavin_localized(t);
self.call_put_from_conditional(t, &c_t)
}
fn sample_paths(&self) -> Array2<f64> {
let T = self.calculate_tau_in_years();
let mu = self.r - self.q.unwrap_or(0.0);
let gbm = Gbm::new(mu, self.v, self.n_steps, Some(self.s), Some(T));
let m = self.n_paths;
let n = self.n_steps;
let mut S = Array2::<f64>::zeros((m, n));
for i in 0..m {
let path = gbm.sample();
S.slice_mut(s![i, ..]).assign(&path);
}
S
}
pub fn conditional_call_malliavin(&self, t_eval: f64) -> (Array1<f64>, Array1<f64>) {
let T = self.calculate_tau_in_years();
assert!(t_eval > 0.0 && t_eval < T, "t_eval must be in (0, T)");
let q = self.q.unwrap_or(0.0);
let mu = self.r - q;
let dt = T / (self.n_steps - 1) as f64;
let S = self.sample_paths();
let m = S.nrows();
let n = S.ncols();
let mut W = Array2::<f64>::zeros((m, n));
for i in 0..m {
let mut w = 0.0;
W[[i, 0]] = w;
for k in 1..n {
let s_prev = S[[i, k - 1]];
let s_curr = S[[i, k]];
let dW = if s_prev.abs() > 1e-14 {
(s_curr - s_prev - mu * s_prev * dt) / (self.v * s_prev)
} else {
0.0
};
w += dW;
W[[i, k]] = w;
}
}
let k_t = ((t_eval / dt).round() as usize).min(n - 1);
let s_t = S.slice(s![.., k_t]).to_owned();
let s_T = S.slice(s![.., n - 1]).to_owned();
let w_t = W.slice(s![.., k_t]).to_owned();
let w_T = W.slice(s![.., n - 1]).to_owned();
let payoff: Array1<f64> = s_T.iter().map(|&x_T| (x_T - self.k).max(0.0)).collect();
let mut coef = Array1::<f64>::zeros(m);
for i in 0..m {
let st = s_t[i];
if st.abs() < 1e-14 {
coef[i] = 0.0;
} else {
let num = (T * w_t[i] - t_eval * w_T[i]) / (T - t_eval) + self.v * t_eval;
coef[i] = num / st;
}
}
let discount_tT = (-self.r * (T - t_eval)).exp();
let mut c_hat = Array1::<f64>::zeros(m);
for i in 0..m {
let x = s_t[i];
let mut num = 0.0;
let mut den = 0.0;
for j in 0..m {
if s_t[j] >= x {
let w = coef[j];
num += payoff[j] * w;
den += w;
}
}
c_hat[i] = if den.abs() > 1e-14 {
discount_tT * (num / den)
} else {
f64::NAN
};
}
(s_t, c_hat)
}
pub fn conditional_call_malliavin_localized(&self, t_eval: f64) -> (Array1<f64>, Array1<f64>) {
let T = self.calculate_tau_in_years();
assert!(t_eval > 0.0 && t_eval < T, "t_eval must be in (0, T)");
let q = self.q.unwrap_or(0.0);
let mu = self.r - q;
let dt = T / (self.n_steps - 1) as f64;
let S = self.sample_paths();
let m = S.nrows();
let n = S.ncols();
let mut W = Array2::<f64>::zeros((m, n));
for i in 0..m {
let mut w = 0.0;
W[[i, 0]] = w;
for k in 1..n {
let s_prev = S[[i, k - 1]];
let s_curr = S[[i, k]];
let dW = if s_prev.abs() > 1e-14 {
(s_curr - s_prev - mu * s_prev * dt) / (self.v * s_prev)
} else {
0.0
};
w += dW;
W[[i, k]] = w;
}
}
let k_t = ((t_eval / dt).round() as usize).min(n - 1);
let s_t = S.slice(s![.., k_t]).to_owned();
let s_T = S.slice(s![.., n - 1]).to_owned();
let w_t = W.slice(s![.., k_t]).to_owned();
let w_T = W.slice(s![.., n - 1]).to_owned();
let payoff: Array1<f64> = s_T.iter().map(|&x_T| (x_T - self.k).max(0.0)).collect();
let mut delta_w = Array1::<f64>::zeros(m);
for i in 0..m {
delta_w[i] = T * w_t[i] - t_eval * w_T[i] + (T - t_eval) * t_eval * self.v;
}
let den_loc: Array1<f64> = payoff.iter().map(|&po| po * po).collect();
let mut t2 = Array1::<f64>::zeros(m);
let denom_scalar = t_eval * (T - t_eval) * self.v;
for i in 0..m {
let st = s_t[i];
if st.abs() > 1e-14 && denom_scalar.abs() > 1e-14 {
t2[i] = delta_w[i] / (denom_scalar * st);
} else {
t2[i] = 0.0;
}
}
let mut num_loc = Array1::<f64>::zeros(m);
for i in 0..m {
num_loc[i] = den_loc[i] * t2[i] * t2[i];
}
let mean_den_loc = den_loc.mean().unwrap_or(0.0);
let mean_num_loc = num_loc.mean().unwrap_or(0.0);
let lf = if mean_den_loc > 0.0 && mean_num_loc >= 0.0 {
(mean_num_loc / mean_den_loc).sqrt()
} else {
0.0
};
let sigma2 = self.v * self.v;
let h = mu - 0.5 * sigma2;
let t = t_eval;
let numer_l = T + sigma2 * t * (T - t);
let denom_l = sigma2 * t * (T - t);
let l1 = if denom_l > 0.0 && self.s > 0.0 {
(1.0 / self.s) * (-(h + sigma2) * t).exp() * (numer_l / denom_l).sqrt()
} else if lf > 0.0 {
lf
} else {
1e-8
};
let discount_tT = (-self.r * (T - t_eval)).exp();
let mut c_hat_loc = Array1::<f64>::zeros(m);
for i in 0..m {
let x = s_t[i];
let mut num_i = 0.0;
let mut den_i = 0.0;
for j in 0..m {
let diff = s_t[j] - x;
let heav = if diff >= 0.0 { 1.0 } else { 0.0 };
let lap_df_l1 = laplace_pdf(diff, l1);
let lap_cdf_l1 = laplace_cdf(diff, l1);
let pp_loc_1 = lap_df_l1 + (heav - lap_cdf_l1) * t2[j];
let lap_df_lf = laplace_pdf(diff, lf);
let lap_cdf_lf = laplace_cdf(diff, lf);
let pp_loc_f = lap_df_lf + (heav - lap_cdf_lf) * t2[j];
den_i += pp_loc_1;
num_i += payoff[j] * pp_loc_f;
}
c_hat_loc[i] = if den_i.abs() > 1e-14 {
discount_tT * (num_i / den_i)
} else {
f64::NAN
};
}
(s_t, c_hat_loc)
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use super::*;
#[test]
fn malliavin_pricer_returns_finite_non_negative_prices() {
let eval = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
let expiration = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
let pricer = GbmMalliavinPricer {
s: 100.0,
v: 0.1,
k: 99.99,
r: 0.1,
q: Some(0.0),
tau: Some(1.0),
eval: Some(eval),
expiration: Some(expiration),
n_paths: 2_000,
n_steps: 128,
t_eval: 0.5,
};
let (call, put) = pricer.calculate_call_put();
println!("Call price: {}", call);
println!("Put price: {}", put);
assert!(call.is_finite(), "Call price should be finite");
assert!(put.is_finite(), "Put price should be finite");
assert!(call >= 0.0, "Call price should be non-negative");
assert!(put >= 0.0, "Put price should be non-negative");
assert!(call < pricer.s * 2.0, "Call price is unreasonably large");
assert!(put < pricer.k * 2.0, "Put price is unreasonably large");
}
#[test]
fn malliavin_pricer_localized_returns_finite_non_negative_prices() {
let eval = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
let expiration = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
let pricer = GbmMalliavinPricer {
s: 100.0,
v: 0.1,
k: 99.99,
r: 0.1,
q: Some(0.0),
tau: Some(1.0),
eval: Some(eval),
expiration: Some(expiration),
n_paths: 2_000,
n_steps: 128,
t_eval: 0.5,
};
let (call, put) = pricer.calculate_call_put_localized();
println!("Localized call price: {}", call);
println!("Localized put price: {}", put);
assert!(call.is_finite(), "Localized call price should be finite");
assert!(put.is_finite(), "Localized put price should be finite");
assert!(call >= 0.0, "Localized call price should be non-negative");
assert!(put >= 0.0, "Localized put price should be non-negative");
assert!(
call < pricer.s * 2.0,
"Localized call price is unreasonably large"
);
assert!(
put < pricer.k * 2.0,
"Localized put price is unreasonably large"
);
}
}