Home > Asset Allocation, Backtesting, Portfolio Construction, R > Weekend Reading: F-Squared

Weekend Reading: F-Squared

Mebane Faber posted another interesting blog post: Building a Simple Sector Rotation on Momentum and Trend that caught my interest. Today I want to show how you can test such strategy 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')		
	
	data = new.env()
	# load historical market returns
	temp = get.fama.french.data('F-F_Research_Data_Factors', periodicity = '',download = T, clean = T)
		ret = cbind(temp[[1]]$Mkt.RF + temp[[1]]$RF, temp[[1]]$RF)
		price = bt.apply.matrix(ret / 100, function(x) cumprod(1 + x))
	data$SPY = make.stock.xts( price$Mkt.RF )
	data$SHY = make.stock.xts( price$RF )
	
	# load historical sector returns
	temp = get.fama.french.data('10_Industry_Portfolios', periodicity = '',download = T, clean = T)		
		ret = temp[[1]]
		price = bt.apply.matrix(ret[,1:9] / 100, function(x) cumprod(1 + x))
	for(n in names(price)) data[[n]] = make.stock.xts( price[,n] )
	
	# align dates
	data$symbolnames = c(names(price), 'SHY', 'SPY')
	bt.prep(data, align='remove.na', dates='2000::')

	# back-test dates
	bt.dates = '2001:04::'

	#*****************************************************************
	# Setup
	#****************************************************************** 	
	prices = data$prices  
	n = ncol(data$prices)
		
	models = list()
	
	#*****************************************************************
	# Benchmark Strategies
	#****************************************************************** 			
	data$weight[] = NA
		data$weight$SPY[1] = 1
	models$SPY = bt.run.share(data, clean.signal=F, dates=bt.dates)
			
	weight = prices
		weight$SPY = NA
		weight$SHY = NA
	
	data$weight[] = NA
		data$weight[] = ntop(weight[], n)
	models$EW = bt.run.share(data, clean.signal=F, dates=bt.dates)
	
	#*****************************************************************
	# Code Strategies
	# http://www.mebanefaber.com/2013/12/04/square-root-of-f-squared/
	#****************************************************************** 			
	sma = bt.apply.matrix(prices, SMA, 10)
	
	# create position score
	position.score = sma
	position.score[ prices < sma ] = NA
		position.score$SHY = NA	
		position.score$SPY = NA	
	
	# equal weight allocation
	weight = ntop(position.score[], n)	
	
	# number of invested funds
	n.selected = rowSums(weight != 0)
	
	# cash logic
	weight$SHY[n.selected == 0,] = 1
	
	weight[n.selected == 1,] = 0.25 * weight[n.selected == 1,]
	weight$SHY[n.selected == 1,] = 0.75
	
	weight[n.selected == 2,] = 0.5 * weight[n.selected == 2,]
	weight$SHY[n.selected == 2,] = 0.5
	
	weight[n.selected == 3,] = 0.75 * weight[n.selected == 3,]
	weight$SHY[n.selected == 3,] = 0.25
	
	# cbind(round(100*weight,0), n.selected)	
	
	data$weight[] = NA
		data$weight[] = weight
	models$strategy1 = bt.run.share(data, clean.signal=F, dates=bt.dates)	
	
	#*****************************************************************
	# Create Report
	#******************************************************************       	
	strategy.performance.snapshoot(models, one.page = T)

plot1

Mebane thank you very much for sharing your great ideas. I would encourage readers to play with this strategy and report back.

Please note that I back-tested the strategy using the monthly observations. The strategy’s draw-down is around 17% using monthly data. If we switch to the daily data, the strategy’s draw-down goes to around 22%. There was one really bad month in 2002.

To view the complete source code for this example, please have a look at the bt.mebanefaber.f.squared.test() function in bt.test.r at github.

  1. December 7, 2013 at 6:11 pm

    An important piece is the frequency of execution,
    “The critical process, executed on a weekly cycle in the AlphaSector Premium Index, is the model’s review of each of the nine sectors to be either included or excluded from the portfolio based on likelihood of forward-looking positive return.”
    I would think that transaction costs would be high. It would be interesting to see what rebalancing frequency would be optimum to minimize costs.

    Wade

  2. Edward
    December 8, 2013 at 11:09 pm

    I used the daily data (periodicity = ‘_daily’) downloaded from the F-F website and the model results showed a MaxDD of 42%, not 22% you indicate for daily data, and the EW strategy wins with CAGR 8.15% vs Strategy1 CAGR of 6.79%. Is there something I’m missing here?

    • December 10, 2013 at 12:44 am

      Strange, I used XLY,XLP,XLE,XLF,XLV,XLI,XLB,XLK,XLU sectors ETFs and got maximum draw-down of around 22% for 2001 – 2013 period

  3. blotka
    December 8, 2013 at 11:34 pm

    Thank you for sharing this very interesting code.
    Do you intend to combine your previous code(s) (for example: https://systematicinvestor.wordpress.com/2011/12/06/multi-asset-backtest-rotational-trading-strategies/) with this one to include the cash positions depending on the market conditions but instead of using ‘F-F_Research_Data_Factors’ getting up to date ETF Sectors data from Yahoo?

  4. Brent
    December 27, 2013 at 2:24 pm

    Michael, thank you so much for hacking this strategy!

  5. Brent
    January 7, 2014 at 12:39 pm

    Looking through your code, it looks like you made a 10dma (or whatever lookback you want) trading strategy and not a volatility breakout strategy like Meb talks about. I believe the strategy you are after would be most of an ATR band or Bollinger Band strategy in addition to some trend rule (could be above 10d, above 50d, or have a positive 6/12/36M return. Am i reading the Meb system criteria correctly or am i missing something in your code?

    • Edward
      January 8, 2014 at 3:46 am

      I noticed that 10dma code also, but was so caught up with learning SIT code (and R for that matter) that I did not notice it was different than Meb’s article. I also tried modifying the code to do daily (F-F website) and got very different MaxDD and CAGR, even using SPDR sector data, but that may be due to my getting the SIT & Rcoding correct.

  1. No trackbacks yet.

Leave a comment