use rayon::prelude::*;
use stochastic_rs_distributions::special::norm_cdf;
use crate::OptionType;
use crate::traits::FloatExt;
#[derive(Debug, Clone)]
pub struct MargrabePricer {
pub s1: f64,
pub s2: f64,
pub sigma1: f64,
pub sigma2: f64,
pub rho: f64,
pub q1: f64,
pub q2: f64,
pub t: f64,
}
impl MargrabePricer {
pub fn price(&self) -> f64 {
let v_sq = self.sigma1 * self.sigma1 + self.sigma2 * self.sigma2
- 2.0 * self.rho * self.sigma1 * self.sigma2;
if v_sq < 1e-14 {
return (self.s1 * (-self.q1 * self.t).exp() - self.s2 * (-self.q2 * self.t).exp()).max(0.0);
}
let v = v_sq.sqrt();
let sqrt_t = self.t.sqrt();
let d1 = ((self.s1 / self.s2).ln() + (self.q2 - self.q1 + 0.5 * v_sq) * self.t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
self.s1 * (-self.q1 * self.t).exp() * norm_cdf(d1)
- self.s2 * (-self.q2 * self.t).exp() * norm_cdf(d2)
}
pub fn delta1(&self) -> f64 {
let v_sq = self.sigma1 * self.sigma1 + self.sigma2 * self.sigma2
- 2.0 * self.rho * self.sigma1 * self.sigma2;
if v_sq < 1e-14 {
return (-self.q1 * self.t).exp();
}
let v = v_sq.sqrt();
let sqrt_t = self.t.sqrt();
let d1 = ((self.s1 / self.s2).ln() + (self.q2 - self.q1 + 0.5 * v_sq) * self.t) / (v * sqrt_t);
(-self.q1 * self.t).exp() * norm_cdf(d1)
}
pub fn delta2(&self) -> f64 {
let v_sq = self.sigma1 * self.sigma1 + self.sigma2 * self.sigma2
- 2.0 * self.rho * self.sigma1 * self.sigma2;
if v_sq < 1e-14 {
return -(-self.q2 * self.t).exp();
}
let v = v_sq.sqrt();
let sqrt_t = self.t.sqrt();
let d1 = ((self.s1 / self.s2).ln() + (self.q2 - self.q1 + 0.5 * v_sq) * self.t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
-(-self.q2 * self.t).exp() * norm_cdf(d2)
}
}
#[derive(Debug, Clone)]
pub struct McSpreadPricer {
pub s1: f64,
pub s2: f64,
pub k: f64,
pub sigma1: f64,
pub sigma2: f64,
pub rho: f64,
pub r: f64,
pub q1: f64,
pub q2: f64,
pub t: f64,
pub option_type: OptionType,
pub n_paths: usize,
}
impl McSpreadPricer {
pub fn price(&self) -> f64 {
let phi = match self.option_type {
OptionType::Call => 1.0,
OptionType::Put => -1.0,
};
let drift1 = (self.r - self.q1 - 0.5 * self.sigma1 * self.sigma1) * self.t;
let drift2 = (self.r - self.q2 - 0.5 * self.sigma2 * self.sigma2) * self.t;
let vol1 = self.sigma1 * self.t.sqrt();
let vol2 = self.sigma2 * self.t.sqrt();
let rho = self.rho;
let sqrt_one_minus_rho2 = (1.0 - rho * rho).max(0.0).sqrt();
let mut all_z = vec![0.0_f64; self.n_paths * 2];
<f64 as FloatExt>::fill_standard_normal_slice(&mut all_z);
let sum: f64 = (0..self.n_paths)
.into_par_iter()
.map(|i| {
let z1 = all_z[2 * i];
let z2_indep = all_z[2 * i + 1];
let z2 = rho * z1 + sqrt_one_minus_rho2 * z2_indep;
let s1_t = self.s1 * (drift1 + vol1 * z1).exp();
let s2_t = self.s2 * (drift2 + vol2 * z2).exp();
((phi * (s1_t - s2_t - self.k)).max(0.0)) as f64
})
.sum();
(-self.r * self.t).exp() * sum / self.n_paths as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn margrabe_perfect_correlation_equal_vol() {
let p = MargrabePricer {
s1: 100.0,
s2: 100.0,
sigma1: 0.2,
sigma2: 0.2,
rho: 1.0,
q1: 0.0,
q2: 0.0,
t: 1.0,
};
let price = p.price();
assert!(price.abs() < 1e-8, "perfect-corr Margrabe={price}");
}
#[test]
fn margrabe_atm_zero_corr() {
let p = MargrabePricer {
s1: 100.0,
s2: 100.0,
sigma1: 0.20,
sigma2: 0.20,
rho: 0.0,
q1: 0.0,
q2: 0.0,
t: 1.0,
};
let price = p.price();
let expected = 11.246;
assert!((price - expected).abs() < 0.05, "Margrabe ATM={price}");
}
#[test]
fn margrabe_deep_itm() {
let p = MargrabePricer {
s1: 200.0,
s2: 100.0,
sigma1: 0.20,
sigma2: 0.20,
rho: 0.5,
q1: 0.01,
q2: 0.02,
t: 0.5,
};
let price = p.price();
let intrinsic = 200.0 * (-0.01_f64 * 0.5).exp() - 100.0 * (-0.02_f64 * 0.5).exp();
assert!(
price > intrinsic,
"Margrabe deep ITM={price} vs intrinsic={intrinsic}"
);
}
#[test]
fn margrabe_matches_mc_zero_strike() {
let m = MargrabePricer {
s1: 110.0,
s2: 100.0,
sigma1: 0.25,
sigma2: 0.20,
rho: 0.4,
q1: 0.0,
q2: 0.0,
t: 1.0,
};
let mc = McSpreadPricer {
s1: 110.0,
s2: 100.0,
k: 0.0,
sigma1: 0.25,
sigma2: 0.20,
rho: 0.4,
r: 0.0,
q1: 0.0,
q2: 0.0,
t: 1.0,
option_type: OptionType::Call,
n_paths: 100_000,
};
let m_price = m.price();
let mc_price = mc.price();
let rel = (m_price - mc_price).abs() / m_price;
assert!(rel < 0.02, "margrabe={m_price}, mc={mc_price}, rel={rel}");
}
}