{ "cells": [ { "cell_type": "markdown", "id": "boring-product", "metadata": {}, "source": [ "# MIS: Sharpe Ratio Maximization" ] }, { "cell_type": "markdown", "id": "beautiful-navigation", "metadata": {}, "source": [ "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Infleqtion/client-superstaq/blob/main/docs/source/apps/max_sharpe_ratio_optimization.ipynb) [![Launch Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Infleqtion/client-superstaq/HEAD?labpath=docs/source/apps/max_sharpe_ratio_optimization.ipynb)" ] }, { "cell_type": "markdown", "id": "543c33d0", "metadata": {}, "source": [ "A well-known application of QUBO solving is the Maximum Independent Set (MIS) graph problem - the task of finding the largest independent set in a graph, where an independent set is a set of vertices such that no two vertices are adjacent. Here, we explore a similar method of solving a QUBO, but with a different application.\n", "\n", "In this notebook, we will demonstrate an example portfolio optimization problem by looking at Sharpe ratio maximization. To that, we will formulate the problem as a QUBO and try to find optimal weights for assets in a given portfolio. We will get many results using simulated annealing for our QUBO and then classically post-process to find the one that gives the actual highest Sharpe ratio. " ] }, { "cell_type": "markdown", "id": "af59eece", "metadata": {}, "source": [ "Begin by importing the necessary packages:" ] }, { "cell_type": "code", "execution_count": 1, "id": "df306675", "metadata": {}, "outputs": [], "source": [ "try:\n", " import general_superstaq as gss\n", " import pypfopt\n", " import qubovert as qv\n", " import yfinance as yf\n", "\n", "except ImportError:\n", " print(\"Installing missing packages...\")\n", " %pip install --quiet general-superstaq[examples]\n", " print(\"You may need to restart the kernel to import newly installed packages.\")\n", "\n", " import general_superstaq as gss\n", " import pypfopt\n", " import qubovert as qv\n", " import yfinance as yf" ] }, { "cell_type": "code", "execution_count": 2, "id": "76e1bc12", "metadata": {}, "outputs": [], "source": [ "from datetime import date, timedelta\n", "\n", "import numpy as np\n", "import pandas as pd" ] }, { "cell_type": "markdown", "id": "ee9d0ea1", "metadata": {}, "source": [ "Users should either store their API token in an environment variable or run the following cell below, replacing `\"key\"` with their own token retrieved from `https://superstaq.infleqtion.com`." ] }, { "cell_type": "code", "execution_count": 3, "id": "61cc3a6d", "metadata": {}, "outputs": [], "source": [ "# Run this to use your token as an environment variable\n", "# import os\n", "# os.environ[\"SUPERSTAQ_API_KEY\"] = \"key\"" ] }, { "cell_type": "markdown", "id": "3a961034", "metadata": {}, "source": [ "## Portfolio Risk Background\n", "\n", "Portfolio risk is measured by the standard deviation, $\\sigma$. The greater the standard deviation, the greater the risk. Given a portfolio, $P$, with two assets, $A$ and $B$, we represent the weights of the assets in the portfolio with $w_{i}$ (with $i =$ {A,B}), and the corresponding portfolio standard deviation as:\n", "\n", "$\\sigma_{P} = \\sqrt{w_{A}^{2}σ_{A}^{2} + w_{B}^{2}σ_{B}^{2} + 2w_{A}w_{B}σ_{A}σ_{B}ρ_{{AB}}}$\n", "\n", "Given three assets ($A,B,$ and $C$) in $P$, our portfolio standard deviation would be:\n", "\n", "$\\sigma_{P} = \\sqrt{w_{A}^{2}σ_{A}^{2} + w_{B}^{2}σ_{B}^{2} + w_{C}^{2}σ_{C}^{2} + 2w_{A}w_{B}σ_{A}σ_{B}ρ_{{AB}} + 2w_{B}w_{C}σ_{B}σ_{C}ρ_{{BC}} + 2w_{A}w_{C}σ_{A}σ_{C}ρ_{{AC}}}$\n", "\n", "where $\\rho$ is a symmetric matrix that contains the correlation coefficient between an asset $i$ and asset $j$. The product ${\\sigma_{a_i}}{\\sigma_{a_j}}\\rho_{ij}$ = $\\text{Cov}_{ij}$ is also called the covariance of the assets $a_{i}$ and $a_{j}$. \n", "\n", "In general, for $N$ assets = {$a_1,a_2,...,a_N$} in a portfolio $P$, the square of the portfolio standard deviation, in other words, the variance, is given by the following formula:\n", "\n", "$$ \n", "\\begin{align}\n", "\\sigma_{P}^2 &= \\sum_{i=1}^{N} {w_{a_i}}^2{\\sigma_{a_i}}^2 + 2\\sum_{j=1}^{N}\\sum_{i\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
SymbolSecurityGICS SectorGICS Sub-IndustryHeadquarters LocationDate addedCIKFounded
0MMM3MIndustrialsIndustrial ConglomeratesSaint Paul, Minnesota1957-03-04667401902
1AOSA. O. SmithIndustrialsBuilding ProductsMilwaukee, Wisconsin2017-07-26911421916
2ABTAbbott LaboratoriesHealth CareHealth Care EquipmentNorth Chicago, Illinois1957-03-0418001888
3ABBVAbbVieHealth CareBiotechnologyNorth Chicago, Illinois2012-12-3115511522013 (1888)
4ACNAccentureInformation TechnologyIT Consulting & Other ServicesDublin, Ireland2011-07-0614673731989
...........................
497XYLXylem Inc.IndustrialsIndustrial Machinery & Supplies & ComponentsWhite Plains, New York2011-11-0115244722011
498YUMYum! BrandsConsumer DiscretionaryRestaurantsLouisville, Kentucky1997-10-0610410611997
499ZBRAZebra TechnologiesInformation TechnologyElectronic Equipment & InstrumentsLincolnshire, Illinois2019-12-238772121969
500ZBHZimmer BiometHealth CareHealth Care EquipmentWarsaw, Indiana2001-08-0711368691927
501ZTSZoetisHealth CarePharmaceuticalsParsippany, New Jersey2013-06-2115552801952
\n", "

502 rows × 8 columns

\n", "" ], "text/plain": [ " Symbol Security GICS Sector \\\n", "0 MMM 3M Industrials \n", "1 AOS A. O. Smith Industrials \n", "2 ABT Abbott Laboratories Health Care \n", "3 ABBV AbbVie Health Care \n", "4 ACN Accenture Information Technology \n", ".. ... ... ... \n", "497 XYL Xylem Inc. Industrials \n", "498 YUM Yum! Brands Consumer Discretionary \n", "499 ZBRA Zebra Technologies Information Technology \n", "500 ZBH Zimmer Biomet Health Care \n", "501 ZTS Zoetis Health Care \n", "\n", " GICS Sub-Industry Headquarters Location \\\n", "0 Industrial Conglomerates Saint Paul, Minnesota \n", "1 Building Products Milwaukee, Wisconsin \n", "2 Health Care Equipment North Chicago, Illinois \n", "3 Biotechnology North Chicago, Illinois \n", "4 IT Consulting & Other Services Dublin, Ireland \n", ".. ... ... \n", "497 Industrial Machinery & Supplies & Components White Plains, New York \n", "498 Restaurants Louisville, Kentucky \n", "499 Electronic Equipment & Instruments Lincolnshire, Illinois \n", "500 Health Care Equipment Warsaw, Indiana \n", "501 Pharmaceuticals Parsippany, New Jersey \n", "\n", " Date added CIK Founded \n", "0 1957-03-04 66740 1902 \n", "1 2017-07-26 91142 1916 \n", "2 1957-03-04 1800 1888 \n", "3 2012-12-31 1551152 2013 (1888) \n", "4 2011-07-06 1467373 1989 \n", ".. ... ... ... \n", "497 2011-11-01 1524472 2011 \n", "498 1997-10-06 1041061 1997 \n", "499 2019-12-23 877212 1969 \n", "500 2001-08-07 1136869 1927 \n", "501 2013-06-21 1555280 1952 \n", "\n", "[502 rows x 8 columns]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.options.display.float_format = \"{:,.2f}\".format\n", "# Get dataset to sample some companies\n", "table = pd.read_html(\"https://en.wikipedia.org/wiki/List_of_S%26P_500_companies\")\n", "table[0] # Show dataset in a dataframe format" ] }, { "cell_type": "markdown", "id": "47bd3af6", "metadata": {}, "source": [ "For demonstrative purposes, we can consider a portfolio of $N=10$ stocks that are a pre-defined subset of the S \\& P 500 dataset for repeatability: " ] }, { "cell_type": "code", "execution_count": 5, "id": "013d2700", "metadata": {}, "outputs": [], "source": [ "NUM_STOCKS = 10\n", "assets = [\n", " \"NEM\",\n", " \"KEYS\",\n", " \"WM\",\n", " \"CE\",\n", " \"SYF\",\n", " \"GIS\",\n", " \"AAL\",\n", " \"D\",\n", " \"APH\",\n", " \"AMGN\",\n", "] # List of s & p tickers for assets" ] }, { "cell_type": "markdown", "id": "7756c412", "metadata": {}, "source": [ "### Getting the Correlation Matrix" ] }, { "cell_type": "markdown", "id": "761572e7", "metadata": {}, "source": [ "Given the selection of assets, we can then get the correlation matrix, $\\rho_{ij}$, between the chosen assets. In this case, we take a list of stock tickers as input and return the matrix for the monthly data in the last 2000 days: " ] }, { "cell_type": "code", "execution_count": 6, "id": "75c6426a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading correlation matrix:\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", "
AALAMGNAPHCEDGISKEYSNEMSYFWM
AAL1.00-0.52-0.400.150.35-0.55-0.14-0.090.08-0.39
AMGN-0.521.000.870.28-0.540.550.31-0.120.550.83
APH-0.400.871.000.36-0.520.540.52-0.150.800.95
CE0.150.280.361.00-0.120.080.470.210.450.40
D0.35-0.54-0.52-0.121.00-0.41-0.160.57-0.17-0.51
GIS-0.550.550.540.08-0.411.000.63-0.030.230.67
KEYS-0.140.310.520.47-0.160.631.000.120.600.61
NEM-0.09-0.12-0.150.210.57-0.030.121.000.01-0.19
SYF0.080.550.800.45-0.170.230.600.011.000.74
WM-0.390.830.950.40-0.510.670.61-0.190.741.00
\n", "
" ], "text/plain": [ " AAL AMGN APH CE D GIS KEYS NEM SYF WM\n", "AAL 1.00 -0.52 -0.40 0.15 0.35 -0.55 -0.14 -0.09 0.08 -0.39\n", "AMGN -0.52 1.00 0.87 0.28 -0.54 0.55 0.31 -0.12 0.55 0.83\n", "APH -0.40 0.87 1.00 0.36 -0.52 0.54 0.52 -0.15 0.80 0.95\n", "CE 0.15 0.28 0.36 1.00 -0.12 0.08 0.47 0.21 0.45 0.40\n", "D 0.35 -0.54 -0.52 -0.12 1.00 -0.41 -0.16 0.57 -0.17 -0.51\n", "GIS -0.55 0.55 0.54 0.08 -0.41 1.00 0.63 -0.03 0.23 0.67\n", "KEYS -0.14 0.31 0.52 0.47 -0.16 0.63 1.00 0.12 0.60 0.61\n", "NEM -0.09 -0.12 -0.15 0.21 0.57 -0.03 0.12 1.00 0.01 -0.19\n", "SYF 0.08 0.55 0.80 0.45 -0.17 0.23 0.60 0.01 1.00 0.74\n", "WM -0.39 0.83 0.95 0.40 -0.51 0.67 0.61 -0.19 0.74 1.00" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "time = 2000\n", "\n", "# Set date range for historical prices\n", "end_time = date.today()\n", "start_time = end_time - timedelta(days=time)\n", "\n", "# Reformat date range\n", "end = end_time.strftime(\"%Y-%m-%d\")\n", "start = start_time.strftime(\"%Y-%m-%d\")\n", "\n", "df = pd.DataFrame()\n", "print(\"Loading correlation matrix:\")\n", "for ticker in assets:\n", " # Get monthly stock prices over date range\n", " prices_yf = pd.DataFrame(\n", " yf.download(ticker, start=start, end=end, interval=\"1mo\", auto_adjust=False, progress=False)\n", " )\n", " df[ticker] = prices_yf[\"Adj Close\"]\n", "\n", "df = df.reindex(sorted(df.columns), axis=1)\n", "corr_matrix_df = df.corr(method=\"pearson\")\n", "corr_matrix_df" ] }, { "cell_type": "markdown", "id": "4945d7e0", "metadata": {}, "source": [ "### Getting expected return and volatility" ] }, { "cell_type": "markdown", "id": "5b9625e5", "metadata": {}, "source": [ "Next, we get the expected return and the corresponding volatility for our sample of assets for a given time frame (for example, 1 month). They are put into a dataframe as columns:" ] }, { "cell_type": "code", "execution_count": 7, "id": "29d1cc44", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading expected returns and risks:\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", "
retvol
NEM0.020.35
KEYS0.100.28
WM0.130.20
CE-0.070.38
SYF0.170.41
GIS0.070.18
AAL-0.080.45
D-0.020.22
APH0.260.26
AMGN0.080.25
\n", "
" ], "text/plain": [ " ret vol\n", "NEM 0.02 0.35\n", "KEYS 0.10 0.28\n", "WM 0.13 0.20\n", "CE -0.07 0.38\n", "SYF 0.17 0.41\n", "GIS 0.07 0.18\n", "AAL -0.08 0.45\n", "D -0.02 0.22\n", "APH 0.26 0.26\n", "AMGN 0.08 0.25" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "returns_and_vols = {}\n", "time = 2000\n", "\n", "# Set date range for historical prices\n", "end_time = date.today()\n", "start_time = end_time - timedelta(days=time)\n", "end = end_time.strftime(\"%Y-%m-%d\")\n", "start = start_time.strftime(\"%Y-%m-%d\")\n", "print(\"Loading expected returns and risks:\")\n", "for ticker in assets:\n", " prices_yf = pd.DataFrame(\n", " yf.download(ticker, start=start, end=end, interval=\"1mo\", auto_adjust=False, progress=False)\n", " )\n", " prices_yf.rename(columns={\"Adj Close\": ticker}, inplace=True)\n", " returns_yf = pypfopt.expected_returns.returns_from_prices(prices_yf)\n", " exp_return = pypfopt.expected_returns.mean_historical_return(\n", " returns_yf, returns_data=True, compounding=True, frequency=12\n", " )\n", " vol = returns_yf.std() * np.sqrt(12)\n", " returns_and_vols[ticker] = {\"ret\": exp_return.iloc[0], \"vol\": vol.iloc[0]}\n", "\n", "ret_and_vol = pd.DataFrame(returns_and_vols).transpose()\n", "pd.DataFrame(ret_and_vol)" ] }, { "cell_type": "markdown", "id": "544d18e2", "metadata": {}, "source": [ "### Integration of Weight Discretization and Constraints" ] }, { "cell_type": "markdown", "id": "6f3bf200", "metadata": {}, "source": [ "For formulation as a QUBO, we introduce discretization of our weights:\n", "\n", "$$ w_{a_i} = \\sum_{j=1}^{N} d_j x_{ij} $$" ] }, { "cell_type": "code", "execution_count": 8, "id": "7683919e", "metadata": {}, "outputs": [], "source": [ "N = len(assets)\n", "discretization = 9 # Parameter used in the discretization process\n", "\n", "x = {}\n", "for ticker in assets:\n", " for power in range(discretization):\n", " x[f\"w_{ticker}_{power}\"] = qv.boolean_var(f\"w_{ticker}_{power}\")" ] }, { "cell_type": "markdown", "id": "20b87aa0", "metadata": {}, "source": [ "We then reformulate the expected return and volatility with the binary variables, imposing the weight constraint stated in the beginning: " ] }, { "cell_type": "code", "execution_count": 9, "id": "8a1e46ff", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Creating weight constraint...\n", "Creating objective function...\n", "Done.\n" ] } ], "source": [ "N = len(assets)\n", "expected_return = 0\n", "weight_constraint = 0\n", "print(\"Creating weight constraint...\")\n", "for variable in x:\n", " ticker, power = variable.split(\"_\")[1], int(variable.split(\"_\")[2])\n", " weight = 1 / (2 ** (power + 2))\n", " expected_return += weight * x[variable] * ret_and_vol[\"ret\"][ticker]\n", " weight_constraint += weight * x[variable]\n", "weight_constraint -= 1\n", "volatility = 0\n", "print(\"Creating objective function...\")\n", "for i in range(N):\n", " asset_i_weight_expression = 0\n", " asset_i_vol = ret_and_vol[\"vol\"][assets[i]]\n", " for variable in x:\n", " ticker, power = variable.split(\"_\")[1], int(variable.split(\"_\")[2])\n", " if ticker == assets[i]:\n", " weight = 1 / (2 ** (power + 2))\n", " asset_i_weight_expression += weight * x[variable]\n", "\n", " volatility += asset_i_weight_expression**2 * asset_i_vol**2\n", "\n", " for j in range(i + 1, N):\n", " asset_i_weight_expression = 0\n", " asset_i_vol = ret_and_vol[\"vol\"][assets[i]]\n", " asset_j_weight_expression = 0\n", " asset_j_vol = ret_and_vol[\"vol\"][assets[j]]\n", " correlation = corr_matrix_df[assets[i]][assets[j]]\n", " for variable in x:\n", " ticker, power = variable.split(\"_\")[1], int(variable.split(\"_\")[2])\n", " weight = 1 / (2 ** (power + 2))\n", " if ticker == assets[i]:\n", " asset_i_weight_expression += weight * x[variable]\n", " elif ticker == assets[j]:\n", " asset_j_weight_expression += weight * x[variable]\n", " volatility += (\n", " 2\n", " * asset_i_weight_expression\n", " * asset_j_weight_expression\n", " * asset_i_vol\n", " * asset_j_vol\n", " * correlation\n", " )\n", "print(\"Done.\")" ] }, { "cell_type": "markdown", "id": "235b40d1", "metadata": {}, "source": [ "### Using the `submit_qubo()` Function" ] }, { "cell_type": "markdown", "id": "5d90a58f", "metadata": {}, "source": [ "For our Sharpe ratio optimization as a QUBO, we create an objective function of the form: \n", "\n", "$$ \\text{obj} = kE[R_{P}] - (1-k)\\sigma_{P}^2 $$\n", "\n", "with a hyperparameter, $k$, that is preset and tuned to find the optimal Sharpe ratio." ] }, { "cell_type": "code", "execution_count": 10, "id": "85483d36", "metadata": {}, "outputs": [], "source": [ "k = 0.8\n", "\n", "obj = k * expected_return - (1 - k) * volatility\n", "obj *= -1\n", "M = max(obj.values())\n", "# M = 1\n", "obj = obj.add_constraint_eq_zero(weight_constraint, lam=10)\n", "\n", "obj_QUBO = obj.to_qubo() # Converts our objective function into a QUBO. We then set up the client connection in the following cell." ] }, { "cell_type": "markdown", "id": "f840d0c9", "metadata": {}, "source": [ "We then set up the client connection in the following cell:" ] }, { "cell_type": "code", "execution_count": 11, "id": "3f060b3a", "metadata": {}, "outputs": [], "source": [ "# Setting up client connection\n", "client = gss.Service()" ] }, { "cell_type": "markdown", "id": "a4a521e0", "metadata": {}, "source": [ "Finally, we utilize the functionality in our `submit_qubo()` function to solve the optimization problem of our objective function as a QUBO using multiple techniques. In this case, we employ Superstaq's internal simulator (which uses the simulated annealing technique) by specifying `target=\"ss_unconstrained_simulator\"`. Use `client.get_targets(supports_submit_qubo=True)` to see a complete list of available targets supporting QUBO submission." ] }, { "cell_type": "code", "execution_count": 12, "id": "4b131503", "metadata": {}, "outputs": [], "source": [ "res = client.submit_qubo(obj_QUBO, target=\"ss_unconstrained_simulator\")" ] }, { "cell_type": "markdown", "id": "d1b6723b", "metadata": {}, "source": [ "### Portfolio Construction: Best Sharpe Ratio" ] }, { "cell_type": "markdown", "id": "f7b309fa", "metadata": {}, "source": [ "Based on the solution set obtained from simulated annealing, we update the best Sharpe ratio using the portfolio information," ] }, { "cell_type": "code", "execution_count": 13, "id": "83579735", "metadata": {}, "outputs": [], "source": [ "best_sharpe_ratio = float(\"-inf\") # Set Sharpe ratio at the lowest at the start to compare\n", "best_portfolio = None\n", "for sol in (\n", " res\n", "): # Loops over the simulated annealing solutions to classically post-process the best Sharpe ratio\n", " solution = obj.convert_solution(sol)\n", "\n", " portfolio_weights = {ticker: 0 for ticker in assets}\n", " N = len(assets)\n", "\n", " for variable in solution:\n", " if solution[variable] == 1:\n", " ticker, power = variable.split(\"_\")[1], int(variable.split(\"_\")[2])\n", " weight = 1 / (2 ** (power + 2))\n", " portfolio_weights[ticker] += weight\n", " total = sum(portfolio_weights.values())\n", " for stock in portfolio_weights:\n", " portfolio_weights[stock] /= total\n", " portfolio_expected_return = 0\n", " portfolio_risk = 0\n", "\n", " for ticker in portfolio_weights:\n", " portfolio_expected_return += ret_and_vol[\"ret\"][ticker] * portfolio_weights[ticker]\n", "\n", " for i in range(N):\n", " asset_i_vol = ret_and_vol[\"vol\"][assets[i]]\n", "\n", " portfolio_risk += (portfolio_weights[assets[i]]) ** 2 * (asset_i_vol) ** 2\n", "\n", " for j in range(i + 1, N):\n", " asset_j_vol = ret_and_vol[\"vol\"][assets[j]]\n", " correlation = corr_matrix_df[assets[i]][assets[j]]\n", "\n", " portfolio_risk += (\n", " 2\n", " * portfolio_weights[assets[i]]\n", " * portfolio_weights[assets[j]]\n", " * asset_i_vol\n", " * asset_j_vol\n", " * correlation\n", " )\n", "\n", " portfolio_risk = np.sqrt(portfolio_risk)\n", "\n", " portfolio_sharpe_ratio = portfolio_expected_return / portfolio_risk\n", "\n", " portfolio = {\n", " \"weights\": portfolio_weights,\n", " \"return\": portfolio_expected_return,\n", " \"risk\": portfolio_risk,\n", " \"sharpe\": portfolio_sharpe_ratio,\n", " }\n", "\n", " if portfolio[\"sharpe\"] > best_sharpe_ratio:\n", " best_sharpe_ratio = portfolio[\"sharpe\"]\n", " best_portfolio = portfolio" ] }, { "cell_type": "markdown", "id": "bdb1b34f", "metadata": {}, "source": [ "and output the value of the expected return, risk, and value of the Sharpe ratio:" ] }, { "cell_type": "code", "execution_count": 14, "id": "6233d33a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The expected return value is: 0.16\n", "The portfolio risk is: 0.19\n", "The Sharpe ratio for the best portfolio 0.84\n" ] } ], "source": [ "print(\"The expected return value is:\", round(best_portfolio[\"return\"], 2))\n", "print(\"The portfolio risk is:\", round(best_portfolio[\"risk\"], 2))\n", "print(\"The Sharpe ratio for the best portfolio\", round(best_portfolio[\"sharpe\"], 2))" ] }, { "cell_type": "markdown", "id": "ff547959", "metadata": {}, "source": [ "Lastly, we can view what the optimal stocks and their corresponding weights are in our portfolio:" ] }, { "cell_type": "code", "execution_count": 15, "id": "645d471a", "metadata": {}, "outputs": [], "source": [ "fin_portfolio = pd.DataFrame(\n", " best_portfolio[\"weights\"], [\"weights\"]\n", ") # Output portfolio as a dictionary with asset tickers as keys with corresponding asset weight in portfolio" ] }, { "cell_type": "code", "execution_count": 16, "id": "2ab107e8", "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", "
NEMKEYSWMCESYFGISAALDAPHAMGN
weights0.000.110.090.000.060.060.120.000.500.05
\n", "
" ], "text/plain": [ " NEM KEYS WM CE SYF GIS AAL D APH AMGN\n", "weights 0.00 0.11 0.09 0.00 0.06 0.06 0.12 0.00 0.50 0.05" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Printing our output portfolio\n", "fin_portfolio" ] }, { "cell_type": "code", "execution_count": 17, "id": "00fe4d3e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.0" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(best_portfolio[\"weights\"].values()) # Check to see the weight constraint is satisfied" ] } ], "metadata": { "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.12.7" } }, "nbformat": 4, "nbformat_minor": 5 }