{ "cells": [ { "cell_type": "markdown", "id": "d760e6e6", "metadata": {}, "source": [ "# Portfolio Optimization using Second Order Cone\n", "\n", "In this notebook we show how to use the Second Order Cone (SOC) constraint in the variance portfolio optimization problem.\n", "\n", "## 1. Variance Optimization\n", "\n", "### 1.1 Variance Minimization\n", "\n", "The minimization of portfolio variance is a quadratic optimization problem that can be posed as:\n", "\n", "$$\n", "\\begin{equation}\n", "\\begin{aligned}\n", "& \\underset{x}{\\text{min}} & & x^{\\tau} \\Sigma x \\\\\n", "& \\text{s.t.} & & \\mu x^{\\tau} \\geq \\bar{\\mu} \\\\\n", "& & & \\sum_{i=1}^{N} x_i = 1 \\\\\n", "& & & x_i \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n", "\\end{aligned}\n", "\\end{equation}\n", "$$\n", "\n", "Where $x$ are the weights of assets, $\\mu$ is the mean vector of expected returns and $\\bar{\\mu}$ the minimum expected return of portfolio." ] }, { "cell_type": "code", "execution_count": 1, "id": "2a19278a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[*********************100%***********************] 25 of 25 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", " \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", "
APABABAXBMYCMCSACNPCPBDEHPQJCI...NIPCARPSASEETTGTTMOTXTVZZION
Date
2016-01-05-2.0257%0.4057%0.4036%1.9693%0.0180%0.9305%0.3678%0.5783%0.9483%-1.1953%...1.5881%0.0212%2.8236%0.9758%0.6987%1.7539%-0.1730%0.2410%1.3735%-1.0857%
2016-01-06-11.4863%-1.5878%0.2412%-1.7557%-0.7727%-1.2473%-0.1736%-1.1239%-3.5867%-0.9551%...0.5547%0.0212%0.1592%-1.5647%-0.1466%-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.8855%-4.6059%-2.5394%...-2.2066%-3.0309%-1.0410%-3.1557%-1.6148%-0.2700%-2.2845%-2.0570%-0.5492%-3.0020%
2016-01-080.2736%-2.2705%-1.6037%-2.5425%0.1099%-0.2241%0.5707%-1.6402%-1.7641%-0.1649%...-0.1538%-1.1366%-0.7308%-0.1449%0.0895%-3.3838%-0.1117%-1.1387%-0.9720%-1.1254%
2016-01-11-4.3383%0.1692%-1.6851%-1.0215%0.0914%-1.1792%0.5674%0.5288%0.6616%0.0330%...1.6436%0.0000%0.9869%-0.1450%1.2225%1.4570%0.5367%-0.4607%0.5800%-1.9919%
\n", "

5 rows × 25 columns

\n", "
" ], "text/plain": [ " APA BA BAX BMY CMCSA CNP CPB \\\n", "Date \n", "2016-01-05 -2.0257% 0.4057% 0.4036% 1.9693% 0.0180% 0.9305% 0.3678% \n", "2016-01-06 -11.4863% -1.5878% 0.2412% -1.7557% -0.7727% -1.2473% -0.1736% \n", "2016-01-07 -5.1388% -4.1922% -1.6573% -2.7699% -1.1047% -1.9769% -1.2207% \n", "2016-01-08 0.2736% -2.2705% -1.6037% -2.5425% 0.1099% -0.2241% 0.5707% \n", "2016-01-11 -4.3383% 0.1692% -1.6851% -1.0215% 0.0914% -1.1792% 0.5674% \n", "\n", " DE HPQ JCI ... NI PCAR PSA \\\n", "Date ... \n", "2016-01-05 0.5783% 0.9483% -1.1953% ... 1.5881% 0.0212% 2.8236% \n", "2016-01-06 -1.1239% -3.5867% -0.9551% ... 0.5547% 0.0212% 0.1592% \n", "2016-01-07 -0.8855% -4.6059% -2.5394% ... -2.2066% -3.0309% -1.0410% \n", "2016-01-08 -1.6402% -1.7641% -0.1649% ... -0.1538% -1.1366% -0.7308% \n", "2016-01-11 0.5288% 0.6616% 0.0330% ... 1.6436% 0.0000% 0.9869% \n", "\n", " SEE T TGT TMO TXT VZ ZION \n", "Date \n", "2016-01-05 0.9758% 0.6987% 1.7539% -0.1730% 0.2410% 1.3735% -1.0857% \n", "2016-01-06 -1.5647% -0.1466% -1.0155% -0.7653% -3.0048% -0.9034% -2.9145% \n", "2016-01-07 -3.1557% -1.6148% -0.2700% -2.2845% -2.0570% -0.5492% -3.0020% \n", "2016-01-08 -0.1449% 0.0895% -3.3838% -0.1117% -1.1387% -0.9720% -1.1254% \n", "2016-01-11 -0.1450% 1.2225% 1.4570% 0.5367% -0.4607% 0.5800% -1.9919% \n", "\n", "[5 rows x 25 columns]" ] }, "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 = ['JCI', 'TGT', 'CMCSA', 'CPB', 'MO', 'APA', 'MMC', 'JPM',\n", " 'ZION', 'PSA', 'BAX', 'BMY', 'LUV', 'PCAR', 'TXT', 'TMO',\n", " 'DE', 'MSFT', 'HPQ', 'SEE', 'VZ', 'CNP', 'NI', 'T', 'BA']\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", " \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", "
ECOSSCS
APA0.0002%-0.0007%
BA0.0012%0.0006%
BAX5.2647%5.2662%
BMY4.3893%4.3904%
CMCSA2.1705%2.1772%
CNP6.9881%6.9878%
CPB3.2388%3.2403%
DE0.0707%0.0874%
HPQ0.0001%-0.0005%
JCI2.8381%2.8435%
JPM6.9786%6.9421%
LUV2.8524%2.8590%
MMC12.5818%12.6038%
MO7.2325%7.2291%
MSFT0.0002%-0.0006%
NI11.4513%11.4451%
PCAR0.0003%-0.0019%
PSA14.9188%14.9124%
SEE0.1563%0.1671%
T6.4205%6.4208%
TGT4.0908%4.0965%
TMO0.0004%0.0005%
TXT0.0007%-0.0021%
VZ8.3448%8.3447%
ZION0.0087%-0.0074%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "APA 0.0002% -0.0007%\n", "BA 0.0012% 0.0006%\n", "BAX 5.2647% 5.2662%\n", "BMY 4.3893% 4.3904%\n", "CMCSA 2.1705% 2.1772%\n", "CNP 6.9881% 6.9878%\n", "CPB 3.2388% 3.2403%\n", "DE 0.0707% 0.0874%\n", "HPQ 0.0001% -0.0005%\n", "JCI 2.8381% 2.8435%\n", "JPM 6.9786% 6.9421%\n", "LUV 2.8524% 2.8590%\n", "MMC 12.5818% 12.6038%\n", "MO 7.2325% 7.2291%\n", "MSFT 0.0002% -0.0006%\n", "NI 11.4513% 11.4451%\n", "PCAR 0.0003% -0.0019%\n", "PSA 14.9188% 14.9124%\n", "SEE 0.1563% 0.1671%\n", "T 6.4205% 6.4208%\n", "TGT 4.0908% 4.0965%\n", "TMO 0.0004% 0.0005%\n", "TXT 0.0007% -0.0021%\n", "VZ 8.3448% 8.3447%\n", "ZION 0.0087% -0.0074%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "####################################\n", "# Minimizing Portfolio Variance\n", "####################################\n", "\n", "import cvxpy as cp\n", "from timeit import default_timer as timer\n", "\n", "# Defining initial inputs\n", "mu = Y.mean().to_numpy().reshape(1,-1)\n", "sigma = Y.cov().to_numpy()\n", "\n", "# Defining initial variables\n", "x = cp.Variable((mu.shape[1], 1))\n", "\n", "# Budget and weights constraints\n", "constraints = [cp.sum(x) == 1,\n", " x <= 1,\n", " x >= 0]\n", "\n", "# Defining risk objective\n", "risk = cp.quad_form(x, sigma)\n", "objective = cp.Minimize(risk)\n", "\n", "weights = pd.DataFrame([])\n", "# Solving the problem with several solvers\n", "prob = cp.Problem(objective, constraints)\n", "solvers = ['ECOS', 'SCS']\n", "for i in solvers:\n", " prob.solve(solver=i)\n", " # Showing Optimal Weights\n", " weights_1 = pd.DataFrame(x.value, index=assets, columns=[i])\n", " weights = pd.concat([weights, weights_1], axis=1)\n", "\n", "display(weights)" ] }, { "cell_type": "markdown", "id": "9661b089", "metadata": {}, "source": [ "As we can see the use of CVXPY's __quad_form__ in portfolio optimization can give small negative values to weights that must be zero." ] }, { "cell_type": "code", "execution_count": 3, "id": "80c95d46", "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", "
ECOSSCS
Return12.8507%12.8498%
Std. Dev.10.3737%10.3738%
Variance1.0761%1.0761%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "Return 12.8507% 12.8498%\n", "Std. Dev. 10.3737% 10.3738%\n", "Variance 1.0761% 1.0761%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Calculating Annualized Portfolio Stats\n", "var = weights * (Y.cov() @ weights) * 252\n", "var = var.sum().to_frame().T\n", "std = np.sqrt(var)\n", "ret = Y.mean().to_frame().T @ weights * 252\n", "\n", "stats = pd.concat([ret, std, var], axis=0)\n", "stats.index = ['Return', 'Std. Dev.', 'Variance']\n", "\n", "display(stats)" ] }, { "cell_type": "markdown", "id": "a4900343", "metadata": {}, "source": [ "### 1.2 Return Maximization with Variance Constraint\n", "\n", "The maximization of portfolio return is a problem with a quadratic constraint that can be posed as:\n", "\n", "$$\n", "\\begin{equation}\n", "\\begin{aligned}\n", "& \\underset{x}{\\text{max}} & & \\mu x^{\\tau} \\\\\n", "& \\text{s.t.} & & x^{\\tau} \\Sigma x \\leq \\bar{\\sigma}^{2} \\\\\n", "& & & \\sum_{i=1}^{N} x_i = 1 \\\\\n", "& & & x_i \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n", "\\end{aligned}\n", "\\end{equation}\n", "$$\n", "\n", "Where $x$ are the weights of assets and $\\bar{\\sigma}$ is the maximum expected standard deviation of portfolio.." ] }, { "cell_type": "code", "execution_count": 4, "id": "f04e0f69", "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", " \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", "
ECOSSCS
APA0.0000%0.0006%
BA9.5892%9.6764%
BAX12.6176%12.5937%
BMY0.0000%0.0051%
CMCSA0.0000%0.0072%
CNP3.7021%3.2811%
CPB0.0000%0.0089%
DE6.2565%6.3273%
HPQ0.0000%-0.0003%
JCI0.0000%0.0053%
JPM6.5739%6.5254%
LUV0.0000%0.0048%
MMC18.7461%18.5184%
MO0.0000%0.0161%
MSFT35.7121%36.2356%
NI0.0278%0.0099%
PCAR0.0000%0.0008%
PSA0.0000%0.0179%
SEE0.0000%0.0057%
T0.0000%0.0126%
TGT6.7741%6.7077%
TMO0.0002%0.0011%
TXT0.0000%0.0028%
VZ0.0001%0.0117%
ZION0.0001%-0.0002%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "APA 0.0000% 0.0006%\n", "BA 9.5892% 9.6764%\n", "BAX 12.6176% 12.5937%\n", "BMY 0.0000% 0.0051%\n", "CMCSA 0.0000% 0.0072%\n", "CNP 3.7021% 3.2811%\n", "CPB 0.0000% 0.0089%\n", "DE 6.2565% 6.3273%\n", "HPQ 0.0000% -0.0003%\n", "JCI 0.0000% 0.0053%\n", "JPM 6.5739% 6.5254%\n", "LUV 0.0000% 0.0048%\n", "MMC 18.7461% 18.5184%\n", "MO 0.0000% 0.0161%\n", "MSFT 35.7121% 36.2356%\n", "NI 0.0278% 0.0099%\n", "PCAR 0.0000% 0.0008%\n", "PSA 0.0000% 0.0179%\n", "SEE 0.0000% 0.0057%\n", "T 0.0000% 0.0126%\n", "TGT 6.7741% 6.7077%\n", "TMO 0.0002% 0.0011%\n", "TXT 0.0000% 0.0028%\n", "VZ 0.0001% 0.0117%\n", "ZION 0.0001% -0.0002%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#########################################\n", "# Maximizing Portfolio Return with\n", "# Variance Constraint\n", "#########################################\n", "\n", "import cvxpy as cp\n", "from timeit import default_timer as timer\n", "\n", "# Defining initial inputs\n", "mu = Y.mean().to_numpy().reshape(1,-1)\n", "sigma = Y.cov().to_numpy()\n", "\n", "# Defining initial variables\n", "x = cp.Variable((mu.shape[1], 1))\n", "sigma_hat = 15 / (252**0.5 * 100)\n", "ret = mu @ x\n", "\n", "# Budget and weights constraints\n", "constraints = [cp.sum(x) == 1,\n", " x <= 1,\n", " x >= 0]\n", "\n", "# Defining risk constraint and objective\n", "risk = cp.quad_form(x, sigma)\n", "constraints += [risk <= sigma_hat**2] # variance constraint\n", "objective = cp.Maximize(ret)\n", "\n", "weights = pd.DataFrame([])\n", "# Solving the problem with several solvers\n", "prob = cp.Problem(objective, constraints)\n", "solvers = ['ECOS', 'SCS']\n", "for i in solvers:\n", " prob.solve(solver=i)\n", " # Showing Optimal Weights\n", " weights_1 = pd.DataFrame(x.value, index=assets, columns=[i])\n", " weights = pd.concat([weights, weights_1], axis=1)\n", "\n", "display(weights)" ] }, { "cell_type": "markdown", "id": "54f4e088", "metadata": {}, "source": [ "The small negative values also appear when we use CVXPY's __quad_form__ in constraints." ] }, { "cell_type": "code", "execution_count": 5, "id": "298f0630", "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", "
ECOSSCS
Return26.0352%26.1001%
Std. Dev.15.0000%15.0672%
Variance2.2500%2.2702%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "Return 26.0352% 26.1001%\n", "Std. Dev. 15.0000% 15.0672%\n", "Variance 2.2500% 2.2702%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Calculating Annualized Portfolio Stats\n", "var = weights * (Y.cov() @ weights) * 252\n", "var = var.sum().to_frame().T\n", "std = np.sqrt(var)\n", "ret = Y.mean().to_frame().T @ weights * 252\n", "\n", "stats = pd.concat([ret, std, var], axis=0)\n", "stats.index = ['Return', 'Std. Dev.', 'Variance']\n", "\n", "display(stats)" ] }, { "cell_type": "markdown", "id": "3459910a", "metadata": {}, "source": [ "## 2 Standard Deviation Optimization\n", "\n", "### 2.1 Standard Deviation Minimization\n", "\n", "An alternative problem is to minimize the standard deviation (square root of variance). To do this we can use the SOC constraint. The minimization of portfolio standard deviation can be posed as:\n", "\n", "$$\n", "\\begin{equation}\n", "\\begin{aligned}\n", "& \\underset{x}{\\text{min}} & & g \\\\\n", "& \\text{s.t.} & & \\mu x^{\\tau} \\geq \\bar{\\mu} \\\\\n", "& & & \\sum_{i=1}^{N} x_i = 1 \\\\\n", "& & & \\left\\|\\Sigma^{1/2} x\\right\\| \\leq g \\\\\n", "& & & x_i \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n", "\\end{aligned}\n", "\\end{equation}\n", "$$\n", "\n", "Where $\\left\\|\\Sigma^{1/2} x\\right\\| \\leq g$ is the SOC constraint, $x$ are the weights of assets, $\\mu$ is the mean vector of expected returns, $\\bar{\\mu}$ the minimum expected return of portfolio and $r$ is the matrix of observed returns.\n", "\n", "__Note:__ the SOC constraint can be expressed as $(g,\\Sigma^{1/2} x) \\in Q^{n+1}$, this notation is used to model the __SOC constraint__ in CVXPY." ] }, { "cell_type": "code", "execution_count": 6, "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", " \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", "
ECOSSCS
APA0.0000%0.0000%
BA0.0000%0.0001%
BAX5.2634%5.2632%
BMY4.3887%4.3886%
CMCSA2.1705%2.1705%
CNP6.9872%6.9870%
CPB3.2390%3.2391%
DE0.0789%0.0791%
HPQ0.0000%0.0002%
JCI2.8376%2.8375%
JPM6.9842%6.9839%
LUV2.8523%2.8522%
MMC12.5805%12.5803%
MO7.2314%7.2313%
MSFT0.0000%0.0003%
NI11.4509%11.4508%
PCAR0.0000%0.0001%
PSA14.9186%14.9183%
SEE0.1621%0.1628%
T6.4196%6.4196%
TGT4.0904%4.0904%
TMO0.0000%0.0002%
TXT0.0000%0.0001%
VZ8.3447%8.3446%
ZION0.0000%0.0001%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "APA 0.0000% 0.0000%\n", "BA 0.0000% 0.0001%\n", "BAX 5.2634% 5.2632%\n", "BMY 4.3887% 4.3886%\n", "CMCSA 2.1705% 2.1705%\n", "CNP 6.9872% 6.9870%\n", "CPB 3.2390% 3.2391%\n", "DE 0.0789% 0.0791%\n", "HPQ 0.0000% 0.0002%\n", "JCI 2.8376% 2.8375%\n", "JPM 6.9842% 6.9839%\n", "LUV 2.8523% 2.8522%\n", "MMC 12.5805% 12.5803%\n", "MO 7.2314% 7.2313%\n", "MSFT 0.0000% 0.0003%\n", "NI 11.4509% 11.4508%\n", "PCAR 0.0000% 0.0001%\n", "PSA 14.9186% 14.9183%\n", "SEE 0.1621% 0.1628%\n", "T 6.4196% 6.4196%\n", "TGT 4.0904% 4.0904%\n", "TMO 0.0000% 0.0002%\n", "TXT 0.0000% 0.0001%\n", "VZ 8.3447% 8.3446%\n", "ZION 0.0000% 0.0001%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#########################################\n", "# Minimizing Portfolio Standard Deviation\n", "#########################################\n", "\n", "from scipy.linalg import sqrtm\n", "\n", "# Defining initial inputs\n", "mu = Y.mean().to_numpy().reshape(1,-1)\n", "sigma = Y.cov().to_numpy()\n", "G = sqrtm(sigma)\n", "\n", "# Defining initial variables\n", "x = cp.Variable((mu.shape[1], 1))\n", "g = cp.Variable(nonneg=True)\n", "\n", "# Budget and weights constraints\n", "constraints = [cp.sum(x) == 1,\n", " x >= 0]\n", "\n", "# Defining risk objective\n", "risk = g\n", "constraints += [cp.SOC(g, G @ x)] # SOC constraint\n", "constraints += [risk <= sigma_hat] # variance constraint\n", "objective = cp.Minimize(risk)\n", "\n", "weights = pd.DataFrame([])\n", "# Solving the problem with several solvers\n", "prob = cp.Problem(objective, constraints)\n", "solvers = ['ECOS', 'SCS']\n", "for i in solvers:\n", " prob.solve(solver=i)\n", " # Showing Optimal Weights\n", " weights_1 = pd.DataFrame(x.value, index=assets, columns=[i])\n", " weights = pd.concat([weights, weights_1], axis=1)\n", "\n", "display(weights)" ] }, { "cell_type": "markdown", "id": "5e4fdaab", "metadata": {}, "source": [ "As we can see the use of CVXPY's __SOC constraint__ in portfolio optimization solves the error that we see when we use __quad_form__." ] }, { "cell_type": "code", "execution_count": 7, "id": "f7ba5cc1", "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", "
ECOSSCS
Return12.8508%12.8509%
Std. Dev.10.3737%10.3737%
Variance1.0761%1.0761%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "Return 12.8508% 12.8509%\n", "Std. Dev. 10.3737% 10.3737%\n", "Variance 1.0761% 1.0761%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Calculating Annualized Portfolio Stats\n", "var = weights * (Y.cov() @ weights) * 252\n", "var = var.sum().to_frame().T\n", "std = np.sqrt(var)\n", "ret = Y.mean().to_frame().T @ weights * 252\n", "\n", "stats = pd.concat([ret, std, var], axis=0)\n", "stats.index = ['Return', 'Std. Dev.', 'Variance']\n", "\n", "display(stats)" ] }, { "cell_type": "markdown", "id": "2b1a7724", "metadata": {}, "source": [ "### 2.2 Return Maximization with Standard Deviation Constraint\n", "\n", "The maximization of portfolio return using SOC constraints can be posed as:\n", "\n", "$$\n", "\\begin{equation}\n", "\\begin{aligned}\n", "& \\underset{x}{\\text{max}} & & \\mu x^{\\tau} \\\\\n", "& \\text{s.t.} & & g \\leq \\bar{\\sigma} \\\\\n", "& & & \\left\\|\\Sigma^{1/2} x\\right\\| \\leq g \\\\\n", "& & & \\sum_{i=1}^{N} x_i = 1 \\\\\n", "& & & x_i \\geq 0 \\; ; \\; \\forall \\; i =1, \\ldots, N \\\\\n", "\\end{aligned}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "code", "execution_count": 8, "id": "6e1578ed", "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", " \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", "
ECOSSCS
APA0.0000%0.0003%
BA9.5885%9.5835%
BAX12.6190%12.6236%
BMY0.0000%-0.0011%
CMCSA0.0000%-0.0010%
CNP3.7154%3.7281%
CPB0.0000%-0.0014%
DE6.2555%6.2519%
HPQ0.0000%0.0006%
JCI0.0000%-0.0006%
JPM6.5725%6.5909%
LUV0.0000%-0.0008%
MMC18.7512%18.7542%
MO0.0000%-0.0017%
MSFT35.7087%35.6702%
NI0.0135%0.0331%
PCAR0.0000%-0.0001%
PSA0.0000%-0.0018%
SEE0.0000%-0.0007%
T0.0000%-0.0015%
TGT6.7755%6.7784%
TMO0.0001%0.0001%
TXT0.0000%0.0001%
VZ0.0000%-0.0013%
ZION0.0000%0.0009%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "APA 0.0000% 0.0003%\n", "BA 9.5885% 9.5835%\n", "BAX 12.6190% 12.6236%\n", "BMY 0.0000% -0.0011%\n", "CMCSA 0.0000% -0.0010%\n", "CNP 3.7154% 3.7281%\n", "CPB 0.0000% -0.0014%\n", "DE 6.2555% 6.2519%\n", "HPQ 0.0000% 0.0006%\n", "JCI 0.0000% -0.0006%\n", "JPM 6.5725% 6.5909%\n", "LUV 0.0000% -0.0008%\n", "MMC 18.7512% 18.7542%\n", "MO 0.0000% -0.0017%\n", "MSFT 35.7087% 35.6702%\n", "NI 0.0135% 0.0331%\n", "PCAR 0.0000% -0.0001%\n", "PSA 0.0000% -0.0018%\n", "SEE 0.0000% -0.0007%\n", "T 0.0000% -0.0015%\n", "TGT 6.7755% 6.7784%\n", "TMO 0.0001% 0.0001%\n", "TXT 0.0000% 0.0001%\n", "VZ 0.0000% -0.0013%\n", "ZION 0.0000% 0.0009%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#########################################\n", "# Maximizing Portfolio Return with\n", "# Standard Deviation Constraint\n", "#########################################\n", "\n", "from scipy.linalg import sqrtm\n", "\n", "# Defining initial inputs\n", "mu = Y.mean().to_numpy().reshape(1,-1)\n", "sigma = Y.cov().to_numpy()\n", "G = sqrtm(sigma)\n", "\n", "# Defining initial variables\n", "x = cp.Variable((mu.shape[1], 1))\n", "g = cp.Variable(nonneg=True)\n", "sigma_hat = 15 / (252**0.5 * 100)\n", "ret = mu @ x\n", "\n", "# Budget and weights constraints\n", "constraints = [cp.sum(x) == 1,\n", " x <= 1,\n", " x >= 0]\n", "\n", "\n", "# Defining risk constraint and objective\n", "risk = g\n", "constraints += [cp.SOC(g, G @ x)] # SOC constraint\n", "constraints += [risk <= sigma_hat] # standard deviation constraint\n", "objective = cp.Maximize(ret)\n", "\n", "weights = pd.DataFrame([])\n", "# Solving the problem with several solvers\n", "prob = cp.Problem(objective, constraints)\n", "solvers = ['ECOS', 'SCS']\n", "for i in solvers:\n", " prob.solve(solver=i)\n", " # Showing Optimal Weights\n", " weights_1 = pd.DataFrame(x.value, index=assets, columns=[i])\n", " weights = pd.concat([weights, weights_1], axis=1)\n", "\n", "display(weights)" ] }, { "cell_type": "markdown", "id": "30e42609", "metadata": {}, "source": [ "CVXPY's __SOC constraint__ also solves the error that we see when we use __quad_form__ in constraints." ] }, { "cell_type": "code", "execution_count": 9, "id": "7dc3764c", "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", "
ECOSSCS
Return26.0352%26.0316%
Std. Dev.15.0000%14.9958%
Variance2.2500%2.2487%
\n", "
" ], "text/plain": [ " ECOS SCS\n", "Return 26.0352% 26.0316%\n", "Std. Dev. 15.0000% 14.9958%\n", "Variance 2.2500% 2.2487%" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Calculating Annualized Portfolio Stats\n", "var = weights * (Y.cov() @ weights) * 252\n", "var = var.sum().to_frame().T\n", "std = np.sqrt(var)\n", "ret = Y.mean().to_frame().T @ weights * 252\n", "\n", "stats = pd.concat([ret, std, var], axis=0)\n", "stats.index = ['Return', 'Std. Dev.', 'Variance']\n", "\n", "display(stats)" ] }, { "cell_type": "markdown", "id": "80e0bd8f", "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 }