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 \(N\) securities and a cash account (labeled as security \(N+1\)), and we are at the beginning of period \(t\). Let \(\mathbf{h}_t\) be the portfolio in dollars in period \(t\), and let \(v_t = \mathbf{1}^\mathsf{T}\mathbf{h}_t\) be its total value. Then we can normalize \(\mathbf{h}_t\) to get the portfolio in weights as \(\mathbf{x}_t = \mathbf{h}_t / v_t\).

Let \(\mathbf{u}_t\) be the dollar trade vector and \(\mathbf{z}_t\) be the normalized trade vector in period \(t\). Then the post-trade portfolio will be \(\mathbf{h}_t + \mathbf{u}_t\) or \(\mathbf{x}_t + \mathbf{z}_t\). In the following, we will use only the normalized quantities.

11.1.2 Problem statement

At period \(t\), we wish to determine the trade vector \(\mathbf{z}_t\). The starting portfolio \(\mathbf{x}_t\) is known. All other parameters (future quantities) have to be estimated. These are the mean return \(\EMean_t\), the risk function \(\ERisk_t\), the trading cost function \(\EPhi_t^\mathrm{trade}\), and the holding cost function \(\EPhi_t^\mathrm{hold}\).

The single-period optimization problem will be

(11.1)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \EMean_t^\mathsf{T}(\mathbf{x}_t + \mathbf{z}_t) - \delta_t\ERisk_t(\mathbf{x}_t + \mathbf{z}_t) - & & \\ & - \EPhi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) - \EPhi_t^\mathrm{trade}(\mathbf{z}_t) & &\\ \mbox{subject to} & \mathbf{1}^\mathsf{T}\mathbf{z}_t + \EPhi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) + \EPhi_t^\mathrm{trade}(\mathbf{z}_t) & = & 0,\\ & \mathbf{z}_t & \in & \mathcal{Z}_t,\\ & \mathbf{x}_t + \mathbf{z}_t & \in & \mathcal{X}_t, \end{array}\end{split}\]

where \(\delta_t\) is the risk aversion parameter, and \(\mathcal{Z}_t\) and \(\mathcal{X}_t\) represent the set of trading and the set of holding constraints respectively.

11.1.3 Post-trade computations

After (11.1) is solved, we obtain the optimal solution \(\mathbf{z}^*_t\) together with the optimal cash component \(z^*_{t,N+1}\). However, the latter might be inaccurate, because it is determined using estimated costs. After knowing the realized cost functions \(\phi_t^\mathrm{hold}\) and \(\phi_t^\mathrm{trade}\), we can use them to determine the accurate cash component from the self-financing condition:

\[z_{t,N+1} = -\mathbf{1}^\mathsf{T}\mathbf{z}^*_{t,1..N} - \phi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}^*_t) - \phi_t^\mathrm{trade}(\mathbf{z}^*_t).\]

However, this more precise cash quantity may result in small constraint violations.

Finally, we hold the post-trade portfolio until the end of period \(t\). Then we get the realized return \(\mu_t\), and can compute the new total portfolio value \(v_{t+1}\). This allows us to compute the realized portfolio return using the self financing condition:

(11.2)\[R_{\mathbf{x},t} = \frac{v_{t+1} - v_t}{v_t} = \mu^\mathsf{T}(\mathbf{x}_t + \mathbf{z}_t) - \phi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) - \phi_t^\mathrm{trade}(\mathbf{z}_t)\]

The portfolio composition at the end of period \(t\) can be computed using the realized return \(\mu_t\):

(11.3)\[\mathbf{x}_{t+1} = \frac{1}{1 + R_{\mathbf{x},t}} (\mathbf{1} + \mu_t) \circ (\mathbf{x}_t + \mathbf{z}_t).\]

