Portfolio

Risk Parity/Risk Budgeting Portfolio in Python

Risk Parity Portfolio is an investment allocation strategy which focuses on the allocation of risk, rather than the allocation of capital. For example, a typical 40% bond 60% equity portfolio has a significant risk in equity. A risk parity (equal risk) portfolio is a portfolio, which individual assets, in this case equity and bond, have equal risk contribution to the portfolio risk. The allocation strategy has gained popularity in the last decades, the idea of asset allocation base on risk has been used in many strategies such as managed futures strategy, and the famous Bridgewater all weather fund. Some people argue that this allocation strategy provides better risk adjusted return than capital based allocation strategies. (I will do some simple backtest in the future)

I will go over a very basic example of what risk parity is and how to construct a simple risk parity (equal risk) portfolio  and extend it to a risk budgeting portfolio (target risk allocation)

First define the marginal risk contribution as:

Then, the risk contribution of asset j to the total portfolio is:

Risk Parity portfolio is a portfolio which RC are equal across all individual assets

For example, going back to the previous example and assume we have an equal weight portfolio of four assets with weight = 25% each, it’s RC (in total portfolio risk %) is:

To compute the weight of a risk parity portfolio, we could use the optimise function from python

Let the sum of squared error of a portfolio assets RC  be:ThenIn python

```from __future__ import division
import numpy as np
from matplotlib import pyplot as plt
from numpy.linalg import inv,pinv
from scipy.optimize import minimize

# risk budgeting optimization
def calculate_portfolio_var(w,V):
# function that calculates portfolio risk
w = np.matrix(w)
return (w*V*w.T)[0,0]

def calculate_risk_contribution(w,V):
# function that calculates asset contribution to total risk
w = np.matrix(w)
sigma = np.sqrt(calculate_portfolio_var(w,V))
# Marginal Risk Contribution
MRC = V*w.T
# Risk Contribution
RC = np.multiply(MRC,w.T)/sigma
return RC

def risk_budget_objective(x,pars):
# calculate portfolio risk
V = pars[0]# covariance table
x_t = pars[1] # risk target in percent of portfolio risk
sig_p =  np.sqrt(calculate_portfolio_var(x,V)) # portfolio sigma
risk_target = np.asmatrix(np.multiply(sig_p,x_t))
asset_RC = calculate_risk_contribution(x,V)
J = sum(np.square(asset_RC-risk_target.T))[0,0] # sum of squared error
return J

def total_weight_constraint(x):
return np.sum(x)-1.0

def long_only_constraint(x):
return x

x_t = [0.25, 0.25, 0.25, 0.25] # your risk budget percent of total portfolio risk (equal risk)
cons = ({'type': 'eq', 'fun': total_weight_constraint},
{'type': 'ineq', 'fun': long_only_constraint})
res= minimize(risk_budget_objective, w0, args=[V,x_t], method='SLSQP',constraints=cons, options={'disp': True})
w_rb = np.asmatrix(res.x)
```

Then the weights in the risk parity portfolio are

[ 0.19543974,  0.21521557,  0.16260951,  0.42673519]

and the risk contributions to the total portfolio are

The problem is actually set up to  calculate any risk budget allocation since it actually uses you can set up your target risk allocation for each asset then minimise the objective function of squared error

e.g.

`x_t = [0.3, 0.3, 0.1, 0.3] # your risk budget percent of total portfolio risk`

The weights are
[ 0.22837243, 0.25116466, 0.08875776, 0.43170515]

individual asset RC percentages are:

7 thoughts on “Risk Parity/Risk Budgeting Portfolio in Python”

1. Hansueli Müller says:

Great article – it helps me a lot solving my problem in python. However, when i run your code, I always end up with an equal weighted portfolio. Any hint you can give me? I operate with
w0 = [1/3] * 3
x_t = [1 /3] * 3
and
V=
[[ 0.00021555 0.00041903 0.0003287 ]
[ 0.00041903 0.00216102 0.00106898]
[ 0.0003287 0.00106898 0.00140555]]

any hints?

1. thequantmba says:

Hi Hansueli,
Thanks for verifying, I just checked with my code, it seems the minimize function only runs 1 iteration in this case which is not enough to find an optimal solution. It’s caused by the precision goal settings for the minimize function, which is defaulted at 1e-6, and your problem start at 1e-8.

There are two ways to modify this:

