1use crate::process::StochasticProcess;
2use crate::process::heston::HestonProcess;
3use crate::simulatable::Simulatable;
4use ndarray::{Array1, Array2, array};
5use rand::Rng;
6use rand_distr::{Poisson, StandardNormal};
7
8#[derive(Clone)]
12pub struct BatesProcess {
13 heston: HestonProcess,
14 pub lambda: f64,
15 pub mu_jump: f64,
16 pub sigma_jump: f64,
17}
18
19impl BatesProcess {
20 #[allow(clippy::too_many_arguments)]
21 pub fn new(
22 mu: f64,
23 kappa: f64,
24 theta: f64,
25 sigma: f64,
26 rho: f64,
27 lambda: f64,
28 mu_jump: f64,
29 sigma_jump: f64,
30 initial_price: f64,
31 initial_variance: f64,
32 ) -> Self {
33 Self {
34 heston: HestonProcess::new(
35 mu,
36 kappa,
37 theta,
38 sigma,
39 rho,
40 initial_price,
41 initial_variance,
42 ),
43 lambda,
44 mu_jump,
45 sigma_jump,
46 }
47 }
48
49 pub fn price(&self) -> f64 {
50 self.heston.price()
51 }
52
53 pub fn volatility(&self) -> f64 {
54 self.heston.volatility()
55 }
56
57 pub fn mu(&self) -> f64 {
58 self.heston.mu
59 }
60
61 pub fn kappa(&self) -> f64 {
62 self.heston.kappa
63 }
64
65 pub fn theta(&self) -> f64 {
66 self.heston.theta
67 }
68
69 pub fn sigma(&self) -> f64 {
70 self.heston.sigma
71 }
72
73 pub fn rho(&self) -> f64 {
74 self.heston.rho
75 }
76}
77
78impl Simulatable for BatesProcess {
79 type Output = (f64, f64); fn dim(&self) -> usize {
82 2
83 }
84
85 fn step(&mut self, dt: f64, dw: &[f64], rng: &mut impl Rng) -> (f64, f64) {
86 let (_price, _variance) = Simulatable::step(&mut self.heston, dt, dw, rng);
88
89 if self.lambda > 0.0 {
91 let p_jump = self.lambda * dt;
92 if rng.random::<f64>() < p_jump {
93 let z: f64 = rng.sample(StandardNormal);
94 let log_jump = self.mu_jump + self.sigma_jump * z;
95 let multiplier = log_jump.exp();
96 self.heston.price *= multiplier;
97 }
98 }
99
100 self.heston.current()
101 }
102
103 fn current(&self) -> (f64, f64) {
104 self.heston.current()
105 }
106}
107
108impl StochasticProcess for BatesProcess {
109 type State = Array1<f64>;
110 type Diffusion = Array2<f64>;
111 type BrownianIncrement = Array1<f64>;
112
113 fn drift(&self, x: &Array1<f64>, t: f64) -> Array1<f64> {
114 self.heston.drift(x, t)
115 }
116
117 fn diffusion(&self, x: &Array1<f64>, t: f64) -> Array2<f64> {
118 self.heston.diffusion(x, t)
119 }
120
121 fn jump_intensity(&self, _x: &Array1<f64>, _t: f64) -> f64 {
122 self.lambda
123 }
124
125 fn jump_size<R: Rng + ?Sized>(&self, x: &Array1<f64>, _t: f64, rng: &mut R) -> Array1<f64> {
126 if self.lambda <= 0.0 {
127 return Array1::zeros(2);
128 }
129 let z: f64 = rng.sample(StandardNormal);
130 let log_jump = self.mu_jump + self.sigma_jump * z;
131 let jump_multiplier = log_jump.exp();
132 let s = x[0];
133 let jump_amount = s * (jump_multiplier - 1.0);
134 array![jump_amount, 0.0]
135 }
136
137 fn expected_value(&self, x0: &Array1<f64>, t: f64) -> Array1<f64> {
138 let s0 = x0[0];
139 let v0 = x0[1];
140 let k = (self.mu_jump + 0.5 * self.sigma_jump.powi(2)).exp() - 1.0;
141 let total_drift = self.heston.mu + self.lambda * k;
142 let expected_s = s0 * (total_drift * t).exp();
143 let exp_minus_kappa_t = (-self.heston.kappa * t).exp();
144 let expected_v = v0 * exp_minus_kappa_t + self.heston.theta * (1.0 - exp_minus_kappa_t);
145 array![expected_s, expected_v]
146 }
147
148 fn variance(&self, x0: &Array1<f64>, t: f64) -> Array1<f64> {
149 self.heston.variance(x0, t)
150 }
151
152 fn step<R: Rng + ?Sized>(
153 &self,
154 x: &Array1<f64>,
155 t: f64,
156 dt: f64,
157 dw: &Array1<f64>,
158 rng: &mut R,
159 ) -> Array1<f64> {
160 let drift = self.drift(x, t);
161 let diffusion = self.diffusion(x, t);
162 let diffusion_term = diffusion.dot(dw);
163 let mut next_x = x + &(&drift * dt) + &diffusion_term;
164 if next_x[1] < 0.0 {
165 next_x[1] = 0.0;
166 }
167 if self.lambda > 0.0 {
168 let poisson = Poisson::new(self.lambda * dt).unwrap();
169 let num_jumps = rng.sample(poisson) as u64;
170 for _ in 0..num_jumps {
171 let jump = self.jump_size(&next_x, t, rng);
172 next_x = next_x + jump;
173 }
174 }
175 next_x
176 }
177
178 fn generate_increment<R: Rng + ?Sized>(&self, rng: &mut R, dt: f64) -> Array1<f64> {
179 self.heston.generate_increment(rng, dt)
180 }
181
182 fn simulate<R: Rng + ?Sized>(
183 &self,
184 x0: Array1<f64>,
185 t0: f64,
186 t_end: f64,
187 dt: f64,
188 rng: &mut R,
189 ) -> Vec<(f64, Array1<f64>)> {
190 let n_steps = ((t_end - t0) / dt).ceil() as usize;
191 let mut path = Vec::with_capacity(n_steps + 1);
192 path.push((t0, x0.clone()));
193 let mut x = x0;
194 let mut t = t0;
195 for _ in 0..n_steps {
196 let dw = self.generate_increment(rng, dt);
197 x = self.step(&x, t, dt, &dw, rng);
198 t += dt;
199 path.push((t, x.clone()));
200 }
201 path
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_bates_no_jumps() {
211 let b = BatesProcess::new(0.05, 2.0, 0.04, 0.3, -0.7, 0.0, 0.0, 0.0, 100.0, 0.04);
212 assert_eq!(b.dim(), 2);
213 let (p, v) = b.current();
214 assert_eq!(p, 100.0);
215 assert_eq!(v, 0.04);
216 }
217}