Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

Building Credit Spreads: A Multi-Leg Order Function

Recommended resources

Hello! Welcome back to Module 8.

In our last lesson, we designed a clean, broker-agnostic Order object. This was a crucial step in decoupling our strategy logic from the specifics of any one broker's API. We now have a standardized way to describe the trade we want to make, whether it's a simple stock purchase or a complex credit spread.

Today, we will build the bridge from our internal system to the Interactive Brokers (IB) API. Our learning outcome is to implement a function to construct a multi-leg combo order for a credit spread. This function will act as a "translator," taking our internal Order object as input and producing the precise Contract and Order objects that the IB API requires to execute a spread trade.

1. From Manual Clicks to Automated Code

Before we dive into the code, it's helpful to see what we're trying to automate. In a trading platform like IB's Trader Workstation (TWS), you would manually build a spread by selecting each leg and adding it to a "Strategy Builder."

To visualize this, take a few minutes to watch how a bull put spread is constructed manually in TWS.

Trading Spreads in Interactive Brokers (Verticals, Iron Condors, Butterfly Spreads)

This video from the 'shortthestrike' channel demonstrates building a short vertical put spread (a bull put spread) in Interactive Brokers' TWS. Notice how the user selects a short put and a long put, which are then combined into a single order ticket for a net credit.

Please watch the segment from 09:23 to 11:39. Focus on how two separate options legs are combined into a single spread order with a net credit price.

Our goal is to replicate this process programmatically. Instead of clicking on bids and asks, we will construct the same multi-leg order using Python code.


2. The Interactive Brokers API Approach: "BAG" Contracts

The IB API has a specific way of handling multi-leg orders. Instead of treating a spread as a special order type, it treats it as a special contract type. This is a key concept. You don't place a "spread order"; you place a regular order for a "combo contract."

This special contract is called a "BAG". A BAG contract acts as a container for the individual legs of the strategy.

Interactive Brokers Trader Workstation (TWS) Interface with Order Entry
The Trader Workstation (TWS) interface, where complex orders like spreads can be manually constructed and submitted. Our goal is to automate this process.

To build a combo order for a credit spread, we need to create two main objects from the ibapi library:

  1. ibapi.contract.Contract: This object will define the spread itself. Its secType will be set to 'BAG', and it will contain a list of ComboLeg objects.
  2. ibapi.order.Order: This will be a standard order object (e.g., a Limit order) that specifies the action (buy or sell the BAG), the quantity, and the net price for the entire spread.

The official IB documentation provides the essential details for this process.

TWS Python API Placing Complex Orders | Trading Lesson

The following resource from the Interactive Brokers 'Traders' Campus' explains how to place complex orders. It's the definitive guide for our task.

Please read the initial sections of this page. Start from the beginning and read down to the line 'This is everything we need for our contract.' Focus on: The necessary imports: ComboLeg. How the main contract is defined with secType = 'BAG'. How individual ComboLeg objects are created and appended to the main contract's comboLegs list.

As you've just read, the process involves defining the main BAG contract and then programmatically adding each leg to its comboLegs attribute. Each ComboLeg requires the specific contract ID (conId), the ratio, the action (BUY or SELL), and the exchange.


3. Implementing the Translation Function

Now let's write the Python function that performs this translation. This function will take our internal Order dataclass (from the previous lesson) as input and return a tuple containing the corresponding ibapi.contract.Contract and ibapi.order.Order objects.

Here is the function signature we'll build out:

from typing import Tuple
from ibapi.contract import Contract
from ibapi.order import Order
# Assuming our internal Order class from the previous lesson is in a file named 'framework.models'
from framework.models import Order as InternalOrder 

def create_ib_combo_order(internal_order: InternalOrder) -> Tuple[Contract, Order]:
    # Implementation to follow
    pass

Let's build it step by step.

Step 3.1: Create the Main "BAG" Contract

First, we create the main Contract object that represents the entire spread.

# Inside create_ib_combo_order function

# 1. Create the master 'BAG' contract
ib_contract = Contract()
ib_contract.symbol = internal_order.symbol
ib_contract.secType = "BAG"
ib_contract.currency = "USD"
ib_contract.exchange = "SMART" # SMART is usually the best choice for combo routing

Step 3.2: Create and Attach the ComboLegs

Next, we loop through the legs in our internal_order object and create an ibapi.contract.ComboLeg for each one. These are then appended to the comboLegs list of our main ib_contract.

from ibapi.contract import ComboLeg

# (Continuing inside the function)

# 2. Create and attach the legs
ib_contract.comboLegs = []
for leg in internal_order.legs:
    combo_leg = ComboLeg()
    combo_leg.conId = leg.conId
    combo_leg.ratio = leg.ratio
    combo_leg.action = leg.action
    combo_leg.exchange = leg.exchange
    ib_contract.comboLegs.append(combo_leg)

This structure is conceptually similar to how other APIs handle multi-leg orders. For instance, the Alpaca API, as shown in the article "The Bull Put Spread Explained", uses a list of OptionLegRequest objects within a main order request. Here, we attach ComboLeg objects to a Contract.

Conceptualizing Bull Put Spread Order Construction with Python and Alpaca-py
This conceptual diagram for the Alpaca API illustrates a similar principle: combining individual leg definitions into a single programmatic order for a spread strategy.

Step 3.3: Create the Execution Order

Finally, we create the ibapi.order.Order object. This defines how we want to trade the BAG contract we just built. For a credit spread, we are selling the spread to open the position, and the limit price is the net credit we want to receive.

# (Continuing inside the function)

