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.

Advertisement
  1. orbiter
    August 21, 2012 at 12:00 pm

    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

    • orbiter
      August 24, 2012 at 10:40 am

      I have just realized I did not have kernlab loaded. All working just fine now.

  2. PeteM
    September 6, 2012 at 5:26 pm

    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?

    • September 8, 2012 at 3:38 pm

      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.

      	#*****************************************************************
      	# 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)
      	
      	
      	# contruct another back-test enviroment with split-adjusted prices, do not include dividends
      	data.price <- new.env()
      		for(i in ls(data)) data.price[[i]] = adjustOHLC(data[[i]], symbol.name=i, adjust='split', use.Adjusted=F)
      	bt.prep(data.price, align='keep.all', dates='2004:12::')	
      	
      	
      	# create split and dividend adjusted prices
      		for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T)							
      	bt.prep(data, align='keep.all', dates='2004:12::')
       
      	
      	# flag to indicate whether to use Total(split and dividend adjusted) or Price(split adjusted) prices
      	use.total = FALSE
      	   
          #*****************************************************************
          # Code Strategies
          #******************************************************************
          prices = data$prices      
          n = ncol(prices)
          
      	prices4mom = iif(use.total, data$prices, data.price$prices)
      	prices4vol = iif(use.total, data$prices, data.price$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
              
          #*****************************************************************
          # 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(prices4vol, 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 = prices4mom / mlag(prices4mom, 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)
      
          #*****************************************************************
          # Create Report
          #******************************************************************    
          models = rev(models)
         
          plotbt.custom.report.part1(models)       
          plotbt.custom.report.part2(models)       
      
  3. David_sf
    August 12, 2013 at 11:50 pm

    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.

  4. Alex
    August 15, 2013 at 7:40 pm

    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

  1. August 21, 2012 at 12:58 am
  2. October 29, 2012 at 2:14 pm
  3. March 22, 2013 at 12:49 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: