{ "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 pypfopt\n", "except ImportError:\n", " print(\"Installing PyPortfolioOpt...\")\n", " %pip install --quiet PyPortfolioOpt\n", " print(\"Installed PyPortfolioOpt.\")\n", " print(\"You may need to restart the kernel to import newly installed packages.\")\n", " import pypfopt\n", "\n", "try:\n", " import yfinance as yf\n", "except ImportError:\n", " print(\"Installing yfinance...\")\n", " %pip install --quiet yfinance\n", " print(\"Installed yfinance.\")\n", " print(\"You may need to restart the kernel to import newly installed packages.\")\n", " import yfinance as yf\n", "\n", "try:\n", " import qubovert as qv\n", "except ImportError:\n", " print(\"Installing qubovert...\")\n", " %pip install --quiet qubovert\n", " print(\"Installed qubovert.\")\n", " print(\"You may need to restart the kernel to import newly installed packages.\")\n", " import qubovert as qv" ] }, { "cell_type": "code", "execution_count": 2, "id": "76e1bc12", "metadata": {}, "outputs": [], "source": [ "from datetime import date, timedelta\n", "import warnings\n", "import numpy as np\n", "import os\n", "import pandas as pd\n", "import sympy\n", "from tqdm import tqdm\n", "\n", "warnings.filterwarnings(\"ignore\", category=DeprecationWarning)" ] }, { "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 token as an environment variable\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
2ABTAbbottHealth CareHealth Care EquipmentNorth Chicago, Illinois1957-03-0418001888
3ABBVAbbVieHealth CarePharmaceuticalsNorth Chicago, Illinois2012-12-3115511522013 (1888)
4ACNAccentureInformation TechnologyIT Consulting & Other ServicesDublin, Ireland2011-07-0614673731989
...........................
498YUMYum! BrandsConsumer DiscretionaryRestaurantsLouisville, Kentucky1997-10-0610410611997
499ZBRAZebra TechnologiesInformation TechnologyElectronic Equipment & InstrumentsLincolnshire, Illinois2019-12-238772121969
500ZBHZimmer BiometHealth CareHealth Care EquipmentWarsaw, Indiana2001-08-0711368691927
501ZIONZions BancorporationFinancialsRegional BanksSalt Lake City, Utah2001-06-221093801873
502ZTSZoetisHealth CarePharmaceuticalsParsippany, New Jersey2013-06-2115552801952
\n", "

503 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 Health Care \n", "3 ABBV AbbVie Health Care \n", "4 ACN Accenture Information Technology \n", ".. ... ... ... \n", "498 YUM Yum! Brands Consumer Discretionary \n", "499 ZBRA Zebra Technologies Information Technology \n", "500 ZBH Zimmer Biomet Health Care \n", "501 ZION Zions Bancorporation Financials \n", "502 ZTS Zoetis Health Care \n", "\n", " GICS Sub-Industry Headquarters Location Date added \\\n", "0 Industrial Conglomerates Saint Paul, Minnesota 1957-03-04 \n", "1 Building Products Milwaukee, Wisconsin 2017-07-26 \n", "2 Health Care Equipment North Chicago, Illinois 1957-03-04 \n", "3 Pharmaceuticals North Chicago, Illinois 2012-12-31 \n", "4 IT Consulting & Other Services Dublin, Ireland 2011-07-06 \n", ".. ... ... ... \n", "498 Restaurants Louisville, Kentucky 1997-10-06 \n", "499 Electronic Equipment & Instruments Lincolnshire, Illinois 2019-12-23 \n", "500 Health Care Equipment Warsaw, Indiana 2001-08-07 \n", "501 Regional Banks Salt Lake City, Utah 2001-06-22 \n", "502 Pharmaceuticals Parsippany, New Jersey 2013-06-21 \n", "\n", " CIK Founded \n", "0 66740 1902 \n", "1 91142 1916 \n", "2 1800 1888 \n", "3 1551152 2013 (1888) \n", "4 1467373 1989 \n", ".. ... ... \n", "498 1041061 1997 \n", "499 877212 1969 \n", "500 1136869 1927 \n", "501 109380 1873 \n", "502 1555280 1952 \n", "\n", "[503 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", "df = table[0]\n", "df # 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" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|█| 10/1\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.78-0.61-0.23-0.45-0.75-0.71-0.69-0.07-0.67
AMGN-0.781.000.760.390.350.850.780.680.280.77
APH-0.610.761.000.680.170.860.950.580.660.94
CE-0.230.390.681.000.400.310.680.610.880.58
D-0.450.350.170.401.000.120.300.600.190.25
GIS-0.750.850.860.310.121.000.830.520.290.90
KEYS-0.710.780.950.680.300.831.000.620.670.93
NEM-0.690.680.580.610.600.520.621.000.360.54
SYF-0.070.280.660.880.190.290.670.361.000.57
WM-0.670.770.940.580.250.900.930.540.571.00
\n", "
" ], "text/plain": [ " AAL AMGN APH CE D GIS KEYS NEM SYF WM\n", "AAL 1.00 -0.78 -0.61 -0.23 -0.45 -0.75 -0.71 -0.69 -0.07 -0.67\n", "AMGN -0.78 1.00 0.76 0.39 0.35 0.85 0.78 0.68 0.28 0.77\n", "APH -0.61 0.76 1.00 0.68 0.17 0.86 0.95 0.58 0.66 0.94\n", "CE -0.23 0.39 0.68 1.00 0.40 0.31 0.68 0.61 0.88 0.58\n", "D -0.45 0.35 0.17 0.40 1.00 0.12 0.30 0.60 0.19 0.25\n", "GIS -0.75 0.85 0.86 0.31 0.12 1.00 0.83 0.52 0.29 0.90\n", "KEYS -0.71 0.78 0.95 0.68 0.30 0.83 1.00 0.62 0.67 0.93\n", "NEM -0.69 0.68 0.58 0.61 0.60 0.52 0.62 1.00 0.36 0.54\n", "SYF -0.07 0.28 0.66 0.88 0.19 0.29 0.67 0.36 1.00 0.57\n", "WM -0.67 0.77 0.94 0.58 0.25 0.90 0.93 0.54 0.57 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 tqdm(assets):\n", " # Get monthly stock prices over date range\n", " prices_yf = pd.DataFrame(\n", " yf.download(ticker, start=start, end=end, interval=\"1mo\", progress=False)[\"Adj Close\"]\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" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|█| 10/1\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.050.33
KEYS0.270.29
WM0.160.19
CE0.050.32
SYF0.010.42
GIS0.110.18
AAL-0.180.46
D-0.020.20
APH0.130.26
AMGN0.070.24
\n", "
" ], "text/plain": [ " ret vol\n", "NEM 0.05 0.33\n", "KEYS 0.27 0.29\n", "WM 0.16 0.19\n", "CE 0.05 0.32\n", "SYF 0.01 0.42\n", "GIS 0.11 0.18\n", "AAL -0.18 0.46\n", "D -0.02 0.20\n", "APH 0.13 0.26\n", "AMGN 0.07 0.24" ] }, "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 tqdm(assets):\n", " prices_yf = pd.DataFrame(\n", " yf.download(ticker, start=start, end=end, interval=\"1mo\", progress=False)[\"Adj Close\"]\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[0], \"vol\": vol[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" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|█| 90/9\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Creating objective function...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|█| 10/1\n" ] } ], "source": [ "N = len(assets)\n", "expected_return = 0\n", "weight_constraint = 0\n", "print(\"Creating weight constraint...\")\n", "for variable in tqdm(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 tqdm(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", " )" ] }, { "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", "lam = sympy.Symbol(\"lam\")\n", "obj = obj.add_constraint_eq_zero(weight_constraint, lam=10)\n", "\n", "obj_QUBO = (\n", " obj.to_qubo()\n", ") # 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", "\n", "from general_superstaq import service\n", "import general_superstaq as gss\n", "\n", "client = service.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": [ "from qubovert.sim import anneal_qubo\n", "\n", "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": [ { "name": "stdout", "output_type": "stream", "text": [ "{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 1, 10: 1, 11: 0, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 0, 28: 0, 29: 0, 30: 0, 31: 0, 32: 0, 33: 0, 34: 0, 35: 0, 36: 0, 37: 0, 38: 0, 39: 0, 40: 0, 41: 0, 42: 0, 43: 0, 44: 0, 45: 0, 46: 0, 47: 0, 48: 0, 49: 0, 50: 1, 51: 0, 52: 0, 53: 0, 54: 0, 55: 0, 56: 0, 57: 0, 58: 0, 59: 0, 60: 0, 61: 0, 62: 0, 63: 0, 64: 0, 65: 0, 66: 0, 67: 0, 68: 0, 69: 0, 70: 0, 71: 0, 72: 0, 73: 0, 74: 0, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 0, 82: 0, 83: 0, 84: 0, 85: 0, 86: 0, 87: 0, 88: 0, 89: 0}\n" ] } ], "source": [ "best_sharpe_ratio = float(\"-inf\") # Set Sharpe ratio at the lowest at the start to compare\n", "best_portfolio = None\n", "for (\n", " sol\n", ") in (\n", " res\n", "): # Loops over the simulated annealing solutions to classically post-process the best Sharpe ratio\n", " print(sol)\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.2\n", "The portfolio risk is: 0.24\n", "The Sharpe ratio for the best portfolio 0.85\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.430.500.000.000.010.000.000.060.00
\n", "
" ], "text/plain": [ " NEM KEYS WM CE SYF GIS AAL D APH AMGN\n", "weights 0.00 0.43 0.50 0.00 0.00 0.01 0.00 0.00 0.06 0.00" ] }, "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.9.16" } }, "nbformat": 4, "nbformat_minor": 5 }