Adaptive Asset Allocation
Today I want to highlight a whitepaper about Adaptive Asset Allocation by Butler, Philbrick and Gordillo and the discussion by David Varadi on the robustness of parameters of the Adaptive Asset Allocation algorithm.
In this post I will follow the steps of the Adaptive Asset Allocation paper, and in the next post I will show how to test the sensitivity of parameters of the of the Adaptive Asset Allocation algorithm.
I will use the 10 ETFs that invest into the same asset classes as presented in the paper:
- U.S. Stocks (SPY)
- European Stocks (EFA)
- Japanese Stocks (EWJ)
- Emerging Market Stocks (EEM)
- U.S. REITs (IYR)
- International REITs (RWX)
- U.S. Mid-term Treasuries (IEF)
- U.S. Long-term Treasuries (TLT)
- Commodities (DBC)
- Gold (GLD)
Unfortunately, most of these 10 ETFs only began trading in the end of 2004, so I will only be able to replicate the recent Adaptive Asset Allocation strategy performance.
Let’s start by loading historical prices of 10 ETFs using the Systematic Investor Toolbox:
############################################################################### # 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 #****************************************************************** load.packages('quantmod') tickers = spl('SPY,EFA,EWJ,EEM,IYR,RWX,IEF,TLT,DBC,GLD') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) bt.prep(data, align='keep.all', dates='2004:12::') #***************************************************************** # Code Strategies #****************************************************************** prices = data$prices n = ncol(prices) models = list() # find period ends period.ends = endpoints(prices, 'months') period.ends = period.ends[period.ends > 0] # Adaptive Asset Allocation parameters n.top = 5 # number of momentum positions n.mom = 6*22 # length of momentum look back n.vol = 1*22 # length of volatility look back
Next, let’s create portfolios as outlined in the whitepaper:
#***************************************************************** # Equal Weight #****************************************************************** data$weight[] = NA data$weight[period.ends,] = ntop(prices[period.ends,], n) models$equal.weight = bt.run.share(data, clean.signal=F) #***************************************************************** # Volatliliy Position Sizing #****************************************************************** ret.log = bt.apply.matrix(prices, ROC, type='continuous') hist.vol = bt.apply.matrix(ret.log, runSD, n = n.vol) adj.vol = 1/hist.vol[period.ends,] data$weight[] = NA data$weight[period.ends,] = adj.vol / rowSums(adj.vol, na.rm=T) models$volatility.weighted = bt.run.share(data, clean.signal=F) #***************************************************************** # Momentum Portfolio #***************************************************************** momentum = prices / mlag(prices, n.mom) data$weight[] = NA data$weight[period.ends,] = ntop(momentum[period.ends,], n.top) models$momentum = bt.run.share(data, clean.signal=F) #***************************************************************** # Combo: weight positions in the Momentum Portfolio according to Volatliliy #***************************************************************** weight = ntop(momentum[period.ends,], n.top) * adj.vol data$weight[] = NA data$weight[period.ends,] = weight / rowSums(weight, na.rm=T) models$combo = bt.run.share(data, clean.signal=F,trade.summary = TRUE)
Finally let’s create the Adaptive Asset Allocation portfolio:
#***************************************************************** # Adaptive Asset Allocation (AAA) # weight positions in the Momentum Portfolio according to # the minimum variance algorithm #***************************************************************** weight = NA * prices weight[period.ends,] = ntop(momentum[period.ends,], n.top) for( i in period.ends[period.ends >= n.mom] ) { hist = ret.log[ (i - n.vol + 1):i, ] # require all assets to have full price history include.index = count(hist)== n.vol # also only consider assets in the Momentum Portfolio index = ( weight[i,] > 0 ) & include.index n = sum(index) if(n > 0) { hist = hist[ , index] # create historical input assumptions ia = create.historical.ia(hist, 252) s0 = apply(coredata(hist),2,sd) ia$cov = cor(coredata(hist), use='complete.obs',method='pearson') * (s0 %*% t(s0)) # create constraints: 0<=x<=1, sum(x) = 1 constraints = new.constraints(n, lb = 0, ub = 1) constraints = add.constraints(rep(1, n), 1, type = '=', constraints) # compute minimum variance weights weight[i,] = 0 weight[i,index] = min.risk.portfolio(ia, constraints) } } # Adaptive Asset Allocation (AAA) data$weight[] = NA data$weight[period.ends,] = weight[period.ends,] models$aaa = bt.run.share(data, clean.signal=F,trade.summary = TRUE)
The last step is create reports for all models:
#***************************************************************** # Create Report #****************************************************************** models = rev(models) plotbt.custom.report.part1(models) plotbt.custom.report.part2(models) plotbt.custom.report.part3(models$combo, trade.summary = TRUE) plotbt.custom.report.part3(models$aaa, trade.summary = TRUE)
The AAA portfolio performs very well, producing the highest Sharpe ratio and smallest draw-down across all strategies. In the next post I will look at the sensitivity of AAA parameters.
To view the complete source code for this example, please have a look at the bt.aaa.test() function in bt.test.r at github.
I have been following your posts and been able to use SIT successfully. Kudos for all the hard work and very well presented content.
Trying to run the code for aaa and on my environment min.risk.portfolio returns 0 & NA weight on line 33
weight[i,index] = min.risk.portfolio(ia, constraints)
I would appreciate some help with this. Thanks again, Orbiter
I have just realized I did not have kernlab loaded. All working just fine now.
Greatly appreciate your contribution with these 2 posts on AAA/Sensitivity! I find quite a different and far less favorable result using split-only adjusted data (not accounting for dividends in the adjusted close). Dividends are important and certainly improve the results. However, best practice is to use split-only adjusted prices for indicators like momentum since dividend adjusted prices cannot be actually traded. Would appreciate your thoughts here on how best to accomodate that within this methodology via R?
Below I will refer to the Price return (split-only adjusted data) and Total return (split and dividend adjusted data)
In the literature the ‘Momentum’ is usually defined using historical Total return and ‘Volatility’ is usually defined using Price return.
I added a small modification to the code to use Price returns if use.total = FALSE and the results are a bit worse, but very comparable.
Great work. The authors of AAA have published a few more follow-up articles with more sophisticated allocation methods(http://gestaltu.com/2013/08/dynamic-asset-allocation-for-practitioners.html). Wondering if yo would update your R code as well. Thanks.
I’ve been reviewing this code for some time. Doesn’t the paper apply a target volatility in addition to the minimum variance algorithm?
Thanks