Tuesday, December 13, 2011

EOM Momentum Stocks Strategy with R

Currently I am learning how to use R for my research. I have found a lot of great sites and blogs giving me a lot of insight into the language as well as useful resources like toolboxes and packages for R.

As I was investigating the turn/end of month (EOM) effekt in some former posts I will now use the R-code and great toolbox from Systematic Investor to show how a momentum stocks strategy may be combined with an EOM strategy. The original code was used to test this effect with sector ETFs. 
Thus I simply changed the code in order to test my idea to use high momentum stocks (see code) for a 2 days holding period from closing of the last day of the month. In addition to the momentum selection criteria, only stocks are chosen if the closing was above the 89 day moving average. This idea has been taken from the sector strategy of the Quanting Dutchman blog.

As the total return stocks data of the current NASDAQ 100 components from Yahoo is used, the results show a huge systematic survivourship bias. Just look at the green equal weight portfolio results. But the possible edge of the strategy can be observed by the comparison of the EOM equal weight (red line) and the EOM Top 3 momentum ranked stocks (black line).    


The results are not very promising as there is somehow a positive edge for the momentum selection (eom.top.rank1 vs eom.equal.weight) but not for the EOM strategy (eom.equal.weight vs. equal weight).
So I decided to apply the WMA filter in a different way. Now trades will only be made when the index is above the 89 period WMA. Something I use in a quite similar way for filtering my seasonal trading.





The results improve dramatically. On the one hand performance is much better and even more interesting the max draw down is only -11% vs -32,6% before.
The R-code using the Systematic Investor Toolbox:  

