On this page:
<require>
<provide>
<helpers>
<optimize-portfolio>
<run-example>
<*>

3.13 Portfolio optimization🔗ℹ

Markowitz portfolio optimization chooses asset weights w that minimize risk for a required return:

minimize wᵀΣ w
subject to 1ᵀw = 1, μᵀw ≥ r, w ≥ 0

where Σ is the return covariance, μ the expected returns, r a target return, and w ≥ 0 forbids short positions (a long-only portfolio). This is a quadratic program touching three cone types at once: an equality (the budget), an inequality (the return floor), and the nonnegativity orthant. We expose (optimize-portfolio Sigma mu #:min-return r).

(require racket/list
         scs)

(provide optimize-portfolio run-example)

Standard form.

The objective wᵀΣ w is ½ wᵀP w with P = 2Σ, and c = 0. The constraints, in cone order, are one zero cone row for the budget, then positive-orthant rows for the return floor (−μᵀw ≤ −r) and the long-only bounds (−w_i ≤ 0):

(define (sparse-row n entries)
  (for/list ([k (in-range n)])
    (cond [(assoc k entries) => cdr] [else 0])))

(define (optimize-portfolio Sigma mu #:min-return r)
  (define n (length mu))
 
  (define P
    (apply scs:sparse-matrix n n
           (for*/list ([i (in-range n)] [j (in-range n)]
                       #:when (<= i j)
                       #:when (not (zero? (list-ref (list-ref Sigma i) j))))
             (list i j (* 2.0 (list-ref (list-ref Sigma i) j))))))
  (define budget (make-list n 1.0))                 ; 1ᵀw = 1
  (define return-row (for/list ([m (in-list mu)]) (- m))) ; -μᵀw <= -r
  (define long-only                                 ; -w_i <= 0
    (for/list ([i (in-range n)]) (sparse-row n (list (cons i -1)))))
  (define A
    (apply scs:matrix (+ n 2) n
           (append budget return-row (append* long-only))))
  (define b (append (list 1.0 (- r)) (make-list n 0.0)))
  (define result
    (solve #:A A
           #:b b
           #:c (make-list n 0.0)
           #:P P
           #:cone (make-cone #:zero 1 #:positive (+ n 1))
           #:settings (make-settings #:eps-abs 1e-9 #:eps-rel 1e-9)))
  (scs-result-x result))

Running it.

Two uncorrelated assets, the second less risky, with a return floor that binds. The minimum-risk feasible portfolio splits evenly: w = (0.5, 0.5).

(define Sigma '((2.0 0.0) (0.0 1.0)))    ; covariance
(define mu    '(0.2 0.1))                ; expected returns
 
(define (run-example)
  (optimize-portfolio Sigma mu #:min-return 0.15))

(run-example)   ; #(0.5 0.5)

<*> ::=