Portfolio Optimization

Portfolio optimization is a fundamental problem in quantitative finance, first formalized by Harry Markowitz in his Nobel Prize-winning work on Modern Portfolio Theory.

Finance and QUBO Models

Portfolio optimization is a common QUBO example because the quadratic risk term maps naturally to the formulation. Quantum and quantum-inspired methods are active areas of research here, but practical performance is still highly dependent on the specific solver and instance.

In this example, we will be exploring an optimization model for asset distribution where the expected return is maximized while mitigating the financial risk. The following approach was inspired by a JuMP tutorial, where monthly stock prices for three assets are provided, namely IBM, WMT and SEHI.

Mathematical Formulation

The modelling presented below aggregates the risk measurement $\mathbf{x}' \Sigma \mathbf{x}$ as a penalty term to the objective function, thus yielding

\[\begin{array}{rll} \max_{\mathbf{x}} & \mathbf{\mu}'\mathbf{x} - \lambda\, \mathbf{x}' \Sigma \mathbf{x} \\ \textrm{s.t.} & 0 \le {x}_{i} \le 1 & \forall i \\ & \sum_{i} {x}_{i} = 1 \end{array}\]

where $\mu_{i} = \mathbb{E}[r_{i}]$ is the expected return value for each investment $i$; $\Sigma$ is the covariance matrix and $\lambda$ is the risk-aversion penalty factor.

This is a quadratic programming problem where:

  • The linear term $\mathbf{\mu}'\mathbf{x}$ represents expected returns
  • The quadratic term $\mathbf{x}' \Sigma \mathbf{x}$ represents portfolio risk (variance)
  • The parameter $\lambda$ controls the trade-off between return and risk

Stock prices

using DataFrames
using Statistics

assets = [:IBM, :WMT, :SEHI]

df = DataFrames.DataFrame(
    [
         93.043    51.826    1.063
         84.585    52.823    0.938
        111.453    56.477    1.000
         99.525    49.805    0.938
         95.819    50.287    1.438
        114.708    51.521    1.700
        111.515    51.531    2.540
        113.211    48.664    2.390
        104.942    55.744    3.120
         99.827    47.916    2.980
         91.607    49.438    1.900
        107.937    51.336    1.750
        115.590    55.081    1.800
    ],
    assets,
)
13×3 DataFrame
RowIBMWMTSEHI
Float64Float64Float64
193.04351.8261.063
284.58552.8230.938
3111.45356.4771.0
499.52549.8050.938
595.81950.2871.438
6114.70851.5211.7
7111.51551.5312.54
8113.21148.6642.39
9104.94255.7443.12
1099.82747.9162.98
1191.60749.4381.9
12107.93751.3361.75
13115.5955.0811.8

Solving

using JuMP
using ToQUBO
using PySA

function solve(
    config!::Function,
    df::DataFrame,
    λ::Float64 = 10.;
    optimizer = PySA.Optimizer
)
    # Number of assets
    n = size(df, 2)

    # Relative monthly return
    r = diff(Matrix(df); dims = 1) ./ Matrix(df[1:end-1, :])

    # Expected monthly return value for each stock
    μ = vec(Statistics.mean(r; dims = 1))

    # Covariance matrix
    Σ = Statistics.cov(r)

    # Build model
    model = Model(() -> ToQUBO.Optimizer(optimizer))

    @variable(model, 0 <= x[1:n] <= 1)
    @objective(model, Max, μ'x - λ * x' * Σ * x)
    @constraint(model, sum(x) == 1)

    config!(model)

    optimize!(model)

    return value.(x)
end

function solve(df::DataFrame, λ::Float64 = 10.; optimizer = PySA.Optimizer)
    return solve(identity, df, λ; optimizer)
end
solve (generic function with 4 methods)
solve(df) do model
    JuMP.set_silent(model)
    JuMP.set_optimizer_attribute(model, "n_reads", 200)
end
3-element Vector{Float64}:
 0.0
 1.0
 0.0

Penalty Analysis

To finish our discussion, we are going to sketch some graphics to help our reasoning on how the penalty factor $\lambda$ affects our investments.

using Plots

Λ = collect(0.:5.:50.)
X = Dict{Symbol,Vector{Float64}}(tag => [] for tag in assets)

for λ = Λ
    x = solve(df, λ)

    for (i, tag) in enumerate(assets)
        push!(X[tag], x[i])
    end
end

plt = plot(;
    title="Portfolio Optimization",
    xlabel=raw"penalty factor ($\lambda$)",
    ylabel=raw"investment share ($x$)",
)

for tag in assets
    plot!(plt, Λ, X[tag]; label=string(tag))
end

plt
Example block output