Risk parity builds portfolios by balancing contributions to total portfolio risk rather than allocating capital equally. It addresses a practical trap: a 60/40 stock-bond portfolio can look diversified by dollars while stocks supply more than 117% of its risk contribution. Equal risk contributions occur near 20% stocks in the two-asset illustration. section 11.0 section 11.1.2
For weights w and covariance matrix Σ, asset i’s normalized contribution is w_i(Σw)_i/(wᵀΣw). General risk parity makes these contributions equal across assets. It can be found by minimizing ½wᵀΣw − γΣ_i log(w_i): the first-order condition makes each unnormalized contribution equal to γ. section 11.2.2
Common constructions differ by the covariance and contribution target:
- Naive risk parity assumes a diagonal covariance matrix and sets weights proportional to inverse volatility, then rescales them to sum to one. section 11.2.1
- General risk parity uses convex optimization when assets are correlated, while preserving equal instrument-level risk contributions. section 11.2.2
- Weighted risk parity changes the log-weight penalties so several instruments can share a chosen top-level asset-class risk structure rather than letting asset-class risk depend on instrument counts. section 11.2.3
Eight instruments across three asset classes
If four equity, three bond, and one commodity instruments receive equal instrument-level risk contributions, equities receive 50% of total risk, bonds 3/8, and commodities 1/8. Assigning log-term weights of 1/4 to each equity, 1/3 to each bond, and 1 to commodities targets equal aggregate contributions from the three asset classes while balancing instruments within them. section 11.2.3
Listing 11.4 Weighted risk parity in Python
import yfinance as yf
tickers = ['VV', 'VB', 'VEA', 'VWO', 'AGG', 'BNDX', 'EMB', 'DJP']
rets = yf.download(tickers, start='2014-01-01', end='2021-12-31')
rets = rets['Adj Close'].pct_change().dropna(axis=0, how='any')[tickers]
sigma = np.matrix(rets.cov().values) * 252
N = sigma.shape[0]
w = cp.Variable(N)
gamma = np.concatenate((np.ones(4) / 4, np.ones(3) / 3, np.ones(1) / 1))
objective = cp.Minimize(0.5 * cp.quad_form(w, sigma) - (gamma @ cp.log(w)))
constraints = []
prob = cp.Problem(objective, constraints)
result = prob.solve()
p = w.value
p_var = p @ sigma @ p
risk_contribs = np.diag(p) @ sigma @ p / (p @ sigma @ p)
risk_contribs
The Python listing estimates annualized covariance from ETF returns, solves the weighted convex program with CVXPY, and checks resulting risk contributions. section 11.2.3
Balanced risk does not set the portfolio’s desired return or volatility. In the two-asset case, the unlevered risk-parity allocation has about 4.7% volatility versus 10.3% for 60/40, so leverage can scale exposures toward a chosen risk level. Margin borrowing has interest costs and can trigger cash demands or liquidation after losses. section 11.3 section 11.3.1
