def orderStatus( self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice=0.0, lastLiquidity=0): key = self.orderKey(clientId, orderId, permId) trade = self.trades.get(key) if trade: oldStatus = trade.orderStatus.status new = dict( status=status, filled=filled, remaining=remaining, avgFillPrice=avgFillPrice, permId=permId, parentId=parentId, lastFillPrice=lastFillPrice, clientId=clientId, whyHeld=whyHeld, mktCapPrice=mktCapPrice, lastLiquidity=lastLiquidity) curr = trade.orderStatus.dict() isChanged = curr != {**curr, **new} if isChanged: trade.orderStatus.update(**new) msg = '' elif (status == 'Submitted' and trade.log and trade.log[-1].message == 'Modify'): # order modifications are acknowledged msg = 'Modified' else: msg = None if msg is not None: logEntry = TradeLogEntry(self.lastTime, status, msg) trade.log.append(logEntry) self._logger.info(f'orderStatus: {trade}') self._ib.orderStatusEvent.emit(trade) trade.statusEvent.emit(trade) if status != oldStatus: if status == OrderStatus.Filled: trade.filledEvent.emit(trade) elif status == OrderStatus.Cancelled: trade.cancelledEvent.emit(trade) else: self._logger.error( 'orderStatus: No order found for ' 'orderId %s and clientId %s', orderId, clientId)
def error(self, reqId, errorCode, errorString): # https://interactivebrokers.github.io/tws-api/message_codes.html warningCodes = {165, 202, 399, 434, 10167} isWarning = errorCode in warningCodes or 2100 <= errorCode < 2200 msg = ( f'{"Warning" if isWarning else "Error"} ' f'{errorCode}, reqId {reqId}: {errorString}') contract = self._reqId2Contract.get(reqId) if contract: msg += f', contract: {contract}' if isWarning: self._logger.info(msg) else: self._logger.error(msg) if reqId in self._futures: # the request failed self._endReq(reqId) elif (self.clientId, reqId) in self.trades: # something is wrong with the order, cancel it trade = self.trades[(self.clientId, reqId)] if not trade.isDone(): status = trade.orderStatus.status = OrderStatus.Cancelled logEntry = TradeLogEntry(self.lastTime, status, msg) trade.log.append(logEntry) self._logger.warning(f'Canceled order: {trade}') self.ib.orderStatusEvent.emit(trade) trade.cancelledEvent.emit(trade) elif errorCode == 317: # Market depth data has been RESET ticker = self.reqId2Ticker.get(reqId) if ticker: for side, l in ((0, ticker.domAsks), (1, ticker.domBids)): for position in reversed(l): level = l.pop(position) tick = MktDepthData( self.lastTime, position, '', 2, side, level.price, 0) ticker.domTicks.append(tick) self.ib.errorEvent.emit(reqId, errorCode, errorString, contract)
def execDetails( self, reqId: int, contract: Contract, execution: Execution): """ This wrapper handles both live fills and responses to reqExecutions. """ self._logger.info(f'execDetails {execution}') if execution.orderId == UNSET_INTEGER: # bug in TWS: executions of manual orders have unset value execution.orderId = 0 trade = self.permId2Trade.get(execution.permId) if not trade: key = self.orderKey( execution.clientId, execution.orderId, execution.permId) trade = self.trades.get(key) if trade and contract == trade.contract: contract = trade.contract else: contract = Contract.create(**dataclassAsDict(contract)) execId = execution.execId isLive = reqId not in self._futures time = self.lastTime if isLive else execution.time fill = Fill(contract, execution, CommissionReport(), time) if execId not in self.fills: # first time we see this execution so add it self.fills[execId] = fill if trade: trade.fills.append(fill) logEntry = TradeLogEntry( time, trade.orderStatus.status, f'Fill {execution.shares}@{execution.price}') trade.log.append(logEntry) if isLive: self._logger.info(f'execDetails: {fill}') self.ib.execDetailsEvent.emit(trade, fill) trade.fillEvent(trade, fill) if not isLive: self._results[reqId].append(fill)
def execDetails(self, reqId, contract, execution): """ This wrapper handles both live fills and responses to reqExecutions. """ if execution.orderId == 2147483647: # bug in TWS: executions of manual orders have orderId=2**31 - 1 execution.orderId = 0 key = self.orderKey( execution.clientId, execution.orderId, execution.permId) trade = self.trades.get(key) if trade and contract.conId == trade.contract.conId: contract = trade.contract else: contract = self._getContract(contract) execId = execution.execId execution = Execution(**execution.__dict__) execution.time = util.parseIBDatetime(execution.time). \ astimezone(datetime.timezone.utc) isLive = reqId not in self._futures time = self.lastTime if isLive else execution.time fill = Fill(contract, execution, CommissionReport(), time) if execId not in self.fills: # first time we see this execution so add it self.fills[execId] = fill if trade: trade.fills.append(fill) logEntry = TradeLogEntry( self.lastTime, trade.orderStatus.status, f'Fill {execution.shares}@{execution.price}') trade.log.append(logEntry) if isLive: self._logger.info(f'execDetails: {fill}') self._ib.execDetailsEvent.emit(trade, fill) trade.fillEvent(trade, fill) if not isLive: self._results[reqId].append(fill)
def error(self, reqId: int, errorCode: int, errorString: str): # https://interactivebrokers.github.io/tws-api/message_codes.html warningCodes = {165, 202, 399, 404, 434, 492, 10167} isWarning = errorCode in warningCodes or 2100 <= errorCode < 2200 msg = ( f'{"Warning" if isWarning else "Error"} ' f'{errorCode}, reqId {reqId}: {errorString}') contract = self._reqId2Contract.get(reqId) if contract: msg += f', contract: {contract}' if isWarning: self._logger.info(msg) else: self._logger.error(msg) if reqId in self._futures: # the request failed if self.ib.RaiseRequestErrors: error = RequestError(reqId, errorCode, errorString) self._endReq(reqId, error, success=False) else: self._endReq(reqId) elif (self.clientId, reqId) in self.trades: # something is wrong with the order, cancel it trade = self.trades[(self.clientId, reqId)] if not trade.isDone(): status = trade.orderStatus.status = OrderStatus.Cancelled logEntry = TradeLogEntry(self.lastTime, status, msg) trade.log.append(logEntry) self._logger.warning(f'Canceled order: {trade}') self.ib.orderStatusEvent.emit(trade) trade.statusEvent.emit(trade) trade.cancelledEvent.emit(trade) if errorCode == 165: # for scan data subscription there are no longer matching results dataList = self.reqId2Subscriber.get(reqId) if dataList: dataList.clear() dataList.updateEvent.emit(dataList) elif errorCode == 317: # Market depth data has been RESET ticker = self.reqId2Ticker.get(reqId) if ticker: # clear all DOM levels ticker.domTicks += [MktDepthData( self.lastTime, 0, '', 2, 0, level.price, 0) for level in ticker.domAsks] ticker.domTicks += [MktDepthData( self.lastTime, 0, '', 2, 1, level.price, 0) for level in ticker.domBids] ticker.domAsks.clear() ticker.domBids.clear() self.pendingTickers.add(ticker) elif errorCode == 10225: # Bust event occurred, current subscription is deactivated. # Please resubscribe real-time bars immediately bars = self.reqId2Subscriber.get(reqId) if isinstance(bars, RealTimeBarList): self.ib.client.cancelRealTimeBars(reqId) self.ib.client.reqRealTimeBars( reqId, bars.contract, bars.barSize, bars.whatToShow, bars.useRTH, bars.realTimeBarsOptions) elif isinstance(bars, BarDataList): self.ib.client.cancelHistoricalData(reqId) self.ib.client.reqHistoricalData( reqId, bars.contract, bars.endDateTime, bars.durationStr, bars.barSizeSetting, bars.whatToShow, bars.useRTH, bars.formatDate, bars.keepUpToDate, bars.chartOptions) self.ib.errorEvent.emit(reqId, errorCode, errorString, contract)