# 10.1 Portfolio Optimization¶

This case study is devoted to the Portfolio Optimization Problem.

The complete code can be downloaded `here`

.

## 10.1.1 The Basic Model¶

The classical Markowitz portfolio optimization problem considers investing in \(n\) stocks or assets held over a period of time. Let \(x_j\) denote the amount invested in asset \(j\), and assume a stochastic model where the return of the assets is a random variable \(r\) with known mean

and covariance

The return of the investment is also a random variable \(y = r^Tx\) with mean (or expected return)

and variance (or risk)

The problem facing the investor is to rebalance the portfolio to achieve a good compromise between risk and expected return, e.g., maximize the expected return subject to a budget constraint and an upper bound (denoted \(\gamma\)) on the tolerable risk. This leads to the optimization problem

The variables \(x\) denotes the investment i.e. \(x_j\) is the amount invested in asset \(j\) and \(x_j^0\) is the initial holding of asset \(j\). Finally, \(w\) is the initial amount of cash available.

A popular choice is \(x^0=0\) and \(w=1\) because then \(x_j\) may be interpreted as the relative amount of the total portfolio that is invested in asset \(j\).

Since \(e\) is the vector of all ones then

is the total investment. Clearly, the total amount invested must be equal to the initial wealth, which is

\[w + e^T x^0.\]

This leads to the first constraint

\[e^T x = w + e^T x^0.\]

The second constraint

\[x^T \Sigma x \leq \gamma^2\]

ensures that the variance, or the risk, is bounded by the parameter \(\gamma^2\). Therefore, \(\gamma\) specifies an upper bound of the standard deviation the investor is willing to undertake. Finally, the constraint

\[x_j \geq 0\]

excludes the possibility of short-selling. This constraint can of course be excluded if short-selling is allowed.

The covariance matrix \(\Sigma\) is positive semidefinite by definition and therefore there exist a matrix \(G\) such that

In general the choice of \(G\) is **not** unique and one possible choice of \(G\) is the Cholesky factorization of \(\Sigma\). However, in many cases another choice is better for efficiency reasons as discussed in Sec. 10.1.2 (The Efficient Frontier). For a given \(G\) we have that

Hence, we may write the risk constraint as

or equivalently

where \(\Q^{n+1}\) is the \((n+1)\)-dimensional quadratic cone. Therefore, problem (1) can be written as

which is a conic quadratic optimization problem that can easily be formulated and solved with *Fusion*. Subsequently we will use the example data

and

This implies

Example code

Listing 16 demonstrates how the basic Markowitz model (3) is implemented using *Fusion*.

```
double BasicMarkowitz
( int n,
std::shared_ptr<ndarray<double, 1>> mu,
std::shared_ptr<ndarray<double, 2>> GT,
std::shared_ptr<ndarray<double, 1>> x0,
double w,
double gamma)
{
Model::t M = new Model("Basic Markowitz"); auto _M = finally([&]() { M->dispose(); });
// Redirect log output from the solver to stdout for debugging.
// M->setLogHandler([](const std::string & msg) { std::cout << msg << std::flush; } );
// Defines the variables (holdings). Shortselling is not allowed.
Variable::t x = M->variable("x", n, Domain::greaterThan(0.0));
// Maximize expected return
M->objective("obj", ObjectiveSense::Maximize, Expr::dot(mu, x));
// The amount invested must be identical to intial wealth
M->constraint("budget", Expr::sum(x), Domain::equalsTo(w + sum(x0)));
// Imposes a bound on the risk
M->constraint("risk", Expr::vstack(gamma, Expr::mul(GT, x)), Domain::inQCone());
// Solves the model.
M->solve();
return dot(mu, x->level());
}
```

The source code should be self-explanatory except perhaps for

```
M->constraint("risk", Expr::vstack(gamma, Expr::mul(GT, x)), Domain::inQCone());
```

where the linear expression

is created using the `Expr.vstack`

operator. Finally, the linear expression must lie in a quadratic cone implying

## 10.1.2 The Efficient Frontier¶

The portfolio computed by the Markowitz model is efficient in the sense that there is no other portfolio giving a strictly higher return for the same amount of risk. An efficient portfolio is also sometimes called a Pareto optimal portfolio. Clearly, an investor should only invest in efficient portfolios and therefore it may be relevant to present the investor with all efficient portfolios so the investor can choose the portfolio that has the desired tradeoff between return and risk.

Given a nonnegative \(\alpha\) the problem

