Building a Simple Options Trading Bot
🌟 From Blueprint to Reality: Assembling Your First Trading Bot
We've explored the architecture of a trading bot and the theories that guide it. Now, we take the final, exhilarating step: building one. This article is a practical, hands-on guide to creating a simple options trading bot using Python. We will assemble the components we've discussed—a data handler, a strategy engine, and an execution handler—into a functional script. Our bot will execute a classic options strategy: the covered call. This is where theory becomes code, and strategy becomes action.
Disclaimer: This is a simplified educational example. It lacks the robust error handling and risk management of a production system. DO NOT run this with a live brokerage account without fully understanding the code and the significant risks involved. Use a paper trading account.
The Mission: A Simple Covered Call Bot
Our bot's strategy will be to find and sell a covered call option. The logic is as follows:
- Connect to the brokerage account.
- Verify the price of an underlying stock that we own (e.g., AAPL).
- Find an out-of-the-money call option with a suitable expiration date (e.g., 30-60 days out).
- Place an order to sell one contract of that call option.
- Disconnect.
The Toolkit: Python and ib_insync
Our bot will be built in Python. To communicate with our broker, we'll use ib_insync, a popular and intuitive library for interacting with the Interactive Brokers (IBKR) API. You'll need an IBKR paper trading account and their Trader Workstation (TWS) or IB Gateway software running.
First, install the library:
pip install ib_insync
The Code: Assembling the Bot
Here is the complete, commented script. We'll break down how each part works below.
from ib_insync import *
import pandas as pd
import datetime
def connect_to_broker():
"""Establishes connection to TWS/Gateway."""
ib = IB()
try:
# Make sure your TWS is running on this port
ib.connect('127.0.0.1', 7497, clientId=10)
print("Successfully connected to IBKR.")
return ib
except Exception as e:
print(f"Connection Error: {e}")
return None
def find_covered_call_candidate(ib, symbol):
"""Finds a suitable call option to sell."""
# 1. Get the underlying stock contract
stock = Stock(symbol, 'SMART', 'USD')
ib.qualifyContracts(stock)
# 2. Get the current market price
ticker = ib.reqMktData(stock, '', False, False)
ib.sleep(2) # Allow time for data to arrive
stock_price = ticker.last
if pd.isna(stock_price):
print("Could not fetch stock price.")
return None
print(f"Current {symbol} price: {stock_price}")
# 3. Find option chains
chains = ib.reqSecDefOptParams(stock.symbol, '', stock.secType, stock.conId)
# 4. Filter for a suitable expiration (e.g., 30-60 days out)
today = datetime.date.today()
expirations = sorted([
exp for exp in chains[0].expirations
if (datetime.datetime.strptime(exp, '%Y%m%d').date() - today).days > 30 and
(datetime.datetime.strptime(exp, '%Y%m%d').date() - today).days < 60
])
if not expirations:
print("No suitable expirations found.")
return None
target_expiration = expirations[0]
# 5. Filter for an out-of-the-money strike
strikes = [s for s in chains[0].strikes if s > stock_price]
# Select a strike about 5-10% OTM
target_strike = min(strikes, key=lambda s: abs(s - (stock_price * 1.07)))
# 6. Create the option contract object
option_contract = Option(
symbol, target_expiration, target_strike, 'C', 'SMART')
ib.qualifyContracts(option_contract)
print(f"Found candidate: {option_contract.localSymbol}")
return option_contract
def execute_sell_order(ib, contract, quantity):
"""Places a market order to sell the option."""
# This is a market order for simplicity. In reality, you'd likely use a limit order.
order = MarketOrder('SELL', quantity)
print(f"Placing order to SELL {quantity} of {contract.localSymbol}...")
trade = ib.placeOrder(contract, order)
ib.sleep(5) # Wait for order status to update
if trade.orderStatus.status == 'Filled':
fill_price = trade.orderStatus.avgFillPrice
print(f"Order FILLED at average price: {fill_price}")
else:
print(f"Order status: {trade.orderStatus.status}")
if __name__ == "__main__":
ib_conn = connect_to_broker()
if ib_conn:
# --- Parameters ---
# IMPORTANT: You must own at least 100 shares of this stock
# in your paper account for this to be a covered call.
stock_symbol = 'AAPL'
contracts_to_sell = 1 # 1 contract = 100 shares
# --- Run the Bot ---
candidate = find_covered_call_candidate(ib_conn, stock_symbol)
if candidate:
execute_sell_order(ib_conn, candidate, contracts_to_sell)
# --- Disconnect ---
ib_conn.disconnect()
print("Disconnected.")
Deconstructing the Bot
connect_to_broker(): This is our Data Handler's entry point. It connects to the running TWS instance.find_covered_call_candidate(): This is our Strategy Engine. It takes a stock symbol and performs a series of filtering steps to find a single, suitable option contract that meets our strategic criteria (30-60 days to expiration, ~7% out-of-the-money).execute_sell_order(): This is our Execution Handler. It takes the contract from the strategy engine and places the order. For simplicity, it uses a market order, but a real bot would likely use a more sophisticated limit order to control the fill price.if __name__ == "__main__":: This block orchestrates the whole process, calling the functions in the correct sequence.
The Iceberg of Risk: What This Bot Doesn't Do
This simple script is just the tip of the iceberg. A production-ready bot would need to be far more robust. This example intentionally omits:
- Sophisticated Risk Management: It doesn't check our position size, portfolio value, or have any stop-loss logic.
- Error Handling: What happens if the internet disconnects mid-trade? What if the broker's API returns an error? A real bot needs to handle dozens of potential failure points gracefully.
- State Management: The bot has no memory. It doesn't know what positions it already holds or what orders are currently working.
- Logging: A real bot would log every single action—every price check, every signal, every order—to a file for later debugging and analysis.
💡 Conclusion: A Powerful Foundation
You have now seen the complete, end-to-end process of a trading algorithm: from connecting to a data source, to applying strategic logic, to executing a trade. While simple, this bot contains the fundamental architecture upon which far more complex systems are built. It demonstrates that algorithmic trading is not an unknowable black box, but a discipline of logic, engineering, and rigorous risk management. You've built the chassis; now you can begin to upgrade the engine.
Here’s what to remember:
- Modularity is Key: Breaking the bot into separate functions for connecting, strategy, and execution makes the code cleaner, easier to debug, and simpler to upgrade.
- The API is Your Gateway: The broker's API is the bridge between your code and the market. Understanding its documentation is crucial.
- Simplicity First: Start with a simple, understandable strategy. Complexity can be added later.
- This is a Demo, Not a Deployed System: The gap between a simple script and a robust, reliable trading system is immense and filled with careful engineering and risk management.
Challenge Yourself:
Modify the find_covered_call_candidate function. Instead of just picking a strike that is ~7% out-of-the-money, change the logic to find the strike that has the highest openInterest among the OTM calls. This is a common way traders identify significant levels.
➡️ What's Next?
We've built a bot based on a predefined, rules-based strategy. But what if a system could learn the patterns for itself? In our next article, we'll explore the cutting edge of this field with "The Rise of Machine Learning in Derivatives Trading". We'll see how modern AI is being used to find patterns that are invisible to the human eye.
Read it here: The Rise of Machine Learning in Derivatives Trading
📚 Glossary & Further Reading
Glossary:
- Paper Trading: A simulated trading environment that allows you to test trading strategies with virtual money in real market conditions.
- Covered Call: An options strategy involving holding a long position in an asset and selling call options on that same asset to generate income from the option premium.
ib_insync: A popular third-party Python library that provides a clean, synchronous interface for the Interactive Brokers API.
Further Reading: