RESEARCH

82% of our backtest fills were trades we could not have taken live

Study details

Measured
2026-04-15 – 2026-07-31, out-of-sample; audit over 564,303 eligible candidate rows
Instruments
US equities, intraday movers
Method
replay the same model under live subscription constraints — no future watchlist, snapshot stamped at bucket end, no entry before the symbol was first seen
Result
82.2% of the original fills broke at least one constraint, and those fills carried 48% of the profit. With the constraints applied, the edge did not survive
Contents
We had a model we liked. It passed walk-forward, it passed out-of-sample, and the equity curve looked like something you would want to trade. Then we audited whether the trades could have existed at all, and most of them could not. This is a report on that audit, because the failure mode is not specific to us. Any backtest that reads a watchlist from a database has to answer one question: did that list exist, in that form, at the moment the strategy claims to have acted on it.

1. What "eligible" means

Our engine does not see the whole market. It subscribes to a rotating watchlist, and a symbol becomes tradable only after the engine has subscribed to it. That gives four constraints a backtest must respect: - The candidate list at time t must be built only from trades that happened up to t. A list of "symbols that were top movers at some point that day" is future information, even if every price in it is historical. - A ranking built from the window [t, t+30) is only known at t+30. Labelling it t hands the strategy up to 29 seconds of the future. - No entry before first_seen — the moment the engine actually subscribed to that symbol. - Local accumulators (trade strength, day buy amount, running maxima) restart at first_seen, because live they start at zero when the subscription opens. None of these are exotic. They are just the difference between "the data exists in my database" and "my program could have known it".

2. What the audit found

We rebuilt the dataset under those constraints and re-ran the same model family. Original fills that broke at least one constraint: 82.2% Share of total profit carried by those fills: 48% The second number is the one that matters. Even if you assume the violating trades were only mildly optimistic, removing them removes half the result. When we applied the constraints properly and retrained, the edge did not survive. It held up in only 2 of 11 walk-forward windows, and the out-of-sample result turned negative.

3. We checked whether anything was salvageable

A reasonable objection: maybe the model still ranks eligible candidates well, and we only lost the impossible trades. We tested that directly over the full set of eligible candidate rows. Rows examined: 564,303 Correlation between model score and realised return: -0.0075 Deciles: no monotonic relationship Top 1% by score: still negative So the ranking carried no information on the trades we could actually take. The model had not learned an edge that happened to include some impossible fills; the impossible fills were the edge.

4. Where the violating trades actually came from

We also checked the shape of the 1,714 violating trades, because the natural assumption is that they were obscure names the engine picked up hours later. That is not what happened. At the moment of purchase, their median rank across the whole market was 206 (p25 107, p75 429). Symbols ranked inside the top 500 accounted for 75.7% of the violating profit, and the "discovered hours later, thousands of ranks away" pattern accounted for 0.5%. In other words, the trades were not exotic. They were ordinary active names that our engine simply had not subscribed to yet. That is a coverage problem, not a data-cleanliness problem, and it points at a different fix: watch more of the market, earlier.

5. What we changed

The rules above are now enforced when the dataset is generated, not checked afterwards. Recording starts at first_seen. Snapshots are stamped at the end of their bucket. Local accumulators reset on subscription. An audit pass runs over the generated rows and fails the build if a single row violates a rule. We would rather have a smaller result we can trust than a larger one we cannot reproduce live. Conclusion The useful question about a backtest is not how good the result is. It is whether every input was available at the moment the strategy claims to have used it. We asked that question late, and it cost us the result. Asking it first is cheaper.

Limitations

  • This is one model family, one market (US equities), one session type, and one out-of-sample window (2026-04-15 to 2026-07-31). It is not a claim that 82% of all backtests are wrong.
  • The 82.2% and 48% figures describe our v1 dataset specifically. A backtest that subscribes to the entire market, or that never uses a rotating watchlist, will not have this failure mode at all.
  • The salvage test used our own scoring model. It shows that this model had no edge on eligible candidates; it does not prove no model could.
  • The eligibility rules encode how our engine subscribes. A different engine has different constraints, and would need its own audit.
  • One further rule — a small availability delay between the exchange timestamp and what a broker feed makes visible. Is measured but not yet finalised, so it is not included in these results.

Related reading

← All research

Originally published by TraderWe on August 11, 2026. You may quote and link to this page. Republishing the full text without a link back to the original is not permitted.

5 replies

M
MomoQueen· Aug 2026 ago
oof the first_seen one hits me right in the chest!! my best entries in backtest were always the first 30-60 seconds of a runner and live that is EXACTLY when i'm still waiting for the sub to open. did you look at how much of that 48% profit was just the first minute after first_seen vs spread across the hold?
Q
QuietVol· Aug 2026 ago
The part I keep turning over is whether the surviving fills are just a smaller sample of the same distribution or a genuinely different one. My guess is the constrained set skews toward slower, later entries with fatter loss tails. Did you look at the shape of the survivor returns, or only whether the aggregate edge held?
F
FiveMinFiona· Aug 2026 ago
Useful framing, thank you. What I'd want spelled out is the subscription timing itself — if a symbol enters the watchlist mid-bar, does your replay treat it as available at the close of that 5-minute bucket or the next one? That single choice would move a lot of my own entries.
ZenTrader_Ana· Aug 2026 ago
There's something humbling about liking a curve for months and then finding out the trades inside it were never yours to take. I notice how much of my confidence comes from the picture rather than the mechanics behind it.
GrandpaGrizzly· Aug 2026 ago
Seen this exact ghost since the days we hand-wrote fills on paper, the list you're testing against was assembled after the fact, and hindsight is the cheapest data feed there is. Good on you for publishing the autopsy instead of quietly re-tuning. Stay humble.
Sign in to reply →