use crate::error::IntegrateResult;
use crate::specialized::finance::models::VolatilityModel;
use crate::specialized::finance::solvers::StochasticPDESolver;
use crate::specialized::finance::types::{FinancialOption, OptionStyle, OptionType};
use scirs2_core::random::{Rng, StandardNormal};
pub fn price_monte_carlo(
solver: &StochasticPDESolver,
option: &FinancialOption,
n_paths: usize,
antithetic: bool,
) -> IntegrateResult<f64> {
match &solver.volatility_model {
VolatilityModel::Constant(sigma) => {
monte_carlo_black_scholes(option, *sigma, n_paths, antithetic)
}
VolatilityModel::Heston {
v0,
theta,
kappa,
sigma,
rho,
} => monte_carlo_heston(
option, *v0, *theta, *kappa, *sigma, *rho, n_paths, antithetic,
),
_ => Err(crate::error::IntegrateError::ValueError(
"Monte Carlo not implemented for this volatility model yet".to_string(),
)),
}
}
fn monte_carlo_black_scholes(
option: &FinancialOption,
sigma: f64,
n_paths: usize,
antithetic: bool,
) -> IntegrateResult<f64> {
let mut rng = scirs2_core::random::thread_rng();
let normal = StandardNormal;
let dt = option.maturity / 252.0; let n_steps = 252;
let drift = (option.risk_free_rate - option.dividend_yield - 0.5 * sigma * sigma) * dt;
let vol_sqrt_dt = sigma * dt.sqrt();
let mut payoff_sum = 0.0;
let effective_paths = if antithetic { n_paths / 2 } else { n_paths };
for _ in 0..effective_paths {
let mut s = option.spot;
let mut path_sum = 0.0;
for _ in 0..n_steps {
let z: f64 = rng.sample(normal);
s *= (drift + vol_sqrt_dt * z).exp();
path_sum += s;
}
let payoff = calculate_payoff(option, s, path_sum / n_steps as f64);
payoff_sum += payoff;
if antithetic {
let mut s_anti = option.spot;
let mut path_sum_anti = 0.0;
for _ in 0..n_steps {
let z: f64 = rng.sample(normal);
s_anti *= (drift - vol_sqrt_dt * z).exp(); path_sum_anti += s_anti;
}
let payoff_anti = calculate_payoff(option, s_anti, path_sum_anti / n_steps as f64);
payoff_sum += payoff_anti;
}
}
let actual_paths = if antithetic { n_paths } else { effective_paths };
let discounted_payoff =
(payoff_sum / actual_paths as f64) * (-option.risk_free_rate * option.maturity).exp();
Ok(discounted_payoff)
}
fn monte_carlo_heston(
option: &FinancialOption,
v0: f64,
theta: f64,
kappa: f64,
sigma: f64,
rho: f64,
n_paths: usize,
antithetic: bool,
) -> IntegrateResult<f64> {
let mut rng = scirs2_core::random::thread_rng();
let normal = StandardNormal;
let dt = option.maturity / 252.0;
let n_steps = 252;
let sqrt_dt = dt.sqrt();
let sqrt_1_minus_rho2 = (1.0 - rho * rho).sqrt();
let mut payoff_sum = 0.0;
let effective_paths = if antithetic { n_paths / 2 } else { n_paths };
for _ in 0..effective_paths {
let mut s = option.spot;
let mut v = v0.max(0.0);
let mut path_sum = 0.0;
for _ in 0..n_steps {
let z1: f64 = rng.sample(normal);
let z2: f64 = rng.sample(normal);
let dw_s = z1;
let dw_v = rho * z1 + sqrt_1_minus_rho2 * z2;
let sqrt_v = v.sqrt();
v += kappa * (theta - v) * dt + sigma * sqrt_v * sqrt_dt * dw_v;
v = v.max(0.0);
let drift = (option.risk_free_rate - option.dividend_yield - 0.5 * v) * dt;
s *= (drift + sqrt_v * sqrt_dt * dw_s).exp();
path_sum += s;
}
let payoff = calculate_payoff(option, s, path_sum / n_steps as f64);
payoff_sum += payoff;
if antithetic {
let mut s_anti = option.spot;
let mut v_anti = v0.max(0.0);
let mut path_sum_anti = 0.0;
for _ in 0..n_steps {
let z1: f64 = rng.sample(normal);
let z2: f64 = rng.sample(normal);
let dw_s = -z1; let dw_v = -rho * z1 - sqrt_1_minus_rho2 * z2;
let sqrt_v = v_anti.sqrt();
v_anti += kappa * (theta - v_anti) * dt + sigma * sqrt_v * sqrt_dt * dw_v;
v_anti = v_anti.max(0.0);
let drift = (option.risk_free_rate - option.dividend_yield - 0.5 * v_anti) * dt;
s_anti *= (drift + sqrt_v * sqrt_dt * dw_s).exp();
path_sum_anti += s_anti;
}
let payoff_anti = calculate_payoff(option, s_anti, path_sum_anti / n_steps as f64);
payoff_sum += payoff_anti;
}
}
let actual_paths = if antithetic { n_paths } else { effective_paths };
let discounted_payoff =
(payoff_sum / actual_paths as f64) * (-option.risk_free_rate * option.maturity).exp();
Ok(discounted_payoff)
}
fn calculate_payoff(option: &FinancialOption, final_price: f64, average_price: f64) -> f64 {
match option.option_style {
OptionStyle::European | OptionStyle::American => match option.option_type {
OptionType::Call => (final_price - option.strike).max(0.0),
OptionType::Put => (option.strike - final_price).max(0.0),
},
OptionStyle::Asian => match option.option_type {
OptionType::Call => (average_price - option.strike).max(0.0),
OptionType::Put => (option.strike - average_price).max(0.0),
},
OptionStyle::Barrier {
barrier,
is_up,
is_knock_in,
} => {
let barrier_hit = if is_up {
final_price >= barrier
} else {
final_price <= barrier
};
let barrier_active = if is_knock_in {
barrier_hit
} else {
!barrier_hit
};
if barrier_active {
match option.option_type {
OptionType::Call => (final_price - option.strike).max(0.0),
OptionType::Put => (option.strike - final_price).max(0.0),
}
} else {
0.0
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::specialized::finance::models::VolatilityModel;
use crate::specialized::finance::types::{FinanceMethod, OptionType};
#[test]
fn test_monte_carlo_european_call() {
let option = FinancialOption {
option_type: OptionType::Call,
option_style: OptionStyle::European,
strike: 100.0,
maturity: 1.0,
spot: 100.0,
risk_free_rate: 0.05,
dividend_yield: 0.0,
};
let solver = StochasticPDESolver::new(
100,
50,
VolatilityModel::Constant(0.2),
FinanceMethod::MonteCarlo {
n_paths: 10000,
antithetic: true,
},
);
let price = price_monte_carlo(&solver, &option, 10000, true).expect("Operation failed");
assert!(price > 8.0 && price < 13.0, "Price: {}", price);
}
#[test]
fn test_monte_carlo_european_put() {
let option = FinancialOption {
option_type: OptionType::Put,
option_style: OptionStyle::European,
strike: 100.0,
maturity: 1.0,
spot: 100.0,
risk_free_rate: 0.05,
dividend_yield: 0.0,
};
let solver = StochasticPDESolver::new(
100,
50,
VolatilityModel::Constant(0.2),
FinanceMethod::MonteCarlo {
n_paths: 10000,
antithetic: true,
},
);
let price = price_monte_carlo(&solver, &option, 10000, true).expect("Operation failed");
assert!(price > 4.0 && price < 7.5, "Price: {}", price);
}
#[test]
fn test_monte_carlo_asian_call() {
let option = FinancialOption {
option_type: OptionType::Call,
option_style: OptionStyle::Asian,
strike: 100.0,
maturity: 1.0,
spot: 100.0,
risk_free_rate: 0.05,
dividend_yield: 0.0,
};
let solver = StochasticPDESolver::new(
100,
50,
VolatilityModel::Constant(0.2),
FinanceMethod::MonteCarlo {
n_paths: 10000,
antithetic: true,
},
);
let price = price_monte_carlo(&solver, &option, 10000, true).expect("Operation failed");
assert!(price > 3.0 && price < 8.0, "Price: {}", price);
}
}