use super::OptionContract;
use crate::{
math::distributions::{gaussian::Gaussian, Distribution},
time::{today, DayCountConvention},
};
use time::Date;
#[derive(derive_builder::Builder, Debug, Clone, Copy)]
pub struct AsianOptionAnalyticBackend {
pub initial_price: f64,
pub strike_price: f64,
pub risk_free_rate: f64,
pub volatility: f64,
pub dividend_rate: f64,
#[builder(default = "None")]
pub evaluation_date: Option<Date>,
pub expiration_date: Date,
}
impl AsianOption {
#[must_use]
pub const fn new(
initial_price: f64,
strike_price: f64,
risk_free_rate: f64,
volatility: f64,
dividend_rate: f64,
evaluation_date: Option<Date>,
expiration_date: Date,
) -> Self {
Self {
initial_price,
strike_price,
risk_free_rate,
volatility,
dividend_rate,
evaluation_date,
expiration_date,
}
}
#[must_use]
pub fn price_geometric_average(&self) -> (f64, f64) {
let S = self.initial_price;
let K = self.strike_price;
let r = self.risk_free_rate;
let v = self.volatility;
let q = self.dividend_rate;
let T = DayCountConvention::default().day_count_factor(
self.evaluation_date.unwrap_or(today()),
self.expiration_date,
);
let v_a = v / 3_f64.sqrt();
let b = r - q;
let b_a = 0.5 * (b - v * v / 6.0);
let d1 = ((S / K).ln() + (b_a + 0.5 * v_a * v_a) * T) / (v_a * (T).sqrt());
let d2 = d1 - v_a * (T).sqrt();
let N = Gaussian::default();
let c = S * ((b_a - r) * T).exp() * N.cdf(d1) - K * (-r * T).exp() * N.cdf(d2);
let p = -S * ((b_a - r) * T).exp() * N.cdf(-d1) + K * (-r * T).exp() * N.cdf(-d2);
(c, p)
}
}
#[cfg(test)]
mod tests {
use time::Duration;
use super::*;
use crate::assert_approx_equal;
#[test]
fn test_asian_geometric() {
let expiry_date = today() + Duration::days(92);
let AsianOption = AsianOption {
initial_price: 80.0,
strike_price: 85.0,
risk_free_rate: 0.05,
volatility: 0.2,
evaluation_date: None,
expiration_date: expiry_date,
dividend_rate: -0.03,
};
let prices = AsianOption.price_geometric_average();
assert_approx_equal!(prices.1, 4.6922, 0.0001);
}
}