use crate::options::TypeFlag;
use crate::Instrument;
use time::Date;
use RustQuant_math::distributions::{Distribution, Gaussian};
use RustQuant_time::{utilities::today, DayCountConvention};
#[derive(derive_builder::Builder)]
pub struct BlackScholesMerton {
pub cost_of_carry: f64,
pub underlying_price: f64,
pub strike_price: f64,
pub volatility: f64,
pub risk_free_rate: f64,
#[builder(default = "None")]
pub evaluation_date: Option<Date>,
pub expiration_date: Date,
pub option_type: TypeFlag,
}
impl Instrument for BlackScholesMerton {
fn price(&self) -> f64 {
self.price()
}
fn error(&self) -> Option<f64> {
None
}
fn valuation_date(&self) -> Date {
self.evaluation_date.unwrap_or(today())
}
fn instrument_type(&self) -> &'static str {
"Black-Scholes-Merton European Option"
}
}
impl BlackScholesMerton {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn new(
cost_of_carry: f64,
underlying_price: f64,
strike_price: f64,
volatility: f64,
risk_free_rate: f64,
evaluation_date: Option<Date>,
expiration_date: Date,
option_type: TypeFlag,
) -> Self {
Self {
cost_of_carry,
underlying_price,
strike_price,
volatility,
risk_free_rate,
evaluation_date,
expiration_date,
option_type,
}
}
#[must_use]
pub fn price(&self) -> f64 {
let (S, K, _, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => S * ((b - r) * T).exp() * n.cdf(d1) - K * (-r * T).exp() * n.cdf(d2),
TypeFlag::Put => {
-S * ((b - r) * T).exp() * n.cdf(-d1) + K * (-r * T).exp() * n.cdf(-d2)
}
}
}
pub fn implied_volatility(&self, price: f64) -> f64 {
crate::options::implied_volatility(
price,
self.underlying_price,
self.strike_price,
self.year_fraction(),
self.risk_free_rate,
self.option_type,
)
}
#[must_use]
pub fn year_fraction(&self) -> f64 {
DayCountConvention::default().day_count_factor(
self.evaluation_date.unwrap_or(today()),
self.expiration_date,
)
}
#[must_use]
fn d1_d2(&self) -> (f64, f64) {
let (S, K, v, _, b) = self.unpack();
let T = self.year_fraction();
let d1 = (1.0 / (v * T.sqrt())) * ((S / K).ln() + (b + 0.5 * v.powi(2)) * T);
let d2 = d1 - v * T.sqrt();
(d1, d2)
}
#[must_use]
fn unpack(&self) -> (f64, f64, f64, f64, f64) {
(
self.underlying_price,
self.strike_price,
self.volatility,
self.risk_free_rate,
self.cost_of_carry,
)
}
#[must_use]
pub fn delta(&self) -> f64 {
let (_, _, _, r, b) = self.unpack();
let T = self.year_fraction();
let d1 = self.d1_d2().0;
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => ((b - r) * T).exp() * n.cdf(d1),
TypeFlag::Put => ((b - r) * T).exp() * (n.cdf(d1) - 1.0),
}
}
#[must_use]
pub fn vanna(&self) -> f64 {
let (_, _, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
let n = Gaussian::default();
-((b - r) * T).exp() * n.pdf(d1) * d2 / v
}
#[must_use]
pub fn charm(&self) -> f64 {
let (_, _, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => {
((b - r) * T).exp()
* (n.pdf(d1) * ((b / (v * T.sqrt())) - (d2 / (2.0 * T))) + (b - r) * n.cdf(d1))
}
TypeFlag::Put => {
((b - r) * T).exp()
* (n.pdf(d1) * ((b / (v * T.sqrt())) - (d2 / (2.0 * T))) - (b - r) * n.cdf(-d1))
}
}
}
#[must_use]
pub fn lambda(&self) -> f64 {
self.delta() * self.underlying_price / self.price()
}
#[must_use]
pub fn gamma(&self) -> f64 {
let n = Gaussian::default();
let (S, _, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, _) = self.d1_d2();
((b - r) * T).exp() * n.pdf(d1) / (S * v * T.sqrt())
}
#[must_use]
pub fn gamma_percent(&self) -> f64 {
self.gamma() * self.underlying_price / 100.0
}
#[must_use]
pub fn zomma(&self) -> f64 {
let (d1, d2) = self.d1_d2();
self.gamma() * ((d1 * d2 - 1.0) / self.volatility)
}
#[must_use]
pub fn zomma_percent(&self) -> f64 {
self.zomma() * self.underlying_price / 100.0
}
#[must_use]
pub fn speed(&self) -> f64 {
let (S, _, v, _, _) = self.unpack();
let T = self.year_fraction();
let (d1, _) = self.d1_d2();
let gamma = self.gamma();
-gamma * (1.0 + d1 / (v * T.sqrt())) / S
}
#[must_use]
pub fn colour(&self) -> f64 {
let (_, _, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
let gamma = self.gamma();
gamma * (r - b + b * d1 / (v * T.sqrt()) + (1.0 - d1 * d2) / (2.0 * T))
}
#[must_use]
pub fn vega(&self) -> f64 {
let (S, _, _, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, _) = self.d1_d2();
let n = Gaussian::default();
S * ((b - r) * T).exp() * n.pdf(d1) * T.sqrt()
}
#[must_use]
pub fn vomma(&self) -> f64 {
let (d1, d2) = self.d1_d2();
self.vega() * d1 * d2 / self.volatility
}
#[must_use]
pub fn ultima(&self) -> f64 {
let (d1, d2) = self.d1_d2();
(self.vomma() / self.volatility) * (d1 * d2 - d1 / d2 + d2 / d1 - 1.0)
}
#[must_use]
pub fn vega_bleed(&self) -> f64 {
let (_, _, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
self.vega() * (r - b + b * d1 / (v * T.sqrt()) - (d1 * d2 + 1.0) / (2.0 * T))
}
#[must_use]
pub fn theta(&self) -> f64 {
let (S, K, v, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, d2) = self.d1_d2();
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => {
-S * ((b - r) * T).exp() * n.pdf(d1) * v / (2.0 * T.sqrt())
- (b - r) * S * ((b - r) * T).exp() * n.cdf(d1)
- r * K * (-r * T).exp() * n.cdf(d2)
}
TypeFlag::Put => {
-S * ((b - r) * T).exp() * n.pdf(d1) * v / (2.0 * T.sqrt())
+ (b - r) * S * ((b - r) * T).exp() * n.cdf(-d1)
+ r * K * (-r * T).exp() * n.cdf(-d2)
}
}
}
#[must_use]
pub fn rho(&self) -> f64 {
let T = self.year_fraction();
match self.option_type {
TypeFlag::Call => {
self.strike_price
* T
* (-self.risk_free_rate * T).exp()
* Gaussian::default().cdf(self.d1_d2().1)
}
TypeFlag::Put => {
-self.strike_price
* T
* (-self.risk_free_rate * T).exp()
* Gaussian::default().cdf(-self.d1_d2().1)
}
}
}
#[must_use]
pub fn phi(&self) -> f64 {
let (S, _, _, r, b) = self.unpack();
let T = self.year_fraction();
let (d1, _) = self.d1_d2();
match self.option_type {
TypeFlag::Call => -T * S * ((b - r) * T).exp() * Gaussian::default().cdf(d1),
TypeFlag::Put => T * S * ((b - r) * T).exp() * Gaussian::default().cdf(-d1),
}
}
#[must_use]
pub fn zeta(&self) -> f64 {
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => n.cdf(self.d1_d2().1),
TypeFlag::Put => n.cdf(-self.d1_d2().1),
}
}
#[must_use]
pub fn strike_delta(&self) -> f64 {
let n = Gaussian::default();
let T = self.year_fraction();
match self.option_type {
TypeFlag::Call => -(-self.risk_free_rate * T).exp() * n.cdf(self.d1_d2().1),
TypeFlag::Put => (-self.risk_free_rate * T).exp() * n.cdf(-self.d1_d2().1),
}
}
#[must_use]
pub fn strike_gamma(&self) -> f64 {
let n = Gaussian::default();
let T = self.year_fraction();
n.pdf(self.d1_d2().1) * (-self.risk_free_rate * T).exp()
/ (self.strike_price * self.volatility * T.sqrt())
}
}
#[cfg(test)]
mod tests_black_scholes_merton {
use super::*;
use time::Duration;
use RustQuant_utils::assert_approx_equal;
#[test]
fn black_scholes_1973() {
let bsm = BlackScholesMerton::new(
0.08,
60.0,
65.0,
0.3,
0.08,
None,
today() + Duration::days(91),
TypeFlag::Call,
);
assert_approx_equal!(bsm.price(), 2.121846776001, 1e-2);
}
#[test]
fn merton_1973() {
let bsm = BlackScholesMerton::new(
0.1 - 0.05,
100.0,
95.0,
0.2,
0.1,
None,
today() + Duration::days(182),
TypeFlag::Put,
);
assert_approx_equal!(bsm.price(), 2.456571166461579, 1e-2);
}
}