use ndarray::Array1;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
use crate::error::{DigiFiError, ErrorTitle};
use crate::random_generators::RandomGenerator;
use crate::stochastic_processes::StochasticProcess;
use crate::random_generators::standard_normal_generators::StandardNormalBoxMuller;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FSRSimulationMethod {
EulerMaruyama,
AnalyticEulerMaruyama,
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ArithmeticBrownianMotion {
mu: f64,
sigma: f64,
n_paths: usize,
n_steps: usize,
t_f: f64,
s_0: f64,
dt: f64,
t: Array1<f64>,
}
impl ArithmeticBrownianMotion {
pub fn new(mu: f64, sigma: f64, n_paths: usize, n_steps: usize, t_f: f64, s_0: f64) -> Self {
let dt: f64 = t_f / (n_steps as f64);
let t: Array1<f64> = Array1::range(0.0, t_f + dt, dt);
Self { mu, sigma, n_paths, n_steps, t_f, s_0, dt, t }
}
pub fn get_auto_cov(&self, index_t1: usize, index_t2: usize) -> Result<f64, DigiFiError> {
let t_len: usize = self.t.len();
if t_len < index_t1 {
return Err(DigiFiError::IndexOutOfRange { title: Self::error_title(), index: "index_t1".to_owned(), array: "time".to_owned(), });
}
if t_len < index_t2 {
return Err(DigiFiError::IndexOutOfRange { title: Self::error_title(), index: "index_t2".to_owned(), array: "time".to_owned(), });
}
Ok(self.sigma.powi(2) * self.t[index_t1].min(self.t[index_t2]))
}
}
impl ErrorTitle for ArithmeticBrownianMotion {
fn error_title() -> String {
String::from("Arithmetic Brownian Motion")
}
}
impl StochasticProcess for ArithmeticBrownianMotion {
fn update_n_paths(&mut self, n_paths: usize) -> () {
self.n_paths = n_paths;
}
fn get_n_steps(&self) -> usize {
self.n_steps
}
fn get_t_f(&self) -> f64 {
self.t_f
}
fn get_expectations(&self) -> Option<Array1<f64>> {
Some(self.mu * &self.t + self.s_0)
}
fn get_variances(&self) -> Option<Array1<f64>> {
Some(&self.t * self.sigma.powi(2))
}
fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError> {
let mut paths: Vec<Array1<f64>> = Vec::with_capacity(self.n_paths);
let len: usize = self.t.len();
let drift_coef: f64 = self.mu * self.dt;
let diffusion_coef: f64 = self.sigma * self.dt.sqrt();
for _ in 0..self.n_paths {
let n: Array1<f64> = StandardNormalBoxMuller::new_shuffle(len)?.generate()?;
let (ds, _) = (0..len).into_iter().fold((Vec::with_capacity(len), self.s_0), |(mut ds, prev), i| {
ds.push(prev);
(ds, prev + drift_coef + diffusion_coef * n[i])
} );
paths.push(Array1::from_vec(ds));
}
Ok(paths)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GeometricBrownianMotion {
mu: f64,
sigma: f64,
n_paths: usize,
n_steps: usize,
t_f: f64,
s_0: f64,
dt: f64,
t: Array1<f64>,
}
impl GeometricBrownianMotion {
pub fn new(mu: f64, sigma: f64, n_paths: usize, n_steps: usize, t_f: f64, s_0: f64) -> Self {
let dt: f64 = t_f / (n_steps as f64);
let t: Array1<f64> = Array1::range(0.0, t_f + dt, dt);
Self { mu, sigma, n_paths, n_steps, t_f, s_0, dt, t }
}
}
impl StochasticProcess for GeometricBrownianMotion {
fn update_n_paths(&mut self, n_paths: usize) -> () {
self.n_paths = n_paths;
}
fn get_n_steps(&self) -> usize {
self.n_steps
}
fn get_t_f(&self) -> f64 {
self.t_f
}
fn get_expectations(&self) -> Option<Array1<f64>> {
Some(self.t.map(|t| { self.s_0 * (self.mu * t).exp() } ))
}
fn get_variances(&self) -> Option<Array1<f64>> {
let s_0_sq: f64 = self.s_0.powi(2);
let two_mu: f64 = 2.0 * self.mu;
let sigma_sq: f64 = self.sigma.powi(2);
Some(self.t.map(|t| { s_0_sq * (two_mu * t).exp() * ((t * sigma_sq).exp() - 1.0) } ))
}
fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError> {
let mut paths: Vec<Array1<f64>> = Vec::with_capacity(self.n_paths);
let len: usize = self.t.len();
let drift_coef: f64 = self.mu * self.dt;
let diffusion_coef: f64 = self.sigma * self.dt.sqrt();
for _ in 0..self.n_paths {
let n: Array1<f64> = StandardNormalBoxMuller::new_shuffle(len)?.generate()?;
let (s, _) = (0..len).into_iter().fold((Vec::with_capacity(len), self.s_0), |(mut s, prev), i| {
s.push(prev);
(s, prev + drift_coef * prev + diffusion_coef * prev * n[i])
} );
paths.push(Array1::from_vec(s));
}
Ok(paths)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrnsteinUhlenbeckProcess {
mu: f64,
sigma: f64,
alpha: f64,
n_paths: usize,
n_steps: usize,
t_f: f64,
s_0: f64,
dt: f64,
t: Array1<f64>,
analytic_em: bool,
}
impl OrnsteinUhlenbeckProcess {
pub fn new(mu: f64, sigma: f64, alpha: f64, n_paths: usize, n_steps: usize, t_f: f64, s_0: f64, analytic_em: bool) -> Self {
let dt: f64 = t_f / (n_steps as f64);
let t: Array1<f64> = Array1::range(0.0, t_f + dt, dt);
Self { mu, sigma, alpha, n_paths, n_steps, t_f, s_0, dt, t, analytic_em }
}
}
impl StochasticProcess for OrnsteinUhlenbeckProcess {
fn update_n_paths(&mut self, n_paths: usize) -> () {
self.n_paths = n_paths
}
fn get_n_steps(&self) -> usize {
self.n_steps
}
fn get_t_f(&self) -> f64 {
self.t_f
}
fn get_expectations(&self) -> Option<Array1<f64>> {
let drift_coef: f64 = self.s_0 - self.mu;
Some(self.t.map(|t| { self.mu + drift_coef * (-self.alpha * t).exp() } ))
}
fn get_variances(&self) -> Option<Array1<f64>> {
let two_alpha: f64 = 2.0 * self.alpha;
let sigma_sq: f64 = self.sigma.powi(2);
Some(self.t.map(|t| { (1.0 - (-two_alpha * t).exp()) * sigma_sq / two_alpha } ))
}
fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError> {
let mut paths: Vec<Array1<f64>> = Vec::with_capacity(self.n_paths);
let len: usize = self.t.len();
let alpha_dt: f64 = self.alpha * self.dt;
let (drift_coef, diffusion_coef) = if self.analytic_em {
let drift_coef: f64 = (-alpha_dt).exp();
let diffusion_coef: f64 = self.sigma * ((1.0 - (-2.0 * alpha_dt).exp()) / (2.0 * self.alpha)).sqrt();
(drift_coef, diffusion_coef)
} else {
let drift_coef: f64 = alpha_dt;
let diffusion_coef: f64 = self.sigma * self.dt.sqrt();
(drift_coef, diffusion_coef)
};
for _ in 0..self.n_paths {
let mut s: Array1<f64> = Array1::from_vec(vec![self.s_0; len]);
let r: Array1<f64> = StandardNormalBoxMuller::new_shuffle(len)?.generate()?;
if self.analytic_em {
for i in 1..len {
s[i] = self.mu + drift_coef * (s[i-1] - self.mu) + diffusion_coef * r[i-1]
}
} else {
for i in 1..len {
s[i] = s[i-1] + drift_coef * (self.mu - s[i]) + diffusion_coef * r[i];
}
}
paths.push(s);
}
Ok(paths)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BrownianBridge {
alpha: f64,
beta: f64,
sigma: f64,
n_paths: usize,
n_steps: usize,
t_f: f64,
dt: f64,
t: Array1<f64>,
}
impl BrownianBridge {
pub fn new(alpha: f64, beta: f64, sigma: f64, n_paths: usize, n_steps: usize, t_f: f64) -> Self {
let dt: f64 = t_f / (n_steps as f64);
let t: Array1<f64> = Array1::range(0.0, t_f + dt, dt);
Self { alpha, beta, sigma, n_paths, n_steps, t_f, dt, t }
}
}
impl StochasticProcess for BrownianBridge {
fn update_n_paths(&mut self, n_paths: usize) -> () {
self.n_paths = n_paths
}
fn get_n_steps(&self) -> usize {
self.n_steps
}
fn get_t_f(&self) -> f64 {
self.t_f
}
fn get_expectations(&self) -> Option<Array1<f64>> {
let c: f64 = (self.beta - self.alpha) / self.t_f;
Some(self.t.map(|t| self.alpha + c * t ))
}
fn get_variances(&self) -> Option<Array1<f64>> {
Some(&self.t * (self.t_f - &self.t) / self.t_f)
}
fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError> {
let mut paths: Vec<Array1<f64>> = Vec::with_capacity(self.n_paths);
let len: usize = self.t.len();
let diffusion_coef: f64 = self.sigma * self.dt.sqrt();
for _ in 0..self.n_paths {
let n: Array1<f64> = StandardNormalBoxMuller::new_shuffle(len)?.generate()?;
let mut s: Array1<f64> = Array1::from_vec(vec![self.alpha; len]);
s[len - 1] = self.beta;
for i in 0..(len - 2) {
s[i+1] = s[i] + (self.beta - s[i]) / (len - i) as f64 + diffusion_coef * n[i];
}
paths.push(s)
}
Ok(paths)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FellerSquareRootProcess {
mu: f64,
sigma: f64,
alpha: f64,
n_paths: usize,
n_steps: usize,
t_f: f64,
s_0: f64,
dt: f64,
t: Array1<f64>,
method: FSRSimulationMethod,
}
impl FellerSquareRootProcess {
pub fn new(mu: f64, sigma: f64, alpha: f64, n_paths: usize, n_steps: usize, t_f: f64, s_0: f64, method: FSRSimulationMethod) -> Self {
let dt: f64 = t_f / (n_steps as f64);
let t: Array1<f64> = Array1::range(0.0, t_f + dt, dt);
Self { mu, sigma, alpha, n_paths, n_steps, t_f, s_0, dt, t, method }
}
}
impl StochasticProcess for FellerSquareRootProcess {
fn update_n_paths(&mut self, n_paths: usize) -> () {
self.n_paths = n_paths
}
fn get_n_steps(&self) -> usize {
self.n_steps
}
fn get_t_f(&self) -> f64 {
self.t_f
}
fn get_expectations(&self) -> Option<Array1<f64>> {
let drift_delta: f64 = self.s_0 - self.mu;
Some(self.t.map(|t| { self.mu + drift_delta * (-self.alpha * t).exp() } ))
}
fn get_variances(&self) -> Option<Array1<f64>> {
let neg_two_alpha: f64 = -2.0 * self.alpha;
let mult_1: f64 = self.s_0 / self.alpha;
let mult_2: f64 = self.mu / (2.0 * self.alpha);
let sigma_sq: f64 = self.sigma.powi(2);
Some(self.t.map(|t| {
let v_1: f64 = ((-self.alpha * t).exp() - (neg_two_alpha * t).exp()) * mult_1;
let v_2: f64 = (neg_two_alpha * t).exp() * ((self.alpha * t).exp() - 1.0).powi(2) * mult_2;
sigma_sq * (v_1 + v_2)
} ))
}
fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError> {
let mut paths: Vec<Array1<f64>> = Vec::with_capacity(self.n_paths);
let len: usize = self.t.len();
let alpha_dt: f64 = self.alpha * self.dt;
let (exp_neg_alpha_dt, a, b) = match self.method {
FSRSimulationMethod::EulerMaruyama => (f64::NAN, f64::NAN, f64::NAN),
FSRSimulationMethod::AnalyticEulerMaruyama => {
let exp_neg_alpha_dt: f64 = (-alpha_dt).exp();
let sigma_sq: f64 = self.sigma.powi(2);
let a: f64 = sigma_sq / self.alpha * (exp_neg_alpha_dt - (-2.0 * alpha_dt).exp());
let b: f64 = self.mu * sigma_sq / (2.0 * self.alpha) * (1.0 - exp_neg_alpha_dt).powi(2);
(exp_neg_alpha_dt, a, b)
}
};
for _ in 0..self.n_paths {
let mut s: Array1<f64> = Array1::from_vec(vec![self.s_0; len]);
let r: Array1<f64> = StandardNormalBoxMuller::new_shuffle(len)?.generate()?;
match self.method {
FSRSimulationMethod::EulerMaruyama => {
for i in 1..len {
s[i] = s[i-1] + alpha_dt * (self.mu - s[i-1]) + self.sigma * (s[i-1] * self.dt).sqrt() * r[i-1];
s[i] = s[i].max(0.0);
}
},
FSRSimulationMethod::AnalyticEulerMaruyama => {
for i in 1..len {
s[i] = self.mu + (s[i-1] - self.mu) * exp_neg_alpha_dt + (a * s[i-1] + b).sqrt() * r[i-1];
s[i] = s[i].max(0.0);
}
},
}
paths.push(s);
}
Ok(paths)
}
}
#[cfg(test)]
mod tests {
use crate::stochastic_processes::{StochasticProcessResult, StochasticProcess};
use crate::utilities::TEST_ACCURACY;
#[test]
fn unit_test_arithmetic_brownian_motion() -> () {
use crate::stochastic_processes::standard_stochastic_models::ArithmeticBrownianMotion;
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let abm: ArithmeticBrownianMotion = ArithmeticBrownianMotion::new(1.0, 0.4, n_paths, n_steps, 1.0, 100.0);
let sp_result: StochasticProcessResult = abm.simulate().unwrap();
assert_eq!(sp_result.paths.len(), n_paths);
assert_eq!(sp_result.paths[0].len(), n_steps + 1);
assert_eq!(sp_result.expectations_path.clone().unwrap().len(), n_steps + 1);
assert_eq!(sp_result.variances_path.unwrap().len(), n_steps + 1);
assert!((sp_result.mean - sp_result.expectations_path.unwrap()[n_steps]).abs() < 10_000_000.0 * TEST_ACCURACY);
}
#[test]
fn unit_test_geometric_brownian_motion() -> () {
use crate::stochastic_processes::standard_stochastic_models::GeometricBrownianMotion;
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let gbm: GeometricBrownianMotion = GeometricBrownianMotion::new(0.0, 0.2, n_paths, n_steps, 1.0, 100.0);
let sp_result: StochasticProcessResult = gbm.simulate().unwrap();
assert_eq!(sp_result.paths.len(), n_paths);
assert_eq!(sp_result.paths[0].len(), n_steps + 1);
assert_eq!(sp_result.expectations_path.clone().unwrap().len(), n_steps + 1);
assert_eq!(sp_result.variances_path.unwrap().len(), n_steps + 1);
assert!((sp_result.mean - sp_result.expectations_path.unwrap()[n_steps]).abs() < 100_000_000.0 * TEST_ACCURACY);
}
#[test]
fn unit_test_ornstein_uhlenbeck_process() -> () {
use crate::stochastic_processes::standard_stochastic_models::OrnsteinUhlenbeckProcess;
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let oup: OrnsteinUhlenbeckProcess = OrnsteinUhlenbeckProcess::new(
0.07, 0.1, 10.0, n_paths, n_steps, 1.0, 0.05, true
);
let sp_result: StochasticProcessResult = oup.simulate().unwrap();
assert_eq!(sp_result.paths.len(), n_paths);
assert_eq!(sp_result.paths[0].len(), n_steps + 1);
assert_eq!(sp_result.expectations_path.clone().unwrap().len(), n_steps + 1);
assert_eq!(sp_result.variances_path.unwrap().len(), n_steps + 1);
assert!((sp_result.mean - sp_result.expectations_path.unwrap()[n_steps]).abs() < 1_000_000.0 * TEST_ACCURACY);
}
#[test]
fn unit_test_brownian_bridge() -> () {
use crate::stochastic_processes::standard_stochastic_models::BrownianBridge;
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let bb: BrownianBridge = BrownianBridge::new(1.0, 2.0, 0.5, n_paths, n_steps, 1.0);
let sp_result: StochasticProcessResult = bb.simulate().unwrap();
assert_eq!(sp_result.paths.len(), n_paths);
assert_eq!(sp_result.paths[0].len(), n_steps + 1);
assert_eq!(sp_result.expectations_path.clone().unwrap().len(), n_steps + 1);
assert_eq!(sp_result.variances_path.unwrap().len(), n_steps + 1);
assert!((sp_result.mean - sp_result.expectations_path.unwrap()[n_steps]).abs() < 1_000_000.0 * TEST_ACCURACY);
}
#[test]
fn unit_test_feller_square_root_process() -> () {
use crate::stochastic_processes::standard_stochastic_models::{FellerSquareRootProcess, FSRSimulationMethod};
let n_paths: usize = 1_000;
let n_steps: usize = 200;
let fsrp: FellerSquareRootProcess = FellerSquareRootProcess::new(
0.05, 0.265, 5.0, n_paths, n_steps,1.0, 0.03, FSRSimulationMethod::EulerMaruyama,
);
let sp_result: StochasticProcessResult = fsrp.simulate().unwrap();
assert_eq!(sp_result.paths.len(), n_paths);
assert_eq!(sp_result.paths[0].len(), n_steps + 1);
assert_eq!(sp_result.expectations_path.clone().unwrap().len(), n_steps + 1);
assert_eq!(sp_result.variances_path.unwrap().len(), n_steps + 1);
assert!((sp_result.mean - sp_result.expectations_path.unwrap()[n_steps]).abs() < 1_000_000.0 * TEST_ACCURACY);
}
}