From 40f9e28d887686f525dc2bf3fce6c14d89fc778c Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Wed, 2 Oct 2024 02:18:20 +0200 Subject: [PATCH] feat: add hull_white and hull_white_2f --- src/ai/fou/fou_lstm_datasets.rs | 6 +- src/main.rs | 6 +- src/quant.rs | 2 + src/quant/bonds.rs | 3 + src/quant/bonds/cir.rs | 40 +++++++++++++ src/quant/bonds/hull_white.rs | 40 +++++++++++++ src/quant/bonds/vasicek.rs | 42 +++++++++++++ src/quant/options/bsm.rs | 66 ++++++++++++-------- src/quant/trait.rs | 52 ++++++++++++++++ src/quant/volatility.rs | 16 +---- src/quant/volatility/heston.rs | 4 +- src/stochastic/diffusion/cir.rs | 9 ++- src/stochastic/diffusion/fcir.rs | 12 ++-- src/stochastic/diffusion/fgbm.rs | 12 ++-- src/stochastic/diffusion/fjacobi.rs | 12 ++-- src/stochastic/diffusion/fou.rs | 12 ++-- src/stochastic/diffusion/gbm.rs | 8 +-- src/stochastic/diffusion/ou.rs | 6 +- src/stochastic/interest.rs | 3 + src/stochastic/interest/cir.rs | 1 + src/stochastic/interest/duffie_kan.rs | 6 +- src/stochastic/interest/fvasicek.rs | 12 ++-- src/stochastic/interest/hull_white.rs | 59 ++++++++++++++++++ src/stochastic/interest/hull_white_2f.rs | 76 ++++++++++++++++++++++++ src/stochastic/interest/vasicek.rs | 6 +- src/stochastic/jump/bates.rs | 6 +- src/stochastic/jump/ig.rs | 8 +-- src/stochastic/jump/jump_fou.rs | 14 ++--- src/stochastic/jump/nig.rs | 6 +- src/stochastic/jump/vg.rs | 8 +-- src/stochastic/noise/cfgns.rs | 12 ++-- src/stochastic/noise/cgns.rs | 6 +- src/stochastic/noise/fgn.rs | 10 ++-- src/stochastic/process/bm.rs | 6 +- src/stochastic/process/cbms.rs | 12 ++-- src/stochastic/process/cfbms.rs | 6 +- src/stochastic/process/fbm.rs | 6 +- src/stochastic/volatility/bergomi.rs | 6 +- src/stochastic/volatility/heston.rs | 8 +-- src/stochastic/volatility/rbergomi.rs | 6 +- src/stochastic/volatility/sabr.rs | 6 +- 41 files changed, 478 insertions(+), 159 deletions(-) create mode 100644 src/quant/bonds.rs create mode 100644 src/quant/bonds/cir.rs create mode 100644 src/quant/bonds/hull_white.rs create mode 100644 src/quant/bonds/vasicek.rs create mode 100644 src/quant/trait.rs create mode 100644 src/stochastic/interest/cir.rs create mode 100644 src/stochastic/interest/hull_white.rs create mode 100644 src/stochastic/interest/hull_white_2f.rs diff --git a/src/ai/fou/fou_lstm_datasets.rs b/src/ai/fou/fou_lstm_datasets.rs index 5be2d0f..7df997d 100644 --- a/src/ai/fou/fou_lstm_datasets.rs +++ b/src/ai/fou/fou_lstm_datasets.rs @@ -8,7 +8,7 @@ use ndarray::{s, Array1}; use ndarray_rand::RandomExt; use rand_distr::Uniform; -use crate::stochastic::{diffusion::fou::Fou, Sampling}; +use crate::stochastic::{diffusion::fou::FOU, Sampling}; pub fn test_vasicek_1_d( epoch_size: usize, @@ -34,7 +34,7 @@ pub fn test_vasicek_1_d( for idx in 0..epoch_size { let hurst = hursts[idx]; let theta = thetas[idx]; - let fou = Fou::new(&Fou { + let fou = FOU::new(&FOU { hurst, mu, sigma, @@ -88,7 +88,7 @@ pub fn test_vasicek_2_d( for idx in 0..epoch_size { let hurst = hursts[idx]; let theta = thetas[idx]; - let fou = Fou::new(&Fou { + let fou = FOU::new(&FOU { hurst, mu, sigma, diff --git a/src/main.rs b/src/main.rs index 634f346..319c93f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -use stochastic_rs::stochastic::{noise::fgn::Fgn, Sampling}; +use stochastic_rs::stochastic::{noise::fgn::FGN, Sampling}; fn main() { - let fbm = Fgn::new(0.9, 10000, None, Some(10000)); + let fbm = FGN::new(0.9, 10000, None, Some(10000)); let start = std::time::Instant::now(); for _ in 0..10000 { @@ -14,7 +14,7 @@ fn main() { duration.as_secs_f32() ); - let fbm = Fgn::new(0.9, 10000, None, Some(10000)); + let fbm = FGN::new(0.9, 10000, None, Some(10000)); let start = std::time::Instant::now(); for _ in 0..10000 { diff --git a/src/quant.rs b/src/quant.rs index 6a923ef..93806df 100644 --- a/src/quant.rs +++ b/src/quant.rs @@ -1,4 +1,6 @@ +pub mod bonds; pub mod options; +pub mod r#trait; pub mod volatility; pub mod yahoo; diff --git a/src/quant/bonds.rs b/src/quant/bonds.rs new file mode 100644 index 0000000..d33fad4 --- /dev/null +++ b/src/quant/bonds.rs @@ -0,0 +1,3 @@ +pub mod cir; +pub mod hull_white; +pub mod vasicek; diff --git a/src/quant/bonds/cir.rs b/src/quant/bonds/cir.rs new file mode 100644 index 0000000..ed8d148 --- /dev/null +++ b/src/quant/bonds/cir.rs @@ -0,0 +1,40 @@ +use crate::quant::r#trait::Price; + +/// CIR model for zero-coupon bond pricing +/// dR(t) = theta(mu - R(t))dt + sigma * sqrt(R(t))dW(t) +/// where R(t) is the short rate. +#[derive(Default, Debug)] +pub struct CIR { + /// Initial short rate + pub r_t: f64, + /// Long-term mean of the short rate + pub theta: f64, + /// Mean reversion speed + pub mu: f64, + /// Volatility + pub sigma: f64, + /// Maturity of the bond in days + pub tau: f64, + /// Evaluation date + pub eval: Option, + /// Expiration date + pub expiration: Option, +} + +impl Price for CIR { + fn price(&self) -> f64 { + todo!() + } + + fn tau(&self) -> Option { + Some(self.tau) + } + + fn eval(&self) -> Option { + self.eval + } + + fn expiration(&self) -> Option { + self.expiration + } +} diff --git a/src/quant/bonds/hull_white.rs b/src/quant/bonds/hull_white.rs new file mode 100644 index 0000000..8a011dd --- /dev/null +++ b/src/quant/bonds/hull_white.rs @@ -0,0 +1,40 @@ +use crate::quant::r#trait::Price; + +/// Hull-White model for zero-coupon bond pricing +/// dR(t) = (theta(t) - aR(t))dt + sigma(t)dW(t) +/// where R(t) is the short rate. +#[derive(Debug)] +pub struct HullWhite { + /// Short rate + pub r_t: f64, + /// Long-term mean of the short rate + pub theta: fn(f64) -> f64, + /// Mean reversion speed + pub alpha: f64, + /// Volatility + pub sigma: f64, + /// Maturity of the bond in days + pub tau: f64, + /// Evaluation date + pub eval: Option, + /// Expiration date + pub expiration: Option, +} + +impl Price for HullWhite { + fn price(&self) -> f64 { + 0.0 + } + + fn tau(&self) -> Option { + Some(self.tau) + } + + fn eval(&self) -> Option { + self.eval + } + + fn expiration(&self) -> Option { + self.expiration + } +} diff --git a/src/quant/bonds/vasicek.rs b/src/quant/bonds/vasicek.rs new file mode 100644 index 0000000..53d22b8 --- /dev/null +++ b/src/quant/bonds/vasicek.rs @@ -0,0 +1,42 @@ +use crate::quant::r#trait::Price; + +/// Vasicek model for zero-coupon bond pricing +/// dR(t) = theta(mu - R(t))dt + sigma dW(t) +/// where R(t) is the short rate. +#[derive(Default, Debug)] +pub struct Vasicek { + /// Short rate + pub r_t: f64, + /// Long-term mean of the short rate + pub theta: f64, + /// Mean reversion speed + pub mu: f64, + /// Volatility + pub sigma: f64, + /// Maturity of the bond in days + pub tau: f64, + /// Evaluation date + pub eval: Option, + /// Expiration date + pub expiration: Option, +} + +impl Price for Vasicek { + fn price(&self) -> f64 { + // Itt definiálhatod a Vasicek modell árképzési képletét + // Placeholder érték visszaadása + 0.0 + } + + fn tau(&self) -> Option { + Some(self.tau) + } + + fn eval(&self) -> Option { + self.eval + } + + fn expiration(&self) -> Option { + self.expiration + } +} diff --git a/src/quant/options/bsm.rs b/src/quant/options/bsm.rs index 870f690..af6ef60 100644 --- a/src/quant/options/bsm.rs +++ b/src/quant/options/bsm.rs @@ -1,9 +1,9 @@ use statrs::distribution::{Continuous, ContinuousCDF, Normal}; -use crate::quant::OptionType; +use crate::quant::{r#trait::Price, OptionType}; #[derive(Default, Debug, Clone, Copy)] -pub enum BsmCoc { +pub enum BSMCoc { /// Black-Scholes-Merton 1973 (stock option) /// Cost of carry = risk-free rate #[default] @@ -24,7 +24,7 @@ pub enum BsmCoc { /// Black-Scholes-Merton model #[derive(Default, Debug)] -pub struct Bsm { +pub struct BSM { /// Underlying price pub s: f64, /// Volatility @@ -48,10 +48,40 @@ pub struct Bsm { /// Option type pub option_type: OptionType, /// Cost of carry - pub b: BsmCoc, + pub b: BSMCoc, } -impl Bsm { +impl Price for BSM { + /// Calculate the option price + #[must_use] + fn price(&self) -> f64 { + let (d1, d2) = self.d1_d2(); + let n = Normal::default(); + let tau = self.tau(); + + if self.option_type == OptionType::Call { + self.s * ((self.b() - self.r) * tau).exp() * n.cdf(d1) + - self.k * (-self.r * tau).exp() * n.cdf(d2) + } else { + -self.s * ((self.b() - self.r) * tau).exp() * n.cdf(-d1) + + self.k * (-self.r * tau).exp() * n.cdf(-d2) + } + } + + fn tau(&self) -> Option { + self.tau + } + + fn eval(&self) -> Option { + self.eval + } + + fn expiration(&self) -> Option { + self.expiration + } +} + +impl BSM { /// Create a new BSM model #[must_use] pub fn new(params: &Self) -> Self { @@ -85,22 +115,6 @@ impl Bsm { } } - /// Calculate the option price - #[must_use] - pub fn price(&mut self) -> f64 { - let (d1, d2) = self.d1_d2(); - let n = Normal::default(); - let tau = self.tau(); - - if self.option_type == OptionType::Call { - self.s * ((self.b() - self.r) * tau).exp() * n.cdf(d1) - - self.k * (-self.r * tau).exp() * n.cdf(d2) - } else { - -self.s * ((self.b() - self.r) * tau).exp() * n.cdf(-d1) - + self.k * (-self.r * tau).exp() * n.cdf(-d2) - } - } - /// Calculate d1 fn d1_d2(&self) -> (f64, f64) { let d1 = (1.0 / (self.v * self.tau().sqrt())) @@ -113,11 +127,11 @@ impl Bsm { /// Calculate b (cost of carry) fn b(&self) -> f64 { match self.b { - BsmCoc::BSM1973 => self.r, - BsmCoc::MERTON1973 => self.r - self.q.unwrap(), - BsmCoc::BLACK1976 => 0.0, - BsmCoc::ASAY1982 => 0.0, - BsmCoc::GARMAN1983 => self.r_d.unwrap() - self.r_f.unwrap(), + BSMCoc::BSM1973 => self.r, + BSMCoc::MERTON1973 => self.r - self.q.unwrap(), + BSMCoc::BLACK1976 => 0.0, + BSMCoc::ASAY1982 => 0.0, + BSMCoc::GARMAN1983 => self.r_d.unwrap() - self.r_f.unwrap(), } } diff --git a/src/quant/trait.rs b/src/quant/trait.rs new file mode 100644 index 0000000..9b504d5 --- /dev/null +++ b/src/quant/trait.rs @@ -0,0 +1,52 @@ +use chrono::Local; +use nalgebra::DVector; + +/// Pricer trait. +pub(crate) trait Pricer { + /// Calculate the price of an option. + fn calculate_price(&mut self); + /// Update the parameters. + fn update_params(&mut self, params: DVector); + /// Update strike price. + fn update_strike(&mut self, k: f64); + /// Prices. + fn prices(&self) -> (f64, f64); + /// Derivatives. + fn derivates(&self) -> Vec; +} + +/// Price an instrument. +pub trait Price { + /// Calculate the price of an instrument. + fn price(&self) -> f64; + + /// Calculate the valuation date of an instrument. + fn calculate_tau_in_days(&self) -> f64 { + if let Some(tau) = self.tau() { + tau + } else { + let eval = self + .eval() + .unwrap_or_else(|| Local::now().naive_local().into()); + let expiration = self.expiration().unwrap(); + (expiration - eval).num_days() as f64 + } + } + + /// Calculate the valuation date of an instrument. + fn calculate_tau_in_years(&self) -> f64 { + if let Some(tau) = self.tau() { + tau + } else { + let eval = self + .eval() + .unwrap_or_else(|| Local::now().naive_local().into()); + let expiration = self.expiration().unwrap(); + (expiration - eval).num_days() as f64 / 365.0 + } + } + + fn tau(&self) -> Option; + fn eval(&self) -> Option; + fn expiration(&self) -> Option; +} diff --git a/src/quant/volatility.rs b/src/quant/volatility.rs index bfd0d1b..45d0278 100644 --- a/src/quant/volatility.rs +++ b/src/quant/volatility.rs @@ -5,21 +5,7 @@ use std::cell::RefCell; use levenberg_marquardt::LeastSquaresProblem; use nalgebra::{DMatrix, DVector, Dyn, Owned}; -use super::OptionType; - -/// Pricer trait. -pub(crate) trait Pricer { - /// Calculate the price of an option. - fn calculate_price(&mut self); - /// Update the parameters. - fn update_params(&mut self, params: DVector); - /// Update strike price. - fn update_strike(&mut self, k: f64); - /// Prices. - fn prices(&self) -> (f64, f64); - /// Derivatives. - fn derivates(&self) -> Vec; -} +use super::{r#trait::Pricer, OptionType}; /// A calibrator. pub(crate) struct Calibrator<'a, P> diff --git a/src/quant/volatility/heston.rs b/src/quant/volatility/heston.rs index cd780f8..d843992 100644 --- a/src/quant/volatility/heston.rs +++ b/src/quant/volatility/heston.rs @@ -6,12 +6,10 @@ use num_complex::Complex64; use quadrature::double_exponential; use crate::{ - quant::{volatility::Calibrator, OptionType}, + quant::{r#trait::Pricer, volatility::Calibrator, OptionType}, stats::mle::nmle_heston, }; -use super::Pricer; - #[derive(Default, Clone)] pub struct HestonPricer { /// Initial stock price diff --git a/src/stochastic/diffusion/cir.rs b/src/stochastic/diffusion/cir.rs index d3a998c..98d9718 100644 --- a/src/stochastic/diffusion/cir.rs +++ b/src/stochastic/diffusion/cir.rs @@ -4,8 +4,11 @@ use rand_distr::Normal; use crate::stochastic::Sampling; +/// Cox-Ingersoll-Ross (CIR) process. +/// dX(t) = theta(mu - X(t))dt + sigma * sqrt(X(t))dW(t) +/// where X(t) is the CIR process. #[derive(Default)] -pub struct Cir { +pub struct CIR { pub theta: f64, pub mu: f64, pub sigma: f64, @@ -16,7 +19,7 @@ pub struct Cir { pub m: Option, } -impl Cir { +impl CIR { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -32,7 +35,7 @@ impl Cir { } } -impl Sampling for Cir { +impl Sampling for CIR { fn sample(&self) -> Array1 { assert!( 2.0 * self.theta * self.mu < self.sigma.powi(2), diff --git a/src/stochastic/diffusion/fcir.rs b/src/stochastic/diffusion/fcir.rs index 8d7816f..612e671 100644 --- a/src/stochastic/diffusion/fcir.rs +++ b/src/stochastic/diffusion/fcir.rs @@ -1,9 +1,9 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::fgn::Fgn, Sampling}; +use crate::stochastic::{noise::fgn::FGN, Sampling}; #[derive(Default)] -pub struct Fcir { +pub struct FCIR { pub hurst: f64, pub theta: f64, pub mu: f64, @@ -13,13 +13,13 @@ pub struct Fcir { pub t: Option, pub use_sym: Option, pub m: Option, - pub fgn: Fgn, + pub fgn: FGN, } -impl Fcir { +impl FCIR { #[must_use] pub fn new(params: &Self) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + let fgn = FGN::new(params.hurst, params.n, params.t, params.m); Self { hurst: params.hurst, @@ -36,7 +36,7 @@ impl Fcir { } } -impl Sampling for Fcir { +impl Sampling for FCIR { fn sample(&self) -> Array1 { assert!( 2.0 * self.theta * self.mu < self.sigma.powi(2), diff --git a/src/stochastic/diffusion/fgbm.rs b/src/stochastic/diffusion/fgbm.rs index ec6cdc7..1548a2b 100644 --- a/src/stochastic/diffusion/fgbm.rs +++ b/src/stochastic/diffusion/fgbm.rs @@ -1,9 +1,9 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::fgn::Fgn, Sampling}; +use crate::stochastic::{noise::fgn::FGN, Sampling}; #[derive(Default)] -pub struct Fgbm { +pub struct FGBM { pub hurst: f64, pub mu: f64, pub sigma: f64, @@ -11,13 +11,13 @@ pub struct Fgbm { pub x0: Option, pub t: Option, pub m: Option, - fgn: Fgn, + fgn: FGN, } -impl Fgbm { +impl FGBM { #[must_use] pub fn new(params: &Self) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + let fgn = FGN::new(params.hurst, params.n, params.t, params.m); Self { hurst: params.hurst, @@ -32,7 +32,7 @@ impl Fgbm { } } -impl Sampling for Fgbm { +impl Sampling for FGBM { fn sample(&self) -> Array1 { assert!( self.hurst > 0.0 && self.hurst < 1.0, diff --git a/src/stochastic/diffusion/fjacobi.rs b/src/stochastic/diffusion/fjacobi.rs index 0556272..3fc3e04 100644 --- a/src/stochastic/diffusion/fjacobi.rs +++ b/src/stochastic/diffusion/fjacobi.rs @@ -1,9 +1,9 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::fgn::Fgn, Sampling}; +use crate::stochastic::{noise::fgn::FGN, Sampling}; #[derive(Default)] -pub struct Fjacobi { +pub struct FJacobi { pub hurst: f64, pub alpha: f64, pub beta: f64, @@ -12,13 +12,13 @@ pub struct Fjacobi { pub x0: Option, pub t: Option, pub m: Option, - pub fgn: Fgn, + pub fgn: FGN, } -impl Fjacobi { +impl FJacobi { #[must_use] pub fn new(params: &Self) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + let fgn = FGN::new(params.hurst, params.n, params.t, params.m); Self { hurst: params.hurst, @@ -34,7 +34,7 @@ impl Fjacobi { } } -impl Sampling for Fjacobi { +impl Sampling for FJacobi { fn sample(&self) -> Array1 { assert!( self.hurst > 0.0 && self.hurst < 1.0, diff --git a/src/stochastic/diffusion/fou.rs b/src/stochastic/diffusion/fou.rs index 53ab2fa..63c642d 100644 --- a/src/stochastic/diffusion/fou.rs +++ b/src/stochastic/diffusion/fou.rs @@ -1,9 +1,9 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::fgn::Fgn, Sampling}; +use crate::stochastic::{noise::fgn::FGN, Sampling}; #[derive(Default)] -pub struct Fou { +pub struct FOU { pub hurst: f64, pub mu: f64, pub sigma: f64, @@ -12,13 +12,13 @@ pub struct Fou { pub x0: Option, pub t: Option, pub m: Option, - pub fgn: Fgn, + pub fgn: FGN, } -impl Fou { +impl FOU { #[must_use] pub fn new(params: &Self) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + let fgn = FGN::new(params.hurst, params.n, params.t, params.m); Self { hurst: params.hurst, @@ -34,7 +34,7 @@ impl Fou { } } -impl Sampling for Fou { +impl Sampling for FOU { fn sample(&self) -> Array1 { assert!( self.hurst > 0.0 && self.hurst < 1.0, diff --git a/src/stochastic/diffusion/gbm.rs b/src/stochastic/diffusion/gbm.rs index 666cdbd..94f0d33 100644 --- a/src/stochastic/diffusion/gbm.rs +++ b/src/stochastic/diffusion/gbm.rs @@ -10,7 +10,7 @@ use statrs::{ use crate::stochastic::{Distribution, Sampling}; #[derive(Default)] -pub struct Gbm { +pub struct GBM { pub mu: f64, pub sigma: f64, pub n: usize, @@ -20,7 +20,7 @@ pub struct Gbm { pub distribution: Option, } -impl Gbm { +impl GBM { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -35,7 +35,7 @@ impl Gbm { } } -impl Sampling for Gbm { +impl Sampling for GBM { 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()); @@ -69,7 +69,7 @@ impl Sampling for Gbm { } } -impl Distribution for Gbm { +impl Distribution for GBM { /// Characteristic function of the distribution fn characteristic_function(&self, _t: f64) -> Complex64 { unimplemented!() diff --git a/src/stochastic/diffusion/ou.rs b/src/stochastic/diffusion/ou.rs index a652a49..8ee85b8 100644 --- a/src/stochastic/diffusion/ou.rs +++ b/src/stochastic/diffusion/ou.rs @@ -5,7 +5,7 @@ use rand_distr::Normal; use crate::stochastic::Sampling; #[derive(Default)] -pub struct Ou { +pub struct OU { pub mu: f64, pub sigma: f64, pub theta: f64, @@ -15,7 +15,7 @@ pub struct Ou { pub m: Option, } -impl Ou { +impl OU { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -30,7 +30,7 @@ impl Ou { } } -impl Sampling for Ou { +impl Sampling for OU { 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()); diff --git a/src/stochastic/interest.rs b/src/stochastic/interest.rs index 5adfc7c..35cded7 100644 --- a/src/stochastic/interest.rs +++ b/src/stochastic/interest.rs @@ -1,4 +1,7 @@ +pub mod cir; pub mod duffie_kan; pub mod fvasicek; pub mod ho_lee; +pub mod hull_white; +pub mod hull_white_2f; pub mod vasicek; diff --git a/src/stochastic/interest/cir.rs b/src/stochastic/interest/cir.rs new file mode 100644 index 0000000..43ea678 --- /dev/null +++ b/src/stochastic/interest/cir.rs @@ -0,0 +1 @@ +pub use crate::stochastic::diffusion::cir::CIR; diff --git a/src/stochastic/interest/duffie_kan.rs b/src/stochastic/interest/duffie_kan.rs index 018a839..d9202e9 100644 --- a/src/stochastic/interest/duffie_kan.rs +++ b/src/stochastic/interest/duffie_kan.rs @@ -1,6 +1,6 @@ use ndarray::Array1; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] @@ -22,13 +22,13 @@ pub struct DuffieKan { pub x0: Option, pub t: Option, pub m: Option, - pub cgns: Cgns, + pub cgns: CGNS, } impl DuffieKan { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns: CGNS = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, diff --git a/src/stochastic/interest/fvasicek.rs b/src/stochastic/interest/fvasicek.rs index 0a00e17..b5f3f58 100644 --- a/src/stochastic/interest/fvasicek.rs +++ b/src/stochastic/interest/fvasicek.rs @@ -1,9 +1,9 @@ use ndarray::Array1; -use crate::stochastic::{diffusion::fou::Fou, Sampling}; +use crate::stochastic::{diffusion::fou::FOU, Sampling}; #[derive(Default)] -pub struct Fvasicek { +pub struct FVasicek { pub hurst: f64, pub mu: f64, pub sigma: f64, @@ -12,13 +12,13 @@ pub struct Fvasicek { pub x0: Option, pub t: Option, pub m: Option, - pub fou: Fou, + pub fou: FOU, } -impl Fvasicek { +impl FVasicek { #[must_use] pub fn new(params: &Self) -> Self { - let fou = Fou::new(&Fou { + let fou = FOU::new(&FOU { hurst: params.hurst, mu: params.mu, sigma: params.sigma, @@ -44,7 +44,7 @@ impl Fvasicek { } } -impl Sampling for Fvasicek { +impl Sampling for FVasicek { fn sample(&self) -> Array1 { self.fou.sample() } diff --git a/src/stochastic/interest/hull_white.rs b/src/stochastic/interest/hull_white.rs new file mode 100644 index 0000000..c4bc256 --- /dev/null +++ b/src/stochastic/interest/hull_white.rs @@ -0,0 +1,59 @@ +use ndarray::Array1; +use ndarray_rand::RandomExt; +use rand_distr::Normal; + +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. +pub struct HullWhite { + pub theta: fn(f64) -> f64, + pub alpha: f64, + pub sigma: f64, + pub n: usize, + pub x0: Option, + pub t: Option, + 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 mut hw = Array1::::zeros(self.n + 1); + hw[0] = self.x0.unwrap_or(0.0); + + 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] + } + + hw + } + + fn n(&self) -> usize { + self.n + } + + fn m(&self) -> Option { + self.m + } +} diff --git a/src/stochastic/interest/hull_white_2f.rs b/src/stochastic/interest/hull_white_2f.rs new file mode 100644 index 0000000..1e15250 --- /dev/null +++ b/src/stochastic/interest/hull_white_2f.rs @@ -0,0 +1,76 @@ +use ndarray::Array1; + +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 +pub struct HullWhite2F { + pub k: fn(f64) -> f64, + pub theta: f64, + pub sigma1: f64, + pub sigma2: f64, + pub rho: f64, + pub b: f64, + pub x0: Option, + pub t: Option, + pub n: usize, + pub m: Option, + 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 mut x = Array1::::zeros(self.n + 1); + let mut u = Array1::::zeros(self.n + 1); + + x[0] = self.x0.unwrap_or(0.0); + + 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]; + + u[i] = u[i - 1] + self.b * u[i - 1] * dt + self.sigma2 * cgn2[i - 1]; + } + + [x, u] + } + + fn n(&self) -> usize { + self.n + } + + fn m(&self) -> Option { + self.m + } +} diff --git a/src/stochastic/interest/vasicek.rs b/src/stochastic/interest/vasicek.rs index 5a4ec60..c504392 100644 --- a/src/stochastic/interest/vasicek.rs +++ b/src/stochastic/interest/vasicek.rs @@ -1,6 +1,6 @@ use ndarray::Array1; -use crate::stochastic::{diffusion::ou::Ou, Sampling}; +use crate::stochastic::{diffusion::ou::OU, Sampling}; #[derive(Default)] pub struct Vasicek { @@ -11,13 +11,13 @@ pub struct Vasicek { pub x0: Option, pub t: Option, pub m: Option, - pub ou: Ou, + pub ou: OU, } impl Vasicek { #[must_use] pub fn new(params: &Self) -> Self { - let ou = Ou::new(&Ou { + let ou = OU::new(&OU { mu: params.mu, sigma: params.sigma, theta: params.theta.unwrap_or(1.0), diff --git a/src/stochastic/jump/bates.rs b/src/stochastic/jump/bates.rs index f10ebc7..9611e3e 100644 --- a/src/stochastic/jump/bates.rs +++ b/src/stochastic/jump/bates.rs @@ -1,7 +1,7 @@ use ndarray::Array1; use crate::stochastic::{ - noise::cgns::Cgns, process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling2D, + noise::cgns::CGNS, process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling2D, Sampling3D, }; @@ -27,14 +27,14 @@ where pub use_sym: Option, pub m: Option, pub jumps_distribution: D, - pub cgns: Cgns, + pub cgns: CGNS, pub cpoisson: CompoundPoisson, } impl Bates1996 { #[must_use] pub fn new(params: &Bates1996) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, diff --git a/src/stochastic/jump/ig.rs b/src/stochastic/jump/ig.rs index cb344c5..4f68fdd 100644 --- a/src/stochastic/jump/ig.rs +++ b/src/stochastic/jump/ig.rs @@ -6,7 +6,7 @@ use crate::stochastic::Sampling; #[derive(Default)] -pub struct Ig { +pub struct IG { pub gamma: f64, pub n: usize, pub x0: Option, @@ -14,9 +14,9 @@ pub struct Ig { pub m: Option, } -impl Ig { +impl IG { #[must_use] - pub fn new(params: &Ig) -> Self { + pub fn new(params: &IG) -> Self { Self { gamma: params.gamma, n: params.n, @@ -27,7 +27,7 @@ impl Ig { } } -impl Sampling for Ig { +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()); diff --git a/src/stochastic/jump/jump_fou.rs b/src/stochastic/jump/jump_fou.rs index f888d46..cf75876 100644 --- a/src/stochastic/jump/jump_fou.rs +++ b/src/stochastic/jump/jump_fou.rs @@ -1,11 +1,11 @@ use ndarray::{s, Array1}; use crate::stochastic::{ - noise::fgn::Fgn, process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling, Sampling3D, + noise::fgn::FGN, process::cpoisson::CompoundPoisson, ProcessDistribution, Sampling, Sampling3D, }; #[derive(Default)] -pub struct JumpFou +pub struct JumpFOU where D: ProcessDistribution, { @@ -19,14 +19,14 @@ where pub t: Option, pub m: Option, pub jump_distribution: D, - pub fgn: Fgn, + pub fgn: FGN, pub cpoisson: CompoundPoisson, } -impl JumpFou { +impl JumpFOU { #[must_use] - pub fn new(params: &JumpFou) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + 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, @@ -54,7 +54,7 @@ impl JumpFou { } } -impl Sampling for JumpFou { +impl Sampling for JumpFOU { fn sample(&self) -> Array1 { assert!( self.hurst > 0.0 && self.hurst < 1.0, diff --git a/src/stochastic/jump/nig.rs b/src/stochastic/jump/nig.rs index 446decc..5d6ebef 100644 --- a/src/stochastic/jump/nig.rs +++ b/src/stochastic/jump/nig.rs @@ -6,7 +6,7 @@ use crate::stochastic::Sampling; #[derive(Default)] -pub struct Nig { +pub struct NIG { pub theta: f64, pub sigma: f64, pub kappa: f64, @@ -16,7 +16,7 @@ pub struct Nig { pub m: Option, } -impl Nig { +impl NIG { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -31,7 +31,7 @@ impl Nig { } } -impl Sampling for Nig { +impl Sampling for NIG { fn sample(&self) -> Array1 { let dt = self.t.unwrap_or(1.0) / self.n as f64; let scale = dt.powf(2.0) / self.kappa; diff --git a/src/stochastic/jump/vg.rs b/src/stochastic/jump/vg.rs index 3e57684..bf1c315 100644 --- a/src/stochastic/jump/vg.rs +++ b/src/stochastic/jump/vg.rs @@ -6,7 +6,7 @@ use rand_distr::Normal; use crate::stochastic::Sampling; #[derive(Default)] -pub struct Vg { +pub struct VG { pub mu: f64, pub sigma: f64, pub nu: f64, @@ -16,9 +16,9 @@ pub struct Vg { pub m: Option, } -impl Vg { +impl VG { #[must_use] - pub fn new(params: &Vg) -> Self { + pub fn new(params: &VG) -> Self { Self { mu: params.mu, sigma: params.sigma, @@ -31,7 +31,7 @@ impl Vg { } } -impl Sampling for Vg { +impl Sampling for VG { fn sample(&self) -> Array1 { let dt = self.t.unwrap_or(1.0) / self.n as f64; diff --git a/src/stochastic/noise/cfgns.rs b/src/stochastic/noise/cfgns.rs index 5ddcedb..e379ebe 100644 --- a/src/stochastic/noise/cfgns.rs +++ b/src/stochastic/noise/cfgns.rs @@ -2,22 +2,22 @@ use ndarray::{s, Array1, Array2}; use crate::stochastic::{Sampling, Sampling2D}; -use super::fgn::Fgn; +use super::fgn::FGN; #[derive(Default)] -pub struct Cfgns { +pub struct CFGNS { pub hurst: f64, pub rho: f64, pub n: usize, pub t: Option, pub m: Option, - pub fgn: Fgn, + pub fgn: FGN, } -impl Cfgns { +impl CFGNS { #[must_use] pub fn new(params: &Self) -> Self { - let fgn = Fgn::new(params.hurst, params.n, params.t, params.m); + let fgn = FGN::new(params.hurst, params.n, params.t, params.m); Self { hurst: params.hurst, @@ -30,7 +30,7 @@ impl Cfgns { } } -impl Sampling2D for Cfgns { +impl Sampling2D for CFGNS { fn sample(&self) -> [Array1; 2] { assert!( (0.0..=1.0).contains(&self.hurst), diff --git a/src/stochastic/noise/cgns.rs b/src/stochastic/noise/cgns.rs index cd84cd5..da0cb95 100644 --- a/src/stochastic/noise/cgns.rs +++ b/src/stochastic/noise/cgns.rs @@ -5,14 +5,14 @@ use rand_distr::Normal; use crate::stochastic::Sampling2D; #[derive(Default)] -pub struct Cgns { +pub struct CGNS { pub rho: f64, pub n: usize, pub t: Option, pub m: Option, } -impl Cgns { +impl CGNS { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -24,7 +24,7 @@ impl Cgns { } } -impl Sampling2D for Cgns { +impl Sampling2D for CGNS { fn sample(&self) -> [Array1; 2] { assert!( (-1.0..=1.0).contains(&self.rho), diff --git a/src/stochastic/noise/fgn.rs b/src/stochastic/noise/fgn.rs index 8f521eb..40cb488 100644 --- a/src/stochastic/noise/fgn.rs +++ b/src/stochastic/noise/fgn.rs @@ -9,7 +9,7 @@ use num_complex::{Complex, ComplexDistribution}; use crate::stochastic::Sampling; -pub struct Fgn { +pub struct FGN { pub hurst: f64, pub n: usize, pub t: Option, @@ -19,13 +19,13 @@ pub struct Fgn { pub fft_handler: Arc>, } -impl Default for Fgn { +impl Default for FGN { fn default() -> Self { Self::new(0.7, 1024, None, None) } } -impl Fgn { +impl FGN { #[must_use] pub fn new(hurst: f64, n: usize, t: Option, m: Option) -> Self { if !(0.0..=1.0).contains(&hurst) { @@ -67,7 +67,7 @@ impl Fgn { } } -impl Sampling for Fgn { +impl Sampling for FGN { fn sample(&self) -> Array1 { let num_threads = rayon::current_num_threads(); let chunk_size = (2 * self.n) / num_threads; @@ -112,7 +112,7 @@ mod tests { #[test] fn plot() { - let fgn = Fgn::new(0.7, 1000, Some(1.0), Some(1)); + let fgn = FGN::new(0.7, 1000, Some(1.0), Some(1)); let mut plot = Plot::new(); let d = fgn.sample_par(); for data in d.axis_iter(Axis(0)) { diff --git a/src/stochastic/process/bm.rs b/src/stochastic/process/bm.rs index e91f130..36b0f07 100644 --- a/src/stochastic/process/bm.rs +++ b/src/stochastic/process/bm.rs @@ -5,13 +5,13 @@ use rand_distr::Normal; use crate::stochastic::Sampling; #[derive(Default)] -pub struct Bm { +pub struct BM { pub n: usize, pub t: Option, pub m: Option, } -impl Bm { +impl BM { #[must_use] pub fn new(params: &Self) -> Self { Self { @@ -22,7 +22,7 @@ impl Bm { } } -impl Sampling for Bm { +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()); diff --git a/src/stochastic/process/cbms.rs b/src/stochastic/process/cbms.rs index af4db42..afac6c3 100644 --- a/src/stochastic/process/cbms.rs +++ b/src/stochastic/process/cbms.rs @@ -1,20 +1,20 @@ use ndarray::{Array1, Array2}; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] -pub struct Cbms { +pub struct CBMS { pub rho: f64, pub n: usize, pub t: Option, pub m: Option, - pub cgns: Cgns, + pub cgns: CGNS, } -impl Cbms { +impl CBMS { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, @@ -31,7 +31,7 @@ impl Cbms { } } -impl Sampling2D for Cbms { +impl Sampling2D for CBMS { fn sample(&self) -> [Array1; 2] { assert!( (-1.0..=1.0).contains(&self.rho), diff --git a/src/stochastic/process/cfbms.rs b/src/stochastic/process/cfbms.rs index 849bf8f..ef31d23 100644 --- a/src/stochastic/process/cfbms.rs +++ b/src/stochastic/process/cfbms.rs @@ -1,6 +1,6 @@ use ndarray::{Array1, Array2}; -use crate::stochastic::{noise::cfgns::Cfgns, Sampling2D}; +use crate::stochastic::{noise::cfgns::CFGNS, Sampling2D}; #[derive(Default)] pub struct Cfbms { @@ -10,13 +10,13 @@ pub struct Cfbms { pub n: usize, pub t: Option, pub m: Option, - pub cfgns: Cfgns, + pub cfgns: CFGNS, } impl Cfbms { #[must_use] pub fn new(params: &Self) -> Self { - let cfgns = Cfgns::new(&Cfgns { + let cfgns = CFGNS::new(&CFGNS { hurst: params.hurst1, rho: params.rho, n: params.n, diff --git a/src/stochastic/process/fbm.rs b/src/stochastic/process/fbm.rs index ac3c13a..b84ffdf 100644 --- a/src/stochastic/process/fbm.rs +++ b/src/stochastic/process/fbm.rs @@ -1,6 +1,6 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::fgn::Fgn, Sampling}; +use crate::stochastic::{noise::fgn::FGN, Sampling}; #[derive(Default)] pub struct Fbm { @@ -8,7 +8,7 @@ pub struct Fbm { pub n: usize, pub t: Option, pub m: Option, - pub fgn: Fgn, + pub fgn: FGN, } impl Fbm { @@ -17,7 +17,7 @@ impl Fbm { panic!("Hurst parameter must be in (0, 1)") } - let fgn = Fgn::new(params.hurst, params.n, params.t, None); + let fgn = FGN::new(params.hurst, params.n, params.t, None); Self { hurst: params.hurst, diff --git a/src/stochastic/volatility/bergomi.rs b/src/stochastic/volatility/bergomi.rs index 326290a..d171ef6 100644 --- a/src/stochastic/volatility/bergomi.rs +++ b/src/stochastic/volatility/bergomi.rs @@ -1,6 +1,6 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] pub struct Bergomi { @@ -12,13 +12,13 @@ pub struct Bergomi { pub n: usize, pub t: Option, pub m: Option, - pub cgns: Cgns, + pub cgns: CGNS, } impl Bergomi { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, diff --git a/src/stochastic/volatility/heston.rs b/src/stochastic/volatility/heston.rs index a637a7e..9665e24 100644 --- a/src/stochastic/volatility/heston.rs +++ b/src/stochastic/volatility/heston.rs @@ -1,6 +1,6 @@ use ndarray::Array1; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] @@ -28,13 +28,13 @@ pub struct Heston { /// Number of paths for multithreading pub m: Option, /// Noise generator - pub cgns: Cgns, + pub cgns: CGNS, } impl Heston { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, @@ -113,7 +113,7 @@ mod tests { t: Some(1.0), use_sym: Some(true), m: Some(1), - cgns: Cgns::default(), + cgns: CGNS::default(), }); let mut plot = Plot::new(); let [s, v] = heston.sample(); diff --git a/src/stochastic/volatility/rbergomi.rs b/src/stochastic/volatility/rbergomi.rs index 38fdd2e..b127bdd 100644 --- a/src/stochastic/volatility/rbergomi.rs +++ b/src/stochastic/volatility/rbergomi.rs @@ -1,6 +1,6 @@ use ndarray::{s, Array1}; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] pub struct RoughBergomi { @@ -13,13 +13,13 @@ pub struct RoughBergomi { pub n: usize, pub t: Option, pub m: Option, - pub cgns: Cgns, + pub cgns: CGNS, } impl RoughBergomi { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t, diff --git a/src/stochastic/volatility/sabr.rs b/src/stochastic/volatility/sabr.rs index 1b612e1..291a646 100644 --- a/src/stochastic/volatility/sabr.rs +++ b/src/stochastic/volatility/sabr.rs @@ -1,6 +1,6 @@ use ndarray::Array1; -use crate::stochastic::{noise::cgns::Cgns, Sampling2D}; +use crate::stochastic::{noise::cgns::CGNS, Sampling2D}; #[derive(Default)] @@ -13,13 +13,13 @@ pub struct Sabr { pub v0: Option, pub t: Option, pub m: Option, - pub cgns: Cgns, + pub cgns: CGNS, } impl Sabr { #[must_use] pub fn new(params: &Self) -> Self { - let cgns = Cgns::new(&Cgns { + let cgns = CGNS::new(&CGNS { rho: params.rho, n: params.n, t: params.t,