This dynamics equation ensures that if \(\mathbf{1}^\mathsf{T}\mathbf{x}_t = 1\) then we have \(\mathbf{1}^\mathsf{T}\mathbf{x}_{t+1} = 1\).

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 \(\mathbf{1}^\mathsf{T}\mathbf{z}_t = 0\). In this case the costs are still taken into account in the objective as penalty terms. The benefit will be that the equation \(\mathbf{1}^\mathsf{T}(\mathbf{x}_t + \mathbf{z}_t) = 1\) 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 \(\mathbf{x}_{t+1} = \mathbf{x}_t + \mathbf{z}_t\).

11.1.5 Holding constraints

Holding constraints are imposed on the post-trade portfolio \(\mathbf{x}_t + \mathbf{z}_t\), that is held until the end of period \(t\). Such constraints can be for example the long only constraint, leverage, position size limit, concentration limit, minimum cash balance, factor neutrality, etc.

Some of these constraints can actually be approximations of constraints on \(\mathbf{x}_{t+1}\). When we assume small returns, these approximations will also become more accurate.

11.1.6 Trading constraint

Trading constraints are imposed on the trade vector \(\mathbf{z}_t\). Such constraints can be for example the turnover, trading limits, restrictions on the direction of trade, etc.

Here we assume the trading cost function \(\phi_t^\mathrm{trade}\) to be separable, i. e., \(\phi_t^\mathrm{trade}(\mathbf{x}) = \sum_{i=1}^N\phi_{t, i}^\mathrm{trade}(x_i)\). We also assume that there are no transaction costs associated with the cash account.

11.2 Multi-period model

Here we extend the model (11.1) further by considering a planning horizon of \(H\) periods. In this context, we would like to find the trade vector \(\mathbf{z}_0\) for the first period by optimizing \(\mathbf{z}_t\) for all periods \(t=0,\dots,H-1\). [1] The initial portfolio weight vector \(\mathbf{x}_0\) is known. The rest of the (future) input quantities \(\EMean_t\), \(\ERisk_t\), \(\EPhi_t^\mathrm{trade}\), and \(\EPhi_t^\mathrm{hold}\) have to be estimated for all periods. Then we have to solve the following problem:

(11.4)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \sum_{t=0}^{H-1}\EMean_t^\mathsf{T}(\mathbf{x}_t + \mathbf{z}_t) - \delta_t\ERisk_t(\mathbf{x}_t + \mathbf{z}_t) - & & \\ & -\EPhi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) - \EPhi_t^\mathrm{trade}(\mathbf{z}_t) & &\\ \mbox{subject to} & \mathbf{1}^\mathsf{T}\mathbf{z}_t + \EPhi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) + \EPhi_t^\mathrm{trade}(\mathbf{z}_t) & = & 0,\\ & \mathbf{z}_t & \in & \mathcal{Z}_t,\\ & \mathbf{x}_t + \mathbf{z}_t & \in & \mathcal{X}_t,\\ & \EMean^\mathsf{T}(\mathbf{x}_t + \mathbf{z}_t) - \EPhi_t^\mathrm{hold}(\mathbf{x}_t + \mathbf{z}_t) - \EPhi_t^\mathrm{trade}(\mathbf{z}_t) & = & R_{\mathbf{x},t},\\ & 1 / (1 + R_{\mathbf{x},t}) (\mathbf{1} + \mu_t) \circ (\mathbf{x}_t + \mathbf{z}_t) & = & \mathbf{x}_{t+1},\\ \end{array}\end{split}\]

where we repeat all constraints for all \(t = 0,\dots,H-1\).

The multi-period objective is the sum of the single-period objectives for each period \(t\). Here we implicitly assumed that the returns are independent random variables, so the variance of their sum is the sum of the variances.

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 \(\mathbf{x}_{t+1} = \mathbf{x}_t + \mathbf{z}_t\). This also allows us to eliminate the trade variables \(\mathbf{z}_t\) from the model, and arrive at a simpler form:

