demo/
demo.rs

1// examples/demo.rs
2use fast_sde::mc::mc_engine::{mc_price_option_gbm, mc_delta_european_call_gbm_pathwise, mc_vega_european_call_gbm_pathwise, mc_rho_european_call_gbm_pathwise, mc_gamma_european_call_gbm_finite_diff_batched, McConfig, GreeksConfig};
3use fast_sde::analytics::bs_analytic;
4use fast_sde::math_utils::Timer;
5use fast_sde::rng;
6use fast_sde::output;
7use rayon::prelude::*;
8use std::f64;
9use fast_sde::mc::payoffs::Payoff;
10
11fn main() {
12    let args: Vec<String> = std::env::args().collect();
13    if args.len() > 1 && args[1] == "--bench" && args.len() > 2 && args[2] == "canonical" {
14        run_canonical_benchmark();
15    } else {
16        run_demo_mode();
17    }
18}
19
20fn run_canonical_benchmark() {
21    let paths = 1_000_000;
22    let steps = 1;
23    let s0 = 100.0;
24    let k = 100.0;
25    let r = 0.01;
26    let sigma = 0.2;
27    let t = 1.0;
28    let seed = 42;
29
30    let cfg = McConfig {
31        paths,
32        steps,
33        s0,
34        r,
35        sigma,
36        t,
37        seed,
38        use_antithetic: true,
39        use_control_variate: true,
40        payoff: Payoff::EuropeanCall { k },
41        greeks: GreeksConfig::NONE,
42        epsilon: None,
43    };
44
45    let mut timer = Timer::new();
46    timer.start();
47    let (price, variance) = mc_price_option_gbm(&cfg).expect("Valid configuration");
48    let elapsed = timer.elapsed_ms() / 1000.0;
49    let paths_per_sec = paths as f64 / elapsed;
50    let stderr = variance.sqrt();
51
52    let current_dir = std::env::current_dir().expect("Failed to get current directory");
53    let output_filename = current_dir.join("bench").join("rust_canonical.csv");
54    let mut file = std::fs::File::create(&output_filename).expect("Could not create file");
55    std::io::Write::write_all(&mut file, b"paths,price,stderr,paths_per_sec\n").expect("Could not write header");
56    let line = format!("{}, {:.8}, {:.6}, {:.6}\n", paths, price, stderr, paths_per_sec);
57    std::io::Write::write_all(&mut file, line.as_bytes()).expect("Could not write data");
58
59    println!("Rust benchmark results written to {}", output_filename.display());
60}
61
62fn run_demo_mode() {
63    println!("Running fast-sde Monte Carlo Demo\n");
64
65    let paths = 100_000;
66    let steps = 252; // For Asian option, need multiple steps
67    let s0 = 100.0;
68    let k = 100.0;
69    let r = 0.01;
70    let sigma = 0.2;
71    let t = 1.0;
72    let h = 120.0; // Barrier level
73
74    let cfg_european_call = McConfig {
75        paths,
76        steps,
77        s0,
78        r,
79        sigma,
80        t,
81        seed: 12345,
82        use_antithetic: true,
83        use_control_variate: false,
84        payoff: Payoff::EuropeanCall { k },
85        greeks: GreeksConfig::DELTA | GreeksConfig::VEGA | GreeksConfig::RHO | GreeksConfig::GAMMA,
86        epsilon: Some(0.001 * s0),  // 0.1% of spot for finite difference
87    };
88
89    let cfg_asian_call = McConfig {
90        paths,
91        steps,
92        s0,
93        r,
94        sigma,
95        t,
96        seed: 12345,
97        use_antithetic: true,
98        use_control_variate: false, // Control variate for Asian is more complex, disable for now
99        payoff: Payoff::AsianCall { k },
100        greeks: GreeksConfig::NONE,
101        epsilon: None,
102    };
103
104    let cfg_barrier_call_up_and_out = McConfig {
105        paths,
106        steps,
107        s0,
108        r,
109        sigma,
110        t,
111        seed: 12345,
112        use_antithetic: true,
113        use_control_variate: false, // Control variate for barrier is complex, disable for now
114        payoff: Payoff::BarrierCallUpAndOut { k, h },
115        greeks: GreeksConfig::NONE,
116        epsilon: None,
117    };
118
119    let cfg_barrier_put_up_and_out = McConfig {
120        paths,
121        steps,
122        s0,
123        r,
124        sigma,
125        t,
126        seed: 12345,
127        use_antithetic: true,
128        use_control_variate: false, // Control variate for barrier is complex, disable for now
129        payoff: Payoff::BarrierPutUpAndOut { k, h },
130        greeks: GreeksConfig::NONE,
131        epsilon: None,
132    };
133
134    // --- European Call Pricing ---
135    println!("--- European Call Pricing ---");
136
137    // Benchmark MC Price for European Call
138    let mut timer = Timer::new();
139    timer.start();
140    let (mc_price_european, _variance_european) = mc_price_option_gbm(&cfg_european_call).expect("Valid configuration");
141    let price_time_european = timer.elapsed_ms();
142    println!("MC Price (European Call): {} ({} ms)", mc_price_european, price_time_european);
143    let analytic_price_european = bs_analytic::bs_call_price(cfg_european_call.s0, k, cfg_european_call.r, cfg_european_call.sigma, cfg_european_call.t);
144    println!("Analytic Price (European Call): {}", analytic_price_european);
145    let abs_error_price_european = (mc_price_european - analytic_price_european).abs();
146    let rel_error_price_european = abs_error_price_european / analytic_price_european;
147    println!("Absolute Error (Price): {}", abs_error_price_european);
148    println!("Relative Error (Price): {}\n", rel_error_price_european);
149
150    // Benchmark MC Delta (Pathwise) for European Call
151    timer.start();
152    let mc_delta_european = mc_delta_european_call_gbm_pathwise(&cfg_european_call);
153    let delta_time_european = timer.elapsed_ms();
154    println!("MC Delta (European Call Pathwise): {} ({} ms)", mc_delta_european, delta_time_european);
155    let analytic_delta_european = bs_analytic::bs_call_delta(cfg_european_call.s0, k, cfg_european_call.r, cfg_european_call.sigma, cfg_european_call.t);
156    println!("Analytic Delta (European Call): {}", analytic_delta_european);
157    let abs_error_delta_european = (mc_delta_european - analytic_delta_european).abs();
158    let rel_error_delta_european = abs_error_delta_european / analytic_delta_european;
159    println!("Absolute Error (Delta): {}", abs_error_delta_european);
160    println!("Relative Error (Delta): {}\n", rel_error_delta_european);
161
162    // Benchmark MC Vega (Pathwise) for European Call
163    timer.start();
164    let mc_vega_european = mc_vega_european_call_gbm_pathwise(&cfg_european_call);
165    let vega_time_european = timer.elapsed_ms();
166    println!("MC Vega (European Call Pathwise): {} ({} ms)", mc_vega_european, vega_time_european);
167    let analytic_vega_european = bs_analytic::bs_call_vega(cfg_european_call.s0, k, cfg_european_call.r, cfg_european_call.sigma, cfg_european_call.t);
168    println!("Analytic Vega (European Call): {}", analytic_vega_european);
169    let abs_error_vega_european = (mc_vega_european - analytic_vega_european).abs();
170    let rel_error_vega_european = abs_error_vega_european / analytic_vega_european;
171    println!("Absolute Error (Vega): {}", abs_error_vega_european);
172    println!("Relative Error (Vega): {}\n", rel_error_vega_european);
173
174    // Benchmark MC Rho (Pathwise) for European Call
175    timer.start();
176    let mc_rho_european = mc_rho_european_call_gbm_pathwise(&cfg_european_call);
177    let rho_time_european = timer.elapsed_ms();
178    println!("MC Rho (European Call Pathwise): {} ({} ms)", mc_rho_european, rho_time_european);
179    let analytic_rho_european = bs_analytic::bs_call_rho(cfg_european_call.s0, k, cfg_european_call.r, cfg_european_call.sigma, cfg_european_call.t);
180    println!("Analytic Rho (European Call): {}", analytic_rho_european);
181    let abs_error_rho_european = (mc_rho_european - analytic_rho_european).abs();
182    let rel_error_rho_european = abs_error_rho_european / analytic_rho_european;
183    println!("Absolute Error (Rho): {}", abs_error_rho_european);
184    println!("Relative Error (Rho): {}\n", rel_error_rho_european);
185
186    // Benchmark MC Gamma (Finite Difference) for European Call
187    timer.start();
188    let mc_gamma_european = mc_gamma_european_call_gbm_finite_diff_batched(&cfg_european_call);
189    let gamma_time_european = timer.elapsed_ms();
190    println!("MC Gamma (European Call Finite Diff): {} ({} ms)", mc_gamma_european, gamma_time_european);
191    let analytic_gamma_european = bs_analytic::bs_call_gamma(cfg_european_call.s0, k, cfg_european_call.r, cfg_european_call.sigma, cfg_european_call.t);
192    println!("Analytic Gamma (European Call): {}", analytic_gamma_european);
193    let abs_error_gamma_european = (mc_gamma_european - analytic_gamma_european).abs();
194    let rel_error_gamma_european = abs_error_gamma_european / analytic_gamma_european;
195    println!("Absolute Error (Gamma): {}", abs_error_gamma_european);
196    println!("Relative Error (Gamma): {}\n", rel_error_gamma_european);
197
198    // --- Asian Call Pricing ---
199    println!("--- Asian Call Pricing ---");
200
201    // For Asian option, we need to simulate paths to get average price
202    // No simple analytic solution for Asian option under GBM to compare directly
203
204    let mut timer_asian = Timer::new();
205    timer_asian.start();
206    let (mc_price_asian, _variance_asian) = mc_price_option_gbm(&cfg_asian_call).expect("Valid configuration");
207    let price_time_asian = timer_asian.elapsed_ms();
208    println!("MC Price (Asian Call): {} ({} ms)\n", mc_price_asian, price_time_asian);
209    let elapsed_sec_price_asian = price_time_asian as f64 / 1000.0;
210    println!("Throughput: {:.2} paths/sec\n", cfg_asian_call.paths as f64 / elapsed_sec_price_asian);
211
212    // --- Barrier Call Up and Out Pricing ---
213    println!("--- Barrier Call Up and Out Pricing ---");
214    let mut timer_barrier_call = Timer::new();
215    timer_barrier_call.start();
216    let (mc_price_barrier_call, _variance_barrier_call) = mc_price_option_gbm(&cfg_barrier_call_up_and_out).expect("Valid configuration");
217    let price_time_barrier_call = timer_barrier_call.elapsed_ms();
218    println!("MC Price (Barrier Call Up and Out): {} ({} ms)\n", mc_price_barrier_call, price_time_barrier_call);
219    let elapsed_sec_price_barrier_call = price_time_barrier_call as f64 / 1000.0;
220    println!("Throughput: {:.2} paths/sec\n", cfg_barrier_call_up_and_out.paths as f64 / elapsed_sec_price_barrier_call);
221
222    // --- Barrier Put Up and Out Pricing ---
223    println!("--- Barrier Put Up and Out Pricing ---");
224    let mut timer_barrier_put = Timer::new();
225    timer_barrier_put.start();
226    let (mc_price_barrier_put, _variance_barrier_put) = mc_price_option_gbm(&cfg_barrier_put_up_and_out).expect("Valid configuration");
227    let price_time_barrier_put = timer_barrier_put.elapsed_ms();
228    println!("MC Price (Barrier Put Up and Out): {} ({} ms)\n", mc_price_barrier_put, price_time_barrier_put);
229    let elapsed_sec_price_barrier_put = price_time_barrier_put as f64 / 1000.0;
230    println!("Throughput: {:.2} paths/sec\n", cfg_barrier_put_up_and_out.paths as f64 / elapsed_sec_price_barrier_put);
231
232    // --- CSV Output ---
233    // For CSV output, we need to generate paths with the new Payoff structure
234    let path_data_for_csv: Vec<(f64, f64, f64)> = (0..paths)
235        .into_par_iter()
236        .map(|i| {
237            let mut rng = rng::seed_rng_from_u64(cfg_european_call.seed + i as u64);
238            let mut path_prices = Vec::with_capacity(steps + 1);
239            path_prices.push(s0);
240
241            let mut current_s = s0;
242            let dt = t / steps as f64;
243            let sqrt_dt = dt.sqrt();
244
245            for _ in 0..steps {
246                let z = rng::get_normal_draw(&mut rng);
247                current_s *= ((r - 0.5 * sigma * sigma) * dt + sigma * sqrt_dt * z).exp();
248                path_prices.push(current_s);
249            }
250            
251            let payoff_european = cfg_european_call.payoff.calculate(&path_prices);
252            let delta_path_european = if *path_prices.last().unwrap() > k { *path_prices.last().unwrap() / s0 } else { 0.0 };
253            (*path_prices.last().unwrap(), payoff_european, delta_path_european)
254        })
255        .collect();
256
257    let paths_csv_filename = "results/paths.csv";
258    match output::write_paths_to_csv(paths_csv_filename, &path_data_for_csv) {
259        Ok(_) => println!("Path data written to {}", paths_csv_filename),
260        Err(e) => eprintln!("Error writing path data: {}", e),
261    }
262
263    // Collect summary data into owned Strings
264    let mc_price_european_str = mc_price_european.to_string();
265    let analytic_price_european_str = analytic_price_european.to_string();
266    let abs_error_price_european_str = abs_error_price_european.to_string();
267    let rel_error_price_european_str = rel_error_price_european.to_string();
268    let price_time_european_str = price_time_european.to_string();
269
270    let mc_delta_european_str = mc_delta_european.to_string();
271    let analytic_delta_european_str = analytic_delta_european.to_string();
272    let abs_error_delta_european_str = abs_error_delta_european.to_string();
273    let rel_error_delta_european_str = rel_error_delta_european.to_string();
274    let delta_time_european_str = delta_time_european.to_string();
275
276    let mc_price_asian_str = mc_price_asian.to_string();
277    let price_time_asian_str = price_time_asian.to_string();
278
279    let mc_price_barrier_call_str = mc_price_barrier_call.to_string();
280    let price_time_barrier_call_str = price_time_barrier_call.to_string();
281
282    let mc_price_barrier_put_str = mc_price_barrier_put.to_string();
283    let price_time_barrier_put_str = price_time_barrier_put.to_string();
284
285    let summary_data = vec![
286        ("metric", "value"),
287        ("mc_price_european", &mc_price_european_str),
288        ("analytic_price_european", &analytic_price_european_str),
289        ("abs_error_price_european", &abs_error_price_european_str),
290        ("rel_error_price_european", &rel_error_price_european_str),
291        ("price_time_ms_european", &price_time_european_str),
292        ("mc_delta_european", &mc_delta_european_str),
293        ("analytic_delta_european", &analytic_delta_european_str),
294        ("abs_error_delta_european", &abs_error_delta_european_str),
295        ("rel_error_delta_european", &rel_error_delta_european_str),
296        ("delta_time_ms_european", &delta_time_european_str),
297        ("mc_price_asian", &mc_price_asian_str),
298        ("price_time_ms_asian", &price_time_asian_str),
299        ("mc_price_barrier_call", &mc_price_barrier_call_str),
300        ("price_time_ms_barrier_call", &price_time_barrier_call_str),
301        ("mc_price_barrier_put", &mc_price_barrier_put_str),
302        ("price_time_ms_barrier_put", &price_time_barrier_put_str),
303    ];
304
305    // Write summary to CSV
306    let summary_csv_filename = "results/summary.csv";
307    match output::write_summary_to_csv(summary_csv_filename, &summary_data) {
308        Ok(_) => println!("Summary data written to {}", summary_csv_filename),
309        Err(e) => eprintln!("Error writing summary data: {}", e),
310    }
311}