Wednesday, March 6, 2013

Cluster Portfolio Allocation



from: https://systematicinvestor.wordpress.com/2013/02/12/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.
01###############################################################################
02# Load Systematic Investor Toolbox (SIT)
04###############################################################################
05setInternet2(TRUE)
06con = gzcon(url('http://www.systematicportfolio.com/sit.gz''rb'))
07    source(con)
08close(con)
09    #*****************************************************************
10    # Load historical data for ETFs
11    #******************************************************************
12    load.packages('quantmod')
13 
14    tickers = spl('GLD,UUP,SPY,QQQ,IWM,EEM,EFA,IYR,USO,TLT')
15 
16    data <- new.env()
17    getSymbols(tickers, src = 'yahoo', from = '1900-01-01', env = data, auto.assign = T)
18        for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T)
19         
20    bt.prep(data, align='remove.na')
21 
22    #*****************************************************************
23    # Setup
24    #******************************************************************
25    # compute returns
26    ret = data$prices / mlag(data$prices) - 1
27 
28    # setup period
29    dates = '2012::2012'
30    ret = ret[dates]
Next, let’s compute “Plain” portfolio allocation (i.e. no Clustering)
1fn.name = 'equal.weight.portfolio'             
2fn = match.fun(fn.name)
3 
4# create input assumptions
5ia = create.historical.ia(ret, 252)
6 
7# compute allocation without cluster, for comparison
8weight = fn(ia)
Next, let’s create clusters and compute portfolio allocation within each Cluster
01# create clusters
02group = cluster.group.kmeans.90(ia)
03ngroups = max(group)
04 
05weight0 = rep(NA, ia$n)
06         
07# store returns for each cluster
08hist.g = NA * ia$hist.returns[,1:ngroups]
09         
10# compute weights within each group
11for(g in 1:ngroups) {
12    ifsum(group == g) == 1 ) {
13        weight0[group == g] = 1
14        hist.g[,g] = ia$hist.returns[, group == g, drop=F]
15    else {
16        # create input assumptions for the assets in this cluster
17        ia.temp = create.historical.ia(ia$hist.returns[, group == g, drop=F], 252)
18 
19        # compute allocation within cluster
20        w0 = fn(ia.temp)
21         
22        # set appropriate weights
23        weight0[group == g] = w0
24         
25        # compute historical returns for this cluster
26        hist.g[,g] = ia.temp$hist.returns %*% w0
27    }
28}
Next, let’s compute portfolio allocation across all Clusters and compute final portfolio weights
1# create GROUP input assumptions
2ia.g = create.historical.ia(hist.g, 252)
3         
4# compute allocation across clusters
5group.weights = fn(ia.g)
6             
7# mutliply out group.weights by within group weights
8for(g in 1:ngroups)
9    weight0[group == g] = weight0[group == g] * group.weights[g]
Finally, let’s create reports and compare portfolio allocations
01#*****************************************************************
02# Create Report
03#******************************************************************            
04load.packages('RColorBrewer')
05col = colorRampPalette(brewer.pal(9,'Set1'))(ia$n)
06 
07layout(matrix(1:2,nr=2,nc=1))
08par(mar = c(0,0,2,0))
09index = order(group)
10 
11pie(weight[index], labels = paste(colnames(ret), round(100*weight,1),'%')[index], col=col, main=fn.name)
12 
13pie(weight0[index], labels = paste(colnames(ret), round(100*weight0,1),'%')[index], col=col, main=paste('Cluster',fn.name))
equal.weight.portfolio.plot.png.small
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.
risk.parity.portfolio.plot.png.small
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 thebt.cluster.portfolio.allocation.test() function in bt.test.r at github.


No comments:

Post a Comment