Homework 7

Projection and portfolio Optimization

Import packages
using LinearAlgebra, CSV, DataFrames, Statistics, StatsPlots

1 Projection onto a halfspace

A nonzero n-vector \(a\) and a scalar \(\beta\) define the halfspace

\[H^{-}_{a,β}=\{\,x∈\mathbb R^n\mid ⟨a,x⟩≤β\,\}.\]

1.1 Derive the expression for the projection

Derive an explicit expression for the projection of an arbitrary n-vector b onto \(H^-_{a,β}\):

1.2 Implement the projection

Implement your solution above by completing the following function.

"""
Project the n-vector `b` onto the halfspace {x ∣ ⟨a,x⟩ ≤ β} defined by the nonzero vector `a` and the scalar `β`.
"""
function proj_halfspace(b, a, β)
  # your code here
end;

1.3 Implement a unit test

Verify numerically with a simple example that your projection code is correct. No need to formally verify correctness.

function test_proj_halfspace()
    a = ...
    β = ...
    b = ...
    expected = ...
    result = proj_halfspace(b, a, β)
    result == expected
end
test_proj_halfspace()

2 Alternating projection method

The function projsplx, defined here, computes the projection of any n-vector onto the probability simplex \(\{x\in\mathbb R^n\mid \sum_j^nx_j=1,\ x≥0\}\).

Implementation of projsplx
"""
Projection onto the unit simplex.

    projsplx(b) -> x

Variant of `projsplx`.
"""
function proj_simplex(b::Vector; τ=1.0)
    x = copy(b)
    projsplx!(x, τ)
    return x
end;

"""
Projection onto the unit simplex {x | sum(x) = τ, x ≥ 0}.

    projsplx!(b, τ)

In-place variant of `projsplx`.
"""
function projsplx!(b::Vector{T}, τ::T) where T

    n = length(b)
    bget = false

    idx = sortperm(b, rev=true)
    tsum = zero(T)

    @inbounds for i = 1:n-1
        tsum += b[idx[i]]
        tmax = (tsum - τ)/i
        if tmax  b[idx[i+1]]
            bget = true
            break
        end
    end

    if !bget
        tmax = (tsum + b[idx[n]] - τ) / n
    end

    @inbounds for i = 1:n
        b[i] = max(b[i] - tmax, 0)
    end
end;

The cell below shows the result of the projection of a vector b onto the simplex.

let
    b = [12.0, -23.0, 16.0]
    x = proj_simplex(b)
end
3-element Vector{Float64}:
 0.0
 0.0
 1.0

Now suppose that we wish to compute the projection onto the intersection of the probability simplex and an arbitrary halfspace. Can you do this using the projection function proj_halfspace, which you implemented above, and proj_simplex?

Yes, you can, using the Projection Onto Convex Sets algorithm, which projects onto the intersection of sets \(C_1\bigcap C_2\) each convex set by itereratively projecting onto \(C_1\) and \(C_2\). The algorithm is simple, and involves the following iteration:

\[x_{k+1}=P_1(P_2(x_k)),\]

where \(P_1\) and \(P_2\) are projection operators, respectively, onto the sets \(C_1\) and \(C_2\). Implement this algorithm for generic projection operators \(P_1\) and \(P_2\). Exit the loop when \[\|x_{k+1}-x_k\|≤ϵ\|x_0\|\] for some positive tolerance \(\epsilon\).

Note that this method can in some situations take many iterations. But here we’re only dealing with a small problem, so it should be fine.

2.1 Implement the intersection projection

Complete the following function.

"""
Project a vector `x` onto the intersection of two convex sets. Takes as input two functions `p1` and `p2` that each take a vector and return the projection of `b0` onto the corresponding sets.
"""
function proj_intersection(x::Vector, p1::Function, p2::Function; maxits=100, ϵ=1e-7)
    err = []
    x0 = copy(x)
    ϵ *= norm(x0, Inf)
    for i  1:maxits
        # your code here
        push!(err, norm(x-x0, Inf))
        err[end]  ϵ && break
        x0 = copy(x)
    end
    return x
end;

2.2 Implement a unit test

Test your implementation of proj_intersection on a small problem that aims to find the projection of a vector onto the intersection of the unit probability simplex and a halfspace. Verify that the answer is correct.

Warning

Make sure that your test problem is feasible – i.e., that the intersection isn’t empty.

3 Financial portfolio optimization

Modern portfolio optimization theory is based on the Markovitz model for determining a portfolio of stocks with a desired expected rate of return that has the smallest amount of risk, as measured by the variance of the portfolio’s return. The main idea is that by diversifying (i.e., investing in a mixture of different stocks), one can guard against large amounts of variance in the rates of return of the individual stocks in the portfolio.

Rate of return and risk

Suppose that \(p_1,\ldots,p_m\) are the historical prices of a stock over some period of time. We define the rPPate of return at time \(t\), relative to the intial price \(p_1\) by \[ r_t := (p_t-p_1)/p_1, \quad t=1,\ldots,m. \tag{1}\]

The expected rate of return is the mean \(\mu\) of the rates of return. The risk of the portfolio is measured as the standard deviation \(\sigma\) of the rates of return: \[ \mu := \frac{1}{m}\sum_{t=1}^m r_t, \quad\text{and}\quad \sigma:=\sqrt{\frac{1}{m}\sum_{t=1}^m (r_t-\mu)^2}. \]

