The pyrb
package uses different formulations for the risk parity problem with general linear constraints and with the addition of objective terms such as the mean return and the volatility. Nonetheless, we can fairly compare with their code for the case where only the risk parity term is included and the constraints are \(\texttt{sum}(\mathbf{w}) = 1\) and \(\mathbf{w} \geq 0\).
Following the example shown at https://github.com/jcrichard/pyrb/blob/master/notebooks/RiskBudgeting.ipynb, we have:
[1]:
from pyrb import EqualRiskContribution
import pandas as pd
import numpy as np
import riskparityportfolio as rpp
[2]:
covariance_matrix = pd.read_csv("https://raw.githubusercontent.com/jcrichard/pyrb/master/notebooks/data.csv",sep=";",index_col=0).pct_change().cov() * 260
[3]:
covariance_matrix
[3]:
US BONDS 10Y | GERMAN BONDS 10Y | S&P 500 | EUROSTOXX 50 | NIKKEI | MSCI Emerging | Commodities (CRB) | Iboxx HY US | Iboxx HY EUR | Emerging Debt | |
---|---|---|---|---|---|---|---|---|---|---|
US BONDS 10Y | 0.004116 | 0.002149 | -0.003859 | -0.005064 | -0.004085 | -0.003381 | -0.001574 | -0.000879 | -0.000245 | 0.000484 |
GERMAN BONDS 10Y | 0.002149 | 0.003150 | -0.002809 | -0.005692 | -0.003468 | -0.003604 | -0.001310 | -0.000996 | -0.000262 | 0.000280 |
S&P 500 | -0.003859 | -0.002809 | 0.042571 | 0.030959 | 0.029899 | 0.024753 | 0.008173 | 0.009952 | 0.000856 | 0.004332 |
EUROSTOXX 50 | -0.005064 | -0.005692 | 0.030959 | 0.064347 | 0.027036 | 0.031997 | 0.011019 | 0.009402 | 0.003169 | 0.006211 |
NIKKEI | -0.004085 | -0.003468 | 0.029899 | 0.027036 | 0.060668 | 0.031786 | 0.009928 | 0.010870 | 0.001777 | 0.003836 |
MSCI Emerging | -0.003381 | -0.003604 | 0.024753 | 0.031997 | 0.031786 | 0.058415 | 0.014405 | 0.011145 | 0.003468 | 0.007879 |
Commodities (CRB) | -0.001574 | -0.001310 | 0.008173 | 0.011019 | 0.009928 | 0.014405 | 0.031496 | 0.005023 | 0.001489 | 0.002312 |
Iboxx HY US | -0.000879 | -0.000996 | 0.009952 | 0.009402 | 0.010870 | 0.011145 | 0.005023 | 0.011670 | 0.001523 | 0.002549 |
Iboxx HY EUR | -0.000245 | -0.000262 | 0.000856 | 0.003169 | 0.001777 | 0.003468 | 0.001489 | 0.001523 | 0.004500 | 0.001282 |
Emerging Debt | 0.000484 | 0.000280 | 0.004332 | 0.006211 | 0.003836 | 0.007879 | 0.002312 | 0.002549 | 0.001282 | 0.008640 |
[4]:
cov = np.asarray(covariance_matrix)
[5]:
ERC = EqualRiskContribution(cov)
[6]:
%timeit ERC.solve()
320 µs ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
[7]:
optimal_weights = ERC.x
optimal_weights
[7]:
array([0.22447775, 0.27514463, 0.03922505, 0.03589295, 0.03428179,
0.02991635, 0.05459806, 0.07111012, 0.15281389, 0.08253942])
[8]:
risk_contributions_scaled = ERC.get_risk_contributions()
risk_contributions_scaled
[8]:
array([0.09999997, 0.09999999, 0.10000001, 0.10000001, 0.1 ,
0.1 , 0.1 , 0.1 , 0.1 , 0.1 ])
[9]:
b = np.ones(len(cov)) / len(cov)
[10]:
%timeit rpp.vanilla.design(cov, b)
5.9 µs ± 59.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
[11]:
rpp.vanilla.design(cov, b)
[11]:
array([0.22447778, 0.27514462, 0.03922505, 0.03589295, 0.03428179,
0.02991635, 0.05459806, 0.07111011, 0.15281388, 0.08253942])
It seems that, for this example, riskparityportfolio
is around \(40\times\) faster than pyrb
. However, more examples are to be done in order to draw more conclusive results, especially on more interesting scenarios involving general linear constraints and additional objective terms such as the mean return.
For constrained risk parity, see the pyrb notebook: https://github.com/jcrichard/pyrb/blob/master/notebooks/ConstrainedRiskBudgeting.ipynb
[12]:
vol = [0.05,0.05,0.07,0.1,0.15,0.15,0.15,0.18]
cor = np.array([[100, 80, 60, -20, -10, -20, -20, -20],
[ 80, 100, 40, -20, -20, -10, -20, -20],
[ 60, 40, 100, 50, 30, 20, 20, 30],
[-20, -20, 50, 100, 60, 60, 50, 60],
[-10, -20, 30, 60, 100, 90, 70, 70],
[-20, -10, 20, 60, 90, 100, 60, 70],
[-20, -20, 20, 50, 70, 60, 100, 70],
[-20, -20, 30, 60, 70, 70, 70, 100]])/100
cov = np.outer(vol,vol)*cor
[13]:
my_rpp = rpp.RiskParityPortfolio(covariance=cov)
[14]:
my_rpp.design()
/Users/mirca/opt/miniconda3/lib/python3.7/site-packages/jax/lib/xla_bridge.py:120: UserWarning: No GPU/TPU found, falling back to CPU.
warnings.warn('No GPU/TPU found, falling back to CPU.')
0%| | 0/500 [00:00<?, ?it/s]
[16]:
my_rpp.weights
[16]:
array([0.26830622, 0.28676891, 0.11409499, 0.09798482, 0.05613501,
0.05902912, 0.06655992, 0.05112101])
[18]:
my_rpp.risk_contributions
[18]:
array([0.12500001, 0.12500001, 0.125 , 0.12499999, 0.125 ,
0.125 , 0.125 , 0.125 ])
[19]:
# inequality constraints matrix and vector
Dmat = np.array([[0,0,0,0,-1,-1,-1,-1],
[1,-1,0,0,1,-1,0,0]])
dvec = np.array([-0.3,-0.05])
[20]:
my_rpp.design(Dmat=Dmat, dvec=dvec)
1%|▏ | 7/500 [00:00<00:44, 11.11it/s]
[22]:
my_rpp.weights
[22]:
array([0.23186426, 0.27676939, 0.12093305, 0.0704333 , 0.07264196,
0.07773683, 0.08587855, 0.06374266])
[24]:
Dmat @ my_rpp.weights
[24]:
array([-0.3 , -0.05])
[25]:
my_rpp.risk_contributions
[25]:
array([0.07508326, 0.08382447, 0.10969463, 0.08355962, 0.1614587 ,
0.1651071 , 0.16492485, 0.15634737])
[ ]: