Real World Use Case: Stock Portfolio Optimisation with Quantum Algorithms on the Dynex Platform
Portfolio Optimization (PO) is a standard problem in the financial industry. The computational complexity of such combinatorial optimization problems tend to increase exponentially with the number of variables — here, the number of assets — which at large scale can make solvers incapable of providing only optimal solutions. Instead, the results are likely suboptimal. This article provides detailed introduction on how stock portfolio optimisation can be performed by using quantum computing algorithm on the Dynex Neuromorphic Computing Cloud overcoming these limitations.
Introduction: What is portfolio optimization?
Portfolio optimization is a crucial process for anyone who wants to maximise returns from their investments. Investments are usually a collection of so-called assets (stock, credits, bonds, derivatives, calls, puts, etc..) and this collection of assets is called a portfolio.
The goal of portfolio optimization is to minimise risks (financial loss) and maximize returns (financial gain). But this process is not as simple as it may seem. Gaining high returns with little risk is indeed too good to be true. Risks and returns usually have a trade-off relationship which makes optimising your portfolio a little more complicated. As Dr. Harry Markowitz states in his Modern Portfolio Theory he created in 1952, “risk is an inherent part of higher reward.”
Modern Portfolio Theory (MPT)
An investment theory based on the idea that investors are risk-averse, meaning that when given two portfolios that offer the same expected return they will prefer the less risky one. Investors can construct portfolios to maximize expected return based on a given level of market risk, emphasizing that risk is an inherent part of higher reward. It is one of the most important and influential economic theories dealing with finance and investment. Dr. Harry Markowitz created the modern portfolio theory (MPT) in 1952 and won the Nobel Prize in Economic Sciences in 1990 for it.
The Modern portfolio theory (MPT) serves as a general framework to determine an ideal portfolio for investors. The MPT is also referred to as mean-variance portfolio theory because it assumes that any investor will choose the optimal portfolio from the set of portfolios that
- Maximises expected return for a given level of risk; and
- Minimises risks for a given level of expected returns.
Consider a situation where you have two stocks to choose from: A and B. You can invest your entire wealth in one of these two stocks. Or you can invest 10% in A and 90% in B, or 20% in A and 80% in B, or 70% in A and 30% in B, etc … There is a huge number of possible combinations and this is a simple case when considering two stocks. Imagine the different combinations you have to consider when you have thousands of stocks.
The minimum variance frontier shows the minimum variance that can be achieved for a given level of expected return. To construct a minimum-variance frontier of a portfolio:
- Use historical data to estimate the mean, variance of each individual stock in the portfolio, and the correlation of each pair of stocks.
- Use a computer program to find out the weights of all stocks that minimise the portfolio variance for each pre-specified expected return.
- Calculate the expected returns and variances for all the minimum variance portfolios determined in step 2 and then graph the two variables.
Investors will never want to hold a portfolio below the minimum variance point. They will always get higher returns along the positively sloped part of the minimum-variance frontier. And the positively sloped part of the minimum-variance frontier is called the efficient frontier.
The efficient frontier is where the optimal portfolios are. And it helps narrow down the different portfolios from which the investor may choose.
Quantum algorithms for portfolio optimization
The goal of this article is to find the efficient frontier for an inherent risk using a quantum approach. We will use Fujitsu Research’s AutoQUBO modules to convert our portfolio optimization problem into a quadratic model so we can then use quantum algorithms to solve our optimization problem on the Dynex Neuromorphic Computing Cloud. We will use Python (using a Jupyter Notebook) in this article, the entire source code is available in our Dynex SDK Github. Let’s go through the entire process step by step:
- Full source code here: https://github.com/dynexcoin/DynexSDK/blob/main/Dynex_Portfolio_Optimisation.ipynb
Step 1: Importing the modules
We start by importing the required modules, including the Dynex SDK, the AutoQUBO library, and the Yahoo Finance module (which we will be using to import financial data):
import csv
import numpy as np
import dynex #pip install dynex
from autoqubo import Binarization, SamplingCompiler, SearchSpace, Utils
import yfinance as yf. #pip install yfinance
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
Step 2: Importing stock data
Using the yfinance module we download historical stock data for the period from 01/01/2018 up to 01/01/2024 for any number of stocks, cryptocurrencies or other financial instruments which we would like to include in our portfolio optimisation:
symbols = [‘AAPL’, ‘MSFT’, ‘GOOG’, ‘AMZN’, ‘META’]
start_date = ‘2018–01–01’
end_date = ‘2024–01–01’
budget = 100
data = yf.download(symbols, start=start_date, end=end_date)[‘Adj Close’]
We are using the daily “adjusted close” in our example, and receive a table with 1509 historical prices for the selected symbols:
Ticker AAPL AMZN GOOG META MSFT
Date
2018-01-02 40.670971 59.450500 53.250000 181.227707 80.080925
2018-01-03 40.663902 60.209999 54.124001 184.474274 80.453598
2018-01-04 40.852764 60.479500 54.320000 184.134628 81.161713
2018-01-05 41.317890 61.457001 55.111500 186.651962 82.167969
2018-01-08 41.164433 62.343498 55.347000 188.080444 82.251816
... ... ... ... ... ...
2023-12-22 193.353287 153.419998 142.720001 353.015472 373.888580
2023-12-26 192.803986 153.410004 142.820007 354.453918 373.968445
2023-12-27 192.903839 153.339996 141.440002 357.450714 373.379547
2023-12-28 193.333298 153.380005 141.279999 357.940216 374.587280
2023-12-29 192.284637 151.940002 140.929993 353.584839 375.345886
[1509 rows x 5 columns]
Step 3: Preparing the data
The Markowitz Mean-Variance Model is based on expected return μ and the covariance matrix Σ. Expected return of a portfolio is the anticipated amount of returns that a portfolio may generate, making it the mean (average) of the portfolio’s possible return distribution. For example, let’s say stock A, B and C each weighted 50%, 20% and 30% respectively in the portfolio. If the expected return for each stock was 15%, 6% and 9% respectively, the expected return of the portfolio would be:
μ = (50% x 15%) + (20% x 6%) + (30% x 9%) = 11.4%
Covariance Σ is a statistical measure of how two asset’s mean returns vary with respect to each other and helps us understand the amount of risk involved from an investment portfolio’s perspective to make an informed decision about buying or selling stocks.
Based on the financial data we downloaded above, these two are generated with the following code:
trading_days = 252; # number of trading days per year
returns = data.pct_change().dropna();
mean_daily_returns = returns.mean();
mean_annual_returns = mean_daily_returns * trading_days # annualized
cov_matrix = returns.cov();
n = len(symbols)
cov_matrix_np = np.zeros((n, n))
cov_matrix_np = np.array(cov_matrix)
cov_matrix_np = cov_matrix_np * trading_days # annualized
mean_vector_np = np.zeros(n)
mean_vector_np = np.array(mean_annual_returns)
Which results in the following two arrays:
Mean vector:
Ticker
AAPL 0.309906
AMZN 0.219349
GOOG 0.211479
META 0.204932
MSFT 0.303639
dtype: float64
Covariance matrix:
Ticker AAPL AMZN GOOG META MSFT
Ticker
AAPL 0.100343 0.069905 0.066677 0.077528 0.071554
AMZN 0.069905 0.125078 0.074557 0.091815 0.074206
GOOG 0.066677 0.074557 0.097505 0.088823 0.071861
META 0.077528 0.091815 0.088823 0.182890 0.078538
MSFT 0.071554 0.074206 0.071861 0.078538 0.090745
The left-to-right diagonal values of the covariance matrix show the relation of a stock with ‘itself’. And the off-diagonal values show the deviation of each stock’s mean expected return with respect to each other. A simple way to look at a covariance matrix is:
- If two stocks increase and decrease simultaneously then the covariance value will be positive.
- If one increases while the other decreases then the covariance will be negative.
You may have heard the phrase “Don’t Put All Your Eggs in One Basket.” If you invest in things that always move in the same direction, there will be a risk of losing all your money at the same time. Covariance matrix is a nice measure to help investors diversify their assets to reduce such risk.
Step 4: Defining the portfolio optimisation model
As we are using the AutoQUBO library, we can define the optimisation problem with standard python functions. The Dynex platform will find the optimal solution to the objective function f(x) as defined below. Note that this function can be adjusted and modified to our needs as we like:
def variance(x):
"""
Variance
"""
return x@cov_matrix_np@x
def mean(x):
"""
Mean return
"""
return x@mean_vector_np
def constraint(x):
"""
Budget constraint
"""
return (x.sum() - budget)**2
A, B, C = 1, -1, 100
# Objective: Mean-variance portfolio optimization model
def f(x):
return A*variance(x) + B*mean(x) + C*constraint(x)
Step 5: Conversion to QUBO
Quadratic unconstrained binary optimization (QUBO) is a mathematical optimization problem that is mostly used in the field of quantum computing. It involves finding the optimal solution to a problem by minimising a quadratic objective function subject to binary (i.e., 0 or 1) constraints. QUBO is natively supported by the Dynex Neuromorphic Computing Platform with the difference that there are almost no limitations of qubits, accessibility and no error-correction related issues. This makes it a perfect compute environment for computing quantum computing based tasks. The heavy lifting of converting our objective function which we have defined in python, including the conversion from binary to real or integer variables can be done with a few lines of code:
s = SearchSpace()
weights_vector = Binarization.get_uint_vector_type(n, n)
s.add('x', weights_vector, n * n)
qubo, offset = SamplingCompiler.generate_qubo_matrix(fitness_function=f, input_size=s.size, searchspace=s, use_multiprocessing=False)
if SamplingCompiler.test_qubo_matrix(f, qubo, offset, search_space=s):
print("QUBO generation successful")
else:
print("QUBO generation failed - the objective function is not quadratic")
We can inspect the automatically generated QUBO matrix:
[[-19900.20956279 400.40137333 800.80274666 1601.60549333
3203.21098665 200.13980919 400.27961838 800.55923676
1601.11847353 3202.23694706 200.13335453 400.26670907
800.53341813 1601.06683626 3202.13367252 200.15505622
400.31011243 800.62022486 1601.24044972 3202.48089945
200.14310807 400.28621613 800.57243227 1601.14486454
3202.28972907]
[ 0. -39600.21843891 1601.60549333 3203.21098665
6406.42197331 400.27961838 800.55923676 1601.11847353
3202.23694706 6404.47389411 400.26670907 800.53341813
1601.06683626 3202.13367252 6404.26734505 400.31011243
800.62022486 1601.24044972 3202.48089945 6404.96179889
400.28621613 800.57243227 1601.14486454 3202.28972907
6404.57945815]
[ 0. 0. -78399.63413115 6406.42197331
12812.84394661 800.55923676 1601.11847353 3202.23694706
6404.47389411 12808.94778822 800.53341813 1601.06683626
3202.13367252 6404.26734505 12808.53469009 800.62022486
1601.24044972 3202.48089945 6404.96179889 12809.92359778
800.57243227 1601.14486454 3202.28972907 6404.57945815
12809.1589163 ]] offset: 1000000.0
Step 6: Computing on the Dynex platform
The optimal portfolio allocation is being computed by minimising our objective function f(x). The following code computes our QUBO problem on the Dynex platform. We will be utilising 10,000 Dynex chips for an integration time of 1,000 steps. This should be sufficient to find the optimal ground state for our computing problem:
print("Best solutions (minimize):")
sampleset = dynex.sample_qubo(qubo, offset, num_reads=10000, annealing_time=1000)
print(sampleset)
We can follow the progress of our computation directly in Python:
Best solutions (minimize):
[ÐYNEX] PRECISION CUT FROM 10.0 TO 1
[DYNEX] PRECISION SET TO 1
[DYNEX] SAMPLER INITIALISED
[DYNEX|TESTNET] *** WAITING FOR READS ***
╭────────────┬─────────────┬───────────┬───────────────────────────┬─────────┬─────────┬────────────────╮
│ DYNEXJOB │ BLOCK FEE │ ELAPSED │ WORKERS READ │ CHIPS │ STEPS │ GROUND STATE │
├────────────┼─────────────┼───────────┼───────────────────────────┼─────────┼─────────┼────────────────┤
│ -1 │ 0 │ │ *** WAITING FOR READS *** │ │ │ │
╰────────────┴─────────────┴───────────┴───────────────────────────┴─────────┴─────────┴────────────────╯
[DYNEX] FINISHED READ AFTER 0.00 SECONDS
[DYNEX] SAMPLESET READY
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... 24 energy num_oc.
0 0 1 1 1 1 1 1 0 1 0 1 1 0 1 1 1 0 ... 1 755.648561 1
['BINARY', 1 rows, 1 samples, 25 variables]
Step 7: Analysing the results
We continue by parsing the solution to construct the optimal configuration of x, being the parameter to be found in our objective function f(x):
sol = sampleset.record[0][0]
x = s.decode_dict(sol)['x']
e = sampleset.first.energy
Which results to:
x=[30 11 27 1 31]
energy=755.6485607431969
obj=755.6485607438183
constraint=0
obj(baseline)=826.0096115957166 (-70.36105085189831)
Given the objective function we defined, the optimal asset allocation of the stocks we chose is 30%, 11%, 27%, 1% and 31%. The energy which was reported during the computation equals the value of the objective function. We can also see that the found optimum is lower than the baseline, which we assumed to be an equally weight of all assets (20% each in our example). The results of the optimisation can be visualised by plotting the P&L of the equally weighted baseline model vs. the computed optimal allocation:
As we can see is the optimised weighted performance (orange) showing reduced risk, decreased drawdowns and increased profits.
Whats next?
This article forms the foundation of the basics on how to use quantum computing algorithms at scale for portfolio optimisation problems. The example code can be easily modified to use alternative portfolio optimisation techniques, to include multiple asset classes (cryptocurrencies, bonds, etc.) as well as more sophisticated objective functions. Also, a continuous optimisation model could be applied, which would rebalance the portfolio on a monthly, quarterly or annual basis. We leave it to the reader to further develop the model. The paper “A real world test of Portfolio Optimization with Quantum Annealing” by authors from Raiffeisen Bank International AG can provide further insights.
Further reading: