1#![allow(non_snake_case)]
14#![allow(clippy::type_complexity)]
15#![allow(clippy::too_many_arguments)]
16
17pub mod convenience;
18pub mod grid_plotter;
19pub mod plottable;
20
21pub use convenience::plot_distribution;
22pub use convenience::plot_process;
23pub use convenience::plot_vol_surface;
24pub use grid_plotter::GridPlotter;
25pub use plottable::Plottable;
26
27#[cfg(test)]
28mod tests {
29 use ndarray::Array1;
30 use plotly::Layout;
31 use plotly::Plot;
32 use plotly::Scatter;
33 use plotly::Surface;
34 use plotly::common::DashType;
35 use plotly::common::Mode;
36 use rand_distr::Exp;
37 use rand_distr::Normal;
38 use stochastic_rs_stochastic::autoregressive::agrach::Agarch;
39 use stochastic_rs_stochastic::autoregressive::ar::ARp;
40 use stochastic_rs_stochastic::autoregressive::arch::Arch;
41 use stochastic_rs_stochastic::autoregressive::arima::Arima;
42 use stochastic_rs_stochastic::autoregressive::egarch::Egarch;
43 use stochastic_rs_stochastic::autoregressive::garch::Garch;
44 use stochastic_rs_stochastic::autoregressive::ma::MAq;
45 use stochastic_rs_stochastic::autoregressive::sarima::Sarima;
46 use stochastic_rs_stochastic::autoregressive::tgarch::Tgarch;
47 use stochastic_rs_stochastic::diffusion::cev::Cev;
48 use stochastic_rs_stochastic::diffusion::cfou::Cfou;
49 use stochastic_rs_stochastic::diffusion::cir::Cir as DiffCIR;
50 use stochastic_rs_stochastic::diffusion::fcir::Fcir;
51 use stochastic_rs_stochastic::diffusion::feller::FellerLogistic;
52 use stochastic_rs_stochastic::diffusion::fgbm::Fgbm;
53 use stochastic_rs_stochastic::diffusion::fjacobi::FJacobi;
54 use stochastic_rs_stochastic::diffusion::fou::Fou;
55 use stochastic_rs_stochastic::diffusion::fouque::FouqueOU2D;
56 use stochastic_rs_stochastic::diffusion::gbm::Gbm;
57 use stochastic_rs_stochastic::diffusion::gbm_ih::GbmIh;
58 use stochastic_rs_stochastic::diffusion::gompertz::Gompertz;
59 use stochastic_rs_stochastic::diffusion::jacobi::Jacobi;
60 use stochastic_rs_stochastic::diffusion::kimura::Kimura;
61 use stochastic_rs_stochastic::diffusion::ou::Ou;
62 use stochastic_rs_stochastic::diffusion::quadratic::Quadratic;
63 use stochastic_rs_stochastic::diffusion::verhulst::Verhulst;
64 use stochastic_rs_stochastic::interest::adg::Adg;
65 use stochastic_rs_stochastic::interest::bgm::Bgm;
66 use stochastic_rs_stochastic::interest::cir::Cir as RateCIR;
67 use stochastic_rs_stochastic::interest::cir_2f::Cir2F;
68 use stochastic_rs_stochastic::interest::duffie_kan::DuffieKan;
69 use stochastic_rs_stochastic::interest::duffie_kan_jump_exp::DuffieKanJumpExp;
70 use stochastic_rs_stochastic::interest::fractional_vasicek::FVasicek;
71 use stochastic_rs_stochastic::interest::hjm::Hjm;
72 use stochastic_rs_stochastic::interest::ho_lee::HoLee;
73 use stochastic_rs_stochastic::interest::hull_white::HullWhite;
74 use stochastic_rs_stochastic::interest::hull_white_2f::HullWhite2F;
75 use stochastic_rs_stochastic::interest::vasicek::Vasicek;
76 use stochastic_rs_stochastic::interest::wu_zhang::WuZhangD;
77 use stochastic_rs_stochastic::isonormal::IsoNormal;
78 use stochastic_rs_stochastic::isonormal::fbm_custom_inc_cov;
79 use stochastic_rs_stochastic::jump::bates::Bates1996;
80 use stochastic_rs_stochastic::jump::cgmy::Cgmy;
81 use stochastic_rs_stochastic::jump::cts::Cts;
82 use stochastic_rs_stochastic::jump::ig::Ig;
83 use stochastic_rs_stochastic::jump::jump_fou::JumpFou;
84 use stochastic_rs_stochastic::jump::jump_fou_custom::JumpFOUCustom;
85 use stochastic_rs_stochastic::jump::kobol::KoBoL;
86 use stochastic_rs_stochastic::jump::kou::Kou;
87 use stochastic_rs_stochastic::jump::levy_diffusion::LevyDiffusion;
88 use stochastic_rs_stochastic::jump::merton::Merton;
89 use stochastic_rs_stochastic::jump::nig::Nig;
90 use stochastic_rs_stochastic::jump::rdts::Rdts;
91 use stochastic_rs_stochastic::jump::vg::Vg;
92 use stochastic_rs_stochastic::noise::cfgns::Cfgns;
93 use stochastic_rs_stochastic::noise::cgns::Cgns;
94 use stochastic_rs_stochastic::noise::fgn::Fgn;
95 use stochastic_rs_stochastic::noise::gn::Gn;
96 use stochastic_rs_stochastic::noise::wn::Wn;
97 use stochastic_rs_stochastic::process::bm::Bm;
98 use stochastic_rs_stochastic::process::cbms::Cbms;
99 use stochastic_rs_stochastic::process::ccustom::CompoundCustom;
100 use stochastic_rs_stochastic::process::cfbms::Cfbms;
101 use stochastic_rs_stochastic::process::cpoisson::CompoundPoisson;
102 use stochastic_rs_stochastic::process::customjt::CustomJt;
103 use stochastic_rs_stochastic::process::fbm::Fbm;
104 use stochastic_rs_stochastic::process::lfsm::Lfsm;
105 use stochastic_rs_stochastic::process::poisson::Poisson;
106 use stochastic_rs_stochastic::process::subordinator::AlphaStableSubordinator;
107 use stochastic_rs_stochastic::process::subordinator::Ctrw;
108 use stochastic_rs_stochastic::process::subordinator::CtrwJumpLaw;
109 use stochastic_rs_stochastic::process::subordinator::CtrwWaitingLaw;
110 use stochastic_rs_stochastic::process::subordinator::GammaSubordinator;
111 use stochastic_rs_stochastic::process::subordinator::IGSubordinator;
112 use stochastic_rs_stochastic::process::subordinator::InverseAlphaStableSubordinator;
113 use stochastic_rs_stochastic::process::subordinator::PoissonSubordinator;
114 use stochastic_rs_stochastic::process::subordinator::TemperedStableSubordinator;
115 use stochastic_rs_stochastic::sheet::fbs::Fbs;
116 use stochastic_rs_stochastic::traits::ProcessExt;
117 use stochastic_rs_stochastic::volatility::HestonPow;
118 use stochastic_rs_stochastic::volatility::bergomi::Bergomi;
119 use stochastic_rs_stochastic::volatility::fheston::RoughHeston;
120 use stochastic_rs_stochastic::volatility::heston::Heston;
121 use stochastic_rs_stochastic::volatility::rbergomi::RoughBergomi;
122 use stochastic_rs_stochastic::volatility::sabr::Sabr;
123 use stochastic_rs_stochastic::volatility::svcgmy::Svcgmy;
124
125 use super::*;
126
127 fn f_const_001(_: f64) -> f64 {
128 0.01
129 }
130
131 fn f_const_002(_: f64) -> f64 {
132 0.02
133 }
134
135 fn f_linear_small(t: f64) -> f64 {
136 0.01 + 0.005 * t
137 }
138
139 fn f_phi_small(t: f64) -> f64 {
140 0.002 * t
141 }
142
143 fn f_hjm_p(t: f64, u: f64) -> f64 {
144 0.01 + 0.01 * (u - t).max(0.0)
145 }
146
147 fn f_hjm_q(_: f64, _: f64) -> f64 {
148 0.5
149 }
150
151 fn f_hjm_v(_: f64, _: f64) -> f64 {
152 0.02
153 }
154
155 fn f_hjm_alpha(_: f64, _: f64) -> f64 {
156 0.01
157 }
158
159 fn f_hjm_sigma(_: f64, _: f64) -> f64 {
160 0.015
161 }
162
163 fn f_adg_k(t: f64) -> f64 {
164 0.02 + 0.002 * t
165 }
166
167 fn f_adg_theta(_: f64) -> f64 {
168 0.6
169 }
170
171 fn f_adg_phi(_: f64) -> f64 {
172 0.01
173 }
174
175 fn f_adg_b(_: f64) -> f64 {
176 0.2
177 }
178
179 fn f_adg_c(_: f64) -> f64 {
180 0.05
181 }
182
183 fn normal_cpoisson(lambda: f64, n: usize, jump_sigma: f64) -> CompoundPoisson<f64, Normal<f64>> {
184 CompoundPoisson::new(
185 Normal::new(0.0, jump_sigma).expect("valid normal"),
186 Poisson::new(lambda, Some(n), Some(1.0)),
187 )
188 }
189
190 #[test]
191 fn plot_grid() {
192 let n = 96;
193 let traj = 1;
194 let j = 64;
195 let sheet_m = 3;
196 let sheet_n = 64;
197
198 let mut isonormal_fbm = IsoNormal::new(
199 |aux_idx, idx| fbm_custom_inc_cov(aux_idx.abs_diff(idx), 0.7),
200 (0..n).collect(),
201 );
202 let mut isonormal_paths = Vec::with_capacity(traj);
203 for _ in 0..traj {
204 let increments = isonormal_fbm.get_path();
205 let mut path = Vec::with_capacity(n);
206 path.push(0.0);
207 let mut acc = 0.0;
208 for &dx in &increments {
209 acc += dx;
210 path.push(acc);
211 }
212 isonormal_paths.push(path);
213 }
214
215 let mut grid = GridPlotter::new()
216 .title("Stochastic Processes (Grid)")
217 .cols(4)
218 .show_legend(false)
219 .line_width(1.2)
220 .x_gap(0.80)
221 .y_gap(5.00);
222
223 grid = grid.register(
224 &ARp::new(Array1::from_vec(vec![0.65, -0.2]), 0.08, n, None),
225 "Autoreg: AR(2)",
226 traj,
227 );
228 grid = grid.register(
229 &MAq::new(Array1::from_vec(vec![0.5, -0.2]), 0.1, n),
230 "Autoreg: MA(2)",
231 traj,
232 );
233 grid = grid.register(
234 &Arima::new(
235 Array1::from_vec(vec![0.4]),
236 Array1::from_vec(vec![0.3]),
237 1,
238 0.1,
239 n,
240 ),
241 "Autoreg: Arima(1,1,1)",
242 traj,
243 );
244 grid = grid.register(
245 &Sarima::new(
246 Array1::from_vec(vec![0.3]),
247 Array1::from_vec(vec![0.2]),
248 Array1::from_vec(vec![0.2]),
249 Array1::from_vec(vec![0.1]),
250 1,
251 1,
252 12,
253 0.08,
254 n,
255 ),
256 "Autoreg: Sarima",
257 traj,
258 );
259 grid = grid.register(
260 &Arch::new(0.05, Array1::from_vec(vec![0.2, 0.1]), n),
261 "Autoreg: Arch",
262 traj,
263 );
264 grid = grid.register(
265 &Garch::new(
266 0.03,
267 Array1::from_vec(vec![0.12]),
268 Array1::from_vec(vec![0.8]),
269 n,
270 ),
271 "Autoreg: Garch",
272 traj,
273 );
274 grid = grid.register(
275 &Tgarch::new(
276 0.03,
277 Array1::from_vec(vec![0.08]),
278 Array1::from_vec(vec![0.05]),
279 Array1::from_vec(vec![0.85]),
280 n,
281 ),
282 "Autoreg: Tgarch",
283 traj,
284 );
285 grid = grid.register(
286 &Egarch::new(
287 -0.1,
288 Array1::from_vec(vec![0.1]),
289 Array1::from_vec(vec![-0.05]),
290 Array1::from_vec(vec![0.9]),
291 n,
292 ),
293 "Autoreg: Egarch",
294 traj,
295 );
296 grid = grid.register(
297 &Agarch::new(
298 0.03,
299 Array1::from_vec(vec![0.1]),
300 Array1::from_vec(vec![0.04]),
301 Array1::from_vec(vec![0.84]),
302 n,
303 ),
304 "Autoreg: Agarch",
305 traj,
306 );
307
308 grid = grid.register(&Wn::new(n, Some(0.0), Some(1.0)), "Noise: White", traj);
309 grid = grid.register(&Gn::new(n, Some(1.0)), "Noise: Gaussian", traj);
310 grid = grid.register(&Fgn::new(0.7, n, Some(1.0)), "Noise: Fgn", traj);
311 grid = grid.register(&Cgns::new(-0.4, n, Some(1.0)), "Noise: Cgns", traj);
312 grid = grid.register(&Cfgns::new(0.7, -0.3, n, Some(1.0)), "Noise: Cfgns", traj);
313
314 grid = grid.register(&Bm::new(n, Some(1.0)), "Process: Bm", traj);
315 grid = grid.register(&Fbm::new(0.7, n, Some(1.0)), "Process: Fbm", traj);
316 grid = grid.register_paths(isonormal_paths, "Process: fBM via IsoNormal (H=0.7)");
317 grid = grid.register(
318 &Poisson::new(2.0, Some(n), Some(1.0)),
319 "Process: Poisson",
320 traj,
321 );
322 grid = grid.register(
323 &CustomJt::new(
324 Some(n),
325 Some(1.0),
326 Exp::new(10.0).expect("positive exponential rate"),
327 ),
328 "Process: CustomJt",
329 traj,
330 );
331 grid = grid.register(
332 &CompoundPoisson::new(
333 Normal::new(0.0, 0.15).expect("valid normal"),
334 Poisson::new(1.2, Some(n), Some(1.0)),
335 ),
336 "Process: CompoundPoisson",
337 traj,
338 );
339 grid = grid.register(
340 &CompoundCustom::new(
341 Some(n),
342 Some(1.0),
343 Normal::new(0.0, 0.1).expect("valid normal"),
344 Exp::new(15.0).expect("positive exponential rate"),
345 CustomJt::new(
346 Some(n),
347 Some(1.0),
348 Exp::new(15.0).expect("positive exponential rate"),
349 ),
350 ),
351 "Process: CompoundCustom",
352 traj,
353 );
354 grid = grid.register(&Cbms::new(0.35, n, Some(1.0)), "Process: Cbms", traj);
355 grid = grid.register(&Cfbms::new(0.7, 0.35, n, Some(1.0)), "Process: Cfbms", traj);
356 grid = grid.register(
357 &Lfsm::new(1.7, 0.0, 0.8, 1.0, n, Some(0.0), Some(1.0)),
358 "Process: Lfsm",
359 traj,
360 );
361 grid = grid.register(
362 &AlphaStableSubordinator::new(0.7, 1.0, n, Some(0.0), Some(1.0)),
363 "Process: AlphaStable Subordinator",
364 traj,
365 );
366 grid = grid.register(
367 &InverseAlphaStableSubordinator::new(0.7, 1.0, n, Some(1.0), 2048, Some(4.0)),
368 "Process: Inverse AlphaStable",
369 traj,
370 );
371 grid = grid.register(
372 &PoissonSubordinator::new(2.0, n, Some(0.0), Some(1.0)),
373 "Process: Poisson Subordinator",
374 traj,
375 );
376 grid = grid.register(
377 &GammaSubordinator::new(3.0, 5.0, n, Some(0.0), Some(1.0)),
378 "Process: Gamma Subordinator",
379 traj,
380 );
381 grid = grid.register(
382 &IGSubordinator::new(1.5, 2.0, n, Some(0.0), Some(1.0)),
383 "Process: Ig Subordinator",
384 traj,
385 );
386 grid = grid.register(
387 &TemperedStableSubordinator::new(0.7, 1.0, 2.0, 0.05, n, Some(0.0), Some(1.0)),
388 "Process: Tempered Stable Subordinator",
389 traj,
390 );
391 grid = grid.register(
392 &Ctrw::new(
393 CtrwWaitingLaw::Exponential { rate: 2.0 },
394 CtrwJumpLaw::Normal {
395 mean: 0.0,
396 std: 0.3,
397 },
398 n,
399 Some(0.0),
400 Some(1.0),
401 ),
402 "Process: Ctrw",
403 traj,
404 );
405
406 grid = grid.register(
407 &Ou::new(2.0, 0.0, 0.2, n, Some(0.0), Some(1.0)),
408 "Diffusion: Ou",
409 traj,
410 );
411 grid = grid.register(
412 &Gbm::new(0.05, 0.2, n, Some(100.0), Some(1.0)),
413 "Diffusion: Gbm",
414 traj,
415 );
416 grid = grid.register(
417 &DiffCIR::new(2.5, 0.04, 0.2, n, Some(0.04), Some(1.0), Some(false)),
418 "Diffusion: Cir",
419 traj,
420 );
421 grid = grid.register(
422 &Cev::new(0.04, 0.2, 0.8, n, Some(1.0), Some(1.0)),
423 "Diffusion: Cev",
424 traj,
425 );
426 grid = grid.register(
427 &FellerLogistic::new(2.0, 1.0, 0.3, n, Some(0.5), Some(1.0), Some(false)),
428 "Diffusion: Feller Logistic",
429 traj,
430 );
431 grid = grid.register(
432 &Verhulst::new(1.2, 2.0, 0.2, n, Some(0.5), Some(1.0), Some(true)),
433 "Diffusion: Verhulst",
434 traj,
435 );
436 grid = grid.register(
437 &Gompertz::new(1.0, 0.8, 0.2, n, Some(1.0), Some(1.0)),
438 "Diffusion: Gompertz",
439 traj,
440 );
441 grid = grid.register(
442 &Kimura::new(1.0, 0.3, n, Some(0.4), Some(1.0)),
443 "Diffusion: Kimura",
444 traj,
445 );
446 grid = grid.register(
447 &Quadratic::new(0.1, -0.2, 0.05, 0.15, n, Some(1.0), Some(1.0)),
448 "Diffusion: Quadratic",
449 traj,
450 );
451 grid = grid.register(
452 &Jacobi::new(0.8, 1.4, 0.4, n, Some(0.3), Some(1.0)),
453 "Diffusion: Jacobi",
454 traj,
455 );
456 grid = grid.register(
457 &Fcir::new(0.7, 2.5, 0.04, 0.2, n, Some(0.04), Some(1.0), Some(false)),
458 "Diffusion: Fcir",
459 traj,
460 );
461 grid = grid.register(
462 &FJacobi::new(0.7, 0.8, 1.4, 0.35, n, Some(0.3), Some(1.0)),
463 "Diffusion: FJacobi",
464 traj,
465 );
466 grid = grid.register(
467 &Fou::new(0.7, 2.0, 0.0, 0.2, n, Some(0.0), Some(1.0)),
468 "Diffusion: Fou",
469 traj,
470 );
471 grid = grid.register(
472 &Cfou::new(0.7, 1.8, 3.0, 0.4, n, Some(0.0), Some(0.0), Some(1.0)),
473 "Diffusion: Complex fOU",
474 traj,
475 );
476 grid = grid.register(
477 &Fgbm::new(0.7, 0.04, 0.2, n, Some(100.0), Some(1.0)),
478 "Diffusion: Fgbm",
479 traj,
480 );
481 grid = grid.register(
482 &GbmIh::new(0.04, 0.2, n, Some(100.0), Some(1.0), None),
483 "Diffusion: GbmIh",
484 traj,
485 );
486 grid = grid.register(
487 &FouqueOU2D::new(1.5, 0.0, 0.3, 0.0, n, Some(0.0), Some(0.0), Some(1.0)),
488 "Diffusion: Fouque Ou 2D",
489 traj,
490 );
491
492 grid = grid.register(
493 &Vasicek::new(3.0, 0.03, 0.02, n, Some(0.03), Some(1.0)),
494 "Interest: Vasicek",
495 traj,
496 );
497 grid = grid.register(
498 &FVasicek::new(0.7, 2.0, 0.03, 0.02, n, Some(0.03), Some(1.0)),
499 "Interest: Fractional Vasicek",
500 traj,
501 );
502 grid = grid.register(
503 &RateCIR::new(2.5, 0.04, 0.2, n, Some(0.04), Some(1.0), Some(false)),
504 "Interest: Cir (Alias)",
505 traj,
506 );
507 grid = grid.register(
508 &HoLee::new(None, Some(0.01), 0.01, n, Some(1.0)),
509 "Interest: Ho-Lee",
510 traj,
511 );
512 grid = grid.register(
513 &HullWhite::new(
514 f_linear_small as fn(f64) -> f64,
515 0.4,
516 0.02,
517 n,
518 Some(0.02),
519 Some(1.0),
520 ),
521 "Interest: Hull-White",
522 traj,
523 );
524 grid = grid.register(
525 &HullWhite2F::new(
526 f_const_001 as fn(f64) -> f64,
527 0.5,
528 0.02,
529 0.015,
530 -0.3,
531 0.4,
532 Some(0.02),
533 Some(1.0),
534 n,
535 ),
536 "Interest: Hull-White 2F",
537 traj,
538 );
539 grid = grid.register(
540 &Hjm::new(
541 f_const_001 as fn(f64) -> f64,
542 f_const_002 as fn(f64) -> f64,
543 f_hjm_p as fn(f64, f64) -> f64,
544 f_hjm_q as fn(f64, f64) -> f64,
545 f_hjm_v as fn(f64, f64) -> f64,
546 f_hjm_alpha as fn(f64, f64) -> f64,
547 f_hjm_sigma as fn(f64, f64) -> f64,
548 n,
549 Some(0.01),
550 Some(1.0),
551 Some(0.01),
552 Some(1.0),
553 ),
554 "Interest: Hjm",
555 traj,
556 );
557 grid = grid.register(
558 &Bgm::new(
559 Array1::from_vec(vec![0.2, 0.15]),
560 Array1::from_vec(vec![0.02, 0.025]),
561 2,
562 Some(1.0),
563 n,
564 ),
565 "Interest: Bgm",
566 traj,
567 );
568 grid = grid.register(
569 &Adg::new(
570 f_adg_k as fn(f64) -> f64,
571 f_adg_theta as fn(f64) -> f64,
572 Array1::from_vec(vec![0.02, 0.018]),
573 f_adg_phi as fn(f64) -> f64,
574 f_adg_b as fn(f64) -> f64,
575 f_adg_c as fn(f64) -> f64,
576 n,
577 2,
578 Array1::from_vec(vec![0.01, 0.015]),
579 Some(1.0),
580 ),
581 "Interest: Adg",
582 traj,
583 );
584 grid = grid.register(
585 &DuffieKan::new(
586 0.2,
587 0.1,
588 0.05,
589 -0.3,
590 -0.1,
591 0.2,
592 0.01,
593 0.1,
594 0.15,
595 -0.2,
596 0.01,
597 0.12,
598 n,
599 Some(0.02),
600 Some(0.01),
601 Some(1.0),
602 ),
603 "Interest: Duffie-Kan",
604 traj,
605 );
606 grid = grid.register(
607 &DuffieKanJumpExp::new(
608 0.2,
609 0.1,
610 0.05,
611 -0.3,
612 -0.1,
613 0.2,
614 0.01,
615 0.1,
616 0.15,
617 -0.2,
618 0.01,
619 0.12,
620 2.0,
621 0.02,
622 n,
623 Some(0.02),
624 Some(0.01),
625 Some(1.0),
626 ),
627 "Interest: Duffie-Kan Jump Exp",
628 traj,
629 );
630 grid = grid.register(
631 &WuZhangD::new(
632 Array1::from_vec(vec![0.05, 0.04]),
633 Array1::from_vec(vec![1.2, 1.0]),
634 Array1::from_vec(vec![0.3, 0.25]),
635 Array1::from_vec(vec![0.4, 0.3]),
636 Array1::from_vec(vec![0.02, 0.025]),
637 Array1::from_vec(vec![0.04, 0.03]),
638 2,
639 Some(1.0),
640 n,
641 ),
642 "Interest: Wu-Zhang",
643 traj,
644 );
645 grid = grid.register(
646 &Cir2F::new(
647 RateCIR::new(2.5, 0.03, 0.12, n, Some(0.03), Some(1.0), Some(false)),
648 RateCIR::new(2.0, 0.02, 0.1, n, Some(0.02), Some(1.0), Some(false)),
649 f_phi_small as fn(f64) -> f64,
650 ),
651 "Interest: Cir 2F",
652 traj,
653 );
654
655 grid = grid.register(
656 &Vg::new(0.0, 0.2, 0.15, n, Some(0.0), Some(1.0)),
657 "Jump: Vg",
658 traj,
659 );
660 grid = grid.register(
661 &Nig::new(0.0, 0.2, 0.3, n, Some(0.0), Some(1.0)),
662 "Jump: Nig",
663 traj,
664 );
665 grid = grid.register(&Ig::new(1.0, n, Some(0.0), Some(1.0)), "Jump: Ig", traj);
666 grid = grid.register(
667 &Rdts::new(4.0, 5.0, 0.7, n, j, Some(0.0), Some(1.0)),
668 "Jump: Rdts",
669 traj,
670 );
671 grid = grid.register(
672 &Cts::new(4.0, 5.0, 0.7, n, j, Some(0.0), Some(1.0)),
673 "Jump: Cts",
674 traj,
675 );
676
677 let g = 4.0;
678 let m = 5.0;
679 let y = 0.7;
680
681 let c = Cgmy::<f64>::c_for_unit_variance(g, m, y);
682 let d = KoBoL::<f64>::d_for_unit_variance(1.0, 1.0, g, m, y);
684
685 grid = grid.register(
686 &Cgmy::<f64>::new(c, g, m, y, n, j, Some(0.0), Some(1.0)),
687 "Jump: Cgmy (unit var, symmetric)",
688 traj,
689 );
690
691 grid = grid.register(
692 &KoBoL::<f64>::new(d, 1.0, 1.0, g, m, y, n, j, Some(0.0), Some(1.0)),
693 "Jump: KoBoL (unit var, p=q=1)",
694 traj,
695 );
696
697 grid = grid.register(
698 &Merton::new(
699 0.03,
700 0.2,
701 1.0,
702 0.0,
703 n,
704 Some(0.0),
705 Some(1.0),
706 normal_cpoisson(1.0, n, 0.1),
707 ),
708 "Jump: Merton",
709 traj,
710 );
711 grid = grid.register(
712 &Kou::new(
713 0.03,
714 0.2,
715 1.0,
716 0.0,
717 n,
718 Some(0.0),
719 Some(1.0),
720 normal_cpoisson(1.0, n, 0.12),
721 ),
722 "Jump: Kou",
723 traj,
724 );
725 grid = grid.register(
726 &LevyDiffusion::new(
727 0.01,
728 0.2,
729 n,
730 Some(0.0),
731 Some(1.0),
732 normal_cpoisson(1.0, n, 0.08),
733 ),
734 "Jump: Levy Diffusion",
735 traj,
736 );
737 grid = grid.register(
738 &JumpFou::new(
739 0.7,
740 2.0,
741 0.03,
742 0.2,
743 n,
744 Some(0.03),
745 Some(1.0),
746 normal_cpoisson(1.0, n, 0.08),
747 ),
748 "Jump: Jump-Fou",
749 traj,
750 );
751 grid = grid.register(
752 &JumpFOUCustom::new(
753 0.7,
754 2.0,
755 0.03,
756 0.2,
757 n,
758 Some(0.03),
759 Some(1.0),
760 Exp::new(20.0).expect("positive exponential rate"),
761 Exp::new(30.0).expect("positive exponential rate"),
762 ),
763 "Jump: Jump-Fou Custom",
764 traj,
765 );
766 grid = grid.register_with_component_labels(
767 &Bates1996::new(
768 Some(0.03),
769 None,
770 None,
771 None,
772 0.8,
773 0.0,
774 1.5,
775 0.8,
776 0.3,
777 -0.5,
778 n,
779 Some(100.0),
780 Some(0.04),
781 Some(1.0),
782 Some(false),
783 normal_cpoisson(0.8, n, 0.05),
784 ),
785 "Jump: Bates 1996 (S: solid, v: dashed)",
786 &["S", "v"],
787 traj,
788 );
789
790 grid = grid.register(
791 &Heston::new(
792 Some(100.0),
793 Some(0.04),
794 2.0,
795 0.04,
796 0.3,
797 -0.7,
798 0.05,
799 n,
800 Some(1.0),
801 HestonPow::Sqrt,
802 Some(false),
803 ),
804 "Volatility: Heston",
805 traj,
806 );
807 grid = grid.register(
808 &Bergomi::new(0.4, Some(0.2), Some(100.0), 0.01, -0.6, n, Some(1.0)),
809 "Volatility: Bergomi",
810 traj,
811 );
812 grid = grid.register(
813 &RoughBergomi::new(0.1, 0.4, Some(0.2), Some(100.0), 0.01, -0.6, n, Some(1.0)),
814 "Volatility: Rough Bergomi",
815 traj,
816 );
817 grid = grid.register(
818 &RoughHeston::new(0.8, Some(0.2), 0.04, 1.5, 0.3, None, None, Some(1.0), n),
819 "Volatility: Rough Heston",
820 traj,
821 );
822 grid = grid.register(
823 &Sabr::new(0.4, 0.7, -0.3, n, Some(1.0), Some(0.3), Some(1.0)),
824 "Volatility: Sabr",
825 traj,
826 );
827 grid = grid.register(
828 &Svcgmy::new(
829 3.0,
830 4.0,
831 0.7,
832 1.5,
833 0.04,
834 0.3,
835 -0.4,
836 n,
837 j,
838 Some(0.0),
839 Some(0.04),
840 Some(1.0),
841 ),
842 "Volatility: Svcgmy",
843 traj,
844 );
845
846 grid.show();
847
848 let fbs_field = Fbs::new(0.7, sheet_m, sheet_n, 2.0).sample();
849 let z: Vec<Vec<f64>> = fbs_field.outer_iter().map(|row| row.to_vec()).collect();
850 let x: Vec<f64> = (0..sheet_n)
851 .map(|i| i as f64 / (sheet_n.saturating_sub(1).max(1) as f64))
852 .collect();
853 let y: Vec<f64> = (0..sheet_m)
854 .map(|i| i as f64 / (sheet_m.saturating_sub(1).max(1) as f64))
855 .collect();
856
857 let mut sheet_plot = Plot::new();
858 let surface = Surface::new(z).x(x).y(y).name("Sheet: Fbs");
859 sheet_plot.add_trace(surface);
860 sheet_plot.set_layout(
861 Layout::new()
862 .title("Sheet: Fbs (3D Surface)")
863 .height(900)
864 .width(1200),
865 );
866 sheet_plot.show();
867 }
868
869 #[test]
870 fn plot_sde_gbm_all_methods() {
871 use ndarray::Array2;
872 use ndarray::array;
873 use plotly::Layout;
874 use plotly::common::Line;
875 use plotly::layout::Margin;
876 use rand::rng;
877 use stochastic_rs_stochastic::sde::NoiseModel;
878 use stochastic_rs_stochastic::sde::Sde;
879 use stochastic_rs_stochastic::sde::SdeMethod;
880
881 let mu = 0.05;
882 let sigma = 0.2;
883 let x0 = array![100.0];
884 let t0: f64 = 0.0;
885 let t1: f64 = 1.0;
886 let dt: f64 = 0.001;
887 let n_paths = 5;
888 let steps = ((t1 - t0) / dt).ceil() as usize;
889
890 let t_axis: Vec<f64> = (0..=steps).map(|i| t0 + i as f64 * dt).collect();
891
892 let colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"];
893 let methods = [
894 (SdeMethod::Euler, "Euler-Maruyama"),
895 (SdeMethod::Milstein, "Milstein"),
896 (SdeMethod::SRK2, "Midpoint RK2"),
897 (SdeMethod::SRK4, "RK4-style"),
898 ];
899
900 let mut plot = Plot::new();
901 plot.set_layout(
902 Layout::new()
903 .title("Gbm: SDE Solver Methods Comparison (dS = 0.05 S dt + 0.2 S dW)")
904 .auto_size(true)
905 .height(700)
906 .margin(Margin::new().left(60).right(30).top(80).bottom(50)),
907 );
908
909 for (m_idx, (method, method_name)) in methods.into_iter().enumerate() {
910 let sde = Sde::new(
911 move |x: &ndarray::Array1<f64>, _t: f64| array![mu * x[0]],
912 move |x: &ndarray::Array1<f64>, _t: f64| Array2::from_elem((1, 1), sigma * x[0]),
913 NoiseModel::Gaussian,
914 None,
915 );
916
917 let paths = sde.solve(&x0, t0, t1, dt, n_paths, method, &mut rng());
918
919 for p in 0..n_paths {
920 let y: Vec<f64> = (0..=steps).map(|i| paths[[p, i, 0]]).collect();
921 let name = if p == 0 {
922 method_name.to_string()
923 } else {
924 format!("{method_name} (path {p})")
925 };
926 let trace = Scatter::new(t_axis.clone(), y)
927 .mode(Mode::Lines)
928 .line(
929 Line::new()
930 .width(if p == 0 { 2.0 } else { 1.0 })
931 .color(colors[m_idx])
932 .dash(match p {
933 0 => DashType::Solid,
934 1 => DashType::Dash,
935 2 => DashType::Dot,
936 3 => DashType::DashDot,
937 _ => DashType::LongDash,
938 }),
939 )
940 .name(name.as_str())
941 .show_legend(p == 0);
942 plot.add_trace(trace);
943 }
944 }
945
946 let mut path = std::env::temp_dir();
947 path.push("stochastic_rs_sde_gbm_methods.html");
948 plot.write_html(&path);
949 assert!(path.exists(), "expected plot HTML at {}", path.display());
950 let _ = std::fs::remove_file(path);
951 }
952
953 #[test]
954 fn plot_process_writes_html() {
955 let bm = Bm::new(64, Some(1.0));
956 let path_arr = bm.sample();
957 let mut out = std::env::temp_dir();
958 out.push("stochastic_rs_test_plot_process.html");
959 plot_process(&path_arr, out.to_str().unwrap());
960 assert!(out.exists(), "plot_process did not write file");
961 let _ = std::fs::remove_file(out);
962 }
963
964 #[test]
965 fn plot_distribution_writes_html() {
966 let samples: Array1<f64> = (0..256).map(|i| (i as f64) * 0.01).collect();
967 let mut out = std::env::temp_dir();
968 out.push("stochastic_rs_test_plot_distribution.html");
969 plot_distribution(&samples, out.to_str().unwrap(), "test");
970 assert!(out.exists(), "plot_distribution did not write file");
971 let _ = std::fs::remove_file(out);
972 }
973
974 #[test]
975 fn plot_vol_surface_writes_html() {
976 use ndarray::Array2;
977 let strikes = vec![80.0, 90.0, 100.0, 110.0, 120.0];
978 let maturities = vec![0.25, 0.5, 1.0];
979 let ivs = Array2::<f64>::from_shape_fn((maturities.len(), strikes.len()), |(j, i)| {
980 0.2 + 0.01 * (j as f64) - 0.005 * ((i as f64) - 2.0).abs()
981 });
982 let mut out = std::env::temp_dir();
983 out.push("stochastic_rs_test_plot_vol_surface.html");
984 plot_vol_surface(&strikes, &maturities, &ivs, out.to_str().unwrap());
985 assert!(out.exists(), "plot_vol_surface did not write file");
986 let _ = std::fs::remove_file(out);
987 }
988
989 #[test]
990 #[should_panic(expected = "ivs shape must be")]
991 fn plot_vol_surface_rejects_bad_shape() {
992 use ndarray::Array2;
993 let strikes = vec![80.0, 90.0, 100.0];
994 let maturities = vec![0.25, 0.5];
995 let bad = Array2::<f64>::zeros((3, 5));
996 plot_vol_surface(&strikes, &maturities, &bad, "/tmp/should_not_exist.html");
997 }
998
999 #[test]
1000 fn grid_plotter_rescale_threshold_disabled_writes_html() {
1001 let bm = Bm::new(64, Some(1.0));
1002 let grid = GridPlotter::new()
1003 .title("rescale-threshold=None smoke")
1004 .cols(1)
1005 .rescale_threshold(None)
1006 .register(&bm, "Bm", 1);
1007 let plot = grid.plot();
1008 let mut out = std::env::temp_dir();
1009 out.push("stochastic_rs_test_rescale_disabled.html");
1010 plot.write_html(&out);
1011 assert!(out.exists(), "plot did not write file");
1012 let _ = std::fs::remove_file(out);
1013 }
1014}