The biggest bug: same-bar execution
The signal used the closing price and the trade also executed at that close. The model was therefore trading on information it could not yet possess.
A practical walkthrough of the checks that turned a spectacular backtest into a credible, reproducible research result.
Verify that every decision uses only information available at that timestamp.
Separate signal generation from the price at which a trade can actually execute.
Apply fees and slippage on every turnover event, not only on the final result.
Use holdout data and permutation tests before accepting an apparent edge.
The first version appeared exceptional. That was the warning, not the victory. We froze development and audited the data path, timing assumptions, costs and statistical validation before changing another parameter.
Each finding changed the result and became a permanent audit control.
The signal used the closing price and the trade also executed at that close. The model was therefore trading on information it could not yet possess.
Frequent rebalancing looked free. For a high-turnover strategy, modest fees and slippage can consume most of the apparent edge.
Rows with incomplete rolling indicators were allowed into evaluation. That made early observations inconsistent with the fully formed model.
The same period was used to choose parameters and to advertise performance. This turns the test set into training data.
A positive backtest alone does not show that the signal contains information. Random alignment can produce impressive equity curves.
Less spectacular, but now useful for making decisions.
The fastest first check is to compare a position with its lagged version. If performance collapses only when the signal is shifted, inspect the execution timing before doing any more research.
# Signal known after the close
position = signal.shift(1)
turnover = position.diff().abs().fillna(0)
strategy_return = position * asset_return
net_return = strategy_return - turnover * cost_rate
Document when inputs become available, when the signal is known and when an order can execute.
Check a small sample row by row, including position changes, fees, slippage and compounding.
Lock the test period before tuning and do not repeatedly optimize against its result.
Vary costs, execution delay and nearby parameters, then compare performance with a null distribution.