digifi 3.0.10

General purpose financial library and framework for financial modelling, portfolio optimisation, and asset pricing.
Documentation
//! # Stochastic process
//! 
//! Contains different types of stochastic processes, as well as the tool for generating custom stochastic processes that can be used for Monte Carlo simulations.


// Re-Exports
pub use self::standard_stochastic_models::{
    ArithmeticBrownianMotion, GeometricBrownianMotion, OrnsteinUhlenbeckProcess, BrownianBridge, FellerSquareRootProcess, FSRSimulationMethod,
};
pub use self::stochastic_volatility_models::{ConstantElasticityOfVariance, HestonStochasticVolatility, VarianceGammaProcess};
pub use self::jump_diffusion_models::{MertonJumpDiffusionProcess, KouJumpDiffusionProcess};


pub mod standard_stochastic_models;
pub mod stochastic_volatility_models;
pub mod jump_diffusion_models;
pub mod stochastic_process_generator;


use ndarray::Array1;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[cfg(feature = "plotly")]
use plotly::{Plot, Trace, Layout, Scatter, layout::Axis};
use crate::error::DigiFiError;
use crate::statistics::{skewness, kurtosis};
#[cfg(feature = "plotly")]
use crate::utilities::compare_len;


#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
/// Result of the stochastic process simulation.
pub struct StochasticProcessResult {
    /// Simulated paths of the stochastic process
    pub paths: Vec<Array1<f64>>,
    /// Path of expected values for each time step in the simulation (If available)
    pub expectations_path: Option<Array1<f64>>,
    /// Variances at each time step in the simulation (If available)
    pub variances_path: Option<Array1<f64>>,
    /// Mean of paths' values at the final time step
    pub mean: f64,
    /// Standard deviation of paths' values at the final time step
    pub std: f64,
    /// Variance of paths' values at the final time step
    pub variance: f64,
    /// Skewness of paths' values at the final time step
    pub skewness: f64,
    /// Kurtosis of paths' values at the final time step
    pub kurtosis: f64,
}


pub trait StochasticProcess {
    /// Updates the number of paths that the stochastic process will generate.
    /// 
    /// # Input
    /// - `n_paths`: Number of paths to generate
    fn update_n_paths(&mut self, n_paths: usize) -> ();

    /// Returns the number of time steps in the stochastic process.
    fn get_n_steps(&self) -> usize;

    /// Returns the final time step.
    fn get_t_f(&self) -> f64;

    fn get_expectations(&self) -> Option<Array1<f64>>;

    fn get_variances(&self) -> Option<Array1<f64>>;

    /// Paths, S, of the stochastic process.
    fn get_paths(&self) -> Result<Vec<Array1<f64>>, DigiFiError>;

    /// Runs stochastic process simulation:
    /// - Gets simulated paths
    /// - Gets expected path and variances at each time stemp (if available)
    /// - Computes distibution statistics (i.e., mean, standard deviation, variance, skewness, kurtosis) for the values
    /// at the final time step (i.e., final distribution) 
    fn simulate(&self) -> Result<StochasticProcessResult, DigiFiError> {
        let paths: Vec<Array1<f64>> = self.get_paths()?;
        let final_steps: Vec<f64> = paths.iter().fold(Vec::with_capacity(paths.len()), |mut fs, path| {
            fs.push(path[self.get_n_steps()]);
            fs
        } );
        let final_steps: Array1<f64> = Array1::from_vec(final_steps);
        let std: f64 = final_steps.std(0.0);
        Ok(StochasticProcessResult {
            paths,
            expectations_path: self.get_expectations(),
            variances_path: self.get_variances(),
            mean: final_steps.mean().ok_or(DigiFiError::MeanCalculation { title: "Stochastic Process Simulation".to_owned(), series: "final_steps".to_owned() })?,
            std,
            variance: std.powi(2),
            skewness: skewness(&final_steps)?,
            kurtosis: kurtosis(&final_steps)?,
        })
    }
}


