Normalmente escribo mis backtests en Python o en un entorno de backtesting dedicado, pero quería experimentar con algunas de las funcionalidades de análisis predictivo de R. Escribí un backtester extremadamente simple para hacer algunas investigaciones rápidas y sucias, pero terminé con algún sesgo obvio en mis resultados. Supongo que el sesgo está relacionado con la búsqueda de futuros, pero no estoy seguro.
El código utiliza un enfoque de ventana móvil y crea una lista de ventanas de entrenamiento/prueba adyacentes, según un enfoque estándar de walk-forward. Para cada ventana de entrenamiento, ajusta un modelo de regresión lineal simple basado en algunas variables retardadas y en el rendimiento del día siguiente. A continuación, prueba el modelo en la ventana de prueba adyacente. Tras recorrer la lista de ventanas, los resultados se agregan y se representan.
Basándome en mi código y en la salida que se muestra a continuación, ¿es obvio de dónde viene el sesgo? Mirando los resultados, debo haber mezclado de alguna manera mis ventanas de entrenamiento y de prueba, o posiblemente no estoy entendiendo la forma correcta de retrasar las variables dependientes. En cualquier caso, estoy atascado y cualquier consejo sería muy apreciado.
## 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')