[Python] Placing a Bull Put Spread order with IBKR's TWS API using ibapi

Discussão em 'Python' iniciado por Stack, Março 6, 2025.

  1. Stack

    Stack Membro Participativo

    I'm playing around with IBKR's ibapi library using the TWS API, and trying to make a simple script that will place a Bull Put Spread order.

    from ibapi.wrapper import EWrapper
    from ibapi.contract import Contract
    from ibapi.order import Order
    import threading
    import time

    class IBapi(EWrapper, EClient):
    def __init__(self):
    EClient.__init__(self, self)
    self.nextOrderId = None
    self.position_data = []
    self.order_status = {}
    self.completed_orders = {}
    self.contract_details = {}
    self.contract_details_end = {}

    def nextValidId(self, orderId: int):
    super().nextValidId(orderId)
    self.nextOrderId = orderId
    print(f"Next Valid Order ID: {orderId}")

    def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
    super().orderStatus(orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice)
    print(f"Order Status - OrderId: {orderId}, Status: {status}, Filled: {filled}, Remaining: {remaining}")
    self.order_status[orderId] = status

    def openOrder(self, orderId, contract, order, orderState):
    super().openOrder(orderId, contract, order, orderState)
    print(f"Open Order - OrderId: {orderId}, Symbol: {contract.symbol}, Order Type: {order.orderType}, Action: {order.action}")

    def execDetails(self, reqId, contract, execution):
    super().execDetails(reqId, contract, execution)
    print(f"Execution Details - ReqId: {reqId}, Symbol: {contract.symbol}, ExecId: {execution.execId}, Price: {execution.price}")

    def contractDetails(self, reqId, contractDetails):
    super().contractDetails(reqId, contractDetails)
    self.contract_details[reqId] = contractDetails
    print(f"Contract Details - ReqId: {reqId}, ConId: {contractDetails.contract.conId}, Symbol: {contractDetails.contract.symbol}")

    def contractDetailsEnd(self, reqId):
    super().contractDetailsEnd(reqId)
    self.contract_details_end[reqId] = True
    print(f"Contract Details End - ReqId: {reqId}")

    def create_option_contract(symbol, expiration, strike, right):
    contract = Contract()
    contract.symbol = symbol
    contract.secType = "OPT"
    contract.exchange = "SMART"
    contract.currency = "USD"
    contract.lastTradeDateOrContractMonth = expiration # Format "YYYYMMDD"
    contract.strike = strike
    contract.right = right # "P" for Put, "C" for Call
    contract.multiplier = "100"
    return contract

    def create_order(action, quantity, price=None, order_type="LMT"):
    order = Order()
    order.action = action
    order.totalQuantity = quantity
    order.orderType = order_type
    if price is not None:
    order.lmtPrice = price

    # Disable problematic attributes
    order.eTradeOnly = False
    order.firmQuoteOnly = False

    # Set additional order properties to ensure proper execution
    order.transmit = True
    order.outsideRth = False # Execute during regular trading hours only

    return order

    def run_loop():
    app.run()

    # Main code to execute a bull put spread
    if __name__ == "__main__":
    from ibapi.contract import ComboLeg

    # Connect to IB TWS or IB Gateway
    app = IBapi()
    app.connect('127.0.0.1', 7497, 0) # 7497 for TWS paper trading, 7496 for IB Gateway paper

    # Start the thread for processing IB messages
    api_thread = threading.Thread(target=run_loop, daemon=True)
    api_thread.start()

    # Wait for connection and nextValidId
    time.sleep(2)

    if app.nextOrderId is None:
    print("Failed to connect to IB API. Check if TWS/IB Gateway is running and API connections are enabled.")
    app.disconnect()
    exit(1)

    try:
    symbol = "SPY"
    expiration = "20250411" # April 11, 2025

    higher_strike = 575
    lower_strike = 570
    quantity = 1
    credit_limit = 1.70

    # Create contracts for both legs
    sell_put_contract = create_option_contract(symbol, expiration, higher_strike, "P")
    buy_put_contract = create_option_contract(symbol, expiration, lower_strike, "P")

    print(f"Requesting contract details for {symbol} puts at strikes {higher_strike} and {lower_strike}...")

    # Request contract details to get the conIds
    app.reqContractDetails(1, sell_put_contract)
    app.reqContractDetails(2, buy_put_contract)

    # Wait for contract details to be received
    timeout = 10 # seconds
    start_time = time.time()
    while (1 not in app.contract_details_end or 2 not in app.contract_details_end) and (time.time() - start_time < timeout):
    time.sleep(0.1)

    if 1 not in app.contract_details or 2 not in app.contract_details:
    print("Failed to receive contract details. Check if the options exist for the specified expiration and strikes.")
    app.disconnect()
    exit(1)

    # Get the contract IDs
    sell_put_conid = app.contract_details[1].contract.conId
    buy_put_conid = app.contract_details[2].contract.conId

    print(f"Retrieved Contract IDs - Sell {higher_strike} Put: {sell_put_conid}, Buy {lower_strike} Put: {buy_put_conid}")

    # Create a combo contract for the bull put spread
    combo = Contract()
    combo.symbol = symbol
    combo.secType = "BAG"
    combo.exchange = "SMART"
    combo.currency = "USD"

    # Create the legs
    # For a bull put spread:
    # - SELL the higher strike put (575)
    # - BUY the lower strike put (570)
    leg1 = ComboLeg()
    leg1.conId = sell_put_conid # ConId for the 575 strike put
    leg1.ratio = 1
    leg1.action = "SELL"
    leg1.exchange = "SMART"

    leg2 = ComboLeg()
    leg2.conId = buy_put_conid # ConId for the 570 strike put
    leg2.ratio = 1
    leg2.action = "BUY"
    leg2.exchange = "SMART"

    combo.comboLegs = [leg1, leg2]

    # Create the combo order
    spread_order = create_order("SELL", quantity, credit_limit)

    print(f"Placing bull put spread on {symbol} as a combo order:")
    print(f"- SELL {quantity} {symbol} {expiration} {higher_strike} Put (ConId: {sell_put_conid})")
    print(f"- BUY {quantity} {symbol} {expiration} {lower_strike} Put (ConId: {buy_put_conid})")
    print(f"- Credit limit: ${credit_limit}")

    # Place the combo order
    app.placeOrder(app.nextOrderId, combo, spread_order)
    combo_order_id = app.nextOrderId
    app.nextOrderId += 1

    print(f"Combo order placed - Order ID: {combo_order_id}")

    # Monitor order status for a bit
    time.sleep(10)

    # Check order status
    print("\nOrder status summary:")
    for order_id, status in app.order_status.items():
    print(f"Order {order_id}: {status}")

    except Exception as e:
    print(f"Error: {e}")

    finally:
    # Disconnect
    print("\nDisconnecting from IB API...")
    app.disconnect()
    print("Disconnected.")


    It seems like it's almost working, except I'm getting an error in TWS that says "Riskless combination orders are not allowed".

    In addition, in the Orders tab in TWS, the order looks correct to me: [​IMG]

    However, when I go into the Order Ticket, the buy/sell actions for the legs are reversed: [​IMG]

    I believe this is the cause of the error, but it's not clear to me what I'm doing wrong in my script. Does anyone know what the issue might be?

    Continue reading...

Compartilhe esta Página