{ "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": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "%%bash\n", "FILE=/content/portfolio_tools.py\n", "if [[ ! -f $FILE ]]; then\n", " wget https://raw.githubusercontent.com/MOSEK/PortfolioOptimization/main/python/notebooks/portfolio_tools.py\n", "fi" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install mosek \n", "!export PYTHONPATH=\"$PYTHONPATH:/content\"\n", "\n", "# To execute the notebook directly in colab make sure your MOSEK license file is in one the locations\n", "#\n", "# /content/mosek.lic or /root/mosek/mosek.lic\n", "#\n", "# inside this notebook's internal filesystem. \n", "#\n", "# You will also need an API key from a stock data provider, or ready data files in a \"stock_data\" folder." ] }, { "cell_type": "code", "execution_count": null, "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": null, "metadata": {}, "outputs": [], "source": [ "# Colab additional settings\n", "os.environ['MOSEKLM_LICENSE_FILE']=\"/content:/root/mosek\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "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": null, "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": null, "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", " # Check if the solution is an optimal point\n", " solsta = M.getPrimalSolutionStatus()\n", " if (solsta != SolutionStatus.Optimal):\n", " # See https://docs.mosek.com/latest/pythonfusion/accessing-solution.html about handling solution statuses.\n", " raise Exception(\"Unexpected solution status!\")\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": null, "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": null, "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": null, "metadata": { "scrolled": false }, "outputs": [], "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": null, "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 }