computes an efficient portfolio. Note that the objective is to maximize the expected return while minimizing the standard deviation. The parameter \(\alpha\) specifies the tradeoff between expected return and risk. Ideally the problem (4) should be solved for all values \(\alpha \geq 0\) but in practice it is impossible. Using the example data from Sec. 10.1.1 (The Basic Model), the optimal values of return and risk for several \(\alpha\)s are listed below:

```
Efficient frontier
alpha return risk
0.0000 1.0730e-01 7.2700e-01
0.0100 1.0730e-01 1.6667e-01
0.1000 1.0730e-01 1.6667e-01
0.2500 1.0321e-01 1.4974e-01
0.3000 8.0529e-02 6.8144e-02
0.3500 7.4290e-02 4.8585e-02
0.4000 7.1958e-02 4.2309e-02
0.4500 7.0638e-02 3.9185e-02
0.5000 6.9759e-02 3.7327e-02
0.7500 6.7672e-02 3.3816e-02
1.0000 6.6805e-02 3.2802e-02
1.5000 6.6001e-02 3.2130e-02
2.0000 6.5619e-02 3.1907e-02
3.0000 6.5236e-02 3.1747e-02
10.0000 6.4712e-02 3.1633e-02
```

Example code

Listing 17 demonstrates how to compute the efficient portfolios for several values of \(\alpha\) in *Fusion*.

```
void EfficientFrontier
( int n,
std::shared_ptr<ndarray<double, 1>> mu,
std::shared_ptr<ndarray<double, 2>> GT,
std::shared_ptr<ndarray<double, 1>> x0,
double w,
std::vector<double> & alphas,
std::vector<double> & frontier_mux,
std::vector<double> & frontier_s)
{
Model::t M = new Model("Efficient frontier"); auto M_ = finally([&]() { M->dispose(); });
// Defines the variables (holdings). Shortselling is not allowed.
Variable::t x = M->variable("x", n, Domain::greaterThan(0.0)); // Portfolio variables
Variable::t s = M->variable("s", 1, Domain::unbounded()); // Risk variable
M->constraint("budget", Expr::sum(x), Domain::equalsTo(w + sum(x0)));
// Computes the risk
M->constraint("risk", Expr::vstack(s, Expr::mul(GT, x)), Domain::inQCone());
Expression::t mudotx = Expr::dot(mu, x);
for (double alpha : alphas)
{
// Define objective as a weighted combination of return and risk
M->objective("obj", ObjectiveSense::Maximize, Expr::sub(mudotx, Expr::mul(alpha, s)));
M->solve();
frontier_mux.push_back(dot(mu, x->level()));
frontier_s.push_back((*s->level())[0]);
}
}
```

Note the efficient frontier could also have been computed using the code in Sec. 10.1.1 (The Basic Model) by varying \(\gamma\). However, when the constraints of a *Fusion* model are changed the model has to be rebuilt whereas a rebuild is not needed if only the objective is modified.

## 10.1.3 Improving the Computational Efficiency¶

In practice it is often important to solve the portfolio problem very quickly. Therefore, in this section we discuss how to improve computational efficiency at the modelling stage.

The computational cost is of course to some extent dependent on the number of constraints and variables in the optimization problem. However, in practice a more important factor is the sparsity: the number of nonzeros used to represent the problem. Indeed it is often better to focus on the number of nonzeros in \(G\) see (2) and try to reduce that number by for instance changing the choice of \(G\).

In other words if the computational efficiency should be improved then it is always good idea to start with focusing at the covariance matrix. As an example assume that

where \(D\) is a positive definite diagonal matrix. Moreover, \(V\) is a matrix with \(n\) rows and \(p\) columns. Such a model for the covariance matrix is called a factor model and usually \(p\) is much smaller than \(n\). In practice \(p\) tends to be a small number independent of \(n\), say less than 100.

One possible choice for \(G\) is the Cholesky factorization of \(\Sigma\) which requires storage proportional to \(n(n+1)/2\). However, another choice is

because then

This choice requires storage proportional to \(n+pn\) which is much less than for the Cholesky choice of \(G\). Indeed assuming \(p\) is a constant storage requirements are reduced by a factor of \(n\).

The example above exploits the so-called factor structure and demonstrates that an alternative choice of \(G\) may lead to a significant reduction in the amount of storage used to represent the problem. This will in most cases also lead to a significant reduction in the solution time.

The lesson to be learned is that it is important to investigate how the covariance matrix is formed. Given this knowledge it might be possible to make a special choice for \(G\) that helps reducing the storage requirements and enhance the computational efficiency. More details about this process can be found in [And13].

## 10.1.4 Slippage Cost¶

The basic Markowitz model assumes that there are no costs associated with trading the assets and that the returns of the assets are independent of the amount traded. Neither of those assumptions is usually valid in practice. Therefore, a more realistic model is

where the function

specifies the transaction costs when the holding of asset \(j\) is changed from its initial value.

## 10.1.5 Market Impact Costs¶

If the initial wealth is fairly small and no short selling is allowed, then the holdings will be small and the traded amount of each asset must also be small. Therefore, it is reasonable to assume that the prices of the assets are independent of the amount traded. However, if a large volume of an asset is sold or purchased, the price, and hence return, can be expected to change. This effect is called market impact costs. It is common to assume that the market impact cost for asset \(j\) can be modelled by

where \(m_j\) is a constant that is estimated in some way by the trader. See [GK00] [p. 452] for details. Hence, we have

From [MOSEKApS12] it is known that

where \(\Q_r^3\) is the 3-dimensional rotated quadratic cone. Hence, it follows

Unfortunately this set of constraints is nonconvex due to the constraint

but in many cases the constraint may be replaced by the relaxed constraint

which is equivalent to

For instance if the universe of assets contains a risk free asset then

cannot hold for an optimal solution.

If the optimal solution has the property (9) then the market impact cost within the model is larger than the true market impact cost and hence money are essentially considered garbage and removed by generating transaction costs. This may happen if a portfolio with very small risk is requested because the only way to obtain a small risk is to get rid of some of the assets by generating transaction costs. We generally assume that this is not the case and hence the models (6) and (7) are equivalent.

The above observations lead to

The revised budget constraint

specifies that the initial wealth covers the investment and the transaction costs. Moreover, \(v\) and \(z\) are auxiliary variables that model the market impact cost so that \(z_j \geq | x_j - x_j^0|\) and \(t_j \geq z_j^{3/2}\).

It should be mentioned that transaction costs of the form

where \(p\) and \(q\) are both integers and \(p\geq q\) can be modelled using quadratic cones. See [MOSEKApS12] for details.

Example code

Listing 18 demonstrates how to compute an optimal portfolio when market impact cost are included using *Fusion*.

```
void MarkowitzWithMarketImpact
( int n,
std::shared_ptr<ndarray<double, 1>> mu,
std::shared_ptr<ndarray<double, 2>> GT,
std::shared_ptr<ndarray<double, 1>> x0,
double w,
double gamma,
std::shared_ptr<ndarray<double, 1>> m,
std::vector<double> & xsol,
std::vector<double> & tsol)
{
Model::t M = new Model("Markowitz portfolio with market impact"); auto M_ = finally([&]() { M->dispose(); });
// Defines the variables. No shortselling is allowed.
Variable::t x = M->variable("x", n, Domain::greaterThan(0.0));
// Addtional "helper" variables
Variable::t t = M->variable("t", n, Domain::unbounded());
Variable::t z = M->variable("z", n, Domain::unbounded());
Variable::t v = M->variable("v", n, Domain::unbounded());
// Maximize expected return
M->objective("obj", ObjectiveSense::Maximize, Expr::dot(mu, x));
// Invested amount + slippage cost = initial wealth
M->constraint("budget", Expr::add(Expr::sum(x), Expr::dot(m, t)), Domain::equalsTo(w + sum(x0)));
// Imposes a bound on the risk
M->constraint("risk", Expr::vstack( gamma, Expr::mul(GT, x)),
Domain::inQCone());
// z >= |x-x0|
M->constraint("buy", Expr::sub(z, Expr::sub(x, x0)), Domain::greaterThan(0.0));
M->constraint("sell", Expr::sub(z, Expr::sub(x0, x)), Domain::greaterThan(0.0));
// t >= z^1.5, z >= 0.0. Needs two rotated quadratic cones to model this term
M->constraint("ta", Expr::hstack(v, t, z), Domain::inRotatedQCone());
M->constraint("tb", Expr::hstack(z, Expr::constTerm(n, 1.0 / 8.0), v),
Domain::inRotatedQCone());
M->solve();
xsol.resize(n);
tsol.resize(n);
auto xlvl = x->level();
auto tlvl = t->level();
std::copy(xlvl->flat_begin(), xlvl->flat_end(), xsol.begin());
std::copy(tlvl->flat_begin(), tlvl->flat_end(), tsol.begin());
}
```