(11.5)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \sum_{t=0}^{H-1}\EMean_{t}^\mathsf{T}\mathbf{x}_{t+1} - \delta_t\ERisk_{t}(\mathbf{x}_{t+1}) - & & \\ & -\EPhi_{t}^\mathrm{hold}(\mathbf{x}_{t+1}) - \EPhi_{t}^\mathrm{trade}(\mathbf{x}_{t+1} - \mathbf{x}_{t}) & &\\ \mbox{subject to} & \mathbf{1}^\mathsf{T}\mathbf{x}_{t+1} + \EPhi_{t}^\mathrm{hold}(\mathbf{x}_{t+1}) + \EPhi_{t}^\mathrm{trade}(\mathbf{x}_{t+1} - \mathbf{x}_{t}) & = & \mathbf{1}^\mathsf{T}\mathbf{x}_{t},\\ & \mathbf{x}_{t+1} - \mathbf{x}_{t} & \in & \mathcal{Z}_{t},\\ & \mathbf{x}_{t+1} & \in & \mathcal{X}_{t},\\ \end{array}\end{split}\]

where we repeat all constraints for all \(t = 0,\dots,H-1\).

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.

  • \(100\%\) 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:

(11.6)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \sum_{t=0}^{H-1}\EMean_{t}^\mathsf{T}\mathbf{x}_{t+1} - \delta_t\ERisk_{t}(\mathbf{x}_{t+1}) - & & \\ & -\EPhi_{t}^\mathrm{hold}(\mathbf{x}_{t+1}) - \EPhi_{t}^\mathrm{trade}(\mathbf{x}_{t+1} - \mathbf{x}_{t}) & &\\ \mbox{subject to} & \mathbf{1}^\mathsf{T}\mathbf{x}_{t+1} + \EPhi_{t}^\mathrm{hold}(\mathbf{x}_{t+1}) + \EPhi_{t}^\mathrm{trade}(\mathbf{x}_{t+1} - \mathbf{x}_{t}) & = & \mathbf{1}^\mathsf{T}\mathbf{x}_{t},\\ & \mathbf{x}_{t+1} - \mathbf{x}_{t} & \in & \mathcal{Z}_{t},\\ & \mathbf{x}_{t+1} & \in & \mathcal{X}_{t},\\ & \mathbf{x}_{H} & = & \mathbf{x}^\mathrm{term},\\ \end{array}\end{split}\]

where \(\mathbf{x}^\mathrm{term}\) is the desired terminal portfolio, and we repeat all constraints for all \(t = 0,\dots,H-1\).

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: \(\ERisk_{t}(\mathbf{x}_{t+1}) = \mathbf{x}_{t+1}^\mathsf{T}\ECov_t\mathbf{x}_{t+1}\).

  • The trading cost function will include a linear term, and a market impact term: \(\EPhi_{t}^\mathrm{trade}(\mathbf{x}_{t+1} - \mathbf{x}_{t}) = \sum_{i=1}^N a_{t,i}|x_{t+1,i}-x_{t,i}| + \tilde{b}_{t,i}|x_{t+1,i}-x_{t,i}|^{3/2}\). The \(a_{t,i}\) are the coefficients of the linear cost term, and the \(b_{t,i}\) are the coefficients of the market impact cost term. \(b_{t,i} = \tilde{b}_{t,i}\sigma_{t,i}/\left(\frac{q_{t,i}}{V_t}\right)^{1/2}\), where \(\tilde{b}_{t,i} = 1\), \(\sigma_{t,i}\) is the volatility of security \(i\) in period \(t\), and \(\frac{q_{t,i}}{V_t}\) is the normalized dollar volume of security \(i\) in period \(t\).

  • There will be no holding cost term: \(\EPhi_{t}^\mathrm{hold}(\mathbf{x}_{t+1}) = 0\).

  • The set of trading constraints will be empty: \(\mathcal{Z}_{t} = \{\}\).

  • The set of holding constraints will contain the long only constraint: \(\mathcal{X}_{t} = \{\mathbf{x}_{t+1} \geq 0\}\).

Based on the above, we will solve the following multiperiod optimization problem:

