Backtesting Part 2: Splits, Dividends, Trading Costs and Log Plots

[This article was first published on Modern Toolmaking, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Note: This post is NOT financial advice!  This is just a fun way to explore some of the capabilities R has for importing and manipulating data.  

In my last post, I demonstrated how to backtest a simple momentum-based stock trading strategy in R.  However, there were a few issues with my implementation: I ignored splits, dividends and transaction costs, all of which can have a large impact on a strategy.  I also came up with a better plot to help show how a given strategy compares to a benchmark, and wrapped everything together into one function.

First of all, we need to reload our functions from the last post.  These functions define our strategy and analyze its performance.
rm(list = ls(all = TRUE))
daysSinceHigh <- function(x, n){
apply(embed(x, n), 1, which.max)-1
}
myStrat <- function(x, nHold=100, nHigh=200) {
position <- ifelse(daysSinceHigh(x, nHigh)<=nHold,1,0)
c(rep(0,nHigh-1),position)
}
Performance <- function(x) {
cumRetx = Return.cumulative(x)
annRetx = Return.annualized(x, scale=252)
sharpex = SharpeRatio.annualized(x, scale=252)
winpctx = length(x[x > 0])/length(x[x != 0])
annSDx = sd.annualized(x, scale=252)
DDs <- findDrawdowns(x)
maxDDx = min(DDs$return)
maxLx = max(DDs$length)
Perf = c(cumRetx, annRetx, sharpex, winpctx, annSDx, maxDDx, maxLx)
names(Perf) = c("Cumulative Return", "Annual Return","Annualized Sharpe Ratio",
"Win %", "Annualized Volatility", "Maximum Drawdown", "Max Length Drawdown")
return(Perf)
}
view raw 1. Functions.R hosted with ❤ by GitHub


Next, we can test our strategy.  I’ve added a couple new indexes:
testStrategy <- function(symbol, nHold=100, nHigh=200, cost=.005, ylog=FALSE, wealth.index = FALSE, ...) {
require(quantmod)
#Load Data
myStock <- getSymbols(symbol,from='1900-01-01')
myStock <- adjustOHLC(get(myStock),symbol.name=symbol)
myStock <- Cl(myStock)
#Determine position
myPosition <- myStrat(myStock,nHold,nHigh)
bmkReturns <- dailyReturn(myStock, type = "arithmetic")
myReturns <- bmkReturns*Lag(myPosition,1)
myReturns[1] <- 0
names(bmkReturns) <- symbol
names(myReturns) <- 'Me'
#Add trading costs
trade = as.numeric(myPosition!=Lag(myPosition,1))
trade[1] = 1
trade = trade*cost
myReturns = myReturns-trade
#Make plot
require(PerformanceAnalytics)
symbol <- sub('^','',symbol,fixed=TRUE)
Title <- paste('High=',nHigh,' Hold=',nHold,' on ',symbol,sep='')
if (ylog) {wealth.index = TRUE}
layout(matrix(c(1, 2, 3)), height = c(2, 1, 1.3), width = 1)
par(mar = c(1, 4, 4, 2))
chart.CumReturns(cbind(bmkReturns,myReturns), main=Title, ylab = "Cumulative Return",
wealth.index = wealth.index,ylog=ylog,...)
chart.RelativePerformance(myReturns,bmkReturns, ylab = "Relative Return", main = "")
chart.Drawdown(cbind(bmkReturns,myReturns),legend.loc = 'bottomleft', ylab = "Drawdown", main = "")
#Return Benchmarked Stats
cbind(Me=Performance(myReturns),Index=Performance(bmkReturns))
}
testStrategy('^GSPC',100,200,ylog=TRUE)
testStrategy('^FTSE',100,200)
testStrategy('^DJI',100,200,ylog=TRUE)
round(testStrategy('^N225',100,200),8)
testStrategy('EEM',100,200)
testStrategy('EFA',100,200)
testStrategy('GLD',100,200)
view raw 2. Analysis.R hosted with ❤ by GitHub


This function does a lot of lifting:
1. It loads the data and adjusts the closing price for splits and dividends.  It uses the splits/dividends data from yahoo, but performs its own, more accurate calculations.
2. It determines a position series, based on the “daysSinceHigh” function.  This part is the same as in my last post.
3. It determines trades, which are defined as days when today’s position is different from the previous day’s positions.  I assumed that transactions costs are 0.5% of equity, so on trading days I subtracted 0.005 from my Returns.
4. It makes a plot.  This plot is different from the charts.PerformanceSummary we used last time.  The first plot shows cumulative returns of my strategy and the index, while the second plot shows the relative performance of my strategy over the benchmark (also known as alpha).  The third plot shows drawdowns.
5. It returns a data table of statistics, comparing the strategy to the benchmark.

I tested this strategy on GSPC, FTSE, DJI, N225, EEM, EFA, and GLD. (The last 3 are ETFs).  The strategy performs well on some indexes, and poorly on others. Here’s the results of my backtest:
GSPC (US Large Cap):
Me Index
Cumulative Return 68.11285310 71.15426170
Annual Return 0.07114901 0.07189781
Annualized Sharpe Ratio 0.61792340 0.46535064
Win % 0.54320873 0.53242454
Annualized Volatility 0.11514213 0.15450243
Maximum Drawdown -0.33509517 -0.56775389
Max Length Drawdown 1378.00000000 1898.00000000
FTSE (UK and Ireland):
Me Index
Cumulative Return 3.27952156 3.8404476
Annual Return 0.05421708 0.0589431
Annualized Sharpe Ratio 0.43879933 0.3298633
Win % 0.53084556 0.5239884
Annualized Volatility 0.12355779 0.1786895
Maximum Drawdown -0.40641281 -0.5256991
Max Length Drawdown 2415.00000000 2960.0000000
DJI (US Large Industrial):
Me Index
Cumulative Return 54.43942856 46.53410275
Annual Return 0.04975879 0.04780746
Annualized Sharpe Ratio 0.39209260 0.25940805
Win % 0.53114609 0.52286637
Annualized Volatility 0.12690571 0.18429444
Maximum Drawdown -0.47873652 -0.89185928
Max Length Drawdown 2939.00000000 6301.00000000
N225 (Japan):
Me Index
Cumulative Return 1.19191792 -0.12146268
Annual Return 0.02944405 -0.00477699
Annualized Sharpe Ratio 0.20219646 -0.02053661
Win % 0.52668464 0.51337842
Annualized Volatility 0.14562099 0.23260863
Maximum Drawdown -0.48041098 -0.81871261
Max Length Drawdown 5342.00000000 5342.00000000
EEM (Emerging Markets):
Me Index
Cumulative Return 1.3516523 2.6866421
Annual Return 0.1067269 0.1673357
Annualized Sharpe Ratio 0.4699541 0.4733869
Win % 0.5393333 0.5437766
Annualized Volatility 0.2271008 0.3534862
Maximum Drawdown -0.2623762 -0.6709596
Max Length Drawdown 488.0000000 980.0000000
EFA (Developed Markets):
Me Index
Cumulative Return 0.46097518 0.37854022
Annual Return 0.03845159 0.03246626
Annualized Sharpe Ratio 0.23270321 0.12961493
Win % 0.53257143 0.53269537
Annualized Volatility 0.16523875 0.25048242
Maximum Drawdown -0.25703652 -0.61761011
Max Length Drawdown 589.00000000 980.00000000
GLD (Gold):
Me Index
Cumulative Return 1.7309002 2.9607932
Annual Return 0.1583762 0.2231517
Annualized Sharpe Ratio 0.9378088 1.0499845
Win % 0.5548335 0.5519860
Annualized Volatility 0.1688790 0.2125286
Maximum Drawdown -0.2326084 -0.2941414
Max Length Drawdown 433.0000000 379.0000000
view raw 3. Results.txt hosted with ❤ by GitHub


As you can see, this strategy tends to reduce drawdowns, but it also sometimes reduces overall returns.  In some cases, you could leverage up the strategy, which would increase both returns and drawdowns, but that’s the subject of another post.

Here’s a buncha charts:


To leave a comment for the author, please follow the link and comment on their blog: Modern Toolmaking.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)