use crate::math::distributions::{gaussian::Gaussian, Distribution};
#[derive(Debug, Clone, Copy)]
#[allow(clippy::module_name_repetitions)]
pub struct BarrierOption {
pub initial_price: f64,
pub strike_price: f64,
pub barrier: f64,
pub time_to_expiry: f64,
pub risk_free_rate: f64,
pub volatility: f64,
pub rebate: f64,
pub dividend_yield: f64,
}
#[derive(Debug, Clone, Copy)]
#[allow(clippy::module_name_repetitions)]
pub enum BarrierType {
CUI,
CDI,
CUO,
CDO,
PUI,
PDI,
PUO,
PDO,
}
impl BarrierOption {
#[must_use]
pub fn price(&self, type_flag: BarrierType) -> f64 {
let S = self.initial_price;
let X = self.strike_price;
let H = self.barrier;
let t = self.time_to_expiry;
let r = self.risk_free_rate;
let v = self.volatility;
let K = self.rebate;
let q = self.dividend_yield;
let b: f64 = r - q;
let mu: f64 = (b - v * v / 2.) / (v * v);
let lambda: f64 = (mu * mu + 2. * r / (v * v)).sqrt();
let z: f64 = (H / S).ln() / (v * t.sqrt()) + lambda * v * t.sqrt();
let x1: f64 = (S / X).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
let x2: f64 = (S / H).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
let y1: f64 = (H * H / (S * X)).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
let y2: f64 = (H / S).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
let norm = Gaussian::default();
let A = |phi: f64| -> f64 {
let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x1);
let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x1 - phi * v * (t).sqrt());
term1 - term2
};
let B = |phi: f64| -> f64 {
let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x2);
let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x2 - phi * v * (t).sqrt());
term1 - term2
};
let C = |phi: f64, eta: f64| -> f64 {
let term1: f64 =
phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y1);
let term2: f64 = phi
* X
* (-r * t).exp()
* (H / S).powf(2. * mu)
* norm.cdf(eta * y1 - eta * v * t.sqrt());
term1 - term2
};
let D = |phi: f64, eta: f64| -> f64 {
let term1: f64 =
phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y2);
let term2: f64 = phi
* X
* (-r * t).exp()
* (H / S).powf(2. * mu)
* norm.cdf(eta * y2 - eta * v * (t).sqrt());
term1 - term2
};
let E = |eta: f64| -> f64 {
let term1: f64 = norm.cdf(eta * x2 - eta * v * (t).sqrt());
let term2: f64 = (H / S).powf(2. * mu) * norm.cdf(eta * y2 - eta * v * t.sqrt());
K * (-r * t).exp() * (term1 - term2)
};
let F = |eta: f64| -> f64 {
let term1: f64 = (H / S).powf(mu + lambda) * norm.cdf(eta * z);
let term2: f64 =
(H / S).powf(mu - lambda) * norm.cdf(eta * z - 2. * eta * lambda * v * t.sqrt());
K * (term1 + term2)
};
if X >= H {
match type_flag {
BarrierType::CDI if S >= H => C(1., 1.) + E(1.),
BarrierType::CUI if S <= H => A(1.) + E(-1.),
BarrierType::PDI if S >= H => B(-1.) - C(-1., 1.) + D(-1., 1.) + E(1.),
BarrierType::PUI if S <= H => A(-1.) - B(-1.) + D(-1., -1.) + E(-1.),
BarrierType::CDO if S >= H => A(1.) - C(1., 1.) + F(1.),
BarrierType::CUO if S <= H => F(-1.),
BarrierType::PDO if S >= H => A(-1.) - B(-1.) + C(-1., 1.) - D(-1., 1.) + F(1.),
BarrierType::PUO if S <= H => B(-1.) - D(-1., -1.) + F(-1.),
_ => panic!("Barrier touched - check barrier and type flag."),
}
}
else {
match type_flag {
BarrierType::CDI if S >= H => A(1.) - B(1.) + D(1., 1.) + E(1.),
BarrierType::CUI if S <= H => B(1.) - C(1., -1.) + D(1., -1.) + E(-1.),
BarrierType::PDI if S >= H => A(-1.) + E(1.),
BarrierType::PUI if S <= H => C(-1., -1.) + E(-1.),
BarrierType::CDO if S >= H => B(1.) - D(1., 1.) + F(1.),
BarrierType::CUO if S <= H => A(1.) - B(1.) + C(1., -1.) - D(1., -1.) + F(-1.),
BarrierType::PDO if S >= H => F(1.),
BarrierType::PUO if S <= H => A(-1.) - C(-1., -1.) + F(-1.),
_ => panic!("Barrier touched - check barrier and type flag."),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_approx_equal;
use crate::RUSTQUANT_EPSILON;
static S_ABOVE_H: BarrierOption = BarrierOption {
initial_price: 110.0,
strike_price: 100.0,
barrier: 105.0,
time_to_expiry: 1.0,
risk_free_rate: 0.05,
volatility: 0.2,
rebate: 0.0,
dividend_yield: 0.01,
};
#[allow(clippy::similar_names)]
#[test]
fn test_S_above_H() {
let cdi = S_ABOVE_H.price(BarrierType::CDI);
let cdo = S_ABOVE_H.price(BarrierType::CDO);
let pdi = S_ABOVE_H.price(BarrierType::PDI);
let pdo = S_ABOVE_H.price(BarrierType::PDO);
assert_approx_equal!(cdi, 9.504_815_211_050_698, RUSTQUANT_EPSILON);
assert_approx_equal!(cdo, 7.295_021_649_666_765, RUSTQUANT_EPSILON);
assert_approx_equal!(pdi, 3.017_297_598_380_377_4, RUSTQUANT_EPSILON);
assert_approx_equal!(pdo, 0.000_000, RUSTQUANT_EPSILON);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn cui_panic() {
let _ = S_ABOVE_H.price(BarrierType::CUI);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn cuo_panic() {
let _ = S_ABOVE_H.price(BarrierType::CUO);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn pui_panic() {
let _ = S_ABOVE_H.price(BarrierType::PUI);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn puo_panic() {
let _ = S_ABOVE_H.price(BarrierType::PUO);
}
static S_BELOW_H: BarrierOption = BarrierOption {
initial_price: 90.0,
strike_price: 100.0,
barrier: 105.0,
time_to_expiry: 1.0,
risk_free_rate: 0.05,
volatility: 0.2,
rebate: 0.0,
dividend_yield: 0.01,
};
#[allow(clippy::similar_names)]
#[test]
fn test_S_below_H() {
let cui = S_BELOW_H.price(BarrierType::CUI);
let cuo = S_BELOW_H.price(BarrierType::CUO);
let pui = S_BELOW_H.price(BarrierType::PUI);
let puo = S_BELOW_H.price(BarrierType::PUO);
assert_approx_equal!(cui, 4.692_603_355_387_815, RUSTQUANT_EPSILON);
assert_approx_equal!(cuo, 0.022_448_676_101_445_74, RUSTQUANT_EPSILON);
assert_approx_equal!(pui, 1.359_553_168_024_573_8, RUSTQUANT_EPSILON);
assert_approx_equal!(puo, 9.373_956_276_110_954, RUSTQUANT_EPSILON);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn cdi_panic() {
let _ = S_BELOW_H.price(BarrierType::CDI);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn cdo_panic() {
let _ = S_BELOW_H.price(BarrierType::CDO);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn pdi_panic() {
let _ = S_BELOW_H.price(BarrierType::PDI);
}
#[test]
#[should_panic(expected = "Barrier touched - check barrier and type flag.")]
fn pdo_panic() {
let _ = S_BELOW_H.price(BarrierType::PDO);
}
}