Any automation that doesn't know a futures contract expires will eventually trade an instrument that has ceased to exist.
Where futures stop behaving like equities
If you've written automation for equities or crypto, the instrument you trade today is the same instrument you'll trade next year. The ticker AAPL in January is the ticker AAPL in December. Your data feed keeps producing prices. Your order template keeps working. The position you hold is still the position you hold.
Futures don't behave that way.
Every futures contract has an expiration month baked into its identity. The E-mini S&P 500 contract trading right now is not the E-mini S&P 500 — it's the September 2026 E-mini S&P 500, and there's also a December 2026 version, a March 2027 version, and so on. They all reference the same underlying index, but they are distinct, separately quoted instruments with separate order books, separate liquidity, and separate expiration dates.
When the September contract expires, it doesn't roll into October. It ends. The order book closes. The data feed goes silent. Any position still open settles — sometimes in cash, sometimes by delivering an actual barrel of oil — and trading shifts to the December contract, which has been listed and quoted the entire time, waiting.
An automation system that doesn't model this assumption explicitly will, eventually, walk straight into it.
Contract symbology
Most futures products list contracts in a regular cadence. Equity index futures (ES, NQ, YM) cycle quarterly — March, June, September, December — while energy products (CL, NG) and grains expire monthly. Metals vary. The cadence is fixed per product and well-documented by the exchange, but it is not uniform across the industry.
Each contract carries a month code and a year digit. The CME's convention uses letters: H for March, M for June, U for September, Z for December, and so on. So the symbols look like this:
ES U26 — E-mini S&P 500, September 2026
ES Z26 — E-mini S&P 500, December 2026
CL N26 — Crude Oil, July 2026
CL Q26 — Crude Oil, August 2026
ZC U26 — Corn, September 2026
Brokers wrap their own opinions around this. One broker prefixes with @, another with /, a third insists the canonical root is enough and the contract month is implicit. Some let you ask for "the active contract" by root symbol and resolve to a specific month server-side. Some don't. The symbol your automation sends to place an order is, in nearly every case, broker-specific. The expiration calendar underneath it is not.
The front month and where the volume goes
At any moment, multiple contract months are listed for the same product. Only one of them — usually the nearest one that hasn't expired — carries the bulk of the trading volume. That contract is called the front month, and it's where active traders, algorithms, and institutions concentrate.
As expiry approaches, this concentration moves. Volume migrates from the expiring contract into the next one over a window of days — sometimes a week, sometimes longer — driven by participants closing positions in the old month and re-opening them in the new one. For a stretch, both contracts trade with meaningful liquidity. After that, the old contract trades thinly, with wider spreads, until it stops altogether.
The exchange publishes the exact expiration date. The volume migration is observable in the tape. The day your automation is supposed to switch from one to the other is a system-design decision, not an industry constant — different desks roll at different times, for different reasons. Whatever you pick, you have to actually do it.
The four failure modes
I've watched naive automation hit each of these in succession. They don't announce themselves cleanly. Some are silent, which makes them worse.
1. Holding through expiry
If a position is open in a contract at the moment it expires, the contract settles. For cash-settled products (equity indices, for example) this means a final settlement price is calculated and cash is debited or credited from the account. For physically-delivered products (crude oil, agricultural commodities) it means the holder is, in principle, obligated to deliver or take delivery of the underlying. In practice the broker won't let a retail account get that far — they'll force-liquidate the position before delivery, often with a margin penalty and at whatever price the market happens to be offering.
Either way, the position you thought you were managing has been closed by an external actor, on someone else's schedule.
2. Subscribing to an expired contract's data feed
After expiry, the contract's data feed stops producing ticks. Not "produces zero values" — produces nothing. Your subscription is still nominally active; the connection is still open. The broker may or may not notify you that the contract is gone. From the automation's perspective, the market has simply paused.
A strategy waiting for the next bar will wait forever. A heartbeat-based liveness check might still pass, because the connection is healthy — it's the data underneath that has ended. This is one of those failures that doesn't trip alarms designed to detect crashes, because nothing crashed.
3. Order templates stuck on an expired symbol
The order template — the configured contract symbol your system uses when it decides to place an order — is usually loaded once at startup and held in memory. After expiry, that template still parses, still serializes, and still submits cleanly to the broker. The broker rejects it with an error along the lines of "unknown contract" or "contract not tradeable."
It's the cleanest failure mode of the four — it's loud at the broker. Whether it's loud at your monitoring is a different question. "The order keeps getting rejected" is not the diagnosis your dashboards will produce if you haven't told them what to look for.
4. Position records that don't know they're stale
Your database has a position record. It says: long two contracts of ES U26. The broker, post-expiry, has settled that position and shows you flat. Your system, if it doesn't reconcile, continues to believe it owns something it doesn't. Every subsequent decision is made against a fictional position.
This is the worst of the four, because the system continues to operate confidently. It places orders sized against a position that doesn't exist. It calculates P&L against entry prices that no longer matter. Nothing about the runtime indicates a problem until a reconciliation pass, a manual review, or a baffling fill surfaces it.
What the system must do
All four failures trace to the same omission: the automation doesn't model the fact that its instrument has a lifespan. Fixing that means treating contract expiry as a first-class event the system observes, anticipates, and reacts to.
At minimum, an automation system trading futures has to do four things:
- Detect approaching expiry. The expiration date is known in advance — it's metadata on the contract. The system needs to read it, store it, and check it against the current date on a schedule it controls. Not when an order is rejected. Not when a fill is missing. Before any of that, on a regular cadence, while everything is still healthy.
- Close the position in the expiring contract. Any working orders against the old contract are cancelled first; then, if there's a position, it has to be flattened before expiry — through the same order pipeline the strategy normally uses, with the same risk discipline. This is not a special case to be done out-of-band. It's the system closing a position the same way it would for any other reason, just triggered by the calendar instead of the strategy.
- Re-subscribe to the new front-month feed. The data feed has to be redirected to the next contract month, and the order template has to be updated to match. Both need to happen as a single uninterrupted step from the strategy's perspective — if the feed is on the new contract but the order template is still on the old one, your next signal will fire an order against a symbol that doesn't trade.
- Carry forward state correctly, or pause. Some state should survive the roll — historical context, strategy parameters, account-level risk numbers. Some state should not — open orders against the old contract, in-flight fills, the position record itself. If the system can't reconcile cleanly, it has to refuse to continue. Automated recovery from an ambiguous post-roll state is worse than a paused session.
These four steps are the irreducible minimum. The order matters: detect, close, re-subscribe, reconcile. Skip any one and you're back to the failure modes above, just with more confidence that you've solved the problem.
Where the complexity actually lives
Listed cleanly like that, the four steps look manageable — a scheduled check, a close, a re-subscribe, a reconcile. The complexity isn't in any one step. It's in the transitions between them, and in the question of what happens when one of them doesn't go to plan.
What if the close order doesn't fill? You can't re-subscribe to a new contract while still holding the old one — that leaves you long two products that are correlated but not identical, in a state no part of the system was designed to handle. What if the process dies between step 2 and step 3? On restart, the position is closed, the data feed is still on the old symbol, the order template still references the expired month, and nothing in state indicates which step was last completed. What if the broker's confirmation of the close arrives late, after the system has already moved on?
The fact that contracts expire is a domain fact. The fact that the roll is a multi-step process that has to survive crashes, partial failures, and broker-side delays — that's a system-design problem. It needs its own state machine, with explicit states, explicit transitions, and an explicit answer for every way the happy path can break.
That state machine — its states, its safety timeouts, what happens when the close doesn't fill, how the system resumes after a crash mid-roll — is what the order book post referred to and deferred. The next post in this series walks through the implementation: four states, three triggers, a five-minute safety timeout, and the rule that says some failures are not the automation's to recover from.
The takeaway
Contract expiry is the kind of domain detail that's easy to dismiss as paperwork until it isn't. None of its failure modes crash the system — which is exactly what makes them dangerous. Automation that doesn't model expiry fails confidently rather than loudly.
The four steps are the minimum. The state machine is what makes them survivable: detect, close, re-subscribe, reconcile — in that order, and built to survive a crash in the middle.
Discussion
Create a free account to join the conversation.