11 Multi-period portfolio optimization¶
Multi-period portfolio optimization is an extension of the single-period MVO problem. Its objective is to select a sequence of trades over a series of time periods. In contrast to repeated single period optimizations, it takes into account recourse and updated information while planning trades for the subsequent period.
While multi period models are more complex, they can help us to naturally handle various inter-temporal effects:
Different time scales: We can model multiple, possibly conflicting return predictions on different time scales.
Transaction costs: Considers whether our trades in the current period put us in favourable position to trade in future periods.
Time-varying constraints: For example, reducing the leverage bound will likely incur lower trading cost if done over several period.
Time-varying return forecasts: For example, we may assign an exponential decay-rate to the return predictions.
Future events: We can act on or exploit known future events that will change risk, liquidity, or volume, etc.
Dynamic programming is a common method to deal with multi-period problems, however it is challenging due to the “curse of dimensionality”. Therefore in practice, we use approximation methods, like approximate dynamic programming. Fortunately, the resulting performance loss is typically negligible in practical scenarios.
In this chapter we describe a practical multi-period portfolio optimization problem based on [BBD+17].
11.1 Single-period model¶
First we show the optimization problem for a single period to introduce the model elements and notation.
11.1.1 Definitions¶
Suppose we have
Let
11.1.2 Problem statement¶
At period
The single-period optimization problem will be
where
11.1.3 Post-trade computations¶
After (11.1) is solved, we obtain the optimal solution
However, this more precise cash quantity may result in small constraint violations.
Finally, we hold the post-trade portfolio until the end of period
The portfolio composition at the end of period
This dynamics equation ensures that if
11.1.4 Simplified problem¶
We can make two approximations to simplify problem (11.1).
Cost terms: We can omit the cost terms from the self-financing condition, reducing it to
. In this case the costs are still taken into account in the objective as penalty terms. The benefit will be that the equation will hold exactly in all cases.Returns: If returns are very small in any period, then we can assume them to be zero. Then we can approximate the dynamics equation (11.3) to be
.
11.1.5 Holding constraints¶
Holding constraints are imposed on the post-trade portfolio
Some of these constraints can actually be approximations of constraints on
11.1.6 Trading constraint¶
Trading constraints are imposed on the trade vector
Here we assume the trading cost function
11.2 Multi-period model¶
Here we extend the model (11.1) further by considering a planning horizon of
where we repeat all constraints for all
The multi-period objective is the sum of the single-period objectives for each period
Unfortunately (11.4) is not a convex problem, because of the dynamic equation. Therefore we have to make a simplification.
11.2.1 Simplified model¶
We can apply the simplification of returns discussed in section Sec. 11.1.4 (Simplified problem) and use the approximate dynamics equation
where we repeat all constraints for all
Note that this simplification does not mean we disregard returns, we only approximate the portfolio dynamics between periods. The returns are still taken into account in the objective.
11.2.2 Extension¶
An extension of (11.5) is to specify the terminal portfolio after the last period. Choice for the terminal portfolio could depend on the use case. Some reasonable choices could be
The predicted benchmark portfolio, which ensures that the optimal portfolio we get will not deviate much from the benchmark.
cash, which ensures that the optimal portfolio we get will not be expensive to liquidate.
Since we never execute the optimal trades for all periods, the purpose of multiperiod planning with a terminal constraint is to avoid moving towards undesired positions, not to actually end up holding the terminal portfolio.
Problem (11.5) with the terminal constraint added will look like:
where
11.3 Example¶
Here we show a code example of the multiperiod portfolio optimization problem (11.5). In this setup, we will use the following specification:
The risk function will be the variance:
.The trading cost function will include a linear term, and a market impact term:
. The are the coefficients of the linear cost term, and the are the coefficients of the market impact cost term. , where , is the volatility of security in period , and is the normalized dollar volume of security in period .There will be no holding cost term:
.The set of trading constraints will be empty:
.The set of holding constraints will contain the long only constraint:
.
Based on the above, we will solve the following multiperiod optimization problem:
where we repeat all constraints for all
Then we rewrite (11.7) into conic form:
where we repeat all constraints for all
Finally, we implement (11.7) in Fusion API. The following auxiliary functions encapsulate specific parts of modeling, and help us to write a clearer code:
def absval(M, x, z):
M.constraint(z >= x)
M.constraint(z >= -x)
def norm1(M, x, t):
z = M.variable(x.getSize(), Domain.greaterThan(0.0))
absval(M, x, z)
M.constraint(Expr.sum(z) == t)
Next we present the Fusion model, built inside a function, that we can easily call later.
def multiperiod_mvo(N, T, m, G, x_0, delta, a, b):
with Model("Multiperiod") as M:
# Variables
# - Portfolio
x = M.variable("x", [N, T], Domain.greaterThan(0.0))
# - Risk
s = M.variable("s", T)
# - Linear transaction cost
v = M.variable("v", [N, T])
# - Market impact cost
w = M.variable("w", [N, T])
# Constraint
M.constraint("budget", Expr.sum(x, 0) == np.ones(T))
# Objective
M.objective("obj", ObjectiveSense.Maximize,
Expr.add([ x[:, t].T @ m[t] - delta[t] * s[t] - \
v[:, t].T @ a[:, t] - w[:, t].T @ b[:, t]
for t in range(T)]))
# Conic constraints related to the objective
for t in range(T):
xt = x[:, t]
xtprev = x_0 if t == 0 else x[:, t - 1]
xtdiff = xt - xtprev
M.constraint(f'risk_{t}',
Expr.flatten(Expr.vstack(s[t], 0.5, G[t].T @ xt)),
Domain.inRotatedQCone()
)
absval(M, xtdiff, v[:, t])
M.constraint(f'market_impact_{t}',
Expr.hstack(w[:, t], Expr.constTerm(N, 1.0), xtdiff),
Domain.inPPowerCone(2 / 3)
)
# Solve the problem
M.solve()
# Get the solution values
x_value = x.level().reshape(N, T)
return x_value
Next, we define the parameters. Here we assume that we have an estimate of the average daily volume and daily volatility (std. dev.) for all periods.
# Number of securities
N = 8
# Number of periods
T = 10
# Initial weights
x_0 = np.array([1] * N) / N
portfolio_value = 10**8
# Transaction cost
a = 0.05 * np.ones((N, T))
# Market impact
beta = 3 / 2
b = 1
# The variable vol contains the volume estimates
rel_volume = [v / portfolio_value for v in vol] # Relative volume.
# The variable vty contains the volatility estimates
impact_coef = \
np.vstack([(b * v / r**(beta - 1)).to_numpy()
for v, r in zip(vty, rel_volume)]).T
Assuming also that we have a list of estimates of the yearly mean return and covariance matrix for each trading period, we compute the matrix
# S is the list of covariance estimates
G = [np.linalg.cholesky(s) for s in S]
Finally, we run the optimization with the risk aversion parameter
delta = np.array([10] * T)
# m is the list of mean return estimates
x = multiperiod_mvo(N, T, m, G, x_0, delta, a, impact_coef)
Footnotes