Change the risk_budget_objective function by penalising the cost and it should work. (e.g. I multiplied J*1000)
def risk_budget_objective(x,pars):
# calculate portfolio risk
V = pars[0]# covariance table
x_t = pars[1] # risk target in percent of portfolio risk
sig_p = np.sqrt(calculate_portfolio_var(x,V)) # portfolio sigma
risk_target = np.asmatrix(np.multiply(sig_p,x_t))
asset_RC = calculate_risk_contribution(x,V)
J = sum(np.square(asset_RC-risk_target.T))[0,0] *1000 # sum of squared error
return J

or you can change the ftol : float (Precision goal for the value of f in the stopping criterion) in the minimise function to something smaller (in this case I use 1e-12)
res= minimize(risk_budget_objective, w0, args=[V,x_t], method=’SLSQP’,constraints=cons, options={‘disp’: True, ‘ftol’: 1e-12})

By doing so, you would potentially run more iteration to get more precision. (a trade off)

2. Hansueli Müller says:

Hi DZ,
This helped, thanks for the hint. It is very useful for me as I am not yet very experienced in the world of python optimizers.
Best Regards

Hansueli

3. Feng says:

I had once been puzzled by the same problem with Hansueli, i.e. the minimize function only runs 1 iteration with certain covariance matrix input. I also tried two ways, i.e. (1)penalizing the cost (2)changing the stopping criterion in the minimize function to 1e-15. It solved the problem in some situation. But there is still problems with certain covariance matrix input.
For example, I have this 9*9 covariance matrix:
V=np.matrix([[3.23822414e-02,2.09586336e-02,1.19953181e-03,-3.42945597e-04,1.44226523e-03,3.18839922e-03,1.28094942e-04,1.09620895e-04,-8.14963119e-05]
,[2.09586336e-02,1.39930713e-01,6.50801720e-03,3.02103328e-03,1.08176923e-02,4.80103744e-02,7.69201371e-04,-1.25520964e-02,-1.88265113e-04]
,[1.19953181e-03,6.50801720e-03,3.69433053e-03,-5.13665176e-04,2.20203633e-03,2.31740623e-03,8.55164862e-05,-7.56440183e-04,-7.81788792e-06]
,[-3.42945597e-04,3.02103328e-03,-5.13665176e-04,1.95207568e-03,-3.27383676e-04,4.38795650e-04,2.68556355e-05,-2.18586766e-04,-2.54360233e-06]
,[1.44226523e-03,1.08176923e-02,2.20203633e-03,-3.27383676e-04,2.96759371e-03,4.80443514e-03,1.93560587e-04,-1.06510489e-03,-2.46200168e-05]
,[3.18839922e-03,4.80103744e-02,2.31740623e-03,4.38795650e-04,4.80443514e-03,2.36495619e-02,9.62021325e-04,-1.86584784e-03,-9.06395586e-05]
,[1.28094942e-04,7.69201371e-04,8.55164862e-05,2.68556355e-05,1.93560587e-04,9.62021325e-04,2.89909625e-04,5.31840917e-04,2.55116425e-06]
,[1.09620895e-04,-1.25520964e-02,-7.56440183e-04,-2.18586766e-04,-1.06510489e-03,-1.86584784e-03,5.31840917e-04,9.29821052e-03,2.14245629e-05]
,[-8.14963119e-05,-1.88265113e-04,-7.81788792e-06,-2.54360233e-06,-2.46200168e-05,-9.06395586e-05,2.55116425e-06,2.14245629e-05,5.78390121e-06]])
With equal risk budgets, the resulting weights in this risk parity portfolio are:
[ 3.96638553e-02 1.31838486e-02 1.15744406e-01 2.23200288e-01
1.00733213e-01 2.98562419e-02 3.71432543e-01 1.06185604e-01
5.36184804e-19]
The weight of the last asset is 0! And the optimization appears not to reach the 1e-15 stopping criterion:
Optimization terminated successfully. (Exit mode 0)
Current function value: 0.000738113620943
Iterations: 21
Function evaluations: 232
The possible reason is that the variance of the last asset, which represents the money market mutual fund, is far more smaller than other assets. This is the usual case in practical market. I am trying to find ways to solve this problem, and to make this algorithm more robust. Do you have any suggestions?
Thank you very much.

4. Feng says:

And when I enter different initial values, it gives totally different results. So strange that the algorithm is sensitive to initial values.
case 1.
w0=np.ones(9)*1.0/9
Optimization terminated successfully. (Exit mode 0)
Current function value: 0.000738113620943
Iterations: 21
Function evaluations: 232
The resulting weights:
[[ 3.96638553e-02 1.31838486e-02 1.15744406e-01 2.23200288e-01
1.00733213e-01 2.98562419e-02 3.71432543e-01 1.06185604e-01
5.36184804e-19]]

