# Equal Risk Contribution portfolio scipy optimization not working

I'm trying to create a tool for an equal risk contribution portfolio,essentially following this article (https://quantdare.com/risk-parity-in-python/) but it is failing at the last step (def risk_parity_weights) as the scipy optimizer isn't working. It keeps giving me the initial weights as the optimized weights, and I know they aren't the optimized weights because even Excel Solver has been able to optimize this. All the others functions have been checked and are correct. Not sure what I'm doing wrong - please help!

import pandas as pd
pd.core.common.is_list_like = pd.api.types.is_list_like
import numpy as np
import datetime
from scipy.optimize import minimize
Tolerance = 1e-10

def calculate_risk_contribution(weights,covariances):

#Convert weights array to numpy matrix
weights = np.matrix(weights)

#Calculate portfolio st.dev
portfolio_stdev = np.sqrt(weights*covariances*weights.T)[0,0]

#Calculate Marginal Risk Contribution of each asset
MRC = covariances*weights.T/portfolio_stdev

#Calculate Risk Contribution of each asset
RC = np.multiply(MRC,weights.T)
return RC

def risk_budget_objective_error(weights,*args):

#Covariance table occupies the first position in args variable
covariances = args

#State risk budgets
assets_risk_budget = args

#Convert weights array to numpy matrix
weights = np.matrix(weights)

#Calculate portfolio st_dev
portfolio_stdev = calculate_portfolio_stdev(ca_begweights,ca_cov)

#Calculate risk contributions
assets_risk_contribution = calculate_risk_contribution(ca_begweights,ca_cov)

#Calculate desired risk contribution of each asset
assets_risk_target = np.asmatrix(np.multiply(portfolio_stdev,assets_risk_budget))

#Calculate error between desired contribution and calculated distribution of each asset
error = sum(np.square(assets_risk_contribution - assets_risk_target.T))[0,0]

return error

def risk_parity_weights(covariances,assets_risk_budget, initial_weights):

#Constraints to optimization
#sum equals 100%
cons = ({'type':'eq','fun':lambda x: np.sum(x) - 1.0},
{'type':'ineq','fun':lambda x: x})

#Optimization in scipy
optimize_result = minimize(risk_budget_objective_error,
x0 = initial_weights,
args = (covariances, assets_risk_budget),
method = 'SLSQP',
constraints = cons,
tol = Tolerance,
options = {'disp':True})

#Get optimized weights
weights = optimize_result.x

return weights


risk_parity_weights(ca_cov,risk_budget_all, ca_begweights) gives me

Optimization terminated successfully.    (Exit mode 0)
Current function value: 9.54000328523598e-07
Iterations: 1
Function evaluations: 5


see data below

    ca_cov = array([[ 5.28024463e-06, 3.29734889e-07, -7.04781216e-08], [ 3.29734889e-07, 1.32373854e-05, 3.71807979e-08], [-7.04781216e-08, 3.71807979e-08, 3.50845569e-05]])

risk_budget_all = Unnamed: 1 0.333333 Unnamed: 2 0.333333 Unnamed: 3 0.333333 Name: Risk Budget, dtype: object

ca_begweights = array([0.33333333, 0.33333333, 0.33333333])

• What is the problem data, i.e. what are the values of ca_cov, risk_budget_all and ca_begweights when you call risk_parity_weights() ? – Alex C Sep 19 '18 at 18:48
• Couldn't figure out how to put code in comments so I added the data to the original post. Let me know if this is what you meant – GbAni Sep 19 '18 at 19:14
• do your initial weights satisfy the constraints? I have had problems with SLSQP before when the initial weights do not. Your inequality does not return a scalar, so I dont know if this will work. Try instead bounds= ((0,None),) * len(initial_weight) rather than the inequality constraint. – Attack68 Sep 19 '18 at 20:26
• @Attack68 unfortunately this didn't work - gave me the same answer – GbAni Sep 20 '18 at 15:02
• You can find a python package and paper on github to solve efficiently. – user1562 Sep 21 '18 at 9:26

Further to my comment is it because your functions are returning matrix's which are deprecated?

Why not re-write your functions using ndarrays;

import numpy as np
ca_cov = np.array([[ 5.28024463e-06, 3.29734889e-07, -7.04781216e-08], [ 3.29734889e-07, 1.32373854e-05, 3.71807979e-08], [-7.04781216e-08, 3.71807979e-08, 3.50845569e-05]])
ca_ini_weights = np.array([0.33333333, 0.33333333, 0.33333333])

def risk_contribution(weights,covariances):
# weights: ndarray of shape (n); covariances: ndarray of shape (n,n)
s_dev = np.sqrt(np.einsum('i,ij,j->', weights, covariances, weights))
risk_contrib = np.einsum('i,ij,j->i',weights, covariances, weights) / s_dev
return risk_contrib

risk_contribution(ca_ini_weights, ca_cov):
>>> array([0.00025082, 0.00061599, 0.00158709])


As I looked to improve your objective function I noticed it had one major flaw:

It accepts weights as an arguments but then uses ca_begweights in all its calculations so as an objective function all it ever does is return the initial value. In which case it returns the 'optimal' value given what it knows,.

I figured out the issue, in the risk_budget_objective_error(weights,*args) function, I used pre-defined variables to figure calculate portfolio_stdev and assets_risk_contribution, which is the optimizer kept spitting back the initial weights after only one iteration.

Instead, I should have used portfolio_stdev = calculate_portfolio_stdev(weights,covariances) and assets_risk_contribution = calculate_risk_contribution(weights,covariances). This worked for me.

EDIT: I now realize that this is what @Attack68 said much earlier, but I couldn't understand at the time.