Skip to main content

market_model/process/
ou.rs

1use crate::process::StochasticProcess;
2use crate::simulatable::Simulatable;
3use rand::Rng;
4use rand_distr::StandardNormal;
5
6/// Ornstein-Uhlenbeck process.
7///
8/// $$dX_t = theta * (mu - X_t) * dt + sigma * dW_t$$
9#[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    /// Closed-form expected value.
32    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    /// Closed-form variance.
38    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}