#[cfg(feature = "plotly")]
/// Plots all generated paths of the stochastic process.
///
/// # Input
/// - `paths`: Vector of paths generated by a stochastic process
/// - `expected_path`: Expected path, E\[S\], of the stochastic process
///
/// # Output
/// - Plot of the paths of the stochastic process
///
/// # Errors
/// - Returns an error if the `paths` and `expected_paths` have arrays of different lengths.
///
/// # Examples
///
/// 1. Geometric Brownian motion paths simulation:
///
/// ```rust,ignore
/// use ndarray::Array1;
/// use digifi::stochastic_processes::{StochasticProcess, GeometricBrownianMotion};
///
/// #[cfg(feature = "plotly")]
/// fn test_gbm_plot() -> () {
///     use plotly::Plot;
///     use digifi::plots::plot_stochastic_paths;
///
///     // Geometric brownian motion
///     let gbm: GeometricBrownianMotion = GeometricBrownianMotion::new(0.0, 0.2, 1_000, 200, 1.0, 100.0);
///     let paths: Vec<Array1<f64>> = gbm.get_paths().unwrap();
///     let expected_path: Array1<f64> = gbm.get_expectations();
///
///     // Paths plot
///     let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
///     plot.show()
/// }
/// ```
///
/// 2. Heston stochastic volatility paths simulation:
///
/// ```rust,ignore
/// use ndarray::Array1;
/// use digifi::stochastic_processes::{StochasticProcess, HestonStochasticVolatility};
///
/// #[cfg(feature = "plotly")]
/// fn test_hsv_plot() -> () {
///     use plotly::Plot;
///     use digifi::plots::plot_stochastic_paths;
///
///     // Heston stochastic volatility
///     let hsv: HestonStochasticVolatility = HestonStochasticVolatility::new(0.1, 5.0, 0.07, 0.2, 0.0, 100, 200, 1.0, 100.0, 0.03);
///     let paths: Vec<Array1<f64>> = hsv.get_paths().unwrap();
///     let expected_path: Array1<f64> = hsv.get_expectations();
///
///     // Paths plot
///     let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
///     plot.show()
/// }
/// ```
///
/// 3. Kou jump diffusion paths simulation:
///
/// ```rust,ignore
/// use ndarray::Array1;
/// use digifi::stochastic_processes::{StochasticProcess, KouJumpDiffusionProcess};
///
/// #[cfg(feature = "plotly")]
/// fn test_hsv_plot() -> () {
///     use plotly::Plot;
///     use digifi::plots::plot_stochastic_paths;
///
///     // Kou jump diffusion
///     let kjd: KouJumpDiffusionProcess = KouJumpDiffusionProcess::build(0.2, 0.3, 0.5, 9.0, 5.0, 0.5, 100, 200, 1.0, 100.0).unwrap();
///     let paths: Vec<Array1<f64>> = kjd.get_paths().unwrap();
///     let expected_path: Array1<f64> = kjd.get_expectations();
///
///     // Paths plot
///     let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
///     plot.show()
/// }
/// ```
///
/// 4. Custom SDE simulation plot (e.g., Arithmetic Brownian motion):
///
/// ```rust,ignore
/// use ndarray::Array1;
/// use digifi::stochastic_processes::{StochasticProcess, stochastic_process_generator::{SDE, SDEComponent, Noise, Jump}};
///
/// #[cfg(feature = "plotly")]
/// fn unit_test_sde_abm_plot() -> () {
///     use plotly::Plot;
///     use digifi::plots::plot_stochastic_paths;
///
///     // Parameter definition
///     let t_f: f64 = 1.0;
///     let s_0: f64 = 100.0;
///     let drift_component: SDEComponent = SDEComponent::Linear { a: 10.0 };
///     let diffusion_component: SDEComponent = SDEComponent::Linear { a: 0.4 };
///     let noise: Noise = Noise::WeinerProcess;
///     let jump: Jump = Jump::NoJumps;
///
///     // Arithmetic Brownian motion SDE definition
///     let sde: SDE = SDE::build(t_f, s_0, 200, 100, drift_component, diffusion_component, noise, jump).unwrap();
///     let paths: Vec<Array1<f64>> = sde.get_paths().unwrap();
///
///     // Paths plot
///     let plot: Plot = plot_stochastic_paths(&paths, None).unwrap();
///     plot.show()
/// }
/// ```
pub fn plot_stochastic_paths(paths: &Vec<Array1<f64>>, expected_path: Option<&Array1<f64>>) -> Result<Plot, DigiFiError> {
    let t: Array1<f64> = Array1::range(0.0, paths[0].len() as f64, 1.0);
    let mut traces: Vec<Box<dyn Trace>> = Vec::<Box<dyn Trace>>::new();
    if let Some(p) = expected_path {
        compare_len(&t.iter(), &p.iter(), "path", "expected_path")?;
        traces.push(Scatter::new(t.to_vec(), p.to_vec()).name("Expected Path"));
    }
    for path in paths {
        traces.push(Scatter::new(t.to_vec(), path.to_vec()));
    }
    // Push expected path to be the last trace plotted
    if let Some(_) = expected_path { traces.rotate_left(1); }
    let mut plot: Plot = Plot::new();
    plot.add_traces(traces);
    let x_axis: Axis = Axis::new().title("Time Step");
    let y_axis: Axis = Axis::new().title("Stochastic Process Value");
    let layout: Layout = Layout::new().title("<b>Stochastic Path Simulations</b>").x_axis(x_axis).y_axis(y_axis);
    plot.set_layout(layout);
    Ok(plot)
}


#[cfg(all(test, feature = "plotly"))]
mod tests {
    use ndarray::Array1;
    use plotly::Plot;
    use crate::stochastic_processes::{StochasticProcess, plot_stochastic_paths};

