use stochastic_rs_distributions::special::norm_cdf;
use stochastic_rs_distributions::special::norm_pdf;
use crate::OptionType;
#[derive(Debug, Clone)]
pub struct CashOrNothingPricer {
pub s: f64,
pub k: f64,
pub cash: f64,
pub r: f64,
pub b: f64,
pub sigma: f64,
pub t: f64,
pub option_type: OptionType,
}
impl CashOrNothingPricer {
pub fn price(&self) -> f64 {
let (_, d2) = self.d1_d2();
let disc = (-self.r * self.t).exp();
match self.option_type {
OptionType::Call => self.cash * disc * norm_cdf(d2),
OptionType::Put => self.cash * disc * norm_cdf(-d2),
}
}
pub fn delta(&self) -> f64 {
let (_, d2) = self.d1_d2();
let disc = (-self.r * self.t).exp();
let denom = self.s * self.sigma * self.t.sqrt();
let sign = match self.option_type {
OptionType::Call => 1.0,
OptionType::Put => -1.0,
};
sign * self.cash * disc * norm_pdf(d2) / denom
}
pub fn gamma(&self) -> f64 {
let (_, d2) = self.d1_d2();
let disc = (-self.r * self.t).exp();
let s = self.s;
let v = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let sign = match self.option_type {
OptionType::Call => 1.0,
OptionType::Put => -1.0,
};
let pdf = norm_pdf(d2);
-sign * self.cash * disc * pdf * (1.0 + d2 * (v * sqrt_t)) / (s * s * v * sqrt_t)
}
pub fn vega(&self) -> f64 {
let (d1, d2) = self.d1_d2();
let disc = (-self.r * self.t).exp();
let sign = match self.option_type {
OptionType::Call => 1.0,
OptionType::Put => -1.0,
};
-sign * self.cash * disc * norm_pdf(d2) * d1 / self.sigma
}
fn d1_d2(&self) -> (f64, f64) {
let v = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let d1 = ((self.s / self.k).ln() + (self.b + 0.5 * v * v) * t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
(d1, d2)
}
}
impl crate::traits::GreeksExt for CashOrNothingPricer {
fn delta(&self) -> f64 {
CashOrNothingPricer::delta(self)
}
fn gamma(&self) -> f64 {
CashOrNothingPricer::gamma(self)
}
fn vega(&self) -> f64 {
CashOrNothingPricer::vega(self)
}
}
#[derive(Debug, Clone)]
pub struct AssetOrNothingPricer {
pub s: f64,
pub k: f64,
pub r: f64,
pub b: f64,
pub sigma: f64,
pub t: f64,
pub option_type: OptionType,
}
impl AssetOrNothingPricer {
pub fn price(&self) -> f64 {
let (d1, _) = self.d1_d2();
let coc = ((self.b - self.r) * self.t).exp();
match self.option_type {
OptionType::Call => self.s * coc * norm_cdf(d1),
OptionType::Put => self.s * coc * norm_cdf(-d1),
}
}
pub fn delta(&self) -> f64 {
let (d1, _) = self.d1_d2();
let coc = ((self.b - self.r) * self.t).exp();
let v = self.sigma;
let sqrt_t = self.t.sqrt();
let cdf_term = match self.option_type {
OptionType::Call => norm_cdf(d1),
OptionType::Put => norm_cdf(-d1),
};
let sign = match self.option_type {
OptionType::Call => 1.0,
OptionType::Put => -1.0,
};
coc * cdf_term + sign * coc * norm_pdf(d1) / (v * sqrt_t)
}
fn d1_d2(&self) -> (f64, f64) {
let v = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let d1 = ((self.s / self.k).ln() + (self.b + 0.5 * v * v) * t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
(d1, d2)
}
}
impl crate::traits::GreeksExt for AssetOrNothingPricer {
fn delta(&self) -> f64 {
AssetOrNothingPricer::delta(self)
}
}
#[derive(Debug, Clone)]
pub struct GapPricer {
pub s: f64,
pub k1: f64,
pub k2: f64,
pub r: f64,
pub b: f64,
pub sigma: f64,
pub t: f64,
pub option_type: OptionType,
}
impl GapPricer {
pub fn price(&self) -> f64 {
let v = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let d1 = ((self.s / self.k1).ln() + (self.b + 0.5 * v * v) * t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
let coc = ((self.b - self.r) * self.t).exp();
let disc = (-self.r * self.t).exp();
match self.option_type {
OptionType::Call => self.s * coc * norm_cdf(d1) - self.k2 * disc * norm_cdf(d2),
OptionType::Put => self.k2 * disc * norm_cdf(-d2) - self.s * coc * norm_cdf(-d1),
}
}
}
#[derive(Debug, Clone)]
pub struct SuperSharePricer {
pub s: f64,
pub x_low: f64,
pub x_high: f64,
pub r: f64,
pub b: f64,
pub sigma: f64,
pub t: f64,
}
impl SuperSharePricer {
pub fn price(&self) -> f64 {
let v = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let d1 = ((self.s / self.x_low).ln() + (self.b + 0.5 * v * v) * t) / (v * sqrt_t);
let d2 = ((self.s / self.x_high).ln() + (self.b + 0.5 * v * v) * t) / (v * sqrt_t);
let coc = ((self.b - self.r) * self.t).exp();
self.s / self.x_low * coc * (norm_cdf(d1) - norm_cdf(d2))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cash_or_nothing_call_closed_form() {
let p = CashOrNothingPricer {
s: 100.0,
k: 80.0,
cash: 10.0,
r: 0.06,
b: 0.06,
sigma: 0.35,
t: 0.75,
option_type: OptionType::Call,
};
let price = p.price();
assert!((price - 7.3444).abs() < 0.005, "price={price}");
}
#[test]
fn cash_call_put_parity() {
let base = CashOrNothingPricer {
s: 100.0,
k: 80.0,
cash: 10.0,
r: 0.06,
b: 0.06,
sigma: 0.35,
t: 0.75,
option_type: OptionType::Call,
};
let put = CashOrNothingPricer {
option_type: OptionType::Put,
..base.clone()
};
let total = base.price() + put.price();
let expected = 10.0 * (-0.06_f64 * 0.75).exp();
assert!((total - expected).abs() < 1e-10, "total={total}");
}
#[test]
fn aon_call_put_parity() {
let c = AssetOrNothingPricer {
s: 100.0,
k: 105.0,
r: 0.05,
b: 0.03,
sigma: 0.25,
t: 1.0,
option_type: OptionType::Call,
};
let p = AssetOrNothingPricer {
option_type: OptionType::Put,
..c.clone()
};
let total = c.price() + p.price();
let expected = 100.0 * ((0.03_f64 - 0.05_f64) * 1.0).exp();
assert!((total - expected).abs() < 1e-9, "total={total}");
}
#[test]
fn vanilla_decomposition() {
let s = 100.0;
let k = 100.0;
let r = 0.05;
let b = 0.05;
let sigma = 0.2;
let t = 1.0;
let aon = AssetOrNothingPricer {
s,
k,
r,
b,
sigma,
t,
option_type: OptionType::Call,
};
let con = CashOrNothingPricer {
s,
k,
cash: 1.0,
r,
b,
sigma,
t,
option_type: OptionType::Call,
};
let vanilla = aon.price() - k * con.price();
assert!((vanilla - 10.4506).abs() < 0.005, "decomposition={vanilla}");
}
#[test]
fn gap_reduces_to_vanilla() {
let p = GapPricer {
s: 100.0,
k1: 100.0,
k2: 100.0,
r: 0.05,
b: 0.05,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Call,
};
let price = p.price();
assert!((price - 10.4506).abs() < 0.005, "gap={price}");
}
#[test]
fn gap_haug_negative_payoff() {
let p = GapPricer {
s: 50.0,
k1: 50.0,
k2: 57.0,
r: 0.09,
b: 0.09,
sigma: 0.20,
t: 0.5,
option_type: OptionType::Call,
};
let price = p.price();
assert!(price.abs() < 0.05, "gap call={price}");
assert!(price < 0.0);
}
#[test]
fn supershare_positive() {
let p = SuperSharePricer {
s: 100.0,
x_low: 90.0,
x_high: 110.0,
r: 0.05,
b: 0.0,
sigma: 0.2,
t: 0.25,
};
let price = p.price();
assert!(price > 0.0, "supershare={price}");
assert!(price < p.s, "supershare must be < S");
}
#[test]
fn cash_delta_matches_fd() {
let h = 0.01;
let base = CashOrNothingPricer {
s: 100.0,
k: 100.0,
cash: 10.0,
r: 0.05,
b: 0.02,
sigma: 0.25,
t: 0.5,
option_type: OptionType::Call,
};
let up = CashOrNothingPricer {
s: 100.0 + h,
..base.clone()
};
let dn = CashOrNothingPricer {
s: 100.0 - h,
..base.clone()
};
let fd = (up.price() - dn.price()) / (2.0 * h);
let analytic = base.delta();
assert!((fd - analytic).abs() < 1e-4, "fd={fd}, analytic={analytic}");
}
}