From f344cd9d65dc63f6d896879d6c99d626393a3255 Mon Sep 17 00:00:00 2001 From: Andy Pack <andy@sarsoo.xyz> Date: Sun, 16 Feb 2025 22:15:10 +0000 Subject: [PATCH] adding comments bit of parralel --- .github/workflows/build.yml | 3 +- finlib/src/ffi/mod.rs | 2 + finlib/src/interest/mod.rs | 1 + finlib/src/lib.rs | 30 ++++++++++++++- finlib/src/risk/mod.rs | 2 + finlib/src/risk/portfolio.rs | 73 ++++++++++++++++++++++++++++-------- finlib/src/risk/var/mod.rs | 2 + finlib/src/stats/mod.rs | 6 --- 8 files changed, 96 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d967f42..da953ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,7 @@ jobs: toolchain: stable - name: Build Docs - run: cargo doc --no-deps --document-private-items + run: cargo doc --no-deps --document-private-items -F py,wasm,ffi - name: Add redirect run: echo '<meta http-equiv="refresh" content="0;url=finlib/index.html">' > target/doc/index.html @@ -160,6 +160,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 22 + registry-url: 'https://registry.npmjs.org' # This is just the default registry URL - name: Publish working-directory: ./finlib-wasm/pkg diff --git a/finlib/src/ffi/mod.rs b/finlib/src/ffi/mod.rs index a4a5a25..b988d0c 100644 --- a/finlib/src/ffi/mod.rs +++ b/finlib/src/ffi/mod.rs @@ -1,3 +1,5 @@ +//! FFI specific functionality to define the struct function interfaces in Python and WASM + #[cfg(feature = "py")] pub mod py; #[cfg(feature = "wasm")] diff --git a/finlib/src/interest/mod.rs b/finlib/src/interest/mod.rs index 56151fc..c42e2e6 100644 --- a/finlib/src/interest/mod.rs +++ b/finlib/src/interest/mod.rs @@ -1,3 +1,4 @@ +//! Compound interest etc pub fn compound_32(principal: f32, rate: f32, time: f32, n: f32) -> f32 { principal * f32::powf( 1f32 + (rate / n), time * n) diff --git a/finlib/src/lib.rs b/finlib/src/lib.rs index ec12a32..7dc8d0d 100644 --- a/finlib/src/lib.rs +++ b/finlib/src/lib.rs @@ -1,5 +1,33 @@ +//! # Quant finance functionality for Rust with FFIs to C/C++, C#, Python and WASM + pub mod interest; pub mod stats; pub mod util; pub mod risk; -pub mod ffi; \ No newline at end of file +pub mod ffi; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[macro_export] +macro_rules! gated_iter { + ($x:expr) => { + { + $x.iter() + } + }; +} + +#[macro_export] +macro_rules! gated_iter_mut { + ($x:expr) => { + { + if cfg!(feature = "parallel") { + $x.par_iter_mut() + } + else { + $x.iter_mut() + } + } + }; +} \ No newline at end of file diff --git a/finlib/src/risk/mod.rs b/finlib/src/risk/mod.rs index 1411c4b..9b7b8fc 100644 --- a/finlib/src/risk/mod.rs +++ b/finlib/src/risk/mod.rs @@ -1,3 +1,5 @@ +//! Calculating risk for a given asset or portfolio using Value at Risk, [`var`] + pub mod var; pub mod portfolio; pub mod forecast; diff --git a/finlib/src/risk/portfolio.rs b/finlib/src/risk/portfolio.rs index cd79a01..5bb44f6 100644 --- a/finlib/src/risk/portfolio.rs +++ b/finlib/src/risk/portfolio.rs @@ -5,12 +5,15 @@ use ndarray_stats::CorrelationExt; use wasm_bindgen::prelude::*; #[cfg(feature = "py")] use pyo3::prelude::*; +#[cfg(feature = "parallel")] +use rayon::prelude::*; use statrs::distribution::{ContinuousCDF, Normal}; use crate::risk::forecast::{mean_investment, std_dev_investment}; use crate::risk::var::varcovar::{investment_value_at_risk}; -use crate::stats; +use crate::{stats}; use crate::util::roc::rates_of_change; +/// Describes a Portfolio as a collection of [`PortfolioAsset`]s #[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "py", pyclass)] #[cfg_attr(feature = "ffi", repr(C))] @@ -28,6 +31,7 @@ pub enum ValueType { RateOfChange } +/// Describes a single instrument as a list of previous values with an associated portfolio proportion #[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "py", pyclass)] #[cfg_attr(feature = "ffi", repr(C))] @@ -46,6 +50,7 @@ impl PortfolioAsset { } } + /// If the asset's values have been given as absolute values, convert those to a percentage change between each pub fn apply_rates_of_change(&mut self) { match self.value_type { ValueType::Absolute => { @@ -56,6 +61,9 @@ impl PortfolioAsset { } } + /// Get the mean and standard deviation of the rates of change of an asset + /// + /// returns (mean, std_dev) pub fn get_mean_and_std(&self) -> Option<(f64, f64)> { match self.value_type { ValueType::Absolute => { @@ -77,18 +85,32 @@ impl Portfolio { } } + /// Return the proportions of a portfolio's assets + /// + /// In a properly formed Portfolio these will add up to 1.0 pub fn get_asset_weight(&self) -> impl Iterator<Item=f64> + use<'_> { self.assets .iter() .map(|x| x.portfolio_weight) } + /// Convert a portfolio of assets with absolute values to the percentage change in values pub fn apply_rates_of_change(&mut self) { - for asset in self.assets.iter_mut() { - asset.apply_rates_of_change(); + #[cfg(feature = "parallel")] + { + self.assets.par_iter_mut().for_each(|asset| { + asset.apply_rates_of_change(); + }); + } + #[cfg(not(feature = "parallel"))] + { + self.assets.iter_mut().for_each(|asset| { + asset.apply_rates_of_change(); + }); } } + /// Do all the assets in the portfolio have the same number of values (required to perform matrix operations) pub fn valid_sizes(&self) -> bool { let mut last_value_length: Option<usize> = None; @@ -109,6 +131,7 @@ impl Portfolio { true } + /// Do the proportions of the assets in the portfolio add up to 100%? pub fn valid_weights(&self) -> bool { let mut weight = 1f64; @@ -123,6 +146,7 @@ impl Portfolio { self.valid_sizes() && self.valid_weights() } + /// Format the asset values in the portfolio as a matrix such that statistical operations can be applied to it pub fn get_matrix(&self) -> Option<Array2<f64>> { if self.assets.is_empty() || !self.valid_sizes() { return None; @@ -131,17 +155,34 @@ impl Portfolio { let column_count = self.assets.len(); let row_count = self.assets[0].values.len(); - let matrix = Array2::from_shape_vec((column_count, row_count), - self.assets - .iter() - .map(|a| a.values.clone()) - .flatten() - .collect::<Vec<f64>>() - ).unwrap(); + #[cfg(feature = "parallel")] + { + let matrix = Array2::from_shape_vec((column_count, row_count), + self.assets + .par_iter() + .map(|a| a.values.clone()) + .flatten() + .collect::<Vec<f64>>() + ).unwrap(); + Some(matrix.into_owned()) + } + #[cfg(not(feature = "parallel"))] + { + let matrix = Array2::from_shape_vec((column_count, row_count), + self.assets + .iter() + .map(|a| a.values.clone()) + .flatten() + .collect::<Vec<f64>>() + ).unwrap(); + Some(matrix.into_owned()) + } - Some(matrix.into_owned()) } + /// Calculate the mean and the standard deviation of a portfolio, taking into account the relative weights and covariance of the portfolio's assets + /// + /// returns (mean, std_dev) pub fn get_mean_and_std(&mut self) -> Option<(f64, f64)> { if !self.valid_sizes() { error!("Can't get portfolio mean and std dev because asset value counts arent't the same"); @@ -178,8 +219,9 @@ impl Portfolio { Some((porfolio_mean_return, portfolio_stddev)) } - // https://www.interviewqs.com/blog/value-at-risk - + /// For a given confidence rate (0.01, 0.05, 0.10) and initial investment value, calculate the parametric value at risk + /// + /// https://www.interviewqs.com/blog/value-at-risk pub fn value_at_risk(&mut self, confidence: f64, initial_investment: f64) -> Option<f64> { match self.get_mean_and_std() { None => None, @@ -198,8 +240,9 @@ impl Portfolio { } } - // https://www.interviewqs.com/blog/value-at-risk - + /// For a given confidence rate (0.01, 0.05, 0.10) calculate the percentage change in an investment + /// + /// https://www.interviewqs.com/blog/value-at-risk pub fn value_at_risk_percent(&mut self, confidence: f64) -> Option<f64> { match self.get_mean_and_std() { None => None, diff --git a/finlib/src/risk/var/mod.rs b/finlib/src/risk/var/mod.rs index 36a75d8..8863494 100644 --- a/finlib/src/risk/var/mod.rs +++ b/finlib/src/risk/var/mod.rs @@ -1,2 +1,4 @@ +//! Calculate Value at Risk using either the [`historical`] or parametric [`varcovar`] methods for an asset or portfolio + pub mod historical; pub mod varcovar; diff --git a/finlib/src/stats/mod.rs b/finlib/src/stats/mod.rs index c2bb3ac..b19353a 100644 --- a/finlib/src/stats/mod.rs +++ b/finlib/src/stats/mod.rs @@ -1,13 +1,9 @@ mod covariance; pub use covariance::*; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - pub fn mean(slice: &[f64]) -> f64 { slice - // .par_iter() .iter() .sum::<f64>() / slice.len() as f64 } @@ -16,7 +12,6 @@ pub fn population_variance(slice: &[f64]) -> f64 { let mean = mean(slice); slice - // .par_iter() .iter() .map(|x| f64::powi(x - mean, 2)) .sum::<f64>() @@ -27,7 +22,6 @@ pub fn sample_variance(slice: &[f64]) -> f64 { let mean = mean(slice); slice - // .par_iter() .iter() .map(|x| f64::powi(x - mean, 2)) .sum::<f64>()