portfolio covariance VAR
This commit is contained in:
parent
c1113f3296
commit
f9a7fb4b2f
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -183,6 +183,17 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_log"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@ -258,6 +269,7 @@ dependencies = [
|
||||
"pyo3",
|
||||
"rayon",
|
||||
"statrs",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -274,7 +286,9 @@ name = "finlib-wasm"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"finlib",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
@ -30,6 +30,7 @@ ndarray-stats = "0.6.0"
|
||||
nalgebra = "0.33.2"
|
||||
statrs = "0.18.0"
|
||||
log = "0.4.25"
|
||||
wasm-bindgen = "0.2.100"
|
||||
pyo3 = { version = "0.23.4", features = ["extension-module", "abi3-py38"] }
|
||||
pyo3-log = "0.12.1"
|
||||
|
||||
|
@ -13,7 +13,7 @@ readme.workspace = true
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
finlib = { path = "../finlib" }
|
||||
finlib = { path = "../finlib", features = ["ffi"] }
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.28.0"
|
||||
|
@ -16,8 +16,10 @@ crate-type = ["cdylib", "rlib"]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.100"
|
||||
wasm-bindgen = { workspace = true }
|
||||
finlib = { path = "../finlib", features = ["wasm"] }
|
||||
log = { workspace = true }
|
||||
console_log = { version = "1.0.0", features = ["color"] }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
|
@ -1,4 +1,13 @@
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use console_log;
|
||||
use log::Level;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
fn start() {
|
||||
if let Err(_) = console_log::init_with_level(Level::Debug) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Interest { }
|
||||
|
@ -11,6 +11,7 @@ documentation.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
pyo3 = { workspace = true, optional = true }
|
||||
rayon = { workspace = true, optional = true }
|
||||
ndarray = { workspace = true }
|
||||
@ -22,5 +23,6 @@ getrandom = "*"
|
||||
|
||||
[features]
|
||||
py = ["dep:pyo3"]
|
||||
wasm = ["getrandom/js"]
|
||||
parallel = ["dep:rayon"]
|
||||
wasm = ["getrandom/js", "dep:wasm-bindgen"]
|
||||
parallel = ["dep:rayon"]
|
||||
ffi = []
|
@ -1,4 +1,6 @@
|
||||
pub mod interest;
|
||||
pub mod stats;
|
||||
pub mod util;
|
||||
pub mod risk;
|
||||
pub mod risk;
|
||||
#[cfg(feature = "py")]
|
||||
pub mod py;
|
30
finlib/src/py/mod.rs
Normal file
30
finlib/src/py/mod.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use pyo3::prelude::*;
|
||||
use crate::risk::portfolio::{Portfolio, PortfolioAsset};
|
||||
|
||||
#[pymethods]
|
||||
impl Portfolio {
|
||||
|
||||
#[new]
|
||||
pub fn init(assets: Vec<PortfolioAsset>) -> Self {
|
||||
Portfolio::from(assets)
|
||||
}
|
||||
|
||||
#[pyo3(name = "is_valid")]
|
||||
pub fn is_valid_py(&self) -> bool {
|
||||
self.is_valid()
|
||||
}
|
||||
|
||||
#[pyo3(name = "value_at_risk")]
|
||||
pub fn value_at_risk_py(&mut self, confidence: f64) -> PyResult<Option<f64>> {
|
||||
Ok(self.value_at_risk(confidence))
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PortfolioAsset {
|
||||
|
||||
#[new]
|
||||
pub fn init(portfolio_weight: f64, name: String, values: Vec<f64>) -> Self {
|
||||
PortfolioAsset::new(portfolio_weight, name, values)
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod var;
|
||||
pub mod var;
|
||||
pub mod portfolio;
|
||||
|
201
finlib/src/risk/portfolio.rs
Normal file
201
finlib/src/risk/portfolio.rs
Normal file
@ -0,0 +1,201 @@
|
||||
use ndarray::prelude::*;
|
||||
use ndarray_stats::CorrelationExt;
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(feature = "py")]
|
||||
use pyo3::prelude::*;
|
||||
use crate::risk::var::varcovar::portfolio_value_at_risk;
|
||||
use crate::stats;
|
||||
use crate::util::roc::rates_of_change;
|
||||
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Clone)]
|
||||
pub struct Portfolio {
|
||||
assets: Vec<PortfolioAsset>
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum ValueType {
|
||||
Absolute,
|
||||
RateOfChange
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Clone)]
|
||||
pub struct PortfolioAsset {
|
||||
portfolio_weight: f64,
|
||||
name: String,
|
||||
values: Vec<f64>,
|
||||
value_type: ValueType
|
||||
}
|
||||
|
||||
impl PortfolioAsset {
|
||||
pub fn new(portfolio_weight: f64, name: String, values: Vec<f64>) -> PortfolioAsset {
|
||||
PortfolioAsset {
|
||||
portfolio_weight, name, values, value_type: ValueType::Absolute
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_rates_of_change(&mut self) {
|
||||
match self.value_type {
|
||||
ValueType::Absolute => {
|
||||
self.values = rates_of_change(&self.values).collect();
|
||||
self.value_type = ValueType::RateOfChange;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mean_and_std(&self) -> Option<(f64, f64)> {
|
||||
match self.value_type {
|
||||
ValueType::Absolute => {
|
||||
let roc = rates_of_change(&self.values).collect::<Vec<f64>>();
|
||||
Some((stats::mean(&roc), stats::sample_std_dev(&roc)))
|
||||
}
|
||||
ValueType::RateOfChange => {
|
||||
Some((stats::mean(&self.values), stats::sample_std_dev(&self.values)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Portfolio {
|
||||
pub fn from(assets: Vec<PortfolioAsset>) -> Portfolio {
|
||||
Portfolio {
|
||||
assets
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_asset_weight(&self) -> impl Iterator<Item=f64> + use<'_> {
|
||||
self.assets
|
||||
.iter()
|
||||
.map(|x| x.portfolio_weight)
|
||||
}
|
||||
|
||||
pub fn apply_rates_of_change(&mut self) {
|
||||
for asset in self.assets.iter_mut() {
|
||||
asset.apply_rates_of_change();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valid_sizes(&self) -> bool {
|
||||
let mut last_value_length: Option<usize> = None;
|
||||
|
||||
for asset in &self.assets {
|
||||
match last_value_length {
|
||||
None => {
|
||||
last_value_length = Some(asset.values.len());
|
||||
}
|
||||
Some(l) => {
|
||||
if l != asset.values.len() {
|
||||
return false;
|
||||
}
|
||||
last_value_length = Some(asset.values.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn valid_weights(&self) -> bool {
|
||||
let mut weight = 1 as f64;
|
||||
|
||||
for asset in &self.assets {
|
||||
weight -= asset.portfolio_weight;
|
||||
}
|
||||
|
||||
f64::abs(weight) < 0.01
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.valid_sizes() && self.valid_weights()
|
||||
}
|
||||
|
||||
pub fn get_matrix(&self) -> Option<Array2<f64>> {
|
||||
if self.assets.is_empty() || !self.valid_sizes() {
|
||||
return None;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Some(matrix.into_owned())
|
||||
}
|
||||
|
||||
pub fn get_mean_and_std(&mut self) -> Option<(f64, f64)> {
|
||||
if !self.valid_sizes() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.apply_rates_of_change();
|
||||
let m = self.get_matrix();
|
||||
if m.is_none() {
|
||||
return None;
|
||||
}
|
||||
let m = m.unwrap();
|
||||
|
||||
let cov = m.cov(1.);
|
||||
if cov.is_err() {
|
||||
return None;
|
||||
}
|
||||
let cov = cov.unwrap();
|
||||
let mean_return = m.mean_axis(Axis(1));
|
||||
if mean_return.is_none() {
|
||||
return None;
|
||||
}
|
||||
let mean_return = mean_return.unwrap();
|
||||
let asset_weights = Array::from_vec(
|
||||
self.get_asset_weight().collect::<Vec<f64>>()
|
||||
).to_owned();
|
||||
|
||||
let porfolio_mean_return = mean_return.dot(&asset_weights);
|
||||
let portfolio_stddev = f64::sqrt(asset_weights.t().dot(&cov).dot(&asset_weights));
|
||||
|
||||
Some((porfolio_mean_return, portfolio_stddev))
|
||||
}
|
||||
|
||||
pub fn value_at_risk(&mut self, confidence: f64) -> Option<f64> {
|
||||
portfolio_value_at_risk(self, confidence)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn var_test() {
|
||||
let assets = vec![
|
||||
PortfolioAsset::new(0.3, "awdad".to_string(), vec![2f64, 3f64, 4f64]),
|
||||
PortfolioAsset::new(0.7, "awdad".to_string(), vec![1f64, 6f64, 8f64]),
|
||||
];
|
||||
|
||||
let m = Portfolio::from(assets).get_matrix().unwrap();
|
||||
println!("matrix 0; {:?}", m);
|
||||
|
||||
let col = m.row(0);
|
||||
println!("column 0; {:?}", col);
|
||||
let cov = m.cov(1.);
|
||||
|
||||
println!("cov 0; {:?}", cov);
|
||||
|
||||
|
||||
col.len();
|
||||
}
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
pub mod historical;
|
||||
pub mod varcovar;
|
||||
pub mod varcovar;
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::util::roc::rates_of_change;
|
||||
use crate::stats;
|
||||
use crate::util::roc::rates_of_change;
|
||||
use ndarray_stats::CorrelationExt;
|
||||
|
||||
use crate::risk::portfolio::Portfolio;
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::prelude::*;
|
||||
use statrs::distribution::{ContinuousCDF, Normal};
|
||||
|
||||
// https://medium.com/@serdarilarslan/value-at-risk-var-and-its-implementation-in-python-5c9150f73b0e
|
||||
|
||||
pub fn value_at_risk(values: &[f64], confidence: f64) -> f64 {
|
||||
@ -16,4 +17,44 @@ pub fn value_at_risk(values: &[f64], confidence: f64) -> f64 {
|
||||
let n = Normal::new(0.0, 1.0).unwrap();
|
||||
|
||||
mean + std_dev * n.inverse_cdf(confidence)
|
||||
}
|
||||
|
||||
pub fn portfolio_value_at_risk(portfolio: &mut Portfolio, confidence: f64) -> Option<f64> {
|
||||
match portfolio.get_mean_and_std() {
|
||||
None => None,
|
||||
Some((mean, std_dev)) => {
|
||||
let n = Normal::new(0.0, 1.0).unwrap();
|
||||
Some(mean + std_dev * n.inverse_cdf(confidence))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::risk::portfolio::PortfolioAsset;
|
||||
|
||||
#[test]
|
||||
fn var_test() {
|
||||
let assets = vec![
|
||||
PortfolioAsset::new(0.3, "awdad".to_string(), vec![2f64, 3f64, 4f64]),
|
||||
PortfolioAsset::new(0.7, "awdad".to_string(), vec![1f64, 6f64, 8f64]),
|
||||
];
|
||||
|
||||
let mut portfolio = Portfolio::from(assets);
|
||||
|
||||
portfolio_value_at_risk(&mut portfolio, 0.1);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_test_one_asset() {
|
||||
let assets = vec![
|
||||
PortfolioAsset::new(0.3, "awdad".to_string(), vec![2f64, 3f64, 4f64])
|
||||
];
|
||||
|
||||
let mut portfolio = Portfolio::from(assets);
|
||||
|
||||
portfolio_value_at_risk(&mut portfolio, 0.1);
|
||||
}
|
||||
}
|
417
notebooks/varcovar_var_portfolio.ipynb
Normal file
417
notebooks/varcovar_var_portfolio.ipynb
Normal file
File diff suppressed because one or more lines are too long
@ -4,6 +4,11 @@ use pyo3::prelude::*;
|
||||
mod pyfinlib {
|
||||
use super::*;
|
||||
|
||||
#[pymodule_export]
|
||||
use finlib::risk::portfolio::Portfolio;
|
||||
#[pymodule_export]
|
||||
use finlib::risk::portfolio::PortfolioAsset;
|
||||
|
||||
#[pymodule_init]
|
||||
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
pyo3_log::init();
|
||||
|
Loading…
x
Reference in New Issue
Block a user