Skip to content

Commit 7044ea2

Browse files
committed
feat: update cgmy
1 parent 2988629 commit 7044ea2

File tree

5 files changed

+216
-46
lines changed

5 files changed

+216
-46
lines changed

src/stats.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod cir;
22
pub mod fd;
33
pub mod mle;
4+
pub mod non_central_chi_squared;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use rand_distr::{ChiSquared, Distribution, Normal};
2+
3+
pub fn sample(df: f64, non_centrality: f64) -> f64 {
4+
let mut rng = rand::thread_rng();
5+
6+
let chi_squared = ChiSquared::new(df).unwrap();
7+
let central_part: f64 = chi_squared.sample(&mut rng);
8+
9+
let normal_dist = Normal::new(non_centrality.sqrt(), 1.0).unwrap();
10+
let non_central_part: f64 = normal_dist.sample(&mut rng).powi(2);
11+
12+
central_part + non_central_part
13+
}

src/stochastic/jump/cgmy.rs

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::stochastic::{process::poisson::Poisson, Sampling};
22
use impl_new_derive::ImplNew;
33
use ndarray::Array1;
4-
use rand::{thread_rng, Rng};
5-
use rand_distr::Exp;
4+
use ndarray_rand::RandomExt;
5+
use rand::Rng;
6+
use rand_distr::{Exp, Uniform};
67
use scilib::math::basic::gamma;
78

89
/// CGMY process
@@ -69,27 +70,25 @@ impl Sampling<f64> for CGMY {
6970
* gamma(1.0 - self.alpha)
7071
* (self.lambda_plus.powf(self.alpha - 1.0) - self.lambda_minus.powf(self.alpha - 1.0));
7172

72-
let mut rng = thread_rng();
73+
let U = Array1::<f64>::random(self.j, Uniform::new(0.0, 1.0));
74+
let E = Array1::<f64>::random(self.j, Exp::new(1.0).unwrap());
75+
let poisson = Poisson::new(1.0, Some(self.j), None, None);
76+
let poisson = poisson.sample();
77+
78+
let mut rng = rand::thread_rng();
7379

7480
for i in 1..self.n {
7581
let mut jump_component = 0.0;
7682

77-
let poisson = Poisson::new(1.0, Some(self.j), None, None);
78-
let poisson = poisson.sample();
79-
80-
for j in 0..self.j {
81-
let u_j: f64 = rng.gen();
82-
let e_j: f64 = rng.sample(Exp::new(1.0).unwrap());
83-
83+
for j in 1..self.j {
8484
let v_j = if rng.gen_bool(0.5) {
8585
self.lambda_plus
8686
} else {
8787
-self.lambda_minus
8888
};
8989

9090
let term1 = (self.alpha * poisson[j] / (2.0 * c)).powf(-1.0 / self.alpha);
91-
let term2 = e_j * u_j.powf(1.0 / self.alpha) / v_j.abs();
92-
91+
let term2 = E[j] * U[j].powf(1.0 / self.alpha) / v_j.abs();
9392
let jump_size = term1.min(term2) * (v_j / v_j.abs());
9493

9594
jump_component += jump_size;
@@ -119,50 +118,19 @@ mod tests {
119118

120119
#[test]
121120
fn cgmy_length_equals_n() {
122-
let cgmy = CGMY {
123-
lambda_plus: 5.0,
124-
lambda_minus: 5.0,
125-
alpha: 0.7,
126-
n: N,
127-
j: 1000,
128-
x0: Some(0.0),
129-
t: Some(1.0),
130-
m: None,
131-
};
132-
121+
let cgmy = CGMY::new(5.0, 5.0, 0.7, N, 1000, Some(0.0), Some(1.0), None);
133122
assert_eq!(cgmy.sample().len(), N);
134123
}
135124

136125
#[test]
137126
fn cgmy_starts_with_x0() {
138-
let cgmy = CGMY {
139-
lambda_plus: 5.0,
140-
lambda_minus: 5.0,
141-
alpha: 0.7,
142-
n: N,
143-
j: 1000,
144-
x0: Some(0.0),
145-
t: Some(1.0),
146-
m: None,
147-
};
148-
127+
let cgmy = CGMY::new(5.0, 5.0, 0.7, N, 1000, Some(0.0), Some(1.0), None);
149128
assert_eq!(cgmy.sample()[0], 0.0);
150129
}
151130

152131
#[test]
153132
fn cgmy_plot() {
154-
let cgmy = CGMY {
155-
lambda_plus: 5.0,
156-
lambda_minus: 5.0,
157-
alpha: 0.7,
158-
n: 1000,
159-
j: 1000,
160-
x0: Some(0.0),
161-
t: Some(1.0),
162-
m: None,
163-
};
164-
165-
// Plot the CGMY sample path
133+
let cgmy = CGMY::new(25.46, 4.604, 0.52, 100, 1024, Some(2.0), Some(1.0), None);
166134
plot_1d!(cgmy.sample(), "CGMY Process");
167135
}
168136
}

src/stochastic/volatility.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod fheston;
33
pub mod heston;
44
pub mod rbergomi;
55
pub mod sabr;
6+
pub mod svcgmy;
67

78
#[derive(Debug, Clone, Copy, Default)]
89
pub enum HestonPow {
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use impl_new_derive::ImplNew;
2+
use ndarray::{s, Array1};
3+
use ndarray_rand::RandomExt;
4+
use rand::Rng;
5+
use rand_distr::{Exp, Uniform};
6+
use scilib::math::basic::gamma;
7+
8+
use crate::{
9+
stats::non_central_chi_squared,
10+
stochastic::{process::poisson::Poisson, Sampling},
11+
};
12+
13+
#[derive(ImplNew)]
14+
pub struct SVCGMY {
15+
/// Positive jump rate lambda_plus (corresponds to G)
16+
pub lambda_plus: f64, // G
17+
/// Negative jump rate lambda_minus (corresponds to M)
18+
pub lambda_minus: f64, // M
19+
/// Jump activity parameter alpha (corresponds to Y), with 0 < alpha < 2
20+
pub alpha: f64,
21+
///
22+
pub kappa: f64,
23+
///
24+
pub eta: f64,
25+
///
26+
pub zeta: f64,
27+
///
28+
pub rho: f64,
29+
/// Number of time steps
30+
pub n: usize,
31+
/// Jumps
32+
pub j: usize,
33+
///
34+
pub x0: Option<f64>,
35+
/// Initial value
36+
pub v0: Option<f64>,
37+
/// Total time horizon
38+
pub t: Option<f64>,
39+
/// Number of samples for parallel sampling (not used in this implementation)
40+
pub m: Option<usize>,
41+
}
42+
43+
impl Sampling<f64> for SVCGMY {
44+
fn sample(&self) -> Array1<f64> {
45+
let t_max = self.t.unwrap_or(1.0);
46+
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64;
47+
let mut x = Array1::<f64>::zeros(self.n);
48+
x[0] = self.x0.unwrap_or(0.0);
49+
50+
let C = (gamma(2.0 - self.alpha)
51+
* (self.lambda_plus.powf(self.alpha - 2.0) + self.lambda_minus.powf(self.alpha - 2.0)))
52+
.powi(-1);
53+
let c = (2.0 * self.kappa) / ((1.0 - (-self.kappa * dt).exp()) * self.zeta.powi(2));
54+
55+
let mut v = Array1::<f64>::zeros(self.n);
56+
v[0] = self.v0.unwrap_or(0.0);
57+
58+
for i in 1..self.n {
59+
let xi = non_central_chi_squared::sample(
60+
4.0 * self.kappa * self.eta / self.zeta.powi(2),
61+
2.0 * c * v[i - 1] * (-self.kappa * dt).exp(),
62+
);
63+
64+
v[i] = xi / (2.0 * c);
65+
}
66+
67+
let U = Array1::<f64>::random(self.j, Uniform::new(0.0, 1.0));
68+
let E = Array1::<f64>::random(self.j, Exp::new(1.0).unwrap());
69+
let E_ = Array1::<f64>::random(self.j, Exp::new(1.0).unwrap());
70+
let P = Poisson::new(1.0, Some(self.j), None, None);
71+
let mut P = P.sample();
72+
73+
for (idx, p) in P.iter_mut().enumerate() {
74+
*p = *p + E_[idx];
75+
}
76+
77+
let tau = Array1::<f64>::random(self.j, Uniform::new(0.0, t_max));
78+
let mut c_t = Array1::<f64>::zeros(self.j);
79+
80+
for j in 1..self.j {
81+
c_t[j] = C * v.slice(s![..j - 1]).sum()
82+
}
83+
84+
let mut rng = rand::thread_rng();
85+
86+
for i in 1..self.n {
87+
let mut jump_component = 0.0;
88+
89+
for j in 1..self.j {
90+
let v_j = if rng.gen_bool(0.5) {
91+
self.lambda_plus
92+
} else {
93+
-self.lambda_minus
94+
};
95+
96+
let term1 = ((self.alpha * P[j]) / (2.0 * c_t[j] * t_max)).powf(-1.0 / self.alpha);
97+
let term2 = E[j] * U[j].powf(1.0 / self.alpha) / v_j.abs();
98+
let jump_size = term1.min(term2) * (v_j / v_j.abs());
99+
100+
jump_component += jump_size;
101+
}
102+
103+
let b = -(v[i]
104+
* (self.lambda_plus.powf(self.alpha - 1.0) - self.lambda_minus.powf(self.alpha - 1.0))
105+
/ ((1.0 - self.alpha)
106+
* (self.lambda_plus.powf(self.alpha - 2.0) + self.lambda_minus.powf(self.alpha) - 2.0)));
107+
108+
x[i] = x[i - 1] + jump_component + b * dt + self.rho * v[i - 1];
109+
}
110+
111+
x
112+
}
113+
114+
fn n(&self) -> usize {
115+
self.n
116+
}
117+
118+
fn m(&self) -> Option<usize> {
119+
self.m
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use super::*;
126+
use crate::{plot_1d, stochastic::N};
127+
128+
#[test]
129+
fn svcgmy_length_equals_n() {
130+
let svcgmy = SVCGMY::new(
131+
25.46,
132+
4.604,
133+
0.52,
134+
1.003,
135+
0.0711,
136+
0.3443,
137+
-2.0280,
138+
N,
139+
1024,
140+
None,
141+
Some(0.0064),
142+
Some(1.0),
143+
None,
144+
);
145+
assert_eq!(svcgmy.sample().len(), N);
146+
}
147+
148+
#[test]
149+
fn svcgmy_starts_with_x0() {
150+
let svcgmy = SVCGMY::new(
151+
25.46,
152+
4.604,
153+
0.52,
154+
1.003,
155+
0.0711,
156+
0.3443,
157+
-2.0280,
158+
N,
159+
1024,
160+
None,
161+
Some(0.0064),
162+
Some(1.0),
163+
None,
164+
);
165+
assert_eq!(svcgmy.sample()[0], 0.0);
166+
}
167+
168+
#[test]
169+
fn svcgmy_plot() {
170+
let svcgmy = SVCGMY::new(
171+
25.46,
172+
4.604,
173+
0.52,
174+
1.003,
175+
0.0711,
176+
0.3443,
177+
-2.0280,
178+
100,
179+
1024,
180+
None,
181+
Some(0.0064),
182+
Some(1.0),
183+
None,
184+
);
185+
plot_1d!(svcgmy.sample(), "CGMY Process");
186+
}
187+
}

0 commit comments

Comments
 (0)