Chapter 8 Risk parity portfolio
8.1 Motivation
Since the global financial crisis in 2008, risk management has particularly become more important than performance management in portfolio optimization.
This chapter is for improvement of the last issue of Markowitz’s portfolio: it only considers the risk of the portfolio as a whole and ignores the risk diversification (i.e., concentrates risk too much in few assets, this was observed in the 2008 financial crisis): solution is the risk parity portfolio.
The alternative risk parity portfolio design has been receiving significant attention from both the theoretical and practical sides because it
- diversifies the risk, instead of the capital, among the assets
- is less sensitive to parameter estimation errors.
Today, pension funds and institutional investors are using this approach in the development of smart indexing and the redefinition of long-term investment policies.
The risk parity approach asserts that when asset allocations are adjusted to the same risk level, the portfolio can achieve a higher Sharpe ratio and can be more resistant to market downturns.
Facts: Lack of diversification of Markowitz portfolio
"2014-01-01"
from = "2016-12-31"
to = c("AAPL", "AMD", "ADI", "ABBV", "AEZS", "A", "APD", "AA","CF")
tickers <- get_data(tickers, from, to)
prices <- ncol(prices)
N <- nrow(prices)
T <- round(T*0.7)
T_trn <- diff(log(prices)) %>% na.omit()
X <-
get_return_and_split(ratio=0.7, prices=prices)
temp <- temp$log_trn
X_log_trn <- temp$log_tst
X_log_tst <- temp$lin_trn
X_lin_trn <- temp$lin_tst
X_lin_tst <- temp$X_lin
X_lin <- temp$X_log
X_log <- colMeans(X)
mu <- cov(X)
Sigma <-plot(prices/rep(prices[1, ], each = nrow(prices)), col = rainbow10equal, legend.loc = "topleft",main = "Normalized prices")
# define portfolio formulations
function(mu, Sigma, lmd = 0.5) {
portolioMarkowitz <- Variable(nrow(Sigma))
w <- Problem(Maximize(t(mu) %*% w - lmd*quad_form(w, Sigma)),
prob <-constraints = list(w >= 0, sum(w) == 1))
solve(prob)
result <-return(as.vector(result$getValue(w)))
}
function(Sigma) {
portolioGMVP <- Variable(nrow(Sigma))
w <- Problem(Minimize(quad_form(w, Sigma)),
prob <-constraints = list(w >= 0, sum(w) == 1))
solve(prob)
result <-return(as.vector(result$getValue(w)))
}
# compute portfolios
portolioMarkowitz(mu, Sigma)
w_Markowitz <- portolioGMVP(Sigma) w_GMVP <-
# put together all portfolios
cbind(w_GMVP, w_Markowitz)
w_all <-rownames(w_all) <- colnames(X_lin)
colnames(w_all) <- c("GMVP", "Markowitz MVP")
# plot
barplot(t(w_all), col = rainbow8equal[1:2],
main = "Portfolio allocation", xlab = "stocks", ylab = "dollars", beside = TRUE,
legend = colnames(w_all)) #args.legend = list(x = "topleft", inset = 0.04)
# compute returns of all portfolios
xts(X_lin %*% w_all, index(X_lin))
ret_all <- ret_all[1:T_trn, ]
ret_all_trn <- ret_all[-c(1:T_trn), ]
ret_all_tst <-
# performance in-sample
t(table.AnnualizedReturns(ret_all_trn))
#> Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
#> GMVP 0.0724 0.1774 0.4084
#> Markowitz MVP -0.1342 0.3500 -0.3834
# performance out-of-sample
t(table.AnnualizedReturns(ret_all_tst))
#> Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
#> GMVP 0.3003 0.1420 2.1140
#> Markowitz MVP 3.0480 0.5314 5.7361
chart.CumReturns(ret_all, main = "Performance of different portfolios",
{ wealth.index = TRUE, legend.loc = "topleft", colorset = rich8equal)
addEventLines(xts("training", index(X_lin[T_trn])), srt=90, pos=2, lwd = 2, col = "darkblue") }
chart.Drawdown(ret_all, main = "Drawdown of different portfolios",
{ legend.loc = "bottomleft", colorset = rich8equal)
addEventLines(xts("training", index(X_lin[T_trn])), srt=90, pos=2, lwd = 2, col = "darkblue") }
8.2 Formulation
marginal risk contribution (MRC):
{\sf MRC}_i = \frac{\partial \sigma}{\partial w_i} = \frac{\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i}{\sqrt{\mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}}} MRC can be defined based on other risk measures, like VaR and CVaR.
risk contribution (RC):
{\sf RC}_i = w_i\frac{\partial\sigma}{\partial w_i}=\frac{w_i\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i}{\sqrt{\mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}}}
relative risk contribution (RRC):
{\sf RRC}_i = \frac{{\sf RC}_i}{\sigma(\mathbf{w})} = \frac{w_i\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i}{\mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}}
note that \sum_{i=1}^N {\sf RRC}_i = 1
8.3 Risk budgeting portfolio (RBP)
Risk budgeting portfolio (RBP) allocates the risk according to the risk profile determined by the weights \mathbf{b}
{\sf RC}_i = b_i \sigma(\mathbf{w})
We can rewrite
w_i\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i = b_i \mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}, \qquad i=1,\ldots,N.
8.3.2 Inverse volatility portfolio
# compute EWP
rep(1/N, N)
w_EWP <-
# compute naive RPP
diag(Sigma)
sigma2 <- 1/sqrt(sigma2)
w_RPP_naive <- w_RPP_naive/sum(w_RPP_naive)
w_RPP_naive <-
# add portfolios to the two previous ones
cbind(w_all,
w_all <-"EWP" = w_EWP,
"RPP (naive)" = w_RPP_naive)
# plot
barplot(t(w_all), col = rainbow8equal[1:4],
main = "Portfolio allocation", xlab = "stocks", ylab = "dollars", beside = TRUE,
legend = colnames(w_all))
Let’s plot the risk contribution:
# compute risk contributions
cbind("GMVP" = as.vector(w_GMVP * (Sigma %*% w_GMVP)),
risk_all <-"Markowitz MVP" = as.vector(w_Markowitz * (Sigma %*% w_Markowitz)),
"EWP" = as.vector(w_EWP * (Sigma %*% w_EWP)),
"RPP (naive)" = as.vector(w_RPP_naive * (Sigma %*% w_RPP_naive)))
rownames(risk_all) <- colnames(X_lin)
sweep(risk_all, MARGIN = 2, STATS = colSums(risk_all), FUN = "/") # normalize each column
RRC_all <-
# plot
barplot(t(RRC_all), col = rainbow8equal[1:4],
main = "Relative risk contribution", xlab = "stocks", ylab = "risk", beside = TRUE, legend = colnames(RRC_all))
8.3.3 PnL comparison
# compute returns of all portfolios
xts(X_lin %*% w_all[, c("GMVP", "Markowitz MVP", "EWP", "RPP (naive)")],
ret_all <-order.by = index(X_lin))
ret_all[1:T_trn, ]
ret_all_trn <- ret_all[-c(1:T_trn), ]
ret_all_tst <-
t(table.AnnualizedReturns(ret_all_trn))
#> Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
#> GMVP 0.0724 0.1774 0.4084
#> Markowitz MVP -0.1342 0.3500 -0.3834
#> EWP -0.1221 0.2569 -0.4753
#> RPP (naive) -0.0157 0.1874 -0.0838
t(table.AnnualizedReturns(ret_all_tst))
#> Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
#> GMVP 0.3003 0.1420 2.1140
#> Markowitz MVP 3.0480 0.5314 5.7361
#> EWP 0.7190 0.2252 3.1933
#> RPP (naive) 0.5346 0.1809 2.9543
chart.CumReturns(ret_all, main = "Cum PnL of different portfolios",
{ wealth.index = TRUE, legend.loc = "topleft", colorset = rainbow8equal)
addEventLines(xts("training", index(X_lin[T_trn])), srt=90, pos=2, lwd = 2, col = "darkblue") }
chart.Drawdown(ret_all, main = "Drawdown of different portfolios",
{ legend.loc = "bottomleft", colorset = rainbow6equal)
addEventLines(xts("training", index(X_lin[T_trn])), srt=90, pos=2, lwd = 2, col = "darkblue") }
8.3.4 vanilla RPP
Solving the risk budgeting equations as a system of nonlinear equations
w_i\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i = b_i \mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}, \qquad i=1,\ldots,N if we define \mathbf{x}=\mathbf{w}/\sqrt{\mathbf{w}^{T}\boldsymbol{\Sigma}\mathbf{w}}, \boldsymbol{\Sigma}\mathbf{x} = \mathbf{b}/\mathbf{x}
Solving the risk budgeting equations as a system of nonlinear equations
library(rootSolve)
rep(1/N, N)
b <-# function definition F(x) = Sigma %*% x - b/x
function(x, parms) {
f_root <- parms
Sigma <- nrow(Sigma)
N <-return(Sigma %*% x - b/x)
}# finding the root
multiroot(f_root, start = b, parms = Sigma)$root
x_root <- x_root/sum(x_root)
w_root <-
cbind(w_all,
w_all <-"RPP (root)" = w_root)
# compute risk contributions
cbind(risk_all,
risk_all <-"RPP (root)" = as.vector(w_root * (Sigma %*% w_root)))
sweep(risk_all, MARGIN = 2, STATS = colSums(risk_all), FUN = "/") # normalize each column
RRC_all <-# plot
barplot(t(RRC_all), col = rainbow8equal[1:5],
main = "Relative risk contribution", xlab = "stocks", ylab = "risk", beside = TRUE, legend = colnames(RRC_all))
Interestingly, Spinu (2013) realized that precisely the risk budgeting equation \boldsymbol{\Sigma}\mathbf{x} = \mathbf{b}/\mathbf{x} corresponds to the gradient of the convex function f(\mathbf{x}) = \frac{1}{2}\mathbf{x}^{T}\boldsymbol{\Sigma}\mathbf{x} - \mathbf{b}^T\log(\mathbf{x})
Thus, we can finally formulate the risk budgeting problem as the following convex optimization problem:
\underset{\mathbf{x}\ge\mathbf{0}}{\textsf{minimize}} \quad \frac{1}{2}\mathbf{x}^{T}\boldsymbol{\Sigma}\mathbf{x} - \mathbf{b}^T\log(\mathbf{x}) But if we really aim for speed and computational efficiency, there are simple iterative algorithms that can be tailored to the problem at hand, like the cyclical coordinate descent algorithm and the Newton algorithm.
# initial point
rep(1/N, N)
x0 <-
# function definition
function(x, Sigma) {
fn_convex <- nrow(Sigma)
N <-return(0.5 * t(x) %*% Sigma %*% x - (1/N)*sum(log(x)))
}
# optimize with general-purpose solver
optim(par = x0, fn = fn_convex, Sigma = Sigma, method = "BFGS")
result <- result$par
x_convex <- x_convex/sum(x_convex)
w_RPP_convex <-
rep(1/N, N)
b <-%*% x_convex - b/x_convex
Sigma #> [,1]
#> AAPL -3.188258e-05
#> AMD -7.931839e-05
#> ADI -1.077151e-05
#> ABBV -9.341533e-06
#> AEZS -3.580135e-05
#> A -2.433692e-06
#> APD -5.438662e-05
#> AA 1.082228e-06
#> CF -9.610402e-06
cbind(w_all, "RPP (convex)" = w_RPP_convex)
w_all <-barplot(t(w_all), col = rainbow8equal[1:7],
main = "Portfolio allocation", xlab = "stocks", ylab = "dollars", beside = TRUE,
legend = colnames(w_all))
# compute risk contributions
cbind(risk_all,
risk_all <-"RPP (convex)" = as.vector(w_RPP_convex * (Sigma %*% w_RPP_convex)))
sweep(risk_all, MARGIN = 2, STATS = colSums(risk_all), FUN = "/") # normalize each column
RRC_all <-
# plot
barplot(t(RRC_all), col = rainbow8equal[1:7],
main = "Relative risk contribution", xlab = "stocks", ylab = "risk", beside = TRUE,
legend = colnames(RRC_all))
8.3.5 RPP: General formulation
In more general cases, we need more sophisticated formulations, which unfortunately are not convex. (like having other constraints like allowing shortselling or box constraints, or maximizing the expected return)
\begin{array}{ll} \underset{\mathbf{w}}{\textsf{minimize}} & \sum_{i,j=1}^{N}\left(w_{i}\left(\boldsymbol{\Sigma}\mathbf{w}\right)_{i}-w_{j}\left(\boldsymbol{\Sigma}\mathbf{w}\right)_{j}\right)^{2}\\ \textsf{subject to} & \mathbf{1}^T\mathbf{w}=1. \end{array}
We will solve it with the general-purpose nonlinear solver optim() in R (but this is totally ignoring the constraints, so a better solver should be used)
# initial point
rep(1/N, N)
x0 <-
# function definition
function(w, Sigma) {
fn_nonconvex <- length(w)
N <- w * (Sigma %*% w)
risks <- rep(risks, times = N) - rep(risks, each = N)
g <-return(sum(g^2))
}
# optimize with general-purpose solver
optim(par = x0, fn = fn_nonconvex, Sigma = Sigma, method = "BFGS")
result <- result$par
x_gen_solver <- x_gen_solver/sum(x_gen_solver)
w_RPP_gen_solver <-
# plot
cbind(w_all, "RPP (gen-solver)" = w_RPP_gen_solver)
w_all <-barplot(t(w_all), col = rainbow8equal[1:7],
main = "Portfolio allocation", xlab = "stocks", ylab = "dollars", beside = TRUE,
legend = colnames(w_all))
# compute risk contributions
cbind(risk_all,
risk_all <-"RPP (gen-solver)" = as.vector(w_RPP_gen_solver * (Sigma %*% w_RPP_gen_solver)))
sweep(risk_all, MARGIN = 2, STATS = colSums(risk_all), FUN = "/") # normalize each column
RRC_all <-
# plot
barplot(t(RRC_all), col = rainbow8equal[1:7],
main = "Relative risk contribution", xlab = "stocks", ylab = "risk", beside = TRUE,
legend = colnames(RRC_all))
We can observe that the solution based on the general solver for the nonconvex formulation is not as perfectly equalized as that from the convex formulation. The reason is that when solving a nonconvex problem one does not have any guarantee of global optimality. In this particular case, we know that perfect risk contribution equalization can actually be achieved (as it is by the solution from the convex formulation) but the nonconvex formulation cannot achieve it.
8.3.6 RBP formulations
This formulation is again based on the double-index summation with budgets: \begin{array}{ll} \underset{\mathbf{w}}{\textsf{minimize}} & \sum_{i,j=1}^{N}\left(\frac{w_{i}\left(\boldsymbol{\Sigma}\mathbf{w}\right)_{i}}{b_i} - \frac{w_{j}\left(\boldsymbol{\Sigma}\mathbf{w}\right)_{j}}{b_j}\right)^{2}\\ \textsf{subject to} & \mathbf{1}^T\mathbf{w}=1. \end{array}