From 75f817d4c5fda64df1aea972d2b0966d981302de Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 6 Oct 2024 21:32:30 +0200 Subject: [PATCH] feat: update package and cleanup code --- Cargo.toml | 2 +- src/stats/fd.rs | 2 +- src/stochastic/diffusion/cev.rs | 5 +- src/stochastic/diffusion/cir.rs | 4 +- src/stochastic/diffusion/fcir.rs | 8 +- src/stochastic/diffusion/fgbm.rs | 8 +- src/stochastic/diffusion/fjacobi.rs | 8 +- src/stochastic/diffusion/fou.rs | 8 +- src/stochastic/diffusion/gbm.rs | 12 +-- src/stochastic/diffusion/jacobi.rs | 4 +- src/stochastic/diffusion/ou.rs | 4 +- src/stochastic/interest/duffie_kan.rs | 44 ++------ src/stochastic/interest/fvasicek.rs | 3 +- src/stochastic/interest/ho_lee.rs | 37 ++----- src/stochastic/interest/hull_white.rs | 25 ++--- src/stochastic/interest/hull_white_2f.rs | 36 ++----- src/stochastic/interest/vasicek.rs | 2 +- src/stochastic/jump/bates.rs | 54 ++-------- src/stochastic/jump/ig.rs | 24 ++--- src/stochastic/jump/jump_fou.rs | 46 +-------- src/stochastic/jump/levy_diffusion.rs | 37 ++----- src/stochastic/jump/merton.rs | 38 ++----- src/stochastic/jump/nig.rs | 28 ++---- src/stochastic/jump/vg.rs | 28 ++---- src/stochastic/noise/cfgns.rs | 30 ++---- src/stochastic/noise/cgns.rs | 26 ++--- src/stochastic/noise/fgn.rs | 32 ++++-- src/stochastic/process/bm.rs | 22 ++-- src/stochastic/process/cbms.rs | 27 +---- src/stochastic/process/ccustom.rs | 30 +----- src/stochastic/process/cfbms.rs | 48 ++------- src/stochastic/process/cpoisson.rs | 28 +----- src/stochastic/process/customjt.rs | 19 +--- src/stochastic/process/fbm.rs | 123 +++++++++++++---------- src/stochastic/volatility/bergomi.rs | 35 ++----- src/stochastic/volatility/fheston.rs | 71 ++----------- src/stochastic/volatility/heston.rs | 98 ++---------------- src/stochastic/volatility/rbergomi.rs | 34 +------ src/stochastic/volatility/sabr.rs | 33 +----- stochastic-rs-macros/Cargo.lock | 47 --------- stochastic-rs-macros/Cargo.toml | 12 --- stochastic-rs-macros/src/lib.rs | 50 --------- 42 files changed, 263 insertions(+), 969 deletions(-) delete mode 100644 stochastic-rs-macros/Cargo.lock delete mode 100644 stochastic-rs-macros/Cargo.toml delete mode 100644 stochastic-rs-macros/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index fa57551..e1ac3ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ candle-datasets = "0.7.2" candle-nn = "0.7.2" candle-transformers = "0.7.2" chrono = "0.4.38" +impl-new-derive = "0.1.0" indicatif = "0.17.8" levenberg-marquardt = "0.14.0" linreg = "0.2.0" @@ -45,7 +46,6 @@ rand_distr = "0.4.3" rayon = "1.10.0" scilib = "1.0.0" statrs = "0.17.1" -stochastic-rs-macros = { version = "0.1.0", path = "stochastic-rs-macros" } tikv-jemallocator = { version = "0.6.0", optional = true } time = { version = "0.3.36", features = [ "formatting", diff --git a/src/stats/fd.rs b/src/stats/fd.rs index 0cc80da..5af02b3 100644 --- a/src/stats/fd.rs +++ b/src/stats/fd.rs @@ -77,7 +77,7 @@ impl FractalDim { mod tests { use approx::assert_relative_eq; - use crate::stochastic::{process::fbm::Fbm, Sampling}; + use crate::stochastic::{process::fbm::FBM, Sampling}; use super::*; diff --git a/src/stochastic/diffusion/cev.rs b/src/stochastic/diffusion/cev.rs index 386bda2..88e1623 100644 --- a/src/stochastic/diffusion/cev.rs +++ b/src/stochastic/diffusion/cev.rs @@ -1,9 +1,10 @@ +#[cfg(feature = "malliavin")] use std::sync::Mutex; +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; -use stochastic_rs_macros::ImplNew; use crate::stochastic::Sampling; @@ -24,7 +25,7 @@ pub struct CEV { impl Sampling for CEV { /// Sample the CEV process fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); let mut cev = Array1::::zeros(self.n); diff --git a/src/stochastic/diffusion/cir.rs b/src/stochastic/diffusion/cir.rs index b9ae5f6..c9bac12 100644 --- a/src/stochastic/diffusion/cir.rs +++ b/src/stochastic/diffusion/cir.rs @@ -1,7 +1,7 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; -use stochastic_rs_macros::ImplNew; use crate::stochastic::Sampling; @@ -28,7 +28,7 @@ impl Sampling for CIR { "2 * theta * mu < sigma^2" ); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); let mut cir = Array1::::zeros(self.n); diff --git a/src/stochastic/diffusion/fcir.rs b/src/stochastic/diffusion/fcir.rs index 302c4a2..f54e777 100644 --- a/src/stochastic/diffusion/fcir.rs +++ b/src/stochastic/diffusion/fcir.rs @@ -1,5 +1,5 @@ -use ndarray::{s, Array1}; -use stochastic_rs_macros::ImplNew; +use impl_new_derive::ImplNew; +use ndarray::Array1; use crate::stochastic::{noise::fgn::FGN, Sampling}; @@ -28,7 +28,7 @@ impl Sampling for FCIR { ); let fgn = self.fgn.sample(); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let mut fcir = Array1::::zeros(self.n); fcir[0] = self.x0.unwrap_or(0.0); @@ -43,7 +43,7 @@ impl Sampling for FCIR { }; } - fcir.slice(s![..self.n()]).to_owned() + fcir } /// Number of time steps diff --git a/src/stochastic/diffusion/fgbm.rs b/src/stochastic/diffusion/fgbm.rs index a5be477..5f50fa6 100644 --- a/src/stochastic/diffusion/fgbm.rs +++ b/src/stochastic/diffusion/fgbm.rs @@ -1,5 +1,5 @@ -use ndarray::{s, Array1}; -use stochastic_rs_macros::ImplNew; +use impl_new_derive::ImplNew; +use ndarray::Array1; use crate::stochastic::{noise::fgn::FGN, Sampling}; @@ -17,7 +17,7 @@ pub struct FGBM { impl Sampling for FGBM { /// Sample the Fractional Geometric Brownian Motion (FGBM) process fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let fgn = self.fgn.sample(); let mut fgbm = Array1::::zeros(self.n); @@ -27,7 +27,7 @@ impl Sampling for FGBM { fgbm[i] = fgbm[i - 1] + self.mu * fgbm[i - 1] * dt + self.sigma * fgbm[i - 1] * fgn[i - 1] } - fgbm.slice(s![..self.n()]).to_owned() + fgbm } /// Number of time steps diff --git a/src/stochastic/diffusion/fjacobi.rs b/src/stochastic/diffusion/fjacobi.rs index c38fa47..c8a3fec 100644 --- a/src/stochastic/diffusion/fjacobi.rs +++ b/src/stochastic/diffusion/fjacobi.rs @@ -1,5 +1,5 @@ -use ndarray::{s, Array1}; -use stochastic_rs_macros::ImplNew; +use impl_new_derive::ImplNew; +use ndarray::Array1; use crate::stochastic::{noise::fgn::FGN, Sampling}; @@ -23,7 +23,7 @@ impl Sampling for FJacobi { assert!(self.sigma > 0.0, "sigma must be positive"); assert!(self.alpha < self.beta, "alpha must be less than beta"); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let fgn = self.fgn.sample(); let mut fjacobi = Array1::::zeros(self.n); @@ -41,7 +41,7 @@ impl Sampling for FJacobi { } } - fjacobi.slice(s![..self.n()]).to_owned() + fjacobi } /// Number of time steps diff --git a/src/stochastic/diffusion/fou.rs b/src/stochastic/diffusion/fou.rs index 87df2aa..606a45a 100644 --- a/src/stochastic/diffusion/fou.rs +++ b/src/stochastic/diffusion/fou.rs @@ -1,5 +1,5 @@ -use ndarray::{s, Array1}; -use stochastic_rs_macros::ImplNew; +use impl_new_derive::ImplNew; +use ndarray::Array1; use crate::stochastic::{noise::fgn::FGN, Sampling}; @@ -18,7 +18,7 @@ pub struct FOU { impl Sampling for FOU { /// Sample the Fractional Ornstein-Uhlenbeck (FOU) process fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let fgn = self.fgn.sample(); let mut fou = Array1::::zeros(self.n); @@ -28,7 +28,7 @@ impl Sampling for FOU { fou[i] = fou[i - 1] + self.theta * (self.mu - fou[i - 1]) * dt + self.sigma * fgn[i - 1] } - fou.slice(s![..self.n()]).to_owned() + fou } /// Number of time steps diff --git a/src/stochastic/diffusion/gbm.rs b/src/stochastic/diffusion/gbm.rs index 5b1991d..76d3ad1 100644 --- a/src/stochastic/diffusion/gbm.rs +++ b/src/stochastic/diffusion/gbm.rs @@ -1,6 +1,7 @@ #[cfg(feature = "malliavin")] use std::sync::Mutex; +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use num_complex::Complex64; @@ -9,7 +10,6 @@ use statrs::{ distribution::{Continuous, ContinuousCDF, LogNormal}, statistics::{Distribution as StatDistribution, Median, Mode}, }; -use stochastic_rs_macros::ImplNew; use crate::stochastic::{Distribution, Sampling}; @@ -31,7 +31,7 @@ pub struct GBM { impl Sampling for GBM { /// Sample the GBM process fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); let mut gbm = Array1::::zeros(self.n); @@ -186,26 +186,26 @@ mod tests { use super::*; #[test] - fn gmb_length_equals_n() { + fn gbm_length_equals_n() { let gbm = GBM::new(0.25, 0.5, N, Some(X0), Some(1.0), None, None, None); assert_eq!(gbm.sample().len(), N); } #[test] - fn gmb_starts_with_x0() { + fn gbm_starts_with_x0() { let gbm = GBM::new(0.25, 0.5, N, Some(X0), Some(1.0), None, None, None); assert_eq!(gbm.sample()[0], X0); } #[test] - fn gmb_plot() { + fn gbm_plot() { let gbm = GBM::new(0.25, 0.5, N, Some(X0), Some(1.0), None, None, None); plot_1d!(gbm.sample(), "Geometric Brownian Motion (GBM) process"); } #[test] #[cfg(feature = "malliavin")] - fn gmb_malliavin() { + fn gbm_malliavin() { let gbm = GBM::new(0.25, 0.5, N, Some(X0), Some(1.0), None, None, Some(true)); let process = gbm.sample(); let malliavin = gbm.malliavin(); diff --git a/src/stochastic/diffusion/jacobi.rs b/src/stochastic/diffusion/jacobi.rs index 783200e..02cae2b 100644 --- a/src/stochastic/diffusion/jacobi.rs +++ b/src/stochastic/diffusion/jacobi.rs @@ -1,7 +1,7 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; -use stochastic_rs_macros::ImplNew; use crate::stochastic::Sampling; @@ -24,7 +24,7 @@ impl Sampling for Jacobi { assert!(self.sigma > 0.0, "sigma must be positive"); assert!(self.alpha < self.beta, "alpha must be less than beta"); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); let mut jacobi = Array1::::zeros(self.n + 1); diff --git a/src/stochastic/diffusion/ou.rs b/src/stochastic/diffusion/ou.rs index 69993f5..855bdfd 100644 --- a/src/stochastic/diffusion/ou.rs +++ b/src/stochastic/diffusion/ou.rs @@ -1,7 +1,7 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; -use stochastic_rs_macros::ImplNew; use crate::stochastic::Sampling; @@ -19,7 +19,7 @@ pub struct OU { impl Sampling for OU { /// Sample the Ornstein-Uhlenbeck (OU) process fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); let mut ou = Array1::::zeros(self.n + 1); diff --git a/src/stochastic/interest/duffie_kan.rs b/src/stochastic/interest/duffie_kan.rs index 8557a7b..515df11 100644 --- a/src/stochastic/interest/duffie_kan.rs +++ b/src/stochastic/interest/duffie_kan.rs @@ -1,8 +1,9 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; -#[derive(Default)] +#[derive(ImplNew)] pub struct DuffieKan { pub alpha: f64, @@ -25,52 +26,19 @@ pub struct DuffieKan { pub cgns: CGNS, } -impl DuffieKan { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns: CGNS = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - alpha: params.alpha, - beta: params.beta, - gamma: params.gamma, - rho: params.rho, - a1: params.a1, - b1: params.b1, - c1: params.c1, - sigma1: params.sigma1, - a2: params.a2, - b2: params.b2, - c2: params.c2, - sigma2: params.sigma2, - n: params.n, - r0: params.r0, - x0: params.x0, - t: params.t, - m: params.m, - cgns, - } - } -} - impl Sampling2D for DuffieKan { /// Sample the Duffie-Kan process fn sample(&self) -> [Array1; 2] { let [cgn1, cgn2] = self.cgns.sample(); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; - let mut r = Array1::::zeros(self.n + 1); - let mut x = Array1::::zeros(self.n + 1); + let mut r = Array1::::zeros(self.n); + let mut x = Array1::::zeros(self.n); r[0] = self.r0.unwrap_or(0.0); x[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { r[i] = r[i - 1] + (self.a1 * r[i - 1] + self.b1 * x[i - 1] + self.c1) * dt + self.sigma1 * (self.alpha * r[i - 1] + self.beta * x[i - 1] + self.gamma) * cgn1[i - 1]; diff --git a/src/stochastic/interest/fvasicek.rs b/src/stochastic/interest/fvasicek.rs index 090be09..97c2e4a 100644 --- a/src/stochastic/interest/fvasicek.rs +++ b/src/stochastic/interest/fvasicek.rs @@ -1,5 +1,5 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; -use stochastic_rs_macros::ImplNew; use crate::stochastic::{diffusion::fou::FOU, Sampling}; @@ -17,6 +17,7 @@ pub struct FVasicek { } impl Sampling for FVasicek { + /// Sample the Fractional Vasicek process fn sample(&self) -> Array1 { self.fou.sample() } diff --git a/src/stochastic/interest/ho_lee.rs b/src/stochastic/interest/ho_lee.rs index 34140bf..0471345 100644 --- a/src/stochastic/interest/ho_lee.rs +++ b/src/stochastic/interest/ho_lee.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; @@ -7,12 +8,9 @@ use rand_distr::Normal; use crate::stochastic::Sampling; #[allow(non_snake_case)] -#[derive(Default)] -pub struct HoLee<'a> -where - 'a: 'static, -{ - pub f_T: Option f64 + Send + Sync + 'a>>, +#[derive(ImplNew)] +pub struct HoLee { + pub f_T: Option f64 + Send + Sync + 'static>>, pub theta: Option, pub sigma: f64, pub n: usize, @@ -20,35 +18,18 @@ where pub m: Option, } -impl<'a> HoLee<'a> { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - f_T: params.f_T.clone(), - theta: params.theta, - sigma: params.sigma, - n: params.n, - t: params.t, - m: params.m, - } - } -} - -impl<'a> Sampling for HoLee<'a> { +impl Sampling for HoLee { fn sample(&self) -> Array1 { assert!( self.theta.is_none() && self.f_T.is_none(), "theta or f_T must be provided" ); - let dt = self.t / self.n as f64; - let gn = Array1::random( - self.n, - Normal::new(0.0, (self.t / self.n as f64).sqrt()).unwrap(), - ); + let dt = self.t / (self.n - 1) as f64; + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut r = Array1::::zeros(self.n + 1); + let mut r = Array1::::zeros(self.n); - for i in 1..=self.n { + for i in 1..self.n { let drift = if let Some(r#fn) = self.f_T.as_ref() { (r#fn)(i as f64 * dt) + self.sigma.powf(2.0) } else { diff --git a/src/stochastic/interest/hull_white.rs b/src/stochastic/interest/hull_white.rs index 1280f1d..71046ff 100644 --- a/src/stochastic/interest/hull_white.rs +++ b/src/stochastic/interest/hull_white.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; @@ -7,6 +8,7 @@ use crate::stochastic::Sampling; /// Hull-White process. /// dX(t) = theta(t)dt - alpha * X(t)dt + sigma * dW(t) /// where X(t) is the Hull-White process. +#[derive(ImplNew)] pub struct HullWhite { pub theta: fn(f64) -> f64, pub alpha: f64, @@ -17,30 +19,15 @@ pub struct HullWhite { pub m: Option, } -impl HullWhite { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - theta: params.theta, - alpha: params.alpha, - sigma: params.sigma, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - } - } -} - impl Sampling for HullWhite { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut hw = Array1::::zeros(self.n + 1); + let mut hw = Array1::::zeros(self.n); hw[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { hw[i] = hw[i - 1] + ((self.theta)(i as f64 * dt) - self.alpha * hw[i - 1]) * dt + self.sigma * gn[i - 1] diff --git a/src/stochastic/interest/hull_white_2f.rs b/src/stochastic/interest/hull_white_2f.rs index 3bb930c..c932a8b 100644 --- a/src/stochastic/interest/hull_white_2f.rs +++ b/src/stochastic/interest/hull_white_2f.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; @@ -5,6 +6,7 @@ use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; /// Hull-White 2-factor model /// dX(t) = (k(t) + U(t) - theta * X(t)) dt + sigma_1 dW1(t) x(0) = x0 /// dU(t) = b * U(t) dt + sigma_2 dW2(t) u(0) = 0 +#[derive(ImplNew)] pub struct HullWhite2F { pub k: fn(f64) -> f64, pub theta: f64, @@ -19,43 +21,17 @@ pub struct HullWhite2F { pub cgns: CGNS, } -impl HullWhite2F { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - k: params.k, - theta: params.theta, - sigma1: params.sigma1, - sigma2: params.sigma2, - rho: params.rho, - b: params.b, - x0: params.x0, - t: params.t, - n: params.n, - m: params.m, - cgns, - } - } -} - impl Sampling2D for HullWhite2F { fn sample(&self) -> [Array1; 2] { let [cgn1, cgn2] = self.cgns.sample(); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; - let mut x = Array1::::zeros(self.n + 1); - let mut u = Array1::::zeros(self.n + 1); + let mut x = Array1::::zeros(self.n); + let mut u = Array1::::zeros(self.n); x[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { x[i] = x[i - 1] + ((self.k)(i as f64 * dt) + u[i - 1] - self.theta * x[i - 1]) * dt + self.sigma1 * cgn1[i - 1]; diff --git a/src/stochastic/interest/vasicek.rs b/src/stochastic/interest/vasicek.rs index 4ceecb4..973a2a4 100644 --- a/src/stochastic/interest/vasicek.rs +++ b/src/stochastic/interest/vasicek.rs @@ -1,5 +1,5 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; -use stochastic_rs_macros::ImplNew; use crate::stochastic::{diffusion::ou::OU, Sampling}; diff --git a/src/stochastic/jump/bates.rs b/src/stochastic/jump/bates.rs index 63e9564..38df837 100644 --- a/src/stochastic/jump/bates.rs +++ b/src/stochastic/jump/bates.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use crate::stochastic::{ @@ -5,7 +6,7 @@ use crate::stochastic::{ Sampling3D, }; -#[derive(Default)] +#[derive(ImplNew)] pub struct Bates1996 where D: ProcessDistribution, @@ -31,56 +32,13 @@ where pub cpoisson: CompoundPoisson, } -impl Bates1996 { - #[must_use] - pub fn new(params: &Bates1996) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - let cpoisson = CompoundPoisson::new(&CompoundPoisson { - n: None, - lambda: params.lambda, - t_max: Some(params.t.unwrap_or(1.0) / params.n as f64), - distribution: params.jumps_distribution, - m: params.m, - ..Default::default() - }); - - Self { - mu: params.mu, - b: params.b, - r: params.r, - r_f: params.r_f, - lambda: params.lambda, - k: params.k, - alpha: params.alpha, - beta: params.beta, - sigma: params.sigma, - rho: params.rho, - n: params.n, - s0: params.s0, - v0: params.v0, - t: params.t, - use_sym: params.use_sym, - m: params.m, - jumps_distribution: params.jumps_distribution, - cgns, - cpoisson, - } - } -} - impl Sampling2D for Bates1996 { fn sample(&self) -> [Array1; 2] { let [cgn1, cgn2] = self.cgns.sample(); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; - let mut s = Array1::::zeros(self.n + 1); - let mut v = Array1::::zeros(self.n + 1); + let mut s = Array1::::zeros(self.n); + let mut v = Array1::::zeros(self.n); s[0] = self.s0.unwrap_or(0.0); v[0] = self.v0.unwrap_or(0.0); @@ -91,7 +49,7 @@ impl Sampling2D for Bates1996 { _ => self.mu.unwrap(), }; - for i in 1..=self.n { + for i in 1..self.n { let [.., jumps] = self.cpoisson.sample(); s[i] = s[i - 1] diff --git a/src/stochastic/jump/ig.rs b/src/stochastic/jump/ig.rs index adf8dbb..417ee4d 100644 --- a/src/stochastic/jump/ig.rs +++ b/src/stochastic/jump/ig.rs @@ -1,10 +1,11 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; use crate::stochastic::Sampling; -#[derive(Default)] +#[derive(ImplNew)] pub struct IG { pub gamma: f64, @@ -14,27 +15,14 @@ pub struct IG { pub m: Option, } -impl IG { - #[must_use] - pub fn new(params: &IG) -> Self { - Self { - gamma: params.gamma, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - } - } -} - impl Sampling for IG { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut ig = Array1::zeros(self.n + 1); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); + let mut ig = Array1::zeros(self.n); ig[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { ig[i] = ig[i - 1] + self.gamma * dt + gn[i - 1] } diff --git a/src/stochastic/jump/jump_fou.rs b/src/stochastic/jump/jump_fou.rs index 4a70f6f..37f1ec7 100644 --- a/src/stochastic/jump/jump_fou.rs +++ b/src/stochastic/jump/jump_fou.rs @@ -1,15 +1,15 @@ +use impl_new_derive::ImplNew; use ndarray::{s, Array1}; use crate::stochastic::{ noise::fgn::FGN, process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling, Sampling3D, }; -#[derive(Default)] +#[derive(ImplNew)] pub struct JumpFOU where D: ProcessDistribution, { - pub hurst: f64, pub mu: f64, pub sigma: f64, pub theta: f64, @@ -23,50 +23,14 @@ where pub cpoisson: CompoundPoisson, } -impl JumpFOU { - #[must_use] - pub fn new(params: &JumpFOU) -> Self { - let fgn = FGN::new(params.hurst, params.n, params.t, params.m); - - let cpoisson = CompoundPoisson::new(&CompoundPoisson { - n: None, - lambda: params.lambda.unwrap(), - t_max: Some(params.t.unwrap_or(1.0) / params.n as f64), - distribution: params.jump_distribution, - m: params.m, - ..Default::default() - }); - - Self { - hurst: params.hurst, - mu: params.mu, - sigma: params.sigma, - theta: params.theta, - lambda: params.lambda, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - jump_distribution: params.jump_distribution, - fgn, - cpoisson, - } - } -} - impl Sampling for JumpFOU { fn sample(&self) -> Array1 { - assert!( - self.hurst > 0.0 && self.hurst < 1.0, - "Hurst parameter must be in (0, 1)" - ); - - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let fgn = self.fgn.sample(); - let mut jump_fou = Array1::::zeros(self.n + 1); + let mut jump_fou = Array1::::zeros(self.n); jump_fou[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { let [.., jumps] = self.cpoisson.sample(); jump_fou[i] = jump_fou[i - 1] diff --git a/src/stochastic/jump/levy_diffusion.rs b/src/stochastic/jump/levy_diffusion.rs index f56803b..6bca5a9 100644 --- a/src/stochastic/jump/levy_diffusion.rs +++ b/src/stochastic/jump/levy_diffusion.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; @@ -6,7 +7,7 @@ use crate::stochastic::{ process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling, Sampling3D, }; -#[derive(Default)] +#[derive(ImplNew)] pub struct LevyDiffusion where D: ProcessDistribution, @@ -22,40 +23,14 @@ where pub cpoisson: CompoundPoisson, } -impl LevyDiffusion { - #[must_use] - pub fn new(params: &LevyDiffusion) -> Self { - let cpoisson = CompoundPoisson::new(&CompoundPoisson { - n: None, - lambda: params.lambda, - t_max: Some(params.t.unwrap_or(1.0) / params.n as f64), - distribution: params.jump_distribution, - m: params.m, - ..Default::default() - }); - - Self { - gamma: params.gamma, - sigma: params.sigma, - lambda: params.lambda, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - jump_distribution: params.jump_distribution, - cpoisson, - } - } -} - impl Sampling for LevyDiffusion { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let mut levy = Array1::::zeros(self.n + 1); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let mut levy = Array1::::zeros(self.n); levy[0] = self.x0.unwrap_or(0.0); - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); - for i in 1..=self.n { + for i in 1..self.n { let [.., jumps] = self.cpoisson.sample(); levy[i] = levy[i - 1] + self.gamma * dt + self.sigma * gn[i - 1] + jumps.sum(); } diff --git a/src/stochastic/jump/merton.rs b/src/stochastic/jump/merton.rs index d56e5b3..dadae1e 100644 --- a/src/stochastic/jump/merton.rs +++ b/src/stochastic/jump/merton.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; @@ -6,7 +7,7 @@ use crate::stochastic::{ process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling, Sampling3D, }; -#[derive(Default)] +#[derive(ImplNew)] pub struct Merton where D: ProcessDistribution, @@ -23,41 +24,14 @@ where pub cpoisson: CompoundPoisson, } -impl Merton { - #[must_use] - pub fn new(params: &Merton) -> Self { - let cpoisson = CompoundPoisson::new(&CompoundPoisson { - n: None, - lambda: params.lambda, - t_max: Some(params.t.unwrap_or(1.0) / params.n as f64), - distribution: params.jump_distribution, - m: params.m, - ..Default::default() - }); - - Self { - alpha: params.alpha, - sigma: params.sigma, - lambda: params.lambda, - theta: params.theta, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - jump_distribution: params.jump_distribution, - cpoisson, - } - } -} - impl Sampling for Merton { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let mut merton = Array1::::zeros(self.n + 1); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let mut merton = Array1::::zeros(self.n); merton[0] = self.x0.unwrap_or(0.0); - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); - for i in 1..=self.n { + for i in 1..self.n { let [.., jumps] = self.cpoisson.sample(); merton[i] = merton[i - 1] + (self.alpha * self.sigma.powf(2.0) / 2.0 - self.lambda * self.theta) * dt diff --git a/src/stochastic/jump/nig.rs b/src/stochastic/jump/nig.rs index ed71ce7..6bcef85 100644 --- a/src/stochastic/jump/nig.rs +++ b/src/stochastic/jump/nig.rs @@ -1,10 +1,11 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::{rand_distr::InverseGaussian, RandomExt}; use rand_distr::Normal; use crate::stochastic::Sampling; -#[derive(Default)] +#[derive(ImplNew)] pub struct NIG { pub theta: f64, @@ -16,32 +17,17 @@ pub struct NIG { pub m: Option, } -impl NIG { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - theta: params.theta, - sigma: params.sigma, - kappa: params.kappa, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - } - } -} - impl Sampling for NIG { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let scale = dt.powf(2.0) / self.kappa; let mean = dt / scale; - let ig = Array1::random(self.n, InverseGaussian::new(mean, scale).unwrap()); - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut nig = Array1::zeros(self.n + 1); + let ig = Array1::random(self.n - 1, InverseGaussian::new(mean, scale).unwrap()); + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); + let mut nig = Array1::zeros(self.n); nig[0] = self.x0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { nig[i] = nig[i - 1] + self.theta * ig[i - 1] + self.sigma * ig[i - 1].sqrt() * gn[i - 1] } diff --git a/src/stochastic/jump/vg.rs b/src/stochastic/jump/vg.rs index 0818c58..2c42349 100644 --- a/src/stochastic/jump/vg.rs +++ b/src/stochastic/jump/vg.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::rand_distr::Gamma; use ndarray_rand::RandomExt; @@ -5,7 +6,7 @@ use rand_distr::Normal; use crate::stochastic::Sampling; -#[derive(Default)] +#[derive(ImplNew)] pub struct VG { pub mu: f64, pub sigma: f64, @@ -16,35 +17,20 @@ pub struct VG { pub m: Option, } -impl VG { - #[must_use] - pub fn new(params: &VG) -> Self { - Self { - mu: params.mu, - sigma: params.sigma, - nu: params.nu, - n: params.n, - x0: params.x0, - t: params.t, - m: params.m, - } - } -} - impl Sampling for VG { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let shape = dt / self.nu; let scale = self.nu; - let mut vg = Array1::::zeros(self.n + 1); + let mut vg = Array1::::zeros(self.n); vg[0] = self.x0.unwrap_or(0.0); - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - let gammas = Array1::random(self.n, Gamma::new(shape, scale).unwrap()); + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); + let gammas = Array1::random(self.n - 1, Gamma::new(shape, scale).unwrap()); - for i in 1..=self.n { + for i in 1..self.n { vg[i] = vg[i - 1] + self.mu * gammas[i - 1] + self.sigma * gammas[i - 1].sqrt() * gn[i - 1]; } diff --git a/src/stochastic/noise/cfgns.rs b/src/stochastic/noise/cfgns.rs index c99ed99..874dafc 100644 --- a/src/stochastic/noise/cfgns.rs +++ b/src/stochastic/noise/cfgns.rs @@ -1,10 +1,11 @@ -use ndarray::{s, Array1, Array2}; +use impl_new_derive::ImplNew; +use ndarray::{Array1, Array2}; use crate::stochastic::{Sampling, Sampling2D}; use super::fgn::FGN; -#[derive(Default)] +#[derive(ImplNew)] pub struct CFGNS { pub hurst: f64, pub rho: f64, @@ -14,22 +15,6 @@ pub struct CFGNS { pub fgn: FGN, } -impl CFGNS { - #[must_use] - pub fn new(params: &Self) -> Self { - let fgn = FGN::new(params.hurst, params.n, params.t, params.m); - - Self { - hurst: params.hurst, - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - fgn, - } - } -} - impl Sampling2D for CFGNS { fn sample(&self) -> [Array1; 2] { assert!( @@ -41,19 +26,16 @@ impl Sampling2D for CFGNS { "Correlation coefficient must be in [-1, 1]" ); - let mut cfgns = Array2::::zeros((2, self.n + 1)); + let mut cfgns = Array2::::zeros((2, self.n)); let fgn1 = self.fgn.sample(); let fgn2 = self.fgn.sample(); - for i in 1..=self.n { + for i in 1..self.n { cfgns[[0, i]] = fgn1[i - 1]; cfgns[[1, i]] = self.rho * fgn1[i - 1] + (1.0 - self.rho.powi(2)).sqrt() * fgn2[i - 1]; } - [ - cfgns.row(0).slice(s![..self.n()]).into_owned(), - cfgns.row(1).slice(s![..self.n()]).into_owned(), - ] + [cfgns.row(0).into_owned(), cfgns.row(1).into_owned()] } /// Number of time steps diff --git a/src/stochastic/noise/cgns.rs b/src/stochastic/noise/cgns.rs index 23003d0..1a6f07d 100644 --- a/src/stochastic/noise/cgns.rs +++ b/src/stochastic/noise/cgns.rs @@ -1,10 +1,11 @@ -use ndarray::{s, Array1, Array2}; +use impl_new_derive::ImplNew; +use ndarray::{Array1, Array2}; use ndarray_rand::RandomExt; use rand_distr::Normal; use crate::stochastic::Sampling2D; -#[derive(Default)] +#[derive(ImplNew)] pub struct CGNS { pub rho: f64, pub n: usize, @@ -12,18 +13,6 @@ pub struct CGNS { pub m: Option, } -impl CGNS { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - } - } -} - impl Sampling2D for CGNS { fn sample(&self) -> [Array1; 2] { assert!( @@ -32,19 +21,16 @@ impl Sampling2D for CGNS { ); let dt = self.t.unwrap_or(1.0) / self.n as f64; - let mut cgns = Array2::::zeros((2, self.n + 1)); + let mut cgns = Array2::::zeros((2, self.n)); let gn1 = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); let gn2 = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - for i in 1..=self.n { + for i in 1..self.n { cgns[[0, i]] = gn1[i - 1]; cgns[[1, i]] = self.rho * gn1[i - 1] + (1.0 - self.rho.powi(2)).sqrt() * gn2[i - 1]; } - [ - cgns.row(0).slice(s![..self.n()]).into_owned(), - cgns.row(1).slice(s![..self.n()]).into_owned(), - ] + [cgns.row(0).into_owned(), cgns.row(1).into_owned()] } /// Number of time steps diff --git a/src/stochastic/noise/fgn.rs b/src/stochastic/noise/fgn.rs index c988a97..54a183a 100644 --- a/src/stochastic/noise/fgn.rs +++ b/src/stochastic/noise/fgn.rs @@ -19,12 +19,6 @@ pub struct FGN { pub fft_handler: Arc>, } -impl Default for FGN { - fn default() -> Self { - Self::new(0.7, 1024, None, None) - } -} - impl FGN { #[must_use] pub fn new(hurst: f64, n: usize, t: Option, m: Option) -> Self { @@ -108,14 +102,32 @@ impl Sampling for FGN { #[cfg(test)] mod tests { - - use crate::plot_1d; + use crate::{plot_1d, stochastic::N}; use super::*; + #[test] + fn fgn_length_equals_n() { + let fbm = FGN::new(0.7, N, Some(1.0), None); + assert_eq!(fbm.sample().len(), N); + } + + #[test] + #[ignore = "Not implemented"] + fn fgn_starts_with_x0() { + unimplemented!() + } + #[test] fn fgn_plot() { - let fgn = FGN::new(0.7, 1000, Some(1.0), None); - plot_1d!(fgn.sample(), "FGN"); + let fbm = FGN::new(0.7, N, Some(1.0), None); + plot_1d!(fbm.sample(), "Fractional Brownian Motion (H = 0.7)"); + } + + #[test] + #[ignore = "Not implemented"] + #[cfg(feature = "malliavin")] + fn fgn_malliavin() { + unimplemented!(); } } diff --git a/src/stochastic/process/bm.rs b/src/stochastic/process/bm.rs index c93bd6b..394757a 100644 --- a/src/stochastic/process/bm.rs +++ b/src/stochastic/process/bm.rs @@ -1,35 +1,25 @@ +use impl_new_derive::ImplNew; use ndarray::{s, Array1}; use ndarray_rand::RandomExt; use rand_distr::Normal; use crate::stochastic::Sampling; -#[derive(Default)] +#[derive(ImplNew)] pub struct BM { pub n: usize, pub t: Option, pub m: Option, } -impl BM { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - n: params.n, - t: params.t, - m: params.m, - } - } -} - impl Sampling for BM { fn sample(&self) -> Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut bm = Array1::::zeros(self.n + 1); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); + let mut bm = Array1::::zeros(self.n); bm.slice_mut(s![1..]).assign(&gn); - for i in 1..=self.n { + for i in 1..self.n { bm[i] += bm[i - 1]; } diff --git a/src/stochastic/process/cbms.rs b/src/stochastic/process/cbms.rs index 8a605d8..1157c5e 100644 --- a/src/stochastic/process/cbms.rs +++ b/src/stochastic/process/cbms.rs @@ -1,8 +1,9 @@ +use impl_new_derive::ImplNew; use ndarray::{Array1, Array2}; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; -#[derive(Default)] +#[derive(ImplNew)] pub struct CBMS { pub rho: f64, pub n: usize, @@ -11,26 +12,6 @@ pub struct CBMS { pub cgns: CGNS, } -impl CBMS { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - cgns, - } - } -} - impl Sampling2D for CBMS { fn sample(&self) -> [Array1; 2] { assert!( @@ -38,10 +19,10 @@ impl Sampling2D for CBMS { "Correlation coefficient must be in [-1, 1]" ); - let mut bms = Array2::::zeros((2, self.n + 1)); + let mut bms = Array2::::zeros((2, self.n)); let [cgn1, cgn2] = self.cgns.sample(); - for i in 1..=self.n { + for i in 1..self.n { bms[[0, i]] = bms[[0, i - 1]] + cgn1[i - 1]; bms[[1, i]] = bms[[1, i - 1]] + self.rho * cgn1[i - 1] + (1.0 - self.rho.powi(2)).sqrt() * cgn2[i - 1]; diff --git a/src/stochastic/process/ccustom.rs b/src/stochastic/process/ccustom.rs index 2d7384c..57a6095 100644 --- a/src/stochastic/process/ccustom.rs +++ b/src/stochastic/process/ccustom.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::{Array1, Axis}; use rand::thread_rng; @@ -5,7 +6,7 @@ use crate::stochastic::{ProcessDistribution, Sampling, Sampling3D}; use super::customjt::CustomJt; -#[derive(Default)] +#[derive(ImplNew)] pub struct CompoundCustom where D: ProcessDistribution, @@ -19,31 +20,6 @@ where pub customjt: CustomJt, } -impl CompoundCustom -where - D: ProcessDistribution, - E: ProcessDistribution, -{ - #[must_use] - pub fn new(params: &Self) -> Self { - let customjt = CustomJt::new(&CustomJt { - n: params.n, - t_max: params.t_max, - m: params.m, - distribution: params.jump_times_distribution, - }); - - Self { - n: params.n, - t_max: params.t_max, - m: params.m, - jumps_distribution: params.jumps_distribution, - jump_times_distribution: params.jump_times_distribution, - customjt, - } - } -} - impl Sampling3D for CompoundCustom where D: ProcessDistribution, @@ -56,7 +32,7 @@ where let p = self.customjt.sample(); let mut jumps = Array1::::zeros(self.n.unwrap_or(p.len())); - for i in 1..=p.len() { + for i in 1..p.len() { jumps[i] = self.jumps_distribution.sample(&mut thread_rng()); } diff --git a/src/stochastic/process/cfbms.rs b/src/stochastic/process/cfbms.rs index e4e4be8..ba93cfd 100644 --- a/src/stochastic/process/cfbms.rs +++ b/src/stochastic/process/cfbms.rs @@ -1,11 +1,10 @@ +use impl_new_derive::ImplNew; use ndarray::{Array1, Array2}; use crate::stochastic::{noise::cfgns::CFGNS, Sampling2D}; -#[derive(Default)] -pub struct Cfbms { - pub hurst1: f64, - pub hurst2: Option, +#[derive(ImplNew)] +pub struct CFBMS { pub rho: f64, pub n: usize, pub t: Option, @@ -13,52 +12,17 @@ pub struct Cfbms { pub cfgns: CFGNS, } -impl Cfbms { - #[must_use] - pub fn new(params: &Self) -> Self { - let cfgns = CFGNS::new(&CFGNS { - hurst: params.hurst1, - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - ..Default::default() - }); - - Self { - hurst1: params.hurst1, - hurst2: params.hurst2, - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - cfgns, - } - } -} - -impl Sampling2D for Cfbms { +impl Sampling2D for CFBMS { fn sample(&self) -> [Array1; 2] { - assert!( - (0.0..=1.0).contains(&self.hurst1), - "Hurst parameter for the first fBM must be in (0, 1)" - ); - - if let Some(hurst2) = self.hurst2 { - assert!( - (0.0..=1.0).contains(&hurst2), - "Hurst parameter for the second fBM must be in (0, 1)" - ); - } assert!( (-1.0..=1.0).contains(&self.rho), "Correlation coefficient must be in [-1, 1]" ); - let mut fbms = Array2::::zeros((2, self.n + 1)); + let mut fbms = Array2::::zeros((2, self.n)); let [fgn1, fgn2] = self.cfgns.sample(); - for i in 1..=self.n { + for i in 1..self.n { fbms[[0, i]] = fbms[[0, i - 1]] + fgn1[i - 1]; fbms[[1, i]] = fbms[[1, i - 1]] + self.rho * fgn1[i - 1] + (1.0 - self.rho.powi(2)).sqrt() * fgn2[i - 1]; diff --git a/src/stochastic/process/cpoisson.rs b/src/stochastic/process/cpoisson.rs index 3b87a1c..b076665 100644 --- a/src/stochastic/process/cpoisson.rs +++ b/src/stochastic/process/cpoisson.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::{Array1, Axis}; use rand::thread_rng; @@ -5,7 +6,7 @@ use crate::stochastic::{ProcessDistribution, Sampling, Sampling3D}; use super::poisson::Poisson; -#[derive(Default)] +#[derive(ImplNew)] pub struct CompoundPoisson where D: ProcessDistribution, @@ -18,27 +19,6 @@ where pub poisson: Poisson, } -impl CompoundPoisson { - #[must_use] - pub fn new(params: &Self) -> Self { - let poisson = Poisson::new(&Poisson { - lambda: params.lambda, - n: params.n, - t_max: params.t_max, - m: params.m, - }); - - Self { - n: params.n, - lambda: params.lambda, - t_max: params.t_max, - m: params.m, - distribution: params.distribution, - poisson, - } - } -} - impl Sampling3D for CompoundPoisson { fn sample(&self) -> [Array1; 3] { if self.n.is_none() && self.t_max.is_none() { @@ -46,8 +26,8 @@ impl Sampling3D for CompoundPoisson { } let poisson = self.poisson.sample(); - let mut jumps = Array1::::zeros(poisson.len() + 1); - for i in 1..=poisson.len() { + let mut jumps = Array1::::zeros(poisson.len()); + for i in 1..poisson.len() { jumps[i] = self.distribution.sample(&mut thread_rng()); } diff --git a/src/stochastic/process/customjt.rs b/src/stochastic/process/customjt.rs index f283787..52df028 100644 --- a/src/stochastic/process/customjt.rs +++ b/src/stochastic/process/customjt.rs @@ -1,10 +1,11 @@ +use impl_new_derive::ImplNew; use ndarray::{Array0, Array1, Axis, Dim}; use ndarray_rand::RandomExt; use rand::thread_rng; use crate::stochastic::{ProcessDistribution, Sampling}; -#[derive(Default)] +#[derive(ImplNew)] pub struct CustomJt where D: ProcessDistribution, @@ -15,24 +16,12 @@ where pub distribution: D, } -impl CustomJt { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - n: params.n, - t_max: params.t_max, - m: params.m, - distribution: params.distribution, - } - } -} - impl Sampling for CustomJt { fn sample(&self) -> Array1 { if let Some(n) = self.n { let random = Array1::random(n, self.distribution); - let mut x = Array1::::zeros(n + 1); - for i in 1..=n { + let mut x = Array1::::zeros(n); + for i in 1..n { x[i] = x[i - 1] + random[i - 1]; } diff --git a/src/stochastic/process/fbm.rs b/src/stochastic/process/fbm.rs index 29b6865..6730003 100644 --- a/src/stochastic/process/fbm.rs +++ b/src/stochastic/process/fbm.rs @@ -1,14 +1,15 @@ #[cfg(feature = "malliavin")] use std::sync::Mutex; +use impl_new_derive::ImplNew; use ndarray::{s, Array1}; #[cfg(feature = "malliavin")] use statrs::function::gamma; use crate::stochastic::{noise::fgn::FGN, Sampling}; -#[derive(Default)] -pub struct Fbm { +#[derive(ImplNew)] +pub struct FBM { pub hurst: f64, pub n: usize, pub t: Option, @@ -20,32 +21,10 @@ pub struct Fbm { malliavin: Mutex>>, } -impl Fbm { - pub fn new(params: &Self) -> Self { - if !(0.0..=1.0).contains(¶ms.hurst) { - panic!("Hurst parameter must be in (0, 1)") - } - - let fgn = FGN::new(params.hurst, params.n, params.t, None); - - Self { - hurst: params.hurst, - t: params.t, - n: params.n, - m: params.m, - fgn, - #[cfg(feature = "malliavin")] - calculate_malliavin: Some(false), - #[cfg(feature = "malliavin")] - malliavin: Mutex::new(None), - } - } -} - -impl Sampling for Fbm { +impl Sampling for FBM { fn sample(&self) -> Array1 { let fgn = self.fgn.sample(); - let mut fbm = Array1::::zeros(self.n + 1); + let mut fbm = Array1::::zeros(self.n); fbm.slice_mut(s![1..]).assign(&fgn); for i in 1..=self.n { @@ -54,9 +33,9 @@ impl Sampling for Fbm { #[cfg(feature = "malliavin")] if self.calculate_malliavin.is_some() && self.calculate_malliavin.unwrap() { - let mut malliavin = Array1::zeros(self.n + 1); - let dt = self.t.unwrap_or(1.0) / self.n as f64; - for i in 1..=self.n { + let mut malliavin = Array1::zeros(self.n); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + for i in 1..self.n { malliavin[i] = 1.0 / (gamma::gamma(self.hurst + 0.5)) * dt.powf(self.hurst - 0.5); } @@ -91,34 +70,70 @@ impl Sampling for Fbm { #[cfg(test)] mod tests { - use ndarray::Axis; - use plotly::{common::Line, Plot, Scatter}; + use crate::{plot_1d, plot_2d, stochastic::N}; use super::*; #[test] - fn plot() { - let fbm = Fbm::new(&Fbm { - hurst: 0.9, - n: 1000, - t: Some(1.0), - m: Some(1), - ..Default::default() - }); - - let mut plot = Plot::new(); - let d = fbm.sample_par(); - for data in d.axis_iter(Axis(0)) { - let trace = Scatter::new((0..data.len()).collect::>(), data.to_vec()) - .mode(plotly::common::Mode::Lines) - .line( - Line::new() - .color("orange") - .shape(plotly::common::LineShape::Linear), - ) - .name("Fbm"); - plot.add_trace(trace); - } - plot.show(); + fn fbm_length_equals_n() { + let fbm = FBM::new( + 0.7, + N, + Some(1.0), + None, + FGN::new(0.7, N, Some(1.0), None), + None, + ); + + assert_eq!(fbm.sample().len(), N); + } + + #[test] + fn fbm_starts_with_x0() { + let fbm = FBM::new( + 0.7, + N, + Some(1.0), + None, + FGN::new(0.7, N, Some(1.0), None), + None, + ); + + assert_eq!(fbm.sample()[0], 0.0); + } + + #[test] + fn fbm_plot() { + let fbm = FBM::new( + 0.7, + N, + Some(1.0), + None, + FGN::new(0.7, N, Some(1.0), None), + None, + ); + + plot_1d!(fbm.sample(), "Fractional Brownian Motion (H = 0.7)"); + } + + #[test] + #[cfg(feature = "malliavin")] + fn fbm_malliavin() { + let fbm = FBM::new( + 0.7, + N, + Some(1.0), + None, + FGN::new(0.7, N, Some(1.0), None), + Some(true), + ); + let process = fbm.sample(); + let malliavin = fbm.malliavin(); + plot_2d!( + process, + "Fractional Brownian Motion (H = 0.7)", + malliavin, + "Malliavin derivative of Fractional Brownian Motion (H = 0.7)" + ); } } diff --git a/src/stochastic/volatility/bergomi.rs b/src/stochastic/volatility/bergomi.rs index d976ceb..d07b765 100644 --- a/src/stochastic/volatility/bergomi.rs +++ b/src/stochastic/volatility/bergomi.rs @@ -1,8 +1,9 @@ +use impl_new_derive::ImplNew; use ndarray::{s, Array1}; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; -#[derive(Default)] +#[derive(ImplNew)] pub struct Bergomi { pub nu: f64, pub v0: Option, @@ -15,41 +16,17 @@ pub struct Bergomi { pub cgns: CGNS, } -impl Bergomi { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - nu: params.nu, - v0: params.v0, - s0: params.s0, - r: params.r, - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - cgns, - } - } -} - impl Sampling2D for Bergomi { fn sample(&self) -> [Array1; 2] { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let [cgn1, z] = self.cgns.sample(); - let mut s = Array1::::zeros(self.n + 1); - let mut v2 = Array1::::zeros(self.n + 1); + let mut s = Array1::::zeros(self.n); + let mut v2 = Array1::::zeros(self.n); s[0] = self.s0.unwrap_or(100.0); v2[0] = self.v0.unwrap_or(1.0).powi(2); - for i in 0..=self.n { + for i in 0..self.n { s[i] = s[i - 1] + self.r * s[i - 1] * dt + v2[i - 1].sqrt() * s[i - 1] * cgn1[i - 1]; let sum_z = z.slice(s![..i]).sum(); diff --git a/src/stochastic/volatility/fheston.rs b/src/stochastic/volatility/fheston.rs index 6039bdb..165b4e7 100644 --- a/src/stochastic/volatility/fheston.rs +++ b/src/stochastic/volatility/fheston.rs @@ -1,3 +1,4 @@ +use impl_new_derive::ImplNew; use ndarray::Array1; use ndarray_rand::RandomExt; use rand_distr::Normal; @@ -5,7 +6,7 @@ use statrs::function::gamma::gamma; use crate::stochastic::Sampling; -#[derive(Default)] +#[derive(ImplNew)] pub struct RoughHeston { pub v0: Option, pub theta: f64, @@ -19,37 +20,19 @@ pub struct RoughHeston { pub m: Option, } -impl RoughHeston { - #[must_use] - pub fn new(params: &Self) -> Self { - Self { - v0: params.v0, - theta: params.theta, - kappa: params.kappa, - nu: params.nu, - hurst: params.hurst, - c1: params.c1, - c2: params.c2, - t: params.t, - n: params.n, - m: params.m, - } - } -} - impl Sampling for RoughHeston { fn sample(&self) -> ndarray::Array1 { - let dt = self.t.unwrap_or(1.0) / self.n as f64; - let gn = Array1::random(self.n, Normal::new(0.0, dt.sqrt()).unwrap()); - let mut yt = Array1::::zeros(self.n + 1); - let mut zt = Array1::::zeros(self.n + 1); - let mut v2 = Array1::zeros(self.n + 1); + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; + let gn = Array1::random(self.n - 1, Normal::new(0.0, dt.sqrt()).unwrap()); + let mut yt = Array1::::zeros(self.n); + let mut zt = Array1::::zeros(self.n); + let mut v2 = Array1::zeros(self.n); yt[0] = self.theta + (self.v0.unwrap_or(1.0).powi(2) - self.theta) * (-self.kappa * 0.0).exp(); zt[0] = 0.0; // Initial condition for Z_t, typically 0 for such integrals. v2[0] = self.v0.unwrap_or(1.0).powi(2); - for i in 1..=self.n { + for i in 1..self.n { let t = dt * i as f64; yt[i] = self.theta + (yt[i - 1] - self.theta) * (-self.kappa * dt).exp(); zt[i] = zt[i - 1] * (-self.kappa * dt).exp() + (v2[i - 1].powi(2)).sqrt() * gn[i - 1]; @@ -79,41 +62,3 @@ impl Sampling for RoughHeston { self.m } } - -#[cfg(test)] -mod tests { - use plotly::{common::Line, Plot, Scatter}; - - use super::*; - - #[test] - fn test_rough_heston() { - let params = RoughHeston { - v0: Some(1.2), - theta: 1.0, - kappa: 1.0, - nu: 0.2, - hurst: 0.75, - c1: Some(1.0), - c2: Some(1.0), - t: Some(1.0), - n: 1000, - m: Some(1000), - }; - - let rh = RoughHeston::new(¶ms); - let _sample = rh.sample(); - - let mut plot = Plot::new(); - let trace = Scatter::new((0.._sample.len()).collect::>(), _sample.to_vec()) - .mode(plotly::common::Mode::Lines) - .line( - Line::new() - .color("orange") - .shape(plotly::common::LineShape::Linear), - ) - .name("Fbm"); - plot.add_trace(trace); - plot.show(); - } -} diff --git a/src/stochastic/volatility/heston.rs b/src/stochastic/volatility/heston.rs index ecd14ef..a64db2c 100644 --- a/src/stochastic/volatility/heston.rs +++ b/src/stochastic/volatility/heston.rs @@ -1,13 +1,14 @@ #[cfg(feature = "malliavin")] use std::sync::Mutex; +use impl_new_derive::ImplNew; use ndarray::Array1; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; use super::HestonPow; -#[derive(Default)] +#[derive(ImplNew)] pub struct Heston { /// Initial stock price @@ -49,52 +50,18 @@ pub struct Heston { malliavin_of_price: Mutex>>, } -impl Heston { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - s0: params.s0, - v0: params.v0, - kappa: params.kappa, - theta: params.theta, - sigma: params.sigma, - rho: params.rho, - mu: params.mu, - n: params.n, - t: params.t, - pow: params.pow, - use_sym: params.use_sym, - m: params.m, - cgns, - #[cfg(feature = "malliavin")] - calculate_malliavin: Some(false), - #[cfg(feature = "malliavin")] - malliavin_of_vol: Mutex::new(None), - #[cfg(feature = "malliavin")] - malliavin_of_price: Mutex::new(None), - } - } -} - impl Sampling2D for Heston { fn sample(&self) -> [Array1; 2] { let [cgn1, cgn2] = self.cgns.sample(); - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; - let mut s = Array1::::zeros(self.n + 1); - let mut v = Array1::::zeros(self.n + 1); + let mut s = Array1::::zeros(self.n); + let mut v = Array1::::zeros(self.n); s[0] = self.s0.unwrap_or(0.0); v[0] = self.v0.unwrap_or(0.0); - for i in 1..=self.n { + for i in 1..self.n { s[i] = s[i - 1] + self.mu * s[i - 1] * dt + s[i - 1] * v[i - 1].sqrt() * cgn1[i - 1]; let dv = self.kappa * (self.theta - v[i - 1]) * dt @@ -174,56 +141,3 @@ impl Sampling2D for Heston { ] } } - -#[cfg(test)] -mod tests { - use plotly::{common::Line, Plot, Scatter}; - - use super::*; - - #[test] - fn plot() { - let heston = Heston::new(&Heston { - s0: Some(0.05), - v0: Some(0.04), - kappa: 2.0, - theta: 0.04, - sigma: 0.1, - rho: -0.7, - mu: 0.05, - n: 1000, - t: Some(1.0), - pow: HestonPow::default(), - use_sym: Some(true), - m: Some(1), - cgns: CGNS::default(), - #[cfg(feature = "malliavin")] - calculate_malliavin: Some(false), - #[cfg(feature = "malliavin")] - malliavin_of_vol: Mutex::new(None), - #[cfg(feature = "malliavin")] - malliavin_of_price: Mutex::new(None), - }); - let mut plot = Plot::new(); - let [s, v] = heston.sample(); - let price = Scatter::new((0..s.len()).collect::>(), s.to_vec()) - .mode(plotly::common::Mode::Lines) - .line( - Line::new() - .color("blue") - .shape(plotly::common::LineShape::Linear), - ) - .name("Heston"); - plot.add_trace(price); - let vol = Scatter::new((0..v.len()).collect::>(), v.to_vec()) - .mode(plotly::common::Mode::Lines) - .line( - Line::new() - .color("orange") - .shape(plotly::common::LineShape::Linear), - ) - .name("Heston"); - plot.add_trace(vol); - plot.show(); - } -} diff --git a/src/stochastic/volatility/rbergomi.rs b/src/stochastic/volatility/rbergomi.rs index 6251c44..6cb322b 100644 --- a/src/stochastic/volatility/rbergomi.rs +++ b/src/stochastic/volatility/rbergomi.rs @@ -1,8 +1,9 @@ +use impl_new_derive::ImplNew; use ndarray::{s, Array1}; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; -#[derive(Default)] +#[derive(ImplNew)] pub struct RoughBergomi { pub hurst: f64, pub nu: f64, @@ -16,38 +17,13 @@ pub struct RoughBergomi { pub cgns: CGNS, } -impl RoughBergomi { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - hurst: params.hurst, - nu: params.nu, - v0: params.v0, - s0: params.s0, - r: params.r, - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - cgns, - } - } -} - impl Sampling2D for RoughBergomi { fn sample(&self) -> [Array1; 2] { - let dt = self.t.unwrap_or(1.0) / self.n as f64; + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; let [cgn1, z] = self.cgns.sample(); - let mut s = Array1::::zeros(self.n + 1); - let mut v2 = Array1::::zeros(self.n + 1); + let mut s = Array1::::zeros(self.n); + let mut v2 = Array1::::zeros(self.n); s[0] = self.s0.unwrap_or(100.0); v2[0] = self.v0.unwrap_or(1.0).powi(2); diff --git a/src/stochastic/volatility/sabr.rs b/src/stochastic/volatility/sabr.rs index 4067881..e2c3985 100644 --- a/src/stochastic/volatility/sabr.rs +++ b/src/stochastic/volatility/sabr.rs @@ -1,11 +1,12 @@ #[cfg(feature = "malliavin")] use std::sync::Mutex; +use impl_new_derive::ImplNew; use ndarray::Array1; use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; -#[derive(Default)] +#[derive(ImplNew)] pub struct Sabr { pub alpha: f64, @@ -25,36 +26,6 @@ pub struct Sabr { malliavin_of_price: Mutex>>, } -impl Sabr { - #[must_use] - pub fn new(params: &Self) -> Self { - let cgns = CGNS::new(&CGNS { - rho: params.rho, - n: params.n, - t: params.t, - m: params.m, - }); - - Self { - alpha: params.alpha, - beta: params.beta, - rho: params.rho, - n: params.n, - f0: params.f0, - v0: params.v0, - t: params.t, - m: params.m, - cgns, - #[cfg(feature = "malliavin")] - calculate_malliavin: Some(false), - #[cfg(feature = "malliavin")] - malliavin_of_vol: Mutex::new(None), - #[cfg(feature = "malliavin")] - malliavin_of_price: Mutex::new(None), - } - } -} - impl Sampling2D for Sabr { fn sample(&self) -> [Array1; 2] { let [cgn1, cgn2] = self.cgns.sample(); diff --git a/stochastic-rs-macros/Cargo.lock b/stochastic-rs-macros/Cargo.lock deleted file mode 100644 index 61e7e8c..0000000 --- a/stochastic-rs-macros/Cargo.lock +++ /dev/null @@ -1,47 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "stochastic-rs-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/stochastic-rs-macros/Cargo.toml b/stochastic-rs-macros/Cargo.toml deleted file mode 100644 index f74d3eb..0000000 --- a/stochastic-rs-macros/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "stochastic-rs-macros" -version = "0.1.0" -edition = "2021" - -[dependencies] -proc-macro2 = "1.0.86" -quote = "1.0.37" -syn = "2.0.79" - -[lib] -proc-macro = true \ No newline at end of file diff --git a/stochastic-rs-macros/src/lib.rs b/stochastic-rs-macros/src/lib.rs deleted file mode 100644 index cf9aa0a..0000000 --- a/stochastic-rs-macros/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, FieldsNamed, Visibility}; - -#[proc_macro_derive(ImplNew)] -pub fn derive_impl_new(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let name = input.ident; - - let fields = if let syn::Data::Struct(data) = input.data { - if let syn::Fields::Named(FieldsNamed { named, .. }) = data.fields { - named - } else { - panic!("`ImplNew` macro can only be used on structs with named fields"); - } - } else { - panic!("`ImplNew` macro can only be used on structs"); - }; - - let pub_fields = fields - .iter() - .filter(|f| matches!(f.vis, Visibility::Public(_))) - .collect::>(); - - let non_pub_fields: Vec<_> = fields - .iter() - .filter(|f| !matches!(f.vis, Visibility::Public(_))) - .collect::>(); - - let pub_field_names = pub_fields.iter().map(|f| &f.ident).collect::>(); - let pub_field_types = pub_fields.iter().map(|f| &f.ty).collect::>(); - let non_pub_field_names = non_pub_fields.iter().map(|f| &f.ident).collect::>(); - - let expanded = quote! { - impl #name { - #[must_use] - pub fn new(#(#pub_field_names: #pub_field_types),*) -> Self { - Self { - #(#pub_field_names),*, - #(#non_pub_field_names: Default::default()),* - } - } - } - }; - - TokenStream::from(expanded) -}