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].iloc[-300:,:].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 | |||||||||||||||||||||
2018-10-19 | 0.0475% | -0.8599% | -1.4333% | -3.0011% | 0.1113% | 1.2968% | 3.4360% | -0.8763% | 0.2945% | -0.7834% | ... | 0.6339% | -0.1823% | 0.9185% | -0.7728% | 1.1385% | -1.6075% | -1.1145% | -1.2872% | 0.4575% | -0.8025% |
2018-10-22 | -1.9240% | -0.0786% | -0.6335% | -6.2983% | -0.6393% | -1.1024% | 0.0527% | -0.3221% | 1.1325% | -0.8199% | ... | -0.8662% | 0.4483% | -1.6953% | -2.8972% | -0.6085% | 1.4752% | -0.6075% | -0.8634% | 0.1457% | -3.4490% |
2018-10-23 | -3.6571% | -1.6658% | -0.4201% | -0.4520% | -0.2797% | -0.5034% | 0.1844% | -3.9948% | -0.7051% | -0.2449% | ... | 0.4766% | -5.1240% | 0.5341% | -0.0321% | 1.0713% | -0.6728% | -1.0807% | -1.8308% | 4.0560% | 4.0353% |
2018-10-24 | -4.5500% | 1.3141% | -1.8042% | -3.5933% | -4.2918% | 0.8674% | 0.9995% | -4.1109% | -3.6759% | -3.7140% | ... | 3.5178% | -4.2683% | 1.5636% | -1.3479% | -8.0557% | -0.4838% | -1.2403% | -4.2187% | 0.3671% | -3.3065% |
2018-10-25 | 0.4741% | 2.5715% | 0.5186% | 0.7782% | 5.0411% | -0.5733% | -1.1719% | 2.1585% | 3.1657% | 2.3271% | ... | -1.0309% | 0.4914% | 0.5082% | 0.9109% | -1.2517% | 1.8962% | 4.3662% | 1.3800% | -1.7241% | 3.3538% |
5 rows × 25 columns
The OWA portfolio model proposed by Cajas (2021) . This model gives an alternative formulation to risk measures that can be expressed using the OWA operator.
It is recommended to use MOSEK to optimize OWA portfolios, due to it requires more computing power for the number of constraints and variables the model use.
Instructions to install MOSEK are in this link, is better to install using Anaconda. Also you will need a license, I recommend you that ask for an academic license here.
In this case we are going to compare the optimal weights using classical formulations and owa formulation of two risk measures: Conditional Value at Risk (CVaR) and Worst Realization (WR or Minimax model).
import riskfolio as rp
import mosek
# Building the portfolio object
port = rp.Portfolio(returns=Y)
# Calculating optimum 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 portfolios:
port.solvers = ['MOSEK'] # It is recommended to use mosek when optimizing GMD
port.sol_params = {'MOSEK': {'mosek_params': {mosek.iparam.num_threads: 2}}}
alpha = 0.05
port.alpha = alpha
model ='Classic' # Could be Classic (historical), BL (Black Litterman) or FM (Factor Model)
rms = ['CVaR', 'WR'] # Risk measure used, this time will be CVaR and Worst Realization
objs = ['MinRisk', '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'
ws = pd.DataFrame([])
for rm in rms:
for obj in objs:
# Using Classical models
w = port.optimization(model=model, rm=rm, obj=obj, rf=rf, l=l, hist=hist)
# Using OWA model
if rm == "CVaR":
owa_w = rp.owa_cvar(len(Y), alpha=alpha)
elif rm == 'WR':
owa_w = rp.owa_wr(len(Y))
w1 = port.owa_optimization(obj=obj, owa_w=owa_w, rf=rf, l=l)
ws1 = pd.concat([w, w1], axis=1)
ws1.columns = ['Classic ' + obj + ' ' + rm, 'OWA ' + obj + ' ' + rm]
ws1['diff ' + obj + ' ' + rm] = ws1['Classic ' + obj + ' ' + rm] - ws1['OWA ' + obj + ' ' + rm]
ws = pd.concat([ws, ws1], axis=1)
ws.style.format("{:.2%}").background_gradient(cmap='YlGn', vmin=0, vmax=1)
Classic MinRisk CVaR | OWA MinRisk CVaR | diff MinRisk CVaR | Classic Sharpe CVaR | OWA Sharpe CVaR | diff Sharpe CVaR | Classic MinRisk WR | OWA MinRisk WR | diff MinRisk WR | Classic Sharpe WR | OWA Sharpe WR | diff Sharpe WR | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
APA | 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 | 3.79% | 3.79% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
BAX | 0.00% | 0.00% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
BMY | 9.47% | 9.47% | -0.00% | 1.84% | 1.84% | -0.00% | 12.88% | 12.88% | -0.00% | 0.00% | 0.00% | 0.00% |
CMCSA | 0.87% | 0.87% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
CNP | 9.42% | 9.42% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
CPB | 6.95% | 6.95% | -0.00% | 28.53% | 28.53% | -0.00% | 0.00% | 0.00% | 0.00% | 33.71% | 33.71% | 0.00% |
DE | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
HPQ | 0.00% | 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% | 0.00% | 0.00% | 0.00% |
JPM | 0.00% | 0.00% | -0.00% | 0.00% | 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% |
MMC | 0.00% | 0.00% | 0.00% | 24.42% | 24.42% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
MO | 0.00% | 0.00% | -0.00% | 0.00% | 0.00% | 0.00% | 21.61% | 21.61% | 0.00% | 0.00% | 0.00% | 0.00% |
MSFT | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
NI | 0.00% | 0.00% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
PCAR | 0.00% | 0.00% | -0.00% | 11.95% | 11.95% | 0.00% | 0.00% | 0.00% | 0.00% | 13.79% | 13.79% | 0.00% |
PSA | 25.40% | 25.40% | 0.00% | 10.28% | 10.28% | -0.00% | 40.27% | 40.27% | 0.00% | 0.00% | 0.00% | 0.00% |
SEE | 2.27% | 2.27% | 0.00% | 0.00% | 0.00% | 0.00% | 8.76% | 8.76% | 0.00% | 0.00% | 0.00% | 0.00% |
T | 4.52% | 4.52% | -0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
TGT | 4.85% | 4.85% | -0.00% | 22.28% | 22.28% | 0.00% | 16.48% | 16.48% | -0.00% | 40.40% | 40.40% | 0.00% |
TMO | 0.00% | 0.00% | -0.00% | 0.70% | 0.70% | 0.00% | 0.00% | 0.00% | 0.00% | 12.09% | 12.09% | -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% |
VZ | 32.46% | 32.46% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
ZION | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
As we can see, classical and OWA formulations give us the same returns because both problem are equivalent.