1use super::functions::*;
6pub struct EulerMaruyama {
11 pub drift: fn(f64) -> f64,
13 pub diffusion: fn(f64) -> f64,
15}
16impl EulerMaruyama {
17 pub fn new(drift: fn(f64) -> f64, diffusion: fn(f64) -> f64) -> Self {
19 EulerMaruyama { drift, diffusion }
20 }
21 pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
25 let mut lcg = Lcg::new(seed);
26 let dt = t_end / n_steps as f64;
27 let sqrt_dt = dt.sqrt();
28 let mut path = Vec::with_capacity(n_steps as usize + 1);
29 let mut t = 0.0f64;
30 let mut x = x0;
31 path.push((t, x));
32 for _ in 0..n_steps {
33 let dw = lcg.next_normal() * sqrt_dt;
34 x += (self.drift)(x) * dt + (self.diffusion)(x) * dw;
35 t += dt;
36 path.push((t, x));
37 }
38 path
39 }
40}
41#[allow(dead_code)]
45pub struct LocalTimeEstimator {
46 pub path: Vec<(f64, f64)>,
48}
49impl LocalTimeEstimator {
50 pub fn new(path: Vec<(f64, f64)>) -> Self {
52 Self { path }
53 }
54 pub fn estimate_local_time(&self, a: f64, epsilon: f64) -> f64 {
58 if self.path.len() < 2 || epsilon <= 0.0 {
59 return 0.0;
60 }
61 let mut total = 0.0f64;
62 for k in 1..self.path.len() {
63 let (t_prev, _) = self.path[k - 1];
64 let (t_curr, x_curr) = self.path[k];
65 let dt = t_curr - t_prev;
66 if (x_curr - a).abs() < epsilon {
67 total += dt;
68 }
69 }
70 total / epsilon
71 }
72 pub fn occupation_time(&self, lo: f64, hi: f64) -> f64 {
74 if self.path.len() < 2 {
75 return 0.0;
76 }
77 let mut total = 0.0f64;
78 for k in 1..self.path.len() {
79 let (t_prev, _) = self.path[k - 1];
80 let (t_curr, x_curr) = self.path[k];
81 let dt = t_curr - t_prev;
82 if x_curr >= lo && x_curr < hi {
83 total += dt;
84 }
85 }
86 total
87 }
88 pub fn quadratic_variation(&self) -> f64 {
90 self.path
91 .windows(2)
92 .map(|w| {
93 let diff = w[1].1 - w[0].1;
94 diff * diff
95 })
96 .sum()
97 }
98}
99#[derive(Debug, Clone)]
104pub struct CtMarkovChain {
105 pub states: Vec<String>,
106 pub rate_matrix: Vec<Vec<f64>>,
108}
109impl CtMarkovChain {
110 pub fn new(states: Vec<String>, rate_matrix: Vec<Vec<f64>>) -> Self {
112 CtMarkovChain {
113 states,
114 rate_matrix,
115 }
116 }
117 pub fn stationary_distribution(&self) -> Option<Vec<f64>> {
122 let n = self.states.len();
123 if n == 0 {
124 return None;
125 }
126 let q_max = self
127 .rate_matrix
128 .iter()
129 .enumerate()
130 .map(|(i, row)| row.get(i).map(|v| v.abs()).unwrap_or(0.0))
131 .fold(0.0f64, f64::max);
132 if q_max < 1e-15 {
133 return Some(vec![1.0 / n as f64; n]);
134 }
135 let mut p = vec![vec![0.0f64; n]; n];
136 for i in 0..n {
137 for j in 0..n {
138 let q_ij = self.rate_matrix[i].get(j).copied().unwrap_or(0.0);
139 p[i][j] = if i == j {
140 1.0 + q_ij / q_max
141 } else {
142 q_ij / q_max
143 };
144 }
145 }
146 let mut pi = vec![1.0 / n as f64; n];
147 for _ in 0..10_000 {
148 let mut pi_new = vec![0.0f64; n];
149 for j in 0..n {
150 for i in 0..n {
151 pi_new[j] += pi[i] * p[i][j];
152 }
153 }
154 let s: f64 = pi_new.iter().sum();
155 if s < 1e-15 {
156 return None;
157 }
158 for v in &mut pi_new {
159 *v /= s;
160 }
161 pi = pi_new;
162 }
163 Some(pi)
164 }
165 pub fn expected_hitting_time(&self, from: usize, to: usize) -> f64 {
169 if from == to {
170 return 0.0;
171 }
172 let rate = self
173 .rate_matrix
174 .get(from)
175 .and_then(|row| row.get(to))
176 .copied()
177 .unwrap_or(0.0);
178 if rate > 1e-15 {
179 1.0 / rate
180 } else {
181 f64::INFINITY
182 }
183 }
184 pub fn is_irreducible(&self) -> bool {
187 let n = self.states.len();
188 for i in 0..n {
189 for j in 0..n {
190 if i == j {
191 continue;
192 }
193 let q = self
194 .rate_matrix
195 .get(i)
196 .and_then(|r| r.get(j))
197 .copied()
198 .unwrap_or(0.0);
199 if q <= 0.0 {
200 return false;
201 }
202 }
203 }
204 true
205 }
206}
207#[derive(Debug, Clone)]
212pub struct PoissonProcessSimulator {
213 pub rate: f64,
215}
216impl PoissonProcessSimulator {
217 pub fn new(rate: f64) -> Self {
219 PoissonProcessSimulator { rate }
220 }
221 pub fn arrival_times(&self, t_end: f64, seed: u64) -> Vec<f64> {
226 let mut lcg = Lcg::new(seed);
227 let mut times = Vec::new();
228 let mut t = 0.0f64;
229 loop {
230 let u = lcg.next_f64().max(1e-15);
231 t += (-u.ln()) / self.rate;
232 if t > t_end {
233 break;
234 }
235 times.push(t);
236 }
237 times
238 }
239 pub fn counting_process(&self, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, u64)> {
244 let arrivals = self.arrival_times(t_end, seed);
245 let dt = t_end / n_steps as f64;
246 let mut path = Vec::with_capacity(n_steps as usize + 1);
247 let mut count = 0u64;
248 let mut arrival_idx = 0usize;
249 for k in 0..=n_steps {
250 let t = k as f64 * dt;
251 while arrival_idx < arrivals.len() && arrivals[arrival_idx] <= t {
252 count += 1;
253 arrival_idx += 1;
254 }
255 path.push((t, count));
256 }
257 path
258 }
259 pub fn expected_count(&self, t: f64) -> f64 {
261 self.rate * t
262 }
263}
264#[derive(Debug, Clone)]
269pub struct CompoundPoissonProcess {
270 pub rate: f64,
272 pub jump_mean: f64,
274 pub jump_std: f64,
276}
277impl CompoundPoissonProcess {
278 pub fn new(rate: f64, jump_mean: f64, jump_std: f64) -> Self {
280 CompoundPoissonProcess {
281 rate,
282 jump_mean,
283 jump_std,
284 }
285 }
286 pub fn simulate(&self, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
290 let mut lcg = Lcg::new(seed);
291 let poisson = PoissonProcessSimulator::new(self.rate);
292 let arrivals = poisson.arrival_times(t_end, seed.wrapping_add(99_999));
293 let jumps: Vec<f64> = arrivals
294 .iter()
295 .map(|_| self.jump_mean + self.jump_std * lcg.next_normal())
296 .collect();
297 let dt = t_end / n_steps as f64;
298 let mut path = Vec::with_capacity(n_steps as usize + 1);
299 let mut cumulative = 0.0f64;
300 let mut arrival_idx = 0usize;
301 for k in 0..=n_steps {
302 let t = k as f64 * dt;
303 while arrival_idx < arrivals.len() && arrivals[arrival_idx] <= t {
304 cumulative += jumps[arrival_idx];
305 arrival_idx += 1;
306 }
307 path.push((t, cumulative));
308 }
309 path
310 }
311 pub fn expected_value(&self, t: f64) -> f64 {
313 self.rate * t * self.jump_mean
314 }
315 pub fn variance(&self, t: f64) -> f64 {
317 self.rate * t * (self.jump_std * self.jump_std + self.jump_mean * self.jump_mean)
318 }
319}
320#[allow(dead_code)]
325pub struct MilsteinScheme {
326 pub drift: fn(f64) -> f64,
328 pub diffusion: fn(f64) -> f64,
330 pub diffusion_deriv: fn(f64) -> f64,
332}
333impl MilsteinScheme {
334 pub fn new(
336 drift: fn(f64) -> f64,
337 diffusion: fn(f64) -> f64,
338 diffusion_deriv: fn(f64) -> f64,
339 ) -> Self {
340 MilsteinScheme {
341 drift,
342 diffusion,
343 diffusion_deriv,
344 }
345 }
346 pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
350 let mut lcg = Lcg::new(seed);
351 let dt = t_end / n_steps as f64;
352 let sqrt_dt = dt.sqrt();
353 let mut path = Vec::with_capacity(n_steps as usize + 1);
354 let mut t = 0.0f64;
355 let mut x = x0;
356 path.push((t, x));
357 for _ in 0..n_steps {
358 let dw = lcg.next_normal() * sqrt_dt;
359 let sig = (self.diffusion)(x);
360 let sig_p = (self.diffusion_deriv)(x);
361 x += (self.drift)(x) * dt + sig * dw + 0.5 * sig * sig_p * (dw * dw - dt);
362 t += dt;
363 path.push((t, x));
364 }
365 path
366 }
367 pub fn monte_carlo_mean(
369 &self,
370 x0: f64,
371 t_end: f64,
372 n_steps: u32,
373 n_paths: u32,
374 seed: u64,
375 ) -> f64 {
376 if n_paths == 0 {
377 return 0.0;
378 }
379 let total: f64 = (0..n_paths)
380 .map(|i| {
381 let path = self.simulate(x0, t_end, n_steps, seed.wrapping_add(i as u64));
382 path.last().map(|&(_, x)| x).unwrap_or(x0)
383 })
384 .sum();
385 total / n_paths as f64
386 }
387}
388#[allow(dead_code)]
395pub struct HestonModel {
396 pub mu: f64,
398 pub kappa: f64,
400 pub theta: f64,
402 pub xi: f64,
404 pub rho: f64,
406}
407impl HestonModel {
408 pub fn new(mu: f64, kappa: f64, theta: f64, xi: f64, rho: f64) -> Self {
410 HestonModel {
411 mu,
412 kappa,
413 theta,
414 xi,
415 rho,
416 }
417 }
418 pub fn simulate(
422 &self,
423 s0: f64,
424 v0: f64,
425 t_end: f64,
426 n_steps: u32,
427 seed: u64,
428 ) -> Vec<(f64, f64, f64)> {
429 let mut lcg = Lcg::new(seed);
430 let dt = t_end / n_steps as f64;
431 let sqrt_dt = dt.sqrt();
432 let mut path = Vec::with_capacity(n_steps as usize + 1);
433 let mut t = 0.0f64;
434 let mut s = s0;
435 let mut v = v0.max(0.0);
436 path.push((t, s, v));
437 for _ in 0..n_steps {
438 let z1 = lcg.next_normal();
439 let z2 = lcg.next_normal();
440 let w1 = z1;
441 let w2 = self.rho * z1 + (1.0 - self.rho * self.rho).max(0.0).sqrt() * z2;
442 let sqrt_v = v.max(0.0).sqrt();
443 s *= 1.0 + self.mu * dt + sqrt_v * w1 * sqrt_dt;
444 v = (v + self.kappa * (self.theta - v) * dt + self.xi * sqrt_v * w2 * sqrt_dt).max(0.0);
445 t += dt;
446 path.push((t, s, v));
447 }
448 path
449 }
450 pub fn feller_condition_satisfied(&self) -> bool {
452 2.0 * self.kappa * self.theta > self.xi * self.xi
453 }
454 pub fn variance_long_run_mean(&self) -> f64 {
456 self.theta
457 }
458}
459#[derive(Debug, Clone)]
464pub struct GeometricBrownianMotionProcess {
465 pub mu: f64,
467 pub sigma: f64,
469}
470impl GeometricBrownianMotionProcess {
471 pub fn new(mu: f64, sigma: f64) -> Self {
473 GeometricBrownianMotionProcess { mu, sigma }
474 }
475 pub fn simulate(&self, s0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
479 geometric_brownian_motion(s0, self.mu, self.sigma, t_end, n_steps, seed)
480 }
481 pub fn expected_value(&self, s0: f64, t: f64) -> f64 {
483 s0 * (self.mu * t).exp()
484 }
485 pub fn variance(&self, s0: f64, t: f64) -> f64 {
487 let s0sq = s0 * s0;
488 s0sq * (2.0 * self.mu * t).exp() * ((self.sigma * self.sigma * t).exp() - 1.0)
489 }
490}
491#[derive(Debug, Clone)]
496pub struct BlackScholesPricer {
497 pub spot: f64,
499 pub strike: f64,
501 pub time_to_expiry: f64,
503 pub rate: f64,
505 pub volatility: f64,
507}
508impl BlackScholesPricer {
509 pub fn new(spot: f64, strike: f64, time_to_expiry: f64, rate: f64, volatility: f64) -> Self {
511 BlackScholesPricer {
512 spot,
513 strike,
514 time_to_expiry,
515 rate,
516 volatility,
517 }
518 }
519 fn d1(&self) -> f64 {
520 let sqrt_t = self.time_to_expiry.sqrt();
521 ((self.spot / self.strike).ln()
522 + (self.rate + 0.5 * self.volatility * self.volatility) * self.time_to_expiry)
523 / (self.volatility * sqrt_t)
524 }
525 fn d2(&self) -> f64 {
526 self.d1() - self.volatility * self.time_to_expiry.sqrt()
527 }
528 pub fn call_price(&self) -> f64 {
530 black_scholes_call(
531 self.spot,
532 self.strike,
533 self.time_to_expiry,
534 self.rate,
535 self.volatility,
536 )
537 }
538 pub fn put_price(&self) -> f64 {
540 black_scholes_put(
541 self.spot,
542 self.strike,
543 self.time_to_expiry,
544 self.rate,
545 self.volatility,
546 )
547 }
548 pub fn call_delta(&self) -> f64 {
550 if self.time_to_expiry <= 0.0 {
551 return if self.spot > self.strike { 1.0 } else { 0.0 };
552 }
553 standard_normal_cdf(self.d1())
554 }
555 pub fn put_delta(&self) -> f64 {
557 self.call_delta() - 1.0
558 }
559 pub fn gamma(&self) -> f64 {
561 if self.time_to_expiry <= 0.0 {
562 return 0.0;
563 }
564 let d1 = self.d1();
565 let phi = (-0.5 * d1 * d1).exp() / (2.0 * std::f64::consts::PI).sqrt();
566 phi / (self.spot * self.volatility * self.time_to_expiry.sqrt())
567 }
568 pub fn vega(&self) -> f64 {
570 if self.time_to_expiry <= 0.0 {
571 return 0.0;
572 }
573 let d1 = self.d1();
574 let phi = (-0.5 * d1 * d1).exp() / (2.0 * std::f64::consts::PI).sqrt();
575 self.spot * phi * self.time_to_expiry.sqrt()
576 }
577 pub fn call_rho(&self) -> f64 {
579 if self.time_to_expiry <= 0.0 {
580 return 0.0;
581 }
582 self.strike
583 * self.time_to_expiry
584 * (-self.rate * self.time_to_expiry).exp()
585 * standard_normal_cdf(self.d2())
586 }
587 pub fn put_rho(&self) -> f64 {
589 if self.time_to_expiry <= 0.0 {
590 return 0.0;
591 }
592 -self.strike
593 * self.time_to_expiry
594 * (-self.rate * self.time_to_expiry).exp()
595 * standard_normal_cdf(-self.d2())
596 }
597 pub fn implied_volatility_call(&self, market_price: f64) -> Option<f64> {
602 let intrinsic =
603 (self.spot - self.strike * (-self.rate * self.time_to_expiry).exp()).max(0.0);
604 if market_price < intrinsic {
605 return None;
606 }
607 let mut lo = 1e-6f64;
608 let mut hi = 10.0f64;
609 for _ in 0..200 {
610 let mid = 0.5 * (lo + hi);
611 let pricer = BlackScholesPricer {
612 volatility: mid,
613 ..*self
614 };
615 let price = pricer.call_price();
616 if (price - market_price).abs() < 1e-8 {
617 return Some(mid);
618 }
619 if price < market_price {
620 lo = mid;
621 } else {
622 hi = mid;
623 }
624 }
625 Some(0.5 * (lo + hi))
626 }
627}
628#[allow(dead_code)]
633pub struct VarianceGammaProcess {
634 pub mu: f64,
636 pub sigma: f64,
638 pub nu: f64,
640}
641impl VarianceGammaProcess {
642 pub fn new(mu: f64, sigma: f64, nu: f64) -> Self {
644 VarianceGammaProcess { mu, sigma, nu }
645 }
646 pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
650 let mut lcg = Lcg::new(seed);
651 let dt = t_end / n_steps as f64;
652 let mut path = Vec::with_capacity(n_steps as usize + 1);
653 let mut t = 0.0f64;
654 let mut x = x0;
655 path.push((t, x));
656 for _ in 0..n_steps {
657 let dg = sp_ext_gamma_sample(dt / self.nu, 1.0 / self.nu, &mut lcg);
658 let z = lcg.next_normal();
659 x += self.mu * dg + self.sigma * dg.sqrt() * z;
660 t += dt;
661 path.push((t, x));
662 }
663 path
664 }
665 pub fn theoretical_mean(&self, x0: f64, t: f64) -> f64 {
667 x0 + self.mu * t
668 }
669 pub fn theoretical_variance(&self, t: f64) -> f64 {
671 self.sigma * self.sigma * t + self.mu * self.mu * self.nu * t
672 }
673 pub fn characteristic_exponent(&self, u: f64) -> f64 {
675 let a = 1.0 - self.mu * self.nu * u * u + 0.5 * self.sigma * self.sigma * self.nu * u * u;
676 let b = self.mu * self.nu * u;
677 let modulus_sq = a * a + b * b;
678 if modulus_sq <= 0.0 {
679 return 0.0;
680 }
681 -0.5 * modulus_sq.ln() / self.nu
682 }
683}
684#[derive(Debug, Clone)]
689pub struct OrnsteinUhlenbeckProcess {
690 pub theta: f64,
692 pub mean: f64,
694 pub sigma: f64,
696}
697impl OrnsteinUhlenbeckProcess {
698 pub fn new(theta: f64, mean: f64, sigma: f64) -> Self {
700 OrnsteinUhlenbeckProcess { theta, mean, sigma }
701 }
702 pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
706 ornstein_uhlenbeck(x0, self.theta, self.mean, self.sigma, t_end, n_steps, seed)
707 }
708 pub fn stationary_variance(&self) -> f64 {
710 self.sigma * self.sigma / (2.0 * self.theta)
711 }
712 pub fn stationary_std(&self) -> f64 {
714 self.stationary_variance().sqrt()
715 }
716}
717#[allow(dead_code)]
722pub struct SubordinatedProcess {
723 pub base_drift: f64,
725 pub base_sigma: f64,
727 pub gamma_a: f64,
729 pub gamma_b: f64,
731}
732impl SubordinatedProcess {
733 pub fn new(base_drift: f64, base_sigma: f64, gamma_a: f64, gamma_b: f64) -> Self {
735 SubordinatedProcess {
736 base_drift,
737 base_sigma,
738 gamma_a,
739 gamma_b,
740 }
741 }
742 pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
746 let mut lcg = Lcg::new(seed);
747 let dt = t_end / n_steps as f64;
748 let mut path = Vec::with_capacity(n_steps as usize + 1);
749 let mut t = 0.0f64;
750 let mut x = x0;
751 path.push((t, x));
752 for _ in 0..n_steps {
753 let d_sub = sp_ext_gamma_sample(self.gamma_a * dt, self.gamma_b, &mut lcg);
754 let dw = lcg.next_normal() * d_sub.sqrt();
755 x += self.base_drift * d_sub + self.base_sigma * dw;
756 t += dt;
757 path.push((t, x));
758 }
759 path
760 }
761 pub fn theoretical_mean(&self, x0: f64, t: f64) -> f64 {
763 x0 + self.base_drift * (self.gamma_a / self.gamma_b) * t
764 }
765 pub fn theoretical_variance(&self, t: f64) -> f64 {
767 let e_sub = self.gamma_a / self.gamma_b * t;
768 let var_sub = self.gamma_a / (self.gamma_b * self.gamma_b) * t;
769 self.base_sigma * self.base_sigma * e_sub + self.base_drift * self.base_drift * var_sub
770 }
771}
772pub(super) struct Lcg {
775 state: u64,
776}
777impl Lcg {
778 pub(super) fn new(seed: u64) -> Self {
779 Lcg {
780 state: seed.wrapping_add(1),
781 }
782 }
783 pub(super) fn next_u32(&mut self) -> u32 {
785 self.state = self
786 .state
787 .wrapping_mul(6364136223846793005)
788 .wrapping_add(1442695040888963407);
789 (self.state >> 33) as u32
790 }
791 pub(super) fn next_f64(&mut self) -> f64 {
793 self.next_u32() as f64 / (u32::MAX as f64 + 1.0)
794 }
795 pub(super) fn next_step(&mut self) -> f64 {
797 if self.next_u32() & 1 == 0 {
798 1.0
799 } else {
800 -1.0
801 }
802 }
803 pub(super) fn next_normal(&mut self) -> f64 {
805 let u1 = self.next_f64().max(1e-15);
806 let u2 = self.next_f64();
807 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
808 }
809}