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.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.0857% |
2016-01-06 | -11.4863% | -1.5879% | 0.2411% | -1.7557% | -0.7727% | -1.2473% | -0.1735% | -1.1239% | -3.5867% | -0.9551% | ... | 0.5547% | 0.0212% | 0.1592% | -1.5647% | 0.3108% | -1.0155% | -0.7653% | -3.0048% | -0.9034% | -2.9145% |
2016-01-07 | -5.1388% | -4.1922% | -1.6573% | -2.7699% | -1.1047% | -1.9769% | -1.2207% | -0.8856% | -4.6059% | -2.5394% | ... | -2.2066% | -3.0309% | -1.0411% | -3.1556% | -1.6148% | -0.2700% | -2.2845% | -2.0570% | -0.5492% | -3.0019% |
2016-01-08 | 0.2736% | -2.2705% | -1.6037% | -2.5425% | 0.1099% | -0.2241% | 0.5706% | -1.6402% | -1.7642% | -0.1649% | ... | -0.1539% | -1.1366% | -0.7308% | -0.1448% | 0.0895% | -3.3839% | -0.1117% | -1.1387% | -0.9720% | -1.1254% |
2016-01-11 | -4.3383% | 0.1693% | -1.6851% | -1.0215% | 0.0914% | -1.1791% | 0.5674% | 0.5287% | 0.6616% | 0.0331% | ... | 1.6436% | 0.0000% | 0.9869% | -0.1451% | 1.2224% | 1.4570% | 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)
# Configuring short weights options
port.sht = True # Allows to use Short Weights
port.uppersht = 0.3 # Maximum absolute value of individual negative weights
port.upperlng = 1.3 # Maximum value of individual positive weights
port.budget = 1.0 # Sum of all weights
port.budgetsht = 0.3 # Maximum absolute value of sum of negative weights
# 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 | -7.4519% | 9.6278% | 13.1758% | -1.2650% | 0.0000% | 13.4986% | 0.0000% | 8.0891% | 0.0000% | 0.0000% | ... | 10.5861% | 0.0000% | -0.0000% | -7.0711% | 0.4849% | 8.3073% | 0.5676% | -14.2119% | 4.9502% | 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)
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.4255% | 0.3940% | 6.7334% | 4.5411% | 2.6268% | 7.4370% | 2.9970% | 0.7996% | -1.9263% | 3.3470% | ... | 11.3536% | -1.4055% | 14.7448% | 0.5034% | 6.5274% | 4.3629% | -1.0414% | -0.0721% | 8.2062% | 0.0197% |
1 | -2.7563% | 3.5594% | 9.0748% | 1.2802% | 2.2703% | 10.2967% | 2.0656% | 3.1161% | -2.3452% | 2.5651% | ... | 12.4810% | -0.4610% | 9.5486% | -2.9418% | 6.5478% | 5.9027% | 0.2749% | -6.0506% | 7.9061% | -0.2858% |
2 | -3.7498% | 4.9086% | 10.0728% | -0.1100% | 2.1184% | 11.5157% | 1.6687% | 4.1033% | -2.5253% | 2.2319% | ... | 12.9618% | -0.0574% | 7.3342% | -4.4107% | 6.5569% | 6.5591% | 0.8365% | -8.5989% | 7.7780% | -0.4142% |
3 | -4.5311% | 5.9694% | 10.8574% | -1.2030% | 1.9990% | 12.4741% | 1.3566% | 4.8797% | -2.6640% | 1.9696% | ... | 13.3396% | 0.2586% | 5.5921% | -5.5650% | 6.5634% | 7.0751% | 1.2777% | -10.6023% | 7.6776% | -0.5179% |
4 | -5.2434% | 6.8737% | 11.5503% | -1.9816% | 1.8439% | 13.2574% | 0.9830% | 5.5489% | -2.4593% | 1.5561% | ... | 13.7203% | 0.3028% | 3.7262% | -6.4013% | 6.3258% | 7.5125% | 1.5731% | -12.0778% | 7.5075% | -0.0000% |
5 rows × 25 columns
# Plotting the efficient frontier
label = 'Max Risk Adjusted Return Portfolio' # Title of point
mu = port.mu # Expected returns
cov = port.cov # Covariance matrix
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, label=label,
marker='*', s=16, c='r', height=6, width=10, ax=None)
# Plotting efficient frontier composition
# This chart doesn't work well will short weights and leverage
ax = rp.plot_frontier_area(w_frontier=frontier, cmap="tab20", height=6, width=10, ax=None)
# Building the portfolio object
port = rp.Portfolio(returns=Y[assets])
# 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)
# Configuring short weights options
port.sht = True # Allows to use Short Weights
port.uppersht = 0.3 # Maximum absolute value of individual negative weights
port.upperlng = 1.3 # Maximum value of individual positive weights
port.budgetsht = 0.3 # Maximum absolute value of sum of negative weights
# Configuring leverage
port.budget = 1.3 # Sum of all weights
# 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 | -8.8386% | 11.6055% | 16.5865% | -0.0000% | 0.0000% | 16.4661% | 0.0000% | 9.4146% | 0.0000% | 0.0000% | ... | 13.7573% | 0.0000% | -0.0000% | -6.2643% | 0.0000% | 10.4658% | 0.0000% | -14.8971% | 6.6789% | 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)
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.5531% | 0.5123% | 8.7533% | 5.9034% | 3.4148% | 9.6681% | 3.8962% | 1.0395% | -2.5042% | 4.3511% | ... | 14.7597% | -1.8272% | 19.1682% | 0.6544% | 8.4856% | 5.6717% | -1.3538% | -0.0937% | 10.6680% | 0.0256% |
1 | -3.4202% | 4.4057% | 11.6333% | 1.8922% | 2.9764% | 13.1857% | 2.7505% | 3.8887% | -3.0192% | 3.3892% | ... | 16.1466% | -0.6651% | 12.7765% | -3.5834% | 8.5107% | 7.5658% | 0.2657% | -7.4472% | 10.2990% | -0.3498% |
2 | -4.6388% | 6.0605% | 12.8574% | 0.1872% | 2.7901% | 14.6808% | 2.2636% | 5.0998% | -3.2383% | 2.9804% | ... | 16.7360% | -0.1713% | 10.0599% | -5.3845% | 8.5214% | 8.3708% | 0.9542% | -10.5728% | 10.1420% | -0.5096% |
3 | -5.6217% | 7.3514% | 13.8285% | -1.0510% | 2.6134% | 15.8252% | 1.8199% | 6.0501% | -3.2126% | 2.5434% | ... | 17.2412% | 0.0692% | 7.7063% | -6.7037% | 8.3760% | 8.9970% | 1.4414% | -12.8728% | 9.9648% | -0.1755% |
4 | -6.5148% | 8.3621% | 14.6498% | -1.2365% | 2.1169% | 16.6169% | 0.9094% | 6.7160% | -1.6184% | 1.2602% | ... | 17.7018% | 0.0000% | 4.0384% | -6.9788% | 7.2424% | 9.4882% | 1.4281% | -13.6515% | 9.6093% | 0.0000% |
5 rows × 25 columns
# Plotting the efficient frontier
label = 'Max Risk Adjusted Return Portfolio' # Title of point
mu = port.mu # Expected returns
cov = port.cov # Covariance matrix
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, label=label,
marker='*', s=16, c='r', height=6, width=10, ax=None)
# Plotting efficient frontier composition
# This chart doesn't work well will short weights and leverage
ax = rp.plot_frontier_area(w_frontier=frontier, cmap="tab20", height=6, width=10, ax=None)
In this part I will calculate optimal portfolios for several risk measures allowing Leverage and Short Weights. First I'm going to calculate the portfolio that maximizes risk adjusted return when CVaR is the risk measure, then I'm going to calculate the portfolios that maximize the risk adjusted return for all available risk measures.
rm = 'CVaR' # Risk measure
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 | -7.8924% | 0.0000% | 19.3944% | -0.0000% | 0.0000% | 17.3216% | 0.0000% | 11.1497% | -0.0000% | -0.0000% | ... | 13.7696% | 0.0000% | -0.0000% | -6.6619% | 0.0000% | 16.2182% | 6.7777% | -15.4456% | 10.3496% | 0.0000% |
1 rows × 25 columns
ax = rp.plot_pie(w=w, title='Sharpe Mean CVaR', 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 | 1.8342% | 0.7830% | 2.0355% | 9.6251% | 0.0000% | 3.1290% | 8.4910% | -6.7987% | -19.2906% | 11.1534% | ... | 11.9926% | 5.5712% | 19.4224% | 1.9219% | 9.4922% | 8.0510% | -0.0000% | -3.2409% | 24.3155% | -0.6699% |
1 | -2.5848% | 1.1783% | 7.1347% | 4.2101% | -0.0000% | 4.1823% | 8.7607% | 3.5851% | -14.8009% | 4.1409% | ... | 9.6288% | 4.8534% | 17.8373% | -7.5913% | 3.8923% | 11.4037% | 4.3931% | -5.0230% | 22.4948% | 0.0000% |
2 | -3.4395% | 0.0000% | 5.9264% | 0.0000% | 0.0000% | 9.7684% | 3.9293% | 8.0762% | -12.0715% | 5.8465% | ... | 9.6966% | 3.0184% | 14.6773% | -6.9885% | 0.3784% | 13.1392% | 2.6852% | -7.5005% | 24.0032% | 0.0000% |
3 | -4.3667% | 0.0000% | 7.9893% | 0.0000% | 0.0000% | 17.4982% | 0.0000% | 8.5019% | -9.6541% | 2.7543% | ... | 10.0505% | 0.0000% | 11.0660% | -9.2572% | 0.2073% | 15.1826% | 1.4811% | -6.7220% | 22.0835% | 0.0000% |
4 | -5.5466% | 3.7188% | 7.9819% | -0.0000% | 0.0000% | 27.6275% | 0.0000% | 5.5998% | -5.1615% | 0.0000% | ... | 6.0290% | 0.0000% | 5.6016% | -10.6301% | 0.0000% | 13.9002% | 0.0743% | -8.6618% | 22.9280% | 0.0000% |
5 rows × 25 columns
label = 'Max Risk Adjusted Return Portfolio' # Title of point
ax = rp.plot_frontier(w_frontier=frontier, mu=mu, cov=cov, returns=returns, rm=rm,
rf=rf, alpha=0.05, cmap='viridis', w=w, label=label,
marker='*', s=16, c='r', height=6, width=10, ax=None)
# Plotting efficient frontier composition
# This chart doesn't work well will short weights and leverage
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([])
for i in rms:
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 | -8.84% | -8.95% | -8.69% | -6.96% | -8.88% | -7.89% | -17.38% | -17.43% | -23.06% | -2.32% | -11.34% | -7.13% | -18.96% |
BA | 11.61% | 11.79% | 11.32% | 13.67% | 11.29% | 0.00% | 5.84% | 0.00% | 15.81% | 10.80% | 11.12% | 10.08% | 9.96% |
BAX | 16.59% | 15.32% | 14.44% | 12.46% | 14.52% | 19.39% | 9.27% | 6.89% | 38.26% | 19.54% | 11.48% | 16.90% | 12.66% |
BMY | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | 0.00% | 0.00% | -0.00% | -2.37% | -3.89% | -3.86% | -5.12% |
CMCSA | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | -0.00% | -6.51% | -1.43% | -5.05% | -0.00% | -0.00% |
CNP | 16.47% | 15.08% | 20.25% | 13.52% | 21.15% | 17.32% | 36.05% | 28.73% | 42.04% | 15.91% | 37.88% | 27.15% | 44.15% |
CPB | 0.00% | -0.00% | -0.00% | -0.00% | -0.00% | 0.00% | 4.96% | 14.05% | -0.00% | 0.00% | -0.00% | -0.00% | -0.00% |
DE | 9.41% | 5.06% | 6.81% | 6.25% | 6.86% | 11.15% | 0.00% | 0.00% | 0.00% | 5.66% | 5.06% | 4.47% | 4.42% |
HPQ | 0.00% | 0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.21% | -5.27% | -2.86% | -0.00% |
JCI | 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% |
JPM | 22.20% | 28.37% | 21.33% | 30.73% | 20.34% | 9.40% | 10.51% | 0.00% | 0.00% | 9.66% | 0.00% | 4.47% | 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 | 31.01% | 25.11% | 31.60% | 22.44% | 32.13% | 33.20% | 33.66% | 32.15% | 38.26% | 24.78% | 24.00% | 24.99% | 27.37% |
MO | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -0.00% | -7.41% | -0.44% | -1.03% | -0.54% | -2.04% | -5.37% |
MSFT | 21.82% | 25.86% | 26.73% | 25.88% | 26.78% | 22.42% | 37.77% | 32.08% | 16.29% | 43.75% | 49.28% | 49.69% | 51.53% |
NI | 13.76% | 13.48% | 11.43% | 16.38% | 11.03% | 13.77% | 0.00% | 0.00% | 5.42% | 6.06% | 3.55% | 3.05% | 3.38% |
PCAR | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 17.67% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
PSA | -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% |
SEE | -6.26% | -5.93% | -6.61% | -6.19% | -6.78% | -6.66% | -8.12% | -0.00% | -0.00% | -6.94% | -0.00% | -1.86% | 0.00% |
T | 0.00% | 7.03% | 0.00% | 6.00% | 0.00% | 0.00% | 0.00% | -0.00% | -0.00% | 1.03% | 0.00% | 0.00% | 0.00% |
TGT | 10.47% | 9.14% | 12.78% | 7.49% | 13.06% | 16.22% | 21.94% | 28.42% | 0.00% | 0.64% | 0.00% | 0.00% | 0.25% |
TMO | 0.00% | 0.50% | 0.00% | 0.04% | 0.00% | 6.78% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
TXT | -14.90% | -15.12% | -14.70% | -16.85% | -14.34% | -15.45% | -4.50% | -5.16% | -0.00% | -15.71% | -3.91% | -12.25% | -0.54% |
VZ | 6.68% | 3.26% | 3.30% | 5.15% | 2.84% | 10.35% | 0.00% | -0.00% | 3.91% | 16.61% | 17.64% | 19.21% | 6.27% |
ZION | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | -0.00% | 0.00% | 5.55% | -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)
<Axes: >