def __str__(self): if self.requestedOrder is not None: # IBridgePy created orders ans = '{ibpyOrderId=%s status=%s order=%s contract=%s}' % ( self._ibpyOrderId, self.status, print_IBCpp_order(self.requestedOrder), print_IBCpp_contract(self.requestedContract)) else: # orders are called-back from IB server if self.get_value_by_tag('whyHeld') == '': if self.openOrderRecord.order.orderId not in [0, -1]: ans = '{ibpyOrderId=%s status=%s order=%s contract=%s}' % ( self.getIbpyOrderId(), self.status, print_IBCpp_order(self.openOrderRecord.order), print_IBCpp_contract(self.openOrderRecord.contract)) else: ans = '{permId=%s status=%s order=%s contract=%s}' % ( self.getIbpyOrderId(), self.status, print_IBCpp_order(self.openOrderRecord.order), print_IBCpp_contract(self.openOrderRecord.contract)) else: if self.openOrderRecord.order.orderId not in [0, -1]: ans = '{ibpyOrderId=%s status=%s order=%s contract=%s whyHeld=%s}' % ( self.getIbpyOrderId(), self.status, print_IBCpp_order(self.openOrderRecord.order), print_IBCpp_contract(self.openOrderRecord.contract), self.get_value_by_tag('whyHeld')) else: ans = '{permId=%s status=%s order=%s contract=%s whyHeld=%s}' % ( self.getIbpyOrderId(), self.status, print_IBCpp_order(self.openOrderRecord.order), print_IBCpp_contract(self.openOrderRecord.contract), self.get_value_by_tag('whyHeld')) return ans
def _placeOrderHelper(self, int_orderId, ibpyRequest, contract, ibcppOrder): ibpyOrderId = self._idConverter.fromIBtoBroker(int_orderId) # Set for ending flat. # Otherwise, the following line in broker_client_factory::CallBacks::orderStatus will not be able to find a reqId # reqId = self.activeRequests.find_reqId_by_int_orderId(int_orderId) ibpyRequest.param['int_orderId'] = int_orderId # Register ibpyOrderId in SingleTrader so that it can search accountCode by incoming int_orderId self._singleTrader.set_from_send_req_to_server(self.name, ibcppOrder.account, ibpyOrderId) # IBCpp function self.placeOrder(int_orderId, contract, ibcppOrder) self._log.debug( __name__ + '::placeOrderWrapper: int_orderId=%s contract=%s ibcppOrder=%s' % (int_orderId, print_IBCpp_contract(contract), print_IBCpp_order(ibcppOrder))) # Only IB order may not follow up. # If do not follow up on order, just set ending flag and assign ibpyOrderId here. if not ibpyRequest.followUp: ibpyRequest.returnedResult = ibpyOrderId ibpyRequest.status = ReqAttr.Status.COMPLETED
def execDetails(self, reqId, contract, execution): """ !!!!!! reqId is always -1 based on experiences :param reqId: :param contract: :param execution: :return: """ self._log.debug(__name__ + '::execDetails: reqId=%s contract=%s execution=%s' % (reqId, print_IBCpp_contract(contract), print_IBCpp_execution(execution))) int_orderId = execution.orderId ibpyOrderId = self._idConverter.fromIBtoBroker(int_orderId) accountCode = execution.acctNumber self._singleTrader.set_execDetails( self.brokerName, accountCode, ExecDetailsRecord(ibpyOrderId, contract, execution)) # IB will not invoke updateAccountValue immediately after execDetails # http://www.ibridgepy.com/knowledge-base/#Q_What_functions_will_be_called_back_from_IB_server_and_what_is_the_sequence_of_call_backs_after_an_order_is_executed # To make sure the user know the rough cashValue, IBridgePy has to simulate them. # The accurate value will be updated by IB regular updateAccountValue every 3 minutes. # The positionValue and portfolioValues are not simulated for live trading because the user just care about cashValue to buy other contracts # These values will be updated by IB regular updateAccountValue every 3 minutes. currentCashValue = self._singleTrader.get_account_info( self.brokerName, accountCode, 'TotalCashValue') cashChange = execution.shares * float(execution.price) if execution.side == 'BOT': currentCashValue -= cashChange elif execution.side == 'SLD': currentCashValue += cashChange self.updateAccountValue('TotalCashValue', currentCashValue, 'USD', accountCode)
def placeOrderWrapper(self, contract, order, ibpyRequest): self._validate_contract(contract, 'placeOrderWrapper') self._log.info('Place Order to %s security=%s order=%s' % (self.name, print_IBCpp_contract(contract), print_IBCpp_order(order))) tdOrder = OrderConverter().fromIBtoTD(contract, order) try: ibpyOrderId = self._tdClient.place_orders(order.account, tdOrder) except RuntimeError as e: if 'buying power' in str(e): raise NotEnoughFund() else: raise RuntimeError(e) if ibpyOrderId is None: raise RuntimeError('Place order to TD failed but there is not RuntimeError.') self._log.info('Order was placed to %s successfully. ibpyOrderId=%s' % (self.name, ibpyOrderId)) # Register int_orderId in _idConverter so that brokerClient::CallBack::orderStatus knows how to handle int_orderId int_orderId = self._idConverter.fromBrokerToIB(ibpyOrderId) self._idConverter.setRelationship(int_orderId, ibpyOrderId) # Set for ending flat. # Otherwise, the following line in broker_client_factory::CallBacks::orderStatus will not be able to find a reqId # reqId = self.activeRequests.find_reqId_by_int_orderId(int_orderId) ibpyRequest.param['int_orderId'] = int_orderId # Register ibpyOrderId in SingleTrader so that it can search accountCode by incoming int_orderId self._singleTrader.set_from_send_req_to_server(self.name, order.account, ibpyOrderId) # IBCpp function order.orderId = int_orderId self.simulateOpenOrder(int_orderId, contract, order, IBCpp.OrderState(), from_contract_to_security(contract).full_print()) # IBCpp function # IBCpp function, this is the ending flag for PlaceOrder self.simulateOrderStatus(int_orderId, 'Submitted', 0, order.totalQuantity, 0.0, 0, 0, 0, 0, '')
def order_status_monitor(self, ibpyOrderId, target_status, waitingTimeInSeconds=30): self._log.notset( __name__ + '::order_status_monitor: ibpyOrderId=%s target_status=%s' % (ibpyOrderId, target_status)) timer = dt.datetime.now() while True: time.sleep(0.1) self._brokerClient.processMessagesWrapper(self.get_datetime()) if (dt.datetime.now() - timer).total_seconds() <= waitingTimeInSeconds: tmp_status = self.get_order(ibpyOrderId).status if isinstance(target_status, str): if tmp_status == target_status: return elif isinstance(target_status, list): if tmp_status in target_status: return else: self._log.error( __name__ + '::order_status_monitor: EXIT, waiting time is too long, >%i' % (waitingTimeInSeconds, )) order = self.get_order(ibpyOrderId) contract = order.contract self._log.error( __name__ + '::order_status_monitor: EXIT, ibpyOrderId=%s status=%s contract=%s' % (ibpyOrderId, order.status, print_IBCpp_contract(contract))) raise OrderStatusNotConfirmed()
def placeOrderWrapper(self, contract, order, ibpyRequest): self._log.debug( __name__ + '::placeOrderWrapper: contract=%s order=%s' % (print_IBCpp_contract(contract), print_IBCpp_order(order))) if isinstance(order.orderId, int): int_orderId = order.orderId else: int_orderId = self.use_next_id() order.orderId = int_orderId # Set for ending flat. # Otherwise, the following line in broker_client_factory::CallBacks::orderStatus will not be able to find a reqId # reqId = self.activeRequests.find_reqId_by_int_orderId(int_orderId) ibpyRequest.param['int_orderId'] = int_orderId ibpyOrderId = self._idConverter.fromIBtoBroker(int_orderId) # Register ibpyOrderId in SingleTrader so that it can search accountCode by incoming int_orderId self._singleTrader.set_from_send_req_to_server(self.name, order.account, ibpyOrderId) self.orderToBeProcessed[int_orderId] = (contract, order) self.simulateOpenOrder( int_orderId, contract, order, IBCpp.OrderState(), from_contract_to_security(contract).full_print()) # IBCpp function self.simulateOrderStatus(int_orderId, 'Submitted', 0, order.totalQuantity, 0.0, 0, 0, 0, 0, '') # IBCpp function self.simulate_process_order(self.get_datetime())
def _validate_contract(self, contract, caller): if contract.secType != 'STK' or contract.currency != 'USD': self._log.error( __name__ + '::%s: EXIT, cannot handle this contract=%s, only STK right now' % ( caller, print_IBCpp_contract(contract), )) exit()
def reqHistoricalDataWrapper(self, reqId, contract, str_endTime, goBack, barSize, whatToShow, useRTH, formatDate): # self._log.info(__name__ + '::reqHistoricalDataWrapper: reqId=%s contract=%s endTime=%s goBack=%s barSize=%s' % (reqId, print_IBCpp_contract(contract), endTime, goBack, barSize)) # https://developer.tdameritrade.com/content/price-history-samples self._validate_contract(contract, 'reqHistoricalDataWrapper') freq, freqType = BarSizeConverter().fromIBtoTD(barSize) pe, peType = GoBackConverter().fromIBtoTD(goBack, barSize) if str_endTime: str_endTime = dt.datetime.strptime( str_endTime, "%Y%m%d %H:%M:%S %Z" ) # string -> dt.datetime; strptime silently ignores timezone!!! str_endTime = pytz.timezone('UTC').localize(str_endTime) endDate = int(dt_to_epoch(str_endTime)) * 1000 ans = self._tdClient.history(str(contract.symbol), periodType=peType, period=pe, frequencyType=freqType, frequency=freq, endDate=endDate) else: ans = self._tdClient.history(str(contract.symbol), periodType=peType, period=pe, frequencyType=freqType, frequency=freq) if 'error' in ans: self._log.error( __name__ + '::reqHistoricalDataWrapper: EXIT, cannot handle contract=%s endTime=%s goBack=%s barSize=%s' % (print_IBCpp_contract(contract), str_endTime, goBack, barSize)) self._log.error( __name__ + '::reqHistoricalDataWrapper: EXIT, cannot handle periodType=%s period=%s frequencyType=%s frequency=%s' % (peType, pe, freqType, freq)) exit() hist = ans['candles'] for row in hist: epoc = int(float(str(row['datetime']))) if epoc > 1e10: epoc /= 1000.0 self.simulateHistoricalData( reqId, str(epoc), float(row['open']), float(row['high']), float(row['low']), float(row['close']), int(float(row['volume'])), 1, # barCount 0.0, # WAP 1) # hasGap self.simulateHistoricalData(reqId, 'finished', 0.0, 0.0, 0.0, 0.0, 1, 1, 0.0, 1)
def reqMktDataWrapper(self, reqId, contract, genericTickList, snapshot): """ Just ignore reqMktData because the real time prices will be simulated :param reqId: :param contract: :param genericTickList: :param snapshot: :return: """ self._log.debug(__name__ + '::reqMktDataWrapper: reqId=%s contract=%s genericTickList=%s snapshot=%s' % (str(reqId, ), print_IBCpp_contract(contract), str(genericTickList), str(snapshot))) self._activeRequests.set_a_request_of_a_reqId_to_a_status(reqId, ReqAttr.Status.COMPLETED) self.processMessagesWrapper(self._timeGenerator.get_current_time())
def reqHistoricalDataWrapper(self, reqId, contract, endTime, goBack, barSize, whatToShow, useRTH, formatDate): if barSize not in ['1 day', '5 mins', '10 mins']: self._log.error( __name__ + '::reqHistoricalDataWrapper: EXIT, Robinhood does not support barSize=%s' % (barSize, )) interval = BarSizeConverter().fromIBtoRB(barSize) span = GoBackConverter().fromIBtoRB(goBack) self._log.notset( __name__ + '::reqHistoricalDataWrapper: contract=%s interval=%s span=%s' % (print_IBCpp_contract(contract), interval, span)) ans = self._robinhoodClient.get_historical_quotes( contract.symbol, interval, span) hist = None try: hist = ans['results'][0]['historicals'] except KeyError: self._log.error(__name__ + '::reqHistoricalDataWrapper: EXIT, ans=%s' % (ans, )) exit() for row in hist: t = dt.datetime.strptime(row['begins_at'], "%Y-%m-%dT%H:%M:%SZ") epoc = dt_to_epoch(t) if formatDate == 2: date = epoc else: date = epoch_to_dt(float(epoc)) date = date.strftime( "%Y%m%d %H:%M:%S" ) # Must be UTC because requested time was cast to UTC self.simulateHistoricalData( reqId, str(date), float(row['open_price']), float(row['high_price']), float(row['low_price']), float(row['close_price']), int(float(row['volume'])), 1, # barCount 0.0, # WAP 1) # hasGap self.simulateHistoricalData(reqId, 'finished', 0.0, 0.0, 0.0, 0.0, 1, 1, 0.0, 1)
def __str__(self): ans = '{reqId=%s;status=%s;reqType=%s;followUp=%s;' % ( self.reqId, self.status, self.reqType, self.followUp) tmp = '{' for key in self.param: if key == 'order': tmp += 'order:%s,' % (print_IBCpp_order(self.param[key])) elif key == 'contract': tmp += 'contract:%s,' % (print_IBCpp_contract(self.param[key])) elif key == 'security': tmp += 'security:%s,' % (self.param[key].full_print()) else: tmp += '%s:%s,' % (key, self.param[key]) tmp += '}' ans += 'param=%s}' % (tmp, ) return ans
def bondContractDetails(self, reqId, contractDetails): """ IB callback function to receive str_security info """ self._log.info(__name__ + '::bondContractDetails:' + str(reqId)) aRequest = self._activeRequests.get_by_reqId_otherwise_exit(reqId) newRow = pd.DataFrame( { 'right': contractDetails.summary.right, 'strike': float(contractDetails.summary.strike), # 'expiry':dt.datetime.strptime(contractDetails.summary.expiry, '%Y%m%d'), 'expiry': contractDetails.summary.expiry, 'contractName': print_IBCpp_contract(contractDetails.summary), 'str_security': contractDetails.summary, 'multiplier': contractDetails.summary.multiplier, 'contractDetails': contractDetails }, index=[len(aRequest.returnedResult)]) aRequest.returnedResult = aRequest.returnedResult.append(newRow)
def position(self, accountCode, contract, amount, cost_basis): """ call back function of IB C++ API which updates the position of a security of a account """ self._log.debug( __name__ + '::position: accountCode=%s contract=%s amount=%s cost_basis=%s' % (accountCode, print_IBCpp_contract(contract), amount, cost_basis)) # Conclusion: called-back position contract may or may not have exchange info, # never see primaryExchange. # STK has exchange, CASH does not, FUT does not # if contract.exchange != '': # self._log.error(__name__ + '::position: EXIT, contract has exchange=%s' % (print_contract(contract),)) # exit() security = stripe_exchange_primaryExchange_from_contract(contract) self._singleTrader.set_position( self.brokerName, accountCode, PositionRecord(security.full_print(), amount, cost_basis, contract))
def placeOrderWrapper(self, contract, ibcppOrder, ibpyRequest): self._check_connectivity_reconn_if_needed() orderId = ibcppOrder.orderId if isinstance(orderId, int) and orderId != 0: self._log.debug(__name__ + '::placeOrderWrapper: orderId=%s' % (ibcppOrder.orderId, )) int_orderId = ibcppOrder.orderId else: self.request_data( ReqIds() ) # For IBinsync, nextId needs updates every time when self._nextId is needed. int_orderId = self._nextId.useOne() ibcppOrder.orderId = int_orderId self._log.debug(__name__ + '::placeOrderWrapper: reqIds and then orderId=%s' % (int_orderId, )) ibpyOrderId = self._idConverter.fromIBtoBroker(int_orderId) # Set for ending flat. # Otherwise, the following line in broker_client_factory::CallBacks::orderStatus will not be able to find a reqId # reqId = self.activeRequests.find_reqId_by_int_orderId(int_orderId) ibpyRequest.param['int_orderId'] = int_orderId # Register ibpyOrderId in SingleTrader so that it can search accountCode by incoming int_orderId self._singleTrader.set_from_send_req_to_server(self.name, ibcppOrder.account, ibpyOrderId) # IBCpp function self.placeOrder(int_orderId, contract, ibcppOrder) self._log.debug( __name__ + '::placeOrderWrapper: int_orderId=%s contract=%s ibcppOrder=%s' % (int_orderId, print_IBCpp_contract(contract), print_IBCpp_order(ibcppOrder))) # Only IB order may not follow up. # If do not follow up on order, just set ending flag and assign ibpyOrderId here. if not ibpyRequest.followUp: ibpyRequest.returnedResult = ibpyOrderId ibpyRequest.status = ReqAttr.Status.COMPLETED
def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection, legsStr): self._log.debug( __name__ + '::scannerData: reqId=%s rank=%s contractDetails.summary%s distance=%s benchmark=%s project=%s legsStr=%s' % (reqId, rank, print_IBCpp_contract(contractDetails.summary), distance, benchmark, projection, legsStr)) aRequest = self._activeRequests.get_by_reqId_otherwise_exit(reqId) security = from_contract_to_security(contractDetails.summary) newRow = pd.DataFrame( { 'rank': rank, # 'contractDetails': contractDetails, 'security': security, # 'distance': distance, # 'benchmark': benchmark, # 'projection': projection, # 'legsStr': legsStr }, index=[len(aRequest.returnedResult)]) aRequest.returnedResult = aRequest.returnedResult.append(newRow)
def openOrder(self, int_ibOrderId, contract, order, orderState): """ call back function of IB C++ API which updates the open orders """ self._log.debug( __name__ + '::openOrder: ibOrderId=%i contract=%s order=%s orderState=%s' % (int_ibOrderId, print_IBCpp_contract(contract), print_IBCpp_order(order), print_IBCpp_orderState(orderState))) # For IB only: # The int_ibOrderId is 0 or -1 if the order is not placed by IB clients # Then, create a string value for it using permId # IBridgePy will not touch these orders and the created value is not registered in idConverter if int_ibOrderId in [-1]: str_ibpyOrderId = 'permIDatIB%s' % (order.permId, ) else: str_ibpyOrderId = self._idConverter.fromIBtoBroker(int_ibOrderId) self._singleTrader.set_openOrder( self.brokerName, order.account, OpenOrderRecord(str_ibpyOrderId, contract, order, orderState))
def contractDetails(self, reqId, contractDetails): """ IB callback function to receive str_security info """ self._log.notset(__name__ + '::contractDetails: reqId=%s contractDetails=%s' % (reqId, print_IBCpp_contractDetails(contractDetails))) aRequest = self._activeRequests.get_by_reqId_otherwise_exit(reqId) newRow = pd.DataFrame( { 'right': contractDetails.summary.right, 'strike': float(contractDetails.summary.strike), 'expiry': contractDetails.summary.expiry, 'contractName': print_IBCpp_contract(contractDetails.summary), 'security': from_contract_to_security(contractDetails.summary), 'contract': contractDetails.summary, 'multiplier': contractDetails.summary.multiplier, 'contractDetails': contractDetails }, index=[len(aRequest.returnedResult)]) aRequest.returnedResult = aRequest.returnedResult.append(newRow)
def order_status_monitor(self, ibpyOrderId, target_status, waitingTimeInSeconds=30): self._log.debug( __name__ + '::order_status_monitor: ibpyOrderId=%s target_status=%s' % (ibpyOrderId, target_status)) timer = dt.datetime.now() while True: time.sleep( 1 ) # Do not send too much request to TD max=120 requests/minute self._submit_request_after_checking_cache(ReqOneOrder(ibpyOrderId)) if (dt.datetime.now() - timer).total_seconds() <= waitingTimeInSeconds: tmp_status = self.get_order(ibpyOrderId).status if isinstance(target_status, str): if tmp_status == target_status: return elif isinstance(target_status, list): if tmp_status in target_status: return else: self._log.error( __name__ + '::order_status_monitor: EXIT, waiting time is too long, >%i' % (waitingTimeInSeconds, )) order = self.get_order(ibpyOrderId) contract = order.contract self._log.error( __name__ + '::order_status_monitor: EXIT, ibpyOrderId=%i status=%s contract=%s' % (ibpyOrderId, order.status, print_IBCpp_contract(contract))) raise OrderStatusNotConfirmed()
def error(self, errorId, errorCode, errorMessage): """ errorId can be either reqId or orderId or -1 only print real error messages, which is errorId < 2000 in IB's error message system, or program is in debug mode """ self._log.debug(__name__ + '::error: errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) if errorCode in [501, 326]: # Do nothing # errorCode=501 errorMessage=Already connected. # errorCode=326 errorMessage=Unable to connect as the client id is already in use. Retry with a unique client id. return if errorCode in [502]: # errorCode:502 errorId:-1 errorMessage=Couldn't connect to TWS. Confirm that "Enable ActiveX and Socket Clients" is enabled on the TWS "Configure->API" menu. self._log.error('Hint 1: Enable API.') self._log.error('Hint 2: Maybe the port number does not match.') raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage)) if errorCode in [509, 10141]: # errorCode:509 errorId:-1 errorMessage=Exception caught while reading socket - Broken pipe # errorCode=509 errorMessage=Exception caught while reading socket - Connection reset by peer -- Reason: Invoke connectWrapper() when there is connection already. # errorCode=509 errorMessage=Exception caught while reading socket - Connection reset by peer -- Reason 1: IB Gateway was turned off suddenly # errorCode:10141 errorId:-1 errorMessage=Paper trading disclaimer must first be accepted for API connection. self._log.error( __name__ + '::error: errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._log.error( 'Hint: Restart Trader Workstation(TWS) or IB Gateway and try it again.' ) self._log.error( 'Hint: IBridgePy Premium members can turn on the auto-connection feature by settings.py --> PROJECT --> autoReconnectPremium --> True' ) self._log.error( 'Hint: Please refer to YouTube tutorial https://youtu.be/pson8T5ZaRw' ) raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage)) elif errorCode in [1100, 1101, 1102]: if errorCode == 1100: # Your TWS/IB Gateway has been disconnected from IB servers. This can occur because of an # internet connectivity issue, a nightly reset of the IB servers, or a competing session. self._connectionGatewayToServer = False elif errorCode in [1101, 1102]: # 1101 # The TWS/IB Gateway has successfully reconnected to IB's servers. Your market data requests have # been lost and need to be re-submitted. # 1102 # The TWS/IB Gateway has successfully reconnected to IB's servers. Your market data requests have been # recovered and there is no need for you to re-submit them. self._connectionGatewayToServer = True elif errorCode in [ 2100, 2103, 2104, 2105, 2106, 2107, 2108, 2110, 2119, 2137, 2157, 2158 ]: if errorCode in [2103, 2157]: # errorCode=2157 errorMessage=Sec-def data farm connection is broken:secdefnj self._connectionMarketDataFarm = False elif errorCode in [2104, 2108, 2158]: # errorCode=2104 errorMessage=Market data farm connection is OK:usfarm.nj # errorCode=2158 errorMessage=Sec-def data farm connection is OK:secdefnj # errorCode=2108 errorMessage=Market data farm connection is inactive but should be available upon demand.usfarm.nj self._connectionMarketDataFarm = True elif errorCode == 2105: # 2105 = HMDS dataFromServer farm connection is broken:ushmds self._connectionHistDataFarm = False elif errorCode in [2106, 2107]: # errorCode=2106 errorMessage=HMDS data farm connection is OK:ushmds self._connectionHistDataFarm = True elif errorCode == 2110: # Connectivity between TWS and server is broken. It will be restored automatically. # noinspection PyAttributeOutsideInit self._connectionGatewayToServer = False # noinspection PyAttributeOutsideInit self._connectionHistDataFarm = False # noinspection PyAttributeOutsideInit self._connectionMarketDataFarm = False elif errorCode == 2137: # errorCode=2137 errorMessage=The closing order quantity is greater than your current position. self._log.error( __name__ + '::error: errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._log.error( 'errorCode=2137 is not a critical error in IBridgePy anymore. To hide this error, TWS -> Global Configuration -> Messages -> Cross side warning (untick & save) -> Uncheck it.' ) elif errorCode == 2100: # errorCode=2100 errorMessage=API client has been unsubscribed from account data. self._activeRequests.set_all_requests_of_a_reqType_to_a_status( 'reqAccountUpdates', ReqAttr.Status.COMPLETED) return elif errorCode in [202, 10147, 10148]: # 202: cancel order is confirmed # 10148, error message: OrderId 230 that needs to be cancelled can not be cancelled, state: Cancelled. # 10147, error message: OrderId 2 that needs to be cancelled is not found. # errorId is OrderId in this case self._activeRequests.set_a_request_of_an_orderId_to_a_status( errorId, ReqAttr.Status.COMPLETED) elif errorCode in [201, 399, 2113, 2148, 2109]: # No action, just show error message # 201 = order rejected - Reason: No such order # 201 errorMessage=Order rejected - reason:CASH AVAILABLE: 28328.69; CASH NEEDED FOR THIS ORDER AND OTHER PENDING ORDERS: 56477.64 # 201 Order rejected - # 2109: Order Event Warning: Attribute 'Outside Regular Trading Hours' is ignored based on the order type and destination. PlaceOrder is now being processed. self._log.error(__name__ + ':errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) elif errorCode == 162: if 'API scanner subscription cancelled' in errorMessage: self._activeRequests.set_a_request_of_a_reqId_to_a_status( errorId, ReqAttr.Status.COMPLETED) return # not a true error else: self._log.error(__name__ + ':errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._print_version() raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage)) elif 110 <= errorCode <= 449: # 404: errorMessage=Order held while securities are located = IB doesn't have available shares to borrow and doesn't let short order. if errorCode == 165: # 165 = Historical Market Data Service query message:10 out of 178 items retrieve return self._log.error(__name__ + '::error:errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) if errorCode == 200: # 200 = No security definition has been found for the request. # A special case. # The connectivity is lost during reqHistoricalData. # If get 200 at this moment, it is a fake error. if (self._connectionGatewayToServer is False) or (self._connectionHistDataFarm is False): self._log.error( __name__ + '::error: <No security definition has been found for the request> might be a false statement because connectivity to IB server is lost.' ) return if errorId in self._allRequests.keys(): reqId = errorId if 'contract' in self._allRequests[errorId].param: self._log.error( print_IBCpp_contract( self._allRequests[errorId].param['contract'])) else: self._log.error( print_IBCpp_contract( self._allRequests[errorId].param['security'])) else: reqId = self._activeRequests.find_reqId_by_int_orderId( errorId) self._log.error( print_IBCpp_contract( self._allRequests[reqId].param['contract'])) # if the reqId is an active request, raise error. # Otherwise, ignore error because IB may throw the old error a few times. if self._activeRequests.has_reqId(reqId): raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage)) else: return # elif errorCode == 165: # Historical Market Data Service query message:Trading TWS session is connected from a different IP address self._print_version() raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage)) elif 994 <= errorCode <= 999 or 972 <= errorCode <= 978: self._log.error(__name__ + ':errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._log.error( 'Hint: Please refer to YouTube tutorial https://youtu.be/pson8T5ZaRw' ) self._print_version_and_exit() elif 979 <= errorCode <= 988: self._log.error(__name__ + ':errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._log.error( 'IBridgePy community version supports backtest on US equities only. Please visit https://ibridgepy.com/features-of-ibridgepy/ and consider IBridgePy Backtester version.' ) self._print_version_and_exit() else: # errorCode=504 errorMessage=Not connected self._log.error( __name__ + '::error: errorId=%s errorCode=%s errorMessage=%s' % (errorId, errorCode, errorMessage)) self._print_version() raise CustomError( errorCode, 'errorId:%s errorMessage=%s' % (errorId, errorMessage))
def _send_req_to_server(self, activeRequests): """ pandas dataFrame: reqData All requests are defined in broker_client_factory::BrokerClientDefs """ self._log.debug(__name__ + '::_send_req_to_server: brokerClient=%s' % (self, )) for reqId in activeRequests.get_request_ids(): aRequest = activeRequests.get_by_reqId_otherwise_exit(reqId) self._allRequests[ reqId] = aRequest # move to allRequest which stores all requests aRequest.status = ReqAttr.Status.SUBMITTED reqType = aRequest.reqType param = aRequest.param self._log.debug('%s' % (aRequest, )) if reqType == 'reqPositions': self.reqPositionsWrapper() elif reqType == 'reqConnect': ans = self.isConnectedWrapper() if ans: aRequest.status = ReqAttr.Status.COMPLETED else: ans = self.connectWrapper() if ans: aRequest.status = ReqAttr.Status.COMPLETED elif reqType == 'reqCurrentTime': self.reqCurrentTimeWrapper() elif reqType == 'reqAllOpenOrders': self.reqAllOpenOrdersWrapper() elif reqType == 'reqOneOrder': ibpyOrderId = param['orderId'] self.reqOneOrderWrapper(ibpyOrderId) elif reqType == 'reqAccountUpdates': accountCode = param['accountCode'] subscribe = param['subscribe'] self.reqAccountUpdatesWrapper( subscribe, accountCode) # Request to update account info elif reqType == 'reqAccountSummary': group = param['group'] tag = param['tag'] self.reqAccountSummaryWrapper(reqId, group, tag) elif reqType == 'reqIds': self.reqIdsWrapper() elif reqType == 'reqHeartBeats': self.reqHeartBeatsWrapper() elif reqType == 'reqHistoricalData': security = param['security'] endTime = param['endTime'] goBack = param['goBack'] barSize = param['barSize'] whatToShow = param['whatToShow'] useRTH = param['useRTH'] formatDate = 2 # param['formatDate'] Send epoch to IB server all the time. It is easier to handle self.reqHistoricalDataWrapper( reqId, from_security_to_contract(security), endTime, goBack, barSize, whatToShow, useRTH, formatDate) elif reqType == 'reqMktData': security = param['security'] genericTickList = param['genericTickList'] snapshot = param['snapshot'] # put security and reqID in dictionary for fast access # it is keyed by both security and reqId self._realTimePriceRequestedList.addReqIdAndStrSecurity( reqId, security.full_print()) self.reqMktDataWrapper( reqId, from_security_to_contract(security), genericTickList, snapshot) # Send market data request to IB server elif reqType == 'cancelMktData': security = param['security'] reqId = self._realTimePriceRequestedList.findByStrSecurity( security.full_print()) self.cancelMktDataWrapper(reqId) self._realTimePriceRequestedList.deleteReqIdAndStrSecurity( reqId, security.full_print()) elif reqType == 'reqRealTimeBars': security = param['security'] barSize = param['barSize'] whatToShow = param['whatToShow'] useRTH = param['useRTH'] self._realTimePriceRequestedList.addReqIdAndStrSecurity( reqId, security.full_print()) self.reqRealTimeBarsWrapper( reqId, from_security_to_contract(security), barSize, whatToShow, useRTH) # Send market dataFromServer request to IB server elif reqType == 'placeOrder': """ Ending label is IBCpp::callBacks::orderStatus """ contract = param['contract'] order = param['order'] self.placeOrderWrapper(contract, order, aRequest) # When the order is placed, IB will callback orderStatus # In broker_client_factory::CallBack::orderStatus, IBridgePy, set ending flag for this request and assign ibpyOrderId # self.activeRequests.get_by_reqId_otherwise_exit(reqId).returnedResult = str_ibpyOrderId # self.activeRequests.get_by_reqId_otherwise_exit(reqId).status = ReqAttr.Status.COMPLETED elif reqType == 'modifyOrder': """ Ending label is IBCpp::callBacks::orderStatus """ ibpyOrderId = param['ibpyOrderId'] contract = param['contract'] order = param['order'] int_orderId = order.orderId self._idConverter.verifyRelationship(int_orderId, ibpyOrderId) self._log.info( 'ModifyOrder ibpyOrderId=%s security=%s order=%s' % (ibpyOrderId, print_IBCpp_contract(contract), print_IBCpp_order(order))) self.modifyOrderWrapper(contract, order, aRequest) elif reqType == 'reqContractDetails': security = param['security'] self.reqContractDetailsWrapper( reqId, from_security_to_contract(security)) elif reqType == 'calculateImpliedVolatility': security = param['security'] optionPrice = float(param['optionPrice']) underPrice = float(param['underPrice']) # put security and reqID in dictionary for fast access # it is keyed by both security and reqId self._realTimePriceRequestedList.addReqIdAndStrSecurity( reqId, security.full_print()) self.calculateImpliedVolatilityWrapper( reqId, from_security_to_contract(security), optionPrice, underPrice) elif reqType == 'reqScannerSubscription': subscription = param['subscription'] self.reqScannerSubscriptionWrapper(reqId, subscription) # Need to upgrade IBCpp # tagValueList = self.end_check_list.loc[idx, 'reqData'].param['tagValueList'] # self.reqScannerSubscription(reqId, subscription, tagValueList) # self._log.debug(__name__ + '::_send_req_to_server:reqScannerSubscription:' # + ' subscription=' + subscription.full_print() + ' tagValueList=' + str(tagValueList)) elif reqType == 'cancelScannerSubscription': tickerId = param['tickerId'] self.cancelScannerSubscriptionWrapper(tickerId) elif reqType == 'cancelOrder': """ Ending label is IBCpp::callBacks::error: errorCode = 10148 Do not use orderStatus callback to follow up on cancelOrder request because the status can be either Canceled Or PendingCancel """ ibpyOrderId = param['ibpyOrderId'] int_orderId = self._idConverter.fromBrokerToIB(ibpyOrderId) param['int_orderId'] = int_orderId self.cancelOrderWrapper(ibpyOrderId) elif reqType == 'reqScannerParameters': self.reqScannerParametersWrapper() else: self._log.error( __name__ + '::_send_req_to_server: EXIT, cannot handle reqType=%s' % (reqType, )) self.end()