Example of how to use Newton’s method to calculate risk parity weights. The algorithm is described in Efficient Algorithms for Computing Risk Parity Portfolio Weights
Set up F(y), system of equations and its jacobian matrix.
# F(y), system of equations
eval_f <- function(x, sigma, lambda){
# x = weights vector, sigma = covariance matrix, lambda = a constant to be found
# sets up n+1 x 1 matrix of equations, where n is the amount of assets or variables
# and the +1 is the restriction that the weights sum to 1
x <- as.vector(x)
x <- x[1:nrow(sigma)]
f0 <- matrix(nrow = NROW(sigma) + 1, ncol = 1)
f0[1:NROW(sigma), 1] <- (sigma %*% x) - (lambda * 1/x)
f0[(NROW(sigma) + 1), 1] <- sum(x) - 1
return(f0)
}
# Jacobian matrix of F(y)
jacob_f <- function(x, sigma, lambda){
# x = weights vector, sigma = covariance matrix, lambda = a constant to be found
# sets up n+1 x n+1 jacobian matrix of F(y)
x <- as.vector(x)
x <- x[1:NROW(sigma)]
g <- matrix(nrow = NROW(sigma) + 1, ncol = NCOL(sigma)+1)
g[1:NROW(sigma), 1:NCOL(sigma)] <- sigma + as.vector(lambda) * diag(1/x^2)
g[1:NROW(sigma), (NCOL(sigma)+1)] <- 1/x
g[(NROW(sigma)+1), 1:NCOL(sigma)] <- rep(1, ncol(sigma))
g[(NROW(sigma)+1), (NCOL(sigma)+1)] <- 0
return(g)
}
Get returns for a hypothetical sector portfolio to test the algorithm
# requires fImport package to get stock returns
if(require(fImport) == FALSE){
install.packages("fImport")
require(fImport)
}
# use fidelity sector ETFs as example
assets <- c("FDIS", "FSTA", "FENY", "FNCL", "FHLC",
"FIDU", "FTEC", "FMAT", "FCOM", "FUTY")
nAssets <- length(assets)
# get historical price data (for past year) from yahoo finance data base
# prices are in every 6th column
prices <- yahooSeries(assets)[, seq(6, nAssets*6, 6)]
colnames(prices) <- c("ConsumerDisc", "ConsumerStap", "Energy",
"Financials", "HealthCare", "Industrials",
"InfoTech", "Materials", "Telecomm", "Utilities")
head(prices)
## GMT
## ConsumerDisc ConsumerStap Energy Financials HealthCare
## 2014-01-06 26.38 25.06 24.87 25.59 26.07
## 2014-01-07 26.54 25.19 25.06 25.62 26.37
## 2014-01-08 26.50 25.01 24.87 25.68 26.63
## 2014-01-09 26.44 25.12 24.80 25.74 26.85
## 2014-01-10 26.52 25.14 24.83 25.75 27.00
## 2014-01-13 25.98 24.98 24.38 25.40 26.79
## Industrials InfoTech Materials Telecomm Utilities
## 2014-01-06 26.58 26.38 25.67 24.82 23.65
## 2014-01-07 26.71 26.62 25.64 24.94 23.83
## 2014-01-08 26.67 26.65 25.75 24.91 23.73
## 2014-01-09 26.78 26.53 25.67 24.54 23.86
## 2014-01-10 26.85 26.59 25.75 24.62 24.15
## 2014-01-13 26.54 26.26 25.43 24.39 23.94
# convert prices to continuously compounded returns
ret <- timeSeries::returns(prices, method = "continuous")
head(ret)
## GMT
## ConsumerDisc ConsumerStap Energy Financials
## 2014-01-07 0.006046882 0.0051741409 0.007610692 0.0011716463
## 2014-01-08 -0.001508296 -0.0071713455 -0.007610692 0.0023391824
## 2014-01-09 -0.002266718 0.0043885967 -0.002818605 0.0023337233
## 2014-01-10 0.003021150 0.0007958616 0.001208946 0.0003884249
## 2014-01-13 -0.020572154 -0.0063846985 -0.018289475 -0.0136854531
## 2014-01-14 0.009576783 0.0047923414 0.012230075 0.0070616221
## HealthCare Industrials InfoTech Materials
## 2014-01-07 0.011441772 0.004878974 0.009056666 -0.001169363
## 2014-01-08 0.009811399 -0.001498689 0.001126338 0.004280995
## 2014-01-09 0.008227421 0.004116002 -0.004512982 -0.003111632
## 2014-01-10 0.005571045 0.002610481 0.002259037 0.003111632
## 2014-01-13 -0.007808182 -0.011612792 -0.012488336 -0.012505048
## 2014-01-14 0.014084740 0.010494849 0.019608471 0.013669418
## Telecomm Utilities
## 2014-01-07 0.004823160 0.0075821762
## 2014-01-08 -0.001203611 -0.0042052207
## 2014-01-09 -0.014964890 0.0054633462
## 2014-01-10 0.003254681 0.0120809634
## 2014-01-13 -0.009385909 -0.0087336800
## 2014-01-14 0.007759892 0.0008350731
# covariance matrix of returns
sigma <- cov(ret)
Run algorithm (Newton’s method) and test results
maxIterations <- 100
tol <- 1e-6
# time algo
timeStart <- proc.time()
for(i in 1:maxIterations){
if (i == 1){
# 1/N first weight guess
x_i <- rep(1/nAssets, nAssets)
# lambda = portfolio standard deviation / number of assets
lambda <- t(x_i) %*% sigma %*% x_i / nAssets
}
else{
# all other iterations after 1st guess
jInv <- solve(jacob_f(c(x_i, 1), sigma, lambda))
Fx <- eval_f(c(x_i, 1), sigma, lambda)
ans <- c(x_i, 1) - jInv %*% Fx
# if solution is found exit the loop
if(norm(c(x_i,1) - ans) <= tol) break
# otherwise keep trying new weight vector guess
x_i <- ans[1:nAssets]
lambda <- t(x_i) %*% sigma %*% x_i / nAssets
}
}
# time elapsed
proc.time() - timeStart
## user system elapsed
## 0.004 0.000 0.004
# check solution
ans <- ans[1:nAssets]
num <- t(ans) %*% sigma
denom <- t(ans) %*% sigma %*% ans
cctr <- num / denom[1,1] * ans
cctr <- t(cctr)
colnames(cctr) <- "Percent.Contribution.to.Risk"
cctr
## Percent.Contribution.to.Risk
## ConsumerDisc 0.1
## ConsumerStap 0.1
## Energy 0.1
## Financials 0.1
## HealthCare 0.1
## Industrials 0.1
## InfoTech 0.1
## Materials 0.1
## Telecomm 0.1
## Utilities 0.1