use crate::instruments::options::TypeFlag;
use crate::math::distributions::{Distribution, Gaussian};
use crate::time::{today, DayCountConvention};
use time::Date;
pub struct Bachelier {
pub underlying_price: f64,
pub strike_price: f64,
pub volatility: f64,
pub evaluation_date: Option<Date>,
pub expiration_date: Date,
pub option_type: TypeFlag,
}
#[allow(clippy::module_name_repetitions)]
#[derive(derive_builder::Builder, Debug, Clone, Copy)]
pub struct ModifiedBachelier {
pub underlying_price: f64,
pub strike_price: f64,
pub volatility: f64,
pub risk_free_rate: f64,
pub dividend_yield: f64,
#[builder(default = "None")]
pub evaluation_date: Option<Date>,
pub expiration_date: Date,
pub option_type: TypeFlag,
}
impl Bachelier {
#[must_use]
pub fn new(
underlying_price: f64,
strike_price: f64,
volatility: f64,
evaluation_date: Option<Date>,
expiration_date: Date,
option_type: TypeFlag,
) -> Self {
Self {
underlying_price,
strike_price,
volatility,
evaluation_date,
expiration_date,
option_type,
}
}
#[must_use]
pub fn price(&self) -> f64 {
let S = self.underlying_price;
let K = self.strike_price;
let v = self.volatility;
let T = DayCountConvention::default().day_count_factor(
self.evaluation_date.unwrap_or(today()),
self.expiration_date,
);
let d1 = (S - K) / (v * T.sqrt());
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => (S - K) * n.cdf(d1) + v * T.sqrt() * n.pdf(d1),
TypeFlag::Put => (K - S) * n.cdf(-d1) + v * T.sqrt() * n.pdf(-d1),
}
}
}
impl ModifiedBachelier {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
underlying_price: f64,
strike_price: f64,
volatility: f64,
risk_free_rate: f64,
dividend_yield: f64,
evaluation_date: Option<Date>,
expiration_date: Date,
option_type: TypeFlag,
) -> Self {
Self {
underlying_price,
strike_price,
volatility,
risk_free_rate,
dividend_yield,
evaluation_date,
expiration_date,
option_type,
}
}
#[must_use]
pub fn price(&self) -> f64 {
let S = self.underlying_price;
let K = self.strike_price;
let v = self.volatility;
let r = self.risk_free_rate;
let T = DayCountConvention::default().day_count_factor(
self.evaluation_date.unwrap_or(today()),
self.expiration_date,
);
let d1 = (S - K) / (v * T.sqrt());
let df = (-r * T).exp();
let n = Gaussian::default();
match self.option_type {
TypeFlag::Call => df * ((S - K) * n.cdf(d1) + v * T.sqrt() * n.pdf(d1)),
TypeFlag::Put => df * ((K - S) * n.cdf(-d1) + v * T.sqrt() * n.pdf(-d1)),
}
}
}
#[cfg(test)]
mod tests_bachelier {
use super::*;
use crate::assert_approx_equal;
use time::Duration;
#[test]
fn bachelier() {
let bachelier = Bachelier::new(
100.0,
100.0,
0.2,
None,
today() + Duration::days(365),
TypeFlag::Call,
);
assert_approx_equal!(bachelier.price(), 0.0797012078791442, 1e-2);
}
#[test]
fn bachelier_modified() {
let bachelier = ModifiedBachelier::new(
100.0,
100.0,
0.2,
0.05,
0.0,
None,
today() + Duration::days(365),
TypeFlag::Call,
);
assert_approx_equal!(bachelier.price(), 0.07589, 1e-2);
}
}