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.0257% | 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.9758% | 0.6987% | 1.7539% | -0.1730% | 0.2409% | 1.3735% | -1.0857% |
2016-01-06 | -11.4863% | -1.5879% | 0.2412% | -1.7557% | -0.7727% | -1.2473% | -0.1736% | -1.1239% | -3.5867% | -0.9551% | ... | 0.5547% | 0.0212% | 0.1592% | -1.5647% | -0.1466% | -1.0155% | -0.7653% | -3.0048% | -0.9034% | -2.9145% |
2016-01-07 | -5.1389% | -4.1922% | -1.6573% | -2.7699% | -1.1047% | -1.9769% | -1.2207% | -0.8855% | -4.6058% | -2.5394% | ... | -2.2066% | -3.0309% | -1.0411% | -3.1557% | -1.6148% | -0.2700% | -2.2844% | -2.0570% | -0.5492% | -3.0020% |
2016-01-08 | 0.2736% | -2.2705% | -1.6037% | -2.5425% | 0.1099% | -0.2241% | 0.5707% | -1.6402% | -1.7642% | -0.1649% | ... | -0.1539% | -1.1366% | -0.7308% | -0.1448% | 0.0895% | -3.3839% | -0.1117% | -1.1387% | -0.9719% | -1.1254% |
2016-01-11 | -4.3384% | 0.1693% | -1.6851% | -1.0215% | 0.0915% | -1.1791% | 0.5674% | 0.5287% | 0.6616% | 0.0330% | ... | 1.6436% | 0.0000% | 0.9869% | -0.1450% | 1.2224% | 1.4570% | 0.5366% | -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, d=0.94)
# Estimate optimal portfolio:
port.alpha = 0.05
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.5018% | 0.0000% | 0.0000% | 8.4807% | 0.0000% | 3.8194% | 0.0000% | 0.0000% | ... | 10.8263% | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 7.1804% | 0.0000% | 0.0000% | 4.2741% | 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)
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'])
views = {'Disabled': [False, False, False],
'Type': ['Classes', 'Classes', 'Classes'],
'Set': ['Industry', 'Industry', 'Industry'],
'Position': ['Energy', 'Consumer Staples', 'Materials'],
'Sign': ['>=', '>=', '>='],
'Weight': [0.08, 0.1, 0.09], # Annual terms
'Type Relative': ['Classes', 'Classes', 'Classes'],
'Relative Set': ['Industry', 'Industry', 'Industry'],
'Relative': ['Financials', 'Utilities', 'Industrials']}
views = pd.DataFrame(views)
display(views)
Disabled | Type | Set | Position | Sign | Weight | Type Relative | Relative Set | Relative | |
---|---|---|---|---|---|---|---|---|---|
0 | False | Classes | Industry | Energy | >= | 8.0000% | Classes | Industry | Financials |
1 | False | Classes | Industry | Consumer Staples | >= | 10.0000% | Classes | Industry | Utilities |
2 | False | Classes | Industry | Materials | >= | 9.0000% | Classes | Industry | Industrials |
P, Q = rp.assets_views(views, asset_classes)
display(pd.DataFrame(P.T))
display(pd.DataFrame(Q))
0 | 1 | 2 | |
---|---|---|---|
0 | 100.0000% | 0.0000% | 0.0000% |
1 | -20.0000% | 0.0000% | 0.0000% |
2 | 0.0000% | 0.0000% | 0.0000% |
3 | 0.0000% | 0.0000% | 0.0000% |
4 | 0.0000% | 0.0000% | 0.0000% |
5 | 0.0000% | -50.0000% | 0.0000% |
6 | 0.0000% | 50.0000% | 0.0000% |
7 | 0.0000% | 0.0000% | -25.0000% |
8 | 0.0000% | 0.0000% | 0.0000% |
9 | 0.0000% | 0.0000% | 0.0000% |
10 | -20.0000% | 0.0000% | 0.0000% |
11 | 0.0000% | 0.0000% | -25.0000% |
12 | -20.0000% | 0.0000% | 0.0000% |
13 | 0.0000% | 50.0000% | 0.0000% |
14 | 0.0000% | 0.0000% | 0.0000% |
15 | 0.0000% | -50.0000% | 0.0000% |
16 | 0.0000% | 0.0000% | -25.0000% |
17 | -20.0000% | 0.0000% | 0.0000% |
18 | 0.0000% | 0.0000% | 100.0000% |
19 | 0.0000% | 0.0000% | 0.0000% |
20 | 0.0000% | 0.0000% | 0.0000% |
21 | 0.0000% | 0.0000% | 0.0000% |
22 | 0.0000% | 0.0000% | -25.0000% |
23 | 0.0000% | 0.0000% | 0.0000% |
24 | -20.0000% | 0.0000% | 0.0000% |
0 | |
---|---|
0 | 8.0000% |
1 | 10.0000% |
2 | 9.0000% |
# Estimate Black Litterman inputs:
port.blacklitterman_stats(P, Q/252, rf=rf, w=w, delta=None, eq=True)
# Estimate optimal portfolio:
model='BL'# Black Litterman
rm = 'MV' # Risk measure used, this time will be variance
obj = 'Sharpe' # Objective function, could be MinRisk, MaxRet, Utility or Sharpe
hist = False # Use historical scenarios for risk measures that depend on scenarios
w_bl = port.optimization(model=model, rm=rm, obj=obj, rf=rf, l=l, hist=hist)
display(w_bl.T)
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 0.5339% | 5.0289% | 11.1757% | 0.0030% | 0.0000% | 2.3385% | 5.8492% | 0.6323% | 0.0000% | 0.0000% | ... | 4.8567% | 0.0000% | 0.0000% | 8.1659% | 0.0000% | 6.8847% | 0.0000% | 0.0000% | 4.5135% | 0.0000% |
1 rows × 25 columns
# Plotting the composition of the portfolio
ax = rp.plot_pie(w=w_bl, title='Sharpe Black Litterman', others=0.05, nrow=25,
cmap = "tab20", height=6, width=10, ax=None)
points = 50 # Number of points of the frontier
frontier = port.efficient_frontier(model=model, rm=rm, points=points, rf=rf, hist=hist)
display(frontier.T.head())
APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | HPQ | JCI | ... | NI | PCAR | PSA | SEE | T | TGT | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0000% | 0.0000% | 5.2635% | 4.3886% | 2.1705% | 6.9871% | 3.2390% | 0.0788% | 0.0000% | 2.8376% | ... | 11.4509% | 0.0000% | 14.9183% | 0.1627% | 6.4196% | 4.0904% | 0.0000% | 0.0000% | 8.3446% | 0.0000% |
1 | 0.0000% | 1.8898% | 7.9755% | 2.9166% | 1.6685% | 5.5405% | 4.1246% | 0.5333% | 0.0000% | 1.7285% | ... | 9.3217% | 0.0000% | 9.7679% | 3.0750% | 4.0054% | 5.1909% | 0.0000% | 0.0000% | 7.0264% | 0.0000% |
2 | 0.0000% | 2.6335% | 8.7146% | 2.2991% | 1.2959% | 4.9345% | 4.5208% | 0.6107% | 0.0000% | 1.2609% | ... | 8.4027% | 0.0000% | 7.6736% | 4.2328% | 3.1397% | 5.5898% | 0.0000% | 0.0000% | 6.4978% | 0.0000% |
3 | 0.0887% | 3.1881% | 9.2750% | 1.8154% | 0.9588% | 4.4141% | 4.8317% | 0.6478% | 0.0000% | 0.8553% | ... | 7.6759% | 0.0000% | 5.9907% | 5.1235% | 2.4056% | 5.8917% | 0.0000% | 0.0000% | 6.1126% | 0.0000% |
4 | 0.2027% | 3.6395% | 9.7407% | 1.4013% | 0.6314% | 3.9448% | 5.0942% | 0.6646% | 0.0000% | 0.4768% | ... | 7.0409% | 0.0000% | 4.5084% | 5.8672% | 1.7321% | 6.1419% | 0.0000% | 0.0000% | 5.8075% | 0.0000% |
5 rows × 25 columns
# Plotting the efficient frontier
label = 'Max Risk Adjusted Return Portfolio' # Title of point
mu = port.mu_bl # Expected returns of Black Litterman model
cov = port.cov_bl # Covariance matrix of Black Litterman model
returns = port.returns # Returns of the assets
ax = rp.plot_frontier(w_frontier=frontier, mu=mu, cov=cov, returns=returns, rm=rm,
rf=rf, alpha=0.05, cmap='viridis', w=w_bl, label=label,
marker='*', s=16, c='r', height=6, width=10, ax=None)
# Plotting efficient frontier composition
ax = rp.plot_frontier_area(w_frontier=frontier, cmap="tab20", 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.
# 'WR': Worst Realization (Minimax)
# 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
# 'ADD': Average Drawdown of uncompounded cumulative returns.
# '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', 'WR', 'MDD', 'ADD', 'CDaR', 'UCI', 'EDaR']
w_s = pd.DataFrame([])
port.alpha = 0.05
for i in rms:
if i == 'MV':
hist = False
else:
hist = True
w = port.optimization(model=model, rm=i, obj=obj, rf=rf, l=l, 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 | WR | MDD | ADD | CDaR | UCI | EDaR | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
APA | 0.53% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
BA | 5.03% | 6.11% | 3.10% | 4.17% | 2.81% | 0.00% | 0.00% | 0.00% | 0.00% | 7.94% | 2.64% | 6.80% | 0.00% |
BAX | 11.18% | 8.35% | 10.66% | 8.86% | 10.66% | 11.94% | 7.64% | 0.00% | 0.00% | 3.89% | 0.00% | 4.66% | 0.00% |
BMY | 0.00% | 2.18% | 0.00% | 2.87% | 0.00% | 0.00% | 4.59% | 8.94% | 0.00% | 0.00% | 0.00% | 0.00% | 1.77% |
CMCSA | 0.00% | 0.00% | 0.00% | 0.06% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
CNP | 2.34% | 3.36% | 1.91% | 3.72% | 1.58% | 2.27% | 4.61% | 0.00% | 52.20% | 11.84% | 31.88% | 16.25% | 30.14% |
CPB | 5.85% | 2.42% | 6.71% | 3.94% | 7.18% | 10.58% | 16.92% | 15.82% | 0.00% | 2.40% | 0.17% | 0.00% | 0.00% |
DE | 0.63% | 1.61% | 0.00% | 0.19% | 0.00% | 0.00% | 0.00% | 0.00% | 5.92% | 0.06% | 0.05% | 0.00% | 0.00% |
HPQ | 0.00% | 0.07% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
JCI | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 1.18% | 0.00% | 0.00% | 0.00% |
JPM | 6.41% | 11.68% | 7.79% | 12.03% | 7.43% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
LUV | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
MMC | 20.51% | 14.44% | 20.37% | 15.55% | 20.52% | 24.15% | 18.16% | 35.57% | 0.00% | 16.64% | 8.81% | 16.23% | 0.00% |
MO | 6.20% | 4.50% | 3.96% | 3.46% | 3.96% | 3.02% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
MSFT | 16.89% | 20.92% | 22.07% | 21.89% | 22.10% | 20.19% | 22.28% | 19.67% | 37.83% | 40.30% | 45.05% | 42.22% | 52.57% |
NI | 4.86% | 7.82% | 6.03% | 6.10% | 6.13% | 1.24% | 0.00% | 0.00% | 0.00% | 4.06% | 3.45% | 3.38% | 0.00% |
PCAR | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.13% |
PSA | 0.00% | 0.00% | 1.10% | 0.00% | 1.26% | 8.31% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.33% | 0.88% |
SEE | 8.17% | 3.02% | 4.23% | 3.11% | 4.36% | 4.94% | 8.03% | 0.00% | 4.04% | 0.00% | 0.00% | 0.00% | 4.65% |
T | 0.00% | 2.51% | 0.00% | 1.23% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.30% | 0.00% |
TGT | 6.88% | 4.85% | 8.78% | 5.67% | 9.17% | 10.06% | 17.77% | 20.00% | 0.00% | 0.89% | 0.00% | 0.58% | 0.00% |
TMO | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.19% | 0.00% | 0.00% | 0.00% |
TXT | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
VZ | 4.51% | 4.77% | 3.28% | 6.02% | 2.84% | 3.31% | 0.00% | 0.00% | 0.00% | 9.12% | 7.94% | 9.24% | 9.86% |
ZION | 0.00% | 1.39% | 0.00% | 1.13% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 1.50% | 0.00% | 0.00% | 0.00% |
import matplotlib.pyplot as plt
# Plotting a comparison of assets weights for each portfolio
fig = plt.gcf()
fig.set_figwidth(14)
fig.set_figheight(6)
ax = fig.subplots(nrows=1, ncols=1)
w_s.plot.bar(ax=ax)
<AxesSubplot:>