Hello! Welcome to Module 8. In the previous module, we successfully connected our signal generation logic to our strategy classes. Now, a strategy can determine when it's a good time to consider opening a position. This brings us to the next logical step: defining what that position is in a way our broker can understand.
Today's lesson focuses on a critical piece of the execution puzzle. Our learning outcome is to design an Order object to represent market and limit orders for single and multi-leg strategies. This object will act as a standardized "message" that our strategy classes will create and send to the execution handler. It will contain all the necessary details to place a trade, from the specific contracts to the desired price.
1. Why Design Our Own Order Object?
You might wonder why we don't just use the Order object provided by the Interactive Brokers (IB) API directly. The answer lies in good software design, specifically the principle of decoupling.
By designing our own internal Order object, we make our strategy logic independent of any specific broker's API. Our Strategy classes will only ever create and work with our internal Order object. It will be the ExecutionHandler's job to translate this internal object into a broker-specific format (like an ibapi.order.Order object for IB).
This approach gives us two main advantages:
- Flexibility: If we ever wanted to switch to a different broker, we would only need to update the
ExecutionHandler. The core strategy logic would remain untouched. - Clarity: Our internal
Ordercan be designed to be simple and perfectly suited to our needs, hiding the complexity and numerous fields of a broker's all-purpose order object.
2. The Anatomy of a Single-Leg Order
Let's start with the basics. What information is essential to place an order for a single instrument, like buying 100 shares of a stock or a single call option?
Based on standard trading practices, we need at least:
- Symbol: The ticker of the instrument (e.g., 'SPY').
- Action: Whether we want to 'BUY' or 'SELL'.
- Quantity: The number of shares or contracts.
- Order Type: The execution instruction, primarily 'MKT' (Market) or 'LMT' (Limit).
- Limit Price: The specific price for a 'LMT' order. This is optional and not used for 'MKT' orders.
- Time in Force: How long the order should remain active if not filled (e.g., 'DAY', 'GTC' - Good 'Til Canceled).
To see how this maps to a real broker API, let's look at a practical demonstration.
Interactive Brokers TWS API Tutorial in Python - with Trading Bot example
The following video from ATJ Traders provides an excellent walkthrough of creating and placing orders with the native Interactive Brokers API. As you watch, pay close attention to the attributes he sets on the order object.
Please watch the segment from 01:10:24 to 01:27:58. Focus on: The attributes required for a Market Order (action, totalQuantity, orderType). The additional attribute needed for a Limit Order (lmtPrice).
As you saw in the video, the ibapi.order.Order object has attributes like action, totalQuantity, orderType, and lmtPrice, which directly correspond to our essential list.
In Python, an elegant way to structure this data is by using a dataclass. They are concise and perfect for creating classes that primarily store data.
Here's a first draft for a single-leg order:
from dataclasses import dataclass
from typing import Optional
@dataclass
class Order:
"""
Represents a single-leg trading order for our internal system.
"""
symbol: str
action: str # 'BUY' or 'SELL'
quantity: int
order_type: str # 'MKT' or 'LMT'
limit_price: Optional[float] = None # Only for LMT orders
time_in_force: str = 'DAY' # Default to Day order
This class is a clean, internal representation of an order. A BullPutSpread strategy could create an instance of this Order class without needing to know anything about the ibapi library.
3. Representing Multi-Leg Strategies
Your goal is to trade credit spreads, which are multi-leg strategies. A bull put spread, for example, involves two simultaneous transactions:
- SELL a put option at strike K1.
- BUY a put option at a lower strike K2.
These two "legs" must be executed as a single, atomic transaction at a specific net credit. Brokers handle this through a special combo order. Our Order object needs to be able to represent this structure.
We can extend our design by introducing a concept of OrderLegs. The main Order object will represent the combo as a whole, and it will contain a list of OrderLeg objects that define each part of the spread.
An OrderLeg needs to define a specific contract. When dealing with options, the most reliable way to identify a contract is with its unique Contract ID (conId), which we can retrieve from the options chain data.
Let's define the OrderLeg and update our Order class:
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class OrderLeg:
"""
Represents a single leg of a multi-leg combo order.
"""
conId: int # The unique contract ID of the option
ratio: int # The ratio of this leg to others (usually 1 for spreads)
action: str # 'BUY' or 'SELL'
exchange: str = 'SMART' # Default to SMART routing
@dataclass
class Order:
"""
Represents a trading order, supporting both single-leg and multi-leg strategies.
"""
symbol: str # Underlying symbol (e.g., 'SPY')
action: str # For a combo, this is 'BUY' (for a net debit) or 'SELL' (for a net credit)
quantity: int # Number of spread contracts to trade
order_type: str # 'MKT' or 'LMT'
limit_price: Optional[float] = None # For spreads, this is the net credit/debit
time_in_force: str = 'DAY'
legs: List[OrderLeg] = field(default_factory=list) # List of order legs for combos
With this design:
- A single-leg order is an
Orderobject where thelegslist is empty. Thesymbolrefers to the stock/option to be traded directly. - A multi-leg order is an
Orderobject where thelegslist is populated withOrderLegobjects. Thesymbolis the underlying, andlimit_priceis the desired net price for the entire spread.
This unified structure is powerful. The ExecutionHandler can simply check if order.legs: to decide whether to build a simple order or a complex combo order.
4. Example: Representing a Bull Put Spread
Let's make this concrete. Imagine our BullPutSpread strategy has analyzed the SPY options chain and decided on the following trade:
- Strategy: Bull Put Spread on SPY.
- Goal: Receive a net credit of at least $1.50.
- Leg 1: Sell 1 SPY Put with strike $450 (
conId12345). - Leg 2: Buy 1 SPY Put with strike $440 (
conId67890).
Here is how we would represent this as one of our Order objects:
# Define the legs first
leg1 = OrderLeg(conId=12345, ratio=1, action='SELL')
leg2 = OrderLeg(conId=67890, ratio=1, action='BUY')
# Create the main order object for the spread
bull_put_spread_order = Order(
symbol='SPY',
action='SELL', # We are selling the spread to receive a credit
quantity=1, # We want to trade one contract of this spread
order_type='LMT',
limit_price=1.50, # The minimum net credit we want to receive
legs=[leg1, leg2]
)
print(bull_put_spread_order)
This bull_put_spread_order object neatly encapsulates the entire trade. It's now ready to be passed to the ExecutionHandler for translation and submission to the broker.
Test your understanding!
Using the Order and OrderLeg dataclasses we just designed, how would you represent an order for a Bear Call Spread on TSLA?
Assume the following:
- You want to trade 5 contracts of the spread.
- You want to receive a net credit of at least $2.10.
- Leg 1: Sell 5 TSLA Calls with strike $200 (
conId99887). - Leg 2: Buy 5 TSLA Calls with strike $210 (
conId77665).
Write the Python code to create the Order object.
Show answer
# Define the legs for the bear call spread
sell_call_leg = OrderLeg(conId=99887, ratio=1, action='SELL')
buy_call_leg = OrderLeg(conId=77665, ratio=1, action='BUY')
# Create the main order object for the 5-lot spread
bear_call_spread_order = Order(
symbol='TSLA',
action='SELL', # We are selling the spread for a credit
quantity=5, # We are trading 5 contracts of the spread
order_type='LMT',
limit_price=2.10, # The desired minimum net credit
legs=[sell_call_leg, buy_call_leg]
)
print(bear_call_spread_order)
Notice that the structure is identical to the bull put spread. Only the leg details (conId) and order parameters (symbol, quantity, limit_price) change. This highlights the flexibility of our design.
Conclusion
In this lesson, we have designed a robust and flexible internal Order object. This is a fundamental data structure in our framework that serves as the bridge between strategy-level decisions and broker-level execution.
Key Takeaways:
- We designed our own
Orderobject to decouple our strategy logic from a specific broker's API. - We used Python's
dataclassto create a clean and readable structure for ourOrderandOrderLegobjects. - Our final design can seamlessly represent both simple single-leg orders and complex multi-leg strategies like the credit spreads you aim to trade.
Preview of the Next Lesson:
We now have a perfect description of the order we want to place. The next step is to translate this into a format Interactive Brokers can execute. In our next lesson, we will implement a function to construct a multi-leg combo order for a credit spread. This will involve taking our internal Order object and using it to populate the Contract and Order objects from the ibapi library, paying special attention to the comboLegs attribute.