Skip to main content

forward_start_option/common/
mod.rs

1//! Shared reporting helpers for the runnable product examples.
2//!
3//! Each example builds one product, prices it on every applicable engine
4//! and model, and prints NPV plus Greeks in a single table. Engines that
5//! refuse a combination (by design) are caught and reported rather than
6//! aborting the run, so these files double as a support matrix.
7
8use std::panic::{catch_unwind, AssertUnwindSafe};
9
10use rustyqlib::core::traits::Instrument;
11use rustyqlib::equity::montecarlo;
12use rustyqlib::equity::utils::Engine;
13use rustyqlib::equity::vanila_option::EquityOption;
14
15// Not every example uses the plotter; silence dead-code warnings there.
16#[allow(dead_code)]
17pub mod plot3d;
18
19pub fn title(text: &str) {
20    println!("\n{}", "=".repeat(96));
21    println!("  {text}");
22    println!("{}", "=".repeat(96));
23}
24
25pub fn section(text: &str) {
26    println!("\n-- {text} {}", "-".repeat(90usize.saturating_sub(text.len())));
27}
28
29pub fn table_header() {
30    println!(
31        "{:<34} {:>12} {:>10} {:>10} {:>9} {:>9} {:>9} {:>9}",
32        "method", "npv", "delta", "gamma", "vega", "theta", "rho", "std err"
33    );
34    println!("{}", "-".repeat(96));
35}
36
37/// Price `option` and print one row. Panics from unsupported combinations
38/// are caught and shown as `unsupported`.
39pub fn row(label: &str, option: &EquityOption) {
40    // keep the table readable: the caught panic is reported in the row
41    let hook = std::panic::take_hook();
42    std::panic::set_hook(Box::new(|_| {}));
43    let result = catch_unwind(AssertUnwindSafe(|| {
44        let (pv, std_err) = if option.engine == Engine::MonteCarlo {
45            let s = montecarlo::npv_with_stats(option);
46            (s.pv, Some(s.std_err))
47        } else {
48            (option.npv(), None)
49        };
50        (pv, option.delta(), option.gamma(), option.vega(), option.theta(), option.rho(), std_err)
51    }));
52    std::panic::set_hook(hook);
53    match result {
54        Ok((pv, delta, gamma, vega, theta, rho, std_err)) => {
55            let se = match std_err {
56                Some(v) => format!("{v:.5}"),
57                None => "-".to_string(),
58            };
59            println!(
60                "{label:<34} {pv:>12.6} {delta:>10.5} {gamma:>10.5} {vega:>9.3} {theta:>9.3} {rho:>9.3} {se:>9}"
61            );
62        }
63        Err(payload) => {
64            let msg = payload
65                .downcast_ref::<String>()
66                .cloned()
67                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
68                .unwrap_or_else(|| "panicked".to_string());
69            let short: String = msg.split(';').next().unwrap_or(&msg).chars().take(52).collect();
70            println!("{label:<34} {:>12}  ({short})", "unsupported");
71        }
72    }
73}
74
75/// Print a labelled scalar, for identities and cross-checks.
76pub fn check(label: &str, value: f64, expected: f64, tol: f64) {
77    let diff = (value - expected).abs();
78    let mark = if diff < tol { "OK " } else { "BAD" };
79    println!("  [{mark}] {label:<52} {value:>13.6}  expected {expected:>13.6}  diff {diff:.2e}");
80}
81
82pub fn note(text: &str) {
83    println!("  . {text}");
84}