This is just a test for fun. What if we buy everything that goes down to a certain point, say -%1 or -%5 at the end of the day everyday? There are probably a bunch of programs out there can do this but I prefer something home-made.
To get a list of all publicly traded stocks on NASDAQ, I went to EOData and grabbed a txt file for all NASDAQ tickers. But the trouble was even though the calculation I wanted to perform was simple, the volume was too large for my computer to handle. So I had to push everything to github, start an Amazon EC2 instance, configure it, pull from my own repository, run my script, then push everything back. The process itself is not complicated, just a lot of annoying details.
Anyhow, before everybody gets bored with my geeky life story, here are the results. The numbers in the legend represent mean-reversion signals. For instance, -0.12 means “what if we buy everything that goes down more than 12%?

It seems as we moveing the signal further away from 0, the strategy becomes more aggressive and performance improves gradually, except for -0.19 and -0.20, which might be two outliers or trend changers.

Source code in R.
## functions
EODMR <- function(returnsData, shockRates = seq(-0.01, -0.2, by = -0.01)) {
output <- mrTest(returnsData, shockRate = 0)
output <- calcRet(output)
outputRet <- output
annRet <- prod(1+output)^(250/length(output))-1
annSd <- sd(output)*sqrt(250)
Sharpe <- annRet/annSd
output <- data.frame(shock0 = rbind(annRet, annSd, Sharpe))
for (i in seq(along = shockRates)) {
testData <- mrTest(returnsData, shockRate = shockRates[i])
finalRet <- calcRet(testData)
outputRet <- merge(outputRet, finalRet)
names(outputRet)[i + 1] <- shockRates[i]
annRet <- prod(1+finalRet)^(250/length(finalRet))-1
annSd <- sd(finalRet)*sqrt(250)
Sharpe <- annRet/annSd
finalRet <- data.frame(rbind(annRet, annSd, Sharpe))
names(finalRet) <- shockRates[i]
output <- cbind(output, finalRet)
print(paste(i, "out of ", length(shockRates), " finished"))
}
return(list(testStat = output, testReturns = outputRet))
}
# download and format returns data
getReturns <- function(tickers, start = Sys.Date() - 2520,
minVol = 100000) {
returnsData <- get.hist.quote("^GSPC", start = start,
quote = "AdjClose")
returnsData <- merge(ROC(returnsData), Next(ROC(returnsData)))
names(returnsData) <- paste("SPX", c("Ret0", "Ret1"), sep = ".")
for (i in seq(along = tickers)) {
nextTicker <- NULL
nextTicker <- try(get.hist.quote(tickers[i], start = start,
quote = c("AdjClose", "Volume")))
if (!is.character(nextTicker[1])) {
meanVol <- as.numeric(mean(nextTicker))
if (meanVol >= minVol) {
nextTicker <- nextTicker$Adj
} else next
nextTicker <- nextTicker[!duplicated(index(nextTicker))]
nextTicker <- merge(ROC(nextTicker), Next(ROC(nextTicker)))
names(nextTicker) <- paste(tickers[i], c("Ret0", "Ret1"), sep = ".")
returnsData <- merge(retournsData, nextTicker)
} else next
print(paste(i, "out of", length(tickers), sep = " "))
}
return(returnsData)
}
# perform MR test
mrTest <- function(returnsData, shockRate = -0.05) {
testRets <- returnsData[, 1:2]
thisName <- gsub(".Ret0", "", names(testRets)[1])
testRets <- ifelse(testRets[, 1] <= shockRate, testRets[, 2], 0)
dim(testRets) <- c(length(testRets), 1)
names(testRets) <- thisName
secNum <- ncol(returnsData) / 2
for (i in 2:secNum) {
thisSec <- returnsData[, (i * 2 - 1):(i * 2)]
thisName <- gsub(".Ret0", "", names(thisSec)[1])
thisSec <- ifelse(thisSec[, 1] <= shockRate, thisSec[, 2], 0)
dim(thisSec) <- c(length(thisSec), 1)
names(thisSec) <- thisName
testRets <- merge(testRets, thisSec)
testRets[is.na(testRets)] <- 0
}
return(testRets)
}
# calculate strategy results
calcRet <- function(testRets) {
timeLength <- nrow(testRets)
finalRet <- rep(0, timeLength)
for (i in 1:timeLength) {
tmp <- as.numeric(testRets[i, ])
tmp <- tmp[tmp != 0]
finalRet[i] <- mean(tmp)
}
finalRet[is.na(finalRet)] <- 0
finalRet <- zoo(finalRet, order.by = index(testRets))
return(finalRet)
}
#### run code
# load packages
library(tseries)
library(quantmod)
library(PerformanceAnalytics)
# perform test
tickersNasdaq <- as.vector(read.table("~/R/EODMR_development/NASDAQ.txt",
sep = "\t")[-1, 1])
testNasdaq <- EODMR(returnsNasdaq)
# visualize results
charts.PerformanceSummary(testNasdaq$testReturns, colorset = rainbow(21), ylog = 1)
chart.RiskReturnScatter(testNasdaq$testReturns, colorset = rainbow(21), symbolset = rep(3, 21))
It works “well” (I mean performance is stratospheric) just because you are using the list of Nasdaq tickers as of today, without taking into account the tickers that disappeared in the meanwhile. These tickers are very important indeed as they include more than a few tickers that went down -20% at some time… and kept on going down rather than recovering back.
You are right Thierry. I didn’t do anything with survivalship bias simply because I didn’t have the data. I wouldn’t worry too much about it though because even among delisted stocks, I have rarely seen anything goes straight down for days until it finally got removed. If that’s the case, momentum strategies would dominate the world.
Why not add a general trend filter just to see what happens? Really good test…
http://backtestingvix.wordpress.com/
http://nightlypatterns.wordpress.com/