    #[test]
    #[ignore]
    fn unit_test_abm_plot() -> () {
        use crate::stochastic_processes::standard_stochastic_models::ArithmeticBrownianMotion;
        // Arithmetic Brownian motion
        let abm: ArithmeticBrownianMotion = ArithmeticBrownianMotion::new(1.0, 0.4, 100, 200, 1.0, 100.0);
        let paths: Vec<Array1<f64>> = abm.get_paths().unwrap();
        let expected_path: Array1<f64> = abm.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_gbm_plot() -> () {
        use crate::stochastic_processes::standard_stochastic_models::GeometricBrownianMotion;
        // Geometric Brownian motion
        let gbm: GeometricBrownianMotion = GeometricBrownianMotion::new(0.0, 0.2, 1_000, 200, 1.0, 100.0);
        let paths: Vec<Array1<f64>> = gbm.get_paths().unwrap();
        let expected_path: Array1<f64> = gbm.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_oup_plot() -> () {
        use crate::stochastic_processes::standard_stochastic_models::OrnsteinUhlenbeckProcess;
        // Ornstein-Uhlebeck process
        let oup: OrnsteinUhlenbeckProcess = OrnsteinUhlenbeckProcess::new(
            0.07, 0.1, 10.0, 100, 200, 1.0, 0.05, true
        );
        let paths: Vec<Array1<f64>> = oup.get_paths().unwrap();
        let expected_path: Array1<f64> = oup.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_bb_plot() -> () {
        use crate::stochastic_processes::standard_stochastic_models::BrownianBridge;
        // Brownian bridge
        let bb: BrownianBridge = BrownianBridge::new(1.0, 2.0, 0.5, 100, 200, 1.0);
        let paths: Vec<Array1<f64>> = bb.get_paths().unwrap();
        let expected_path: Array1<f64> = bb.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_fsrp_plot() -> () {
        use crate::stochastic_processes::standard_stochastic_models::{FellerSquareRootProcess, FSRSimulationMethod};
        // Feller square root process
        let fsrp: FellerSquareRootProcess = FellerSquareRootProcess::new(0.05, 0.265, 5.0, 100, 200,
                                                                         1.0, 0.03, FSRSimulationMethod::EulerMaruyama);
        let paths: Vec<Array1<f64>> = fsrp.get_paths().unwrap();
        let expected_path: Array1<f64> = fsrp.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_cev_plot() -> () {
        use crate::stochastic_processes::stochastic_volatility_models::ConstantElasticityOfVariance;
        // Constant elasticity of variance
        let cev: ConstantElasticityOfVariance = ConstantElasticityOfVariance::build(
            1.0, 0.4, 0.5, 100, 200, 1.0, 100.0
        ).unwrap();
        let paths: Vec<Array1<f64>> = cev.get_paths().unwrap();
        let expected_path: Array1<f64> = cev.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_hsv_plot() -> () {
        use crate::stochastic_processes::stochastic_volatility_models::HestonStochasticVolatility;
        // Heston stochastic volatility
        let hsv: HestonStochasticVolatility = HestonStochasticVolatility::new(
            0.1, 5.0, 0.07, 0.2, 0.0, 100, 200, 1.0, 100.0, 0.03
        );
        let paths: Vec<Array1<f64>> = hsv.get_paths().unwrap();
        let expected_path: Array1<f64> = hsv.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_mjd_plot() -> () {
        use crate::stochastic_processes::jump_diffusion_models::MertonJumpDiffusionProcess;
        // Merton jump diffusion
        let mjd: MertonJumpDiffusionProcess = MertonJumpDiffusionProcess::new(
            0.03, 0.2, -0.03, 0.1, 1.5, 100, 200, 1.0, 100.0
        );
        let paths: Vec<Array1<f64>> = mjd.get_paths().unwrap();
        let expected_path: Array1<f64> = mjd.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_kjd_plot() -> () {
        use crate::stochastic_processes::jump_diffusion_models::KouJumpDiffusionProcess;
        // Kou jump diffusion
        let kjd: KouJumpDiffusionProcess = KouJumpDiffusionProcess::build(
            0.2, 0.3, 0.5, 9.0, 5.0, 0.5, 100, 200, 1.0, 100.0
        ).unwrap();
        let paths: Vec<Array1<f64>> = kjd.get_paths().unwrap();
        let expected_path: Array1<f64> = kjd.get_expectations().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, Some(&expected_path)).unwrap();
        plot.show()
    }

    #[test]
    #[ignore]
    fn unit_test_sde_abm_plot() -> () {
        use crate::stochastic_processes::stochastic_process_generator::{SDE, sde_components::{SDEComponent, Noise, Jump}};
        // Parameter definition
        let t_f: f64 = 1.0;
        let s_0: f64 = 100.0;
        let drift_component: SDEComponent = SDEComponent::Linear { a: 10.0 };
        let diffusion_component: SDEComponent = SDEComponent::Linear { a: 0.4 };
        let noise: Noise = Noise::WeinerProcess;
        let jump: Jump = Jump::NoJumps;
        // Arithmetic Brownian motion SDE definition
        let sde: SDE = SDE::build(t_f, s_0, 200, 100, drift_component, diffusion_component, noise, jump).unwrap();
        let paths: Vec<Array1<f64>> = sde.get_paths().unwrap();
        // Paths plot
        let plot: Plot = plot_stochastic_paths(&paths, None).unwrap();
        plot.show()
    }
}