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)