FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browser

Python notebooks

Description

Python notebooks available in the GitHub repository python-notebooks.


pythonnotebooks.zip[download all files]

Source Files





markowitz_multiobj.ipynb

{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **Markowitz portfolio multi-objective optimization**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "***markowitz_multiojb.ipynb***\n",
    "\n",
    "Markowitz portfolio optimization. A multi-objective quadratic programming example.\n",
    "\n",
    "In Markowitz portfolio optimization there are two objectives: to maximize reward while minimizing risk (i.e. variance). This example plots several points on the optimal frontier using a blended multi-objective approach, and shows that a point computed using a lexicographic approach also lies on this frontier.\n",
    "\n",
    "© Copyright 2025 Fair Isaac Corporation\n",
    "\n",
    "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n",
    " \n",
    "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n",
    "\n",
    "This example uses FICO® Xpress software. By running it, you agree to the Community License terms of the [Xpress Shrinkwrap License Agreement](https://community.fico.com/s/contentdocument/06980000002h0i5AAA) with respect to the FICO® Xpress software. See the [licensing options](https://www.fico.com/en/fico-xpress-trial-and-licensing-options) overview for additional details and information about obtaining a paid license."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install the xpress package\n",
    "%pip install -q xpress"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problem description"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Markowitz portfolio optimization focuses on making a selection on the fraction of a budget to allocate to $S$ possible stocks to form a portfolio with two key objectives:\n",
    "\n",
    "  - Maximize expected returns: $\\max \\sum_{i \\in \\mathcal{S}} RET_i \\cdot frac_i$\n",
    "  - Minimize the portfolio variance: $\\min \\sum_{i,j \\in \\mathcal{S}} frac_i \\cdot frac_j \\cdot COV_{i,j}$ \n",
    "  \n",
    "where:\n",
    "\n",
    "  - $frac$ is the vector of portfolio stock allocations.\n",
    "  - $COV$ is the covariance matrix of asset returns.\n",
    "  - $RET$ is the returns vector.\n",
    "\n",
    "The sum of the portfolio stock allocations should be equal to 1 (fully invested portfolio): $\\sum_{i \\in \\mathcal{S}} frac_i = 1$\n",
    "\n",
    "In this example, we work with $S$ = 5 stocks, where the matrix of co-variance between each pair of stocks is given by:\n",
    "\n",
    "|   | Stock 1 | Stock 2 | Stock 3 | Stock 4 | Stock 5 |\n",
    "|---|---|---|---|---|---|\n",
    "| **Stock 1** | 0.32 | 0.70 | 0.19 | 0.52 | 0.16 |\n",
    "| **Stock 2** | 0.70 | 4.35 | -0.48 | -0.06 | -0.03 |\n",
    "| **Stock 3** | 0.19 | -0.48 | 0.98 | 1.10 | 0.10 |\n",
    "| **Stock 4** | 0.52 | -0.60 | 1.10 | 2.48 | 0.37 |\n",
    "| **Stock 5** | 0.16 | -0.30 | 0.10 | 0.37 | 0.31 |\n",
    "\n",
    "The returns of each stock are given as:\n",
    "\n",
    "|   | Returns |\n",
    "|---|---|\n",
    "| **Stock 1** | 0.31 |\n",
    "| **Stock 2** | 0.87 |\n",
    "| **Stock 3** | 0.31 |\n",
    "| **Stock 4** | 0.66 |\n",
    "| **Stock 5** | 0.24 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The necessary packages are imported, and returns and covariance data is created as NumPy arrays to allow the use of the [xpress.Dot()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/xpress.Dot.html) operator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import xpress as xp\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "\n",
    "# The historical mean return on investment for five stocks\n",
    "RET = np.array([0.31, 0.87, 0.31, 0.66, 0.24])\n",
    "\n",
    "# The historical covariances of the five stocks\n",
    "COV = np.array([\n",
    "  [0.32,  0.70,  0.19,  0.52,  0.16],\n",
    "  [0.70,  4.35, -0.48, -0.06, -0.03],\n",
    "  [0.19, -0.48,  0.98,  1.10,  0.10],\n",
    "  [0.52, -0.6,   1.10,  2.48,  0.37],\n",
    "  [0.16, -0.3,   0.10,  0.37,  0.31]\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model implementation and visualization of efficient frontier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Non-negative variables represent percentage of capital to invest in each stock. In order to use Xpress' built in multi-objective handling functionality, all objectives must be linear, so we define a free variable *variance* to serve as transfer variable.\n",
    "\n",
    "A list of constraints is created and passed as an argument to [problem.addConstraint()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addConstraint.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = xp.problem()\n",
    "\n",
    "# Non-negative variables represent percentage of capital to invest in each stock\n",
    "frac = p.addVariables(len(RET))\n",
    "\n",
    "# All objectives must be linear, so we define a free variable for the variance\n",
    "variance = p.addVariable(lb=-xp.infinity)\n",
    "\n",
    "ctrs = [\n",
    "  xp.Sum(frac) == 1,                             # Must invest 100% of capital\n",
    "  xp.Dot(frac, COV, frac) - variance <= 0    # Set up transfer variable for variance\n",
    "]\n",
    "\n",
    "p.addConstraint(ctrs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we define the two objectives. First, we call [problem.setObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.setObjective.html) to define the first objective, and the second objective is added using the [problem.addObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addObjective.html) method, which is designed for the formulation of multi-objective optimization problems by adding a new objective to an optimization problem.\n",
    "\n",
    "Alternatively, the [problem.setObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.setObjective.html) method can be used to add a new objective to a problem, as long as an *objidx* argument is defined for each objective, as consecutive integers starting from zero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.setObjective(xp.Dot(frac, RET))     # Maximize mean return\n",
    "p.addObjective(variance)              # Minimize variance\n",
    "\n",
    "# or alternatively\n",
    "#p.setObjective(xp.Dot(frac, ret), objidx=0)\n",
    "#p.setObjective(xp.Dot(frac, ret), objidx=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vary the objective weights to explore the optimal frontier, with the first objective having a weight ranging from 0.05 up to 0.95, with the second objective (weight) being the complement for each instance.\n",
    "\n",
    "A loop allows iterating through each of the 100 weight cases, optimize and save the two objective values in each instance (coordinates).\n",
    "\n",
    "When using either [problem.setObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.setObjective.html) or [problem.addObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addObjective.html), if **objectives have equal priority but different weights, a Blended (or Archimedian) approach is applied**, setting as objective function of the problem the linear combination of the added objectives and their weights."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Vary the objective weights to explore the optimal frontier\n",
    "weights = np.linspace(0.05, 0.95, 100)\n",
    "means = []\n",
    "variances = []\n",
    "\n",
    "for w in weights:\n",
    "  p.setObjective(objidx=0, weight=w,sense=xp.maximize)  # First objective defines the sense of the problem\n",
    "  p.setObjective(objidx=1, weight=w-1)                  # Reverse the sense by assigning a negative weight because we minimize variance\n",
    "  p.optimize()\n",
    "  means.append(xp.Dot(p.getSolution(frac), RET).item())\n",
    "  variances.append(p.getSolution(variance))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Draw an efficient frontier using the 20 points with *matplotlib*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(means, variances)\n",
    "plt.title('Return on investment vs variance')\n",
    "plt.xlabel('Expected return')\n",
    "plt.ylabel('Variance')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Applying a lexicographic approach"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we will maximize profit alone, and then minimize variance while not sacrificing more than 10% of the maximum profit possible.\n",
    "\n",
    "When **objectives have a different priority but the same weight, a Lexicographic approach is applied**. Xpress will solve the problem once for each distinct objective priority that is defined. All objectives from previous iterations are fixed to their optimal values within the tolerances:\n",
    "  - For minimization objectives: <tt>objective <= optimal_value * (1 + reltol) + abstol</tt>\n",
    "  - For maximization objectives: <tt>objective <= optimal_value * (1 - reltol) - abstol</tt>\n",
    "\n",
    "with the parameter <tt>reltol</tt> being the relative tolerance and <tt>abstol</tt> the absolute tolerance for the objective in subsequent runs.\n",
    "\n",
    "Further calls to [problem.setObjective()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.setObjective.html) allow users to configure each objective via the <tt>objidx</tt> argument, which receives the <tt>id</tt> of each objective, a counter starting with 0 in the order objectives are created. \n",
    "\n",
    "By running the code cell below, we can observe that the final soluton from the lexicographic method falls within the efficient frontier previously generated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Now we will maximize profit alone, and then minimize variance while not\n",
    "# sacrificing more than 10% of the maximum profit\n",
    "p.setObjective(objidx=0, priority=1, weight=1, reltol=0.1,sense=xp.maximize)\n",
    "p.setObjective(objidx=1, priority=0, weight=-1)\n",
    "p.optimize()\n",
    "m0 = xp.Dot(p.getSolution(frac), RET).item()\n",
    "v0 = p.getSolution(variance)\n",
    "\n",
    "plt.plot(means, variances)\n",
    "plt.plot(m0, v0, c='r', marker='.')\n",
    "plt.title('Return on investment vs variance')\n",
    "plt.xlabel('Expected return')\n",
    "plt.ylabel('Variance')\n",
    "plt.show()"
   ]
  }
 ],
 "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.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

Back to examples browser