import argparse
import datetime
import logging
import sys
import time
import threading
from ibapi import wrapper
from ibapi.client import EClient
from ibapi.contract import Contract
from ibapi.order import Order
from ibapi.utils import iswrapper
from ibapi.common import OrderId
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 4002
DEFAULT_CLIENT_ID = 103
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
class TestApp(wrapper.EWrapper, EClient):
def __init__(self):
wrapper.EWrapper.__init__(self)
EClient.__init__(self, wrapper=self)
self.nKeybInt = 0
self.started = False
self.nextValidOrderId = -1
self.permId2ord = {}
self.reqId2nErr = {}
self.globalCancelOnly = False
self._my_errors = {}
self.order_id = -1
self.order_placed = threading.Event()
self.order_status_received = threading.Event()
self.order_states = {}
@iswrapper
def connectAck(self):
log.info("Connection acknowledged.")
@iswrapper
def nextValidId(self, orderId: int):
super().nextValidId(orderId)
self.nextValidOrderId = orderId
log.info("nextValidId: %d", orderId)
self.start()
def start(self):
if self.started:
return
self.started = True
log.info("Executing order placement test")
self.place_order_test()
log.info("Order placement request finished")
def keyboardInterrupt(self):
self.nKeybInt += 1
if self.nKeybInt == 1:
log.info("Keyboard interrupt detected. Disconnecting...")
self.done = True
self.disconnect()
else:
log.info("Forcing exit...")
sys.exit(0)
@iswrapper
def error(self, reqId: int, errorCode: int, errorString: str, advancedOrderReject=""):
super().error(reqId, errorCode, errorString, advancedOrderReject)
if advancedOrderReject:
log.error("Error. Id: %d, Code: %d, Msg: %s, AdvancedOrderReject: %s",
reqId, errorCode, errorString, advancedOrderReject)
else:
log.error("Error. Id: %d, Code: %d, Msg: %s", reqId, errorCode, errorString)
if reqId > 0:
self.reqId2nErr[reqId] = self.reqId2nErr.get(reqId, 0) + 1
self._my_errors[reqId] = (errorCode, errorString)
if reqId == self.order_id:
log.error(f"Order {reqId} failed with error {errorCode}: {errorString}")
self.order_status_received.set()
@iswrapper
def openOrder(self, orderId: OrderId, contract: Contract, order: Order, orderState):
log.info("CALLBACK openOrder: OrderId: %d, Symbol: %s, Status: %s",
orderId, contract.symbol, orderState.status)
log.debug("OpenOrder. OrderId: %d, Symbol: %s, SecType: %s, Side: %s, Quantity: %f, "
"OrderType: %s, LimitPrice: %s, AuxPrice: %s, Status: %s",
orderId, contract.symbol, contract.secType, order.action, order.totalQuantity,
order.orderType, order.lmtPrice, order.auxPrice, orderState.status)
if orderId == self.order_id:
self.order_states[orderId] = orderState
self.order_placed.set()
@iswrapper
def orderStatus(self, orderId: OrderId, status: str, filled: float,
remaining: float, avgFillPrice: float, permId: int,
parentId: int, lastFillPrice: float, clientId: int, whyHeld: str,
mktCapPrice: float):
log.info("CALLBACK orderStatus: OrderId: %d, Status: %s, Filled: %f, Remaining: %f, "
"AvgFillPrice: %f, PermId: %d",
orderId, status, filled, remaining, avgFillPrice, permId)
if orderId == self.order_id:
self.order_states[orderId] = {
'status': status,
'filled': filled,
'remaining': remaining,
'avgFillPrice': avgFillPrice,
'permId': permId,
'parentId': parentId,
'lastFillPrice': lastFillPrice,
'clientId': clientId,
'whyHeld': whyHeld,
'mktCapPrice': mktCapPrice
}
if status in ['Filled', 'Cancelled', 'ApiCancelled', 'Inactive']:
log.info(f"Order {orderId} reached terminal status: {status}")
self.order_status_received.set()
@iswrapper
def execDetails(self, reqId: int, contract: Contract, execution):
log.info("CALLBACK execDetails: ReqId: %d, Symbol: %s, ExecId: %s, Side: %s, "
"Shares: %f, Price: %f",
reqId, contract.symbol, execution.execId, execution.side,
execution.shares, execution.price)
@iswrapper
def commissionReport(self, commissionReport):
log.info("CALLBACK commissionReport: ExecId: %s, Commission: %f, Currency: %s",
commissionReport.execId, commissionReport.commission,
commissionReport.currency)
def place_order_test(self):
self.order_id = self.nextValidOrderId
self.nextValidOrderId += 1
log.info(f"Placing order with orderId: {self.order_id}")
contract = Contract()
contract.conId = 272093 contract.symbol = "MSFT"
contract.secType = "STK"
contract.exchange = "SMART"
contract.currency = "USD"
contract.localSymbol = "MSFT"
contract.tradingClass = "NMS"
order = Order()
order.orderId = self.order_id
order.orderType = "STP LMT" order.action = "SELL" order.totalQuantity = 1.0 order.lmtPrice = 457.0 order.auxPrice = 461.0 order.tif = "DAY" order.outsideRth = True order.transmit = True
order.minQty = 0 order.percentOffset = 0.0 order.trailingPercent = 0.0 order.trailStopPrice = 471.0 order.ocaType = 3 order.triggerMethod = 0 order.clearingIntent = "IB" order.openClose = "O" order.origin = 0
order.scaleInitLevelSize = 0 order.volatilityType = 0
order.deltaNeutralOrderType = "None" order.deltaNeutralConId = 0 order.deltaNeutralShortSale = False order.deltaNeutralShortSaleSlot = 0
order.dontUseAutoPriceForHedge = True order.usePriceMgmtAlgo = False order.duration = 0 order.postToAts = 0 order.minTradeQty = 0 order.minCompeteSize = 100 order.midOffsetAtWhole = 0.0 order.midOffsetAtHalf = 0.0 order.cashQty = 0.0 order.adjustableTrailingUnit = 0
log.info(f"Placing {order.action} {order.totalQuantity} {contract.symbol} "
f"{order.orderType} order: Stop=${order.auxPrice}, Limit=${order.lmtPrice}")
self.placeOrder(self.order_id, contract, order)
log.info(f"Order {self.order_id} placement request sent.")
def main():
parser = argparse.ArgumentParser(description="IB API Order Placement Test")
parser.add_argument("--host", default=DEFAULT_HOST, help="Host address")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="Port number")
parser.add_argument("--clientId", type=int, default=DEFAULT_CLIENT_ID, help="Client ID")
args = parser.parse_args()
log.info("Starting Order Placement Test")
log.info(f"Connecting to {args.host}:{args.port} with clientId {args.clientId}")
try:
app = TestApp()
log.info(f"Logger level set to: {logging.getLevelName(log.level)}")
app.connect(args.host, args.port, args.clientId)
log.info("Connection initiated. Server version: %s", app.serverVersion())
log.info("Starting EClient.run() message loop in background thread...")
thread = threading.Thread(target=app.run, daemon=True)
thread.start()
log.info("EClient.run() thread started.")
placement_timeout_secs = 30
log.info(f"Main thread waiting up to {placement_timeout_secs}s for order placement confirmation...")
placement_successful = app.order_placed.wait(timeout=placement_timeout_secs)
if placement_successful:
log.info(f"Main thread: Order {app.order_id} placement confirmed.")
status_timeout_secs = 60
log.info(f"Main thread waiting up to {status_timeout_secs}s for order status updates...")
status_received = app.order_status_received.wait(timeout=status_timeout_secs)
if status_received:
log.info(f"Main thread: Order {app.order_id} reached terminal status.")
if app.order_id in app.order_states:
state = app.order_states[app.order_id]
if isinstance(state, dict):
log.info(f" Final Status: {state.get('status', 'Unknown')}")
log.info(f" Filled: {state.get('filled', 0.0)}")
log.info(f" Remaining: {state.get('remaining', 0.0)}")
log.info(f" PermId: {state.get('permId', 0)}")
else:
log.info(f" Order State: {state.status}")
else:
log.warning(f"Main thread: Order {app.order_id} status update timed out!")
if app.isConnected() and not app._my_errors.get(app.order_id):
log.info(f"Main thread: Attempting to cancel order {app.order_id} due to timeout.")
app.cancelOrder(app.order_id, "")
else:
log.warning(f"Main thread: Order {app.order_id} placement timed out!")
log.info("Main thread: Disconnecting...")
if app.isConnected():
app.disconnect()
else:
log.info("Main thread: Already disconnected.")
join_timeout_secs = 10
log.info(f"Main thread waiting for EClient thread to join (timeout {join_timeout_secs}s)...")
thread.join(timeout=join_timeout_secs)
log.info("Main thread finished waiting for EClient thread.")
if thread.is_alive():
log.warning("EClient thread did not exit cleanly after disconnect and join timeout.")
if app.isConnected():
log.warning("Attempting disconnect again...")
app.disconnect()
else:
log.info("EClient thread exited cleanly.")
log.info("Order Placement Test finished.")
except Exception as e:
log.exception("Unhandled exception in main:")
finally:
log.info("Exiting.")
if __name__ == "__main__":
main()