In-house R&D

Automating a 0DTE index-options strategy on Interactive Brokers

Type
In-house R&D / team-training build
Market
US index options, 0DTE (same-day expiry)
Broker & API
Interactive Brokers (IBKR)
Language
Python
Focus
Live options execution & order management

What this is

An in-house build, not a client project, and not something we sell or trade. The strategy is one we researched ourselves and then chose to automate as a sample project for our own team: a real, end-to-end way for engineers to learn what automating a live options strategy actually demands, the IBKR API, options mechanics, and the order-handling that separates a demo from something you would trust with a live account. It is representative of the options automation work we do for clients.

The brief, in one line

Automate a live, intraday index-options strategy on Interactive Brokers. At a set of pre-defined times through the day, the system sells a call and a put together, picks strikes whose price sits near a target, sizes each order to a configured amount, and then manages the fills inside tight time windows. That is the shape of it. The specific parameters, thresholds and timing stay in-house.

Why this is a genuinely hard build

On paper it is "place two orders." In practice, same-day-expiry (0DTE) index options are about as unforgiving as automated trading gets:

  • Prices move fast and the contracts expire the same day, so there is no tomorrow to fix a mistake.
  • It is two legs that have to behave as one: a call and a put, entered close together, each chasing a moving price.
  • Everything depends on data that is genuinely live, and on the system reacting in milliseconds, not seconds.

Get any of those wrong and you don't get a slightly worse result. You get the wrong strike, a half-open position, or an order that never completes.

How we structured it

We split the system into parts that each do one job, so any one of them can be tested, frozen, and trusted while the others keep changing:

  • A live market-data feed that streams the option chain.
  • A strike selector that turns "a price near the target" into an actual contract to trade.
  • An entry engine that places the two legs and enforces the timing rules between them.
  • An order and risk manager that tracks every order's state, handles stops, and enforces limits.
  • A configuration layer. The schedule of entry times and every tunable parameter live in settings, not in code, so behaviour can change without touching (and risking) tested logic.

Keeping entry separate from ongoing management was deliberate. Once a position is on, a different set of rules takes over, and mixing the two is exactly how bots end up in states nobody planned for.

Live data, and the latency that decides everything

For a 0DTE strategy, "nearly live" data is worse than useless. It is actively misleading. A quote that is even a moment stale can point the strike selector at a price that no longer exists, so the system either picks the wrong contract or misses the window entirely. The data has to be a true live stream, and the system has to know when it isn't. A stall or a gap must be detected, never silently traded through.

The same pressure applies inside the code. Between a price arriving and an order going out, the system has to find strikes, size the order, and check the rules, and all of that has to happen in milliseconds. A tidy-looking calculation that takes a fraction of a second too long is, in this context, a bug.

Choosing the strikes

The system doesn't trade a fixed strike. It hunts for the contract whose price is closest to a target, within a band. There is a soft band (drift outside it and it looks at a neighbouring strike instead) and a hard band (a price it will never cross, full stop). Doing that reliably means reading the live chain, comparing candidate strikes on both the call and the put side, and doing it fast enough that the price you selected on is still the price you get. It also means holding several strikes in play at once, and the same underlying carrying multiple expiries, without the code ever confusing one contract for another.

Placing the two legs

The call and the put go in together, but real markets don't fill two orders at the same instant, so the rules are about what happens in between. Both legs have a window to complete, and once one fills, the other has only seconds to follow, which means re-pricing the unfilled leg (from mid toward the bid) to get it done in time. Then there are partial fills: you asked for several contracts and got one. If the rest doesn't complete in the window, the system cancels what's left and places a fresh order for exactly the premium still owed. None of this is exotic on its own. The difficulty is doing all of it correctly, every time, at speed, without ending up half-in a position.

The order lifecycle: where most of the work lives

People assume the hard part is the strategy. It isn't. It is everything that can happen to an order after you send it. A production system has to handle, cleanly:

  • Rejections. The exchange or broker says no.
  • Cancellations and modifications. You change course mid-flight, or the price moved.
  • Bracket orders. An entry that carries its own protective exit.
  • One-cancels-other (OCO) with a trailing price. Filling one order automatically pulls another, while the protective level follows the market.

Each of these is a small state machine, and they interact. The real discipline is adding a new order type, say, the trailing OCO, and proving it works without quietly breaking the rejection or partial-fill handling you had already tested and frozen. That regression risk is what makes "nearly finished" order code so dangerous, and why we spend more time here than anywhere else.

Risk and position management

Once a position exists, a separate set of rules protects it. Stops are the main one. One case from this build shows why state is everything: if a new entry lands on a strike that already holds a position with a stop on it, you can't just fire the new order. You have to pause the existing stop, place the new position, then re-transmit the stop, all without ever leaving a gap where the position sits unprotected. Above the per-position rules sit the system-wide ones: hard limits, and a kill switch that halts everything if the day runs past a set loss. A trading bot's most important feature is the one that makes it stop.

The parts that only break in a live market

Most of these never appear in testing, and all of them appear eventually:

  • Reconnection and recovery. The link to the broker will drop mid-session. When it returns, the system has to work out what actually happened while it was gone and carry on, without re-sending an order it already sent.
  • Clock accuracy and the market calendar. The whole strategy is triggered by time, so the clock has to be right and aware of half-days, holidays, and how options behave in the final minutes before a same-day expiry.
  • The broker's own realities. Running through Interactive Brokers means living with the Gateway/TWS connection, qualifying every contract before trading it, respecting market-data line limits, and staying under the API's pacing limits so you're not throttled at the worst possible moment.
  • A complete audit trail. Every decision and every order is logged, because you cannot debug a fast intraday system from memory. When something looks wrong, you need to replay exactly what it saw and did.

Testing it without blowing anything up

A system like this isn't tested once. It is tested in rounds, and each round tends to surface a gap between what the written rules say and what the code does, which then gets reconciled. The order-handling especially is tested against deliberate failure: forced rejections, cancellations and modifications, to prove the system does the right thing when the broker doesn't cooperate. Every change is checked against everything that already worked. The whole reason for the modular structure and the frozen components is that adding one feature must not silently undo ten that were already right. And progression is paper first, then live, because 0DTE is very hard to simulate honestly and the only fully trustworthy test is a real, small order in a real market.

What it demonstrates

The point of building this in-house was never the strategy. It was the engineering. It is a compact demonstration of the things that actually matter when you automate options: genuinely live data, low-latency decisions, live strike selection, tight multi-leg execution, and order and risk handling that holds up under pressure and failure. It is the same discipline we bring to the options and multi-asset systems we build for clients.

This is an engineering exercise built in-house. It is not a product, not a signal or advisory service, not investment advice, and we make no claim about how the strategy performs.

← All case studies

start here

Tell us your market, your broker, and what you want automated.

Send us the requirement. We'll come back with the questions that turn it into a real scope, cost and timeline, usually within one working day.

Book a consultation