Futures research guide

Historical Futures Data for Backtesting: A Practical Guide

Learn how to choose futures data, validate its schema and sessions, control adjustments, and build a credible backtest with a concrete example.

Published · 1,497 words

Start with the question the data must answer

A useful backtest begins with a decision, not a download. Specify the instrument, holding period, order type, session, and execution assumption before selecting a dataset. A strategy that enters on a five-minute close needs different evidence from one that reacts to individual trades. The former can usually be tested with bars; the latter may require ticks and a more conservative fill model.

Write down the signal timestamp and the earliest possible execution timestamp. If a bar closes at 14:05, its final price was not known at 14:04. Executing at that close without delay assumes an unrealistic ability to act on completed information. A simple next-bar entry often exposes this error immediately.

Also decide what “futures price” means for the study. A single expiry answers questions about that contract. A multi-year rule usually needs a continuous series, but its construction affects both returns and thresholds. Keep those choices explicit so that results can be reproduced rather than merely rediscovered.

Choose per-contract or continuous data

Per-contract data preserves one expiry’s trading history. Use it to study delivery behavior, liquidity near expiry, contract-specific spreads, or execution in the instrument actually traded. It also avoids treating a roll rule as if it were a market price. The trade-off is a finite history and changing activity as traders move to another expiry.

Continuous data joins contracts into a research series. It is appropriate when a signal needs a long history, such as momentum, volatility, or moving averages. The join method matters: an unadjusted join can turn a price difference between expiries into a fictitious strategy return. Back adjustment shifts earlier prices so the series is more suitable for return calculations, while changing the historical price level.

In these derived datasets, continuous ticks add price_raw and back_adjustment, with price = price_raw + back_adjustment. Test that identity before calculating signals. Preserve the raw component when a rule depends on actual traded prices, and never compare an adjusted historical price directly with an unadjusted order level.

The companion guide to continuous futures contracts explains roll and adjustment choices in more depth. For any study, record the roll rule alongside the strategy parameters.

Select ticks or bars deliberately

Tick data contains individual trade events and supports analysis of sequencing, short-lived signals, and execution within a bar. OHLCV bars compress events into fixed intervals, making long research runs smaller and faster. Neither representation is inherently more accurate; they answer different questions.

Use bars when entries and exits can reasonably be evaluated at bar boundaries and intrabar ordering is irrelevant. Use ticks when a stop and target could both fall inside one bar, when the strategy reacts to trade flow, or when the assumed fill depends on event order. If both outcomes are possible within a bar, choosing whichever gives the better return is not a valid execution model.

Bars in this collection are derived from back-adjusted continuous ticks. Available intervals are 10s, 1m, 2m, 5m, 30m, 1h, 4h, and 1D. Intraday buckets are epoch-aligned in UTC; daily bars follow the Chicago 17:00 session with daylight-saving changes. Empty buckets are omitted, so a missing row does not establish a missing file or a zero-volume market.

Validate schema and time before signals

Schema checks should run before any performance report. Confirm column names, data types, ordering, uniqueness where expected, and numeric relationships. For per-contract ticks here, ts and recv_time are equal UTC timestamps, event_time is null, prices are float64, and volume is uint32. The oi field is zero because open interest is unavailable; zero must not be interpreted as measured market participation.

For bars, expect ts, open, high, low, close, volume, and trade_count. Check that high is at least the open, close, and low, and that low is at most the other prices. Investigate negative prices rather than deleting them: back adjustment can produce them legitimately, and they can break percentage-return or volatility formulas.

assert (bars["high"] >= bars[["open", "close", "low"]].max(axis=1)).all()
assert (bars["low"] <= bars[["open", "close", "high"]].min(axis=1)).all()
assert bars["ts"].is_monotonic_increasing
assert (bars["volume"] >= 0).all()

Time checks deserve equal attention. Store timestamps in UTC, localize only for session rules and presentation, and test around daylight-saving transitions. A daily label should identify its session unambiguously. Do not assume that every weekday has a full session or that interval boundaries match a local wall clock.

Audit quality and trading context

Data quality is more than a count of nulls. A focused audit should cover:

  • Tick-derived volume compared with bar volume.
  • Duplicate timestamps and repeated prices that may represent genuine trades.
  • Gaps checked against holidays and expected sessions, not filled automatically.

Forward-filling prices through a closure can invent bars and trading opportunities.

Look for discontinuities at contract joins and distinguish adjustment effects from bad ticks. Review extreme moves in both raw and adjusted space. Outliers may be real market events; removing them because they weaken a strategy biases the experiment. Corrections should follow documented rules applied uniformly across the sample.

Execution context belongs in the same audit. These tick fields describe trades, not a resting order book: bid_sz and ask_sz are executed volume at bid and ask, not available queue sizes. They cannot tell you how many contracts were waiting for a fill. Use them accordingly and avoid treating trade-side classification as a complete liquidity model.

The dataset documentation is the reference for field definitions and derivations. Keep a small validation report with each research run so later comparisons use the same assumptions.

Separate training, validation, and testing

Chronological splitting prevents future observations from shaping past decisions. Choose parameters on an early training interval, compare a small number of candidates on a later validation interval, and evaluate the frozen rule once on the final test interval. Include contract rolls and changing volatility regimes in those intervals. Randomly shuffling rows destroys this ordering and can leak regime information.

Every fit must respect the split. Normalization constants, volatility thresholds, roll parameters, and even the choice of interval belong to training. If test results prompt a rule change, the old test period has become part of development and a genuinely untouched evaluation is needed.

Report more than net profit. Include trade count, turnover, drawdown, exposure, performance by year, and sensitivity to costs and delayed entry. A result supported by a handful of trades or one exceptional week is fragile. Compare against simple alternatives, such as no position or a fixed holding, to learn whether complexity adds information.

A walk-forward evaluation can supplement the final holdout. Refit on an expanding or rolling training window, apply the rule to the next unseen window, and concatenate only those out-of-sample results. Keep window lengths and refit frequency fixed in advance. This shows whether performance survives changing regimes, but repeated walk-forward experiments still count as model selection and do not replace an untouched test.

Work through a small example

Suppose a rule buys after a 20-day high and exits ten sessions later. Build the calculation in this order:

  1. Select back-adjusted daily bars and calculate the channel using only completed prior sessions.
  2. Generate the signal after session t closes and enter at session t+1, not at the high that triggered it.
  3. Apply one consistent cost and slippage assumption to entry and exit.

Inspect the shifted signal

A simplified research frame might contain these rows after signals are shifted:

session     close   prior_20d_high   enter_next
2025-03-03  6012.5       6008.0          True
2025-03-04  6021.0       6012.5         False
2025-03-05  6017.5       6021.0         False

The March 3 signal uses information available through that close; the March 4 row cannot retroactively improve the entry. Verify that the highest value used on March 3 excludes March 3 itself. Then inspect several actual roll joins, including one inside a holding period. Confirm that adjustment does not create an artificial breakout or change position sizing in a way the live rule could not reproduce.

Finally, reserve later data, freeze the rule, and repeat the entire pipeline unchanged. Browse available histories in the data browser only after defining the required interval and coverage; convenience should not determine the hypothesis.

State what the backtest cannot prove

Historical results cannot establish future profitability. A continuous research price is not necessarily an executable contract price, and a bar cannot reveal the exact path within its interval. Tick history improves sequencing but still does not provide queue position, rejected orders, latency, or the full set of execution conditions.

Other limits come from the experiment. Multiple parameter trials increase the chance of a flattering result, while a selected market and period may omit regimes where the rule fails. Costs, margin, position limits, and contract feasibility need separate treatment. Adjustment can also distort price-based rules if raw and adjusted values are mixed.

Present these constraints beside the results, not in a footnote after an equity curve. A credible backtest is a reproducible comparison under stated assumptions. Its value comes from exposing sensitivity and failure modes before capital is involved, not from producing a precise forecast.