Given a collection of \(n\) stocks, let \(r_t^i\) be the rate of return of stock \(i\) at time \(t\). Let \(r\) be the \(n\)-vector of the expected rates of return of the \(n\) stocks. In addition, let \(\Sigma\) be the \(n\times n\) covariance matrix of the rates of return of the \(n\) stocks. Thus, \(r_i\) is the mean of the rates of return of stock \(i\), the \(i\)th diagonal element \(\Sigma_{ii}\) is its variance, and \(\Sigma_{ij}\) is the covariance of the rates of return of stocks \(i\) and \(j\), i.e., \[ r_i:=\frac{1}{m}\sum_{t=1}^m r_t^i \quad\text{and}\quad \Sigma_{ij}:=\frac{1}{m}\sum_{t=1}^m (r_t^i-r_i)(r_t^j-r_j). \]

Portfolio expected return and risk

We let \(x_i\) be the fraction of our investment money we put into stock \(i\), for \(i=1,\ldots,n\). For the sake of this study, we assume there is no short selling (i.e., holding a stock in negative quantity.) Thus, \(x=(x_1,\ldots,x_n)\) is a vector of length \(n\) that has nonnegative entries that sum to one (i.e, \(x\ge0\) and \(\sum_{i=1}^n x_i=1\)). The vector \(x\) represents our portfolio of investments. The expected rate of return and standard deviation of a portfolio \(x\) are then given by \[ \mu:= r^T x \quad\text{and}\quad \sigma:=\sqrt{x^T\Sigma x}. \]

The code below downloads one year (2022-2023) of historical prices of a collection of stocks and implements meancov, which computes the expected rate of return and covariance matrix of the rates of return.

Download stock data and implement meancov
stocks = 
    ["AAPL", "META", "GOOG", "MSFT", "NVDA", # technology
     "AMZN", "COST", "EBAY", "TGT", "WMT",   # services
     "BMO", "BNS", "HBCP", "RY", "TD",       # finance
     "BP", "CVX", "IMO", "TTE", "XOM"        # energy
    ]

tmpdir = mktempdir()

for s in stocks
    url = "https://query1.finance.yahoo.com/v7/finance/download/$(s)?period1=1647815267&period2=1679351267&interval=1d&events=history&includeAdjustedClose=true"
    Base.download(url, joinpath(tmpdir,"$(s).csv"))
end

df = map(stocks) do stock
    f = joinpath(tmpdir,"$(stock).csv")
    dfs = DataFrame(CSV.File(f, select=[1,6])) # keep only date and closing price
    rename!(dfs, "Adj Close" => stock)
    select!(dfs, :Date, "$stock" .=> p->(p.-p[1])/p[1])
    rename!(dfs, "$(stock)_function" => "$stock")
end |> z->innerjoin(z..., on=:Date)

function meancov(df)
    r = mean.(eachcol(df[:,2:end]))
    Σ = cov(Matrix(df[:,2:end]))
    return r, Σ
end;

Here are the expected rates of return for each stock.

Plot expected rates of return
r, Σ = meancov(df)
df_plot = DataFrame(stock=stocks, r=r)
sort!(df_plot, :r)
@df df_plot bar(:stock, :r, legend=false)
xlabel!("Stock"); ylabel!("Return"); title!("Returns of Stocks")

3.1 Implement a projected gradient minimizer

The Markovitz model that chooses a portfolio allocation among the given \(n\) stocks that minimizes the risk given a desired expected rate of return \(\mu\). The optimization problem is \[ \min{x}\ \set{\tfrac 12x^T \Sigma x \mid r^T x \ge \mu, \ x\ge0, \ \sum_{i=1}^n x_i = 1}, \] which minimizes a positive semidefinite quadratic objective subject to the intersection of a halfspace (\(r^T x \ge \mu\)) and the probability simplex.

The function below is meant to minimize a quadratic function \(\frac12 x^T Qx\) (with no or constant terms) over a convex set \(C\). Complete the implementation.

"""
Projected gradient method for the quadratic optimization problem

    minimize_{x}  1/2 x'Qx  subj to  x ∈ C.

The function `proj(b)` compute the projection of the vector `b` onto the convex set 𝒞.
"""
function pgrad(Q, proj::Function; maxits=100, ϵ=1e-5)
    # your code here
    return x
end;

3.2 Compute a minimum-risk portfolio

Use the function pgrad to obtain the minimum-risk portfolio from a set of assets with returns r and covariances Σ. Use proj_intersection as the input projection function for pgrad.

"""
Given a set of `n` assets with expected returns `r` and covariance matrix `Σ`, compute the minimum-risk portfolio with expected return `μ`.
"""
function efficient_portfolio(r, Σ, μ)
    n = length(r)
    p1 = # your code here
    p2 = # your code here 
    p = b -> proj_intersection(b, p1, p2, maxits=10000, ϵ=1e-8)[1]
    x = pgrad(Σ, p, maxits=1000)
end;

Here we use the function efficient_portfolio to compute an optimal portfolio with expected return of 10%. Show in a pie chart the allocation of the portfolio among the stocks.

xp1 = efficient_portfolio(r, Σ, .1)
pie(stocks, xp1, title="Portfolio Allocation")

3.3 Compute the efficient frontier (optional)

The efficient frontier is the set of optimal portfolios that offer the minimum risk for a given expected rate of return. Use the function efficient_portfolio to compute the efficient frontier for expected rates of return between 0 and 10%. Plot the objective value (risk) for 10 values of expected return between 0 and 10%.

# Define target returns
target_returns = range(0, 0.1, length=10)

# Initialize arrays to store results
portfolio_risks = Float64[]
portfolio_returns = Float64[]

# Loop over target returns
for μ in target_returns
  # your code here
end

Plot the efficient frontier.

plot(portfolio_returns, portfolio_risks, label="Efficient Frontier", 
    xlabel="Return", ylabel="Risk", title="Portfolio Risk vs Return")