blob: 60649b61a18a5dace6d95a0b1cff0efee8cc3dda [file] [log] [blame]
Austin Schuh9049e202022-02-20 17:34:16 -08001Portfolio optimization
2======================
3
4
5Portfolio optimization seeks to allocate assets in a way that maximizes the risk adjusted return,
6
7
8.. math::
9 \begin{array}{ll}
10 \mbox{maximize} & \mu^T x - \gamma \left( x^T \Sigma x \right) \\
11 \mbox{subject to} & \boldsymbol{1}^T x = 1 \\
12 & x \ge 0
13 \end{array}
14
15
16where :math:`x \in \mathbf{R}^{n}` represents the portfolio, :math:`\mu \in \mathbf{R}^{n}` the vector of expected returns, :math:`\gamma > 0` the risk aversion parameter, and :math:`\Sigma \in \mathbf{S}^{n}_{+}` the risk model covariance matrix.
17The risk model is usually assumed to be the sum of a diagonal and a rank :math:`k < n` matrix,
18
19
20.. math::
21 \Sigma = F F^T + D,
22
23
24where :math:`F \in \mathbf{R}^{n \times k}` is the factor loading matrix and :math:`D \in \mathbf{S}^{n}_{+}` is a diagonal matrix describing the asset-specific risk.
25The resulting problem has the following equivalent form,
26
27.. math::
28 \begin{array}{ll}
29 \mbox{minimize} & \frac{1}{2} x^T D x + \frac{1}{2} y^T y - \frac{1}{2\gamma}\mu^T x \\
30 \mbox{subject to} & y = F^T x \\
31 & \boldsymbol{1}^T x = 1 \\
32 & x \ge 0
33 \end{array}
34
35
36
37Python
38------
39
40.. code:: python
41
42 import osqp
43 import numpy as np
44 import scipy as sp
45 from scipy import sparse
46
47 # Generate problem data
48 sp.random.seed(1)
49 n = 100
50 k = 10
51 F = sparse.random(n, k, density=0.7, format='csc')
52 D = sparse.diags(np.random.rand(n) * np.sqrt(k), format='csc')
53 mu = np.random.randn(n)
54 gamma = 1
55
56 # OSQP data
57 P = sparse.block_diag([D, sparse.eye(k)], format='csc')
58 q = np.hstack([-mu / (2*gamma), np.zeros(k)])
59 A = sparse.vstack([
60 sparse.hstack([F.T, -sparse.eye(k)]),
61 sparse.hstack([sparse.csc_matrix(np.ones((1, n))), sparse.csc_matrix((1, k))]),
62 sparse.hstack((sparse.eye(n), sparse.csc_matrix((n, k))))
63 ], format='csc')
64 l = np.hstack([np.zeros(k), 1., np.zeros(n)])
65 u = np.hstack([np.zeros(k), 1., np.ones(n)])
66
67 # Create an OSQP object
68 prob = osqp.OSQP()
69
70 # Setup workspace
71 prob.setup(P, q, A, l, u)
72
73 # Solve problem
74 res = prob.solve()
75
76
77
78Matlab
79------
80
81.. code:: matlab
82
83 % Generate problem data
84 rng(1)
85 n = 100;
86 k = 10;
87 F = sprandn(n, k, 0.7);
88 D = sparse(diag( sqrt(k)*rand(n,1) ));
89 mu = randn(n, 1);
90 gamma = 1;
91
92 % OSQP data
93 P = blkdiag(D, speye(k));
94 q = [-mu/(2*gamma); zeros(k, 1)];
95 A = [F', -speye(k);
96 ones(1, n), zeros(1, k);
97 speye(n), sparse(n, k)];
98 l = [zeros(k, 1); 1; zeros(n, 1)];
99 u = [zeros(k, 1); 1; ones(n, 1)];
100
101 % Create an OSQP object
102 prob = osqp;
103
104 % Setup workspace
105 prob.setup(P, q, A, l, u);
106
107 % Solve problem
108 res = prob.solve();
109
110
111
112CVXPY
113-----
114
115.. code:: python
116
117 from cvxpy import *
118 import numpy as np
119 import scipy as sp
120 from scipy import sparse
121
122 # Generate problem data
123 sp.random.seed(1)
124 n = 100
125 k = 10
126 F = sparse.random(n, k, density=0.7, format='csc')
127 D = sparse.diags(np.random.rand(n) * np.sqrt(k), format='csc')
128 mu = np.random.randn(n)
129 gamma = 1
130 Sigma = F*F.T + D
131
132 # Define problem
133 x = Variable(n)
134 objective = mu.T*x - gamma*quad_form(x, Sigma)
135 constraints = [sum(x) == 1, x >= 0]
136
137 # Solve with OSQP
138 Problem(Maximize(objective), constraints).solve(solver=OSQP)
139
140
141
142YALMIP
143------
144
145.. code:: matlab
146
147 % Generate problem data
148 rng(1)
149 n = 100;
150 k = 10;
151 F = sprandn(n, k, 0.7);
152 D = sparse(diag( sqrt(k)*rand(n,1) ));
153 mu = randn(n, 1);
154 gamma = 1;
155 Sigma = F*F' + D;
156
157 % Define problem
158 x = sdpvar(n, 1);
159 objective = gamma * (x'*Sigma*x) - mu'*x;
160 constraints = [sum(x) == 1, x >= 0];
161
162 % Solve with OSQP
163 options = sdpsettings('solver', 'osqp');
164 optimize(constraints, objective, options);
165