UPDATE 9/21/2011: Costas correctly points out that I should lag my strategy vector by one day, as today's returns are determined the position we chose yesterday. I updated the code, results, and charts to reflect this. Please alert me to any similar errors in the future.
I recently read a post on ETF Prophet that explored an interesting stock trading strategy in Excel. The strategy is simple: Find the high point of the stock over the last 200 days, and count the number of days that have elapsed since that high. If its been more less than 100 days, own the stock. If it's been more than 100 days, don't own it. This strategy is very simple, but it yields some impressive results. (Note; however, that this example uses data that has not been adjusted from splits or dividends and could contain other errors. Furthermore, we're ignoring trading costs and execution delays, both of which affect strategy performance.)
Implementing this strategy in R is simple, and provides numerous advantages over excel, the primary of which is that pulling stock market data into R is easy, and we can test this strategy on a wide range of indexes with relatively little effort.
First of all, we download data for GSPC using quantmod. (GSPC stands for the S&P 500 index). Next, we construct a function to calculate the number of days since the n-day high in a time series, and a function to implement our trading strategy. The latter function takes 2 parameters: the n-day high you wish to use, and the numbers of days past that high you will hold the stock. The example is 200 and 100, but you could easily change this to the 500-day high and see what happens if you hold the stock 300 days past the high before bailing out. Since this function is parameterized, we can easily test many other versions of our strategy. We pad the beginning of our strategy with zeros so it will be the same length as our input data. (If you wish for a more detailed explaination of the daysSinceHigh function, see the discussion on cross-validated).
We multiply our position (0,1) vector by the returns from the index to get our strategy's returns. Now we construct a function to return some statistics about a trading strategy, and compare our strategy to the benchmark. Somewhat arbitrarily, I've decided to look at cumulative return, mean annual return, sharpe ratio, winning %, mean annual volatility, max drawdown, and max length drawdown. Other stats would be easy to implement.
Results:
As you can see, this strategy compares favorably to the default "buy-and-hold" approach.
Finally, we test our strategy on 3 other indexes: FTSE which represents Ireland and the UK, the Dow Jones Industrial Index, which goes back to 1896, and the N225, which represents Japan. I've functionalized the entire process, so you can test each new strategy with 1 line of code:
Results:
FTSE:
DJIA:
N225:
The strategy out-performs the other indexes as well. It even performs better than the N225 index, mainly by staying out of it.
Feel free to test this strategy with other parameters, or on other indexes. For homework, think of possible ways that I have fooled myself in this backtest, and post them in the comments. One example of this is that we have't looked at transaction costs, which might be significant...




Love it. Well done.
ReplyDeleteOne small suggestion especially on the Dow would be to use ylog=TRUE on the PerformanceSummary.
Look forward to lots more.
Perhaps you should test sensitivity to parameters: 200 days and a 100 days. Additionally, backtesting is most useful for recent times. If a strategy worked in 1920 it really doesn't make it much more credible today.
ReplyDelete@timelyportfolio: Thanks You! @David Gonzales: that will be the subject of my next post.
ReplyDeleteI am not sure about these but....
ReplyDelete1. Shouldn't you lag your strategy 1 day backwards?
2. in the performance charting, shouldn't you use geometric=FALSE?
Best,
c.
@Costas:
ReplyDelete1. I'm assuming you can compute and execute the entire strategy instantly, at the end of each trading day. This isn't possible, but you CAN compute the strategy 5 minutes before the close,and execute a Buy-At-Close order.
2. I'm actually not sure. Run the code with geometric=FALSE and note the difference in the charts. It seems that ylog=TRUE and geometric=FALSE result in the same chart, but with a different y axis. I'm leaning towards geometric=TRUE, but perhaps someone with more knowledge could weigh in here.
Dear Zach,
ReplyDeleteThanks for your reply.
However, unless I am aligning the data wrongly:
When you go long (say the day of the new high after being out of the market for some days) you place 1 on that date's closing price. Hence you benefit from that trading day's return (over the previous one's) if it is positive. However, you are not in the market (unless you entered at the previous day's close while the new day's high had not been realized yet - so how did you know about it beforehand???). So, my point is that if you enter on day 't' closing time, the value of the strategy should be 0 on that date and 1 on the following trading day...
This does not apply for when you close your position since you know beforehand you are going to keep it for 'n' days. So that part seems ok.
Again, apologies if this is due to me misaligning the returns data with the prices and your strategy.
@Costas: You are correct. I updated my post.
ReplyDeleteGlad to be of help!
ReplyDeleteHi,
ReplyDeleteI am not well enough informed to comment beyond saying you hit your mark and more given your introductory note.
I get the following (probably my problem, but I thought it was worth reporting):
> testStrategy(na.omit(DJIA),100,200)
Error in `*.default`(bmkReturns, Lag(myPosition, 1)) :
non-conformable arrays
Hi margareeman,
ReplyDeleteTry running "getSymbols('DJIA', src='FRED')" and then type "head(DJIA)" and make sure there is something there. I just re-ran all the code from this post without errors.
Make sure you are using an up-to-date version of R (such as 2.14 or 2.15). Also try doing a fresh install of quantmod and PerformanceAnalytics.
-Zach