# 3. Create the order to trade the 'BAG' contract
ib_order = Order()
ib_order.action = internal_order.action  # 'SELL' for a credit spread
ib_order.orderType = internal_order.order_type # 'LMT'
ib_order.totalQuantity = internal_order.quantity
ib_order.lmtPrice = internal_order.limit_price # The net credit
ib_order.tif = internal_order.time_in_force # 'DAY', 'GTC', etc.

return ib_contract, ib_order

4. Complete Code and Example

Let's put all the pieces together into the complete function. We will then test it using the bull_put_spread_order example from our previous lesson.

# --- Assume these are our internal dataclasses from the previous lesson ---
from dataclasses import dataclass, field
from typing import Optional, List, Tuple

@dataclass
class OrderLeg:
    conId: int
    ratio: int
    action: str
    exchange: str = 'SMART'

@dataclass
class InternalOrder:
    symbol: str
    action: str
    quantity: int
    order_type: str
    limit_price: Optional[float] = None
    time_in_force: str = 'DAY'
    legs: List[OrderLeg] = field(default_factory=list)

# --- IB API imports ---
from ibapi.contract import Contract, ComboLeg
from ibapi.order import Order

# --- Our translation function ---
def create_ib_combo_order(internal_order: InternalOrder) -> Tuple[Contract, Order]:
    """
    Translates an internal Order object into IB-specific Contract and Order objects
    for a multi-leg combo.
    """
    if not internal_order.legs:
        raise ValueError("Cannot create combo order with no legs.")

    # 1. Create the master 'BAG' contract
    ib_contract = Contract()
    ib_contract.symbol = internal_order.symbol
    ib_contract.secType = "BAG"
    ib_contract.currency = "USD"
    ib_contract.exchange = "SMART"

    # 2. Create and attach the legs
    ib_contract.comboLegs = []
    for leg in internal_order.legs:
        combo_leg = ComboLeg()
        combo_leg.conId = leg.conId
        combo_leg.ratio = leg.ratio
        combo_leg.action = leg.action
        combo_leg.exchange = leg.exchange
        ib_contract.comboLegs.append(combo_leg)

    # 3. Create the order to trade the 'BAG' contract
    ib_order = Order()
    ib_order.action = internal_order.action
    ib_order.orderType = internal_order.order_type
    ib_order.totalQuantity = internal_order.quantity
    ib_order.lmtPrice = internal_order.limit_price
    ib_order.tif = internal_order.time_in_force

    return ib_contract, ib_order

# --- Example Usage ---
# Recreate the bull put spread order from the previous lesson
leg1 = OrderLeg(conId=12345, ratio=1, action='SELL') # Short Put
leg2 = OrderLeg(conId=67890, ratio=1, action='BUY')  # Long Put

bull_put_spread_order = InternalOrder(
    symbol='SPY',
    action='SELL',         # Sell the spread for a net credit
    quantity=1,            
    order_type='LMT',
    limit_price=1.50,      # Min credit of $1.50
    legs=[leg1, leg2]
)

# Perform the translation
ib_spread_contract, ib_spread_order = create_ib_combo_order(bull_put_spread_order)

# Print the results to verify
print("--- IB Contract ---")
print(f"Symbol: {ib_spread_contract.symbol}")
print(f"SecType: {ib_spread_contract.secType}")
print(f"Leg 1 conId: {ib_spread_contract.comboLegs[0].conId}, Action: {ib_spread_contract.comboLegs[0].action}")
print(f"Leg 2 conId: {ib_spread_contract.comboLegs[1].conId}, Action: {ib_spread_contract.comboLegs[1].action}")
print("\n--- IB Order ---")
print(f"Action: {ib_spread_order.action}")
print(f"Quantity: {ib_spread_order.totalQuantity}")
print(f"Order Type: {ib_spread_order.orderType}")
print(f"Limit Price: {ib_spread_order.lmtPrice}")

The output of this code demonstrates that our internal, abstract Order object has been successfully translated into the concrete objects the IB API can work with.

Test your understanding!

Imagine you are closing the bull put spread position from the example above. To close a credit spread, you need to execute the opposite trade: you buy the spread back. Assuming you are willing to pay up to a net debit of $0.25 to close the position.

How would the InternalOrder object look for this closing trade? What would be the action and limit_price?

Show answer

The InternalOrder object would be constructed as follows. The legs remain the same, but the main order action and price are reversed.

closing_order = InternalOrder(
    symbol='SPY',
    action='BUY',          # We BUY the spread to close it
    quantity=1,
    order_type='LMT',
    limit_price=0.25,      # The maximum net debit we are willing to pay
    legs=[leg1, leg2]
)

# When passed to our function, this would create an ib_order with action='BUY'
# and lmtPrice=0.25.

The key is that the overall action on the BAG contract determines whether you are opening for a credit (SELL) or closing for a debit (BUY), or vice-versa for debit spreads.


Conclusion

In this lesson, you have built a critical component of your trading framework's execution layer. You now have a function that can translate your framework's internal representation of a credit spread into the specific format required by the Interactive Brokers API.

Key Takeaways:

  • A multi-leg spread is represented in the IB API as a Contract with secType='BAG'.
  • The individual legs of the spread are defined as ComboLeg objects and attached to the main BAG contract.
  • A standard Order object is used to specify how to trade this BAG contract (e.g., SELL 1 BAG at a limit price of 1.50).
  • We created a robust create_ib_combo_order function to automate this translation, keeping our core strategy logic clean and broker-agnostic.

Preview of the Next Lesson:
We can now construct the order, but we can't send it to the broker yet. The next step is to build the component responsible for managing the API connection and submitting these orders. In the next lesson, we will build an ExecutionHandler to submit, modify, and cancel orders through the broker API. This handler will use the translation function we created today as a core part of its logic.