7 Benchmark relative portfolio optimization¶
We often measure the performance of portfolios relative to a benchmark. The benchmark is typically a market index representing an asset class or some segment of the market, and its composition is assumed known. Benchmark relative portfolio management can have two types of strategies: active or passive. The passive strategy tries to replicate the benchmark, i. e. match its performance as closely as possible. In contrast, active management seeks to consistently beat (outperform) the benchmark (after management fees). This section is based on [CJPT18].
7.1 Active return¶
Let the portfolio and benchmark weights be
where
The active portfolio risk or tracking error will be the standard deviation of active return:
where
In general we can construct a class of functions for the purpose of measuring tracking error differently, from any deviation measure. See in Sec. 8 (Other risk measures).
7.2 Factor model on active returns¶
In the active return there is still a systematic component attributable to the benchmark. We can account for that using a single factor model. First we create a factor model for security returns:
where
where
This factor model allows us to decompose the portfolio return into a systematic component, which is explained by the benchmark, and a residual component, which is specific to the portfolio:
where
Finally, by subtracting the benchmark return from both sides we can write the active return as:
where
where
We must note that if a different factor model is used to forecast risk and alpha (which is often the case in practice), there could be factors included in one model but not included in the other model. The optimizer will interpret such misalignments as factor return without risk or factor risk without return and it will exploit them as false opportunities, leading to unintended biases in the results. Risk and alpha factors are considered aligned if alpha can be written as a linear combination of the risk factors. In case of a misalignment, there are different approaches to mitigate it [KS13].
7.3 Optimization¶
An active investment strategy would try to gain higher portfolio alpha
where
where we must estimate the optimal shrinkage factor
7.4 Extensions¶
7.4.1 Variance constraint¶
Optimization of alpha with constraining only the tracking error can increase total portfolio risk. According to [Jor04] it can be helpful to constrain also the total portfolio variance, especially in cases when the benchmark portfolio is relatively inefficient:
7.4.2 Passive management¶
In the case of passive management, the goal of optimization is to construct a benchmark tracking portfolio, i. e., to have a tracking error as small as possible, given the constraints. This usually also means that
7.4.3 Linear tracking error measures¶
The tracking error is one possible measure of closeness between the portfolio and the benchmark. However, there can be other measures [RWZ99]. For example, we can use the mean absolute deviation (MAD) measure, i. e.,
where
If the investor perceives risk as portfolio return being below the benchmark return, we can also use downside deviation measures. One example is the lower partial moment
Note that for a symmetric portfolio return distribution, this will be equivalent to the MAD model.
Linear models might be also preferable because of their more intuitive interpretation. By measuring the tracking error according to a linear function, the measurement unit of the objective function is percentage instead of squared percentage.
7.5 Example¶
Here we show an example of a benchmark relative optimization problem. The benchmark will be the equally weighted portfolio of the eight stocks from the previous examples, therefore
# Create benchmark
df_prices['bm'] = df_prices.iloc[:-2, 0:8].mean(axis=1)
Then we follow the same Monte Carlo procedure as in Sec. 5.5.1 (Single factor model), just with the benchmark instead of the market factor. This will yield scenarios of linear returns on the investment time horizon of
In the Fusion model, we make the following modifications:
We define the active holdings variable
by# Active holdings xa = x - xbm
We modify the constraint on risk to be the constraint on tracking error:
# Conic constraint for the portfolio variance M.constraint('risk', Expr.vstack(s, 1, G.T @ xa), Domain.inRotatedQCone())
We also specify bounds on the active holdings and on the portfolio active beta:
# Constraint on active holdings M.constraint('bound-h', xa, Domain.inRange(lh, uh)) # Constraint on portfolio active beta port_act_beta = x.T @ B - 1 M.constraint('bound-b', port_act_beta, Domain.inRange(lb, ub))
Finally, we modify the objective to maximize the portfolio alpha:
# Objective (quadratic utility version) delta = M.parameter() M.objective('obj', ObjectiveSense.Maximize, x.T @ a - delta * s)
The complete Fusion model of the optimization problem (7.1) will then be
def EfficientFrontier(N, a, B, G, xbm, deltas, uh, ub, lh, lb):
with Model("Frontier") as M:
# Variables
# The variable x is the fraction of holdings in each security.
# Non-negative, no short-selling
x = M.variable("x", N, Domain.greaterThan(0.0))
# Active holdings
xa = x - xbm
# Portfolio variance term in the objective.
s = M.variable("s", 1, Domain.unbounded())
# Budget constraint
M.constraint('budget_x', Expr.sum(x) == 1.0)
# Constraint on active holdings
M.constraint('bound-h', xa, Domain.inRange(lh, uh))
# Constraint on portfolio active beta
port_act_beta = x.T @ B - 1
M.constraint('bound-b', port_act_beta, Domain.inRange(lb, ub))
# Conic constraint for the portfolio variance
M.constraint('risk', Expr.vstack(s, 1, G.T @ xa),
Domain.inRotatedQCone())
# Objective (quadratic utility version)
delta = M.parameter()
M.objective('obj', ObjectiveSense.Maximize, x.T @ a - delta * s)
# Create DataFrame to store the results. SPY is removed.
columns = ["delta", "obj", "return", "risk"] + \
df_prices.columns[:-1].tolist()
df_result = pd.DataFrame(columns=columns)
for d in deltas:
# Update parameter
delta.setValue(d);
# Solve optimization
M.solve()
# Save results
portfolio_return = a @ x.level()
portfolio_risk = np.sqrt(2 * s.level()[0])
row = pd.Series([d, M.primalObjValue(), portfolio_return,
portfolio_risk] + \
list(x.level()), index=columns)
df_result = pd.concat([df_result, row.to_frame().T],
ignore_index=True)
return df_result
We give the input parameters and compute the efficient frontier using the following code:
deltas = np.logspace(start=-0.5, stop=2, num=20)[::-1]
xbm = np.ones(N) / N
uh = np.ones(N) * 0.5
lh = -np.ones(N) * 0.5
ub = 0.5
lb = -0.5
df_result = EfficientFrontier(N, a, B, G, xbm, deltas, uh, ub, lh, lb)
mask = df_result < 0
mask.iloc[:, :2] = False
df_result[mask] = 0
df_result
On Fig. 7.1 we plot the efficient frontier and on Fig. 7.2 the portfolio composition. On the latter we see that as the tracking error decreases, the portfolio gets closer to the benchmark, i. e., the equal-weighted portfolio.

Fig. 7.1 The benchmark relative efficient frontier.¶

Fig. 7.2 Portfolio composition