How Continuous Futures Contracts Are Built
No single futures contract has a long history. Every futures chart is stitched. How continuous futures contracts get rolled, spliced and back adjusted.
A continuous futures contract is a synthetic price series built by stitching many separate, expiring contracts end to end. No individual futures contract has a long history: each one is listed for a single delivery month, trades for a limited window, and then stops trading for good. Every multi-year futures chart you have looked at is a stitched series, and whoever built it made two decisions on your behalf: when to roll from one contract to the next, and how to splice the two together.
Both decisions change the numbers a backtest reports, and one of them is why old crude oil prices show up below zero on charts people screenshot. The panels here use listed options and a dividend-paying stock rather than futures. Options are dated contracts that expire the same way, and dividend adjustment is the same arithmetic as roll adjustment.
Why no single futures contract has a long history
An exchange does not list a thing called crude oil. It lists a contract for one delivery month: WTI crude for December 2026, E-mini S&P 500 for March 2027. Each carries a month code in its symbol, F for January through Z for December, which our guide to reading a futures symbol takes apart character by character. Trading ends on a date published years ahead. NYMEX WTI crude stops trading a few business days before the 25th of the month preceding delivery, and CME equity index futures settle against an opening quote on the third Friday of March, June, September and December.
Liquidity does not spread evenly across those listings. It concentrates in the contract nearest expiry, the front month, and moves to the next one over a handful of sessions. The panel below shows that concentration in listed options on Apple, where every contract carries a fixed expiration date too. Volume is grouped by how much time each contract had left to run.
The exact SQL behind every number
SELECT
multiIf(days_to_expiry <= 7, 'under 1 week',
days_to_expiry <= 30, '1 to 4 weeks',
days_to_expiry <= 90, '1 to 3 months',
days_to_expiry <= 180, '3 to 6 months',
'over 6 months') AS time_to_expiry,
round(sum(volume) / 1e6, 2) AS contracts_traded_m
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
AND date >= '2026-04-01'
AND date <= '2026-06-30'
AND volume > 0
GROUP BY time_to_expiry
ORDER BY min(days_to_expiry)Over that quarter, contracts with under a week left traded 28.52 million contracts, against 2.71 million for everything dated more than six months out. Futures liquidity behaves the same way, which is why any single contract's own history is a thin line, one busy month, then nothing at all.
When to roll a continuous futures contract
The first decision is when the series stops quoting one contract and starts quoting the next. Three rules cover most of what is in use.
- A fixed calendar rule. Roll a set number of business days before expiry, or on a fixed day of the month. It is reproducible and knowable in advance, and it can leave the series quoting a contract the market has already left behind.
- A volume crossover. Roll on the first session the deferred contract trades more than the front. It follows real activity, and it flickers, so implementations usually demand two or three consecutive sessions before switching.
- An open-interest crossover. Roll when more contracts sit open in the deferred month. Open interest counts positions held rather than turnover, so it moves slowly and reverses less often. Exchanges publish it the next morning: a backtest that rolls on the same day's figure uses a number nobody had at the time, the plain form of look-ahead bias in a backtest.
The changeover spreads across days rather than landing on a single instant. Through the handover both contracts trade, the deferred one takes a larger share of the turnover each session, and the front one thins out into its last trading day. A calendar rule that rolls three business days before expiry and a volume rule that rolls at the crossing can hold different contracts for several sessions, and they book different prices for those sessions. The bucket shape in the panel above is the shape a futures roll has: turnover piles into the contract nearest expiry, then leaves it for good on a date the exchange fixed years earlier.
How to splice: raw, difference-adjusted, ratio-adjusted
The second decision is what to do at the seam. On the roll day both contracts have a price, and the two differ. Deferred months trade above the front in contango and below it in backwardation. That difference is not a return: nobody made or lost the money, and it is an artifact of the stitch. Three conventions handle it.
- Raw, or unadjusted. Paste the two paths together. Every level in the series is a price that traded, and every roll leaves a step that looks like a return.
- Difference-adjusted, usually called back-adjusted. Add the roll gap to every earlier price. Dollar changes survive intact, percentage returns get distorted, and historical levels stop matching anything that traded.
- Ratio-adjusted, or proportional. Multiply every earlier price by the roll ratio. Percentage returns survive intact, dollar changes get distorted, and the historical levels are again not traded prices.
The script below is plain Python 3 with nothing to install. It hardcodes one expiring contract and its replacement, overlapping on a single roll day, and prints the three stitched series side by side.
# Two overlapping contracts. The prices are illustrative, not market data.
front = [96.00, 98.00, 97.00, 100.00] # expiring contract, day 1 to day 4
back = [104.00, 105.00, 103.00] # replacement contract, day 4 to day 6
gap = back[0] - front[-1] # 4.00, the roll gap in dollars
ratio = back[0] / front[-1] # 1.04, the roll gap as a multiple
series = {
'raw': front + back[1:],
'difference': [p + gap for p in front] + back[1:],
'ratio': [p * ratio for p in front] + back[1:],
}
def moves(prices, in_percent):
out = []
for i in range(1, len(prices)):
if in_percent:
out.append((prices[i] / prices[i - 1] - 1) * 100)
else:
out.append(prices[i] - prices[i - 1])
return out
print('roll gap %+.2f roll ratio %.4f' % (gap, ratio))
print()
for name, prices in series.items():
print('%-11s levels %s' % (name, ' '.join('%8.2f' % p for p in prices)))
print()
for name, prices in series.items():
print('%-11s dollars %s' % (name, ' '.join('%+8.2f' % m for m in moves(prices, False))))
print()
for name, prices in series.items():
print('%-11s returns %s' % (name, ' '.join('%+7.2f%%' % m for m in moves(prices, True))))
The gap is 4.00 and the ratio is 1.04. The raw series steps from 100.00 to 105.00 at the seam, a 5.00% move on a day when the contract actually held moved 0.96%. The difference series reproduces the old contract's dollar moves exactly, +2.00 then -1.00 then +3.00, while its historical returns read 2.00% where the traded return was 2.08%. The ratio series reproduces those traded returns exactly and prices day 1 at 99.84, a level that never printed. Levels and returns cannot both be preserved, whatever numbers you feed in.
Why back-adjusted crude charts show prices below zero
Difference adjustment accumulates. Every roll shifts the entire history before it, and a series with hundreds of rolls behind it carries the sum of hundreds of gaps at its left edge. Crude oil has spent long stretches in backwardation, with each replacement contract listed below the one going off the board. Each of those rolls subtracts from every earlier price. Add up decades of them and the adjusted prices of the 1980s sit below zero, on a chart of a commodity nobody has ever given away.
Those negative levels are an artifact of the stitch. They are also routinely mixed up with a separate event: in April 2020 one WTI contract genuinely traded below zero in the sessions before it expired, with storage at Cushing effectively full and holders of a physically delivered contract facing delivery. A chart will not tell you which of the two you are looking at, and the axis label will not either.
The same class of mistake, different asset
Equity investors meet this arithmetic under other names. A four-for-one split prints a price at a quarter of the day before without anyone losing three quarters of their money, and a stock going ex-dividend opens lower by roughly the payment. Vendors adjust for both, using the same difference or ratio choice made at a futures roll. Our post on split-adjusted price history works through the equity side in detail.
The panel below makes the tension concrete with a decade of Coca-Cola dividends, each measured against the share price on the same session.
The exact SQL behind every number
SELECT
toString(d.ex_date) AS ex_date,
round(100 * toFloat64(d.cash_amount), 2) AS dividend_cents,
round(toFloat64(a.close), 2) AS share_price_usd,
round(100 * toFloat64(d.cash_amount) / toFloat64(a.close), 3) AS step_pct
FROM
(
SELECT
ex_dividend_date AS ex_date,
max(cash_amount) AS cash_amount
FROM global_markets.stocks_dividends
WHERE ticker = 'KO'
AND ex_dividend_date >= '2016-01-01'
AND ex_dividend_date <= '2026-06-30'
GROUP BY ex_dividend_date
) AS d
INNER JOIN
(
SELECT date, close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'KO'
AND date >= '2016-01-01'
AND date <= '2026-06-30'
) AS a ON a.date = d.ex_date
ORDER BY ex_dateCoca-Cola's quarterly cash dividend went from 35 cents a share to 53 cents across that window. Measured against the share price on the ex-dividend date, the same payment was worth 0.774% at the start and 0.655% at the end. A difference adjustment holds the cash step fixed through history. A ratio adjustment holds the percentage fixed. Identical inputs, two different price series.
Any adjusted price series is a model output rather than a record of what traded. That puts it in the same category as the other silent modelling choice buried in historical data, the one covered in survivorship bias in stock data.
What to record about your continuous futures series
- The roll rule and its parameters, including how many confirming sessions a crossover needs.
- The splice method, and the date the adjustment is anchored to.
- The date you built the series. A back-adjusted history changes at every future roll, so the 2015 prices in your file today will not match the 2015 prices you download next year.
- Which series fed which calculation. Percentage returns line up with a ratio-adjusted series, dollar profit and loss per contract with a difference-adjusted one, and log returns are undefined wherever a difference-adjusted series crosses zero.
FAQ
What is a continuous futures contract?
It is one long price series stitched from many individual futures contracts, each of which trades for a few months and then expires. No exchange lists it and nobody trades it. It exists so charts and backtests have a history longer than a single contract can offer.
When do futures contracts roll?
The exchange fixes the last trading day; the roll date in a continuous series is the builder's choice. Common rules are a set number of days before expiry, the first session the next contract out-trades the front, and the session when open interest crosses over.
What is the difference between back-adjusted and ratio-adjusted futures prices?
Back-adjusted data shifts old prices by the roll gaps in dollars, which keeps dollar moves exact and distorts percentage returns. Ratio-adjusted data multiplies old prices by the roll ratios, which keeps percentage returns exact and distorts dollar moves. Neither leaves the old levels equal to prices that traded.
Why do old crude oil prices show as negative on some charts?
Difference adjustment carries the roll gaps backwards through the whole history. Crude has spent long stretches in backwardation, and summing those gaps across decades pushes the adjusted early history below zero. It is an artifact of the stitch, separate from April 2020, when a live WTI contract really did trade below zero.
Every panel here ships with the SQL that produced it. Open one, swap the ticker, and watch where the volume sits as expiry approaches. The same questions can be asked in plain English on the Strasmore terminal.