The major new features compared to the previous examples are

```
M->constraint("ta", Expr::hstack(v, t, z), Domain::inRotatedQCone());
```

and

```
M->constraint("tb", Expr::hstack(z, Expr::constTerm(n, 1.0 / 8.0), v),
Domain::inRotatedQCone());
```

In the first line the variables \(v\), \(t\) and \(z\) are stacked horizontally which corresponds to creating a list of linear expressions where the \(j\)‘th element has the form

and finally each linear expression is constrained to a rotated quadratic cone i.e.

Similarly the second line is equivalent to the constraint

or equivalently

## 10.1.6 Transaction Costs¶

Now assume there is a cost associated with trading asset \(j\) given by

Here \(\Delta x_j\) is the change in the holding of asset \(j\) i.e.

Hence, whenever asset \(j\) is traded we pay a fixed setup cost \(f_j\) and a variable cost of \(g_j\) per unit traded. Given the assumptions about transaction costs in this section problem (5) may be formulated as

(11)¶\[\begin{split}\begin{array}{lrcll} \mbox{maximize} & \mu^T x & & &\\ \mbox{subject to} & e^T x + \sum_{j=1}^n (f_j y_j + g_j z_j) & = & w + e^T x^0, &\\ & [\gamma;G^T x] & \in & \Q^{n+1}, & \\ & z_j & \geq & x_j - x_j^0, & j=1,\ldots,n,\\ & z_j & \geq & x_j^0 - x_j, & j=1,\ldots,n,\\ & z_j & \leq & U_j y_j, & j=1,\ldots,n,\\ & y_j & \in & \{0,1\}, & j=1,\ldots,n, \\ & x & \geq & 0. & \end{array}\end{split}\]

First observe that

Here \(U_j\) is some a priori chosen upper bound on the amount of trading in asset \(j\) and therefore if \(z_j>0\) then \(y_j = 1\) has to be the case. This implies that the transaction costs for the asset \(j\) is given by

\[f_j y_j + g_j z_j.\]

Example code

The following example code demonstrates how to compute an optimal portfolio when transaction costs are included.

```
std::shared_ptr<ndarray<double, 1>> MarkowitzWithTransactionsCost
( int n,
std::shared_ptr<ndarray<double, 1>> mu,
std::shared_ptr<ndarray<double, 2>> GT,
std::shared_ptr<ndarray<double, 1>> x0,
double w,
double gamma,
std::shared_ptr<ndarray<double, 1>> f,
std::shared_ptr<ndarray<double, 1>> g)
{
// Upper bound on the traded amount
std::shared_ptr<ndarray<double, 1>> u(new ndarray<double, 1>(shape_t<1>(n), w + sum(x0)));
Model::t M = new Model("Markowitz portfolio with transaction costs"); auto M_ = finally([&]() { M->dispose(); });
// Defines the variables. No shortselling is allowed.
Variable::t x = M->variable("x", n, Domain::greaterThan(0.0));
// Addtional "helper" variables
Variable::t z = M->variable("z", n, Domain::unbounded());
// Binary varables
Variable::t y = M->variable("y", n, Domain::binary());
// Maximize expected return
M->objective("obj", ObjectiveSense::Maximize, Expr::dot(mu, x));
// Invest amount + transactions costs = initial wealth
M->constraint("budget", Expr::add(Expr::add(Expr::sum(x), Expr::dot(f, y)), Expr::dot(g, z)),
Domain::equalsTo(w + sum(x0)));
// Imposes a bound on the risk
M->constraint("risk", Expr::vstack( gamma, Expr::mul(GT, x)),
Domain::inQCone());
// z >= |x-x0|
M->constraint("buy", Expr::sub(z, Expr::sub(x, x0)), Domain::greaterThan(0.0));
M->constraint("sell", Expr::sub(z, Expr::sub(x0, x)), Domain::greaterThan(0.0));
// Alternatively, formulate the two constraints as
//M->constraint("trade", Expr::hstack(z,Expr::sub(x,x0)), Domain::inQCone());
// Consraints for turning y off and on. z-diag(u)*y<=0 i.e. z_j <= u_j*y_j
M->constraint("y_on_off", Expr::sub(z, Expr::mul(Matrix::diag(u), y)), Domain::lessThan(0.0));
// Integer optimization problems can be very hard to solve so limiting the
// maximum amount of time is a valuable safe guard
M->setSolverParam("mioMaxTime", 180.0);
M->solve();
return x->level();
}
```