market_model/process/
jump_diffusion.rs1use crate::process::StochasticProcess;
2use crate::simulatable::Simulatable;
3use rand::Rng;
4use rand_distr::StandardNormal;
5
6#[derive(Clone)]
11pub struct JumpDiffusion {
12 pub mu: f64,
13 pub sigma: f64,
14 pub lambda: f64,
15 pub mu_jump: f64,
16 pub sigma_jump: f64,
17 state: f64,
18}
19
20impl JumpDiffusion {
21 pub fn new(
22 mu: f64,
23 sigma: f64,
24 lambda: f64,
25 mu_jump: f64,
26 sigma_jump: f64,
27 initial_price: f64,
28 ) -> Self {
29 Self {
30 mu,
31 sigma,
32 lambda,
33 mu_jump,
34 sigma_jump,
35 state: initial_price,
36 }
37 }
38
39 pub fn value(&self) -> f64 {
40 self.state
41 }
42}
43
44impl Simulatable for JumpDiffusion {
45 type Output = f64;
46
47 fn dim(&self) -> usize {
48 1
49 }
50
51 fn step(&mut self, dt: f64, dw: &[f64], rng: &mut impl Rng) -> f64 {
52 let drift_log = (self.mu - 0.5 * self.sigma.powi(2)) * dt;
54 let diff_log = self.sigma * dw[0];
55 self.state *= (drift_log + diff_log).exp();
56
57 if self.lambda > 0.0 {
59 let p_jump = self.lambda * dt;
60 if rng.random::<f64>() < p_jump {
61 let z: f64 = rng.sample(StandardNormal);
62 let log_jump = self.mu_jump + self.sigma_jump * z;
63 self.state *= log_jump.exp();
64 }
65 }
66 self.state
67 }
68
69 fn current(&self) -> f64 {
70 self.state
71 }
72}
73
74impl StochasticProcess for JumpDiffusion {
75 type State = f64;
76 type Diffusion = f64;
77 type BrownianIncrement = f64;
78
79 fn drift(&self, x: &f64, _t: f64) -> f64 {
80 self.mu * x
81 }
82
83 fn diffusion(&self, x: &f64, _t: f64) -> f64 {
84 self.sigma * x
85 }
86
87 fn jump_intensity(&self, _x: &f64, _t: f64) -> f64 {
88 self.lambda
89 }
90
91 fn jump_size<R: Rng + ?Sized>(&self, _x: &f64, _t: f64, rng: &mut R) -> f64 {
92 let z: f64 = rng.sample(StandardNormal);
93 self.mu_jump + self.sigma_jump * z
94 }
95
96 fn expected_value(&self, x0: &f64, t: f64) -> f64 {
97 let drift_gbm = x0 * (self.mu * t).exp();
98 let jump_drift = (self.mu_jump.exp() - 1.0) * self.lambda * t;
99 drift_gbm * (1.0 + jump_drift)
100 }
101
102 fn variance(&self, x0: &f64, t: f64) -> f64 {
103 let sigma_sq_t = self.sigma.powi(2) * t;
104 let jump_var = self.lambda * t * (self.sigma_jump.powi(2) + self.mu_jump.powi(2));
105 x0.powi(2) * (sigma_sq_t + jump_var)
106 }
107
108 fn step<R: Rng + ?Sized>(&self, x: &f64, t: f64, dt: f64, dw: &f64, rng: &mut R) -> f64 {
109 let drift_log = (self.mu - 0.5 * self.sigma.powi(2)) * dt;
110 let diff_log = self.sigma * dw;
111 let mut next_x = x * (drift_log + diff_log).exp();
112
113 if self.lambda > 0.0 {
114 let p = self.lambda * dt;
115 if rng.random::<f64>() < p {
116 let jump = self.jump_size(x, t, rng);
117 next_x *= jump.exp();
118 }
119 }
120 next_x
121 }
122
123 fn generate_increment<R: Rng + ?Sized>(&self, rng: &mut R, dt: f64) -> f64 {
124 let dw: f64 = rng.sample(StandardNormal);
125 dw * dt.sqrt()
126 }
127
128 fn simulate<R: Rng + ?Sized>(
129 &self,
130 x0: f64,
131 t0: f64,
132 t_end: f64,
133 dt: f64,
134 rng: &mut R,
135 ) -> Vec<(f64, f64)> {
136 let n_steps = ((t_end - t0) / dt).ceil() as usize;
137 let mut path = Vec::with_capacity(n_steps + 1);
138 let mut t = t0;
139 let mut x = x0;
140 path.push((t, x));
141
142 for _ in 0..n_steps {
143 let dw = self.generate_increment(rng, dt);
144 x = self.step(&x, t, dt, &dw, rng);
145 t += dt;
146 path.push((t, x));
147 }
148 path
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_jump_diffusion_no_jumps() {
158 let jd = JumpDiffusion::new(0.0, 0.2, 0.0, 0.0, 0.0, 100.0);
159 assert_eq!(jd.dim(), 1);
160 assert_eq!(jd.current(), 100.0);
161 }
162
163 #[test]
164 fn test_jump_diffusion_negative_jumps_reduce_price() {
165 let mut jd = JumpDiffusion::new(
166 0.0, 0.0, 10.0, -0.5, 0.1, 100.0, );
173 let initial = jd.current();
174 for _ in 0..200 {
175 Simulatable::step(&mut jd, 0.01, &[0.0], &mut rand::rng());
176 }
177 assert!(jd.current() < initial);
178 }
179}