use ndarray::Array1;
use crate::error::DigiFiError;
use crate::financial_instruments::Payoff;
use crate::stochastic_processes::StochasticProcess;
pub fn monte_carlo_simulation(stochastic_process: &dyn StochasticProcess, payoff_object: &dyn Payoff, r: f64, exercise_time_steps: &Option<Vec<bool>>) -> Result<f64, DigiFiError> {
let n_steps: usize = stochastic_process.get_n_steps();
let dt: f64 = stochastic_process.get_t_f() / (n_steps as f64);
let exercise_time_steps: &Vec<bool> = match exercise_time_steps {
Some(exercise_time_steps_vec) => {
if exercise_time_steps_vec.len() != n_steps {
return Err(DigiFiError::WrongLength { title: "Monte-Carlo Simulation".to_owned(), arg: "exercise time steps".to_owned(), len: n_steps, });
}
exercise_time_steps_vec
},
None => &vec![true; n_steps],
};
let stochastic_paths: Vec<Array1<f64>> = stochastic_process.get_paths()?;
let sum_of_payoffs: f64 = stochastic_paths.iter().fold(0.0, |total, path| {
let value: f64 = (0..(path.len() - 1)).rev().fold(payoff_object.payoff(path[path.len() - 1]), |value, i| {
let mut value: f64 = value * (-dt * r).exp();
if exercise_time_steps[i] {
let exercise: f64 = payoff_object.payoff(path[i]);
value = value.max(exercise);
}
value
} );
total + value
} );
Ok(sum_of_payoffs / stochastic_paths.len() as f64)
}
#[cfg(test)]
mod tests {
use crate::utilities::TEST_ACCURACY;
#[test]
fn unit_test_stochastic_model() -> () {
use crate::financial_instruments::derivatives::{BlackScholesType, black_scholes_formula};
use crate::monte_carlo::monte_carlo_simulation;
use crate::financial_instruments::LongCall;
use crate::stochastic_processes::standard_stochastic_models::GeometricBrownianMotion;
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let rf: f64 = 0.02;
let gbm: GeometricBrownianMotion = GeometricBrownianMotion::new(rf, 0.2, n_paths, n_steps, 1.0, 10.0);
let long_call: LongCall = LongCall { k: 11.0, cost: 0.0 };
let predicted_value: f64 = monte_carlo_simulation(
&gbm, &long_call, rf, &Some(vec![false; n_steps])
).unwrap();
let theoretical_value: f64 = black_scholes_formula(10.0, 11.0, 0.2, 1.0, 0.02, 0.0, &BlackScholesType::Call).unwrap();
assert!((predicted_value - theoretical_value).abs() < 10_000_000.0 * TEST_ACCURACY);
}
}