{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "LaTeX macros (hidden cell)\n", "$\n", "\\newcommand{\\Q}{\\mathcal{Q}}\n", "\\newcommand{\\ECov}{\\boldsymbol{\\Sigma}}\n", "\\newcommand{\\EMean}{\\boldsymbol{\\mu}}\n", "\\newcommand{\\EAlpha}{\\boldsymbol{\\alpha}}\n", "\\newcommand{\\EBeta}{\\boldsymbol{\\beta}}\n", "$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Imports and configuration" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "scrolled": false }, "outputs": [], "source": [ "import sys\n", "import os\n", "import re\n", "import glob\n", "import datetime as dt\n", "\n", "import numpy as np\n", "import pandas as pd\n", "%matplotlib inline\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", "from matplotlib.colors import LinearSegmentedColormap\n", "\n", "from mosek.fusion import *\n", "\n", "from notebook.services.config import ConfigManager\n", "\n", "from portfolio_tools import data_download, DataReader" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.6.9 (default, Jan 26 2021, 15:33:00) \n", "[GCC 8.4.0]\n", "matplotlib: 3.3.4\n" ] } ], "source": [ "# Version checks\n", "print(sys.version)\n", "print('matplotlib: {}'.format(matplotlib.__version__))\n", "\n", "# Jupyter configuration\n", "c = ConfigManager()\n", "c.update('notebook', {\"CodeCell\": {\"cm_config\": {\"autoCloseBrackets\": False}}}) \n", "\n", "# Numpy options\n", "np.set_printoptions(precision=5, linewidth=120, suppress=True)\n", "\n", "# Pandas options\n", "pd.set_option('display.max_rows', None)\n", "\n", "# Matplotlib options\n", "plt.rcParams['figure.figsize'] = [12, 8]\n", "plt.rcParams['figure.dpi'] = 200" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Prepare input data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, the input data is given. It consists of the vector $\\EMean$ of expected returns, and the covariance matrix $\\ECov$." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# Linear return statistics on the investment horizon\n", "mu = np.array([0.07197349, 0.15518171, 0.17535435, 0.0898094 , 0.42895777, 0.39291844, 0.32170722, 0.18378628])\n", "Sigma = np.array([\n", " [0.09460323, 0.03735969, 0.03488376, 0.03483838, 0.05420885, 0.03682539, 0.03209623, 0.03271886],\n", " [0.03735969, 0.07746293, 0.03868215, 0.03670678, 0.03816653, 0.03634422, 0.0356449 , 0.03422235],\n", " [0.03488376, 0.03868215, 0.06241065, 0.03364444, 0.03949475, 0.03690811, 0.03383847, 0.02433733],\n", " [0.03483838, 0.03670678, 0.03364444, 0.06824955, 0.04017978, 0.03348263, 0.04360484, 0.03713009],\n", " [0.05420885, 0.03816653, 0.03949475, 0.04017978, 0.17243352, 0.07886889, 0.06999607, 0.05010711],\n", " [0.03682539, 0.03634422, 0.03690811, 0.03348263, 0.07886889, 0.09093307, 0.05364518, 0.04489357],\n", " [0.03209623, 0.0356449 , 0.03383847, 0.04360484, 0.06999607, 0.05364518, 0.09649728, 0.04419974],\n", " [0.03271886, 0.03422235, 0.02433733, 0.03713009, 0.05010711, 0.04489357, 0.04419974, 0.08159633]\n", " ])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Define the optimization model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The optimization problem we would like to solve is \n", "$$\n", " \\begin{array}{lrcl}\n", " \\mbox{maximize} & \\EMean^\\mathsf{T}\\mathbf{x} & &\\\\\n", " \\mbox{subject to} & \\left(\\gamma^2, \\frac{1}{2}, \\mathbf{G}^\\mathsf{T}\\mathbf{x}\\right) & \\in & \\Q_\\mathrm{r}^{N+2},\\\\\n", " & \\mathbf{1}^\\mathsf{T}\\mathbf{x} & = & 1,\\\\\n", " & \\mathbf{x} & \\geq & 0.\\\\\n", " \\end{array}\n", "$$\n", "\n", "Here we define this model in MOSEK Fusion." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# Define function solving the optimization model\n", "def Markowitz(N, m, G, gamma2):\n", " with Model(\"markowitz\") as M:\n", " # Settings\n", " M.setLogHandler(sys.stdout) \n", "\n", " # Decision variable (fraction of holdings in each security)\n", " # The variable x is restricted to be positive, which imposes the constraint of no short-selling. \n", " x = M.variable(\"x\", N, Domain.greaterThan(0.0)) \n", "\n", " # Budget constraint\n", " M.constraint('budget', Expr.sum(x), Domain.equalsTo(1))\n", "\n", " # Objective \n", " M.objective('obj', ObjectiveSense.Maximize, Expr.dot(m, x))\n", "\n", " # Imposes a bound on the risk\n", " M.constraint('risk', Expr.vstack(gamma2, 0.5, Expr.mul(G.transpose(), x)), Domain.inRotatedQCone())\n", "\n", " # Solve optimization\n", " M.solve()\n", " \n", " returns = M.primalObjValue()\n", " portfolio = x.level()\n", " \n", " return returns, portfolio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Run the optimization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define the parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The problem parameters are the number of securities $N$ and the risk limit $\\gamma^2$. " ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "N = mu.shape[0] # Number of securities\n", "gamma2 = 0.05 # Risk limit (variance)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Factorize the covariance matrix" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we factorize $\\ECov$ because the model is defined in conic form, and it expects a matrix $G$ such that $\\ECov = GG^\\mathsf{T}$." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "G = np.linalg.cholesky(Sigma) # Cholesky factor of S to use in conic risk constraint " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Solve the optimization problem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we call the function that defines the Fusion model and runs the optimization." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Problem\n", " Name : markowitz \n", " Objective sense : max \n", " Type : CONIC (conic optimization problem)\n", " Constraints : 11 \n", " Cones : 1 \n", " Scalar variables : 19 \n", " Matrix variables : 0 \n", " Integer variables : 0 \n", "\n", "Optimizer started.\n", "Presolve started.\n", "Linear dependency checker started.\n", "Linear dependency checker terminated.\n", "Eliminator started.\n", "Freed constraints in eliminator : 0\n", "Eliminator terminated.\n", "Eliminator - tries : 1 time : 0.00 \n", "Lin. dep. - tries : 1 time : 0.00 \n", "Lin. dep. - number : 0 \n", "Presolve terminated. Time: 0.00 \n", "Problem\n", " Name : markowitz \n", " Objective sense : max \n", " Type : CONIC (conic optimization problem)\n", " Constraints : 11 \n", " Cones : 1 \n", " Scalar variables : 19 \n", " Matrix variables : 0 \n", " Integer variables : 0 \n", "\n", "Optimizer - threads : 20 \n", "Optimizer - solved problem : the primal \n", "Optimizer - Constraints : 8\n", "Optimizer - Cones : 1\n", "Optimizer - Scalar variables : 17 conic : 10 \n", "Optimizer - Semi-definite variables: 0 scalarized : 0 \n", "Factor - setup time : 0.00 dense det. time : 0.00 \n", "Factor - ML order time : 0.00 GP order time : 0.00 \n", "Factor - nonzeros before factor : 36 after factor : 36 \n", "Factor - dense dim. : 0 flops : 6.00e+02 \n", "ITE PFEAS DFEAS GFEAS PRSTATUS POBJ DOBJ MU TIME \n", "0 1.0e+00 1.6e+00 1.4e+00 0.00e+00 0.000000000e+00 3.889087297e-01 1.0e+00 0.02 \n", "1 1.6e-01 2.5e-01 4.9e-02 7.08e-01 4.275713587e-01 5.950950777e-01 1.6e-01 0.03 \n", "2 5.5e-02 8.6e-02 8.7e-03 1.69e+00 3.610061700e-01 4.006907873e-01 5.5e-02 0.03 \n", "3 3.1e-02 4.8e-02 4.0e-03 1.36e+00 3.098345093e-01 3.290205233e-01 3.1e-02 0.04 \n", "4 8.9e-03 1.4e-02 6.4e-04 1.27e+00 2.910409783e-01 2.958703450e-01 8.9e-03 0.04 \n", "5 3.5e-03 5.5e-03 1.7e-04 1.05e+00 2.845165201e-01 2.862826893e-01 3.5e-03 0.04 \n", "6 1.3e-03 2.1e-03 4.4e-05 8.02e-01 2.789487823e-01 2.796238224e-01 1.3e-03 0.04 \n", "7 1.6e-04 2.5e-04 1.8e-06 1.02e+00 2.770260963e-01 2.771103927e-01 1.6e-04 0.04 \n", "8 2.1e-05 3.3e-05 8.5e-08 1.00e+00 2.767556228e-01 2.767667414e-01 2.1e-05 0.04 \n", "9 1.4e-06 2.2e-06 1.4e-09 9.99e-01 2.767193888e-01 2.767201074e-01 1.4e-06 0.04 \n", "10 7.3e-09 1.1e-08 5.4e-13 1.00e+00 2.767173176e-01 2.767173215e-01 7.3e-09 0.04 \n", "Optimizer terminated. Time: 0.05 \n", "\n", "\n", "Interior-point solution summary\n", " Problem status : PRIMAL_AND_DUAL_FEASIBLE\n", " Solution status : OPTIMAL\n", " Primal. obj: 2.7671731760e-01 nrm: 1e+00 Viol. con: 5e-09 var: 0e+00 cones: 2e-09 \n", " Dual. obj: 2.7671732147e-01 nrm: 6e+00 Viol. con: 0e+00 var: 4e-09 cones: 0e+00 \n", "========================\n", "\n", "RESULTS:\n", "Optimal expected portfolio return: 27.6717%\n", "Optimal portfolio weights: [0. 0.09126 0.26911 0. 0.02531 0.32162 0.17652 0.11618]\n", "Sum of weights: 0.9999999951391261\n" ] } ], "source": [ "# Run optimization \n", "f, x = Markowitz(N, mu, G, gamma2)\n", "print(\"========================\\n\")\n", "print(\"RESULTS:\")\n", "print(f\"Optimal expected portfolio return: {f*100:.4f}%\")\n", "print(f\"Optimal portfolio weights: {x}\")\n", "print(f\"Sum of weights: {np.sum(x)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Test result" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "scrolled": true }, "outputs": [], "source": [ "expected_x = np.array([0., 0.09126, 0.26911, 0., 0.02531, 0.32162, 0.17652, 0.11618])\n", "diff = np.sum(np.abs(expected_x - x))\n", "assert diff < 1e-4, f\"Resulting portfolio does not match expected one. Difference is {diff}\"" ] } ], "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.6.9" } }, "nbformat": 4, "nbformat_minor": 2 }