{
"cells": [
{
"cell_type": "markdown",
"id": "d760e6e6",
"metadata": {},
"source": [
"# Entropic Portfolio Optimization\n",
"\n",
"In this notebook we show how to use the exponential cone to model the perspective of the `log_sum_exp` function and its application in portfolio optimization.\n",
"\n",
"## 1. Entropic Value at Risk Optimization\n",
"\n",
"### 1.1 The Entropic Value at Risk\n",
"\n",
"The Entropic Value at Risk (EVaR) which is a new risk measure introduced by __[Ahmadi-Javid (2012)](https://link.springer.com/article/10.1007/s10957-011-9968-2?r=1&l=ri&fst=0&error=cookies_not_supported&code=ccfb8a5e-692b-43d1-b76e-ae596c7f0bed)__. It is the upper bound based on Chernoff Inequality of Value at Risk (VaR) and Conditional Value at Risk (CVaR), formally it is defined as:\n",
"\n",
"$$\n",
"\\text{EVaR}_{\\alpha}(X) = \\inf_{z>0} \\left \\{z\\log \\left ( \\frac{1}{\\alpha} M_{X} (\\frac{1}{z}) \\right ) \\right \\}\n",
"$$\n",
"\n",
"Where $M_{X} (t) = \\text{E} [e^{tX}]$ is the moment generating function and $\\alpha \\in [0,1]$ is the significance level.\n",
"\n",
"### 1.2 EVaR Minimization\n",
"\n",
"To discretize the EVaR we need the perspective of the `log_sum_exp` function, we can do this using the exponential cone in CVXPY. The discipined convex programming (DCP) problem of EVaR minimization was proposed by __[Cajas (2021)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3792520)__ and it is posed as:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\begin{aligned}\n",
"& \\underset{x, \\, z, \\, t, \\, u}{\\text{min}} & & t + z \\ln \\left ( \\frac{1}{\\alpha T} \\right ) \\\\\n",
"& \\text{s.t.} & & \\mu x^{\\tau} \\geq \\bar{\\mu} \\\\\n",
"& & & \\sum_{i=1}^{N} x_i = 1 \\\\\n",
"& & & z \\geq \\sum^{T}_{j=1} u_{j} \\\\\n",
"& & & (-r_{j}x^{\\tau}-t, z, u_{j}) \\in K_{\\text{exp}} \\; \\forall \\; j=1, \\ldots, T \\\\\n",
"& & & x_i \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n",
"\\end{aligned}\n",
"\\end{equation}\n",
"$$\n",
"\n",
"Where $t$ is an auxiliar variable that represents the perspectives of the `log_sum_exp` function, $z$ is the factor of perspective function, $u_{j}$ is an auxiliary variable, $x$ are the weights of assets, $\\mu$ is the mean vector of expected returns, $\\bar{\\mu}$ the minimum expected return of portfolio, $K_{\\text{exp}}$ is an exponential cone and $r$ is the matrix of observed returns."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2a19278a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[*********************100%***********************] 14 of 14 completed\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" BAX | \n",
" BMY | \n",
" CMCSA | \n",
" CNP | \n",
" CPB | \n",
" GE | \n",
" GOOG | \n",
" MO | \n",
" MSFT | \n",
" NI | \n",
" SEE | \n",
" T | \n",
" TGT | \n",
" VZ | \n",
"
\n",
" \n",
" Date | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" 2016-01-05 | \n",
" 0.4036% | \n",
" 1.9693% | \n",
" 0.0180% | \n",
" 0.9305% | \n",
" 0.3678% | \n",
" 0.0977% | \n",
" 0.0998% | \n",
" 2.0213% | \n",
" 0.4562% | \n",
" 1.5881% | \n",
" 0.9758% | \n",
" 0.6987% | \n",
" 1.7539% | \n",
" 1.3735% | \n",
"
\n",
" \n",
" 2016-01-06 | \n",
" 0.2412% | \n",
" -1.7556% | \n",
" -0.7727% | \n",
" -1.2473% | \n",
" -0.1736% | \n",
" -1.5940% | \n",
" 0.1400% | \n",
" 1.0589% | \n",
" -1.8165% | \n",
" 0.5548% | \n",
" -1.5647% | \n",
" -0.1466% | \n",
" -1.0155% | \n",
" -0.9034% | \n",
"
\n",
" \n",
" 2016-01-07 | \n",
" -1.6573% | \n",
" -2.7699% | \n",
" -1.1047% | \n",
" -1.9769% | \n",
" -1.2207% | \n",
" -4.2314% | \n",
" -2.3170% | \n",
" -1.7408% | \n",
" -3.4783% | \n",
" -2.2066% | \n",
" -3.1557% | \n",
" -1.6148% | \n",
" -0.2700% | \n",
" -0.5492% | \n",
"
\n",
" \n",
" 2016-01-08 | \n",
" -1.6037% | \n",
" -2.5425% | \n",
" 0.1099% | \n",
" -0.2241% | \n",
" 0.5707% | \n",
" -1.7950% | \n",
" -1.6410% | \n",
" 0.1720% | \n",
" 0.3067% | \n",
" -0.1539% | \n",
" -0.1448% | \n",
" 0.0895% | \n",
" -3.3838% | \n",
" -0.9719% | \n",
"
\n",
" \n",
" 2016-01-11 | \n",
" -1.6851% | \n",
" -1.0215% | \n",
" 0.0915% | \n",
" -1.1791% | \n",
" 0.5674% | \n",
" 0.4569% | \n",
" 0.2184% | \n",
" 2.0948% | \n",
" -0.0573% | \n",
" 1.6436% | \n",
" -0.1451% | \n",
" 1.2224% | \n",
" 1.4570% | \n",
" 0.5800% | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" BAX BMY CMCSA CNP CPB GE GOOG \\\n",
"Date \n",
"2016-01-05 0.4036% 1.9693% 0.0180% 0.9305% 0.3678% 0.0977% 0.0998% \n",
"2016-01-06 0.2412% -1.7556% -0.7727% -1.2473% -0.1736% -1.5940% 0.1400% \n",
"2016-01-07 -1.6573% -2.7699% -1.1047% -1.9769% -1.2207% -4.2314% -2.3170% \n",
"2016-01-08 -1.6037% -2.5425% 0.1099% -0.2241% 0.5707% -1.7950% -1.6410% \n",
"2016-01-11 -1.6851% -1.0215% 0.0915% -1.1791% 0.5674% 0.4569% 0.2184% \n",
"\n",
" MO MSFT NI SEE T TGT VZ \n",
"Date \n",
"2016-01-05 2.0213% 0.4562% 1.5881% 0.9758% 0.6987% 1.7539% 1.3735% \n",
"2016-01-06 1.0589% -1.8165% 0.5548% -1.5647% -0.1466% -1.0155% -0.9034% \n",
"2016-01-07 -1.7408% -3.4783% -2.2066% -3.1557% -1.6148% -0.2700% -0.5492% \n",
"2016-01-08 0.1720% 0.3067% -0.1539% -0.1448% 0.0895% -3.3838% -0.9719% \n",
"2016-01-11 2.0948% -0.0573% 1.6436% -0.1451% 1.2224% 1.4570% 0.5800% "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"####################################\n",
"# Downloading Data\n",
"####################################\n",
"!pip install --quiet yfinance\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import yfinance as yf\n",
"import warnings\n",
"\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"yf.pdr_override()\n",
"pd.options.display.float_format = '{:.4%}'.format\n",
"\n",
"# Date range\n",
"start = '2016-01-01'\n",
"end = '2019-12-30'\n",
"\n",
"# Tickers of assets\n",
"assets = ['TGT', 'CMCSA', 'CPB', 'MO', 'T', 'BAX', 'BMY',\n",
" 'MSFT', 'SEE', 'VZ', 'CNP', 'NI', 'GE', 'GOOG']\n",
"assets.sort()\n",
"\n",
"# Downloading data\n",
"data = yf.download(assets, start = start, end = end)\n",
"data = data.loc[:,('Adj Close', slice(None))]\n",
"data.columns = assets\n",
"\n",
"# Calculating returns\n",
"Y = data[assets].pct_change().dropna()\n",
"\n",
"display(Y.head())"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "583f7d9a",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Weights | \n",
"
\n",
" \n",
" \n",
" \n",
" BAX | \n",
" 5.8220% | \n",
"
\n",
" \n",
" BMY | \n",
" 8.9075% | \n",
"
\n",
" \n",
" CMCSA | \n",
" 7.9345% | \n",
"
\n",
" \n",
" CNP | \n",
" 15.6326% | \n",
"
\n",
" \n",
" CPB | \n",
" 15.5868% | \n",
"
\n",
" \n",
" GE | \n",
" 0.0000% | \n",
"
\n",
" \n",
" GOOG | \n",
" 4.7998% | \n",
"
\n",
" \n",
" MO | \n",
" 1.8486% | \n",
"
\n",
" \n",
" MSFT | \n",
" 0.0000% | \n",
"
\n",
" \n",
" NI | \n",
" 12.4118% | \n",
"
\n",
" \n",
" SEE | \n",
" 7.5494% | \n",
"
\n",
" \n",
" T | \n",
" 1.1044% | \n",
"
\n",
" \n",
" TGT | \n",
" 17.1416% | \n",
"
\n",
" \n",
" VZ | \n",
" 1.2611% | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Weights\n",
"BAX 5.8220%\n",
"BMY 8.9075%\n",
"CMCSA 7.9345%\n",
"CNP 15.6326%\n",
"CPB 15.5868%\n",
"GE 0.0000%\n",
"GOOG 4.7998%\n",
"MO 1.8486%\n",
"MSFT 0.0000%\n",
"NI 12.4118%\n",
"SEE 7.5494%\n",
"T 1.1044%\n",
"TGT 17.1416%\n",
"VZ 1.2611%"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"####################################\n",
"# Finding the Min EVaR Portfolio\n",
"####################################\n",
"\n",
"import cvxpy as cp\n",
"\n",
"# Defining initial inputs\n",
"mu = Y.mean().to_numpy().reshape(1,-1)\n",
"returns = Y.to_numpy()\n",
"n = returns.shape[0]\n",
"\n",
"# Defining initial variables\n",
"w = cp.Variable((mu.shape[1], 1))\n",
"alpha = 0.05\n",
"ret = mu @ w\n",
"X = returns @ w\n",
"\n",
"# Entropic Value at Risk Model Variables\n",
"t = cp.Variable((1, 1))\n",
"z = cp.Variable((1, 1), nonneg=True)\n",
"ui = cp.Variable((n, 1))\n",
"constraints = [cp.sum(ui) <= z,\n",
" cp.constraints.ExpCone(-X - t, np.ones((n, 1)) @ z, ui)] # Exponential cone constraint\n",
"\n",
"# Budget and weights constraints\n",
"constraints += [cp.sum(w) == 1,\n",
" w >= 0]\n",
"\n",
"# Defining risk objective\n",
"risk = t + z * np.log(1 / (alpha * n))\n",
"objective = cp.Minimize(risk)\n",
"\n",
"# Solving problem\n",
"prob = cp.Problem(objective, constraints)\n",
"prob.solve()\n",
"\n",
"# Showing Optimal Weights\n",
"weights = pd.DataFrame(w.value, index=assets, columns=['Weights'])\n",
"display(weights)"
]
},