(11.7)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \sum_{t=0}^{H-1}\EMean_t^\mathsf{T}\mathbf{x}_{t+1} - \delta_t \mathbf{x}_{t+1}^\mathsf{T}\ECov_t\mathbf{x}_{t+1} - & & \\ & -\left(\sum_{i=1}^N a_{t,i}|x_{t+1,i}-x_{t,i}| + b_{t,i}|x_{t+1,i}-x_{t,i}|^{3/2}\right) & &\\ \mbox{subject to} & \mathbf{1}^\mathsf{T}(\mathbf{x}_{t+1}-\mathbf{x}_{t})+ \left(\sum_{i=1}^N a_{t,i}|x_{t+1,i}-x_{t,i}| + \tilde{b}_{t,i}|x_{t+1,i}-x_{t,i}|^{3/2}\right) & = & 0,\\ & \mathbf{x}_{t+1} & \geq & 0,\\ \end{array}\end{split}\]

where we repeat all constraints for all \(t = 0,\dots,H-1\).

Then we rewrite (11.7) into conic form:

(11.8)\[\begin{split}\begin{array}{lrcl} \mbox{maximize} & \sum_{t=0}^{H-1}\EMean_t^\mathsf{T}\mathbf{x}_{t+1} - \delta_t s_{t} - & & \\ & -\left(\sum_{i=1}^N a_{t,i}v_{t,i} + b_{t,i}w_{t,i}\right) & &\\ \mbox{subject to} & (s_{t}, 0.5, \mathbf{G}_{t}^\mathsf{T}\mathbf{x}_{t+1}) & \in & \Q_\mathrm{r}^{N+2},\\ & |x_{t+1}-x_{t}| & \leq & v_{t},\\ & (w_{t,i}, 1, x_{t+1,i}-x_{t,i}) & \in & \mathcal{P}_3^{2/3,1/3},\quad i = 1,\dots,N\\ & \mathbf{1}^\mathsf{T}\mathbf{x}_{t+1} + \left(\sum_{i=1}^N a_{t,i}v_{t,i} + b_{t,i}w_{t,i}\right) & = & \mathbf{1}^\mathsf{T}\mathbf{x}_{t},\\ & \mathbf{x}_{t+1} & \geq & 0,\\ \end{array}\end{split}\]

where we repeat all constraints for all \(t = 0,\dots,H-1\).

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(Expr.sub(z, x), Domain.greaterThan(0.0))
    M.constraint(Expr.add(z, x), Domain.greaterThan(0.0))

def norm1(M, x, t):
    z = M.variable(x.getSize(), Domain.greaterThan(0.0))
    absval(M, x, z)
    M.constraint(Expr.sub(Expr.sum(z), t), Domain.equalsTo(0.0))

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:
        # Output settings
        M.setLogHandler(sys.stdout)

        # 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), Domain.equalsTo(np.ones(T)))

        # Objective
        obj_terms = []
        for t in range(T):
            xt = x.pick([[i, t] for i in range(N)])
            term_t = Expr.add([
                Expr.dot(m[t], xt),
                Expr.mul(-delta[t], s.index(t)),
                Expr.add([Expr.mul(-a[i,t], v.index(i,t))
                    for i in range(N)]),
                Expr.add([Expr.mul(-b[i,t], w.index(i,t))
                    for i in range(N)])
            ])
            obj_terms.append(term_t)
        M.objective("obj", ObjectiveSense.Maximize, Expr.add(obj_terms))

        # Conic constraints related to the objective
        for t in range(T):
            xt = x.pick([[i, t] for i in range(N)])
            vt = v.pick([[i, t] for i in range(N)])
            wt = w.pick([[i, t] for i in range(N)])
            xtprev = x_0 if t == 0 \
                else x.pick([[i, t - 1] for i in range(N)])
            xtdiff = Expr.sub(xt, xtprev)
            M.constraint(f'risk_{t}',
                Expr.vstack(s.index(t), 0.5, Expr.mul(G[t].T, xt)),
                Domain.inRotatedQCone()
            )
            absval(M, xtdiff, vt)
            M.constraint(f'market_impact_{t}',
                Expr.hstack(wt, 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 \(\mathbf{G}\) such that \(\ECov=\mathbf{G}\mathbf{G}^\mathsf{T}\) for all periods, using Cholesky factorization.

# 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 = 10\) for each period.

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