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",
|
"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]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@ -258,6 +269,7 @@ dependencies = [
|
|||||||
"pyo3",
|
"pyo3",
|
||||||
"rayon",
|
"rayon",
|
||||||
"statrs",
|
"statrs",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -274,7 +286,9 @@ name = "finlib-wasm"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"console_log",
|
||||||
"finlib",
|
"finlib",
|
||||||
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
]
|
]
|
||||||
|
@ -30,6 +30,7 @@ ndarray-stats = "0.6.0"
|
|||||||
nalgebra = "0.33.2"
|
nalgebra = "0.33.2"
|
||||||
statrs = "0.18.0"
|
statrs = "0.18.0"
|
||||||
log = "0.4.25"
|
log = "0.4.25"
|
||||||
|
wasm-bindgen = "0.2.100"
|
||||||
pyo3 = { version = "0.23.4", features = ["extension-module", "abi3-py38"] }
|
pyo3 = { version = "0.23.4", features = ["extension-module", "abi3-py38"] }
|
||||||
pyo3-log = "0.12.1"
|
pyo3-log = "0.12.1"
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ readme.workspace = true
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
finlib = { path = "../finlib" }
|
finlib = { path = "../finlib", features = ["ffi"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.28.0"
|
cbindgen = "0.28.0"
|
||||||
|
@ -16,8 +16,10 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2.100"
|
wasm-bindgen = { workspace = true }
|
||||||
finlib = { path = "../finlib", features = ["wasm"] }
|
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
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
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]
|
#[wasm_bindgen]
|
||||||
pub struct Interest { }
|
pub struct Interest { }
|
||||||
|
@ -11,6 +11,7 @@ documentation.workspace = true
|
|||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
pyo3 = { workspace = true, optional = true }
|
pyo3 = { workspace = true, optional = true }
|
||||||
rayon = { workspace = true, optional = true }
|
rayon = { workspace = true, optional = true }
|
||||||
ndarray = { workspace = true }
|
ndarray = { workspace = true }
|
||||||
@ -22,5 +23,6 @@ getrandom = "*"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
py = ["dep:pyo3"]
|
py = ["dep:pyo3"]
|
||||||
wasm = ["getrandom/js"]
|
wasm = ["getrandom/js", "dep:wasm-bindgen"]
|
||||||
parallel = ["dep:rayon"]
|
parallel = ["dep:rayon"]
|
||||||
|
ffi = []
|
@ -1,4 +1,6 @@
|
|||||||
pub mod interest;
|
pub mod interest;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod util;
|
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 historical;
|
||||||
pub mod varcovar;
|
pub mod varcovar;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::util::roc::rates_of_change;
|
|
||||||
use crate::stats;
|
use crate::stats;
|
||||||
|
use crate::util::roc::rates_of_change;
|
||||||
|
use ndarray_stats::CorrelationExt;
|
||||||
|
|
||||||
|
use crate::risk::portfolio::Portfolio;
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use statrs::distribution::{ContinuousCDF, Normal};
|
use statrs::distribution::{ContinuousCDF, Normal};
|
||||||
|
|
||||||
// https://medium.com/@serdarilarslan/value-at-risk-var-and-its-implementation-in-python-5c9150f73b0e
|
// https://medium.com/@serdarilarslan/value-at-risk-var-and-its-implementation-in-python-5c9150f73b0e
|
||||||
|
|
||||||
pub fn value_at_risk(values: &[f64], confidence: f64) -> f64 {
|
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();
|
let n = Normal::new(0.0, 1.0).unwrap();
|
||||||
|
|
||||||
mean + std_dev * n.inverse_cdf(confidence)
|
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 {
|
mod pyfinlib {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::risk::portfolio::Portfolio;
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::risk::portfolio::PortfolioAsset;
|
||||||
|
|
||||||
#[pymodule_init]
|
#[pymodule_init]
|
||||||
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
pyo3_log::init();
|
pyo3_log::init();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user