{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
BAXBMYCMCSACNPCPBGEGOOGMOMSFTNISEETTGTVZ
Date
2016-01-050.4036%1.9693%0.0180%0.9305%0.3678%0.0977%0.0998%2.0213%0.4562%1.5881%0.9758%0.6987%1.7539%1.3735%
2016-01-060.2412%-1.7556%-0.7727%-1.2473%-0.1736%-1.5940%0.1400%1.0589%-1.8165%0.5548%-1.5647%-0.1466%-1.0155%-0.9034%
2016-01-07-1.6573%-2.7699%-1.1047%-1.9769%-1.2207%-4.2314%-2.3170%-1.7408%-3.4783%-2.2066%-3.1557%-1.6148%-0.2700%-0.5492%
2016-01-08-1.6037%-2.5425%0.1099%-0.2241%0.5707%-1.7950%-1.6410%0.1720%0.3067%-0.1539%-0.1448%0.0895%-3.3838%-0.9719%
2016-01-11-1.6851%-1.0215%0.0915%-1.1791%0.5674%0.4569%0.2184%2.0948%-0.0573%1.6436%-0.1451%1.2224%1.4570%0.5800%
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Weights
BAX5.8220%
BMY8.9075%
CMCSA7.9345%
CNP15.6326%
CPB15.5868%
GE0.0000%
GOOG4.7998%
MO1.8486%
MSFT0.0000%
NI12.4118%
SEE7.5494%
T1.1044%
TGT17.1416%
VZ1.2611%
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Weights
BAX0.6007%
BMY0.0000%
CMCSA0.0000%
CNP40.7637%
CPB0.0000%
GE0.0000%
GOOG0.0000%
MO0.0000%
MSFT53.4767%
NI0.0000%
SEE0.0000%
T0.0000%
TGT1.4542%
VZ3.7048%
\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 }