Cluster Portfolio Allocation
Today, I want to continue with clustering theme and show how the portfolio weights are determined in the Cluster Portfolio Allocation method. One example of the Cluster Portfolio Allocation method is Cluster Risk Parity (Varadi, Kapler, 2012).
The Cluster Portfolio Allocation method has 3 steps:
- Create Clusters
- Allocate funds within each Cluster
- Allocate funds across all Clusters
I will illustrate below all 3 steps using “Equal Weight” and “Risk Parity” portfolio allocation methiods. Let’s start by loading historical prices for the 10 major asset classes.
############################################################################### # Load Systematic Investor Toolbox (SIT) # https://systematicinvestor.wordpress.com/systematic-investor-toolbox/ ############################################################################### setInternet2(TRUE) con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb')) source(con) close(con) #***************************************************************** # Load historical data for ETFs #****************************************************************** load.packages('quantmod') tickers = spl('GLD,UUP,SPY,QQQ,IWM,EEM,EFA,IYR,USO,TLT') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1900-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) bt.prep(data, align='remove.na') #***************************************************************** # Setup #****************************************************************** # compute returns ret = data$prices / mlag(data$prices) - 1 # setup period dates = '2012::2012' ret = ret[dates]
Next, let’s compute “Plain” portfolio allocation (i.e. no Clustering)
fn.name = 'equal.weight.portfolio' fn = match.fun(fn.name) # create input assumptions ia = create.historical.ia(ret, 252) # compute allocation without cluster, for comparison weight = fn(ia)
Next, let’s create clusters and compute portfolio allocation within each Cluster
# create clusters group = cluster.group.kmeans.90(ia) ngroups = max(group) weight0 = rep(NA, ia$n) # store returns for each cluster hist.g = NA * ia$hist.returns[,1:ngroups] # compute weights within each group for(g in 1:ngroups) { if( sum(group == g) == 1 ) { weight0[group == g] = 1 hist.g[,g] = ia$hist.returns[, group == g, drop=F] } else { # create input assumptions for the assets in this cluster ia.temp = create.historical.ia(ia$hist.returns[, group == g, drop=F], 252) # compute allocation within cluster w0 = fn(ia.temp) # set appropriate weights weight0[group == g] = w0 # compute historical returns for this cluster hist.g[,g] = ia.temp$hist.returns %*% w0 } }
Next, let’s compute portfolio allocation across all Clusters and compute final portfolio weights
# create GROUP input assumptions ia.g = create.historical.ia(hist.g, 252) # compute allocation across clusters group.weights = fn(ia.g) # mutliply out group.weights by within group weights for(g in 1:ngroups) weight0[group == g] = weight0[group == g] * group.weights[g]
Finally, let’s create reports and compare portfolio allocations
#***************************************************************** # Create Report #****************************************************************** load.packages('RColorBrewer') col = colorRampPalette(brewer.pal(9,'Set1'))(ia$n) layout(matrix(1:2,nr=2,nc=1)) par(mar = c(0,0,2,0)) index = order(group) pie(weight[index], labels = paste(colnames(ret), round(100*weight,1),'%')[index], col=col, main=fn.name) pie(weight0[index], labels = paste(colnames(ret), round(100*weight0,1),'%')[index], col=col, main=paste('Cluster',fn.name))
The difference is most striking in the “Equal Weight” portfolio allocation method. The Cluster version allocates 25% to each cluster first, and then allocates equally within each cluster. The Plain version allocates equally among all assets. The “Risk Parity” version below works in similar way, but instead of having equal weights, the focus is on the equal risk allocations. I.e. UUP gets a much bigger allocation because it is far less risky than any other asset.
Next week, I will show how to back-test Cluster Portfolio Allocation methods.
To view the complete source code for this example, please have a look at the bt.cluster.portfolio.allocation.test() function in bt.test.r at github.
I’m still not understanding why a clustering approach is preferred to a minimum variance approach that considers the entire co-variance matrix of each asset, optimizing allocations to minimize total risk. Or, perhaps another way, applying risk-parity to orthogonal risk factors based on principle component analysis on the assets.
The main focus of clustering portfolio allocation is diversification. So if you want to get a portfolio with minimum variance, you should use minimum variance approach. The clustering portfolio allocation method will not produce the portfolio with minimum variance.
On the other hand, if you want to create a portfolio that distributes risk equally within clusters and across clusters (i.e. diversify you risk bets) than you should use clustering portfolio allocation method.
One way to think about clustering portfolio allocation – is that one of the methods you can use to create portfolios. For example, if you want minimum risk, please use minimum variance approach. If you want to diversify risk allocations, please use clustering portfolio allocation method.
Thanks for the reply
Well done! Thanks for sharing.
Is it possible (and if so, do you think it would create any value) to create a framework where a minimum variance allocation is applied to clusters?
In this use case, the primary objective is to diversify factor risks by clustering similar strategies together (hence reducing the potential for overweighting a to any subset of highly coorellated strategies). The secondary objective is to then achieve the lowest risk allocation combination to each “strategy cluster”. This could be done by treating each clustered sub portfolio as an asset and running the MVO to determine cluster weight.
For example:
Consider a fund of funds or proprietary trading group with many individual traders. They may have many correlated strategies trading the same underlying index (or even equities in the same sector). You would not know the specific approach but could identify correlation.
It may be desirable to first diversify factor risk by clustering the most correlated returns together. Next, the master account could properly allocate % of buying power to each “strategy cluster” (group of traders or family of funds) to achieve Min Variance. Lastly, target volatility could be used to set leverage targets at the master account level.
Damian