I’ve long been a fan of Woodshedder’s event studies, so I thought I would do my own investigation into a couple indicators using his visualization technique. Here is an example from his blog:

Source: System Trading with Woodshedder

As you can see, the technique graphs the Avg % profit/loss after the event happens for 50 days. From that you can visually compare different levels of an indicator or compare it to a baseline like the after return of the S&P500. For our purposes I will be plotting the average return (equity curve), which is slightly different but should give similar returns.

Today I thought we could run a simple case of extremely oversold conditions (X < 20) for the % of stocks trading above the 50 day moving average (NYSE-All) indicator to see how they would plot against the average return of the S%P500. Using the Systematic Investor Toolbox (SIT) lets get started….

Let’s load the data….

# 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 historical data
#******************************************************************
load.packages('quantmod,quadprog,lpSolve,kernlab')
require(XLConnect)
require(rJava)
#LOAD DATA
filename = "I:/2013/2013 backtesting/Breadth/SPYbreadthdata.csv"
#Stock Prices
data.SPY = getSymbols('^GSPC', src = 'yahoo', from = '1996-01-01',auto.assign = FALSE)
breadth <- read.csv(filename, header = FALSE)
breadth <- make.xts(breadth[,-1], as.Date(as.character(breadth[,1]),'%Y%m%d'))
colnames(breadth) <- spl('50perc,20perc')[1:ncol(breadth)]
SPY.breadth = merge(data.SPY,breadth)
ret = SPY.breadth$GSPC.Close/mlag(SPY.breadth$GSPC.Close) - 1
ret = na.omit(ret)
SPY.breadth = merge(SPY.breadth,ret)
colnames(SPY.breadth) <- spl('Open,High,Low,Close,Volume,AdjC,p50,p20,ret')[1:ncol(SPY.breadth)]
SPY.breadth = SPY.breadth["1997::"]

Next lets set the variables, calculate the average SP500 return and create a plot

#Set variables
forward.len = 50
threshold.50 = 20
#SP500 average returns for period
SPYonly = matrix(NA, nr=forward.len,nc=1)
for (i in 1:nrow(SPY.breadth) ) {
SPYonly = cbind(SPYonly,coredata(SPY.breadth$ret[(i+1):min((i + forward.len),nrow(SPY.breadth))]))
}
SPYonly.avg = apply(SPYonly,1,mean,na.rm=TRUE)
SPYonly.avg.equity = cumprod(1 + SPYonly.avg)
plot(SPYonly.avg.equity, type = 'l',col = 'blue', main = 'SP500 average return', ylim = range(SPYonly.avg.equity),axes = TRUE,ann=FALSE)
title(xlab="# of Days trade is held")
title(ylab="Avg Equity (Day 0 = 1)")
title(main ="SP500 average return")

Here’s what it looks like:

Finally, lets calculate the returns from the indicator being oversold and plot them. Note that I added the criteria of having to be above 20 for the indicator on the previous and then below the next day for the event to trigger. You can play around with different combinations of this too, ie. didn’t trigger last X days.

#Oversold Indicator returns
results = matrix(NA, nr=forward.len,nc=1)
for (i in 2:nrow(SPY.breadth) ) {
if (SPY.breadth$p50[i] <= threshold.50 && SPY.breadth$p50[i-1] > threshold.50 ) {
results = cbind(results,coredata(SPY.breadth$ret[(i+1):min((i + forward.len),nrow(SPY.breadth))]))
}
}
results2 = results
results2.avg = apply(results2,1,mean,na.rm=TRUE)
results2.min = apply(results2,1,min,na.rm=TRUE)
results2.max = apply(results2,1,max,na.rm=TRUE)
results2.stdev = apply(results2,1,sd,na.rm=TRUE)
results2.1sdH = results2.avg + results2.stdev * 1
results2.1sdL = results2.avg - results2.stdev * 1
results2.avg.equity = cumprod(1 + results2.avg)
results2.1sdH.equity = cumprod(1 + results2.1sdH)
results2.1sdL.equity = cumprod(1 + results2.1sdL)
plot(results2.avg.equity, type = 'l',col = 'blue', main = 'SP500 average returns after %Stocks above 50dma <20', ylim = range(results2.avg.equity),axes = TRUE,ann=FALSE)
lines(SPYonly.avg.equity, type = 'l', col = 'red')
title(xlab="# of Days trade is held")
title(ylab="Avg Equity (Day 0 = 1)")
title(main ="SP500 average returns after %Stocks above 50dma <20")
legend(1,max(results2.avg.equity),c("%Stocks above 50dma <20","SP500 average return"),cex = 0.8,col=c("blue","red"),lty=1)

As you can see the oversold conditions lead to short term under-performance and long term out performance versus the index. Personally I’m interested in seeing the point where the short term weakness is eliminated. We must also consider the shrinking sample size of trades as we narrow the criteria. Next time we’ll play around with different values for the threshold and maybe add some confidence intervals around the equity curves…