1use crate::process::StochasticProcess;
2use crate::simulatable::Simulatable;
3use ndarray::{Array1, Array2, array};
4use rand::Rng;
5use rand_distr::StandardNormal;
6
7#[derive(Clone)]
14pub struct HestonProcess {
15 pub mu: f64,
16 pub kappa: f64,
17 pub theta: f64,
18 pub sigma: f64,
19 pub rho: f64,
20 pub price: f64,
21 pub variance: f64,
22 sqrt_one_minus_rho2: f64,
24}
25
26impl HestonProcess {
27 #[allow(clippy::too_many_arguments)]
28 pub fn new(
29 mu: f64,
30 kappa: f64,
31 theta: f64,
32 sigma: f64,
33 rho: f64,
34 initial_price: f64,
35 initial_variance: f64,
36 ) -> Self {
37 let sqrt_one_minus_rho2 = if rho.abs() < 1.0 {
38 (1.0 - rho * rho).sqrt()
39 } else {
40 0.0
41 };
42 Self {
43 mu,
44 kappa,
45 theta,
46 sigma,
47 rho,
48 price: initial_price,
49 variance: initial_variance,
50 sqrt_one_minus_rho2,
51 }
52 }
53
54 pub fn price(&self) -> f64 {
55 self.price
56 }
57
58 pub fn volatility(&self) -> f64 {
59 self.variance.max(0.0).sqrt()
60 }
61
62 pub fn expected_variance(&self, t: f64) -> f64 {
64 let exp = (-self.kappa * t).exp();
65 self.variance * exp + self.theta * (1.0 - exp)
66 }
67
68 pub fn variance_of_variance(&self, t: f64) -> f64 {
70 let e1 = (-self.kappa * t).exp();
71 let e2 = (-2.0 * self.kappa * t).exp();
72 self.variance * (self.sigma.powi(2) / self.kappa) * (e1 - e2)
73 + (self.theta * self.sigma.powi(2) / (2.0 * self.kappa)) * (1.0 - e1).powi(2)
74 }
75}
76
77impl Simulatable for HestonProcess {
78 type Output = (f64, f64); fn dim(&self) -> usize {
81 2
82 }
83
84 fn step(&mut self, dt: f64, dw: &[f64], _rng: &mut impl Rng) -> (f64, f64) {
85 let z1 = dw[0];
89 let z2 = if self.rho.abs() < 1.0 {
90 self.rho * dw[0] + self.sqrt_one_minus_rho2 * dw[1]
91 } else {
92 self.rho.signum() * dw[0]
93 };
94
95 let v = self.variance.max(0.0);
96 let sqrt_v = v.sqrt();
97
98 self.price *= (self.mu * dt + sqrt_v * z1).exp();
100
101 self.variance += self.kappa * (self.theta - v) * dt + self.sigma * sqrt_v * z2;
103 if self.variance < 0.0 {
104 self.variance = 0.0;
105 }
106
107 (self.price, self.variance)
108 }
109
110 fn current(&self) -> (f64, f64) {
111 (self.price, self.variance)
112 }
113}
114
115impl StochasticProcess for HestonProcess {
116 type State = Array1<f64>;
117 type Diffusion = Array2<f64>;
118 type BrownianIncrement = Array1<f64>;
119
120 fn drift(&self, x: &Array1<f64>, _t: f64) -> Array1<f64> {
121 let s = x[0];
122 let v = x[1];
123 array![self.mu * s, self.kappa * (self.theta - v)]
124 }
125
126 fn diffusion(&self, x: &Array1<f64>, _t: f64) -> Array2<f64> {
127 let s = x[0];
128 let v = if x[1] < 0.0 { 0.0 } else { x[1] };
129 let sqrt_v = v.sqrt();
130 let rho = self.rho;
131 let sqrt_one_minus_rho2 = (1.0 - rho * rho).sqrt();
132
133 array![
134 [sqrt_v * s * sqrt_one_minus_rho2, sqrt_v * s * rho],
135 [0.0, self.sigma * sqrt_v]
136 ]
137 }
138
139 fn jump_size<R: Rng + ?Sized>(&self, x: &Array1<f64>, _t: f64, _rng: &mut R) -> Array1<f64> {
140 Array1::zeros(x.len())
141 }
142
143 fn expected_value(&self, x0: &Array1<f64>, t: f64) -> Array1<f64> {
144 let s0 = x0[0];
145 let v0 = x0[1];
146 let expected_s = s0 * (self.mu * t).exp();
147 let exp_minus_kappa_t = (-self.kappa * t).exp();
148 let expected_v = v0 * exp_minus_kappa_t + self.theta * (1.0 - exp_minus_kappa_t);
149 array![expected_s, expected_v]
150 }
151
152 fn variance(&self, x0: &Array1<f64>, t: f64) -> Array1<f64> {
153 let v0 = x0[1];
154 let var_s = 0.0;
155 let exp_minus_kappa_t = (-self.kappa * t).exp();
156 let exp_minus_2_kappa_t = (-2.0 * self.kappa * t).exp();
157 let var_v =
158 v0 * (self.sigma.powi(2) / self.kappa) * (exp_minus_kappa_t - exp_minus_2_kappa_t)
159 + (self.theta * self.sigma.powi(2) / (2.0 * self.kappa))
160 * (1.0 - exp_minus_kappa_t).powi(2);
161 array![var_s, var_v]
162 }
163
164 fn step<R: Rng + ?Sized>(
165 &self,
166 x: &Array1<f64>,
167 t: f64,
168 dt: f64,
169 dw: &Array1<f64>,
170 _rng: &mut R,
171 ) -> Array1<f64> {
172 let drift = self.drift(x, t);
173 let diffusion = self.diffusion(x, t);
174 let diffusion_term = diffusion.dot(dw);
175 let mut next_x = x + &(&drift * dt) + &diffusion_term;
176 if next_x[1] < 0.0 {
177 next_x[1] = 0.0;
178 }
179 next_x
180 }
181
182 fn generate_increment<R: Rng + ?Sized>(&self, rng: &mut R, dt: f64) -> Array1<f64> {
183 let dw1: f64 = rng.sample(StandardNormal);
184 let dw2: f64 = rng.sample(StandardNormal);
185 let dw = array![dw1, dw2];
186 dw * dt.sqrt()
187 }
188
189 fn simulate<R: Rng + ?Sized>(
190 &self,
191 x0: Array1<f64>,
192 t0: f64,
193 t_end: f64,
194 dt: f64,
195 rng: &mut R,
196 ) -> Vec<(f64, Array1<f64>)> {
197 let n_steps = ((t_end - t0) / dt).ceil() as usize;
198 let mut path = Vec::with_capacity(n_steps + 1);
199 let mut t = t0;
200 let mut x = x0;
201 path.push((t, x.clone()));
202 let sqrt_dt = dt.sqrt();
203 for _ in 0..n_steps {
204 let dw1: f64 = rng.sample(StandardNormal);
205 let dw2: f64 = rng.sample(StandardNormal);
206 let dw = array![dw1, dw2] * sqrt_dt;
207 x = self.step(&x, t, dt, &dw, rng);
208 t += dt;
209 path.push((t, x.clone()));
210 }
211 path
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_heston_dim() {
221 let h = HestonProcess::new(0.05, 2.0, 0.04, 0.3, -0.7, 100.0, 0.04);
222 assert_eq!(h.dim(), 2);
223 }
224
225 #[test]
226 fn test_heston_initial_state() {
227 let h = HestonProcess::new(0.05, 2.0, 0.04, 0.3, -0.7, 100.0, 0.04);
228 let (p, v) = h.current();
229 assert_eq!(p, 100.0);
230 assert_eq!(v, 0.04);
231 }
232
233 #[test]
234 fn test_heston_variance_stays_nonnegative() {
235 let mut h = HestonProcess::new(0.05, 1.0, 0.01, 0.5, -0.7, 100.0, 0.01);
236 for _ in 0..1000 {
237 Simulatable::step(&mut h, 0.001, &[0.0, 0.0], &mut rand::rng());
238 let (_, v) = h.current();
239 assert!(v >= 0.0);
240 }
241 }
242}