use crate::options::{Option, OptionPricing, OptionStrategy, OptionStyle};
#[derive(Debug, Default)]
pub struct BinomialTreeModel {
pub risk_free_rate: f64,
pub volatility: f64,
pub steps: usize,
}
impl BinomialTreeModel {
pub fn new(risk_free_rate: f64, volatility: f64, steps: usize) -> Self {
Self {
risk_free_rate,
volatility,
steps,
}
}
}
impl OptionPricing for BinomialTreeModel {
fn price<T: Option>(&self, option: &T) -> f64 {
let dt = option.time_to_maturity() / self.steps as f64;
let u = (self.volatility * dt.sqrt()).exp();
let d = 1.0 / u;
let p = (((self.risk_free_rate - option.instrument().continuous_dividend_yield) * dt)
.exp()
- d)
/ (u - d);
let discount_factor = (-self.risk_free_rate * dt).exp();
let mut option_values: Vec<f64> = (0..=self.steps)
.map(|i| {
option.payoff(Some(
option.instrument().spot() * u.powi(i as i32) * d.powi((self.steps - i) as i32),
))
})
.collect();
for step in (0..self.steps).rev() {
for i in 0..=step {
let expected_value =
discount_factor * (p * option_values[i + 1] + (1.0 - p) * option_values[i]);
if matches!(option.style(), OptionStyle::American)
|| matches!(option.style(), OptionStyle::Bermudan)
&& option
.expiration_dates()
.unwrap()
.contains(&(step as f64 * dt))
{
let early_exercise = option.payoff(Some(
option.instrument().spot() * u.powi(i as i32) * d.powi((step - i) as i32),
));
option_values[i] = expected_value.max(early_exercise);
} else {
option_values[i] = expected_value;
}
}
}
if matches!(option.style(), OptionStyle::American)
|| matches!(option.style(), OptionStyle::Bermudan)
&& option.expiration_dates().unwrap().contains(&0.0)
{
option_values[0].max(option.payoff(Some(option.instrument().spot())))
} else {
option_values[0] }
}
fn implied_volatility<T: Option>(&self, _option: &T, _market_price: f64) -> f64 {
panic!("BinomialTreeModel does not support implied volatility calculation yet");
}
}
impl OptionStrategy for BinomialTreeModel {}