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)
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.
Categories: Asset Allocation, Backtesting, Portfolio Construction, R
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
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?
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
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?
Michael, thank you so much for hacking this strategy!
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?
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.