Skip to content

Commit 9cbb4c9

Browse files
authored
Merge pull request #15 from rust-dd/feat/autoregressive
feat: add auto regressive processes
2 parents 64352e3 + 38ede28 commit 9cbb4c9

File tree

12 files changed

+1068
-3
lines changed

12 files changed

+1068
-3
lines changed

src/stochastic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//! | **volatility** | Focuses on modeling stochastic volatility, including processes like the Heston model, which are used to simulate changes in volatility over time in financial markets. |
1616
//!
1717
18+
pub mod autoregressive;
1819
pub mod diffusion;
1920
pub mod interest;
2021
pub mod isonormal;

src/stochastic/autoregressive.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub mod agrach;
2+
pub mod ar;
3+
pub mod arch;
4+
pub mod arima;
5+
pub mod egarch;
6+
pub mod garch;
7+
pub mod ma;
8+
pub mod sarima;
9+
pub mod tgarch;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use impl_new_derive::ImplNew;
2+
use ndarray::Array1;
3+
use ndarray_rand::RandomExt;
4+
use rand_distr::Normal;
5+
6+
use crate::stochastic::Sampling;
7+
8+
/// A generic Asymmetric GARCH(p,q) model (A-GARCH),
9+
/// allowing a separate "delta" term for negative-lag effects.
10+
///
11+
/// One possible form:
12+
/// \[
13+
/// \sigma_t^2
14+
/// = \omega
15+
/// + \sum_{i=1}^p \Bigl[\alpha_i X_{t-i}^2
16+
/// + \delta_i X_{t-i}^2 \mathbf{1}_{\{X_{t-i}<0\}}\Bigr]
17+
/// + \sum_{j=1}^q \beta_j \sigma_{t-j}^2,
18+
/// \quad X_t = \sigma_t \cdot z_t, \quad z_t \sim \mathcal{N}(0,1).
19+
/// \]
20+
///
21+
/// # Parameters
22+
/// - `omega`: Constant term \(\omega\).
23+
/// - `alpha`: Array \(\{\alpha_1, \ldots, \alpha_p\}\) for positive squared terms.
24+
/// - `delta`: Array \(\{\delta_1, \ldots, \delta_p\}\) for negative-lag extra effect.
25+
/// - `beta`: Array \(\{\beta_1, \ldots, \beta_q\}\).
26+
/// - `n`: Length of the time series.
27+
/// - `m`: Optional batch size (unused by default).
28+
///
29+
/// # Notes
30+
/// - This is essentially a T-GARCH-like structure but with different naming (`delta`).
31+
/// - Stationarity constraints typically require \(\sum \alpha_i + \tfrac{1}{2}\sum \delta_i + \sum \beta_j < 1\).
32+
#[derive(ImplNew)]
33+
pub struct AGARCH {
34+
pub omega: f64,
35+
pub alpha: Array1<f64>,
36+
pub delta: Array1<f64>,
37+
pub beta: Array1<f64>,
38+
pub n: usize,
39+
pub m: Option<usize>,
40+
}
41+
42+
impl Sampling<f64> for AGARCH {
43+
fn sample(&self) -> Array1<f64> {
44+
let p = self.alpha.len();
45+
let q = self.beta.len();
46+
47+
// Generate white noise
48+
let z = Array1::random(self.n, Normal::new(0.0, 1.0).unwrap());
49+
50+
// Arrays for X_t and sigma_t^2
51+
let mut x = Array1::<f64>::zeros(self.n);
52+
let mut sigma2 = Array1::<f64>::zeros(self.n);
53+
54+
// Summation for unconditional variance init
55+
let sum_alpha: f64 = self.alpha.iter().sum();
56+
let sum_delta_half: f64 = self.delta.iter().sum::<f64>() * 0.5;
57+
let sum_beta: f64 = self.beta.iter().sum();
58+
let denom = (1.0 - sum_alpha - sum_delta_half - sum_beta).max(1e-8);
59+
60+
for t in 0..self.n {
61+
if t == 0 {
62+
sigma2[t] = self.omega / denom;
63+
} else {
64+
let mut var_t = self.omega;
65+
// p-lag terms
66+
for i in 1..=p {
67+
if t >= i {
68+
let x_lag = x[t - i];
69+
let indicator = if x_lag < 0.0 { 1.0 } else { 0.0 };
70+
71+
var_t +=
72+
self.alpha[i - 1] * x_lag.powi(2) + self.delta[i - 1] * x_lag.powi(2) * indicator;
73+
}
74+
}
75+
// q-lag terms
76+
for j in 1..=q {
77+
if t >= j {
78+
var_t += self.beta[j - 1] * sigma2[t - j];
79+
}
80+
}
81+
sigma2[t] = var_t;
82+
}
83+
x[t] = sigma2[t].sqrt() * z[t];
84+
}
85+
86+
x
87+
}
88+
89+
fn n(&self) -> usize {
90+
self.n
91+
}
92+
93+
fn m(&self) -> Option<usize> {
94+
self.m
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use ndarray::arr1;
101+
102+
use crate::{
103+
plot_1d,
104+
stochastic::{autoregressive::agrach::AGARCH, Sampling},
105+
};
106+
107+
#[test]
108+
fn agarch_plot() {
109+
let alpha = arr1(&[0.05, 0.01]); // p=2
110+
let delta = arr1(&[0.03, 0.01]); // p=2
111+
let beta = arr1(&[0.8]); // q=1
112+
let agarchpq = AGARCH::new(0.1, alpha, delta, beta, 100, None);
113+
plot_1d!(agarchpq.sample(), "A-GARCH(p,q) process");
114+
}
115+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use impl_new_derive::ImplNew;
2+
use ndarray::Array1;
3+
use ndarray_rand::RandomExt;
4+
use rand_distr::Normal;
5+
6+
use crate::stochastic::Sampling;
7+
8+
/// Implements an AR(p) model:
9+
///
10+
/// \[
11+
/// X_t = \phi_1 X_{t-1} + \phi_2 X_{t-2} + \dots + \phi_p X_{t-p}
12+
/// + \epsilon_t,
13+
/// \quad \epsilon_t \sim \mathcal{N}(0, \sigma^2).
14+
/// \]
15+
///
16+
/// # Fields
17+
/// - `phi`: Vector of AR coefficients (\(\phi_1, \ldots, \phi_p\)).
18+
/// - `sigma`: Standard deviation of the noise \(\epsilon_t\).
19+
/// - `n`: Length of the time series.
20+
/// - `m`: Optional batch size (for parallel sampling).
21+
/// - `x0`: Optional array of initial values. If provided, should have length at least `phi.len()`.
22+
#[derive(ImplNew)]
23+
pub struct ARp {
24+
/// AR coefficients
25+
pub phi: Array1<f64>,
26+
/// Noise std dev
27+
pub sigma: f64,
28+
/// Number of observations
29+
pub n: usize,
30+
/// Optional batch size
31+
pub m: Option<usize>,
32+
/// Optional initial conditions
33+
pub x0: Option<Array1<f64>>,
34+
}
35+
36+
impl Sampling<f64> for ARp {
37+
fn sample(&self) -> Array1<f64> {
38+
let p = self.phi.len();
39+
let noise = Array1::random(self.n, Normal::new(0.0, self.sigma).unwrap());
40+
let mut series = Array1::<f64>::zeros(self.n);
41+
42+
// Fill initial conditions if provided
43+
if let Some(init) = &self.x0 {
44+
// Copy up to min(p, n)
45+
for i in 0..p.min(self.n) {
46+
series[i] = init[i];
47+
}
48+
}
49+
50+
// AR recursion
51+
for t in 0..self.n {
52+
let mut val = 0.0;
53+
// Sum over AR lags
54+
for k in 1..=p {
55+
if t >= k {
56+
val += self.phi[k - 1] * series[t - k];
57+
}
58+
}
59+
// Add noise
60+
series[t] += val + noise[t];
61+
}
62+
63+
series
64+
}
65+
66+
fn n(&self) -> usize {
67+
self.n
68+
}
69+
70+
fn m(&self) -> Option<usize> {
71+
self.m
72+
}
73+
}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use ndarray::arr1;
78+
79+
use crate::{
80+
plot_1d,
81+
stochastic::{autoregressive::ar::ARp, Sampling},
82+
};
83+
84+
#[test]
85+
fn ar_plot() {
86+
// Suppose p=2 with user-defined coefficients
87+
let phi = arr1(&[0.5, -0.25]);
88+
let ar_model = ARp::new(phi, 1.0, 100, None, None);
89+
plot_1d!(ar_model.sample(), "AR(p) process");
90+
}
91+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use impl_new_derive::ImplNew;
2+
use ndarray::Array1;
3+
use ndarray_rand::RandomExt;
4+
use rand_distr::Normal;
5+
6+
use crate::stochastic::Sampling;
7+
8+
/// Implements an ARCH(m) model:
9+
///
10+
/// \[
11+
/// \sigma_t^2 = \omega + \sum_{i=1}^m \alpha_i X_{t-i}^2,
12+
/// \quad X_t = \sigma_t \cdot z_t, \quad z_t \sim \mathcal{N}(0,1).
13+
/// \]
14+
///
15+
/// # Fields
16+
/// - `omega`: Constant term.
17+
/// - `alpha`: Array of ARCH coefficients.
18+
/// - `n`: Number of observations.
19+
/// - `m`: Optional batch size.
20+
#[derive(ImplNew)]
21+
pub struct ARCH {
22+
/// Omega (constant term in variance)
23+
pub omega: f64,
24+
/// Coefficients alpha_i
25+
pub alpha: Array1<f64>,
26+
/// Length of series
27+
pub n: usize,
28+
/// Optional batch size
29+
pub m: Option<usize>,
30+
}
31+
32+
impl Sampling<f64> for ARCH {
33+
fn sample(&self) -> Array1<f64> {
34+
let m = self.alpha.len();
35+
let z = Array1::random(self.n, Normal::new(0.0, 1.0).unwrap());
36+
let mut x = Array1::<f64>::zeros(self.n);
37+
38+
for t in 0..self.n {
39+
// compute sigma_t^2
40+
let mut var_t = self.omega;
41+
for i in 1..=m {
42+
if t >= i {
43+
let x_lag = x[t - i];
44+
var_t += self.alpha[i - 1] * x_lag.powi(2);
45+
}
46+
}
47+
let sigma_t = var_t.sqrt();
48+
x[t] = sigma_t * z[t];
49+
}
50+
51+
x
52+
}
53+
54+
fn n(&self) -> usize {
55+
self.n
56+
}
57+
58+
fn m(&self) -> Option<usize> {
59+
self.m
60+
}
61+
}
62+
63+
#[cfg(test)]
64+
mod tests {
65+
use ndarray::arr1;
66+
67+
use crate::{
68+
plot_1d,
69+
stochastic::{autoregressive::arch::ARCH, Sampling},
70+
};
71+
72+
#[test]
73+
fn arch_plot() {
74+
let alpha = arr1(&[0.2, 0.1]);
75+
let arch_model = ARCH::new(0.1, alpha, 100, None);
76+
plot_1d!(arch_model.sample(), "ARCH(m) process");
77+
}
78+
}

0 commit comments

Comments
 (0)