Skip to main content

What Is Lookahead Bias?

Lookahead bias occurs when a trading strategy uses information that would not have been available at the time a trade decision was made. It’s the single most common cause of backtests that look profitable but fail in live trading.
The problem isn’t that programmers are careless. The problem is that vectorized frameworks make it structurally possible to introduce lookahead bias with a single indexing mistake.

How ClyptQ Prevents It

ClyptQ prevents lookahead bias through four structural mechanisms — not through code reviews or best practices, but through architecture that makes lookahead physically impossible.

1. RollingBuffer: Fixed-Size Circular Buffer

Every operator input is delivered through a RollingBuffer — a pre-allocated circular buffer that contains only the declared lookback number of past ticks:
Key property: The buffer is pre-allocated to exactly lookback slots. There is no array of future prices. There is no array at all — just a fixed window of past data that overwrites itself circularly. An operator requesting lookback=20 receives exactly 20 ticks of historical data. Not 21. Not the entire dataset. Twenty ticks, ordered oldest-to-newest, with no possibility of accessing tick 21.

2. Input Declarations: Explicit Lookback Contracts

Every operator must declare exactly how much history it needs through Input objects:
The lookback parameter determines the size of the RollingBuffer allocated for that specific consumer. The operator’s compute() method receives a TaggedArray of shape (lookback, n_symbols) — no more, no less. This is fundamentally different from vectorized frameworks where every operation has access to the full price array:

3. Automatic Warmup Calculation

Before the backtest starts, ClyptQ automatically computes how many ticks are needed to fill all RollingBuffers. This warmup phase runs the graph without executing any trades: The algorithm works by:
  1. Tracing backward from every node in execution_order through its Input dependencies
  2. Accumulating lookback values along each path (adjusting for overlaps: total = node_lookback + accumulated - 1)
  3. Converting to source ticks (accounting for timeframe differences: a 20-bar 1h lookback = 1,200 1m source ticks)
  4. Taking the maximum across all paths to each FIELD source
  5. Adding a 5% safety buffer (warmup = int(max_warmup * 1.05))
During warmup:
  • Operators execute normally (buffers fill up)
  • STATE is extracted (portfolio state available)
  • No trading orders are executed (extra_context={"is_warmup": True})
  • Intention operators produce no output
This means the first real trade happens only after all operators have sufficient history — matching exactly what would happen if you deployed the strategy live.
Warmup ticks are automatically computed — you never need to manually set warmup. The graph traces its own dependency tree to determine the exact number of pre-run ticks needed. See Warmup Calculation and Lookback Buffers for the full algorithm.

4. Topological Execution Order

The graph executes operators in dependency order (Kahn’s algorithm), ensuring that every operator’s inputs are computed before the operator runs: Each operator sees only:
  • Its declared inputs (through RollingBuffer)
  • The current tick’s FIELD data (through the graph’s on_tick dispatch)
  • STATE data extracted from the executor (cash, positions, margin)
There is no global state, no shared mutable array, no way for operator 5 to access the output of operator 7.

The Complete Picture

These four mechanisms work together to create an environment where lookahead is structurally impossible:

Comparison with Vectorized Frameworks

Pandas / NumPy (Used by Freqtrade, bt, Moonshot)

The burden is entirely on the developer to get every .shift() call correct.

Backtrader / Zipline (Event-Driven)

These frameworks process data bar-by-bar, which is better than pure vectorized. But:
The data object still contains the full series. Positive indexing accesses future bars. No structural prevention.

ClyptQ (RollingBuffer)

The operator physically cannot access data outside its declared lookback window. This isn’t a convention — it’s a constraint enforced by the RollingBuffer allocation.

Summary

The result: you cannot introduce lookahead bias in ClyptQ, even if you try. The architecture doesn’t rely on developer discipline — it makes the wrong thing impossible.