A backtest that looks ahead by one bar: the shift(1) fix, shown on six close prices
Posted: Thu Oct 29, 2026 11:11 am
A backtest that looks ahead by one bar: the shift(1) fix, shown on six close prices
The most common bug in vectorised backtests is trading on a signal using the same bar that produced it. Here are six closes and a toy rule: be long if the bar's close is above the previous close.
In the bad version, the signal at bar 1 says the close rose by 6 pips, and the same 6 pips are booked as profit. Up bars give 6, 9 and 8, so 23 pips from three trades with a hundred percent win rate. That's what a leak looks like.
With shift(1) the signal can only be acted on during the next bar. The three positions then earn minus 4, minus 3 and minus 6, a total of minus 13 pips. The strategy went from a genius to a loser by one line.
The check I use now is to shuffle the future: change the last bar's close to a random value and confirm the position and result on earlier bars don't change. If they do, something is reading ahead. Another test is to compare results from running on a series cut at bar N with the same series run on all data, looking at the decisions up to bar N.
If a result looks too good, assume a leak before you assume an edge.
The most common bug in vectorised backtests is trading on a signal using the same bar that produced it. Here are six closes and a toy rule: be long if the bar's close is above the previous close.
Code: Select all
close = pd.Series([1.0850, 1.0856, 1.0852, 1.0861, 1.0858, 1.0866, 1.0860])
ret = close.diff() * 1e4 # pips per bar
sig = (close.diff() > 0).astype(int)
bad = (sig * ret).sum() # +23.0 pips
good = (sig.shift(1) * ret).sum() # -13.0 pips
With shift(1) the signal can only be acted on during the next bar. The three positions then earn minus 4, minus 3 and minus 6, a total of minus 13 pips. The strategy went from a genius to a loser by one line.
The check I use now is to shuffle the future: change the last bar's close to a random value and confirm the position and result on earlier bars don't change. If they do, something is reading ahead. Another test is to compare results from running on a series cut at bar N with the same series run on all data, looking at the decisions up to bar N.
If a result looks too good, assume a leak before you assume an edge.