use crate::options::{Option, OptionPricing, OptionStrategy, OptionStyle, SimMethod};
use rand::rngs::ThreadRng;
use rayon::prelude::*;
#[derive(Debug, Default, Clone, Copy)]
pub enum AvgMethod {
Geometric,
Arithmetic,
#[default]
Brownian,
}
#[derive(Debug, Default, Clone)]
pub struct MonteCarloModel {
pub risk_free_rate: f64,
pub volatility: f64,
pub simulations: usize,
pub steps: usize,
pub method: AvgMethod,
}
impl MonteCarloModel {
pub fn new(
risk_free_rate: f64,
volatility: f64,
simulations: usize,
steps: usize,
method: AvgMethod,
) -> Self {
Self {
risk_free_rate,
volatility,
simulations,
steps: steps.max(1),
method,
}
}
pub fn geometric(
risk_free_rate: f64,
volatility: f64,
simulations: usize,
steps: usize,
) -> Self {
Self::new(
risk_free_rate,
volatility,
simulations,
steps,
AvgMethod::Geometric,
)
}
pub fn arithmetic(
risk_free_rate: f64,
volatility: f64,
simulations: usize,
steps: usize,
) -> Self {
Self::new(
risk_free_rate,
volatility,
simulations,
steps,
AvgMethod::Arithmetic,
)
}
pub fn brownian(
risk_free_rate: f64,
volatility: f64,
simulations: usize,
steps: usize,
) -> Self {
Self::new(
risk_free_rate,
volatility,
simulations,
steps,
AvgMethod::Brownian,
)
}
fn simulate_price_paths<T: Option>(&self, option: &T) -> f64 {
let discount_factor = (-self.risk_free_rate * option.time_to_maturity()).exp();
let total_payoff: f64 = (0..self.simulations)
.into_par_iter() .map(|_| {
let mut rng = rand::rng();
let simulated_price = match self.method {
AvgMethod::Geometric => option.instrument().simulate_geometric_average(
&mut rng,
SimMethod::Log,
self.volatility,
option.time_to_maturity(),
self.risk_free_rate,
self.steps,
),
AvgMethod::Arithmetic => option.instrument().simulate_arithmetic_average(
&mut rng,
SimMethod::Log,
self.volatility,
option.time_to_maturity(),
self.risk_free_rate,
self.steps,
),
AvgMethod::Brownian => option.instrument().simulate_geometric_brownian_motion(
&mut rng,
self.volatility,
option.time_to_maturity(),
self.risk_free_rate,
self.steps,
),
};
option.payoff(Some(simulated_price))
})
.sum();
(total_payoff / self.simulations as f64) * discount_factor
}
}
impl OptionPricing for MonteCarloModel {
fn price<T: Option>(&self, option: &T) -> f64 {
match option.style() {
OptionStyle::European => self.simulate_price_paths(option),
OptionStyle::Basket => self.simulate_price_paths(option),
OptionStyle::Rainbow(_) => self.simulate_price_paths(option),
OptionStyle::Barrier(_) => self.simulate_price_paths(option),
OptionStyle::DoubleBarrier(_, _) => self.simulate_price_paths(option),
OptionStyle::Asian(_) => self.price_asian(option),
OptionStyle::Lookback(_) => self.price_asian(option),
OptionStyle::Binary(_) => self.simulate_price_paths(option),
_ => panic!("Monte Carlo model does not support this option style"),
}
}
fn implied_volatility<T: Option>(&self, _option: &T, _market_price: f64) -> f64 {
unimplemented!()
}
}
impl MonteCarloModel {
fn price_asian<T: Option>(&self, option: &T) -> f64 {
let mut rng: ThreadRng = rand::rng();
let mut sum = 0.0;
let mut option_clone = option.clone();
for _ in 0..self.simulations {
let avg_price = {
let instrument = option_clone.instrument_mut();
match self.method {
AvgMethod::Geometric => instrument.simulate_geometric_average_mut(
&mut rng,
SimMethod::Log,
self.volatility,
option.time_to_maturity(),
self.risk_free_rate,
self.steps,
),
AvgMethod::Arithmetic => instrument.simulate_arithmetic_average_mut(
&mut rng,
SimMethod::Log,
self.volatility,
option.time_to_maturity(),
self.risk_free_rate,
self.steps,
),
_ => panic!("Invalid averaging method"),
}
};
sum += (-self.risk_free_rate * option_clone.time_to_maturity()).exp()
* option_clone.payoff(Some(avg_price));
}
sum / self.simulations as f64
}
}
impl OptionStrategy for MonteCarloModel {}