
{
"cell_type": "markdown",
"id": "3459910a",
"metadata": {},
"source": [
"## 2. Entropic Drawdown at Risk Optimization\n",
"\n",
"### 2.1 The Entropic Drawdown at Risk\n",
"\n",
"The Entropic Drawdown at Risk (EDaR) which is a new risk measure introduced by __[Cajas (2021)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3792520)__. It is the upper bound based on Chernoff Inequality of Drawdown at Risk (DaR) and Conditional Drawdown at Risk (CDaR), formally it is defined as:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\begin{aligned}\n",
"\\text{EDaR}_{\\alpha}(X) & = \\text{EVaR}_{\\alpha}(\\text{DD}(X)) \\\\\n",
"\\text{EDaR}_{\\alpha}(X) & = \\inf_{z>0} \\left \\{ z \\ln \\left (\\frac{1}{\\alpha}M_{\\text{DD}(X)} \\left (\\frac{1}{z} \\right ) \\right ) \\right \\} \\\\\n",
"\\end{aligned}\n",
"\\end{equation}\n",
"$$\n",
"\n",
"where $M_{X}(t)$ is the moment generating function of $t$, $\\alpha \\in [0,1]$ is the significance level and $\\text{DD}(X)$ is the drawdown of $X$.\n",
"\n",
"### 2.2 Maximization of Return/EDaR ratio\n",
"\n",
"This problem is a __[linear fractional programming](https://en.wikipedia.org/wiki/Linear-fractional_programming)__ problem and can be converted to a DCP problem using __Charnes and Cooper transformation__. The DCP problem of maximization of return EDaR ratio was proposed by __[Cajas (2021)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3792520)__ and it is posed as:\n",
"\n",
"$$\n",
"\\begin{equation}\n",
"\\begin{aligned}\n",
"& \\underset{y, \\, k, \\, z, \\, t, \\, u, \\, d}{\\text{min}} & & t + z \\ln \\left ( \\frac{1}{\\alpha T} \\right )\\\\\n",
"& \\text{s.t.} & & \\mu y^{\\tau} - r_{f} k= 1 \\\\\n",
"& & & \\sum_{i=1}^{N} y_{i} = k \\\\\n",
"& & & z \\geq \\sum^{T}_{j=1} u_{j} \\\\\n",
"& & & (d_{j} - R_{j} y^{\\tau} - t, z, u_{j}) \\in K_{\\text{exp}} \\; \\forall \\; j =1, \\ldots, T \\\\\n",
"& & & d_{j} \\geq R_{j} y^{\\tau} \\; \\forall \\; j=1, \\ldots, T \\\\ \n",
"& & & d_{j} \\geq d_{j-1} \\; \\forall \\; j=1, \\ldots, T \\\\ \n",
"& & & d_{j} \\geq 0 \\; \\forall \\; j=1, \\ldots, T \\\\ \n",
"& & & d_{0} = 0 \\\\ \n",
"& & & k \\geq 0 \\\\\n",
"& & & y_{i} \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n",
"\\end{aligned}\n",
"\\end{equation}\n",
"$$\n",
"\n",
"where $R_{j} x^{\\tau} = \\sum^{j}_{i=1} r_{i} x^{\\tau}$, $d_{j}$ is a variable that represents the uncompounded cumulative return of the portfolio and $r_{f}$ is the risk free rate.\n",
"\n",
"Finally, the optimal portfolio is obtained making the transformation $x = y / k$."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c85ededb",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Weights | \n",
"
\n",
" \n",
" \n",
" \n",
" BAX | \n",
" 0.6007% | \n",
"
\n",
" \n",
" BMY | \n",
" 0.0000% | \n",
"
\n",
" \n",
" CMCSA | \n",
" 0.0000% | \n",
"
\n",
" \n",
" CNP | \n",
" 40.7637% | \n",
"
\n",
" \n",
" CPB | \n",
" 0.0000% | \n",
"
\n",
" \n",
" GE | \n",
" 0.0000% | \n",
"
\n",
" \n",
" GOOG | \n",
" 0.0000% | \n",
"
\n",
" \n",
" MO | \n",
" 0.0000% | \n",
"
\n",
" \n",
" MSFT | \n",
" 53.4767% | \n",
"
\n",
" \n",
" NI | \n",
" 0.0000% | \n",
"
\n",
" \n",
" SEE | \n",
" 0.0000% | \n",
"
\n",
" \n",
" T | \n",
" 0.0000% | \n",
"
\n",
" \n",
" TGT | \n",
" 1.4542% | \n",
"
\n",
" \n",
" VZ | \n",
" 3.7048% | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Weights\n",
"BAX 0.6007%\n",
"BMY 0.0000%\n",
"CMCSA 0.0000%\n",
"CNP 40.7637%\n",
"CPB 0.0000%\n",
"GE 0.0000%\n",
"GOOG 0.0000%\n",
"MO 0.0000%\n",
"MSFT 53.4767%\n",
"NI 0.0000%\n",
"SEE 0.0000%\n",
"T 0.0000%\n",
"TGT 1.4542%\n",
"VZ 3.7048%"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#######################################\n",
"# Finding the max return/EDaR Portfolio\n",
"#######################################\n",
"\n",
"# Defining initial inputs\n",
"mu = Y.mean().to_numpy().reshape(1,-1)\n",
"returns = Y.to_numpy()\n",
"nav = Y.cumsum().to_numpy()\n",
"n = returns.shape[0]\n",
"\n",
"# Defining initial variables\n",
"w = cp.Variable((mu.shape[1], 1))\n",
"k = cp.Variable((1, 1))\n",
"rf0 = 0\n",
"alpha = 0.05\n",
"ret = mu @ w\n",
"X1 = nav @ w\n",
"\n",
"# Drawdown Variables\n",
"d = cp.Variable((nav.shape[0] + 1, 1))\n",
"constraints = [d[1:] >= X1,\n",
" d[1:] >= d[:-1],\n",
" d[1:] >= 0,\n",
" d[0] == 0]\n",
"\n",
"# Entropic Drawdown at Risk Model Variables\n",
"t = cp.Variable((1, 1))\n",
"z = cp.Variable((1, 1), nonneg=True)\n",
"ui = cp.Variable((n, 1))\n",
"constraints += [cp.sum(ui) <= z,\n",
" cp.constraints.ExpCone(d[1:] - X1 - t, np.ones((n, 1)) @ z, ui)] # Exponential cone constraint\n",
"\n",
"# Budget and weights constraints\n",
"constraints += [cp.sum(w) == k,\n",
" ret - rf0 * k == 1,\n",
" w >= 0,\n",
" k >= 0]\n",
"\n",
"# Defining risk objective\n",
"risk = t + z * np.log(1 / (alpha * n))\n",
"objective = cp.Minimize(risk)\n",
"\n",
"# Solving problem\n",
"prob = cp.Problem(objective, constraints)\n",
"prob.solve()\n",
"\n",
"# Showing Optimal Weights\n",
"weights = pd.DataFrame(w.value/k.value, index=assets, columns=['Weights'])\n",
"display(weights)"
]
},
{
"cell_type": "markdown",
"id": "de8cc7f7",
"metadata": {},
"source": [
"For more portfolio optimization models and applications, you can see the CVXPY based library __[Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib)__."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}