Home / Blog / Backtest contamination

Your backtest Sharpe is contaminated.

If a research agent with persistent memory helped produce a backtest, there's a question you can't currently answer: did it use anything it couldn't have known at simulation time? For one of the two contamination classes, your vector store can't even represent the question.

2026-07-05 · Ethan Beirne · 6 min read

Contamination class 1

FUTURE_EVENT: the one you already check for.

The obvious form of lookahead: a memory whose underlying event happens after the simulated decision date. Your backtest runs at January 15; the agent recalls a March earnings surprise. Every quant knows to guard against this, and any store with an event timestamp can filter it. If this were the whole problem, a WHERE event_time <= sim_date clause would end the discussion.

Contamination class 2

LATE_REVISION: the one your vector store can't see.

Financial data gets revised. Guidance is raised, GDP prints are corrected, filings are amended. A revision carries the original event's date, but it arrived months later.

The setup

Q3 revenue, event time September 30. In February, the figure is restated. Your agent's memory now holds the corrected number, stamped with its September event time.

The backtest

Simulation checkpoint: January 15. The filter says September < January, so it looks safely historical. The agent recalls the corrected figure and trades on it.

The lie

On January 15, the corrected number did not exist. The agent used information from three weeks in its future while every timestamp it can see says "past." That's lookahead bias wearing a historical date.

A store with one clock cannot represent this, let alone filter it. You need two: event time (when the fact was true in the world) and ingestion time (when your system learned it). Honest recall at simulated time T requires both to precede T. This is the bitemporal property, and it's why bolting a date filter onto a vector database doesn't fix the problem.

Why it inflates results

Revisions are free alpha, in simulation only.

Revisions are systematically informative: figures get corrected toward the truth, and the truth moves prices. An agent that recalls post-revision numbers at pre-revision checkpoints is trading on corrections before they were published. In simulation, that edge is real and shows up directly in the Sharpe ratio. Live, the revision hasn't landed yet, so the edge evaporates, and the strategy you funded is not the strategy you tested. The more your agent's memory accumulates revised data over time, the wider the gap between the contaminated backtest and live performance. The failure mode isn't a crash; it's a quiet overstatement you discover with real money.

The check

Audit the memory, not just the strategy.

Lians ships a contamination detector as an API primitive. Point it at an agent and a simulation checkpoint; it scans every memory the agent possessed and flags both classes:

$ curl -X POST $LIANS/v1/backtest/check \
   -d '{"agent_id":"desk","simulation_as_of":"2026-01-15T00:00:00Z"}'

{
  "memories_checked": 1204,
  "flags": [{
    "contamination_type": "late_revision",
    "event_time": "2025-09-30",
    "ingestion_time": "2026-02-06",   ← arrived 22 days after the checkpoint
    "delta_days": 22.4
  }],
  "contamination_rate": 0.0008,
  "is_clean": false
}

A report with is_clean: true is the artifact a research lead can attach to a backtest before capital moves. And because recall itself can be gated the same way: recall_at(query, as_of=T) refuses anything either clock places after T, so the honest path is to prevent contamination at recall time and use the detector as the independent audit.

In the regulated-memory eval, the lookahead guard is the invariant where the field gap is widest: none of the five competitor stores expose an API for it. Last run 2026-07-04. Reproduce it: python -m benchmarks.compare_regulated.

Try it on your own agent

Two commands to a verdict.

Installpip install lians-sdk[local]
Checkmem.backtest_check(agent_id="desk", simulation_as_of="2026-01-15")

Local mode runs on SQLite: no server, no API key. Docs → · Financial services →

Backtest on memory you can defend.

Get an API key →