0
$\begingroup$

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 pandas_datareader.data as web
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[0]

    #State risk budgets
    assets_risk_budget = args[1]

    #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
            Gradient evaluations: 1

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]) 
$\endgroup$
  • $\begingroup$ 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() ? $\endgroup$ – Alex C Sep 19 '18 at 18:48
  • 1
    $\begingroup$ 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 $\endgroup$ – GbAni Sep 19 '18 at 19:14
  • $\begingroup$ 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. $\endgroup$ – Attack68 Sep 19 '18 at 20:26
  • $\begingroup$ @Attack68 unfortunately this didn't work - gave me the same answer $\endgroup$ – GbAni Sep 20 '18 at 15:02
  • $\begingroup$ You can find a python package and paper on github to solve efficiently. $\endgroup$ – user1562 Sep 21 '18 at 9:26
1
$\begingroup$

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,.

$\endgroup$
1
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.