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.

Risk-parity portfolio formulation

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}$.

Parameter definition for the test

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.

Vanilla formulation

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

Formulation "rc-over-var vs 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))

Formulation "rc-over-sd vs b-times-sd"

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))

Conclusion

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.

References {-}

\setlength{\parindent}{-0.2in} \setlength{\leftskip}{0.2in} \setlength{\parskip}{8pt} \noindent



dppalomar/riskParityPortfolio documentation built on Nov. 25, 2022, 1:34 p.m.