case 2.
w0=[0.05217992,0.04176691,0.08060158,0.03545008,0.00784548,0.00609901,0.01657915,0.07723674,0.68224114]
Optimization terminated successfully. (Exit mode 0)
Current function value: 1.00601342965e-15
Iterations: 33
Function evaluations: 363
The resulting weights:
[[ 0.00787686 0.00260464 0.020088 0.03888814 0.01903772 0.0061617
0.05590877 0.01845764 0.83097653]]

5. Feng says:

I had once been puzzled by the same problem with Hansueli, i.e. the minimize function only runs 1 iteration with certain covariance matrix input. I also tried two ways, i.e. (1)penalizing the cost (2)changing the stopping criterion in the minimize function to 1e-15. It solved the problem in some situation. But there is still problems with certain covariance matrix input.
For example, I have this 9*9 covariance matrix:
V=np.matrix([[3.23822414e-02,2.09586336e-02,1.19953181e-03,-3.42945597e-04,1.44226523e-03,3.18839922e-03,1.28094942e-04,1.09620895e-04,-8.14963119e-05]
,[2.09586336e-02,1.39930713e-01,6.50801720e-03,3.02103328e-03,1.08176923e-02,4.80103744e-02,7.69201371e-04,-1.25520964e-02,-1.88265113e-04]
,[1.19953181e-03,6.50801720e-03,3.69433053e-03,-5.13665176e-04,2.20203633e-03,2.31740623e-03,8.55164862e-05,-7.56440183e-04,-7.81788792e-06]
,[-3.42945597e-04,3.02103328e-03,-5.13665176e-04,1.95207568e-03,-3.27383676e-04,4.38795650e-04,2.68556355e-05,-2.18586766e-04,-2.54360233e-06]
,[1.44226523e-03,1.08176923e-02,2.20203633e-03,-3.27383676e-04,2.96759371e-03,4.80443514e-03,1.93560587e-04,-1.06510489e-03,-2.46200168e-05]
,[3.18839922e-03,4.80103744e-02,2.31740623e-03,4.38795650e-04,4.80443514e-03,2.36495619e-02,9.62021325e-04,-1.86584784e-03,-9.06395586e-05]
,[1.28094942e-04,7.69201371e-04,8.55164862e-05,2.68556355e-05,1.93560587e-04,9.62021325e-04,2.89909625e-04,5.31840917e-04,2.55116425e-06]
,[1.09620895e-04,-1.25520964e-02,-7.56440183e-04,-2.18586766e-04,-1.06510489e-03,-1.86584784e-03,5.31840917e-04,9.29821052e-03,2.14245629e-05]
,[-8.14963119e-05,-1.88265113e-04,-7.81788792e-06,-2.54360233e-06,-2.46200168e-05,-9.06395586e-05,2.55116425e-06,2.14245629e-05,5.78390121e-06]])
With equal risk budgets, the resulting weights in this risk parity portfolio are:
[ 3.96638553e-02 1.31838486e-02 1.15744406e-01 2.23200288e-01
1.00733213e-01 2.98562419e-02 3.71432543e-01 1.06185604e-01
5.36184804e-19]
The weight of the last asset is 0! And the optimization appears not to reach the 1e-15 stopping criterion:
Optimization terminated successfully. (Exit mode 0)
Current function value: 0.000738113620943
Iterations: 21
Function evaluations: 232
The possible reason is that the variance of the last asset, which represents the money market mutual fund, is far more smaller than other assets. This is the usual case in practical market. I am trying to find ways to solve this problem, and to make this algorithm more robust. Do you have any suggestions?
Thank you very much.

And when I enter different initial values, it gives totally different results. So strange that the algorithm is sensitive to initial values.
case 1.
w0=np.ones(9)*1.0/9
Optimization terminated successfully. (Exit mode 0)
Current function value: 0.000738113620943
Iterations: 21
Function evaluations: 232
The resulting weights:
[[ 3.96638553e-02 1.31838486e-02 1.15744406e-01 2.23200288e-01
1.00733213e-01 2.98562419e-02 3.71432543e-01 1.06185604e-01
5.36184804e-19]]

case 2.
w0=[0.05217992,0.04176691,0.08060158,0.03545008,0.00784548,0.00609901,0.01657915,0.07723674,0.68224114]
Optimization terminated successfully. (Exit mode 0)
Current function value: 1.00601342965e-15
Iterations: 33
Function evaluations: 363