Skip to content
  • Overview
  • Curriculum
    • FLUMental maths and numerical fluency
    • MKTMarkets and products
    • CSData structures and algorithms
    • PYPython and data for quants
      • 1Python fundamentals

        • Idiomatic Python: generators, itertools and the gotchas
      • 2NumPy

        • NumPy: broadcasting, axes and vectorisation
      • 3pandas

        • pandas: groupby, joins, resampling and time zones
      • 4Market data handling

        • Market data in pandas, and where look-ahead hides
      • 5Vectorised backtesting

        • Vectorised backtesting: signal to position to P&L
      • 6Performance

        • Performance: profiling, vectorising and when to leave Python
    • NUMNumerical methods
    • SYSSystems and low latency

Practise

  • Question bank
  • Mental arithmetic
  • Market simulator
  • Arbitrage trees
  • Horse racing
  • Bid book
  • Screening tests
  • Mock papers

Reference

  • Formula reference
  • Search

Your record

  • Review queue
  • Progress
  • Leaderboard
  • Profile
  • Invite friends
AccountSend feedback
  1. Curriculum
  2. /Quantitative development
  3. /Python and data for quants
  4. /Market data handling

Market data in pandas, and where look-ahead hides

PY · Chapter 4·13 min read·Asked at Citadel Securities, Two Sigma, Jump, IMC

After this lesson you should be able to

  • Align two time series without leaking the future.
  • Name the four biases that live in a naive backtest.
  • Say when merge_asof is the right join and why an ordinary merge is not.

Most backtests that look too good are not wrong about the model; they are wrong about the timestamps. Handling market data well is mostly a discipline about what was knowable when, and the pandas idioms exist to encode that discipline rather than to be clever.

Definition 4.1

Look-ahead bias

Look-ahead bias — Using information in a decision that was not available at the time of the decision. It rarely arrives as an obvious mistake; it arrives as a shift in the wrong direction, a close-to-close signal traded at the same close, a restated fundamental, or an index membership list downloaded today and applied to 2015.

2504000800000.40.8200-day moving averageDays of historyFraction unusable
Figure 4.2 · How much of a backtest the warm-up eats. A two-hundred-day average needs two hundred days before it says anything. On a single year of data that is four fifths of the sample — and a backtest that quietly starts at the first non-null row is measuring a different period from the one it claims.
BiasWhat it isThe fix
Look-aheadUsing data not yet published at decision timeLag by the publication delay, not by one bar
SurvivorshipA universe containing only firms that still existPoint-in-time universe including delistings
RestatementFundamentals as revised, not as first reportedPoint-in-time fundamentals with an as-of date
SelectionA universe or period chosen after seeing resultsFix the universe before the test, and count the variants tried
Table 4.3 · The four that matter. Survivorship is the one candidates name; look-ahead is the one that actually appears in their code.
import pandas as pd

# WRONG: the signal at time t is multiplied by the return realised at time t.
pnl = signal * returns

# RIGHT: act on the signal at t, earn the return from t to t+1.
pnl = signal.shift(1) * returns

# RIGHT, and honest about execution: a signal computed on the close
# of day t is traded at the open of t+1 at the earliest.
position = signal.shift(1)
pnl = position * returns - position.diff().abs() * cost_per_turn
Listing 4.4 · Aligning a signal with the return it predicts. The first line is the single most common bug in a first backtest, and it is worth knowing that it usually produces a spectacular Sharpe ratio rather than a subtle one. Time O(n) · Space O(n).

Proposition 4.5

Why merge_asof exists

Market data arrives on irregular timestamps, and an ordinary merge on time joins only exact matches — which for tick data means almost nothing joins. merge_asof joins each row to the most recent row on the other side at or before its timestamp, which is exactly the "what did I know at this instant" semantics a backtest needs.

Holds when

  • Both frames must be sorted by the join key, or the result is silently wrong.
  • The default direction is backward. Using direction="forward" in a backtest is look-ahead written explicitly.
  • tolerance caps how stale a match may be, which is how you avoid quoting off an hour-old print.

The rest of this lesson is in Premium

You have read the opening. 9 more sections follow, including 4 worked examples and 3 quick checks.

Start the free 7-day trialSign in

Nothing is charged for 7 days, and you can cancel before then. Or read Complexity: reading it off, and deriving it in full, free.

← pandas: groupby, joins, resampling and time zonesVectorised backtesting: signal to position to P&L →
On this page
  • Look-ahead bias
  • How much of a backtest the warm-up eats
  • The four that matter
  • Aligning a signal with the return it predicts
  • Why merge_asof exists

QuantMax · 141 lessons · 1342 questions · c5c0caa

  • Premium
  • Arbitrage trees
  • Horse racing
  • Invite friends
  • Account
  • About QuantMax
  • Terms
  • Privacy

Firm names identify publicly reported question patterns and nothing more. QuantMax is not affiliated with, endorsed by, or recruiting for any firm named in the curriculum. Everything you do in lessons and the question bank is kept to your account.