###############################################################################
# maQuant Rotational Trading EOM : 
# based on work of
# http://www.etfscreen.com/sectorstrategy.php (Strategy)
# Quanting Dutchman: http://quantingdutchman.wordpress.com/2010/06/30/strategy-2-monthly-end-of-the-month-meom/ (Strategy)
# Systematic Investor: http://systematicinvestor.wordpress.com/2011/12/09/simple-and-profitable/ (R-code and Toolbox SIT!)
###############################################################################
# Load Systematic Investor Toolbox (SIT)
 setInternet2(TRUE)
 con = gzcon(url('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', 'rb'))
     source(con)
 close(con)
 
 #*****************************************************************
 # Load components & historical data
 #****************************************************************** 
 load.packages('quantmod')
 
 source="yahoo"
 index="^NDX"
 start='2000-01-01'
 
 # Components from NASDAQ Website
 url = paste('http://www.nasdaq.com/markets/indices/nasdaq-100.aspx',sep="")
 txt = join(readLines(url)) 
 
 # extract tables from this pages
 temp = extract.table.from.webpage(txt, 'Symbol', hasHeader = T)
 temp[,2]
 
 # Symbols
 symbols = c(temp[,2])[2:101]
 
 data = new.env() # Stock Data
 data2 = new.env() # Index
 getSymbols(c(index), src=source, from=start, env = data2, auto.assign=T, warnings=FALSE)
 getSymbols(symbols, src=source, from=start,  env = data, auto.assign=T, warnings=FALSE) 
        for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T)
 bt.prep(data, align='keep.all', dates='2000::2011')
 bt.prep(data2, align='keep.all', dates='2000::2011')
 
 #*****************************************************************
 # Begin code strategies
 #****************************************************************** 
 prices = data$prices   
 prices2 = data2$prices 
 
 n = ncol(prices)
 nperiods = nrow(prices)
 
 #*****************************************************************
 # FOR COMPARISON: 
 #*****************************************************************
 
 # Equal Weight for Comparison !Survivorship Bias!
 data$weight[] = ntop(prices, n)
 equal.weight = bt.run(data) 
 
 # find month ends, entry and exit dates
 holding.period=2 
 month.ends = endpoints(prices, 'months')
  month.ends = month.ends[month.ends > 0]  
 month.ends2 = iif(month.ends + holding.period > nperiods, nperiods, month.ends + holding.period)
 
 # EOM INDEX Strategy
 data2$weight[] = NA
  data2$weight[month.ends,] =  ntop(prices2, 1)[month.ends,]
  data2$weight[month.ends2,] = 0
  capital = 100000
  data2$weight[] = (capital / prices2) * data2$weight
 eom.index = bt.run(data2, type='share')
 
 # EOM Equal Weight Strategy 
 data$weight[] = NA
  data$weight[month.ends,] = ntop(prices, n)[month.ends,] 
  data$weight[month.ends2,] = 0
 
  capital = 100000
  data$weight[] = (capital / prices) * data$weight
 eom.equal.weight = bt.run(data, type='share')
 
 #*****************************************************************
 # MOMENTUM EOM STRATEGY: 
 #*****************************************************************
 
 # BuyRule = C > WMA(C, WMA.period)
 WMA.period=89 # Apply x day average for Index
 buy.rule = prices2 > bt.apply.matrix(prices2, function(x) { WMA(x, WMA.period) } )  
  buy.rule = ifna(buy.rule, F)
 buy.rule2=prices
  buy.rule2[,1:n]=buy.rule
 
 lag.period=20 # Apply x day momentum for Stocks
 ret2 = ifna(prices / mlag(prices, lag.period), 0)
 
 # Rank1 = MA( C/Ref(C,-lag.period), 5 ) * MA( C/Ref(C,-lag.period), 40 ) / Simply taken from original Systematic Investor Post
 position.score = bt.apply.matrix(ret2, SMA, 5) * bt.apply.matrix(ret2, SMA, 40) 
  position.score[!buy.rule2] = NA
 
 pos.number=3 # Number of Stocks choosen 
 # Strategy EOM - top X    
 data$weight[] = NA;
  data$weight[month.ends,] = ntop(position.score[month.ends,], pos.number)  
  data$weight[month.ends2,] = 0  
 
  capital = 100000
  data$weight[] = (capital / prices) * data$weight
 eom.top.rank1 = bt.run(data, type='share', trade.summary=T)
 
 #*****************************************************************
 # Create Report
 #****************************************************************** 
 
 report.name=paste('EOM2', start, index, 'WMA', WMA.period, 'lag',lag.period, 'posnum',pos.number, 'hold',holding.period, sep=" ")
 
 # put all reports into one pdf file
 pdf(file = paste(report.name,'.pdf',sep=""), width=8.5, height=11)
  plotbt.custom.report(eom.top.rank1, eom.equal.weight, equal.weight, eom.index, trade.summary=T)
 dev.off() 
 
 png(filename =  paste(report.name,' 1.png',sep=""), width = 600, height = 500, units = 'px', pointsize = 12, bg = 'white')          
  plotbt.custom.report.part1(eom.top.rank1, eom.equal.weight, equal.weight, eom.index,trade.summary=T)
 dev.off() 
 
 png(filename = paste(report.name,' 2.png',sep=""), width = 1200, height = 800, units = 'px', pointsize = 12, bg = 'white') 
  plotbt.custom.report.part2(eom.top.rank1, eom.equal.weight, equal.weight, eom.index,trade.summary=T)
 dev.off() 
 
 png(filename = paste(report.name,' 3.png',sep=""), width = 1200, height = 2000, units = 'px', pointsize = 12, bg = 'white') 
  plotbt.custom.report.part3(eom.top.rank1, eom.equal.weight, equal.weight, eom.index,trade.summary=T)
 dev.off()

I am sure that there is a lot of room for further improvement. Some of the selected stocks might be too heavily overbought. The holding period of the first two trading days of the month seems to be optimal for the EOM strategy but some systematic reaction to the performance of the single trades (stops, targets, holding period) might be useful in order to improve the overall results. Please keep in mind that the results are hypothetical not reflecting any costs of trading. 


Disclaimer

The information on this site is provided for statistical and informational purposes only. Nothing herein should be interpreted or regarded as personalized investment advice or to state or imply that past results are an indication of future performance. The author of this website is not a licensed financial advisor and will not accept liability for any loss or damage, including without limitation to, any loss of profit, which may arise directly or indirectly from use of or reliance on the content of this website(s).  

Under no circumstances does this information represent an advice or recommendation to buy, sell or hold any security.


3 comments:

Craig said...

Can you explain in more detail how the green line displays survivourship bias?

systematicinvestor said...

Andreas,

Good idea and Great post.

Thank you for using the Systematic Investor Toolbox in your posts.

Andreas Marquardt said...

As the stocks used are still part of the NDX they are successful anyway. The green line reflects the historical performance of an equally weighted portfolio of the stocks now part of the Nasdaq 100. Stocks that have failed und were taken out of the index are not considered (as I have no corresponding data) and successful stocks that were not part of the index before are part of the backtest as they are now in the list. A great post about the bias can be found there:
http://engineering-returns.com/2010/09/02/the-impact-of-survivorship-free-backtesting/