library(knitr) opts_chunk$set( collapse = TRUE, comment = "#>", fig.align = "center", fig.retina = 2, out.width = "75%", dpi = 96 ) knit_hooks$set(pngquant = hook_pngquant) # Help on bookdown: https://bookdown.org/yihui/bookdown/ # rmarkdown::render("vignettes/example.Rmd", "bookdown::html_document2") # rmarkdown::render("vignettes/example.Rmd", "bookdown::pdf_document2")
This note contains an example under consideration by Alexis Nortier.
The risk-parity portfolio formulation is of the form [@FengPal2015riskparity][@FengPal2016monograph]: $$\begin{array}{ll} \underset{\mathbf{w}}{\textsf{minimize}} & R(\mathbf{w})\ \textsf{subject to} & \mathbf{1}^T\mathbf{w}=1\ & \mathbf{w}\ge\mathbf{0}, \end{array}$$ where the risk term is of the form (double summation) $R(\mathbf{w}) = \sum_{i,j=1}^{N}(g_{ij}(\mathbf{w}))^{2}$ or simply (single summation) $R(\mathbf{w}) = \sum_{i=1}^{N}(g_{i}(\mathbf{w}))^{2}$.
Consider the following parameters: $$\boldsymbol{\Sigma} = \left[ \begin{array}{ccc} 1.0000 & 0.0015 & -0.0119\ 0.0015 & 1.0000 & -0.0308\ -0.0119 & -0.0308 & 1.0000 \end{array} \right]$$ $$\mathbf{b} = \left[ \begin{array}{c} 0.1594 \ 0.0126 \ 0.8280 \end{array} \right]$$ and the corresponding code:
Sigma <- rbind(c(1.0000, 0.0015, -0.0119), c(0.0015, 1.0000, -0.0308), c(-0.0119, -0.0308, 1.0000)) b <- c(0.1594, 0.0126, 0.8280)
The optimal solution is known to be $$\mathbf{w}^\star = \left[ \begin{array}{c} 0.2799 \ 0.0877 \ 0.6324 \end{array} \right],$$ but the Matlab implementation seems to converge to $$\mathbf{w} = \left[ \begin{array}{c} 0.3106 \ 0.0000 \ 0.6895 \end{array} \right].$$ Let's explore this problem with the R package riskParityPortfolio.
This problem is a vanilla formulation because it just contains the risk-parity term subject to the budget constraint and the no-shortselling constraint. Therefore, it can be reformulated as a convex problem and the global optimal solution can be obtained:
library(riskParityPortfolio) res <- riskParityPortfolio(Sigma, b = b) res$w res$risk_contribution/b
Even though we really have a vanilla formulation, we can still consider a direct nonconvex formulation. Consider the risk expression:
$$R(\mathbf{w}) = \sum_{i=1}^{N}\left(\frac{w_{i}\left(\boldsymbol{\Sigma}\mathbf{w}\right)_i}{\mathbf{w}^T\boldsymbol{\Sigma}\mathbf{w}}-b_i\right)^{2}.$$
The general solver alabama
is sensitive to the initial point in this formulation (the first case gets stuck in a local minimum):
set.seed(234) res_ala1 <- riskParityPortfolio(Sigma, b = b, w0 = b, formulation = "rc-over-var vs b", method = "alabama") res_ala1$w tail(res_ala1$obj_fun, 1) res_ala2 <- riskParityPortfolio(Sigma, b = b, w0 = c(1, 1, 1)/3, formulation = "rc-over-var vs b", method = "alabama") res_ala2$w tail(res_ala2$obj_fun, 1) res_ala3 <- riskParityPortfolio(Sigma, b = b, w0 = (w0 <- runif(3))/sum(w0), formulation = "rc-over-var vs b", method = "alabama") res_ala3$w tail(res_ala3$obj_fun, 1)
# plot the portfolios barplot(rbind(res_ala1$w, res_ala2$w, res_ala3$w), main = "Portfolios with different initial points", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3)) # plot the risk contributions barplot(rbind(res_ala1$risk_contribution/b, res_ala2$risk_contribution/b, res_ala3$risk_contribution/b), main = "Risk contribution of the portfolios", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3))
The SCA method gives the same results:
set.seed(234) res_sca1 <- riskParityPortfolio(Sigma, b = b, w0 = b, formulation = "rc-over-var vs b", method = "sca") res_sca1$w tail(res_sca1$obj_fun, 1) res_sca2 <- riskParityPortfolio(Sigma, b = b, w0 = c(1, 1, 1)/3, formulation = "rc-over-var vs b", method = "sca") res_sca2$w tail(res_sca2$obj_fun, 1) res_sca3 <- riskParityPortfolio(Sigma, b = b, w0 = (w0 <- runif(3))/sum(w0), formulation = "rc-over-var vs b", method = "sca") res_sca3$w tail(res_sca3$obj_fun, 1)
# plot the portfolios barplot(rbind(res_sca1$w, res_sca2$w, res_sca3$w), main = "Portfolios with different initial points", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3)) # plot the risk contributions barplot(rbind(res_sca1$risk_contribution/b, res_sca2$risk_contribution/b, res_sca3$risk_contribution/b), main = "Risk contribution of the portfolios", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3))
Consider now the risk expression: $$R(\mathbf{w}) = \sum_{i=1}^{N}\left(\frac{w_{i}\left(\boldsymbol{\Sigma}\mathbf{w}\right)i}{\sqrt{\mathbf{w}^T\boldsymbol{\Sigma}\mathbf{w}}}-b_i\sqrt{\mathbf{w}^T\boldsymbol{\Sigma}\mathbf{w}}\right)^{2} = \sum{i=1}^{N}\left(\frac{r_i}{\sqrt{\mathbf{1}^T\mathbf{r}}}-b_i\sqrt{\mathbf{1}^T\mathbf{r}}\right)^{2}.$$
The general solver alabama
is again sensitive to the initial point in this formulation (the third case gets stuck in a local minimum):
set.seed(234) res_ala1 <- riskParityPortfolio(Sigma, b = b, w0 = b, formulation = "rc-over-sd vs b-times-sd", method = "alabama") res_ala1$w tail(res_ala1$obj_fun, 1) res_ala2 <- riskParityPortfolio(Sigma, b = b, w0 = c(1, 1, 1)/3, formulation = "rc-over-sd vs b-times-sd", method = "alabama") res_ala2$w tail(res_ala2$obj_fun, 1) res_ala3 <- riskParityPortfolio(Sigma, b = b, w0 = (w0 <- runif(3))/sum(w0), formulation = "rc-over-sd vs b-times-sd", method = "alabama") res_ala3$w tail(res_ala3$obj_fun, 1)
# plot the portfolios barplot(rbind(res_ala1$w, res_ala2$w, res_ala3$w), main = "Portfolios with different initial points", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3)) # plot the risk contributions barplot(rbind(res_ala1$risk_contribution/b, res_ala2$risk_contribution/b, res_ala3$risk_contribution/b), main = "Risk contribution of the portfolios", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3))
The SCA method is also sensitive to the initial point (the first case gets stuck in a local minimum):
set.seed(234) res_sca1 <- riskParityPortfolio(Sigma, b = b, w0 = b, formulation = "rc-over-sd vs b-times-sd", method = "sca") res_sca1$w tail(res_sca1$obj_fun, 1) res_sca2 <- riskParityPortfolio(Sigma, b = b, w0 = c(1, 1, 1)/3, formulation = "rc-over-sd vs b-times-sd", method = "sca") res_sca2$w tail(res_sca2$obj_fun, 1) res_sca3 <- riskParityPortfolio(Sigma, b = b, w0 = (w0 <- runif(3))/sum(w0), formulation = "rc-over-sd vs b-times-sd", method = "sca") res_sca3$w tail(res_sca3$obj_fun, 1)
# plot the portfolios barplot(rbind(res_sca1$w, res_sca2$w, res_sca3$w), main = "Portfolios with different initial points", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3)) # plot the risk contributions barplot(rbind(res_sca1$risk_contribution/b, res_sca2$risk_contribution/b, res_sca3$risk_contribution/b), main = "Risk contribution of the portfolios", xlab = "stocks", ylab = "dollars", beside = TRUE, col = heat.colors(3))
The vanilla risk-parity portfolio is a convex problem and it can be solved optimally. However, if instead one uses a direct nonconvex formulation (which is required when having different constrains or additional objectives apart from the risk-parity one), there are local minima.
\setlength{\parindent}{-0.2in} \setlength{\leftskip}{0.2in} \setlength{\parskip}{8pt} \noindent
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.