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', 'AMZN', 'CMCSA', 'CPB', 'MO', 'APA', 'MMC', 'JPM',
'ZION', 'AAPL', '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())
AAPL | AMZN | APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | ... | MO | MSFT | NI | PCAR | SEE | T | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | |||||||||||||||||||||
2018-10-19 | 1.5230% | -0.3778% | 0.0475% | -0.8599% | -1.4332% | -3.0011% | 0.1113% | 1.2968% | 3.4360% | -0.8763% | ... | 1.6740% | 0.1475% | 0.6339% | -0.1823% | -0.7728% | 1.1385% | -1.1145% | -1.2872% | 0.4575% | -0.8025% |
2018-10-22 | 0.6110% | 1.4325% | -1.9240% | -0.0786% | -0.6335% | -6.2983% | -0.6393% | -1.1024% | 0.0527% | -0.3221% | ... | -1.0331% | 0.8927% | -0.8661% | 0.4483% | -2.8972% | -0.6084% | -0.6075% | -0.8634% | 0.1457% | -3.4490% |
2018-10-23 | 0.9427% | -1.1513% | -3.6571% | -1.6658% | -0.4202% | -0.4520% | -0.2797% | -0.5034% | 0.1844% | -3.9948% | ... | 0.8808% | -1.3956% | 0.4766% | -5.1240% | -0.0321% | 1.0713% | -1.0807% | -1.8308% | 4.0560% | 4.0353% |
2018-10-24 | -3.4302% | -5.9083% | -4.5500% | 1.3141% | -1.8042% | -3.5933% | -4.2917% | 0.8674% | 0.9995% | -4.1109% | ... | 0.7437% | -5.3469% | 3.5178% | -4.2683% | -1.3479% | -8.0557% | -1.2403% | -4.2187% | 0.3671% | -3.3065% |
2018-10-25 | 2.1898% | 7.0887% | 0.4741% | 2.5716% | 0.5186% | 0.7782% | 5.0411% | -0.5732% | -1.1719% | 2.1585% | ... | 1.3642% | 5.8444% | -1.0309% | 0.4914% | 0.9109% | -1.2516% | 4.3662% | 1.3799% | -1.7241% | 3.3538% |
5 rows × 25 columns
The Semi Kurtosis portfolio model proposed by Cajas (2022) shows how to optimize the fourth lower moment of portfolio returns using an lower semi cokurtosis matrix as an heuristic in a similar way than lower semi covariance matrix.
It is recommended to use MOSEK to optimize Kurtosis for a large number of assets due the model use semidefinite programming. Also, for a large number of assets is recommended to use the relaxed version of this model based only on second order cone programming. To use the relaxed version we have to use a number of assets higher than the property n_max_kurt, so for example if number of assets is 30 and we set port.n_max_kurt = 25, riskfolio-lib is going to use the relaxed version.
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.
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.
method_kurt='hist' # Method to estimate cokurtosis square matrix based on historical data.
port.assets_stats(method_mu=method_mu,
method_cov=method_cov,
method_kurt=method_kurt,
)
# Estimate optimal portfolio:
port.solvers = ['MOSEK'] # It is recommended to use mosek when optimizing GMD
port.sol_params = {'MOSEK': {'mosek_params': {'MSK_IPAR_NUM_THREADS': 2}}}
model ='Classic' # Could be Classic (historical), BL (Black Litterman) or FM (Factor Model)
rm = 'SKT' # Risk measure used, this time will be Tail Gini Range
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)
You must convert self.kurt to a positive definite matrix You must convert self.skurt to a positive definite matrix
AAPL | AMZN | APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | ... | MO | MSFT | NI | PCAR | SEE | T | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 2.2933% | 1.5069% | 0.0000% | 10.1992% | 0.0000% | ... | 0.0000% | 0.0000% | 0.0000% | 4.7230% | 5.3222% | 0.0000% | 5.1388% | 0.0000% | 27.6922% | 0.0000% |
1 rows × 25 columns
# Plotting the composition of the portfolio
ax = rp.plot_pie(w=w,
title='Sharpe Mean - Kurtosis',
others=0.05,
nrow=25,
cmap = "tab20",
height=6,
width=10,
ax=None)
ax = rp.plot_hist(returns=Y,
w=w,
alpha=0.05,
bins=50,
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())
The problem doesn't have a solution with actual input parameters
AAPL | AMZN | APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | ... | MO | MSFT | NI | PCAR | SEE | T | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 1.4624% | 6.3368% | 12.0102% | 16.6038% | 0.0000% | 0.0000% | ... | 10.2422% | 0.0000% | 5.6465% | 0.0000% | 8.8630% | 0.0000% | 0.0000% | 0.0000% | 28.2496% | 0.0000% |
1 | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 1.0674% | 5.9072% | 11.2322% | 11.7571% | 0.0000% | 0.0000% | ... | 5.6054% | 0.0000% | 5.9885% | 0.0000% | 9.2228% | 0.0000% | 0.0000% | 0.0000% | 30.2239% | 0.0000% |
2 | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 0.5698% | 5.4444% | 8.8890% | 4.5476% | 0.0802% | 0.0000% | ... | 0.0000% | 0.0000% | 7.4434% | 0.0000% | 9.7238% | 0.0000% | 0.0000% | 0.0000% | 33.1218% | 0.0000% |
3 | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 0.1879% | 5.4249% | 6.5311% | 0.0001% | 4.9114% | 0.0000% | ... | 0.0000% | 0.0000% | 4.7880% | 0.0000% | 8.6779% | 0.0000% | 0.0005% | 0.0000% | 33.0473% | 0.0000% |
4 | 0.0000% | 0.0000% | 0.0000% | 0.0000% | 0.0001% | 4.4636% | 4.2342% | 0.0000% | 8.1199% | 0.0000% | ... | 0.0000% | 0.0000% | 0.0001% | 0.0000% | 6.8826% | 0.0000% | 2.0224% | 0.0000% | 32.0412% | 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
ax = rp.plot_frontier_area(w_frontier=frontier, cmap="tab20", height=6, width=10, ax=None)
b = None # Risk contribution constraints vector
w_rp = port.rp_optimization(model=model, rm=rm, rf=rf, b=b, hist=hist)
display(w_rp.T)
AAPL | AMZN | APA | BA | BAX | BMY | CMCSA | CNP | CPB | DE | ... | MO | MSFT | NI | PCAR | SEE | T | TMO | TXT | VZ | ZION | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
weights | 2.5334% | 2.6996% | 2.4343% | 3.6101% | 4.2748% | 4.0051% | 4.0332% | 6.4981% | 4.6227% | 2.5405% | ... | 5.6576% | 2.9168% | 5.8968% | 3.4460% | 4.6167% | 3.9412% | 3.5702% | 3.2022% | 7.6373% | 3.4864% |
1 rows × 25 columns
ax = rp.plot_pie(w=w_rp,
title='Risk Parity Square Root Semi Kurtosis',
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.05,
color="tab:blue", height=6, width=10, ax=None)
Due to semi kurtosis use an heuristic based on lower semi cokurtosis matrix to approach portfolio lower semi kurtosis, the solution is only an approximation.
# Plotting the efficient frontier
ws = pd.concat([w, w_rp],axis=1)
ws.columns = ["Max Return/ Semi Kurtosis", "Risk Parity Semi Kurtosis"]
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=ws,
marker='*', s=16, c='r', height=6, width=10, ax=None)