Financionerioncios
Orenji
Riskfolio-Lib
Dany Cajas
import numpy as np
import pandas as pd
import yfinance as yf
import warnings
warnings.filterwarnings("ignore")
pd.options.display.float_format = '{:.4%}'.format
# Date range
start = '2016-01-01'
end = '2019-12-30'
# Tickers of assets
assets = ['JCI', 'TGT', 'CMCSA', 'CPB', 'MO', 'APA', 'MMC', 'JPM',
'ZION', 'PSA', 'BAX', 'BMY', 'LUV', 'PCAR', 'TXT', 'TMO',
'DE', 'MSFT', 'HPQ', 'SEE', 'VZ', 'CNP', 'NI', 'T', 'BA']
assets.sort()
# Downloading data
data = yf.download(assets, start = start, end = end)
data = data.loc[:,('Adj Close', slice(None))]
data.columns = assets
[*********************100%***********************] 25 of 25 completed
# Calculating returns
Y = data[assets].pct_change().dropna()
display(Y.head())
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | |||||||||||||||||||||
2016-01-05 | -2.0256% | 0.4057% | 0.4035% | 1.9693% | 0.0180% | 0.9305% | 0.3678% | 0.5783% | 0.9483% | -1.1953% | ... | 1.5881% | 0.0212% | 2.8236% | 0.9759% | 0.6987% | 1.7539% | -0.1730% | 0.2410% | 1.3734% | -1.0857% |
2016-01-06 | -11.4863% | -1.5879% | 0.2411% | -1.7557% | -0.7727% | -1.2473% | -0.1736% | -1.1239% | -3.5867% | -0.9551% | ... | 0.5547% | 0.0212% | 0.1592% | -1.5646% | 0.3108% | -1.0155% | -0.7653% | -3.0048% | -0.9035% | -2.9145% |
2016-01-07 | -5.1389% | -4.1922% | -1.6573% | -2.7699% | -1.1047% | -1.9769% | -1.2206% | -0.8855% | -4.6058% | -2.5393% | ... | -2.2066% | -3.0310% | -1.0411% | -3.1557% | -1.6148% | -0.2700% | -2.2845% | -2.0570% | -0.5492% | -3.0020% |
2016-01-08 | 0.2736% | -2.2705% | -1.6037% | -2.5425% | 0.1099% | -0.2241% | 0.5706% | -1.6402% | -1.7641% | -0.1649% | ... | -0.1539% | -1.1366% | -0.7308% | -0.1448% | 0.0895% | -3.3838% | -0.1116% | -1.1387% | -0.9719% | -1.1254% |
2016-01-11 | -4.3384% | 0.1692% | -1.6851% | -1.0216% | 0.0915% | -1.1791% | 0.5674% | 0.5287% | 0.6616% | 0.0330% | ... | 1.6436% | 0.0000% | 0.9869% | -0.1450% | 1.2225% | 1.4570% | 0.5366% | -0.4607% | 0.5799% | -1.9919% |
5 rows × 25 columns
import riskfolio as rp
# Plotting Assets Clusters
ax = rp.plot_dendrogram(returns=Y,
codependence='pearson',
linkage='single',
k=None,
max_k=10,
leaf_order=True,
ax=None)
The dendrogram above suggest that optimal number of clusters are four. However HRP portfolios don't use a number of clusters as input.
This is the original model proposed by López de Prado (2016). Riskfolio-Lib expand this model to 32 risk measures.
# Building the portfolio object
port = rp.HCPortfolio(returns=Y)
# Estimate optimal portfolio:
model = 'HRP' # Could be HRP or HERC
codependence = 'pearson' # Correlation matrix used to group assets in clusters
rm = 'MV' # Risk measure used, this time will be variance
rf = 0 # Risk free rate
linkage = 'single' # Linkage method used to build clusters
max_k = 10 # Max number of clusters used in two difference gap statistic, only for HERC model
leaf_order = True # Consider optimal order of leafs in dendrogram
w = port.optimization(model=model,
codependence=codependence,
rm=rm,
rf=rf,
linkage=linkage,
max_k=max_k,
leaf_order=leaf_order)
display(w.T)
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 1.4534% | 2.3214% | 4.3891% | 3.2421% | 4.4245% | 5.2409% | 3.3767% | 2.3246% | 2.1718% | 5.4299% | ... | 5.0545% | 1.8952% | 6.0077% | 3.7508% | 5.8637% | 3.1666% | 5.2246% | 3.0513% | 9.2365% | 2.2744% |
1 rows × 25 columns
# Plotting the composition of the portfolio
ax = rp.plot_pie(w=w,
title='HRP Naive Risk Parity',
others=0.05,
nrow=25,
cmap="tab20",
height=8,
width=10,
ax=None)
# Plotting the risk contribution per asset
mu = Y.mean()
cov = Y.cov() # Covariance matrix
returns = Y # Returns of the assets
ax = rp.plot_risk_con(w=w,
cov=cov,
returns=returns,
rm=rm,
rf=0,
alpha=0.05,
color="tab:blue",
height=6,
width=10,
t_factor=252,
ax=None)
asset_classes = {'Assets': ['JCI','TGT','CMCSA','CPB','MO','APA','MMC','JPM',
'ZION','PSA','BAX','BMY','LUV','PCAR','TXT','TMO',
'DE','MSFT','HPQ','SEE','VZ','CNP','NI','T','BA'],
'Industry': ['Consumer Discretionary','Consumer Discretionary',
'Consumer Discretionary', 'Consumer Staples',
'Consumer Staples','Energy','Financials',
'Financials','Financials','Financials',
'Health Care','Health Care','Industrials','Industrials',
'Industrials','Health care','Industrials',
'Information Technology','Information Technology',
'Materials','Telecommunications Services','Utilities',
'Utilities','Telecommunications Services','Financials']}
asset_classes = pd.DataFrame(asset_classes)
asset_classes = asset_classes.sort_values(by=['Assets'])
constraints = {'Disabled': [False, False, False, False, False],
'Type': ['Assets', 'Assets', 'All Assets',
'Each asset in a class', 'Each asset in a class'],
'Set': ['', '', '','Industry', 'Industry'],
'Position': ['HPQ', 'PSA', '', 'Financials', 'Information Technology'],
'Sign': ['>=', '<=', '<=', '<=', '<='],
'Weight': [0.01, 0.05, 0.06, 0.04, 0.02]}
constraints = pd.DataFrame(constraints)
display(constraints)
Disabled | Type | Set | Position | Sign | Weight | |
---|---|---|---|---|---|---|
0 | False | Assets | HPQ | >= | 1.0000% | |
1 | False | Assets | PSA | <= | 5.0000% | |
2 | False | All Assets | <= | 6.0000% | ||
3 | False | Each asset in a class | Industry | Financials | <= | 4.0000% |
4 | False | Each asset in a class | Industry | Information Technology | <= | 2.0000% |
# Estimate optimal portfolio with constraints:
w_max, w_min = rp.hrp_constraints(constraints, asset_classes)
port.w_max = w_max
port.w_min = w_min
w_1 = port.optimization(model=model,
codependence=codependence,
rm=rm,
rf=rf,
linkage=linkage,
max_k=max_k,
leaf_order=leaf_order)
display(w_1.T)
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 6.0000% | 3.1161% | 4.6732% | 3.4520% | 4.2589% | 5.8543% | 4.0944% | 3.1204% | 2.0000% | 6.0000% | ... | 5.6461% | 2.0179% | 4.0000% | 3.9936% | 6.0000% | 3.3715% | 5.5627% | 3.4169% | 4.2589% | 2.4216% |
1 rows × 25 columns
# Plotting the composition of the portfolio with constraints
ax = rp.plot_pie(w=w_1,
title='HRP Naive Risk Parity',
others=0.05,
nrow=25,
cmap="tab20",
height=8,
width=10,
ax=None)
# Plotting the risk contribution per asset
mu = Y.mean()
cov = Y.cov() # Covariance matrix
returns = Y # Returns of the assets
ax = rp.plot_risk_con(w=w_1,
cov=cov,
returns=returns,
rm=rm,
rf=0,
alpha=0.05,
color="tab:blue",
height=6,
width=10,
t_factor=252,
ax=None)