1use nalgebra::DVector;
4use rand::Rng;
5use serde::{Serialize, Deserialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct BrownianConfig {
9 pub dimension: usize,
10 pub dt: f64,
11 pub sigma: f64,
12 pub drift: f64,
13}
14
15impl Default for BrownianConfig {
16 fn default() -> Self {
17 Self { dimension: 1, dt: 0.01, sigma: 1.0, drift: 0.0 }
18 }
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct WienerStep {
23 pub time: f64,
24 pub position: DVector<f64>,
25 pub increment: DVector<f64>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct BrownianPath {
30 pub steps: Vec<WienerStep>,
31 pub config: BrownianConfig,
32}
33
34impl BrownianPath {
35 pub fn simulate(config: &BrownianConfig, n_steps: usize) -> Self {
36 let mut rng = rand::thread_rng();
37 let mut steps = Vec::with_capacity(n_steps);
38 let mut position = DVector::zeros(config.dimension);
39 let sqrt_dt = config.dt.sqrt();
40
41 for i in 0..n_steps {
42 let t = i as f64 * config.dt;
43 let mut inc = DVector::zeros(config.dimension);
44 for j in 0..config.dimension {
45 let z: f64 = rng.gen_range(-1.0..1.0);
46 inc[j] = config.sigma * sqrt_dt * z * std::f64::consts::SQRT_2
47 + config.drift * config.dt;
48 }
49 position += &inc;
50 steps.push(WienerStep { time: t, position: position.clone(), increment: inc });
51 }
52 BrownianPath { steps, config: config.clone() }
53 }
54
55 pub fn final_position(&self) -> Option<&DVector<f64>> {
56 self.steps.last().map(|s| &s.position)
57 }
58
59 pub fn quadratic_variation(&self) -> f64 {
60 self.steps.iter().map(|s| s.increment.iter().map(|x| x * x).sum::<f64>()).sum()
61 }
62}
63
64pub fn ito_lemma(f_value: f64, f_t: f64, f_w: f64, f_ww: f64, dt: f64, dw: f64) -> f64 {
65 f_value + f_t * dt + f_w * dw + 0.5 * f_ww * dw * dw
66}
67
68pub fn ito_integral(f_values: &[f64], increments: &[f64]) -> f64 {
69 f_values.iter().zip(increments.iter()).map(|(f, dw)| f * dw).sum()
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct GeometricBrownian {
74 pub initial_value: f64,
75 pub mu: f64,
76 pub sigma: f64,
77 pub path: Vec<(f64, f64)>,
78}
79
80impl GeometricBrownian {
81 pub fn simulate(s0: f64, mu: f64, sigma: f64, dt: f64, n_steps: usize) -> Self {
82 let mut rng = rand::thread_rng();
83 let mut path = Vec::with_capacity(n_steps + 1);
84 let mut s = s0;
85 path.push((0.0, s));
86 let sqrt_dt = dt.sqrt();
87
88 for i in 1..=n_steps {
89 let z: f64 = rng.gen_range(-1.0..1.0);
90 let dw = z * sqrt_dt * std::f64::consts::SQRT_2;
91 s = s * ((mu - 0.5 * sigma * sigma) * dt + sigma * dw).exp();
92 path.push((i as f64 * dt, s));
93 }
94 GeometricBrownian { initial_value: s0, mu, sigma, path }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct OrnsteinUhlenbeck {
100 pub theta: f64,
101 pub mu: f64,
102 pub sigma: f64,
103 pub path: Vec<(f64, f64)>,
104}
105
106impl OrnsteinUhlenbeck {
107 pub fn simulate(x0: f64, theta: f64, mu: f64, sigma: f64, dt: f64, n_steps: usize) -> Self {
108 let mut rng = rand::thread_rng();
109 let mut path = Vec::with_capacity(n_steps + 1);
110 let mut x = x0;
111 path.push((0.0, x));
112 let sqrt_dt = dt.sqrt();
113
114 for i in 1..=n_steps {
115 let z: f64 = rng.gen_range(-1.0..1.0);
116 let dw = z * sqrt_dt * std::f64::consts::SQRT_2;
117 x = x + theta * (mu - x) * dt + sigma * dw;
118 path.push((i as f64 * dt, x));
119 }
120 OrnsteinUhlenbeck { theta, mu, sigma, path }
121 }
122
123 pub fn stationary_variance(&self) -> f64 {
124 self.sigma * self.sigma / (2.0 * self.theta)
125 }
126}
127
128pub fn reflecting_brownian(x0: f64, sigma: f64, dt: f64, n_steps: usize, lower: f64, upper: f64) -> Vec<(f64, f64)> {
129 let mut rng = rand::thread_rng();
130 let mut path = Vec::with_capacity(n_steps + 1);
131 let mut x = x0;
132 let sqrt_dt = dt.sqrt();
133 path.push((0.0, x));
134
135 for i in 1..=n_steps {
136 let z: f64 = rng.gen_range(-1.0..1.0);
137 x += sigma * z * sqrt_dt * std::f64::consts::SQRT_2;
138 while x < lower || x > upper {
139 if x < lower { x = 2.0 * lower - x; }
140 if x > upper { x = 2.0 * upper - x; }
141 }
142 path.push((i as f64 * dt, x));
143 }
144 path
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use approx::assert_relative_eq;
151
152 #[test]
153 fn test_brownian_default_config() {
154 let cfg = BrownianConfig::default();
155 assert_eq!(cfg.dimension, 1);
156 assert_eq!(cfg.sigma, 1.0);
157 assert_eq!(cfg.drift, 0.0);
158 }
159
160 #[test]
161 fn test_brownian_simulation() {
162 let cfg = BrownianConfig { dimension: 2, dt: 0.01, sigma: 1.0, drift: 0.0 };
163 let path = BrownianPath::simulate(&cfg, 100);
164 assert_eq!(path.steps.len(), 100);
165 assert!(path.final_position().is_some());
166 }
167
168 #[test]
169 fn test_quadratic_variation() {
170 let cfg = BrownianConfig { dimension: 1, dt: 0.001, sigma: 1.0, drift: 0.0 };
171 let path = BrownianPath::simulate(&cfg, 10000);
172 let qv = path.quadratic_variation();
173 let t = 10000.0 * 0.001;
174 assert!(qv > 0.0 && (qv - t).abs() / t < 1.0, "QV={} vs T={}", qv, t);
175 }
176
177 #[test]
178 fn test_ito_lemma_constant() {
179 let result = ito_lemma(0.0, 0.0, 1.0, 0.0, 0.01, 0.1);
180 assert_relative_eq!(result, 0.1, epsilon = 1e-10);
181 }
182
183 #[test]
184 fn test_ito_lemma_quadratic() {
185 let w = 1.0;
186 let dw = 0.05;
187 let result = ito_lemma(w * w, 1.0, 2.0 * w, 2.0, 0.01, dw);
188 let expected = w * w + 0.01 + 2.0 * w * dw + 0.5 * 2.0 * dw * dw;
189 assert_relative_eq!(result, expected, epsilon = 1e-10);
190 }
191
192 #[test]
193 fn test_ito_integral() {
194 let f = vec![1.0, 2.0, 3.0];
195 let dw = vec![0.1, -0.2, 0.3];
196 let result = ito_integral(&f, &dw);
197 assert_relative_eq!(result, 1.0 * 0.1 + 2.0 * (-0.2) + 3.0 * 0.3);
198 }
199
200 #[test]
201 fn test_geometric_brownian() {
202 let gbm = GeometricBrownian::simulate(100.0, 0.05, 0.2, 0.01, 1000);
203 assert_eq!(gbm.path.len(), 1001);
204 assert_relative_eq!(gbm.path[0].1, 100.0);
205 assert!(gbm.path.last().unwrap().1 > 0.0);
206 }
207
208 #[test]
209 fn test_ou_stationary_variance() {
210 let ou = OrnsteinUhlenbeck { theta: 1.0, mu: 0.0, sigma: 1.0, path: vec![] };
211 let var = ou.stationary_variance();
212 assert_relative_eq!(var, 0.5, epsilon = 1e-10);
213 }
214
215 #[test]
216 fn test_ou_simulation() {
217 let ou = OrnsteinUhlenbeck::simulate(5.0, 1.0, 0.0, 1.0, 0.01, 500);
218 assert_eq!(ou.path.len(), 501);
219 assert_relative_eq!(ou.path[0].1, 5.0);
220 }
221
222 #[test]
223 fn test_reflecting_brownian_bounds() {
224 let path = reflecting_brownian(0.5, 1.0, 0.01, 1000, 0.0, 1.0);
225 for (_, x) in &path {
226 assert!(*x >= -1e-10 && *x <= 1.0 + 1e-10, "x={}", x);
227 }
228 }
229}