Risk Contribution

Table of contents

Overview of Risk Contribution

In a portfolio with 2 assets, total portfolio risk is calculated by

$\sigma_p^2 = w_1^2 \sigma_1^2 + w_2^1 \sigma_2^2 + 2w_1 w_2 \sigma_1 \sigma_2 \rho_{1, 2}$

In a portfolio with N assets, total portfolio risk is calculated by

$\sigma_p^2 = \sum_{i=1}^{N}w_i^2\sigma_i^2 + \sum_{i=1}^{N} \sum_{j \neq i}^{N} w_i w_j \sigma_{ij}$

To calculate the risk contribution of one asset, take the contribution of asset i's variance, $w_i^2 \sigma_i^2$, and add the part of the overlapping components of the covariance where asset i is involved: $\sum_{j \neq i}^{N} w_i w_j \sigma_{ij}$

To get the proportion of asset i's risk contribution to the total portfolio risk, divide its risk contribution by the total portfolio risk, $\sigma_p^2$

$p_i = \frac{w_i^2\sigma_i^2 + \sum_{j \neq i}^{N}w_i w_j \sigma_{ij}}{\sigma_p^2}$

def risk_contribution(w,cov):
    """
    Compute the contributions to risk of the constituents of a portfolio, given a set of portfolio weights and a covariance matrix
    """
    total_portfolio_var = portfolio_vol(w,cov)**2
    # Marginal contribution of each constituent
    marginal_contrib = cov@w
    risk_contrib = np.multiply(marginal_contrib,w.T)/total_portfolio_var
    return risk_contrib

If you calculate the risk contribution of each asset in your portfolio, you can find the effective number of correlated bets (ENCB) with 1 divided by the sum of the squared risk contributions.

$\text{ENCB} = (\sum_{i=1}^N p_i^2)^{-1}$

From this formula, we can see that ENCB is maximized when all the risk contributions are equal. A risk parity portfolio is a portfolio where all risk contributions are equal.

There's no analytical expression to calculate the weights for the risk parity portfolio. To calculate them, you need to use a numerical optimization algorithm:

def target_risk_contributions(target_risk, cov):
    """
    Returns the weights of the portfolio that gives you the weights such
    that the contributions to portfolio risk are as close as possible to
    the target_risk, given the covariance matrix
    """
    n = cov.shape[0]
    init_guess = np.repeat(1/n, n)
    bounds = ((0.0, 1.0),) * n # an N-tuple of 2-tuples!
    # construct the constraints
    weights_sum_to_1 = {'type': 'eq',
                        'fun': lambda weights: np.sum(weights) - 1
    }
    def msd_risk(weights, target_risk, cov):
        """
        Returns the Mean Squared Difference in risk contributions
        between weights and target_risk
        """
        w_contribs = risk_contribution(weights, cov)
        return ((w_contribs-target_risk)**2).sum()

    weights = minimize(msd_risk, init_guess,
                       args=(target_risk, cov), method='SLSQP',
                       options={'disp': False},
                       constraints=(weights_sum_to_1,),
                       bounds=bounds)
    return weights.x

def equal_risk_contributions(cov):
    """
    Returns the weights of the portfolio that equalizes the contributions
    of the constituents based on the given covariance matrix
    """
    n = cov.shape[0]
    return target_risk_contributions(target_risk=np.repeat(1/n,n), cov=cov)

def weight_erc(r, cov_estimator=sample_cov, **kwargs):
    """
    Produces the weights of the ERC portfolio given a covariance matrix of the returns 
    """
    est_cov = cov_estimator(r, **kwargs)
    return equal_risk_contributions(est_cov)

Calculate the risk contributions of assets in a cap-weighted portfolio

Use the 30 industry returns dataset from 1990 to 2015. Use cap-weights from 1990-01 as the initial weights.

In [1]:
import edhec_risk_kit as erk
import numpy as np
import pandas as pd

ind_rets = erk.get_ind_returns(n_inds=30)['1990':'2018']
ind_mcaps = erk.get_ind_market_caps(n_inds=30, weights=True)['1990':'2018']
cw_weights = erk.weight_cw(r=ind_rets['1990-01'], cap_weights=ind_mcaps['1990-1'])

Which industry had the highest risk contribution?

In [2]:
cov = ind_rets.cov()
cw_rc = erk.risk_contribution(w=cw_weights, cov=cov)
cw_rc.idxmax()
Out[2]:
'Fin'

Top 5 industries in terms of risk contribution?

In [3]:
cw_rc.sort_values(inplace=True, ascending=False)
cw_rc.head()
Out[3]:
Fin      0.154304
Telcm    0.103318
BusEq    0.079783
Oil      0.078628
Hlth     0.054381
dtype: float64

Bottom 5 industries in terms of risk contribution?

In [4]:
cw_rc.tail()
Out[4]:
Meals    0.008078
Mines    0.007162
Clths    0.005693
Txtls    0.003657
Coal     0.000549
dtype: float64

Calculate the risk contributions of assets in a equal-weighted portfolio

In [5]:
ew_weights = erk.weight_ew(r=ind_rets['1990-01'], cap_weights=ind_mcaps['1990-1'])
ew_rc = erk.risk_contribution(w=ew_weights, cov=cov)
ew_rc.sort_values(inplace=True, ascending=False)
In [6]:
ew_rc.head()
Out[6]:
Steel    0.051301
Coal     0.046956
Txtls    0.045735
Autos    0.044962
FabPr    0.044463
dtype: float64

Bottom 5 contributors of risk

In [7]:
ew_rc.tail()
Out[7]:
Hshld    0.021112
Smoke    0.019548
Food     0.019267
Beer     0.018846
Util     0.014544
dtype: float64

Compare the largest contribution to portfolio risk in the cap-weight vs equal weighted portfolio

In [8]:
print('Max risk contribution of a single asset in the cap-weighted portfolio:', cw_rc.max())
print('Max risk contribution of a single asset in the equal-weighted portfolio:', ew_rc.max())
Max risk contribution of a single asset in the cap-weighted portfolio: 0.15430426349844867
Max risk contribution of a single asset in the equal-weighted portfolio: 0.05130070432408191
In [9]:
weights_erc = pd.Series(erk.weight_erc(r=ind_rets['1990':'2014']), index=ind_rets.columns)
weights_erc.sort_values(ascending=False, inplace=True)
In [10]:
# highest allocation in ERC portfolio
weights_erc.head()
Out[10]:
Util     0.062375
Beer     0.049559
Food     0.049467
Hlth     0.046716
Smoke    0.045797
dtype: float64
In [11]:
# lowest allocation in ERC portfolio
weights_erc.tail()
Out[11]:
FabPr    0.023851
Autos    0.023244
Coal     0.022898
Txtls    0.022894
Steel    0.021020
dtype: float64