9 min read

Quadratic Form and Minimum-Variance Portfolio Optimisation

\(\newcommand{\t}[1]{ {#1}^T }\) \(\newcommand{\inv}[1]{ {#1}^{-1} }\) \(\newcommand{\vec}[1]{\boldsymbol{#1}}\) \(\newcommand{\diff}[1]{\mathop{}\!\mathrm{\partial}#1}\) \(\newcommand{\fun}[2]{#1\left(#2\right)}\) \(\newcommand{\norm}[1]{\left\lVert #1 \right\rVert}\) \(\newcommand{\abs}[1]{\left| #1 \right|}\) \(\newcommand{\name}[1]{\mathrm{#1}}\) \(\newcommand{\new}{\name{new}}\) \(\newcommand{\dist}[1]{\name{#1}}\) \(\DeclareMathOperator*{\argmax}{arg\,max}\) \(\DeclareMathOperator*{\argmin}{arg\,min}\) \(\DeclareMathOperator*{\diag}{diag}\) \(\newcommand{\rv}[1]{\mathbf{#1}}\) \(\newcommand{\follows}{\thicksim}\) \(\newcommand{\normal}[1]{\mathcal{N}\!\left(#1\right)}\) \(\newcommand{\ga}[1]{\mathrm{Gamma}\!\left(#1\right)}\) \(\newcommand{\diff}[1]{\mathop{}\!\mathrm{d}#1}\) \(\newcommand{\expect}[1]{\mathbb{E}\left[#1\right]}\) \(\newcommand{\vari}[1]{\mathbb{V}\left[#1\right]}\) \(\newcommand{\covari}[1]{\mathrm{Cov}\left(#1\right)}\) \(\newcommand{\prob}[1]{\fun{p}{#1}}\)

Quadratic forms, namely polynomials whose terms are all of degree of two, are of great importance in linear algebra and its various applications. In matrix notation, a quadratic form for a \(K\)-dimension vector \(\vec{w} = \t{\left( w_1, w_2, \dots, w_K \right)}\) can be written as:

\[ \fun{q}{\vec{w}} = \t{\vec{w}} \vec{A} \vec{w} \] where the matrix \(\vec{A}\) specifies the coefficients in the quadratic form. When expanded, we can clearly see how the above expression corresponds to a quadratic form:

\[ \begin{align*} \fun{q}{\vec{w}} &= \t{\vec{w}} \vec{A} \vec{w} \\ &= \t{\begin{pmatrix} w_1 & w_2 & \dots & w_K \end{pmatrix}} \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1K} \\ a_{21} & a_{22} & \cdots & a_{2K} \\ \vdots & \vdots & \vdots & \vdots \\ a_{K1} & a_{K2} & \cdots & a_{KK} \\ \end{pmatrix} \begin{pmatrix} w_1 \\ w_2 \\ \vdots \\ w_K \\ \end{pmatrix} \\ &= \t{\begin{pmatrix} w_1 & w_2 & \dots & w_K \end{pmatrix}} \begin{pmatrix} a_{11} w_1 + a_{12} w_2 + \cdots + a_{1K} w_K \\ a_{21} w_1 + a_{22} w_2 + \cdots + a_{2K} w_K \\ \vdots \\ a_{K1} w_1 + a_{K2} w_2 + \cdots + a_{KK} w_K \\ \end{pmatrix} \\ &= \begin{matrix} & a_{11} w_1 w_1 &+& a_{12} w_1 w_2 &+& \cdots &+& a_{1K} w_1 w_K \\ + & a_{21} w_2 w_1 &+& a_{22} w_2 w_2 &+& \cdots &+& a_{2K} w_2 w_K \\ + & \dots &+& \dots &+& \dots &+& \dots \\ + & a_{K1} w_K w_1 &+& a_{K2} w_K w_2 &+& \cdots &+& a_{KK} w_K w_K \end{matrix} \end{align*} \]

A quadratic form becomes particular interesting when its coefficient matrix \(\vec{A}\) is a covariance matrix — suppose we have a random vector \(\vec{X} = \t{\left( X_1, X_2, \dots, X_K \right)}\), and let \(\vec{Y}\) be a linear combination of the elements in \(\vec{X}\) weighted by \(\vec{w}\), then \(\vec{Y}\) is also a random variable with the following expectation and variance: \[ \begin{align*} \expect{\vec{Y}} &= \expect{\t{\vec{w}} \vec{X}} \\ &= \t{\vec{w}} \expect{\vec{X}} \\ \vari{\vec{X}} &= w_1 w_1 \covari{X_1, X_1} + w_1 w_2 \covari{X_1, X_2} + \dots + w_K w_K \covari{X_K, X_K} \\ &= \begin{pmatrix} w_1 \covari{X_1, X_1} & w_1 \covari{X_1, X_2} & \cdots & w_1 \covari{X_1, X_K} \\ + & + & \ddots & + \\ w_2 \covari{X_2, X_1} & w_2 \covari{X_2, X_2} & \cdots & w_2 \covari{X_2, X_K} \\ + & + & \ddots & + \\ w_K \covari{X_K, X_1} & w_K \covari{X_K, X_2} & \cdots & w_K \covari{X_K, X_K} \end{pmatrix} \begin{pmatrix} w_1 \\ w_2 \\ \vdots \\ w_K \end{pmatrix} \\ &= \begin{pmatrix} w_1 & w_2 & \cdots & w_K \end{pmatrix} \begin{pmatrix} \covari{X_1, X_1} & \covari{X_1, X_2} & \cdots & \covari{X_1, X_K} \\ \covari{X_2, X_1} & \covari{X_2, X_2} & \cdots & \covari{X_2, X_K} \\ \vdots & \vdots & \ddots & \vdots \\ \covari{X_K, X_1} & \covari{X_K, X_2} & \cdots & \covari{X_K, X_K} \end{pmatrix} \begin{pmatrix} w_1 \\ w_2 \\ \vdots \\ w_K \end{pmatrix} \\ &= \t{\vec{w}} \vec{\Sigma} \vec{w} \end{align*} \]

In other words, the quadratic form \(\t{\vec{w}} \vec{\Sigma} \vec{w}\) represents the variance of a linear combination of a random vector’s constituents when its covariance matrix is \(\vec{\Sigma}\).

This representation of variance of a linear combination of random variables often comes up in the context of financial portfolio optimisation. The usual set up is that we have a collection of \(K\) assets whose returns over a unit period of time, as defined by \(R_t = \frac{P_{t} - P_{t-1}}{P_{t-1}}\), are represented by the elements in a random vector \(\vec{X} = \left(X_1, X_2, \dots, X_K\right)\). A portfolio can be formed from these assets by allocating a weight to each aseet according to a \(K\)-dimensional weight vector \(\vec{w}\); the return of the portfolio is therefore a linear combination of the elements of \(\vec{X}\), i.e. \(\t{\vec{w}} \vec{X}\); the variance of the portfolio’s return, as we have shown above, is exactly \(\t{\vec{w}} \vec{\Sigma} \vec{w}\).

Once the expectation and the variance of the portfolio are defined, we can do some interesting things with them. The variance/standard-deviation of the return, for example, is often used as a measure for the risk associated with the portfolio — a desriable portfolio would have a high expected return with a low level exposure to risk. In its simplest form, this can be translated into the following optimisation problem:

\[ \begin{array}{rll} \text{maximise: } & \qquad \frac{1}{2} \t{\vec{w}} \vec{\Sigma} \vec{w} & & \text{(we want to minimise the variance of the portfolio)} \\ \text{subject to: } & \qquad \t{\vec{1}} \vec{w} &= 1 & \text{(this is to ensure the weights sum up to a constant)} \\ & \qquad \t{\expect{\vec{R}}} \vec{w} &\ge C & \text{(the expected return of the portfolio should be greater than a threshold)} \end{array} \]

Note that here we are not considering any risk-free assets and the transaction costs incurred when trading into the desired position. This particular formulation of the portfolio optimisation can be solved with quadratic programming. To demonstrate the process, I will replicate part of the example from Matlab’s documentation on portfolio optimisation with R language.

I trasncribed the original dataset used in the Matlab documentation into a RData file and load it here so that we can reproduce some of the results:

load('BlueChipStockMoments.RData')

Just take a quick look at the variables loaded from the RData file. The dataset contains the means and the covariance matrix of the returns of the 30 assets:

print(asset_list) # print the list of tickers for the assets
##  [1] "AA"   "AIG"  "AXP"  "BA"   "C"    "CAT"  "DD"   "DIS"  "GE"   "GM"  
## [11] "HD"   "HON"  "HPQ"  "IBM"  "INTC" "JNJ"  "JPM"  "KO"   "MCD"  "MMM" 
## [21] "MO"   "MRK"  "MSFT" "PFE"  "PG"   "SBC"  "UTX"  "VZ"   "WMT"  "XOM"
print(asset_means)
##            AA           AIG           AXP            BA             C 
##  0.0149243571  0.0095278690  0.0113101190  0.0069712619  0.0137759762 
##           CAT            DD           DIS            GE            GM 
##  0.0150674167  0.0029306667  0.0030687738  0.0090499048  0.0058126786 
##            HD           HON           HPQ           IBM          INTC 
##  0.0144927857  0.0079065714  0.0079954286  0.0130997143  0.0146523810 
##           JNJ           JPM            KO           MCD           MMM 
##  0.0112448452  0.0084923333 -0.0012911071  0.0078821786  0.0123067738 
##            MO           MRK          MSFT           PFE            PG 
##  0.0129688214  0.0007541071  0.0150997738  0.0045406667  0.0082302738 
##           SBC           UTX            VZ           WMT           XOM 
##  0.0033750476  0.0179275833  0.0054528095  0.0156251786  0.0093157619
print(asset_covar[1:5, 1:5])
##              AA         AIG         AXP          BA           C
## AA  0.013675787 0.002489241 0.003981606 0.004091740 0.004833671
## AIG 0.002489241 0.005999770 0.004255254 0.002388906 0.004387941
## AXP 0.003981606 0.004255254 0.006612873 0.004149297 0.005536310
## BA  0.004091740 0.002388906 0.004149297 0.009567257 0.002800733
## C   0.004833671 0.004387941 0.005536310 0.002800733 0.008938242

Two additional variables, cash_mean and cash_variance, capture the expected return and the risk associated with holding cash, a relatively riskless asset:

print(cash_mean)
## [1] 0.002734881
print(cash_variance)
## [1] 2.494875e-06

We will first approach the portfolio optimisation problem by minimising the variance of the portfolio while keeping the expected return of the portfolio above the risk-free asset (i.e. cash). We will be using the solve.QP function in the quadprog library to solve this:

pacman::p_load("tidyverse")
pacman::p_load("quadprog")	# we will be using the `quadprog` library to solve the optimisation problem

The documentation of the solve.QP function is shown below:

  • The Dmat argument is simply the covariance matrix of the assets, asset_covar.
  • We will not be using the dvec argument since we don’t have the first-order term in our optimisation problem, so we will fill a vector of zeros here.
  • The Amat and the bvec arguments are used to specify the constraints. Some extra bits of care are needed here:
    • We will encode the equality constraint, \(\t{\vec{1}} \vec{w} = 1\), using the first row of Amat and the first element of bvec; the summation can be conveniently coded as a dot product with a vector of ones. It is also important to set the meq argument to 1 to indicate that the first contraint is an equality constraint.
    • We use the second row of Amat to encode the inequality contraint that the expected return of the portfolio should exceed that of the risk-free asset, namely cash_mean.
    • The rest of the Amat matrix encodes the extra constraint that no shorting is allowed — without such constraint, it is possible for the solver to return negative weights for certain assets; this would mean that the portfolio will have to enter a short position on those assets by borrowing them from the market. The constraint is specified by appending an identity matrix to Amat and a vector of zeros to bvec.

The specification of the optimisation problem then becomes quite straightforward:

K        <-   length(asset_means)
Amat     <-    cbind(1, asset_means, diag(rep(1, K)))
bvec     <-        c(1,   cash_mean,      rep(0, K) )
portfolio <- solve.QP(Dmat = asset_covar, dvec = rep(0, K), Amat = Amat, bvec = bvec, meq = 1)
print(portfolio)
## $solution
##  [1]  3.412747e-17  5.370080e-18 -8.939030e-17 -6.564778e-18  5.887635e-17
##  [6]  4.141009e-18 -4.355909e-18  4.218599e-02  5.050503e-02 -8.400927e-19
## [11] -2.131723e-19  3.841085e-17  1.690746e-02 -2.737338e-19  7.461713e-04
## [16]  1.934536e-02 -2.077243e-17 -1.773789e-17 -2.803253e-17  8.028406e-02
## [21]  7.652513e-02 -1.583827e-19  2.344458e-02  1.166485e-01  1.911548e-01
## [26]  1.379132e-18 -1.671579e-16  0.000000e+00  6.425835e-02  3.179946e-01
## 
## $value
## [1] 0.0006057603
## 
## $unconstrained.solution
##  [1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 
## $iterations
## [1] 20  0
## 
## $Lagrangian
##  [1] 1.211521e-03 0.000000e+00 7.232438e-04 2.319600e-04 7.785867e-04
##  [6] 4.860429e-04 6.688958e-04 4.764734e-04 2.613163e-04 0.000000e+00
## [11] 0.000000e+00 2.538115e-04 2.706206e-05 1.080283e-03 0.000000e+00
## [16] 2.429306e-04 0.000000e+00 0.000000e+00 8.471320e-04 1.899856e-04
## [21] 2.761950e-04 0.000000e+00 0.000000e+00 3.388052e-05 0.000000e+00
## [26] 0.000000e+00 0.000000e+00 1.029870e-04 6.155201e-04 7.824339e-06
## [31] 0.000000e+00 0.000000e+00
## 
## $iact
##  [1]  1  5  7 29 14  3  6 19  8  9 16 21  4 20 12 28 24 13 30

The portfolio$solution element captures the weights for the assets in the portfolio. In the original example from Matlab’s documentation, instead of finding a single portfolio that exhibits the lowest variance, a collection of portfolios were created, each for a different expected return level. These portfolios could then be used to plot the efficient frontier for the collection of assets so that one can see the relationship between the risks and expected returns of the various portfolios. A Matlab program equivalent to the above R program is listed as follows:

load BlueChipStockMoments.mat

p = Portfolio('AssetList', AssetList, 'RiskFreeRate', CashMean);
p = setAssetMoments(p, AssetMean, AssetCovar);
p = setDefaultConstraints(p);

% note when we ask for a single portfolio from the frontier estimation procedure, 
% it will return the portfolio with minimal variance:
pwgt = estimateFrontier(p, 1);
pwgt

with the following output:

pwgt =

         0
         0
         0
         0
         0
         0
         0
    0.0422
    0.0505
         0
         0
         0
    0.0169
         0
    0.0007
    0.0193
         0
         0
         0
    0.0803
    0.0765
         0
    0.0234
    0.1166
    0.1912
         0
         0
         0
    0.0643
    0.3180

We can see that the weights estimated using Matlab are consistent with what we obtained with our R program:

as.matrix(round(portfolio$solution, digits = 4), ncol = 1)
##         [,1]
##  [1,] 0.0000
##  [2,] 0.0000
##  [3,] 0.0000
##  [4,] 0.0000
##  [5,] 0.0000
##  [6,] 0.0000
##  [7,] 0.0000
##  [8,] 0.0422
##  [9,] 0.0505
## [10,] 0.0000
## [11,] 0.0000
## [12,] 0.0000
## [13,] 0.0169
## [14,] 0.0000
## [15,] 0.0007
## [16,] 0.0193
## [17,] 0.0000
## [18,] 0.0000
## [19,] 0.0000
## [20,] 0.0803
## [21,] 0.0765
## [22,] 0.0000
## [23,] 0.0234
## [24,] 0.1166
## [25,] 0.1912
## [26,] 0.0000
## [27,] 0.0000
## [28,] 0.0000
## [29,] 0.0643
## [30,] 0.3180