{ "cells": [ { "cell_type": "code", "execution_count": 25, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 235 }, "colab_type": "code", "executionInfo": { "elapsed": 7090, "status": "ok", "timestamp": 1576771469964, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "vI1xySqDhHqm", "outputId": "7d785476-d21c-4ef3-bf1b-274e8900fcb4" }, "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", "
MKTUSBondUSEMMKTxUSBondxUS
Date
2016-08-310.0050-0.0086170.0249860.000638-0.009752
2016-09-300.0025-0.0166170.0129530.0125360.009779
2016-10-31-0.0202-0.0496600.002274-0.020583-0.043676
2016-11-300.0486-0.081736-0.046071-0.019898-0.050459
2016-12-310.0182-0.0055960.0026040.034083-0.023507
\n", "
" ], "text/plain": [ " MKTUS BondUS EM MKTxUS BondxUS\n", "Date \n", "2016-08-31 0.0050 -0.008617 0.024986 0.000638 -0.009752\n", "2016-09-30 0.0025 -0.016617 0.012953 0.012536 0.009779\n", "2016-10-31 -0.0202 -0.049660 0.002274 -0.020583 -0.043676\n", "2016-11-30 0.0486 -0.081736 -0.046071 -0.019898 -0.050459\n", "2016-12-31 0.0182 -0.005596 0.002604 0.034083 -0.023507" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "#GlobalFinMonthly\n", "url=\"https://raw.githubusercontent.com/amoreira2/Lectures/4b95c7d60e869dffa6367c1730183b68920ed2b9/assets/data/GlobalFinMonthly.csv\"\n", "Data = pd.read_csv(url,na_values=-99)\n", "# tell python Date is date:\n", "Data['Date']=pd.to_datetime(Data['Date'])\n", "# set an an index\n", "Data=Data.set_index(['Date'])\n", "Data=Data.rename(columns={Data.columns[1]: \"MKTUS\",Data.columns[2]: \"BondUS\",\n", " Data.columns[3]: \"EM\",Data.columns[4]: \"MKTxUS\",Data.columns[5]: \"BondxUS\" })\n", "df=(Data.drop('RF',axis=1)).subtract(Data['RF'],axis='index')\n", "df.tail()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "QgykM3kZhHqj" }, "source": [ "# Portfolio Optimization with constraints\n", "\n", "* Another practical issue that practioners face is that MV analysis (in it's more pure form) does not account for several real world frictions that are more or less important depending on the investor:\n", "\n", " * Shorting an asset requires paying a borrowing fee to the owner of the asset. Shorting costs are sometimes prohibitive for small illiquid stocks\n", " \n", " * Most investors cannot really borrow at anything close to the risk-free rate--i.e., often they have to pay a substantially higher rate to borrow than to lend. \n", " \n", "* Python has the flexibility to solve the mean-variance problem numerically\n", "\n", "* Allows you to impose realistic features to our portfolio problem \n", "\n", " * Shorting costs\n", " * Borrowing costs higher than lending costs\n", " * Leverage constraints\n", " * Position limits due to investment mandates\n", "\n", "Lets start by importing optimization package -- think of a much more powerful solver (excel)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "colab": {}, "colab_type": "code", "id": "EeCKiapGhHqs" }, "outputs": [], "source": [ "from scipy.optimize import minimize" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "yG4F0U3DhHqt" }, "source": [ "* we will now learn how to do a numerical minimization. \n", "\n", "* start by defining some useful variables" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 221 }, "colab_type": "code", "executionInfo": { "elapsed": 470, "status": "ok", "timestamp": 1576771485393, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "CQ7eN1_HhHqu", "outputId": "b6684972-dd8f-4c15-be81-9a6873db2501" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MKTUS 0.005140\n", "BondUS 0.002523\n", "EM 0.006923\n", "MKTxUS 0.004149\n", "BondxUS 0.002054\n", "dtype: float64\n", " MKTUS BondUS EM MKTxUS BondxUS\n", "MKTUS 0.001948 0.000111 0.001292 0.001264 0.000187\n", "BondUS 0.000111 0.001227 -0.000204 -0.000013 0.000264\n", "EM 0.001292 -0.000204 0.003556 0.001661 0.000249\n", "MKTxUS 0.001264 -0.000013 0.001661 0.002182 0.000422\n", "BondxUS 0.000187 0.000264 0.000249 0.000422 0.000407\n" ] } ], "source": [ "ERe=Re.mean()\n", "print(ERe)\n", "Cove=Re.cov()\n", "print(Cove)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Modeling Borrowing limits**" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 51 }, "colab_type": "code", "executionInfo": { "elapsed": 443, "status": "ok", "timestamp": 1576771499016, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "sBi8AosojWMY", "outputId": "2889f000-99fe-4487-d815-99688638bd94" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0008333333333333334\n", "0.005\n" ] } ], "source": [ "# the risk-free rate is special because at a given time we know exactly what the expected returns of investing\n", "# in it, since it is risk-free!\n", "# so it does not make sense to use the average risk-free rate in the portfolio problem, and we should rather use the one that\n", "# we currently can invest at no risk\n", "# here we are dividing by 12 becasue we are using annualized numbers\n", "rf=0.01/12\n", "print(rf)\n", "\n", "# below is the target for monthly expected returns\n", "Ertarget=0.06/12\n", "print(Ertarget)\n", "\n", "# We pick a margin requirement\n", "margin=0.5" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "LRy1O4YChHqw" }, "source": [ "* The program will choose a vector of weights that minimzies some function (of the chosen weights) subject to some constraint\n", "\n", "* we need to provide an initial condition for these weights, which essentially tells python the shape of the variable that it is choosing. Here we will start with a equal weighted portfolio\n", "\n", "* When solving minimization problems it is important to check that the exact initial conditions that you put in don't matter. \n", "\n", "* I mean, it's shape obviously matter, but whether is equal weighted or any other weighting scheme, the minimization should end up in the same place otherwise the numerical is not converging and you need to adjust the stop conditions\n", "\n", "* We refer to this as making sure that you are finding a \"global optimum\" and not a \"local optimum\"" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "colab_type": "code", "executionInfo": { "elapsed": 593, "status": "ok", "timestamp": 1576771502607, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "xc58RNm2hHqw", "outputId": "3cc18867-40f5-4b90-d2a3-434e7e67938e" }, "outputs": [ { "data": { "text/plain": [ "array([0.2, 0.2, 0.2, 0.2, 0.2])" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n=Re.shape[1]\n", "W0=np.ones(n)/n \n", "W0\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "Qw4LGPS1hHqy" }, "source": [ "**The objective function**\n", "\n", "* We need to specify the function that our minimizer will try to minimize,i.e, our objective\n", "\n", "* obviously this should be written as a function of the portfolio weights\n", "\n", "* In this case it is the variance of a portfolio with weights W\n", "\n", "* the weights will be our choice variable\n", "\n", "* The minimization function will change the weights until this variance is minimized\n", "\n" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "colab": {}, "colab_type": "code", "id": "saNsLsmWhHqy" }, "outputs": [], "source": [ "# STEP 1: define the objective function, which is the variance of the portfolio in our case\n", "\n", "def func(W,Cove=Re.cov()):\n", " return W @ Cove @ W\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "xmxQ0xTohHq0" }, "source": [ "* Finally we need to specify the set of constraints that the minimization problem must satisfy\n", "\n", "* you can specify two types of constraints: Equalities and inequalities.\n", "\n", "* Equalities:\n", " * specified as 'eq' \n", " * For example:`{'eq', 'fun' : lambda W : W.T @ (ERe+rf) +(1-W.sum())*rf-target}`\n", " * defines the function `F(W)=W @ (ERe+rf) +(1-W.sum())*rf-target`\n", " * and requires W to satisfy F(W)=0, i.e., `W @ (ERe+rf) +(1-W.sum())*rf=target`, or\n", " \n", " $$W'(ERe+rf) +(1-\\sum w_i)rf=target$$\n", " \n", " * \"target\" here is the target for expected returns\n", " \n", "* Inequalities:\n", " * specified as 'ineq'\n", " * For example: `{'ineq', 'fun' : lambda W: -W.sum()+1/margin}`\n", " * Defines `F(W)=-W.sum()+1/margin`\n", " * and requires F(W)>=0, i.e.,\n", " \n", " $$\\frac{1}{margin}\\geq W.sum()$$\n", " \n", " It is useful to create a function that returns the expected returns of our portfolio given a weight choice" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "def expected_return(W):\n", " return W @ (ERe+rf) +(1-W.sum())*rf\n" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 51 }, "colab_type": "code", "executionInfo": { "elapsed": 457, "status": "ok", "timestamp": 1576771515213, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "Vn1Ta5LihHq1", "outputId": "aa408da2-0d5c-449d-8037-2f0e96aa5ac2" }, "outputs": [ { "data": { "text/plain": [ "({'type': 'eq', 'fun': (W)>},\n", " {'type': 'ineq', 'fun': (W)>})" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# STEP 2: define constraint functions, we have two constraints: (1) target average return, and (2) leverage limit\n", "Ertarget=0.12/12\n", "cons=({'type': 'eq',\n", " 'fun' : lambda W : expected_return(W)-Ertarget},\n", " {'type': 'ineq',\n", " 'fun' : lambda W: -W.sum()+1/margin}) \n", "\n", "\n", "cons\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "CZaDkZb6hHq2" }, "source": [ "* Finally we are ready to do our minimization\n", "* `options={'disp': True}`: tell python to report intermediary steps so we can check if the algorithm is doing progress towards a solution.\n", "* There is a variety of additional parameters that you can pass to the minimizer. \n", "* see: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html\n" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 102 }, "colab_type": "code", "executionInfo": { "elapsed": 905, "status": "ok", "timestamp": 1576771544739, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "YLkxDEhJhHq3", "outputId": "dcf80769-8fb4-4eb3-b55d-8aac8ed4a285" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 0.003404665865183952\n", " Iterations: 7\n", " Function evaluations: 42\n", " Gradient evaluations: 7\n" ] } ], "source": [ "# STEP 3: solve the minimization problem with constraints and save as `sol`\n", "\n", "sol = minimize(lambda x: func(x,Cove=Re.cov()),W0, constraints=cons, options={'disp': True})" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2.0" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sol.x.sum()" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "colab_type": "code", "executionInfo": { "elapsed": 857, "status": "ok", "timestamp": 1576771547802, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "RupZVNqyoWDG", "outputId": "50d85bbd-8ef1-457b-a727-18c0f293d6dd" }, "outputs": [ { "data": { "text/plain": [ "array([ 0.58565363, 0.67667759, 0.69052641, -0.20448374, 0.25162612])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# STEP 4: get the optimal portfolio weights\n", "\n", "sol.x" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "PlZkfwj-hHq4" }, "source": [ "* Solution is saved in sol\n", "* Lets plot the optimal weights" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 265 }, "colab_type": "code", "executionInfo": { "elapsed": 440, "status": "ok", "timestamp": 1576771549330, "user": { "displayName": "yuchi yao", "photoUrl": "", "userId": "00457884373990713813" }, "user_tz": 300 }, "id": "yFk-okgvhHq5", "outputId": "231544e0-500a-41fa-f153-ead60fb4007e" }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "\n", "# below I am plotting the solution weights.\n", "width=0.3\n", "ind=np.arange(Re.shape[1])\n", "fig, ax = plt.subplots()\n", "ax.bar(ind,sol.x,width)\n", "\n", "# ax.set_xticks(np.array([0,1,2,3,4,5])+ width / 2)\n", "ax.set_xticks(ind)\n", "ax.set_xticklabels(Re.columns)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* lets now introduce the realistic feature that you have to pya a spread to borrow on margin\n", "\n", "* all we need to do is to change our expected return function\n", "* And we need to redefine the constraints to they re updated\n", "* I am setting a spread of 2%" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [], "source": [ "\n", "def expected_return(W,spread):\n", " return W @ (ERe+rf) +(1-W.sum())*rf+ min(0,1-W.sum())*spread\n", "\n", "def constraint(target,spread):\n", " cons=({'type': 'eq',\n", " 'fun' : lambda W : expected_return(W,spread)-target},\n", " {'type': 'ineq',\n", " 'fun' : lambda W: -W.sum()+1/margin}) \n", " return cons" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 0.005671948276218396\n", " Iterations: 17\n", " Function evaluations: 119\n", " Gradient evaluations: 17\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "Ertarget=0.12/12\n", "spread=0.02/12\n", "\n", "sol = minimize(lambda x: func(x,Cove=Re.cov()),W0, constraints=constraint(Ertarget,spread), options={'disp': True})\n", "width=0.3\n", "ind=np.arange(Re.shape[1])\n", "fig, ax = plt.subplots()\n", "ax.bar(ind,sol.x,width)\n", "\n", "# ax.set_xticks(np.array([0,1,2,3,4,5])+ width / 2)\n", "ax.set_xticks(ind)\n", "ax.set_xticklabels(Re.columns)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* What do you think will happen with the sum of weights as the spread increases?\n", "\n", "* Suppose you increase this spread quite a bit, will a reduction in margin impact the optimal choice?\n", "\n", "* When the margin is more likely to bind for an investor?" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "0sCcXFlnhHq7", "tags": [] }, "source": [ "**Shorting**\n", "\n", "* Lets now model shorting constraints\n", "\n", "* We will mode shorting fees which can be scalar or a vector for each asset, we label this `sf`\n", "\n", "* The shorting fee will be charge to any negative postion, hence `min(0,W)` which has value zero if the weight is positive\n", "\n", "* We will also change the margin constraint to account for shorting margin by simply using the absolute value of your positions\n", "\n" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "margin=0.5\n", "def expected_return(W,spread,sf):\n", " return W @ (ERe+rf) +(1-W.sum())*rf+ min(0,1-W.sum())*spread+(sf*np.minimum(0,W)).sum()\n", "\n", "def constraint(target,spread,sf):\n", " cons=({'type': 'eq',\n", " 'fun' : lambda W : expected_return(W,spread,sf)-target},\n", " {'type': 'ineq',\n", " 'fun' : lambda W: -np.abs(W).sum()+1/margin}) \n", " return cons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To focus on the role of shortign constraints let me start with the case of zero spread and lending fee" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 0.0033237802440886253\n", " Iterations: 7\n", " Function evaluations: 42\n", " Gradient evaluations: 7\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "Ertarget=0.12/12\n", "spread=0.0/12\n", "sf=0\n", "\n", "sol = minimize(lambda x: func(x,Cove=Re.cov()),W0, constraints=constraint(Ertarget,spread,sf), options={'disp': True})\n", "width=0.3\n", "ind=np.arange(Re.shape[1])\n", "fig, ax = plt.subplots()\n", "ax.bar(ind,sol.x,width)\n", "\n", "# ax.set_xticks(np.array([0,1,2,3,4,5])+ width / 2)\n", "ax.set_xticks(ind)\n", "ax.set_xticklabels(Re.columns)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "ecCDUFjQhHrA" }, "source": [ "* You can do a multitude of different problems by making small changes to this code:\n", "\n", " * Borrowing at the risk-free rate is not allowed, but lending is allowed\n", " * Shorting of any asset is not allowed\n", " * Shorting of some assets are not allowed\n", " * Shorting has an extra cost\n", " * Leverage is allowed, but borrowing rate is higher than risk-free rate\n", " \n", "\n", "* How would you implement a hard shorting constraint?\n", " \n", "* How would you implement no shorting of bonds, but allow investor to short equity markets (which can be done using futures)?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "colab": { "collapsed_sections": [], "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12" } }, "nbformat": 4, "nbformat_minor": 4 }