1use crate::rng;
3use rayon::prelude::*;
4use std::f64;
5use crate::mc::payoffs::Payoff;
6use crate::analytics::bs_analytic;
7use crate::error::{SdeError, SdeResult, validation::*};
8use bitflags::bitflags;
9
10bitflags! {
11 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12 pub struct GreeksConfig: u32 {
13 const NONE = 0;
14 const DELTA = 1 << 0;
15 const VEGA = 1 << 1;
16 const RHO = 1 << 2;
17 const GAMMA = 1 << 3;
18 }
19}
20
21#[derive(Clone)]
22pub struct McConfig {
23 pub paths: usize,
24 pub steps: usize,
25 pub s0: f64,
26 pub r: f64,
27 pub sigma: f64,
28 pub t: f64,
29 pub use_antithetic: bool,
30 pub use_control_variate: bool,
31 pub seed: u64,
32 pub payoff: Payoff,
33 pub greeks: GreeksConfig,
34 pub epsilon: Option<f64>, }
36
37impl McConfig {
38 pub fn validate(&self) -> SdeResult<()> {
40 validate_paths(self.paths)?;
41 validate_steps(self.steps)?;
42 validate_positive("s0", self.s0)?;
43 validate_finite("r", self.r)?;
44 validate_positive("sigma", self.sigma)?;
45 validate_positive("t", self.t)?;
46
47 if let Some(eps) = self.epsilon {
48 validate_positive("epsilon", eps)?;
49 if eps > self.s0 * 0.1 {
50 return Err(SdeError::InvalidParameters {
51 parameter: "epsilon".to_string(),
52 value: eps,
53 constraint: format!("should be much smaller than spot price ({})", self.s0),
54 });
55 }
56 }
57
58 Ok(())
59 }
60}
61
62impl Default for McConfig {
63 fn default() -> Self {
64 McConfig {
65 paths: 1_000_000,
66 steps: 1,
67 s0: 100.0,
68 r: 0.01,
69 sigma: 0.2,
70 t: 1.0,
71 use_antithetic: true,
72 use_control_variate: true,
73 seed: 12345,
74 payoff: Payoff::EuropeanCall { k: 100.0 },
75 greeks: GreeksConfig::NONE,
76 epsilon: None,
77 }
78 }
79}
80
81pub fn mc_price_option_gbm(cfg: &McConfig) -> SdeResult<(f64, f64)> {
119 cfg.validate()?;
121 let n = cfg.paths;
122 let dt = cfg.t / cfg.steps as f64;
123 let sqrt_dt = dt.sqrt();
124 let discount = (-cfg.r * cfg.t).exp();
125
126 let (sum_payoff_path, sum_control_path, sum_payoff_times_control_path, sum_control_sq_path, sum_european_analytic_price_path, sum_payoff_sq_path) = (0..n)
127 .into_par_iter()
128 .map(|i| {
129 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
130
131 let mut path_prices = Vec::with_capacity(cfg.steps + 1);
135 path_prices.push(cfg.s0);
136
137 let mut current_s = cfg.s0;
138 for _ in 0..cfg.steps {
139 let z = rng::get_normal_draw(&mut rng);
140 current_s *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z).exp();
142 path_prices.push(current_s);
143 }
144
145 let payoff_raw = cfg.payoff.calculate(&path_prices);
147
148 let mut control_var_raw = 0.0;
151 let mut european_analytic_price = 0.0;
152
153 match cfg.payoff {
154 Payoff::EuropeanCall { k } => {
155 control_var_raw = Payoff::EuropeanCall { k }.calculate(&path_prices);
157 european_analytic_price = bs_analytic::bs_call_price(cfg.s0, k, cfg.r, cfg.sigma, cfg.t);
158 },
159 Payoff::AsianCall { k } => {
160 let st_final = *path_prices.last().unwrap();
163 control_var_raw = Payoff::EuropeanCall { k }.calculate(&vec![st_final]);
164 european_analytic_price = bs_analytic::bs_call_price(cfg.s0, k, cfg.r, cfg.sigma, cfg.t);
165 },
166 _ => {
167 }
170 }
171
172 let mut payoff_path = payoff_raw;
173 let mut control_var_path = control_var_raw;
174
175 if cfg.use_antithetic {
178 let mut path_prices2 = Vec::with_capacity(cfg.steps + 1);
179 path_prices2.push(cfg.s0);
180
181 let mut current_s2 = cfg.s0;
182 for _ in 0..cfg.steps {
183 let z2 = -rng::get_normal_draw(&mut rng);
186 current_s2 *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z2).exp();
187 path_prices2.push(current_s2);
188 }
189
190 let payoff2_raw = cfg.payoff.calculate(&path_prices2);
191
192 let mut control_var2_raw = 0.0;
193 match cfg.payoff {
194 Payoff::EuropeanCall { k } => {
195 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&path_prices2);
196 },
197 Payoff::AsianCall { k } => {
198 let st2_final = *path_prices2.last().unwrap();
199 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&vec![st2_final]);
200 },
201 _ => {}
202 }
203
204 payoff_path = 0.5 * (payoff_raw + payoff2_raw);
207 control_var_path = 0.5 * (control_var_raw + control_var2_raw);
208 }
209
210 (payoff_path, control_var_path, payoff_path * control_var_path, control_var_path * control_var_path, european_analytic_price, payoff_path * payoff_path)
211 })
212 .reduce(|| (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
213 |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3, a.4 + b.4, a.5 + b.5));
214
215 let mean_payoff = sum_payoff_path / n as f64;
217 let mean_control = sum_control_path / n as f64;
218 let mean_payoff_times_control = sum_payoff_times_control_path / n as f64;
219 let mean_control_sq = sum_control_sq_path / n as f64;
220 let mean_european_analytic_price = sum_european_analytic_price_path / n as f64;
221 let mean_payoff_sq = sum_payoff_sq_path / n as f64;
222
223 let estimated_price;
224 let mut variance_of_estimate;
225
226 if cfg.use_control_variate {
229 let cov_payoff_control = mean_payoff_times_control - mean_payoff * mean_control;
232 let var_control = mean_control_sq - mean_control * mean_control;
233
234 let b = if var_control > 1e-10 { cov_payoff_control / var_control } else { 0.0 };
236
237 let controlled_payoffs_sum = (0..n)
238 .into_par_iter()
239 .map(|i| {
240 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
241 let mut path_prices = Vec::with_capacity(cfg.steps + 1);
242 path_prices.push(cfg.s0);
243
244 let mut current_s = cfg.s0;
245 for _ in 0..cfg.steps {
246 let z = rng::get_normal_draw(&mut rng);
247 current_s *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z).exp();
248 path_prices.push(current_s);
249 }
250
251 let payoff_raw = cfg.payoff.calculate(&path_prices);
252
253 let mut control_var_raw = 0.0;
254 match cfg.payoff {
255 Payoff::EuropeanCall { k } => {
256 control_var_raw = Payoff::EuropeanCall { k }.calculate(&path_prices);
257 },
258 Payoff::AsianCall { k } => {
259 let st_final = *path_prices.last().unwrap();
260 control_var_raw = Payoff::EuropeanCall { k }.calculate(&vec![st_final]);
261 },
262 _ => {}
263 }
264
265 let mut payoff_path = payoff_raw;
266 let mut control_var_path = control_var_raw;
267
268 if cfg.use_antithetic {
269 let mut path_prices2 = Vec::with_capacity(cfg.steps + 1);
270 path_prices2.push(cfg.s0);
271
272 let mut current_s2 = cfg.s0;
273 for _ in 0..cfg.steps {
274 let z2 = -rng::get_normal_draw(&mut rng);
275 current_s2 *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z2).exp();
276 path_prices2.push(current_s2);
277 }
278
279 let payoff2_raw = cfg.payoff.calculate(&path_prices2);
280
281 let mut control_var2_raw = 0.0;
282 match cfg.payoff {
283 Payoff::EuropeanCall { k } => {
284 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&path_prices2);
285 },
286 Payoff::AsianCall { k } => {
287 let st2_final = *path_prices2.last().unwrap();
288 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&vec![st2_final]);
289 },
290 _ => {}
291 }
292 payoff_path = 0.5 * (payoff_raw + payoff2_raw);
293 control_var_path = 0.5 * (control_var_raw + control_var2_raw);
294 }
295
296 discount * (payoff_path - b * (control_var_path - mean_european_analytic_price))
297 })
298 .sum::<f64>();
299
300 let mean_controlled_payoff = controlled_payoffs_sum / n as f64;
301 let sum_controlled_payoff_sq = (0..n)
302 .into_par_iter()
303 .map(|i| {
304 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
305 let mut path_prices = Vec::with_capacity(cfg.steps + 1);
306 path_prices.push(cfg.s0);
307
308 let mut current_s = cfg.s0;
309 for _ in 0..cfg.steps {
310 let z = rng::get_normal_draw(&mut rng);
311 current_s *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z).exp();
312 path_prices.push(current_s);
313 }
314
315 let payoff_raw = cfg.payoff.calculate(&path_prices);
316
317 let mut control_var_raw = 0.0;
318 match cfg.payoff {
319 Payoff::EuropeanCall { k } => {
320 control_var_raw = Payoff::EuropeanCall { k }.calculate(&path_prices);
321 },
322 Payoff::AsianCall { k } => {
323 let st_final = *path_prices.last().unwrap();
324 control_var_raw = Payoff::EuropeanCall { k }.calculate(&vec![st_final]);
325 },
326 _ => {}
327 }
328
329 let mut payoff_path = payoff_raw;
330 let mut control_var_path = control_var_raw;
331
332 if cfg.use_antithetic {
333 let mut path_prices2 = Vec::with_capacity(cfg.steps + 1);
334 path_prices2.push(cfg.s0);
335
336 let mut current_s2 = cfg.s0;
337 for _ in 0..cfg.steps {
338 let z2 = -rng::get_normal_draw(&mut rng);
339 current_s2 *= ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * dt + cfg.sigma * sqrt_dt * z2).exp();
340 path_prices2.push(current_s2);
341 }
342
343 let payoff2_raw = cfg.payoff.calculate(&path_prices2);
344
345 let mut control_var2_raw = 0.0;
346 match cfg.payoff {
347 Payoff::EuropeanCall { k } => {
348 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&path_prices2);
349 },
350 Payoff::AsianCall { k } => {
351 let st2_final = *path_prices2.last().unwrap();
352 control_var2_raw = Payoff::EuropeanCall { k }.calculate(&vec![st2_final]);
353 },
354 _ => {}
355 }
356 payoff_path = 0.5 * (payoff_raw + payoff2_raw);
357 control_var_path = 0.5 * (control_var_raw + control_var2_raw);
358 }
359
360 let controlled_payoff = discount * (payoff_path - b * (control_var_path - mean_european_analytic_price));
361 controlled_payoff * controlled_payoff
362 })
363 .sum::<f64>() / n as f64;
364
365 estimated_price = mean_controlled_payoff;
366 variance_of_estimate = (sum_controlled_payoff_sq - mean_controlled_payoff * mean_controlled_payoff) / (n as f64 * (n as f64 - 1.0));
367
368 if variance_of_estimate < 0.0 {
370 if variance_of_estimate > -1e-10 {
371 variance_of_estimate = 0.0;
373 } else {
374 return Err(SdeError::NumericalInstability {
375 method: "Control Variate Monte Carlo".to_string(),
376 reason: format!("Variance estimate became significantly negative: {}", variance_of_estimate),
377 });
378 }
379 }
380
381 } else {
382 estimated_price = discount * mean_payoff;
383 variance_of_estimate = (mean_payoff_sq - mean_payoff * mean_payoff) * discount.powi(2) / (n as f64 * (n as f64 - 1.0));
384
385 if variance_of_estimate < 0.0 {
387 if variance_of_estimate > -1e-10 {
388 variance_of_estimate = 0.0;
389 } else {
390 return Err(SdeError::NumericalInstability {
391 method: "Monte Carlo".to_string(),
392 reason: format!("Variance estimate became significantly negative: {}", variance_of_estimate),
393 });
394 }
395 }
396 }
397
398 if !estimated_price.is_finite() {
400 return Err(SdeError::NumericalInstability {
401 method: "Monte Carlo".to_string(),
402 reason: format!("Price estimate is not finite: {}", estimated_price),
403 });
404 }
405
406 if !variance_of_estimate.is_finite() {
407 return Err(SdeError::NumericalInstability {
408 method: "Monte Carlo".to_string(),
409 reason: format!("Variance estimate is not finite: {}", variance_of_estimate),
410 });
411 }
412
413 Ok((estimated_price, variance_of_estimate))
414}
415
416pub fn mc_delta_european_call_gbm_pathwise(cfg: &McConfig) -> f64 {
440 let n = cfg.paths;
441 let discount = (-cfg.r * cfg.t).exp();
442
443 let k = match cfg.payoff {
444 Payoff::EuropeanCall { k } => k,
445 _ => {
446 return 0.0;
447 }
448 };
449
450 (0..n)
451 .into_par_iter()
452 .map(|i| {
453 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
454 let z = rng::get_normal_draw(&mut rng);
455
456 let st = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * cfg.t.sqrt() * z).exp();
457
458 let mut delta_path = 0.0;
459 if st > k {
460 delta_path = st / cfg.s0;
461 }
462
463 if cfg.use_antithetic {
464 let z2 = -z;
465 let st2 = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * cfg.t.sqrt() * z2).exp();
466 let mut delta_path2 = 0.0;
467 if st2 > k {
468 delta_path2 = st2 / cfg.s0;
469 }
470 delta_path = 0.5 * (delta_path + delta_path2);
471 }
472 delta_path
473 })
474 .reduce(|| 0.0, |a,b| a + b) / n as f64 * discount
475}
476
477pub fn mc_vega_european_call_gbm_pathwise(cfg: &McConfig) -> f64 {
503 let n = cfg.paths;
504 let discount = (-cfg.r * cfg.t).exp();
505 let sqrt_t = cfg.t.sqrt();
506
507 let k = match cfg.payoff {
508 Payoff::EuropeanCall { k } => k,
509 _ => {
510 return 0.0;
511 }
512 };
513
514 (0..n)
516 .into_par_iter()
517 .map(|i| {
518 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
519 let z = rng::get_normal_draw(&mut rng);
520 let w_t = sqrt_t * z; let st = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * w_t).exp();
523
524 let mut vega_path = 0.0;
525 if st > k {
526 let ds_dsigma = st * (-cfg.sigma * cfg.t + w_t);
528 vega_path = ds_dsigma;
529 }
530
531 if cfg.use_antithetic {
532 let z2 = -z;
533 let w_t2 = sqrt_t * z2;
534 let st2 = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * w_t2).exp();
535
536 let mut vega_path2 = 0.0;
537 if st2 > k {
538 let ds_dsigma2 = st2 * (-cfg.sigma * cfg.t + w_t2);
539 vega_path2 = ds_dsigma2;
540 }
541 vega_path = 0.5 * (vega_path + vega_path2);
542 }
543 vega_path
544 })
545 .reduce(|| 0.0, |a,b| a + b) / n as f64 * discount
546}
547
548pub fn mc_rho_european_call_gbm_pathwise(cfg: &McConfig) -> f64 {
574 let n = cfg.paths;
575 let discount = (-cfg.r * cfg.t).exp();
576 let sqrt_t = cfg.t.sqrt();
577
578 let k = match cfg.payoff {
579 Payoff::EuropeanCall { k } => k,
580 _ => {
581 return 0.0;
582 }
583 };
584
585 (0..n)
586 .into_par_iter()
587 .map(|i| {
588 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
589 let z = rng::get_normal_draw(&mut rng);
590
591 let st = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z).exp();
592
593 let payoff = (st - k).max(0.0);
594 let indicator = if st > k { 1.0 } else { 0.0 };
595
596 let ds_dr = st * cfg.t;
599 let mut rho_path = -cfg.t * payoff + indicator * ds_dr;
600
601 if cfg.use_antithetic {
602 let z2 = -z;
603 let st2 = cfg.s0 * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z2).exp();
604
605 let payoff2 = (st2 - k).max(0.0);
606 let indicator2 = if st2 > k { 1.0 } else { 0.0 };
607 let ds_dr2 = st2 * cfg.t;
608 let rho_path2 = -cfg.t * payoff2 + indicator2 * ds_dr2;
609
610 rho_path = 0.5 * (rho_path + rho_path2);
611 }
612 rho_path
613 })
614 .reduce(|| 0.0, |a,b| a + b) / n as f64 * discount
615}
616
617pub fn mc_gamma_european_call_gbm_finite_diff(cfg: &McConfig) -> f64 {
648 let epsilon = cfg.epsilon.unwrap_or(1e-3 * cfg.s0);
650
651 let mut cfg_up = cfg.clone();
653 cfg_up.s0 = cfg.s0 + epsilon;
654
655 let mut cfg_down = cfg.clone();
656 cfg_down.s0 = cfg.s0 - epsilon;
657
658 let delta_up = mc_delta_european_call_gbm_pathwise(&cfg_up);
660 let delta_down = mc_delta_european_call_gbm_pathwise(&cfg_down);
661
662 (delta_up - delta_down) / (2.0 * epsilon)
664}
665
666pub fn mc_gamma_european_call_gbm_finite_diff_batched(cfg: &McConfig) -> f64 {
697 let n = cfg.paths;
698 let discount = (-cfg.r * cfg.t).exp();
699 let sqrt_t = cfg.t.sqrt();
700
701 let k = match cfg.payoff {
702 Payoff::EuropeanCall { k } => k,
703 _ => {
704 return 0.0;
705 }
706 };
707
708 let epsilon = cfg.epsilon.unwrap_or(1e-3 * cfg.s0);
710 let s0_up = cfg.s0 + epsilon;
711 let s0_down = cfg.s0 - epsilon;
712
713 let (sum_delta_up, sum_delta_down) = (0..n)
715 .into_par_iter()
716 .map(|i| {
717 let mut rng = rng::seed_rng_from_u64(cfg.seed + i as u64);
719 let z = rng::get_normal_draw(&mut rng);
720
721 let st_up = s0_up * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z).exp();
723 let st_down = s0_down * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z).exp();
724
725 let delta_up = if st_up > k { st_up / s0_up } else { 0.0 };
727
728 let delta_down = if st_down > k { st_down / s0_down } else { 0.0 };
730
731 if cfg.use_antithetic {
732 let z2 = -z;
733
734 let st_up2 = s0_up * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z2).exp();
735 let st_down2 = s0_down * ((cfg.r - 0.5 * cfg.sigma * cfg.sigma) * cfg.t + cfg.sigma * sqrt_t * z2).exp();
736
737 let delta_up2 = if st_up2 > k { st_up2 / s0_up } else { 0.0 };
738 let delta_down2 = if st_down2 > k { st_down2 / s0_down } else { 0.0 };
739
740 (0.5 * (delta_up + delta_up2), 0.5 * (delta_down + delta_down2))
741 } else {
742 (delta_up, delta_down)
743 }
744 })
745 .reduce(|| (0.0, 0.0), |a, b| (a.0 + b.0, a.1 + b.1));
746
747 let mean_delta_up = sum_delta_up / n as f64 * discount;
748 let mean_delta_down = sum_delta_down / n as f64 * discount;
749
750 (mean_delta_up - mean_delta_down) / (2.0 * epsilon)
752}