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 = {}
selfpleted_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"
comboboLegs = [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?
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 = {}
selfpleted_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"
comboboLegs = [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?
Share Improve this question edited Mar 6 at 16:41 Rhys Causey asked Mar 6 at 16:31 Rhys CauseyRhys Causey 7877 silver badges22 bronze badges1 Answer
Reset to default 0I believe I've figured it out. When you open a position (whether net debit or credit), it's a buy, and when you close a position, it's a sell.