I usually write my backtests in Python or a dedicated backtesting environment, but I wanted to experiment with some of R's predictive analytics functionality. I wrote an extremely simple backtester for doing some quick and dirty research, but wind up with some obvious bias in my results. I assume the bias is related to future-peeking, but I am not sure.
The code uses a rolling window approach and creates a list of adjacent training/testing windows, as per a standard walk-forward approach. For each training window, it fits a simple linear regression model based on some lagged variables and the next-day's return. It then tests the model on the adjacent testing window. After looping through the list of windows, the results are aggregated and plotted.
Based on my code and output shown below, is it obvious where the bias is coming from? Looking at the results, I must have somehow mixed my training and testing windows, or possibly I am not understanding the correct way to lag the dependent variables. Either way, I am stuck and any advice would be greatly appreciated.
## Libraries
library(caret);library(quantmod);library(TTR);require(ggplot2);require(reshape)
## Data Acquisition and Wrangling
# download data from yahoo/google
getSymbols("SPY", from="2010-01-01")
# create returns series and some indicators
# remember to lag indicators to prevent look-ahead bias!
ret <- Delt(Op(SPY), Cl(SPY)) #day session return
atr <- ATR(SPY[, c(2:4)], 14)$atr
rsi <- RSI(Cl(SPY), n=14, maType="SMA") #relative strength index
dpo <- DPO(Cl(SPY)) #de-trended price oscillator
dat <- cbind(ret, lag(atr, 1), lag(rsi, 1), lag(dpo, 1))
colnames(dat) <- c("ret", "atr", "rsi", "dpo")
dat <- as.data.frame(dat[complete.cases(dat), ]) #get rid of rows with NA, NaN etc
## Set up train-test windows
# create indexes for TSCV windows
init = 200 #initial window
horiz = 50 #prediction horizon
wdw <- createTimeSlices(1:nrow(dat), initialWindow = init, horizon = horiz,
skip = horiz-1, fixedWindow = TRUE)
trainSlices <- wdw[[1]]
testSlices <- wdw[[2]]
# verify visually correct window setup:
trainSlices[[length(trainSlices)]]
testSlices[[length(testSlices)]]
# train and test linear regression model on rolling window
ModelReturn <- list() #store return of each out-of-sample window
BHReturn <- list() #store buy and hold returns for comparison
for(i in c(1:length(testSlices)))
{
model <- lm(ret ~., data=dat[trainSlices[[i]], ])
preds <- predict(model, newdata=dat[testSlices[[i]], -1])
returns <- ifelse(preds>0, dat[testSlices[[i]], 1], -dat[testSlices[[i]], 1])
ModelReturn[[i]] <- returns
BHReturn[[i]] <- dat[testSlices[[i]], "ret"]
}
# plot results
results <- data.frame(unlist(ModelReturn), unlist(BHReturn))
colnames(results) <- c("Model", "BuyHold")
plot(cumprod(1+results$Model)-1, type='l', col='blue', ylab="Cum.Return")
lines(cumprod(1+results$BuyHold)-1, col='red')