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.0257% | 0.4057% | 0.4036% | 1.9693% | 0.0180% | 0.9305% | 0.3678% | 0.5783% | 0.9483% | -1.1954% | ... | 1.5881% | 0.0212% | 2.8236% | 0.9758% | 0.6987% | 1.7539% | -0.1730% | 0.2410% | 1.3734% | -1.0858% |
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.9034% | -2.9145% |
2016-01-07 | -5.1389% | -4.1922% | -1.6573% | -2.7699% | -1.1047% | -1.9770% | -1.2206% | -0.8855% | -4.6058% | -2.5394% | ... | -2.2066% | -3.0310% | -1.0410% | -3.1557% | -1.6148% | -0.2700% | -2.2844% | -2.0570% | -0.5492% | -3.0019% |
2016-01-08 | 0.2736% | -2.2705% | -1.6037% | -2.5425% | 0.1098% | -0.2240% | 0.5707% | -1.6402% | -1.7642% | -0.1649% | ... | -0.1538% | -1.1366% | -0.7308% | -0.1448% | 0.0895% | -3.3839% | -0.1117% | -1.1387% | -0.9719% | -1.1254% |
2016-01-11 | -4.3383% | 0.1692% | -1.6851% | -1.0215% | 0.0915% | -1.1792% | 0.5674% | 0.5287% | 0.6616% | 0.0330% | ... | 1.6436% | 0.0000% | 0.9869% | -0.1450% | 1.2224% | 1.4569% | 0.5367% | -0.4607% | 0.5800% | -1.9919% |
5 rows × 25 columns
import riskfolio as rp
# Building the portfolio object
port = rp.Portfolio(returns=Y)
# Calculating optimal portfolio
# Select method and estimate input parameters:
method_mu='hist' # Method to estimate expected returns based on historical data.
method_cov='hist' # Method to estimate covariance matrix based on historical data.
port.assets_stats(method_mu=method_mu, method_cov=method_cov)
# Estimate optimal portfolio:
model='Classic' # Could be Classic (historical), BL (Black Litterman) or FM (Factor Model)
rm = 'MV' # Risk measure used, this time will be variance
obj = 'Sharpe' # Objective function, could be MinRisk, MaxRet, Utility or Sharpe
hist = True # Use historical scenarios for risk measures that depend on scenarios
rf = 0 # Risk free rate
l = 0 # Risk aversion factor, only useful when obj is 'Utility'
w = port.optimization(model=model, rm=rm, obj=obj, rf=rf, l=l, hist=hist)
display(w.T)
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 0.0000% | 6.1590% | 11.5019% | 0.0000% | 0.0000% | 8.4806% | 0.0000% | 3.8193% | 0.0000% | 0.0000% | ... | 10.8264% | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 7.1804% | 0.0000% | 0.0000% | 4.2740% | 0.0000% |
1 rows × 25 columns
# Plotting the composition of the portfolio
ax = rp.plot_pie(w=w, title='Sharpe Mean Variance', others=0.05, nrow=25, cmap = "tab20",
height=6, width=10, ax=None)
# Plotting the risk composition of the portfolio
ax = rp.plot_risk_con(w, cov=port.cov, returns=port.returns, rm=rm, rf=0, alpha=0.01,
color="tab:blue", height=6, width=10, ax=None)
In this part I will calculate risk parity portfolios. First I'm going to calculate risk parity portfolio when we use variance as risk measure, then I'm going to calculate the risk parity portfolios for all available risk measures.
b = None # Risk contribution constraints vector
w_rp = port.rp_optimization(model=model, rm=rm, rf=rf, b=b, hist=hist)
display(w.T)
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 0.0000% | 6.1590% | 11.5019% | 0.0000% | 0.0000% | 8.4806% | 0.0000% | 3.8193% | 0.0000% | 0.0000% | ... | 10.8264% | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 7.1804% | 0.0000% | 0.0000% | 4.2740% | 0.0000% |
1 rows × 25 columns
ax = rp.plot_pie(w=w_rp, title='Risk Parity Variance', others=0.05, nrow=25, cmap = "tab20",
height=6, width=10, ax=None)
ax = rp.plot_risk_con(w_rp, cov=port.cov, returns=port.returns, rm=rm, rf=0, alpha=0.01,
color="tab:blue", height=6, width=10, ax=None)
# Risk Measures available:
#
# 'MV': Standard Deviation.
# 'MAD': Mean Absolute Deviation.
# 'MSV': Semi Standard Deviation.
# 'FLPM': First Lower Partial Moment (Omega Ratio).
# 'SLPM': Second Lower Partial Moment (Sortino Ratio).
# 'CVaR': Conditional Value at Risk.
# 'EVaR': Entropic Value at Risk.
# 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
# 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
# 'UCI': Ulcer Index of uncompounded cumulative returns.
rms = ['MV', 'MAD', 'MSV', 'FLPM', 'SLPM', 'CVaR',
'EVaR', 'CDaR', 'UCI', 'EDaR']
w_s = pd.DataFrame([])
port.solvers = ['MOSEK']
for i in rms:
w = port.rp_optimization(model=model, rm=i, rf=rf, b=b, hist=hist)
w_s = pd.concat([w_s, w], axis=1)
w_s.columns = rms
w_s.style.format("{:.2%}").background_gradient(cmap='YlGn')
MV | MAD | MSV | FLPM | SLPM | CVaR | EVaR | CDaR | UCI | EDaR | |
---|---|---|---|---|---|---|---|---|---|---|
APA | 2.40% | 2.20% | 2.51% | 2.03% | 2.40% | 2.45% | 2.80% | 1.16% | 1.16% | 1.05% |
BA | 3.08% | 3.22% | 2.99% | 3.30% | 3.04% | 2.87% | 2.76% | 6.48% | 6.10% | 4.23% |
BAX | 4.03% | 4.40% | 3.93% | 4.64% | 4.02% | 3.77% | 3.49% | 2.67% | 3.41% | 2.69% |
BMY | 4.12% | 4.24% | 4.19% | 4.07% | 4.07% | 3.83% | 4.67% | 2.01% | 1.59% | 2.24% |
CMCSA | 3.89% | 4.01% | 3.89% | 3.86% | 3.90% | 3.72% | 3.52% | 2.66% | 2.52% | 3.59% |
CNP | 5.20% | 5.06% | 5.22% | 5.11% | 5.34% | 5.63% | 5.50% | 7.25% | 5.67% | 9.14% |
CPB | 5.10% | 4.54% | 5.69% | 4.48% | 5.66% | 6.94% | 7.73% | 2.83% | 2.51% | 2.64% |
DE | 3.04% | 3.02% | 2.91% | 3.08% | 2.94% | 2.79% | 2.95% | 5.21% | 5.33% | 7.12% |
HPQ | 2.83% | 2.95% | 2.64% | 2.91% | 2.62% | 2.36% | 2.69% | 2.57% | 2.57% | 2.43% |
JCI | 3.61% | 3.51% | 3.60% | 3.50% | 3.58% | 3.37% | 3.27% | 1.90% | 2.03% | 1.97% |
JPM | 3.35% | 3.67% | 3.35% | 3.89% | 3.40% | 2.99% | 3.08% | 3.70% | 3.75% | 3.17% |
LUV | 3.42% | 3.37% | 3.33% | 3.13% | 3.28% | 2.92% | 3.80% | 1.98% | 1.82% | 1.76% |
MMC | 4.41% | 4.69% | 4.33% | 5.03% | 4.44% | 4.09% | 4.36% | 5.64% | 6.52% | 5.05% |
MO | 5.18% | 5.02% | 4.98% | 4.82% | 4.83% | 5.01% | 4.04% | 2.27% | 2.01% | 2.14% |
MSFT | 3.26% | 3.88% | 3.25% | 4.27% | 3.34% | 2.94% | 3.15% | 6.07% | 9.19% | 4.42% |
NI | 6.54% | 6.54% | 6.63% | 6.95% | 6.77% | 6.70% | 6.40% | 5.80% | 6.74% | 5.50% |
PCAR | 2.99% | 2.81% | 3.04% | 2.81% | 3.06% | 3.25% | 3.23% | 1.89% | 1.92% | 2.25% |
PSA | 6.87% | 6.29% | 7.30% | 5.79% | 7.14% | 7.98% | 6.09% | 13.37% | 10.92% | 12.54% |
SEE | 3.54% | 3.45% | 3.38% | 3.14% | 3.25% | 3.32% | 3.58% | 1.96% | 1.83% | 2.36% |
T | 4.59% | 4.56% | 4.41% | 4.50% | 4.39% | 4.31% | 4.04% | 3.43% | 3.55% | 3.05% |
TGT | 3.97% | 3.66% | 4.19% | 3.88% | 4.34% | 4.80% | 5.59% | 2.00% | 2.22% | 1.74% |
TMO | 3.43% | 3.60% | 3.36% | 3.72% | 3.41% | 3.08% | 3.25% | 4.57% | 4.70% | 4.00% |
TXT | 2.95% | 2.93% | 2.86% | 2.68% | 2.75% | 2.66% | 3.12% | 1.58% | 1.58% | 1.37% |
VZ | 5.19% | 5.19% | 5.15% | 5.19% | 5.19% | 5.69% | 4.10% | 7.75% | 6.36% | 11.08% |
ZION | 3.00% | 3.19% | 2.86% | 3.21% | 2.85% | 2.54% | 2.79% | 3.25% | 4.02% | 2.47% |
import matplotlib.pyplot as plt
# Plotting a comparison of assets weights for each portfolio
fig = plt.gcf()
fig.set_figwidth(16)
fig.set_figheight(6)
ax = fig.subplots(nrows=1, ncols=1)
w_s.plot.bar(ax=ax)
<Axes: >