def getMktData(): callback = IBWrapper() callback.initiate_variables() tws_conn = EClientSocket(callback) tws_conn.eConnect("", 7497, 110) # market data connection) # tws_conn = Connection.create(port=7497, clientId=110) # now there is a problem: I have to change clientId every time to avoid usfarm issue # tws_conn.connect() # tws.connect() # Assign the handling function defined above # tws_conn.register(error_handler, 'Error') # tws_conn.registerAll(reply_handler) create = contract() contract_info = create.create_contract('EUR', 'CASH', 'IDEALPRO', 'USD') tickedId = 1002 sleep(10) tws_conn.reqMktData(tickedId, contract_info, "", False) sleep(10) # tws_conn.disconnect() tick_data = pd.DataFrame( callback.tick_Price, columns=['ticketId', 'field', 'price', 'canAutoExecute']) # print(tick_data) return tick_data
class IbTextApp(object): """IB链接器""" def __init__(self, host, port, client_id): """ TODO: 不应该维护一个巨大的类,可以考虑使用混合模式来管理这个类 :host:地址 :port:接口 :client_id:这个可以随便写一个,int :req_id:请求id """ self.host = host self.port = port self.client_id = client_id self.warpper = IbTestWrapper() self.connection = EClientSocket(self.warpper) self.req_id = 0 def econnect(self): """docstring for connect""" self.connection.eConnect(self.host, self.port, self.client_id) def subscribe(self): """ :return: 返回req_id和合约代码信息返回,好做好映射, """ contract = Contract() contract.m_symbol = "EUR" # 底层资产的代码 PS:CL contract.m_currency = "USD" # 币种 PS:USD contract.m_secType = "CASH" # 证券类型 PS:FUT contract.m_exchange = "IDEALPRO" # 交易所NYMEX contract.m_localSymbol = "" #合约代码 self.connection.reqMktData(self.req_id + 1, contract, '', False) def query_account(self, acc_code): """ docstring for query_account 查询账户信息 似乎是主动推送 """ self.connection.reqAccountUpdates(1, acc_code) def req_history_data(self): contract = Contract() contract.m_symbol = 'CL' contract.m_secType = 'FUT' contract.m_exchange = 'NYMEX' contract.m_localSymbol = "CLX6" endtime = '20161014 15:30:30' self.connection.reqHistoricalData(tickerId=1, contract=contract, endDateTime=endtime, durationStr='1 D', barSizeSetting='1 min', whatToShow='TRADES', useRTH=0, formatDate=1)
class IBProvider(BaseProvider): def __init__(self): basicConfig() # These two variables are initialized in Connect method self._connection = None self._wrapper = None self._request_id = 0 def connect(self): self._wrapper = ReferenceWrapper() self._connection = EClientSocket(self._wrapper) self._connection.eConnect(IB_HOST, IB_PORT, IB_PROVIDER_CLIENT_ID) def disconnect(self): if self._connection.isConnected(): self._connection.eDisconnect() def _make_contract(self, symbol): contract = Contract() contract.m_symbol = symbol contract.m_secType = 'STK' contract.m_exchange = 'SMART' contract.m_primaryExch = 'SMART' contract.m_currency = 'USD' contract.m_localSymbol = symbol return contract def _get_next_request_id(self): self._request_id += 1 return self._request_id def get_market_depth_L1(self, symbol): contract = self._make_contract(symbol) request_id = self._get_next_request_id() self._connection.reqMktData(request_id, contract, '', True) while not self._wrapper.isExecutionRequestFinished(request_id): err = self._wrapper.getError(request_id) if err is not None: raise Exception(err) very_short_sleep() return self._wrapper.getMarketDepthL1(request_id)
class IbGateway(VtGateway): """IB接口""" #---------------------------------------------------------------------- def __init__(self, eventEngine, gatewayName='IB'): """Constructor""" super(IbGateway, self).__init__(eventEngine, gatewayName) self.host = EMPTY_STRING # 连接地址 self.port = EMPTY_INT # 连接端口 self.clientId = EMPTY_INT # 用户编号 self.tickerId = 0 # 订阅行情时的代码编号 self.tickDict = {} # tick快照字典,key为tickerId,value为VtTickData对象 self.orderId = 0 # 订单编号 self.orderDict = {} # 报单字典,key为orderId,value为VtOrderData对象 self.accountDict = {} # 账户字典 self.connected = False # 连接状态 self.wrapper = IbWrapper(self) # 回调接口 self.connection = EClientSocket(self.wrapper) # 主动接口 #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 fileName = self.gatewayName + '_connect.json' fileName = os.getcwd() + '/ibGateway/' + fileName try: f = file(fileName) except IOError: log = VtLogData() log.gatewayName = self.gatewayName log.logContent = u'读取连接配置出错,请检查' self.onLog(log) return # 解析json文件 setting = json.load(f) try: self.host = str(setting['host']) self.port = int(setting['port']) self.clientId = int(setting['clientId']) except KeyError: log = VtLogData() log.gatewayName = self.gatewayName log.logContent = u'连接配置缺少字段,请检查' self.onLog(log) return # 发起连接 self.connection.eConnect(self.host, self.port, self.clientId) # 查询服务器时间 self.connection.reqCurrentTime() # 请求账户数据主推更新 self.connection.reqAccountUpdates(True, '') #---------------------------------------------------------------------- def subscribe(self, subscribeReq): """订阅行情""" # 订阅行情 self.tickerId += 1 contract = Contract() contract.m_symbol = str(subscribeReq.symbol) contract.m_exchange = exchangeMap.get(subscribeReq.exchange, '') contract.m_secType = productClassMap.get(subscribeReq.productClass, '') contract.m_currency = currencyMap.get(subscribeReq.currency, '') contract.m_expiry = subscribeReq.expiry contract.m_strike = subscribeReq.strikePrice contract.m_right = optionTypeMap.get(subscribeReq.optionType, '') self.connection.reqMktData(self.tickerId, contract, '', False) # 创建Tick对象并保存到字典中 tick = VtTickData() tick.symbol = subscribeReq.symbol tick.exchange = subscribeReq.exchange tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) tick.gatewayName = self.gatewayName self.tickDict[self.tickerId] = tick #---------------------------------------------------------------------- def sendOrder(self, orderReq): """发单""" # 增加报单号1,最后再次进行查询 # 这里双重设计的目的是为了防止某些情况下,连续发单时,nextOrderId的回调推送速度慢导致没有更新 self.orderId += 1 # 创建合约对象 contract = Contract() contract.m_symbol = str(orderReq.symbol) contract.m_exchange = exchangeMap.get(orderReq.exchange, '') contract.m_secType = productClassMap.get(orderReq.productClass, '') contract.m_currency = currencyMap.get(orderReq.currency, '') contract.m_expiry = orderReq.expiry contract.m_strike = orderReq.strikePrice contract.m_right = optionTypeMap.get(orderReq.optionType, '') # 创建委托对象 order = Order() order.m_orderId = self.orderId order.m_clientId = self.clientId order.m_action = directionMap.get(orderReq.direction, '') order.m_lmtPrice = orderReq.price order.m_totalQuantity = orderReq.volume order.m_orderType = priceTypeMap.get(orderReq.priceType, '') # 发送委托 self.connection.placeOrder(self.orderId, contract, order) # 查询下一个有效编号 self.connection.reqIds(1) #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): """撤单""" self.connection.cancelOrder(cancelOrderReq.orderID) #---------------------------------------------------------------------- def qryAccount(self): """查询账户资金""" log = VtLogData() log.gatewayName = self.gatewayName log.logContent = u'IB接口账户信息提供主推更新,无需查询' self.onLog(log) #---------------------------------------------------------------------- def qryPosition(self): """查询持仓""" log = VtLogData() log.gatewayName = self.gatewayName log.logContent = u'IB接口持仓信息提供主推更新,无需查询' self.onLog(log) #---------------------------------------------------------------------- def close(self): """关闭""" self.connection.eDisconnect()
class IB: """ Interface to Interactive Brokers. """ # Definition of the tick data "field" value. tick_type = { 0: "BID_SIZE", 1: "BID_PRICE", 2: "ASK_PRICE", 3: "ASK_SIZE", 4: "LAST_PRICE", 5: "LAST_SIZE", 6: "HIGH", 7: "LOW", 8: "VOLUME", 9: "CLOSE_PRICE", 10: "BID_OPTION_COMPUTATION", 11: "ASK_OPTION_COMPUTATION", 12: "LAST_OPTION_COMPUTATION", 13: "MODEL_OPTION_COMPUTATION", 14: "OPEN_PRICE", 15: "LOW_13_WEEK", 16: "HIGH_13_WEEK", 17: "LOW_26_WEEK", 18: "HIGH_26_WEEK", 19: "LOW_52_WEEK", 20: "HIGH_52_WEEK", 21: "AVG_VOLUME", 22: "OPEN_INTEREST", 23: "OPTION_HISTORICAL_VOL", 24: "OPTION_IMPLIED_VOL", 27: "OPTION_CALL_OPEN_INTEREST", 28: "OPTION_PUT_OPEN_INTEREST", 29: "OPTION_CALL_VOLUME" } def __init__(self, account_name: str, host: str, port: int, client_id: int): self.account_name = account_name # Instantiate IBWrapper callback. self.callback = IBWrapper() # Instantiate EClientSocket and return data to callback self.tws = EClientSocket(self.callback) # Connect to TWS. self.tws.eConnect(host, port, client_id) # Instantiate contract class. self.create = contract() self.callback.initiate_variables() def request_account_data(self): """ Requests the relevant account data. http://interactivebrokers.github.io/tws-api/account_updates.html http://interactivebrokers.github.io/tws-api/classIBApi_1_1EClient.html#aea1b0d9b6b85a4e0b18caf13a51f837f """ self.callback.update_AccountValue = [] self.callback.update_Portfolio = [] self.callback.update_AccountTime = None print(f'Requesting account data.') self.tws.reqAccountUpdates(subscribe=True, acctCode=self.account_name) # Retrieve the data from the callback when it arrives. while True: # Don't check for portfolio, as it may be empty to begin with. if all([ len(self.callback.update_AccountValue) > 0, self.callback.update_AccountTime ]): account_data = pd.DataFrame( self.callback.update_AccountValue, columns=['key', 'value', 'currency', 'accountName']) portfolio = pd.DataFrame( self.callback.update_Portfolio, columns=[ 'Contract ID', 'Currency', 'Expiry', 'Include Expired', 'Local Symbol', 'Multiplier', 'Primary Exchange', 'Right', 'Security Type', 'Strike', 'Symbol', 'Trading Class', 'Position', 'Market Price', 'Market Value', 'Average Cost', 'Unrealised PnL', 'Realised PnL', 'Account Name' ]) account_time = self.callback.update_AccountTime break print(f'Waiting for account data. ') time.sleep(2) # Unsubscribe. self.tws.reqAccountUpdates(subscribe=False, acctCode=self.account_name) self.callback.update_AccountValue = [] self.callback.update_Portfolio = [] self.callback.update_AccountTime = None return account_data, portfolio, account_time def request_portfolio(self): """ Requests the portfolio. http://interactivebrokers.github.io/tws-api/account_updates.html http://interactivebrokers.github.io/tws-api/interfaceIBApi_1_1EWrapper.html#a790ccbe25033df73996f36a79ce2ce5a """ self.callback.update_AccountValue = [] print(f'Requesting portfolio data.') self.tws.reqAccountUpdates(subscribe=True, acctCode=self.account_name) # Retrieve the data from the callback when it arrives. while True: if len(self.callback.update_AccountValue) > 0: data = pd.DataFrame( self.callback.update_AccountValue, columns=['key', 'value', 'currency', 'accountName']) break print(f'Waiting for portfolio data.') time.sleep(2) # Unsubscribe. self.tws.reqAccountUpdates(subscribe=False, acctCode=self.account_name) self.callback.update_AccountValue = [] return data def request_tick_data(self, ticker, max_num_tries=10): """ Requests current tick data for the given ticker. """ # Create the contract to request tick data for. contract_info = self.create.create_contract(ticker, 'STK', 'SMART', 'USD') tickerId = 1004 self.callback.tick_Price = [] # Request the data. # Take a snapshot, so we don't continue to get data. self.tws.reqMktData(tickerId=tickerId, contract=contract_info, genericTickList="", snapshot=True) # Retrieve the data from the callback when it arrives. received_data = False num_tries = 0 while num_tries < max_num_tries: if len(self.callback.tick_Price) > 0: data = pd.DataFrame( self.callback.tick_Price, columns=['tickerId', 'field', 'price', 'canAutoExecute']) data["Type"] = data["field"].map(IB.tick_type) print(data) if 'LAST_PRICE' in data['Type'].values: # We have the information we need. break if 'CLOSE_PRICE' in data['Type'].values: if not received_data: # We have the close price. Wait a little longer to see if we get the last price. received_data = True time.sleep(2) continue break print( f'Waiting for tick data for ticker {ticker}, with ID {tickerId}, after try {num_tries} of {max_num_tries}.' ) num_tries += 1 time.sleep(2) self.tws.cancelMktData(tickerId) # Attach the type of each row, based on the field. # data["Type"] = data["field"].map(IB.tick_type) # Reset the tick_Price data. self.callback.tick_Price = [] if not received_data: data = None return data def request_tick_data_repeat(self, ticker, max_num_tries): """ Wrapper around request_tick_data, that makes a given number of attempts. On each attempt, a new connection is established. """ num_tries = 0 while num_tries < max_num_tries: data = self.request_tick_data(ticker) if data is not None: break num_tries += 1 return data def request_historical_data(self, ticker: str, num_days: int, last_date: datetime = None): """ https://interactivebrokers.github.io/tws-api/historical_bars.html http://interactivebrokers.github.io/tws-api/classIBApi_1_1EClient.html#aad87a15294377608e59aec1d87420594 """ # Create the contract to request tick data for. contract_info = self.create.create_contract(ticker, 'STK', 'SMART', 'USD') tickerId = 1004 self.callback.historical_Data = [] # Example: To get 3 days ending on 1/7/2013, use the last second of 1/7/2013 as the endDateTime, # and 3D as the durationStr. data_endtime = '' if last_date is None else last_date.strftime( "%Y%m%d %H:%M:%S") # Request the data. # reqHistoricalData(self, tickerId, contract, endDateTime, durationStr, barSizeSetting, whatToShow, useRTH, formatDate): self.tws.reqHistoricalData( tickerId=tickerId, contract=contract_info, endDateTime= data_endtime, # The request's end date and time (the empty string indicates current present moment). durationStr=f"{num_days} D", barSizeSetting="1 day", whatToShow= "TRADES", # Hopefully this will be a bar summarising the trades... useRTH= 1, # "Regular trading hours". For some reason, 1 chops of the most recent day. formatDate= 1, # dates applying to bars returned in the format: yyyymmdd{space}{space}hh:mm:dd #keepUpToDate=False ) # Retrieve the data from the callback when it arrives. while True: # I think the last row has "finished" in the "date" column, to indicate all data is retrieved. if len( self.callback.historical_Data ) > 0 and 'finished' in self.callback.historical_Data[-1][1]: print(self.callback.historical_Data) data = pd.DataFrame( self.callback. historical_Data[:-1], # Removed "finished" row. columns=[ "reqId", "date", "open", "high", "low", "close", "volume", "count", "WAP", "hasGaps" ]) break print(f'Waiting for historical data with ID {tickerId}.') time.sleep(2) # Reset the historical_Data data. self.callback.historical_Data = [] data['date'] = pd.to_datetime(data['date']) data.set_index('date') # print(data.to_string()) return data[[ 'date', 'open', 'high', 'low', 'close', 'volume', 'count' ]] def close(self): self.tws.eDisconnect()
class Wrapper(EWrapper): orders = None order_ids = [0] parameters = None connection = None def __init__(self, parameters): # Variable initialization #self.orders = orders.OrderBook() #self.price_log = data.PriceLog() #self.parameters = parameters self.connection = EClientSocket(self) self.connection.eConnect('localhost', 7496, 0) # host, port, clientId tick_id = 1 symbol = "SLV" contract = self.makeContract(symbol) self.connection.reqMktData(tick_id, contract, [], False) def makeContract(self, symbol): contract = Contract() contract.m_symbol = symbol contract.m_secType = 'STK' contract.m_exchange = 'SMART' contract.m_primaryExch = 'SMART' contract.m_currency = 'USD' contract.m_localSymbol = symbol return contract def tickPrice(self, tickerId, field, price, canAutoExecute): #showmessage('tickPrice', vars()) # 1 = bid # 2 = ask # 4 = last # 6 = high # 7 = low # 9 = close priceLog = {} side = "" if field == 2: print "a%0.2f " % price elif field == 1: print "b%0.2f " % price if side != "": print side, price def openOrder(self, orderId, contract, order, state): orderId = order.m_orderId symbol = contract.m_symbol qty = order.m_totalQuantity price = order.m_lmtPrice action = order.m_action self.orders.add(orderId, symbol, qty, price, action) order = [orderId, symbol, qty, price, action] print "--> Open order:%s Status:%s Warning:%s" % (order, state.m_status, state.m_warningText) def error(self, id=None, errorCode=None, errorMsg=None): if errorCode == 2104: print "--> %s" % errorMsg else: showmessage('error', vars()) def nextValidId(self, orderId): self.order_ids.append(orderId) def connectionClosed(self): print "--> Connection closed, exiting..." sys.exit(0) def tickSize(self, tickerId, field, size): pass #showmessage('tickSize', vars()) def tickGeneric(self, tickerId, tickType, value): pass #showmessage('tickGeneric', vars()) def tickString(self, tickerId, tickType, value): pass #showmessage('tickString', vars()) def tickEFP(self, tickerId, tickType, basisPoints, formattedBasisPoints, impliedFuture, holdDays, futureExpiry, dividendImpact, dividendsToExpiry): showmessage('tickEFP', vars()) def tickOptionComputation(self, tickerId, field, impliedVolatility, delta): showmessage('tickOptionComputation', vars()) def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeId): pass #showmessage('orderStatus', vars()) def openOrderEnd(self): showmessage('openOrderEnd', vars()) def updateAccountValue(self, key, value, currency, accountName): showmessage('updateAccountValue', vars()) def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName): showmessage('updatePortfolio', vars()) def updateAccountTime(self, timeStamp): showmessage('updateAccountTime', vars()) def accountDownloadEnd(self, accountName): showmessage('accountDownloadEnd', vars()) def contractDetails(self, contractDetails): showmessage('contractDetails', vars()) def bondContractDetails(self, contractDetails): showmessage('bondContractDetails', vars()) def contractDetailsEnd(self, reqId): showmessage('contractDetailsEnd', vars()) def execDetails(self, orderId, contract, execution): showmessage('execDetails', vars()) def execDetailsEnd(self, reqId): showmessage('execDetailsEnd', vars()) def error_0(self, strval): showmessage('error_0', vars()) def error_1(self, strval): showmessage('error_1', vars()) def updateMktDepth(self, tickerId, position, operation, side, price, size): showmessage('updateMktDepth', vars()) def updateMktDepthL2(self, tickerId, position, marketMaker, operation, side, price, size): showmessage('updateMktDepthL2', vars()) def updateNewsBulletin(self, msgId, msgType, message, origExchange): showmessage('updateNewsBulletin', vars()) def managedAccounts(self, accountsList): pass #showmessage('managedAccounts', vars()) def receiveFA(self, faDataType, xml): showmessage('receiveFA', vars()) def historicalData(self, reqId, date, open, high, low, close, volume, count, WAP, hasGaps): showmessage('historicalData', vars()) def scannerParameters(self, xml): showmessage('scannerParameters', vars()) def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection): showmessage('scannerData', vars()) def scannerDataEnd(self, reqId): showmessage('scannerDataEnd', vars()) def realtimeBar(self, reqId, time, open, high, low, close, volume, wap, count): showmessage('realtimeBar', vars()) def currentTime(self, time): showmessage('currentTime', vars()) def fundamentalData(self, reqId, data): showmessage('fundamentalData', vars()) def deltaNeutralValidation(self, reqId, underComp): showmessage('deltaNeutralValidation', vars()) def tickSnapshotEnd(self, reqId): showmessage('tickSnapshotEnd', vars()) def marketDataType(self, reqId, marketDataType): showmessage('marketDataType', vars()) def commissionReport(self, commissionReport): showmessage('commissionReport', vars())
class Wrapper(EWrapper): orders = None order_ids = [0] parameters = None connection = None _storage = None def __init__(self, storage): # Variable initialization #self.orders = orders.OrderBook() #self.price_log = data.PriceLog() #self.parameters = parameters self._storage = storage self.connection = EClientSocket(self) self.connection.eConnect('localhost', 7496, 0) # host, port, clientId tick_id = 1 symbol = "SLV" contract = self.makeContract(symbol) self.connection.reqMktData(tick_id, contract, [], False) def makeContract(self, symbol): contract = Contract() contract.m_symbol = symbol contract.m_secType = 'STK' contract.m_exchange = 'SMART' contract.m_primaryExch = 'SMART' contract.m_currency = 'USD' contract.m_localSymbol = symbol return contract def tickPrice(self, tickerId, field, price, canAutoExecute): # 1 = bid # 2 = ask # 4 = last # 6 = high # 7 = low # 9 = close priceLog = {} side = "" if field == 2: self._storage.log_price("ask", price) elif field == 1: self._storage.log_price("bid", price) if side != "": print(side, price) def openOrder(self, orderId, contract, order, state): orderId = order.m_orderId symbol = contract.m_symbol qty = order.m_totalQuantity price = order.m_lmtPrice action = order.m_action self.orders.add(orderId, symbol, qty, price, action) order = [orderId, symbol, qty, price, action] print("--> Open order:{} Status:{} Warning:{}".format(order, state.m_status, state.m_warningText)) def error(self, id=None, errorCode=None, errorMsg=None): if errorCode == 2104: print("--> {}".format(errorMsg)) elif errorCode == 502: raise Exception(errorMsg) else: showmessage('error', vars()) def nextValidId(self, orderId): self.order_ids.append(orderId) def connectionClosed(self): """ Something broke, connection lost. """ print("--> Connection closed, exiting...") sys.exit(0) def connected(self): """ Returns True of connected to TraderWorkstation, otherwise False. """ return self.connection.m_connected def tickSize(self, tickerId, field, size): pass #showmessage('tickSize', vars()) def tickGeneric(self, tickerId, tickType, value): pass #showmessage('tickGeneric', vars()) def tickString(self, tickerId, tickType, value): pass #showmessage('tickString', vars()) def tickEFP(self, tickerId, tickType, basisPoints, formattedBasisPoints, impliedFuture, holdDays, futureExpiry, dividendImpact, dividendsToExpiry): showmessage('tickEFP', vars()) def tickOptionComputation(self, tickerId, field, impliedVolatility, delta): showmessage('tickOptionComputation', vars()) def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeId): pass #showmessage('orderStatus', vars()) def openOrderEnd(self): showmessage('openOrderEnd', vars()) def updateAccountValue(self, key, value, currency, accountName): showmessage('updateAccountValue', vars()) def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName): showmessage('updatePortfolio', vars()) def updateAccountTime(self, timeStamp): showmessage('updateAccountTime', vars()) def accountDownloadEnd(self, accountName): showmessage('accountDownloadEnd', vars()) def contractDetails(self, contractDetails): showmessage('contractDetails', vars()) def bondContractDetails(self, contractDetails): showmessage('bondContractDetails', vars()) def contractDetailsEnd(self, reqId): showmessage('contractDetailsEnd', vars()) def execDetails(self, orderId, contract, execution): showmessage('execDetails', vars()) def execDetailsEnd(self, reqId): showmessage('execDetailsEnd', vars()) def error_0(self, strval): showmessage('error_0', vars()) def error_1(self, strval): showmessage('error_1', vars()) def updateMktDepth(self, tickerId, position, operation, side, price, size): showmessage('updateMktDepth', vars()) def updateMktDepthL2(self, tickerId, position, marketMaker, operation, side, price, size): showmessage('updateMktDepthL2', vars()) def updateNewsBulletin(self, msgId, msgType, message, origExchange): showmessage('updateNewsBulletin', vars()) def managedAccounts(self, accountsList): pass #showmessage('managedAccounts', vars()) def receiveFA(self, faDataType, xml): showmessage('receiveFA', vars()) def historicalData(self, reqId, date, open, high, low, close, volume, count, WAP, hasGaps): showmessage('historicalData', vars()) def scannerParameters(self, xml): showmessage('scannerParameters', vars()) def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection): showmessage('scannerData', vars()) def scannerDataEnd(self, reqId): showmessage('scannerDataEnd', vars()) def realtimeBar(self, reqId, time, open, high, low, close, volume, wap, count): showmessage('realtimeBar', vars()) def currentTime(self, time): showmessage('currentTime', vars()) def fundamentalData(self, reqId, data): showmessage('fundamentalData', vars()) def deltaNeutralValidation(self, reqId, underComp): showmessage('deltaNeutralValidation', vars()) def tickSnapshotEnd(self, reqId): showmessage('tickSnapshotEnd', vars()) def marketDataType(self, reqId, marketDataType): showmessage('marketDataType', vars()) def commissionReport(self, commissionReport): showmessage('commissionReport', vars())
class ReferenceApp: def __init__(self, host='localhost', port=7496, clientId=0): self.host = host self.port = port self.clientId = clientId self.wrapper = ReferenceWrapper() self.connection = EClientSocket(self.wrapper) @ref def eConnect(self): self.connection.eConnect(self.host, self.port, self.clientId) @ref def reqAccountUpdates(self): self.connection.reqAccountUpdates(1, '') @ref def reqOpenOrders(self): self.connection.reqOpenOrders() @ref def reqExecutions(self): filt = ExecutionFilter() self.connection.reqExecutions(0, filt) @ref def reqIds(self): self.connection.reqIds(10) @ref def reqNewsBulletins(self): self.connection.reqNewsBulletins(1) @ref def cancelNewsBulletins(self): self.connection.cancelNewsBulletins() @ref def setServerLogLevel(self): self.connection.setServerLogLevel(3) @ref def reqAutoOpenOrders(self): self.connection.reqAutoOpenOrders(1) @ref def reqAllOpenOrders(self): self.connection.reqAllOpenOrders() @ref def reqManagedAccts(self): self.connection.reqManagedAccts() @ref def requestFA(self): self.connection.requestFA(1) @ref def reqMktData(self): contract = Contract() # contract.m_symbol = 'AUD' contract.m_currency = 'USD' contract.m_secType = 'CASH' contract.m_exchange = 'IDEALPRO' self.connection.reqMktData(1, contract, '', False) @ref def reqHistoricalData(self): contract = Contract() contract.m_symbol = 'QQQQ' contract.m_secType = 'STK' contract.m_exchange = 'SMART' endtime = strftime('%Y%m%d %H:%M:%S') self.connection.reqHistoricalData( tickerId=1, contract=contract, endDateTime=endtime, durationStr='1 D', barSizeSetting='1 min', whatToShow='TRADES', useRTH=0, formatDate=1) @ref def eDisconnect(self): sleep(5) self.connection.eDisconnect()
class IBClient(object): """IB Socket client""" def __init__(self, client_name='IB', host='localhost', port=7496, client_id=0): """Constructor""" self.client_name = client_name self.host = host # host IP address in a string; e.g. '127.0.0.1', 'localhost' self.port = port # socket port; TWS default value: 7496; TWS demo account default value: 7497 self.client_id = client_id # socket client id self.connected = False # status of the socket connection self.tickerId = 0 # known as ticker ID or request ID self.ipc_msg_dict = {} # key: ticker ID or request ID; value: request and response objects; response objects ususally carrys data, Events, and Status self.order_id = 0 # current available order ID self.order_dict = {} # key: ticker ID or request ID; value: request and response objects; response objects ususally carrys data, Events, and Status self.context = None # key: ticker ID or request ID; value: request and response objects; response objects ususally carrys data, Events, and Status self.data = None self.wrapper = IBMsgWrapper(self) # the instance with IB message callback methods self.connection = EClientSocket(self.wrapper) # low layer socket client # TWS's data connection status self.hmdf_status_dict = {} for farm in IB_FARM_NAME_LS: self.hmdf_status_dict[farm] = 'unknown' # EVENTS self.conn_down_event = Event() # sock connection event self.mdf_conn_event = Event() # market data connection event self.hdf_conn_event = Event() # hist data connection event self.order_event = Event() self.account_event = Event() self.get_order_event = Event() # LOCKER self.req_id_locker = threading.Lock() # CONSTANT VALUES self.PRICE_DF_HEADER1 = ['time', 'open', 'high', 'low', 'close', 'volume'] self.PRICE_DF_HEADER2 = ['symbol', 'time', 'open', 'high', 'low', 'close', 'volume'] def connect(self): """ Connect to socket host, e.g. TWS """ self.connection.eConnect(self.host, self.port, self.client_id) timeout = 5. count = 0. while not self.connected and count < timeout: count += 0.05 sleep(0.05) if self.connected: self.order_id = self.connection.reqIds(-1) if self.context is not None: # TODO: may need to move this to a thread or at user layer self.enable_account_info_update() else: print('failed to connect.') return self.connected def close(self): """ disconnect from IB host """ self.disconnect() def disconnect(self): """ disconnect from IB host """ if self.context is not None: self.disable_account_info_update() self.connection.eDisconnect() self.connected = False def register_strategy(self, context, data): """ TBA """ self.context = context self.data = data def __get_new_request_id(self): '''' genew request ID (ticker ID) in a thread safe way ''' self.req_id_locker.acquire() self.tickerId += 1 __id = self.tickerId self.req_id_locker.release() return __id # # Tick Data Methods # def request_tick_data(self, contract): """ Subscribe tick data for a specified contract Args: contract: a legal IBPY Contract object or a string for U.S. stock only Returns: tickerId: the ID of this request. this ID could be used to cancel request later. tick_data: a reference to the tick data dictionary which will be updated with latest quote. """ if isinstance(contract, Contract): pass elif isinstance(contract, str): contract = new_stock_contract(contract) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") if not self.connected: raise RuntimeError('IB client is not connected to TWS') __id = self.__get_new_request_id() request = RequestDetails('reqMktData', 'Snapshot', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # False - indicating request live quotes instead of a snapshot self.connection.reqMktData(__id, contract, '', False) return __id, self.ipc_msg_dict[__id][1].tick_data def get_tick_snapshot(self, contract, max_wait_time=5): """ Get a snapshot with default tick types and corresponding tick data for a given contract Note: 1) no generic ticks can be specified. 2) only return data fields which have changed within an 11 second interval. If it is necessary for an API client to receive a certain data field, it is better to subscribe to market data until that field has been returned and then cancel the market data request. Known Issues: 1) When called outside of market hours, get_tick_snapshot request could take more than 10 sec to reach the end (tickSnapshotEnd) 2) Need to check Issue#1 during market hours Args: contract: a legal IBPY Contract object or a string for U.S. stock only Returns: a copy of tick data dictionary Raises: None """ if isinstance(contract, Contract): pass elif isinstance(contract, str): contract = new_stock_contract(contract) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") if not self.connected: raise RuntimeError('IB client is not connected to TWS') __id = self.__get_new_request_id() request = RequestDetails('reqMktData', 'Snapshot', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # send reqMktData req # True - indicating request live quotes instead of a snapshot # # Important: # When set it to 'True', each regulatory snapshot made will incur a fee of 0.01 USD to the account. # This applies to both live and paper accounts. self.connection.reqMktData(__id, contract, '', True) response.event.wait(max_wait_time) if response.event.is_set(): # tickPrice() and tickeSize() may write after tickSnapshotEnd() completed. sleep(0.5) snapshot = copy(response.tick_data) # remove the from dict and free the memory response.event.clear() self.ipc_msg_dict.pop(__id) else: response.event.clear() self.ipc_msg_dict.pop(__id) raise RuntimeError('reqMktData (get_tick_snapshot) is timeout. max_wait_time=%d' % (max_wait_time)) return snapshot def cancel_tick_request(self, tickerId): """ Cancel tick data request for a given ticker ID (request ID) Args: tickerId: the ticker request to cancel Returns: None Raises: None """ if not self.connected: raise RuntimeError('IB client is not connected to TWS') # TODO: check if tickerID is in the list self.connection.cancelMktData(tickerId) self.ipc_msg_dict.pop(tickerId) return def request_realtime_price(self, contract, price_type='TRADES'): """ Get real-time price/volume for a specific contract, e.g. stocks, futures and option contracts. IB API support only 5 sec duration between two real-time bar (price) records. Args: contract: one IB contract instance price_type: 'TRADES', 'MIDPOINT', 'BID', 'ASK' Returns: tickerId: the request ID; it's also the key to get response msg from ipc_msg_dict realtime_price: a reference to the real-time price (OCHL) list which will be updated with latest price (OCHL) record. Raises: None """ if isinstance(contract, Contract): pass elif isinstance(contract, str): contract = new_stock_contract(contract) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") if not self.connected: raise RuntimeError('IB client is not connected to TWS') if price_type not in ['TRADES', 'MIDPOINT', 'BID', 'ASK']: raise TypeError("Got incorrect price_type") __id = self.__get_new_request_id() request = RequestDetails('reqHistoricalData', price_type, contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # only 5 sec duration supported # price_type: 'TRADES', 'MIDPOINT', 'BID', 'ASK' # useRTH - set to True self.connection.reqRealTimeBars(__id, contract, 5, price_type, True) return __id, self.ipc_msg_dict[__id][1].rt_price def cancel_realtime_price(self, req_id): """ Cancel realtime price/volumne request. Args: req_id: the ticker ID (or request ID) Returns: None """ if not self.connected: raise RuntimeError('IB client is not connected to TWS') self.connection.cancelRealTimeBars(req_id) # remove request/response data from ipc_msg_dict self.ipc_msg_dict.pop(req_id) return # # Historical Data Methods # def get_price_history(self, contract, ts_end, duration='1 M', frequency='daily', max_wait_time=30): """ Get price/volumne history for a specific contract, e.g. stocks, futures and option ocntract. Args: contract: one IB contract instance ts_end: a string in '%Y%m%d' or '%Y%m%d %H:%M:%S' format duration: string X S Seconds X D Day X W Week X M Month X Y Year frequency: {‘daily’, ‘minute’}, optional; Resolution of the data to be returned. max_wait_time: int; max num of sec to wait after calling reqHistoricalData Returns: pandas Panel/DataFrame/Series – The pricing data that was requested. Open High Low Close Volume Date 2017-12-15 6.96 6.96 6.86 6.90 366523000 2017-12-18 6.88 7.02 6.87 6.98 303664000 2017-12-19 7.00 7.02 6.98 7.01 299342000 Raises: None """ if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(contract, Contract): pass elif isinstance(contract, str): contract = new_stock_contract(contract) else: raise TypeError("contract must be a Contract object or string") if frequency == 'daily': bar_size = '1 day' elif frequency == 'minute': bar_size = '1 min' elif frequency == 'second': bar_size = '1 sec' elif frequency == '5 seconds': bar_size = '5 secs' else: raise ValueError("get_price_history: incorrect frequency value") if len(ts_end) == 8 or len(ts_end) == 17: if len(ts_end) == 8: ts_end = ts_end + ' 23:59:59' else: print('get_price_history: incorrect ts_end format') return __id = self.__get_new_request_id() request = RequestDetails('reqHistoricalData', '', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqHistoricalData(tickerId=__id, contract=contract, endDateTime=ts_end, durationStr=duration, barSizeSetting=bar_size, whatToShow='TRADES', useRTH=0, formatDate=1) df = None response.event.wait(max_wait_time) if response.event.is_set(): df = pd.DataFrame(response.price_hist, columns=self.PRICE_DF_HEADER1) # clean up the time format date = df['time'][0] if len(date) == 8: df['time'] = pd.to_datetime(df['time'], format='%Y%m%d') elif len(date) == 18: # len('20161020 23:46:00') --> 2 Spaces!!!!! # adj_date = datetime.strptime(date, "%Y%m%d %H:%M:%S") df['time'] = pd.to_datetime(df['time'], format="%Y%m%d %H:%M:%S") else: # adj_date = datetime.strptime(date, "%Y%m%d %H:%M:%S") df['time'] = pd.to_datetime(df['time'], format="%Y%m%d %H:%M:%S") # TODO: check for timezone # exchange = request.contract.m_exchange # server_timezone = pytz.timezone("Asia/Shanghai") # timezone where the server runs # mkt_timezone = pytz.timezone(IBEXCHANGE.get_timezone(exchange)) # Get Exchange's timezone # adj_date = server_timezone.localize(adj_date).astimezone( # mkt_timezone) # covert server time to Exchange's time # adj_date = adj_date.strftime("%Y%m%d %H:%M:%S") # from datetime to string df = df.set_index('time') # remove the from dict and free the memory response.event.clear() self.ipc_msg_dict.pop(__id) else: self.ipc_msg_dict.pop(__id) print('reqHistoricalData is timeout.') raise RuntimeError('reqHistoricalData is timeout.') return df def get_stock_price_history(self, security_list, ts_end, duration='1 M', frequency='daily', max_wait_time=30): """Get price/volumne history for a list of stocks. Args: security_list: a list of security symbols, .e.g. ['IBM', 'DATA'] endDateTime: a string in '%Y%m%d' or '%Y%m%d %H:%M:%S' format durationStr: see IB API doc. frequency: {‘daily’, ‘minute’}, optional; Resolution of the data to be returned. max_wait_time: int; max num of sec to wait after calling reqHistoricalData Returns: pandas Panel/DataFrame/Series – The pricing data that was requested. Raises: None """ if not self.connected: raise RuntimeError('IB client is not connected to TWS') num_secs = len(security_list) if num_secs <= 0: return if frequency == 'daily': bar_size = '1 day' elif frequency == 'minute': bar_size = '1 min' elif frequency == 'second': bar_size = '1 sec' elif frequency == '5 seconds': bar_size = '5 secs' else: print('get_stock_price_history: incorrect frequency') return if len(ts_end) == 8 or len(ts_end) == 17: if len(ts_end) == 8: ts_end = ts_end + ' 23:59:59' else: print('get_stock_price_history: incorrect ts_end format') return # ['Symbol', 'Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df = pd.DataFrame(columns=self.PRICE_DF_HEADER2) for sec in security_list: __id = self.__get_new_request_id() contract = new_stock_contract(sec) request = RequestDetails('reqHistoricalData', '', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqHistoricalData(tickerId=__id, contract=contract, endDateTime=ts_end, durationStr=duration, barSizeSetting=bar_size, whatToShow='TRADES', useRTH=0, formatDate=1) response.event.wait(max_wait_time) if response.event.is_set(): df_tmp = pd.DataFrame(response.price_hist, columns=self.PRICE_DF_HEADER1) # clean up the time format date = df_tmp['time'][0] if len(date) == 8: df_tmp['time'] = pd.to_datetime(df_tmp['time'], format='%Y%m%d') elif len(date) == 18: # len('20161020 23:46:00') --> 2 Spaces!!!!! # adj_date = datetime.strptime(date, "%Y%m%d %H:%M:%S") df_tmp['time'] = pd.to_datetime(df_tmp['time'], format="%Y%m%d %H:%M:%S") else: # adj_date = datetime.strptime(date, "%Y%m%d %H:%M:%S") df_tmp['time'] = pd.to_datetime(df_tmp['time'], format="%Y%m%d %H:%M:%S") # TODO: check for timezone # exchange = request.contract.m_exchange # server_timezone = pytz.timezone("Asia/Shanghai") # timezone where the server runs # mkt_timezone = pytz.timezone(IBEXCHANGE.get_timezone(exchange)) # Get Exchange's timezone # adj_date = server_timezone.localize(adj_date).astimezone( # mkt_timezone) # covert server time to Exchange's time # adj_date = adj_date.strftime("%Y%m%d %H:%M:%S") # from datetime to string df_tmp['symbol'] = pd.DataFrame([sec] * len(df_tmp)) df = df.append(df_tmp) # remove the from dict and free the memory self.ipc_msg_dict.pop(__id) response.event.clear() else: self.ipc_msg_dict.pop(__id) raise RuntimeError('reqHistoricalData is timeout.') return df def get_contract_price_history(self, contract, ts_end, duration='1 M', frequency='daily', max_wait_time=30): # Same function as get_price_history return self.get_price_history(self, contract, ts_end, duration, frequency, max_wait_time) # # Placing/Changing/Canceling Order Methods # def order_amount(self, contract, amount, style=MarketOrder()): ''' Place an order. Order X units of security Y. Warning: only mkt order and limited order work; calling stoploss/stoplimited order will result in IB disconnection. :param contract: A IB Contract object. :param amount: The integer amount of shares. Positive means buy, negative means sell. :param style: :return: ''' if amount == 0: return -1 elif amount > 0: action = 'BUY' else: action = 'SELL' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if not isinstance(contract, Contract): raise TypeError("contract must be a contract object") # request next valid order ID from IB host; this request will update self.order_id self.connection.reqIds(-1) # note: input param is always ignored; sleep(0.05) order = Order() order.m_orderId = self.order_id order.m_client_id = self.client_id order.m_action = action order.m_totalQuantity = abs(amount) order.m_orderType = style.order_type if style.limit_price is not None: order.m_lmtPrice = style.limit_price if style.stop_price is not None: order.m_auxPrice = style.stop_price order.m_overridePercentageConstraints = True # override TWS order size constraints # place order self.connection.placeOrder(self.order_id, contract, order) # TODO: wait for returns from orderStatus return self.order_id def combo_order_amount(self, contract, amount, style=MarketOrder()): ''' Place an order :param contract: A security object. :param amount: The integer amount of shares. Positive means buy, negative means sell. :param style: :return: ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if amount == 0: return -1 elif amount > 0: action = 'BUY' else: action = 'SELL' if isinstance(contract, Contract): if len(contract.m_comboLegs) == 0: raise TypeError("contract must contains combo legs") else: raise TypeError("contract must be a contract object") # request next valid order ID from IB host; this request will update self.order_id self.connection.reqIds(-1) # note: input param is always ignored; sleep(0.05) order = Order() order.m_orderId = self.order_id order.m_client_id = self.client_id order.m_action = action order.m_totalQuantity = abs(amount) order.m_orderType = style.order_type if style.limit_price is not None: order.m_lmtPrice = style.limit_price if style.stop_price is not None: order.m_auxPrice = style.stop_price order.m_overridePercentageConstraints = True # override TWS order size constraints ''' # Advanced configuration. Not tested yet. if style.is_combo_order: if style.non_guaranteed: tag = TagValue() tag.m_tag = "NonGuaranteed" tag.m_value = "1" order.m_smartComboRoutingParams = [tag] ''' self.connection.placeOrder(self.order_id, contract, order) return self.order_id def order_value(self, contract, value, style): ''' Reserve for future implementation :param contract: :param value: :param style: :return: ''' pass def order_target(self, contract, amount, style): ''' Places an order to adjust a position to a target number of shares. :param contract: :param value: :param style: :return: ''' pass def order_target_value(self, contract, amount, style): ''' Places an order to adjust a position to a target value. :param contract: :param value: :param style: :return: ''' pass def modify_order(self, order_id, contract, amount, style=MarketOrder()): ''' Change amount or order type (including limited price for limtied orders) for a existing order specified by order_id :param order_id: a existing order's order_id :param contract: A IB Contract object. supposed to be the same with the order-to-be-modified. :param amount: The integer amount of shares. Positive means buy, negative means sell. :param style: market order or limited order :return: the existing order's order_id (same as the input) ''' if amount == 0: return -1 elif amount > 0: action = 'BUY' else: action = 'SELL' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if not isinstance(contract, Contract): raise TypeError("contract must be a contract object") order = Order() order.m_orderId = order_id order.m_client_id = self.client_id order.m_action = action order.m_totalQuantity = abs(amount) order.m_orderType = style.order_type if style.limit_price is not None: order.m_lmtPrice = style.limit_price if style.stop_price is not None: order.m_auxPrice = style.stop_price order.m_overridePercentageConstraints = True # override TWS order size constraints # place order self.connection.placeOrder(self.order_id, contract, order) # TODO: wait for returns from orderStatus return self.order_id def cancel_order(self, order): ''' Attempts to cancel the specified order. Cancel is attempted asynchronously. :param order: Can be the order_id as a string or the order object. :return: None ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(order, int): order_id = order elif isinstance(order, Order): order_id = order.m_orderId else: raise TypeError("order must be a order_id (int) or order object") self.connection.cancelOrder(order_id) def get_open_orders(self): ''' Attempts to get all open orders. :param :return: None ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if 'reqOpenOrders' not in self.ipc_msg_dict: request = RequestDetails('reqOpenOrders', '', '') response = ResponseDetails() self.ipc_msg_dict['reqOpenOrders'] = (request, response) else: response = self.ipc_msg_dict['reqOpenOrders'][1] # self.connection.reqOpenOrders() # self.reqAutoOpenOrders(True) self.connection.reqAllOpenOrders() max_wait_time = 3. self.get_order_event.wait(max_wait_time) if self.get_order_event.is_set(): # snapshot = copy(response.tick_snapshot) # self.ipc_msg_dict.pop(self.tickerId) self.get_order_event.clear() else: # self.ipc_msg_dict.pop(self.tickerId) raise RuntimeError('get_open_orders is timeout.') return # # Account Info Methods # def enable_account_info_update(self): ''' Turn on auto account update, meaning IB socket host will push account info to IB socket client. updateAccountTime() updateAccountValue() updatePortfolio() ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') # TODO: check self.IB_acct_id before using it # request IB host (e.g. TWS) push account info to IB client (socket client) self.connection.reqAccountUpdates(True, self.context.account.account_id) return def disable_account_info_update(self): ''' Turn off auto account update, meaning IB socket host will stop pushing account info to IB socket client. ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') # TODO: check self.IB_acct_id before using it # stop IB host (e.g. TWS) to push account info to IB client (socket client) self.connection.reqAccountUpdates(False, self.context.account.account_id) return # # Fundamental Data Methods # def get_financial_statements(self, symbol, max_wait_time=20): ''' Get a company's financial statements :param: symbol: stock symbol string, e.g. 'IBM'; or a IB contract object :return: a string of financial statements ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'ReportsFinStatements', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqFundamentalData(__id, contract, 'ReportsFinStatements') response.event.wait(max_wait_time) raw_xml = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # covert from xml to dest. format raw_xml = copy(response.fundamental_data) else: pass # raise RuntimeError('get_financial_statements: reqFundamentalData got error. Security=%s Reason:%s' % (symbol, response.error_msg)) else: # Timeout pass # ('get_financial_statements: reqFundamentalData is timeout. Security=%s' % symbol) status = response.status self.ipc_msg_dict.pop(__id) return status, raw_xml def get_company_ownership(self, symbol, max_wait_time=60.0 * 5): ''' Get a company's ownership report :param: symbol: stock symbol string, e.g. 'IBM' max_wait_time: max number of seconds to wait before raise timeout :return: a string of ownership report ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): # For US stock only contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'ReportsOwnership', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqFundamentalData(__id, contract, 'ReportsOwnership') response.event.wait(max_wait_time) report = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # covert from xml to dest. format report = parse_ownership_report(response.fundamental_data) else: pass # ('get_company_ownership: reqFundamentalData got error. Security=%s Reason:%s' % (symbol, response.error_msg)) else: pass # ('get_company_ownership: reqFundamentalData is timeout. Security=%s' % symbol) status = response.status self.ipc_msg_dict.pop(__id) return status, report def get_analyst_estimates(self, symbol, max_wait_time=20): ''' Get analyst estimates report for a company :param: symbol: stock symbol string, e.g. 'IBM'; or a IB contract object :return: a string of financial statements ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'RESC-Analyst Estimates', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqFundamentalData(__id, contract, 'RESC') response.event.wait(max_wait_time) report = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # covert from xml to dest. format report = parse_analyst_estimates(response.fundamental_data) else: pass # ('get_analyst_estimates: reqFundamentalData got error. Security=%s Reason:%s' % (symbol, response.error_msg)) else: pass # ('get_analyst_estimates: reqFundamentalData is timeout. Security=%s' % symbol) status = response.status self.ipc_msg_dict.pop(__id) return status, report def get_company_overview(self, symbol, max_wait_time=10): ''' Get company overview infomration :param: symbol: stock symbol string, e.g. 'IBM'; or a IB contract object :return: a string of financial statements ''' # ReportsFinSummary Financial summary if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'ReportSnapshot-Company overview', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # ReportSnapshot Company's financial overview self.connection.reqFundamentalData(__id, contract, 'ReportSnapshot') response.event.wait(max_wait_time) report = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # TODO: covert from xml to dest. format report = response.fundamental_data else: pass # ('get_analyst_estimates: reqFundamentalData got error. Security=%s Reason:%s' % (symbol, response.error_msg)) else: pass # ('get_analyst_estimates: reqFundamentalData is timeout. Security=%s' % symbol) status = response.status self.ipc_msg_dict.pop(__id) return status, report def get_financial_summary(self, symbol, max_wait_time=10): ''' Get company finanical summary information, such as revenue history, net profit, and dividends history. :param: symbol: stock symbol string, e.g. 'IBM'; or a IB contract object :return: a string of financial statements ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'ReportsFinSummary-Financial summary', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) self.connection.reqFundamentalData(__id, contract, 'ReportsFinSummary') response.event.wait(max_wait_time) report = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # TODO: covert from xml to dest. format report = response.fundamental_data else: pass else: pass status = response.status self.ipc_msg_dict.pop(__id) return status, report def get_financial_ratios(self, symbol, max_wait_time=5): ''' Get analyst estimates report for a company :param: symbol: stock symbol string, e.g. 'IBM' :return: a string of financial statements ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'RESC-Analyst Estimates', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # 258 - financial ratios ''' TTMNPMGN=16.1298;NLOW=80.6;TTMPRCFPS=6.26675;TTMGROSMGN=60.76731;TTMCFSHR=15.004 46;QCURRATIO=1.42071;TTMREV=259842;TTMINVTURN=5.28024;TTMOPMGN=14.22711;TTMPR2RE V=1.39703;AEPSNORM=8.55;TTMNIPEREM=144524.1;EPSCHNGYR=8.47727;TTMPRFCFPS=62.4260 6;TTMRECTURN=19.99938;TTMPTMGN=17.88125;QCSHPS=40.50882;TTMFCF=5815; LATESTADATE=2016-12-31;APTMGNPCT=17.88125;AEBTNORM=46463;TTMNIAC=33008;NetDebt_I=152080; PRYTDPCTR=-1.55563;TTMEBITD=53326;AFEEPSNTM=0;PR2TANBK=5.01599;EPSTRENDGR=- 15.53209;QTOTD2EQ=72.60778;TTMFCFSHR=1.50625;QBVPS=110.0867;NPRICE=94.1;YLD5YAVG =3.88751;REVTRENDGR=51.11774;TTMEPSXCLX=8.54981;QTANBVPS=18.75999;PRICE2BK=0.854 78;MKTCAP=363007.5;TTMPAYRAT=31.32574;TTMINTCOV=-99999.99;TTMDIVSHR=2.585;TTMREVCHG=55.81794; TTMROAPCT=4.09615;TTMROEPCT=7.73685; TTMREVPERE=896006.9;APENORM=11.00585;TTMROIPCT=5.51924;REVCHNGYR=- 6.66885;CURRENCY=HKD;DIVGRPCT=-8.33887;TTMEPSCHG=-32.80548;PEEXCLXOR=11.00609;QQUICKRATI=1.30087; TTMREVPS=67.30638;BETA=0.90979;TTMEBT=46463;ADIV5YAVG=3.1048;ANIACNORM=33008;QLTD2EQ=55.46377;NHIG=103.9 ''' report = None self.connection.reqMktData(__id, contract, "258", False) response.event.wait(max_wait_time) if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # TODO: convert the format to a table alike report = response.tick_str return report def get_dividends_info(self, symbol, max_wait_time=5): ''' Get analyst estimates report for a company :param: symbol: stock symbol string, e.g. 'IBM' :return: a string of financial statements ''' if not self.connected: raise RuntimeError('IB client is not connected to TWS') if isinstance(symbol, Contract): contract = symbol elif isinstance(symbol, str): contract = new_stock_contract(symbol) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") __id = self.__get_new_request_id() request = RequestDetails('reqFundamentalData', 'RESC-Analyst Estimates', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # IB Dividends ("456") # # This tick type provides four different comma-separated elements: # The sum of dividends for the past 12 months (0.83 in the example below). # The sum of dividends for the next 12 months (0.92 from the example below). # The next dividend date (20130219 in the example below). # The next single dividend amount (0.23 from the example below). # Example: 0.83,0.92,20130219,0.23 self.connection.reqMktData(__id, contract, "456", False) result = None response.event.wait(max_wait_time) if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: # TODO: convert the format result = set(response.tick_str.split(',')) self.ipc_msg_dict.pop(__id) return result def get_contract_details(self, contract, max_wait_time=5): """ Get contract details for a specified contract Args: contract: a legal IBPY Contract object or a string for U.S. stock only Returns: status: a reference to the tick data dictionary which will be updated with latest quote. contract_details: a contractDetails instance """ if isinstance(contract, Contract): pass elif isinstance(contract, str): contract = new_stock_contract(contract) else: raise TypeError("contract must be a contract object or string (for U.S. stocks only).") if not self.connected: raise RuntimeError('IB client is not connected to TWS') __id = self.__get_new_request_id() request = RequestDetails('reqContractDetails', '', contract) response = ResponseDetails() self.ipc_msg_dict[__id] = (request, response) # False - indicating request live quotes instead of a snapshot self.connection.reqContractDetails(__id, contract) response.event.wait(max_wait_time) contract_details = None if response.event.is_set(): if response.status == ResponseDetails.STATUS_FINISHED: if len(response.contract_list) > 0: contract_details = copy(response.contract_list[0]) else: pass else: pass status = response.status self.ipc_msg_dict.pop(__id) return status, contract_details def get_full_contract(self, contract): """ Subscribe tick data for a specified contract Args: contract: a legal IBPY Contract object or a string for U.S. stock only Returns: tickerId: the ID of this request. this ID could be used to cancel request later. tick_data: a reference to the tick data dictionary which will be updated with latest quote. """ status, contract_details = self.get_contract_details(contract) new_contract = copy(contract_details.m_summary) return status, new_contract
class IBMarketData(object): def __init__(self, host='127.0.0.1', port=7496, clientId=0): self.host = host self.port = port self.clientId = clientId self.wrapper = ReferenceWrapper() self.connection = EClientSocket(self.wrapper) self.logger = make_logger(self) if not self.logger.handlers: self.logger.addHandler(logging.StreamHandler()) def market_data_get(self, symbol, data_type): self.connection.reqMktData(1, ) contract = Contract() contract.m_symbol = 'QQQ' contract.m_secType = 'STK' contract.m_exchange = 'SMART' self.connection.reqMktData(1, contract, '', False) def market_data_register(self, symbol, data_type, handler, handler_ctx): pass def market_data_unregister(self, symbol, data_type): pass def _make_stock_contract(self, symbol): contract = Contract() contract.m_symbol = symbol contract.m_secType = 'STK' contract.m_exchange = 'SMART/ISLAND' return contract def _generate_ticker_id(self): global DataRequests while True: id = random.randint(1, 9999) if id not in DataRequests.keys(): return id def historical_bars_get(self, symbol, bar_type, start, end=None): self.logger.error('symbol={}, bar_type={}, start={}, end={}'.format( symbol, bar_type, start, end)) id = self._generate_ticker_id() global DataRequests data_request = DataRequest(BAR_TYPE_TO_REQ[bar_type]) DataRequests[id] = data_request if end is None: end = start if bar_type == BAR_MIN: end += timedelta(minutes=1) elif bar_type == BAR_DAY: start = datetime(year=start.year, month=start.month, day=start.day) end = datetime(year=end.year, month=end.month, day=end.day) end += timedelta(days=1) else: raise ValueError() if start.tzname() == None: start = TZ_NY.localize(start) end = TZ_NY.localize(end) start = start.astimezone(TZ_LOCAL) end = end.astimezone(TZ_LOCAL) assert end >= start end_start_delta = end - start duration = '{days} D'.format(days=end_start_delta.days + 1) if bar_type == BAR_DAY and end_start_delta.days + 1 > 365: duration = '{years} Y'.format(years=(end_start_delta.days / 365) + 1) endtime = end.strftime('%Y%m%d %H:%M:%S') args = dict( tickerId=id, contract=self._make_stock_contract(symbol), endDateTime=endtime, durationStr=duration, barSizeSetting=bar_type, whatToShow='TRADES', useRTH=0, formatDate=2 #linux time ) self.logger.error( 'reqHistoricalData(id:{} | type:"{}" | duration:"{}" | end:"{}"'. format(id, bar_type, duration, endtime)) self.connection.reqHistoricalData(**args) data_request.wait() start = start.astimezone(TZ_NY) end = end.astimezone(TZ_NY) bars = [b for b in data_request.bars if start <= b.get_time() <= end] DataRequests.pop(id) return bars def bars_register(self, symbol, handler, handler_ctx): pass def bars_unregister(self, symbol): pass def connect(self): self.connection.eConnect(self.host, self.port, self.clientId) def disconnect(self): self.connection.eDisconnect() time.sleep(0.15)
class Bot(object): def __init__(self, host, port, strategies, instruments, logger): self.msgs = queue.Queue() self.book_builder = BookBuilder() # strategies self.instruments = instruments self.contracts = dict() self.strategies = [] for strategy in strategies: strat = Recoil2(strategy['watch_threshold'], strategy['watch_duration'], strategy['slowdown_threshold'], strategy['slowdown_duration']) self.strategies.append(strat) # operations self.host = host self.port = port self.connection = EClientSocket(Connector(self.instruments, self.msgs)) self.next_id = None self.log = logger def connect(self): template = 'Attempting to connect host: {} port: {}...' self.log.operation(template.format(self.host, self.port)) self.connection.eConnect(self.host, self.port, 0) self.log.operation('Connected.') def disconnect(self): self.log.operation('Disconnecting...') self.connection.eDisconnect() self.log.operation('Disconnected.') def request_data(self): for ticker_id, instrument in self.instruments.items(): contract = Contract() contract.m_symbol = instrument['symbol'] contract.m_currency = instrument['currency'] contract.m_secType = instrument['secType'] contract.m_exchange = instrument['exchange'] self.contracts[instrument['symbol']] = contract self.connection.reqMktData(ticker_id, contract, '', False) def run(self): while True: msg = self.msgs.get() self.log.raw(msg) if msg['type'] == 'nextValidId': self.log.order({'msg': 'new order ID', 'orderId': msg['orderId']}) self.next_id = msg['orderId'] continue tick = self.book_builder.process_raw_tick(msg) if not tick: continue self.log.data(tick) for strategy in self.strategies: signal = strategy.handle_tick(tick) if not signal: continue self.log.order(signal) order = strategy.place_order(signal) if not order: continue c = self.contracts[signal['symbol']] self.log.order({'symbol': signal['symbol'], 'qty': order.m_totalQuantity, 'type': order.m_orderType, 'goodTill': order.m_goodTillDate, 'px': order.m_lmtPrice, 'action': order.m_action}) self.connection.placeOrder(id=self.next_id, contract=c, order=order) self.next_id += 1
orders = pd.DataFrame(callback.order_Status, columns = ['orderId', 'status', 'filled', 'remaining', 'avgFillPrice', 'permId', 'parentId', 'lastFillPrice', 'clientId', 'whyHeld']) callback.open_Order[:1] tws.cancelOrder(order_id) contract_info = create.create_contract('EUR', 'CASH', 'IDEALPRO', 'USD') tickedId = 1002 tws.reqMktData(tickedId, contract_info, "", False) tick_data = pd.DataFrame(callback.tick_Price, columns = ['tickerId', 'field', 'price', 'canAutoExecute']) tick_type = {0 : "BID SIZE", 1 : "BID PRICE", 2 : "ASK PRICE", 3 : "ASK SIZE", 4 : "LAST PRICE", 5 : "LAST SIZE", 6 : "HIGH", 7 : "LOW", 8 : "VOLUME", 9 : "CLOSE PRICE", 10 : "BID OPTION COMPUTATION", 11 : "ASK OPTION COMPUTATION",
class IbApp(object): """IB链接器""" def __init__(self, host, port, client_id, req_dict, tick_dict, redis, mongo_db): """ TODO: 不应该维护一个巨大的类,可以考虑使用混合模式来管理这个类 :host:地址 :port:接口 :client_id:这个可以随便写一个,int :req_id:请求id :redis:redis实例 """ self.host = host self.port = port self.client_id = client_id self.warpper = IbTestWrapper(req_dict, tick_dict, mongo_db) self.connection = EClientSocket(self.warpper) self.req_id = 0 self.req_dict = req_dict self.tick_dict = tick_dict self.redis = redis def econnect(self): """docstring for connect""" # 连接TWS self.connection.eConnect(self.host, self.port, self.client_id) def subscribe(self, m_symbol, currency, m_sec_type, m_exchange, m_local_symbol): """ :return: 返回req_id和合约代码信息返回,好做好映射, """ contract = Contract() contract.m_symbol = m_symbol # 底层资产的代码 PS:CL contract.m_currency = currency # 币种 PS:USD contract.m_secType = m_sec_type # 证券类型 PS:FUT contract.m_exchange = m_exchange # 交易所NYMEX contract.m_localSymbol = m_local_symbol #合约代码 self.req_id += 1 self.connection.reqMktData(self.req_id, contract, '', False) # 订阅 logging.info("正在订阅:" + m_local_symbol) self.tick_dict[self.req_id] = {} # 初始化Tick的数据 #cache_symbol cache_symbol = m_local_symbol if ' ' not in m_local_symbol else m_local_symbol.replace( ' ', '_') # 将合约ID存入Redis以后使用 sec_id = 'ib.{m_exchange}.{m_local_symbol}'.format( m_exchange=m_exchange, m_local_symbol=cache_symbol if not m_local_symbol == '' else m_symbol) self.redis.sadd('IB_id_set', sec_id) # 将订阅合约信息保存在字典中 self.req_dict[self.req_id] = { 'symbol': m_symbol, 'currency': currency, 'sec_type': m_sec_type, 'exchange': m_exchange, 'local_symbol': cache_symbol } def query_account(self, acc_code): """ docstring for query_account 查询账户信息 似乎是主动推送 """ self.connection.reqAccountUpdates(1, acc_code) def run(self): """运行""" while True: pass
class IbApp(object): """IB链接器""" def __init__(self, host, port, client_id, req_dict, order_dict, tick_dict): """ TODO: 不应该维护一个巨大的类,可以考虑使用混合模式来管理这个类 :host:地址 :port:接口 :client_id:这个可以随便写一个,int :req_id:请求id :redis:redis实例 """ self.host = host self.port = port self.client_id = client_id self.warpper = IbTestWrapper(req_dict, order_dict, tick_dict) self.connection = EClientSocket(self.warpper) self.req_id = 0 self.order_id = 0 self.req_dict = req_dict self.order_dict = order_dict self.tick_dict = tick_dict def econnect(self): """docstring for connect""" # 连接TWS self.connection.eConnect(self.host, self.port, self.client_id) def subscribe(self, m_symbol, currency, m_sec_type, m_exchange, m_local_symbol): """ :return: 返回req_id和合约代码信息返回,好做好映射, """ contract = Contract() contract.m_symbol = m_symbol # 底层资产的代码 PS:CL contract.m_currency = currency # 币种 PS:USD contract.m_secType = m_sec_type # 证券类型 PS:FUT contract.m_exchange = m_exchange # 交易所NYMEX contract.m_localSymbol = m_local_symbol #合约代码 self.req_id += 1 self.connection.reqMktData(self.req_id, contract, '', False) # 订阅 self.tick_dict[self.req_id] = {} # 初始化Tick的数据 #cache_symbol cache_symbol = m_local_symbol if ' ' not in m_local_symbol else m_local_symbol.replace( ' ', '_') # 将订阅合约信息保存在字典中 self.req_dict[self.req_id] = { 'symbol': m_symbol, 'currency': currency, 'sec_type': m_sec_type, 'exchange': m_exchange, 'local_symbol': cache_symbol } def send_order(self, order_dict): """发单""" # 发单ID self.order_id += 1 contract = self._make_opt_contract(order_dict) order = self._make_opt_order(self.order_id, self.client_id, order_dict) self.connection.placeOrder(self.order_id, contract, order) self.connection.reqIds() self.order_dict[self.order_id] = order_dict return self.order_id def cancle_order(self, order_id): """撤单""" self.connection.cancelOrder(order_id) def run(self): """运行""" while True: pass def _make_opt_contract(self, order_dict): """docstring for make_opt_contract""" contract = Contract() contract.m_localSymbol = order_dict['m_local_symbol'] contract.m_secType = order_dict['m_sec_type'] contract.m_right = order_dict['m_right'] contract.m_expiry = order_dict['m_expiry'] contract.m_strike = order_dict['m_strike'] contract.m_exchange = order_dict['m_exchange'] contract.m_currency = order_dict['currency'] return contract def _make_opt_order(self, order_id, client_id, order_dict): """docstring for _make_opt_contract""" order = Order() order.m_orderId = order_id order.m_clientId = client_id order.m_action = order_dict['m_action'] order.m_lmtPrice = order_dict['price'] order.m_totalQuantity = order_dict['volume'] order.m_orderType = order_dict['m_order_type'] return order
class ReferenceApp: parameters = None def __init__(self, host='localhost', port=7496, clientId=0): self.host = host self.port = port self.clientId = clientId self.parameters = settings.TradeParameters() self.wrapper = ReferenceWrapper(self.parameters) self.connection = EClientSocket(self.wrapper) def eConnect(self): self.connection.eConnect(self.host, self.port, self.clientId) def reqAccountUpdates(self): self.connection.reqAccountUpdates(1, '') def reqOpenOrders(self): self.connection.reqOpenOrders() def reqExecutions(self): filt = ExecutionFilter() self.connection.reqExecutions(filt) def reqIds(self): self.connection.reqIds(10) def reqNewsBulletins(self): self.connection.reqNewsBulletins(1) def cancelNewsBulletins(self): self.connection.cancelNewsBulletins() def setServerLogLevel(self): self.connection.setServerLogLevel(3) def reqAutoOpenOrders(self): self.connection.reqAutoOpenOrders(1) def reqAllOpenOrders(self): self.connection.reqAllOpenOrders() def reqManagedAccts(self): self.connection.reqManagedAccts() def requestFA(self): self.connection.requestFA(1) def reqMktData(self): for tick_id, symbol in self.parameters.tickers().iteritems(): contract = app.wrapper.makeContract(symbol) self.connection.reqMktData(tick_id, contract, [], False) def reqHistoricalData(self): contract = Contract() contract.m_symbol = 'QQQQ' contract.m_secType = 'STK' contract.m_exchange = 'SMART' endtime = strftime('%Y%m%d %H:%M:%S') self.connection.reqHistoricalData(tickerId=1, contract=contract, endDateTime=endtime, durationStr='1 D', barSizeSetting='1 min', whatToShow='TRADES', useRTH=0, formatDate=1) def eDisconnect(self): sleep(5) self.connection.eDisconnect()