market_model/process/
cir.rs1use crate::process::StochasticProcess;
2use crate::simulatable::Simulatable;
3use rand::Rng;
4use rand_distr::StandardNormal;
5
6#[derive(Clone)]
10pub struct CoxIngersollRoss {
11 pub kappa: f64,
12 pub theta: f64,
13 pub sigma: f64,
14 state: f64,
15}
16
17impl CoxIngersollRoss {
18 pub fn new(kappa: f64, theta: f64, sigma: f64, initial_value: f64) -> Self {
19 Self {
20 kappa,
21 theta,
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.kappa * t).exp();
34 self.state * exp + self.theta * (1.0 - exp)
35 }
36
37 pub fn variance(&self, t: f64) -> f64 {
39 let e1 = (-self.kappa * t).exp();
40 let e2 = (-2.0 * self.kappa * t).exp();
41 self.state * (self.sigma.powi(2) / self.kappa) * (e1 - e2)
42 + (self.theta * self.sigma.powi(2) / (2.0 * self.kappa)) * (1.0 - e1).powi(2)
43 }
44}
45
46impl Simulatable for CoxIngersollRoss {
47 type Output = f64;
48
49 fn dim(&self) -> usize {
50 1
51 }
52
53 fn step(&mut self, dt: f64, dw: &[f64], _rng: &mut impl Rng) -> f64 {
54 let v = self.state.max(0.0);
55 let drift = self.kappa * (self.theta - v);
56 let diffusion = if v <= 0.0 { 0.0 } else { self.sigma * v.sqrt() };
57 self.state = v + drift * dt + diffusion * dw[0];
58 if self.state < 0.0 {
59 self.state = 0.0;
60 }
61 self.state
62 }
63
64 fn current(&self) -> f64 {
65 self.state
66 }
67}
68
69impl StochasticProcess for CoxIngersollRoss {
70 type State = f64;
71 type Diffusion = f64;
72 type BrownianIncrement = f64;
73
74 fn drift(&self, x: &f64, _t: f64) -> f64 {
75 self.kappa * (self.theta - x)
76 }
77
78 fn diffusion(&self, x: &f64, _t: f64) -> f64 {
79 if *x < 0.0 { 0.0 } else { self.sigma * x.sqrt() }
80 }
81
82 fn jump_size<R: Rng + ?Sized>(&self, _x: &f64, _t: f64, _rng: &mut R) -> f64 {
83 0.0
84 }
85
86 fn expected_value(&self, x0: &f64, t: f64) -> f64 {
87 let exp_minus_kappa_t = (-self.kappa * t).exp();
88 x0 * exp_minus_kappa_t + self.theta * (1.0 - exp_minus_kappa_t)
89 }
90
91 fn variance(&self, x0: &f64, t: f64) -> f64 {
92 let exp_minus_kappa_t = (-self.kappa * t).exp();
93 let exp_minus_2_kappa_t = (-2.0 * self.kappa * t).exp();
94 let term1 =
95 x0 * (self.sigma.powi(2) / self.kappa) * (exp_minus_kappa_t - exp_minus_2_kappa_t);
96 let term2 = (self.theta * self.sigma.powi(2) / (2.0 * self.kappa))
97 * (1.0 - exp_minus_kappa_t).powi(2);
98 term1 + term2
99 }
100
101 fn step<R: Rng + ?Sized>(&self, x: &f64, t: f64, dt: f64, dw: &f64, _rng: &mut R) -> f64 {
102 let drift = self.drift(x, t);
103 let diffusion = self.diffusion(x, t);
104 let mut next_x = x + drift * dt + diffusion * dw;
105 if next_x < 0.0 {
106 next_x = 0.0;
107 }
108 next_x
109 }
110
111 fn generate_increment<R: Rng + ?Sized>(&self, rng: &mut R, dt: f64) -> f64 {
112 let dw: f64 = rng.sample(StandardNormal);
113 dw * dt.sqrt()
114 }
115
116 fn simulate<R: Rng + ?Sized>(
117 &self,
118 x0: f64,
119 t0: f64,
120 t_end: f64,
121 dt: f64,
122 rng: &mut R,
123 ) -> Vec<(f64, f64)> {
124 let n_steps = ((t_end - t0) / dt).ceil() as usize;
125 let mut path = Vec::with_capacity(n_steps + 1);
126 let mut t = t0;
127 let mut x = x0;
128 path.push((t, x));
129
130 let sqrt_dt = dt.sqrt();
131 for _ in 0..n_steps {
132 let z: f64 = rng.sample(StandardNormal);
133 let dw = z * sqrt_dt;
134 x = self.step(&x, t, dt, &dw, rng);
135 t += dt;
136 path.push((t, x));
137 }
138 path
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_cir_expected_value() {
148 let cir = CoxIngersollRoss::new(3.0, 0.04, 0.3, 0.04);
149 let ev = cir.expected_value(1.0);
150 assert!((ev - 0.04).abs() < 0.01);
151 }
152
153 #[test]
154 fn test_cir_step_nonnegative() {
155 let mut cir = CoxIngersollRoss::new(3.0, 0.04, 0.3, 0.01);
156 for _ in 0..1000 {
157 Simulatable::step(&mut cir, 0.01, &[0.0], &mut rand::rng());
158 assert!(cir.current() >= 0.0);
159 }
160 }
161}