Lagging Indicators to Avoid Lookahead Bias in Backtests
Summary
The document explains why a backtest must respect when an indicator becomes observable. A moving average calculated from daily closing prices includes that day’s close, so it cannot support a decision made before the close. Using it to trade earlier on the same day introduces lookahead bias. The same timing issue applies to other indicators, such as RSI or rate of change, when they use that day’s closing price.
The practical rule is to align each signal with the time it could actually have been known and traded. Lagging the moving average by one day is appropriate for a strategy that decides before the current day’s close using only completed daily bars. The exact lag depends on the assumed decision and execution time; a signal observed after the close might instead be traded at a later available price. The answer gives a concise timing explanation but does not discuss intraday data, order execution, or how to model closing-auction trades.
Key ideas
- A closing-price indicator includes information unavailable before that close.
- Using a signal before its inputs are observable creates lookahead bias.
- Lag indicators according to the strategy’s actual decision and execution timing.
- The same timing check applies to RSI, rate of change, and other close-based indicators.
Tags
Full text
# Do we need to lag values for backtesting? # Do we need to lag values for backtesting? I am using R moving average crossover backtest script from eickonomics. But I have a question about this section. ``` #Calculate the moving averages and lag them one day to prevent lookback bias PreviousSMA_50 <- lag(SMA(Cl(data[['SPY']]),50)) PreviousSMA_200 <- lag(SMA(Cl(data[['SPY']]),200)) . . data$weight[] = ifelse(as.integer(PreviousSMA_50>PreviousSMA_200)==1,1,0) #If price of SPY is above the SMA then buy ``` Why does the author need to lag the SMA values? Why can't he simply use unlagged values? ``` PreviousSMA_50 <- SMA(Cl(data[['SPY']]),50) PreviousSMA_200 <- SMA(Cl(data[['SPY']]),200) . . data$weight[] = ifelse(as.integer(PreviousSMA_50>PreviousSMA_200)==1,1,0) ``` Why would we use previous SMA value instead of current SMA value? How does it cause lookahead bias? If we need other indicators do we have to lag those values as well? ``` rsi<-RSI(Cl(data[['SPY']]),50) roc<-ROC(Cl(data[['SPY']]),50) ``` ## Answer by sashkello (score 3, accepted) https://quant.stackexchange.com/a/29966 Because SMA value for a certain day includes that day's closing price. But before the market closing time, obviously it isn't known (because it's in the future), you only know yesterday's closing value. You can not use today's closing price (or any other indicator which is calculated using it) to make any trading decisions or forecasts for this same day.
Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.