Automating an options strategy in Python
Automating options is a different job from automating stocks. The rule that decides to trade is usually the easy part. The work is in the machinery around it: picking the right contract out of hundreds, getting several legs to fill together, and reacting before the price moves. This guide walks through that machinery in Python. The strategy stays yours. We are showing how to turn it into software.
Why options are harder to automate than stocks
With a stock, there is one thing to trade. With options, a single underlying carries hundreds of contracts across many strikes and expiries, and your code has to choose the right one, in the moment, without ever confusing one for another. On top of that, most real options strategies use more than one leg, and those legs have to behave as a single position even though the market fills them separately. That is where the difficulty lives.
Step 1: get the option chain
Everything starts with the chain, the live list of strikes and expiries for your underlying. Through Interactive Brokers, the ib_insync library is the friendliest way in. You ask the broker for the option parameters and get back the expiries and strikes you can trade:
from ib_insync import IB, Stock, Option
ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1)
# The underlying. An index or ETF works the same way.
spy = Stock('SPY', 'SMART', 'USD')
ib.qualifyContracts(spy)
# Ask the broker for the available expiries and strikes for this underlying.
params = ib.reqSecDefOptParams(spy.symbol, '', spy.secType, spy.conId)
chain = next(p for p in params if p.exchange == 'SMART')
expiry = sorted(chain.expirations)[0] # the nearest expiry
strikes = sorted(chain.strikes)Now you have the raw material: which expiries exist, and which strikes. The next job is to choose from it.
Step 2: turn your rule into an actual contract
A strategy rarely says "buy the 470 put". It says something like "sell the put nearest a target below spot", or "buy the call closest to a certain delta". Your code has to turn that rule into a specific, tradable contract, using live data:
# Your rule decides the strike. Here we pick the put strike nearest a
# target 2% below spot. The 2% is an illustration, not a recommendation.
tick = ib.reqMktData(spy)
ib.sleep(1) # wait for a real tick, do not trust nan
spot = tick.marketPrice()
target = spot * 0.98
put_strike = min(strikes, key=lambda s: abs(s - target))
put = Option('SPY', expiry, put_strike, 'P', 'SMART')
ib.qualifyContracts(put)
# 'put' is now a real, tradable contract you can send an order for.The important detail is the live tick. If you read the price before the feed has delivered a real value, you get nothing useful and you pick the wrong strike. Waiting for a genuine tick is not optional, and it is one of the first things a demo gets wrong.
Step 3: place the legs so they behave as one position
A spread, a straddle or a strangle is several orders that need to end up as one position. The market will not fill them at the same instant, so the rules are about what happens in between: how long each leg has to complete, what to do when one fills and the other has not, and how to re-price the unfilled leg to get it done before the moment passes. Getting this wrong leaves you half in a position, which is worse than not trading at all. This is exactly the kind of two-leg timing our 0DTE options case study walks through in detail.
Step 4: manage the position, and the expiry
Once a position is on, a different set of rules takes over: your stops, your exit targets, and what happens as expiry approaches. Options have a clock that stocks do not, and near expiry their behaviour changes fast, so the code has to know how close it is and act accordingly. Keeping entry logic and management logic separate is deliberate. Mixing them is how bots end up in states nobody planned for.
The parts that only bite in a live market
- Partial fills. You ask for five contracts and get two. The system has to decide what to do with the rest, in seconds.
- Stale data. A feed can stall without any error. Your code has to notice and refuse to trade on a price that is no longer real.
- Speed. Between a price arriving and an order going out, the system finds the strike, sizes the order and checks the rules. On fast-moving options, milliseconds decide whether you get the strike you wanted.
You bring the strategy. The machinery is the work.
None of this decides whether your strategy is any good. That is a separate question, and not one a guide can answer. What it shows is the engineering that stands between a rule on paper and a system you would trust with a live account. If you would rather have that built properly, that is our options automation work, and testing the rule first is our backtesting work. We build the software. We do not supply strategies or trading calls, and we make no claim about returns.
Common questions
- Can you automate options with Python?
- Yes. Python is the usual choice for options automation, because the broker libraries are mature and the code stays readable when you inevitably have to change it. The exception is the very fast end, where microseconds matter and C++ earns its place. For most options strategies, Python is the sensible default.
- What is the hardest part of automating an options strategy?
- Not the signal. It is everything around the order: choosing the right contract from a live chain, getting several legs to fill close together, handling a partial fill, and doing it fast enough that the price has not moved. Most of the engineering effort goes there, not into the rule that decides to trade.
- Do I need a special data feed for options?
- It depends on the strategy. A slow, positional options strategy is fine on a broker feed. Anything that reacts to fast moves, and same-day-expiry (0DTE) work especially, needs a genuinely live feed, because a delayed chain will point you at the wrong strike. It is a real cost worth being honest about before you build.