Your Backtest Is Lying to You

Maciej Lewandowski · July 2026 · Engineering notes

I built a scoring and backtesting system for my own investing: a multi-factor stock ranker on one side, a replay engine on the other. The first equity curve it produced was gorgeous. Smooth, steep, barely a drawdown. I did not feel clever. I felt suspicious, because I have written enough systems to know that when a program tells you something wonderful on the first run, the program is usually describing itself rather than the world.

It was. The curve was a bug report written in the notation of finance. Here is what was in it, and why almost every one of these failures is a familiar systems problem wearing a ticker symbol.

Look-ahead bias, the version that actually gets you

Nobody writes buy(tomorrow_close). The real leaks are quieter:

The fix is not vigilance. Vigilance fails at 1am when you are adding one more feature. The fix is an invariant that the engine cannot violate: at simulated time T, the only data reachable is data whose timestamp is less than or equal to T, enforced by the API, not by the author's discipline. Point-in-time storage with two timestamps per record, the event time and the time it became knowable, and an accessor that takes T and physically cannot return anything else. Make the wrong thing unrepresentable and the class of bug disappears instead of moving around.

look-ahead leak visible: timestamp <= T must not be visible decision at T

Survivorship: your universe is a list of winners

Ask where your universe of stocks came from. If the answer is "today's index members" or "everything my data provider currently lists", the backtest is running on companies pre-selected for the property of still existing. Everything that went bankrupt, got delisted, or was acquired at a discount has been quietly removed from history, and those were precisely the positions that would have hurt.

Testing "the S&P 500 constituents" with today's membership list compounds the problem, because index membership itself is a momentum and quality filter. Companies join after doing well and leave after doing badly. Backtesting today's members over the last decade means buying, in 2016, the firms that a committee would later decide had earned their place. You need historical constituent lists with the dates they changed, and a universe that includes the dead. A strategy that only works on survivors has discovered survival, not alpha.

The point-in-time problem, generally

Survivorship and look-ahead are two symptoms of one disease: history is not a fixed object. It gets rewritten under you.

Run the same backtest against "the same" data twice, a month apart, and get two different equity curves. That is not a market observation. That is a reproducibility failure, and it means the number you are staring at belongs to your data pipeline rather than to your strategy.

Overfitting: the best of 200 tries is a measurement of noise

Search a parameter grid of 200 combinations and report the winner, and you have not found a strategy, you have run a lottery and reported the ticket that hit. The more configurations you test, the higher the best in-sample result you should expect from pure chance, which means the winner's performance needs to be discounted by how hard you searched before it means anything. Two hundred coin-flippers, one of them lands eight heads in a row, and there is nothing special about his wrist.

The countermeasures are unglamorous. Hold out data you genuinely never look at. Use walk-forward: fit on a window, trade the next window blind, roll, and read only the stitched out-of-sample result. Count your experiments honestly, including the ones you abandoned, because the ones you abandoned are part of the search. And remember that "my strategy beat buy-and-hold from 2015 to 2020" is a single sample from a single regime, reported by someone who chose the dates after seeing the data.

Costs the naive engine forgets

The first version of any engine fills every order instantly at the close, in unlimited size, for free. Reality charges for all three:

A strategy whose per-trade edge is smaller than the spread is not an edge, it is an invoice. And an engine that fills you at a price nobody was actually offering is not simulating a market, it is simulating a wish.

Regime dependence: n equals one

Markets are not stationary. The relationships your factors exploit are conditional on a monetary and structural backdrop that changes on a timescale of years. A ten-year backtest feels like a lot of data because it contains thousands of daily bars, but on the variable that dominates asset returns, the interest rate cycle, it may contain exactly one observation. Thousands of samples of the noise, one sample of the thing that matters. Ask what happens to your strategy across a regime it has never seen, and if the honest answer is "unknown", say so in the report.

The engineering frame

Here is the reframing that changed how I built mine. A backtest is a replay engine. It is the same category of system as any event-sourced service that must reconstruct the past faithfully, and the properties that make those systems trustworthy are exactly the ones that make a backtest trustworthy:

If your backtest is not reproducible byte for byte from a pinned dataset, you are not measuring a strategy. You are measuring your data pipeline.

The checklist

  1. Point-in-time access only: at time T the engine can reach nothing timestamped after T, enforced by the API rather than by care.
  2. Signal time and execution time are separate, with a realistic gap between them.
  3. The universe includes delisted, bankrupt and acquired names, reconstituted from historical membership with dates.
  4. Identity keys off a stable security identifier, never off the ticker.
  5. Costs are modelled explicitly: spread, slippage, impact, borrow, commissions, taxes. Then ask whether the edge survives.
  6. Out-of-sample and walk-forward results are the only ones you quote, and you count every experiment you ran.
  7. Every run is pinned to a dataset snapshot plus a code commit, and reruns match byte for byte.
  8. The report states which regimes were covered and, more importantly, which were not.

None of this makes a backtest true. It makes it falsifiable, which is the most a simulation of the past can offer. The beautiful curve is the null hypothesis. Your job is to spend a week trying to break it, and to be genuinely pleased when you succeed, because finding the leak in your own engine is enormously cheaper than finding it in the market.