Skip to content

Instantly share code, notes, and snippets.

@FJR2
Last active October 24, 2023 17:57
Show Gist options
  • Save FJR2/f91e9660044a482ffefdcf4ce9cc98f5 to your computer and use it in GitHub Desktop.
Save FJR2/f91e9660044a482ffefdcf4ce9cc98f5 to your computer and use it in GitHub Desktop.
import pandas as pd
import pandas_datareader.data as web
import numpy as np
import datetime
from scipy.optimize import minimize
TOLERANCE = 1e-10
def _allocation_risk(weights, covariances):
# We calculate the risk of the weights distribution
portfolio_risk = np.sqrt((weights * covariances * weights.T))[0, 0]
# It returns the risk of the weights distribution
return portfolio_risk
def _assets_risk_contribution_to_allocation_risk(weights, covariances):
# We calculate the risk of the weights distribution
portfolio_risk = _allocation_risk(weights, covariances)
# We calculate the contribution of each asset to the risk of the weights
# distribution
assets_risk_contribution = np.multiply(weights.T, covariances * weights.T) \
/ portfolio_risk
# It returns the contribution of each asset to the risk of the weights
# distribution
return assets_risk_contribution
def _risk_budget_objective_error(weights, args):
# The covariance matrix occupies the first position in the variable
covariances = args[0]
# The desired contribution of each asset to the portfolio risk occupies the
# second position
assets_risk_budget = args[1]
# We convert the weights to a matrix
weights = np.matrix(weights)
# We calculate the risk of the weights distribution
portfolio_risk = _allocation_risk(weights, covariances)
# We calculate the contribution of each asset to the risk of the weights
# distribution
assets_risk_contribution = \
_assets_risk_contribution_to_allocation_risk(weights, covariances)
# We calculate the desired contribution of each asset to the risk of the
# weights distribution
assets_risk_target = \
np.asmatrix(np.multiply(portfolio_risk, assets_risk_budget))
# Error between the desired contribution and the calculated contribution of
# each asset
error = \
sum(np.square(assets_risk_contribution - assets_risk_target.T))[0, 0]
# It returns the calculated error
return error
def _get_risk_parity_weights(covariances, assets_risk_budget, initial_weights):
# Restrictions to consider in the optimisation: only long positions whose
# sum equals 100%
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0},
{'type': 'ineq', 'fun': lambda x: x})
# Optimisation process in scipy
optimize_result = minimize(fun=_risk_budget_objective_error,
x0=initial_weights,
args=[covariances, assets_risk_budget],
method='SLSQP',
constraints=constraints,
tol=TOLERANCE,
options={'disp': False})
# Recover the weights from the optimised object
weights = optimize_result.x
# It returns the optimised weights
return weights
def get_weights(yahoo_tickers=['GOOGL', 'AAPL', 'AMZN'],
start_date=datetime.datetime(2016, 10, 31),
end_date=datetime.datetime(2017, 10, 31)):
# We download the prices from Yahoo Finance
prices = pd.DataFrame([web.DataReader(t,
'yahoo',
start_date,
end_date).loc[:, 'Adj Close']
for t in yahoo_tickers],
index=yahoo_tickers).T.asfreq('B').ffill()
# We calculate the covariance matrix
covariances = 52.0 * \
prices.asfreq('W-FRI').pct_change().iloc[1:, :].cov().values
# The desired contribution of each asset to the portfolio risk: we want all
# asset to contribute equally
assets_risk_budget = [1 / prices.shape[1]] * prices.shape[1]
# Initial weights: equally weighted
init_weights = [1 / prices.shape[1]] * prices.shape[1]
# Optimisation process of weights
weights = \
_get_risk_parity_weights(covariances, assets_risk_budget, init_weights)
# Convert the weights to a pandas Series
weights = pd.Series(weights, index=prices.columns, name='weight')
# It returns the optimised weights
return weights
@gituser768
Copy link

Nice demo! Very clear code too! I get the most weight on AAPL unless I restrict the time period to only the past few months, in which case GOOGL dominates. Pretty much what you'd expect considering the recent events/talk!

Just a heads up for those trying to run this in the future that lines 108 and 111 should be initialized with 1.0 rather than 1. Otherwise we'll just get zeros for these values :)

@gbani
Copy link

gbani commented Sep 20, 2018

I tried this and it keeps giving me back weights in the optimizer that are the same as the initial weights. Also, the optimizer only runs one iteration. Any idea why this is?

@NYjiang-Alex
Copy link

line 103, why you multiply covariance with 52.0? even though I know it may help get correct weights.

@x829901
Copy link

x829901 commented Mar 4, 2021

line 103, why you multiply covariance with 52.0? even though I know it may help get correct weights.

probably because he's using weekly data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment