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: However, when I go into the Order Ticket, the buy/sell actions for the legs are reversed: 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...