market_model/process/
ou.rs1use crate::process::StochasticProcess;
2use crate::simulatable::Simulatable;
3use rand::Rng;
4use rand_distr::StandardNormal;
5
6#[derive(Clone)]
10pub struct OrnsteinUhlenbeck {
11 pub theta: f64,
12 pub mu: f64,
13 pub sigma: f64,
14 state: f64,
15}
16
17impl OrnsteinUhlenbeck {
18 pub fn new(theta: f64, mu: f64, sigma: f64, initial_value: f64) -> Self {
19 Self {
20 theta,
21 mu,
22 sigma,
23 state: initial_value,
24 }
25 }
26
27 pub fn value(&self) -> f64 {
28 self.state
29 }
30
31 pub fn expected_value(&self, t: f64) -> f64 {
33 let exp = (-self.theta * t).exp();
34 self.state * exp + self.mu * (1.0 - exp)
35 }
36
37 pub fn variance(&self, t: f64) -> f64 {
39 let exp = (-2.0 * self.theta * t).exp();
40 (self.sigma.powi(2) / (2.0 * self.theta)) * (1.0 - exp)
41 }
42}
43
44impl Simulatable for OrnsteinUhlenbeck {
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 = self.theta * (self.mu - self.state);
53 self.state += drift * dt + self.sigma * dw[0];
54 self.state
55 }
56
57 fn current(&self) -> f64 {
58 self.state
59 }
60}
61
62impl StochasticProcess for OrnsteinUhlenbeck {
63 type State = f64;
64 type Diffusion = f64;
65 type BrownianIncrement = f64;
66
67 fn drift(&self, x: &f64, _t: f64) -> f64 {
68 self.theta * (self.mu - x)
69 }
70
71 fn diffusion(&self, _x: &f64, _t: f64) -> f64 {
72 self.sigma
73 }
74
75 fn jump_size<R: Rng + ?Sized>(&self, _x: &f64, _t: f64, _rng: &mut R) -> f64 {
76 0.0
77 }
78
79 fn expected_value(&self, x0: &f64, t: f64) -> f64 {
80 let exp_minus_theta_t = (-self.theta * t).exp();
81 x0 * exp_minus_theta_t + self.mu * (1.0 - exp_minus_theta_t)
82 }
83
84 fn variance(&self, _x0: &f64, t: f64) -> f64 {
85 let exp_minus_2_theta_t = (-2.0 * self.theta * t).exp();
86 (self.sigma.powi(2) / (2.0 * self.theta)) * (1.0 - exp_minus_2_theta_t)
87 }
88
89 fn step<R: Rng + ?Sized>(&self, x: &f64, t: f64, dt: f64, dw: &f64, _rng: &mut R) -> f64 {
90 let drift = self.drift(x, t);
91 let diffusion = self.diffusion(x, t);
92 x + drift * dt + diffusion * dw
93 }
94
95 fn generate_increment<R: Rng + ?Sized>(&self, rng: &mut R, dt: f64) -> f64 {
96 let dw: f64 = rng.sample(StandardNormal);
97 dw * dt.sqrt()
98 }
99
100 fn simulate<R: Rng + ?Sized>(
101 &self,
102 x0: f64,
103 t0: f64,
104 t_end: f64,
105 dt: f64,
106 rng: &mut R,
107 ) -> Vec<(f64, f64)> {
108 let n_steps = ((t_end - t0) / dt).ceil() as usize;
109 let mut path = Vec::with_capacity(n_steps + 1);
110 let mut t = t0;
111 let mut x = x0;
112 path.push((t, x));
113
114 let sqrt_dt = dt.sqrt();
115 for _ in 0..n_steps {
116 let z: f64 = rng.sample(StandardNormal);
117 let dw = z * sqrt_dt;
118 x = self.step(&x, t, dt, &dw, rng);
119 t += dt;
120 path.push((t, x));
121 }
122 path
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_ou_expected_value() {
132 let ou = OrnsteinUhlenbeck::new(2.0, 0.05, 0.1, 0.03);
133 let ev = ou.expected_value(1.0);
134 assert!(ev > 0.03);
135 assert!(ev < 0.05);
136 }
137
138 #[test]
139 fn test_ou_step_updates_state() {
140 let mut ou = OrnsteinUhlenbeck::new(2.0, 0.05, 0.1, 0.03);
141 let s = Simulatable::step(&mut ou, 0.01, &[0.0], &mut rand::rng());
142 assert!(s > 0.03);
143 assert_eq!(ou.current(), s);
144 }
145}