From adb884e19f02892caa2a7b9168d778ab2ea49874 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 14 Mar 2019 09:28:12 +0800 Subject: [PATCH] [Add]ib api source code into vnpy.api --- vnpy/api/ib/__init__.py | 18 + vnpy/api/ib/account_summary_tags.py | 47 + vnpy/api/ib/client.py | 3269 +++++++++++++++++++++++++++ vnpy/api/ib/comm.py | 75 + vnpy/api/ib/commission_report.py | 21 + vnpy/api/ib/common.py | 190 ++ vnpy/api/ib/connection.py | 122 + vnpy/api/ib/contract.py | 205 ++ vnpy/api/ib/decoder.py | 1512 +++++++++++++ vnpy/api/ib/enum_implem.py | 22 + vnpy/api/ib/errors.py | 41 + vnpy/api/ib/execution.py | 50 + vnpy/api/ib/message.py | 171 ++ vnpy/api/ib/news.py | 10 + vnpy/api/ib/object_implem.py | 14 + vnpy/api/ib/order.py | 226 ++ vnpy/api/ib/order_condition.py | 282 +++ vnpy/api/ib/order_state.py | 28 + vnpy/api/ib/reader.py | 51 + vnpy/api/ib/scanner.py | 57 + vnpy/api/ib/server_versions.py | 104 + vnpy/api/ib/softdollartier.py | 17 + vnpy/api/ib/tag_value.py | 24 + vnpy/api/ib/ticktype.py | 109 + vnpy/api/ib/utils.py | 115 + vnpy/api/ib/wrapper.py | 701 ++++++ vnpy/gateway/ib/ib_gateway.py | 20 +- 27 files changed, 7491 insertions(+), 10 deletions(-) create mode 100644 vnpy/api/ib/__init__.py create mode 100644 vnpy/api/ib/account_summary_tags.py create mode 100644 vnpy/api/ib/client.py create mode 100644 vnpy/api/ib/comm.py create mode 100644 vnpy/api/ib/commission_report.py create mode 100644 vnpy/api/ib/common.py create mode 100644 vnpy/api/ib/connection.py create mode 100644 vnpy/api/ib/contract.py create mode 100644 vnpy/api/ib/decoder.py create mode 100644 vnpy/api/ib/enum_implem.py create mode 100644 vnpy/api/ib/errors.py create mode 100644 vnpy/api/ib/execution.py create mode 100644 vnpy/api/ib/message.py create mode 100644 vnpy/api/ib/news.py create mode 100644 vnpy/api/ib/object_implem.py create mode 100644 vnpy/api/ib/order.py create mode 100644 vnpy/api/ib/order_condition.py create mode 100644 vnpy/api/ib/order_state.py create mode 100644 vnpy/api/ib/reader.py create mode 100644 vnpy/api/ib/scanner.py create mode 100644 vnpy/api/ib/server_versions.py create mode 100644 vnpy/api/ib/softdollartier.py create mode 100644 vnpy/api/ib/tag_value.py create mode 100644 vnpy/api/ib/ticktype.py create mode 100644 vnpy/api/ib/utils.py create mode 100644 vnpy/api/ib/wrapper.py diff --git a/vnpy/api/ib/__init__.py b/vnpy/api/ib/__init__.py new file mode 100644 index 00000000..1a91dea9 --- /dev/null +++ b/vnpy/api/ib/__init__.py @@ -0,0 +1,18 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +""" Package implementing the Python API for the TWS/IB Gateway """ + +VERSION = { + 'major': 9, + 'minor': 73, + 'micro': 7} + + +def get_version_string(): + version = '{major}.{minor}.{micro}'.format(**VERSION) + return version + +__version__ = get_version_string() diff --git a/vnpy/api/ib/account_summary_tags.py b/vnpy/api/ib/account_summary_tags.py new file mode 100644 index 00000000..78ebeca4 --- /dev/null +++ b/vnpy/api/ib/account_summary_tags.py @@ -0,0 +1,47 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +class AccountSummaryTags: + AccountType = "AccountType" + NetLiquidation = "NetLiquidation" + TotalCashValue = "TotalCashValue" + SettledCash = "SettledCash" + AccruedCash = "AccruedCash" + BuyingPower = "BuyingPower" + EquityWithLoanValue = "EquityWithLoanValue" + PreviousEquityWithLoanValue = "PreviousEquityWithLoanValue" + GrossPositionValue = "GrossPositionValue" + ReqTEquity = "ReqTEquity" + ReqTMargin = "ReqTMargin" + SMA = "SMA" + InitMarginReq = "InitMarginReq" + MaintMarginReq = "MaintMarginReq" + AvailableFunds = "AvailableFunds" + ExcessLiquidity = "ExcessLiquidity" + Cushion = "Cushion" + FullInitMarginReq = "FullInitMarginReq" + FullMaintMarginReq = "FullMaintMarginReq" + FullAvailableFunds = "FullAvailableFunds" + FullExcessLiquidity = "FullExcessLiquidity" + LookAheadNextChange = "LookAheadNextChange" + LookAheadInitMarginReq = "LookAheadInitMarginReq" + LookAheadMaintMarginReq = "LookAheadMaintMarginReq" + LookAheadAvailableFunds = "LookAheadAvailableFunds" + LookAheadExcessLiquidity = "LookAheadExcessLiquidity" + HighestSeverity = "HighestSeverity" + DayTradesRemaining = "DayTradesRemaining" + Leverage = "Leverage" + + AllTags = ",".join((AccountType, NetLiquidation, TotalCashValue, + SettledCash, AccruedCash, BuyingPower, EquityWithLoanValue, + PreviousEquityWithLoanValue, GrossPositionValue, ReqTEquity, + ReqTMargin, SMA, InitMarginReq, MaintMarginReq, AvailableFunds, + ExcessLiquidity , Cushion, FullInitMarginReq, FullMaintMarginReq, + FullAvailableFunds, FullExcessLiquidity, + LookAheadNextChange, LookAheadInitMarginReq, LookAheadMaintMarginReq, + LookAheadAvailableFunds, LookAheadExcessLiquidity, HighestSeverity, + DayTradesRemaining, Leverage)) + + diff --git a/vnpy/api/ib/client.py b/vnpy/api/ib/client.py new file mode 100644 index 00000000..6a23c42f --- /dev/null +++ b/vnpy/api/ib/client.py @@ -0,0 +1,3269 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" +import time + + +""" +The main class to use from API user's point of view. +It takes care of almost everything: +- implementing the requests +- creating the answer decoder +- creating the connection to TWS/IBGW +The user just needs to override EWrapper methods to receive the answers. +""" + +import logging +import queue +import socket + +from ibapi import (decoder, reader, comm) +from ibapi.connection import Connection +from ibapi.message import OUT +from ibapi.common import * # @UnusedWildImport +from ibapi.contract import Contract +from ibapi.order import Order +from ibapi.execution import ExecutionFilter +from ibapi.scanner import ScannerSubscription +from ibapi.comm import (make_field, make_field_handle_empty) +from ibapi.utils import (current_fn_name, BadMessage) +from ibapi.errors import * #@UnusedWildImport +from ibapi.server_versions import * # @UnusedWildImport + +#TODO: use pylint + +logger = logging.getLogger(__name__) + + +class EClient(object): + (DISCONNECTED, CONNECTING, CONNECTED, REDIRECT) = range(4) + + #TODO: support redirect !! + + def __init__(self, wrapper): + self.msg_queue = queue.Queue() + self.wrapper = wrapper + self.decoder = None + self.reset() + + + def reset(self): + self.done = False + self.nKeybIntHard = 0 + self.conn = None + self.host = None + self.port = None + self.extraAuth = False + self.clientId = None + self.serverVersion_ = None + self.connTime = None + self.connState = None + self.optCapab = "" + self.asynchronous = False + self.reader = None + self.decode = None + self.setConnState(EClient.DISCONNECTED) + + + def setConnState(self, connState): + _connState = self.connState + self.connState = connState + logger.debug("%s connState: %s -> %s" % (id(self), _connState, + self.connState)) + + def sendMsg(self, msg): + full_msg = comm.make_msg(msg) + logger.info("%s %s %s", "SENDING", current_fn_name(1), full_msg) + self.conn.sendMsg(full_msg) + + + def logRequest(self, fnName, fnParams): + if logger.isEnabledFor(logging.INFO): + if 'self' in fnParams: + prms = dict(fnParams) + del prms['self'] + else: + prms = fnParams + logger.info("REQUEST %s %s" % (fnName, prms)) + + + def startApi(self): + """ Initiates the message exchange between the client application and + the TWS/IB Gateway. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), + NOT_CONNECTED.msg()) + return + + VERSION = 2 + + msg = make_field(OUT.START_API) \ + + make_field(VERSION) \ + + make_field(self.clientId) + + if self.serverVersion() >= MIN_SERVER_VER_OPTIONAL_CAPABILITIES: + msg += make_field(self.optCapab) + + self.sendMsg(msg) + + def connect(self, host, port, clientId): + """This function must be called before any other. There is no + feedback for a successful connection, but a subsequent attempt to + connect will return the message \"Already connected.\" + + host:str - The host name or IP address of the machine where TWS is + running. Leave blank to connect to the local host. + port:int - Must match the port specified in TWS on the + Configure>API>Socket Port field. + clientId:int - A number used to identify this client connection. All + orders placed/modified from this client will be associated with + this client identifier. + + Note: Each client MUST connect with a unique clientId.""" + + + try: + self.host = host + self.port = port + self.clientId = clientId + logger.debug("Connecting to %s:%d w/ id:%d", self.host, self.port, self.clientId) + + self.conn = Connection(self.host, self.port) + + self.conn.connect() + self.setConnState(EClient.CONNECTING) + + #TODO: support async mode + + v100prefix = "API\0" + v100version = "v%d..%d" % (MIN_CLIENT_VER, MAX_CLIENT_VER) + #v100version = "v%d..%d" % (MIN_CLIENT_VER, 101) + msg = comm.make_msg(v100version) + logger.debug("msg %s", msg) + msg2 = str.encode(v100prefix, 'ascii') + msg + logger.debug("REQUEST %s", msg2) + self.conn.sendMsg(msg2) + + self.decoder = decoder.Decoder(self.wrapper, self.serverVersion()) + fields = [] + + #sometimes I get news before the server version, thus the loop + while len(fields) != 2: + self.decoder.interpret(fields) + buf = self.conn.recvMsg() + logger.debug("ANSWER %s", buf) + if len(buf) > 0: + (size, msg, rest) = comm.read_msg(buf) + logger.debug("size:%d msg:%s rest:%s|", size, msg, rest) + fields = comm.read_fields(msg) + logger.debug("fields %s", fields) + else: + fields = [] + + (server_version, conn_time) = fields + server_version = int(server_version) + logger.debug("ANSWER Version:%d time:%s", server_version, conn_time) + self.connTime = conn_time + self.serverVersion_ = server_version + self.decoder.serverVersion = self.serverVersion() + + self.setConnState(EClient.CONNECTED) + + self.reader = reader.EReader(self.conn, self.msg_queue) + self.reader.start() # start thread + logger.info("sent startApi") + self.startApi() + self.wrapper.connectAck() + except socket.error: + if self.wrapper: + self.wrapper.error(NO_VALID_ID, CONNECT_FAIL.code(), CONNECT_FAIL.msg()) + logger.info("could not connect") + self.disconnect() + self.done = True + + + def disconnect(self): + """Call this function to terminate the connections with TWS. + Calling this function does not cancel orders that have already been + sent.""" + + self.setConnState(EClient.DISCONNECTED) + if self.conn is not None: + logger.info("disconnecting") + self.conn.disconnect() + self.wrapper.connectionClosed() + self.reset() + + + def isConnected(self): + """Call this function to check if there is a connection with TWS""" + + logger.debug("%s isConn: %s" % (id(self), self.connState)) + return EClient.CONNECTED == self.connState + + def keyboardInterrupt(self): + #intended to be overloaded + pass + + def keyboardInterruptHard(self): + self.nKeybIntHard += 1 + if self.nKeybIntHard > 5: + raise SystemExit() + + + def run(self): + """This is the function that has the message loop.""" + timeStart = time.time() + timeOut = 20 + + try: + while not self.done and (self.isConnected() + or not self.msg_queue.empty()): + if time.time() - timeStart > timeOut: # stop application after timeout + self.keyboardInterrupt() + self.keyboardInterruptHard() + + try: + try: + text = self.msg_queue.get(block=True, timeout=0.2) + if len(text) > MAX_MSG_LEN: + self.wrapper.error(NO_VALID_ID, BAD_LENGTH.code(), + "%s:%d:%s" % (BAD_LENGTH.msg(), len(text), text)) + self.disconnect() + break + except queue.Empty: + logger.debug("queue.get: empty") + else: + fields = comm.read_fields(text) + logger.debug("fields %s", fields) + self.decoder.interpret(fields) + except (KeyboardInterrupt, SystemExit): + logger.info("detected KeyboardInterrupt, SystemExit") + self.keyboardInterrupt() + self.keyboardInterruptHard() + except BadMessage: + logger.info("BadMessage") + self.conn.disconnect() + + logger.debug("conn:%d queue.sz:%d", + self.isConnected(), + self.msg_queue.qsize()) + finally: + self.disconnect() + + + def reqCurrentTime(self): + """Asks the current system time on the server side.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), + NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_CURRENT_TIME) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def serverVersion(self): + """Returns the version of the TWS instance to which the API + application is connected.""" + + return self.serverVersion_ + + + def setServerLogLevel(self, logLevel:int): + """The default detail level is ERROR. For more details, see API + Logging.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), + NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.SET_SERVER_LOGLEVEL) \ + + make_field(VERSION) \ + + make_field(logLevel) + + self.sendMsg(msg) + + + def twsConnectionTime(self): + """Returns the time the API application made a connection to TWS.""" + + return self.connTime + + + + + + ########################################################################## + ################## Market Data + ########################################################################## + + + def reqMktData(self, reqId:TickerId, contract:Contract, + genericTickList:str, snapshot:bool, regulatorySnapshot: bool, + mktDataOptions:TagValueList): + """Call this function to request market data. The market data + will be returned by the tickPrice and tickSize events. + + reqId: TickerId - The ticker id. Must be a unique value. When the + market data returns, it will be identified by this tag. This is + also used when canceling the market data. + contract:Contract - This structure contains a description of the + Contractt for which market data is being requested. + genericTickList:str - A commma delimited list of generic tick types. + Tick types can be found in the Generic Tick Types page. + Prefixing w/ 'mdoff' indicates that top mkt data shouldn't tick. + You can specify the news source by postfixing w/ ':. + Example: "mdoff,292:FLY+BRF" + snapshot:bool - Check to return a single snapshot of Market data and + have the market data subscription cancel. Do not enter any + genericTicklist values if you use snapshots. + regulatorySnapshot: bool - With the US Value Snapshot Bundle for stocks, + regulatory snapshots are available for 0.01 USD each. + mktDataOptions:TagValueList - For internal use only. + Use default value XYZ. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), + NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL: + if contract.deltaNeutralContract: + self.wrapper.error(reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support delta-neutral orders.") + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_MKT_DATA_CONID: + if contract.conId > 0: + self.wrapper.error(reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support conId parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support tradingClass parameter in reqMktData.") + return + + VERSION = 11 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_MKT_DATA), + make_field(VERSION), + make_field(reqId)] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_REQ_MKT_DATA_CONID: + flds += [make_field(contract.conId),] + + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), # srv v15 and above + make_field(contract.exchange), + make_field(contract.primaryExchange), # srv v14 and above + make_field(contract.currency), + make_field(contract.localSymbol) ] # srv v2 and above + + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + + # Send combo legs for BAG requests (srv v8 and above) + if contract.secType == "BAG": + comboLegsCount = len(contract.comboLegs) if contract.comboLegs else 0 + flds += [make_field(comboLegsCount),] + for comboLeg in contract.comboLegs: + flds += [make_field(comboLeg.conId), + make_field( comboLeg.ratio), + make_field( comboLeg.action), + make_field( comboLeg.exchange)] + + if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL: + if contract.deltaNeutralContract: + flds += [make_field(True), + make_field(contract.deltaNeutralContract.conId), + make_field(contract.deltaNeutralContract.delta), + make_field(contract.deltaNeutralContract.price)] + else: + flds += [make_field(False),] + + flds += [make_field(genericTickList), # srv v31 and above + make_field(snapshot)] # srv v35 and above + + if self.serverVersion() >= MIN_SERVER_VER_REQ_SMART_COMPONENTS: + flds += [make_field(regulatorySnapshot),] + + # send mktDataOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + #current doc says this part if for "internal use only" -> won't support it + if mktDataOptions: + raise NotImplementedError("not supported") + mktDataOptionsStr = "" + flds += [make_field(mktDataOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + def cancelMktData(self, reqId:TickerId): + """After calling this function, market data for the specified id + will stop flowing. + + reqId: TickerId - The ID that was specified in the call to + reqMktData(). """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 2 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.CANCEL_MKT_DATA), + make_field(VERSION), + make_field(reqId)] + + msg = "".join(flds) + self.sendMsg(msg) + + + def reqMarketDataType(self, marketDataType:int): + """The API can receive frozen market data from Trader + Workstation. Frozen market data is the last data recorded in our system. + During normal trading hours, the API receives real-time market data. If + you use this function, you are telling TWS to automatically switch to + frozen market data after the close. Then, before the opening of the next + trading day, market data will automatically switch back to real-time + market data. + + marketDataType:int - 1 for real-time streaming market data or 2 for + frozen market data""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_MARKET_DATA_TYPE: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support market data type requests.") + return + + VERSION = 1 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_MARKET_DATA_TYPE), + make_field(VERSION), + make_field(marketDataType)] + + msg = "".join(flds) + self.sendMsg(msg) + + def reqSmartComponents(self, reqId: int, bboExchange: str): + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + + if self.serverVersion() < MIN_SERVER_VER_REQ_SMART_COMPONENTS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support smart components request.") + return + + msg = make_field(OUT.REQ_SMART_COMPONENTS) \ + + make_field(reqId) \ + + make_field(bboExchange) + + self.sendMsg(msg) + + def reqMarketRule(self, marketRuleId: int): + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_MARKET_RULES: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support market rule requests.") + return + + msg = make_field(OUT.REQ_MARKET_RULE) \ + + make_field(marketRuleId) + + self.sendMsg(msg) + + def reqTickByTickData(self, reqId: int, contract: Contract, tickType: str, + numberOfTicks: int, ignoreSize: bool): + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tick-by-tick data requests.") + return + + if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support ignoreSize and numberOfTicks parameters " + "in tick-by-tick data requests.") + return + + msg = make_field(OUT.REQ_TICK_BY_TICK_DATA)\ + + make_field(reqId) \ + + make_field(contract.conId) \ + + make_field(contract.symbol) \ + + make_field(contract.secType) \ + + make_field(contract.lastTradeDateOrContractMonth) \ + + make_field(contract.strike) \ + + make_field(contract.right) \ + + make_field(contract.multiplier) \ + + make_field(contract.exchange) \ + + make_field(contract.primaryExchange) \ + + make_field(contract.currency) \ + + make_field(contract.localSymbol) \ + + make_field(contract.tradingClass) \ + + make_field(tickType) + + if self.serverVersion() >= MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE: + msg += make_field(numberOfTicks) \ + + make_field(ignoreSize) + + self.sendMsg(msg) + + def cancelTickByTickData(self, reqId: int): + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tick-by-tick data requests.") + return + + msg = make_field(OUT.CANCEL_TICK_BY_TICK_DATA) \ + + make_field(reqId) + + self.sendMsg(msg) + + ########################################################################## + ################## Options + ########################################################################## + + def calculateImpliedVolatility(self, reqId:TickerId, contract:Contract, + optionPrice:float, underPrice:float, + implVolOptions:TagValueList): + """Call this function to calculate volatility for a supplied + option price and underlying price. Result will be delivered + via EWrapper.tickOptionComputation() + + reqId:TickerId - The request id. + contract:Contract - Describes the contract. + optionPrice:double - The price of the option. + underPrice:double - Price of the underlying.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support calculateImpliedVolatility req.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tradingClass parameter in calculateImpliedVolatility.") + return + + VERSION = 3 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_CALC_IMPLIED_VOLAT), + make_field(VERSION), + make_field(reqId), + # send contract fields + make_field(contract.conId), + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + flds += [ make_field( optionPrice), + make_field( underPrice)] + + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + implVolOptStr = "" + tagValuesCount = len(implVolOptions) if implVolOptions else 0 + if implVolOptions: + for implVolOpt in implVolOptions: + implVolOptStr += str(implVolOpt) + flds += [make_field(tagValuesCount), + make_field(implVolOptStr)] + + msg = "".join(flds) + self.sendMsg(msg) + + + + def cancelCalculateImpliedVolatility(self, reqId:TickerId): + """Call this function to cancel a request to calculate + volatility for a supplied option price and underlying price. + + reqId:TickerId - The request ID. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support calculateImpliedVolatility req.") + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_CALC_IMPLIED_VOLAT) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def calculateOptionPrice(self, reqId:TickerId, contract:Contract, + volatility:float, underPrice:float, + optPrcOptions:TagValueList): + """Call this function to calculate option price and greek values + for a supplied volatility and underlying price. + + reqId:TickerId - The ticker ID. + contract:Contract - Describes the contract. + volatility:double - The volatility. + underPrice:double - Price of the underlying.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support calculateImpliedVolatility req.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tradingClass parameter in calculateImpliedVolatility.") + return + + VERSION = 3 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_CALC_OPTION_PRICE), + make_field(VERSION), + make_field(reqId), + # send contract fields + make_field(contract.conId), + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + flds += [ make_field(volatility), + make_field(underPrice)] + + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + optPrcOptStr = "" + tagValuesCount = len(optPrcOptions) if optPrcOptions else 0 + if optPrcOptions: + for implVolOpt in optPrcOptions: + optPrcOptStr += str(implVolOpt) + flds += [make_field(tagValuesCount), + make_field(optPrcOptStr)] + + msg = "".join(flds) + self.sendMsg(msg) + + + + def cancelCalculateOptionPrice(self, reqId:TickerId): + """Call this function to cancel a request to calculate the option + price and greek values for a supplied volatility and underlying price. + + reqId:TickerId - The request ID. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support calculateImpliedVolatility req.") + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_CALC_OPTION_PRICE) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def exerciseOptions(self, reqId:TickerId, contract:Contract, + exerciseAction:int, exerciseQuantity:int, + account:str, override:int): + """reqId:TickerId - The ticker id. multipleust be a unique value. + contract:Contract - This structure contains a description of the + contract to be exercised + exerciseAction:int - Specifies whether you want the option to lapse + or be exercised. + Values are 1 = exercise, 2 = lapse. + exerciseQuantity:int - The quantity you want to exercise. + account:str - destination account + override:int - Specifies whether your setting will override the system's + natural action. For example, if your action is "exercise" and the + option is not in-the-money, by natural action the option would not + exercise. If you have override set to "yes" the natural action would + be overridden and the out-of-the money option would be exercised. + Values are: 0 = no, 1 = yes.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support conId, multiplier, tradingClass parameter in exerciseOptions.") + return + + VERSION = 2 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.EXERCISE_OPTIONS), + make_field(VERSION), + make_field(reqId)] + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.conId),] + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + flds += [make_field(exerciseAction), + make_field(exerciseQuantity), + make_field(account), + make_field(override)] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Orders + ######################################################################## + + def placeOrder(self, orderId:OrderId , contract:Contract, order:Order): + """Call this function to place an order. The order status will + be returned by the orderStatus event. + + orderId:OrderId - The order id. You must specify a unique value. When the + order START_APItus returns, it will be identified by this tag. + This tag is also used when canceling the order. + contract:Contract - This structure contains a description of the + contract which is being traded. + order:Order - This structure contains the details of tradedhe order. + Note: Each client MUST connect with a unique clientId.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(orderId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL: + if contract.deltaNeutralContract: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support delta-neutral orders.") + return + + if self.serverVersion() < MIN_SERVER_VER_SCALE_ORDERS2: + if order.scaleSubsLevelSize != UNSET_INTEGER: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support Subsequent Level Size for Scale orders.") + return + + if self.serverVersion() < MIN_SERVER_VER_ALGO_ORDERS: + if order.algoStrategy: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support algo orders.") + return + + if self.serverVersion() < MIN_SERVER_VER_NOT_HELD: + if order.notHeld: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support notHeld parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_SEC_ID_TYPE: + if contract.secIdType or contract.secId: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support secIdType and secId parameters.") + return + + if self.serverVersion() < MIN_SERVER_VER_PLACE_ORDER_CONID: + if contract.conId and contract.conId > 0: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support conId parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_SSHORTX: + if order.exemptCode != -1: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support exemptCode parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_SSHORTX: + if contract.comboLegs: + for comboLeg in contract.comboLegs: + if comboLeg.exemptCode != -1: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support exemptCode parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_HEDGE_ORDERS: + if order.hedgeType: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support hedge orders.") + return + + if self.serverVersion() < MIN_SERVER_VER_OPT_OUT_SMART_ROUTING: + if order.optOutSmartRouting: + self.wrapper.error( orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support optOutSmartRouting parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL_CONID: + if order.deltaNeutralConId > 0 \ + or order.deltaNeutralSettlingFirm \ + or order.deltaNeutralClearingAccount \ + or order.deltaNeutralClearingIntent: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support deltaNeutral parameters: ConId, SettlingFirm, ClearingAccount, ClearingIntent.") + return + + if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE: + if order.deltaNeutralOpenClose \ + or order.deltaNeutralShortSale \ + or order.deltaNeutralShortSaleSlot > 0 \ + or order.deltaNeutralDesignatedLocation: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support deltaNeutral parameters: OpenClose, ShortSale, ShortSaleSlot, DesignatedLocation.") + return + + if self.serverVersion() < MIN_SERVER_VER_SCALE_ORDERS3: + if order.scalePriceIncrement > 0 and order.scalePriceIncrement != UNSET_DOUBLE: + if order.scalePriceAdjustValue != UNSET_DOUBLE \ + or order.scalePriceAdjustInterval != UNSET_INTEGER \ + or order.scaleProfitOffset != UNSET_DOUBLE \ + or order.scaleAutoReset \ + or order.scaleInitPosition != UNSET_INTEGER \ + or order.scaleInitFillQty != UNSET_INTEGER \ + or order.scaleRandomPercent: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support Scale order parameters: PriceAdjustValue, PriceAdjustInterval, " + + "ProfitOffset, AutoReset, InitPosition, InitFillQty and RandomPercent") + return + + if self.serverVersion() < MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE and contract.secType == "BAG": + if order.orderComboLegs: + for orderComboLeg in order.orderComboLegs: + if orderComboLeg.price != UNSET_DOUBLE: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support per-leg prices for order combo legs.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRAILING_PERCENT: + if order.trailingPercent != UNSET_DOUBLE: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support trailing percent parameter") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tradingClass parameter in placeOrder.") + return + + if self.serverVersion() < MIN_SERVER_VER_SCALE_TABLE: + if order.scaleTable or order.activeStartTime or order.activeStopTime: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support scaleTable, activeStartTime and activeStopTime parameters") + return + + if self.serverVersion() < MIN_SERVER_VER_ALGO_ID: + if order.algoId: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support algoId parameter") + return + + if self.serverVersion() < MIN_SERVER_VER_ORDER_SOLICITED: + if order.solicited: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support order solicited parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT: + if order.modelCode: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support model code parameter.") + return + + if self.serverVersion() < MIN_SERVER_VER_EXT_OPERATOR: + if order.extOperator: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support ext operator parameter") + return + + if self.serverVersion() < MIN_SERVER_VER_SOFT_DOLLAR_TIER: + if order.softDollarTier.name or order.softDollarTier.val: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support soft dollar tier") + return + + if self.serverVersion() < MIN_SERVER_VER_CASH_QTY: + if order.cashQty: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support cash quantity parameter") + return + + if self.serverVersion() < MIN_SERVER_VER_DECISION_MAKER and (order.mifid2DecisionMaker != "" or order.mifid2DecisionAlgo != ""): + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support MIFID II decision maker parameters") + return + + if self.serverVersion() < MIN_SERVER_VER_MIFID_EXECUTION and (order.mifid2ExecutionTrader != "" or order.mifid2ExecutionAlgo != ""): + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support MIFID II execution parameters") + return + + if self.serverVersion() < MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE and order.dontUseAutoPriceForHedge: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support dontUseAutoPriceForHedge parameter") + return + + if self.serverVersion() < MIN_SERVER_VER_ORDER_CONTAINER and order.isOmsContainer: + self.wrapper.error(orderId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support oms container parameter") + return + + VERSION = 27 if (self.serverVersion() < MIN_SERVER_VER_NOT_HELD) else 45 + + # send place order msg + flds = [] + flds += [make_field(OUT.PLACE_ORDER)] + + if self.serverVersion() < MIN_SERVER_VER_ORDER_CONTAINER: + flds += [make_field(VERSION)] + + flds += [make_field(orderId)] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_PLACE_ORDER_CONID: + flds.append(make_field( contract.conId)) + flds += [make_field( contract.symbol), + make_field( contract.secType), + make_field( contract.lastTradeDateOrContractMonth), + make_field( contract.strike), + make_field( contract.right), + make_field( contract.multiplier), # srv v15 and above + make_field( contract.exchange), + make_field( contract.primaryExchange), # srv v14 and above + make_field( contract.currency), + make_field( contract.localSymbol)] # srv v2 and above + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds.append(make_field( contract.tradingClass)) + + if self.serverVersion() >= MIN_SERVER_VER_SEC_ID_TYPE: + flds += [make_field( contract.secIdType), + make_field( contract.secId)] + + # send main order fields + flds.append(make_field( order.action)) + + if self.serverVersion() >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + flds.append(make_field(order.totalQuantity)) + else: + flds.append(make_field(int(order.totalQuantity))) + + flds.append(make_field(order.orderType)) + if self.serverVersion() < MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE: + flds.append(make_field( + order.lmtPrice if order.lmtPrice != UNSET_DOUBLE else 0)) + else: + flds.append(make_field_handle_empty( order.lmtPrice)) + if self.serverVersion() < MIN_SERVER_VER_TRAILING_PERCENT: + flds.append(make_field( + order.auxPrice if order.auxPrice != UNSET_DOUBLE else 0)) + else: + flds.append(make_field_handle_empty( order.auxPrice)) + + # send extended order fields + flds += [make_field( order.tif), + make_field( order.ocaGroup), + make_field( order.account), + make_field( order.openClose), + make_field( order.origin), + make_field( order.orderRef), + make_field( order.transmit), + make_field( order.parentId), # srv v4 and above + make_field( order.blockOrder), # srv v5 and above + make_field( order.sweepToFill), # srv v5 and above + make_field( order.displaySize), # srv v5 and above + make_field( order.triggerMethod), # srv v5 and above + make_field( order.outsideRth), # srv v5 and above + make_field( order.hidden)] # srv v7 and above + + # Send combo legs for BAG requests (srv v8 and above) + if contract.secType == "BAG": + comboLegsCount = len(contract.comboLegs) if contract.comboLegs else 0 + flds.append(make_field(comboLegsCount)) + if comboLegsCount > 0: + for comboLeg in contract.comboLegs: + assert comboLeg + flds += [make_field(comboLeg.conId), + make_field( comboLeg.ratio), + make_field( comboLeg.action), + make_field( comboLeg.exchange), + make_field( comboLeg.openClose), + make_field( comboLeg.shortSaleSlot), #srv v35 and above + make_field( comboLeg.designatedLocation)] # srv v35 and above + if self.serverVersion() >= MIN_SERVER_VER_SSHORTX_OLD: + flds.append(make_field(comboLeg.exemptCode)) + + # Send order combo legs for BAG requests + if self.serverVersion() >= MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE and contract.secType == "BAG": + orderComboLegsCount = len(order.orderComboLegs) if order.orderComboLegs else 0 + flds.append(make_field( orderComboLegsCount)) + if orderComboLegsCount: + for orderComboLeg in order.orderComboLegs: + assert orderComboLeg + flds.append(make_field_handle_empty( orderComboLeg.price)) + + if self.serverVersion() >= MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS and contract.secType == "BAG": + smartComboRoutingParamsCount = len(order.smartComboRoutingParams) if order.smartComboRoutingParams else 0 + flds.append(make_field( smartComboRoutingParamsCount)) + if smartComboRoutingParamsCount > 0: + for tagValue in order.smartComboRoutingParams: + flds += [make_field(tagValue.tag), + make_field(tagValue.value)] + + ###################################################################### + # Send the shares allocation. + # + # This specifies the number of order shares allocated to each Financial + # Advisor managed account. The format of the allocation string is as + # follows: + # /,/,...N + # E.g. + # To allocate 20 shares of a 100 share order to account 'U101' and the + # residual 80 to account 'U203' enter the following share allocation string: + # U101/20,U203/80 + ##################################################################### + # send deprecated sharesAllocation field + flds += [make_field( ""), # srv v9 and above + + make_field( order.discretionaryAmt), # srv v10 and above + make_field( order.goodAfterTime), # srv v11 and above + make_field( order.goodTillDate), # srv v12 and above + + make_field( order.faGroup), # srv v13 and above + make_field( order.faMethod), # srv v13 and above + make_field( order.faPercentage), # srv v13 and above + make_field( order.faProfile)] # srv v13 and above + + if self.serverVersion() >= MIN_SERVER_VER_MODELS_SUPPORT: + flds.append(make_field( order.modelCode)) + + # institutional short saleslot data (srv v18 and above) + flds += [make_field( order.shortSaleSlot), # 0 for retail, 1 or 2 for institutions + make_field( order.designatedLocation)] # populate only when shortSaleSlot = 2. + if self.serverVersion() >= MIN_SERVER_VER_SSHORTX_OLD: + flds.append(make_field( order.exemptCode)) + + # not needed anymore + #bool isVolOrder = (order.orderType.CompareNoCase("VOL") == 0) + + # srv v19 and above fields + flds.append(make_field( order.ocaType)) + #if( self.serverVersion() < 38) { + # will never happen + # send( /* order.rthOnly */ false); + #} + flds += [make_field( order.rule80A), + make_field( order.settlingFirm), + make_field( order.allOrNone), + make_field_handle_empty( order.minQty), + make_field_handle_empty( order.percentOffset), + make_field( order.eTradeOnly), + make_field( order.firmQuoteOnly), + make_field_handle_empty( order.nbboPriceCap), + make_field( order.auctionStrategy), # AUCTION_MATCH, AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT + make_field_handle_empty( order.startingPrice), + make_field_handle_empty( order.stockRefPrice), + make_field_handle_empty( order.delta), + make_field_handle_empty( order.stockRangeLower), + make_field_handle_empty( order.stockRangeUpper), + + make_field( order.overridePercentageConstraints), #srv v22 and above + + # Volatility orders (srv v26 and above) + make_field_handle_empty( order.volatility), + make_field_handle_empty( order.volatilityType), + make_field( order.deltaNeutralOrderType), # srv v28 and above + make_field_handle_empty( order.deltaNeutralAuxPrice)] # srv v28 and above + + if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL_CONID and order.deltaNeutralOrderType: + flds += [make_field( order.deltaNeutralConId), + make_field( order.deltaNeutralSettlingFirm), + make_field( order.deltaNeutralClearingAccount), + make_field( order.deltaNeutralClearingIntent)] + + if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE and order.deltaNeutralOrderType: + flds += [make_field( order.deltaNeutralOpenClose), + make_field( order.deltaNeutralShortSale), + make_field( order.deltaNeutralShortSaleSlot), + make_field( order.deltaNeutralDesignatedLocation)] + + flds += [make_field( order.continuousUpdate), + make_field_handle_empty( order.referencePriceType), + make_field_handle_empty( order.trailStopPrice)] # srv v30 and above + + if self.serverVersion() >= MIN_SERVER_VER_TRAILING_PERCENT: + flds.append(make_field_handle_empty( order.trailingPercent)) + + # SCALE orders + if self.serverVersion() >= MIN_SERVER_VER_SCALE_ORDERS2: + flds += [make_field_handle_empty( order.scaleInitLevelSize), + make_field_handle_empty( order.scaleSubsLevelSize)] + else: + # srv v35 and above) + flds += [make_field( ""), # for not supported scaleNumComponents + make_field_handle_empty(order.scaleInitLevelSize)] # for scaleComponentSize + + flds.append(make_field_handle_empty( order.scalePriceIncrement)) + + if self.serverVersion() >= MIN_SERVER_VER_SCALE_ORDERS3 \ + and order.scalePriceIncrement != UNSET_DOUBLE \ + and order.scalePriceIncrement > 0.0: + + flds += [make_field_handle_empty( order.scalePriceAdjustValue), + make_field_handle_empty( order.scalePriceAdjustInterval), + make_field_handle_empty( order.scaleProfitOffset), + make_field( order.scaleAutoReset), + make_field_handle_empty( order.scaleInitPosition), + make_field_handle_empty( order.scaleInitFillQty), + make_field( order.scaleRandomPercent)] + + if self.serverVersion() >= MIN_SERVER_VER_SCALE_TABLE: + flds += [make_field( order.scaleTable), + make_field( order.activeStartTime), + make_field( order.activeStopTime)] + + # HEDGE orders + if self.serverVersion() >= MIN_SERVER_VER_HEDGE_ORDERS: + flds.append(make_field( order.hedgeType)) + if order.hedgeType: + flds.append(make_field( order.hedgeParam)) + + if self.serverVersion() >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING: + flds.append(make_field( order.optOutSmartRouting)) + + if self.serverVersion() >= MIN_SERVER_VER_PTA_ORDERS: + flds += [make_field( order.clearingAccount), + make_field( order.clearingIntent)] + + if self.serverVersion() >= MIN_SERVER_VER_NOT_HELD: + flds.append(make_field( order.notHeld)) + + if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL: + if contract.deltaNeutralContract: + flds += [make_field(True), + make_field(contract.deltaNeutralContract.conId), + make_field(contract.deltaNeutralContract.delta), + make_field(contract.deltaNeutralContract.price)] + else: + flds.append(make_field(False)) + + if self.serverVersion() >= MIN_SERVER_VER_ALGO_ORDERS: + flds.append(make_field( order.algoStrategy)) + if order.algoStrategy: + algoParamsCount = len(order.algoParams) if order.algoParams else 0 + flds.append(make_field(algoParamsCount)) + if algoParamsCount > 0: + for algoParam in order.algoParams: + flds += [make_field(algoParam.tag), + make_field(algoParam.value)] + + if self.serverVersion() >= MIN_SERVER_VER_ALGO_ID: + flds.append(make_field( order.algoId)) + + flds.append(make_field( order.whatIf)) # srv v36 and above + + # send miscOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + miscOptionsStr = "" + if order.orderMiscOptions: + for tagValue in order.orderMiscOptions: + miscOptionsStr += str(tagValue) + flds.append(make_field( miscOptionsStr)) + + if self.serverVersion() >= MIN_SERVER_VER_ORDER_SOLICITED: + flds.append(make_field(order.solicited)) + + if self.serverVersion() >= MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE: + flds += [make_field(order.randomizeSize), + make_field(order.randomizePrice)] + + if self.serverVersion() >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK: + if order.orderType == "PEG BENCH": + flds += [make_field(order.referenceContractId), + make_field(order.isPeggedChangeAmountDecrease), + make_field(order.peggedChangeAmount), + make_field(order.referenceChangeAmount), + make_field(order.referenceExchangeId)] + + flds.append(make_field(len(order.conditions))) + + if len(order.conditions) > 0: + for cond in order.conditions: + flds.append(make_field(cond.type())) + flds += cond.make_fields() + + flds += [make_field(order.conditionsIgnoreRth), + make_field(order.conditionsCancelOrder)] + + flds += [make_field(order.adjustedOrderType), + make_field(order.triggerPrice), + make_field(order.lmtPriceOffset), + make_field(order.adjustedStopPrice), + make_field(order.adjustedStopLimitPrice), + make_field(order.adjustedTrailingAmount), + make_field(order.adjustableTrailingUnit)] + + if self.serverVersion() >= MIN_SERVER_VER_EXT_OPERATOR: + flds.append(make_field( order.extOperator)) + + if self.serverVersion() >= MIN_SERVER_VER_SOFT_DOLLAR_TIER: + flds += [make_field(order.softDollarTier.name), + make_field(order.softDollarTier.val)] + + if self.serverVersion() >= MIN_SERVER_VER_CASH_QTY: + flds.append(make_field( order.cashQty)) + + if self.serverVersion() >= MIN_SERVER_VER_DECISION_MAKER: + flds.append(make_field( order.mifid2DecisionMaker)) + flds.append(make_field( order.mifid2DecisionAlgo)) + + if self.serverVersion() >= MIN_SERVER_VER_MIFID_EXECUTION: + flds.append(make_field( order.mifid2ExecutionTrader)) + flds.append(make_field( order.mifid2ExecutionAlgo)) + + if self.serverVersion() >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE: + flds.append(make_field(order.dontUseAutoPriceForHedge)) + + if self.serverVersion() >= MIN_SERVER_VER_ORDER_CONTAINER: + flds.append(make_field(order.isOmsContainer)) + + if self.serverVersion() >= MIN_SERVER_VER_D_PEG_ORDERS: + flds.append(make_field(order.discretionaryUpToLimitPrice)) + + msg = "".join(flds) + self.sendMsg(msg) + + + def cancelOrder(self, orderId:OrderId): + """Call this function to cancel an order. + + orderId:OrderId - The order ID that was specified previously in the call + to placeOrder()""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_ORDER) \ + + make_field(VERSION) \ + + make_field(orderId) + + self.sendMsg(msg) + + + def reqOpenOrders(self): + """Call this function to request the open orders that were + placed from this client. Each open order will be fed back through the + openOrder() and orderStatus() functions on the EWrapper. + + Note: The client with a clientId of 0 will also receive the TWS-owned + open orders. These orders will be associated with the client and a new + orderId will be generated. This association will persist over multiple + API and TWS sessions. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_OPEN_ORDERS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def reqAutoOpenOrders(self, bAutoBind:bool): + """Call this function to request that newly created TWS orders + be implicitly associated with the client. When a new TWS order is + created, the order will be associated with the client, and fed back + through the openOrder() and orderStatus() functions on the EWrapper. + + Note: This request can only be made from a client with clientId of 0. + + bAutoBind: If set to TRUE, newly created TWS orders will be implicitly + associated with the client. If set to FALSE, no association will be + made.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_AUTO_OPEN_ORDERS) \ + + make_field(VERSION) \ + + make_field(bAutoBind) + + self.sendMsg(msg) + + + def reqAllOpenOrders(self): + """Call this function to request the open orders placed from all + clients and also from TWS. Each open order will be fed back through the + openOrder() and orderStatus() functions on the EWrapper. + + Note: No association is made between the returned orders and the + requesting client.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_ALL_OPEN_ORDERS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def reqGlobalCancel(self): + """Use this function to cancel all open orders globally. It + cancels both API and TWS open orders. + + If the order was created in TWS, it also gets canceled. If the order + was initiated in the API, it also gets canceled.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_GLOBAL_CANCEL) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def reqIds(self, numIds:int): + """Call this function to request from TWS the next valid ID that + can be used when placing an order. After calling this function, the + nextValidId() event will be triggered, and the id returned is that next + valid ID. That ID will reflect any autobinding that has occurred (which + generates new IDs and increments the next valid ID therein). + + numIds:int - deprecated""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_IDS) \ + + make_field(VERSION) \ + + make_field(numIds) + + self.sendMsg(msg) + + + + ######################################################################### + ################## Account and Portfolio + ######################################################################## + + def reqAccountUpdates(self, subscribe:bool, acctCode:str): + """Call this function to start getting account values, portfolio, + and last update time information via EWrapper.updateAccountValue(), + EWrapperi.updatePortfolio() and Wrapper.updateAccountTime(). + + subscribe:bool - If set to TRUE, the client will start receiving account + and Portfoliolio updates. If set to FALSE, the client will stop + receiving this information. + acctCode:str -The account code for which to receive account and + portfolio updates.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 2 + + flds = [] + flds += [make_field(OUT.REQ_ACCT_DATA), + make_field(VERSION), + make_field(subscribe), # TRUE = subscribe, FALSE = unsubscribe. + make_field(acctCode)] # srv v9 and above, the account code. This will only be used for FA clients + + msg = "".join(flds) + self.sendMsg(msg) + + + def reqAccountSummary(self, reqId:int, groupName:str, tags:str): + """Call this method to request and keep up to date the data that appears + on the TWS Account Window Summary tab. The data is returned by + accountSummary(). + + Note: This request is designed for an FA managed account but can be + used for any multi-account structure. + + reqId:int - The ID of the data request. Ensures that responses are matched + to requests If several requests are in process. + groupName:str - Set to All to returnrn account summary data for all + accounts, or set to a specific Advisor Account Group name that has + already been created in TWS Global Configuration. + tags:str - A comma-separated list of account tags. Available tags are: + accountountType + NetLiquidation, + TotalCashValue - Total cash including futures pnl + SettledCash - For cash accounts, this is the same as + TotalCashValue + AccruedCash - Net accrued interest + BuyingPower - The maximum amount of marginable US stocks the + account can buy + EquityWithLoanValue - Cash + stocks + bonds + mutual funds + PreviousDayEquityWithLoanValue, + GrossPositionValue - The sum of the absolute value of all stock + and equity option positions + RegTEquity, + RegTMargin, + SMA - Special Memorandum Account + InitMarginReq, + MaintMarginReq, + AvailableFunds, + ExcessLiquidity, + Cushion - Excess liquidity as a percentage of net liquidation value + FullInitMarginReq, + FullMaintMarginReq, + FullAvailableFunds, + FullExcessLiquidity, + LookAheadNextChange - Time when look-ahead values take effect + LookAheadInitMarginReq, + LookAheadMaintMarginReq, + LookAheadAvailableFunds, + LookAheadExcessLiquidity, + HighestSeverity - A measure of how close the account is to liquidation + DayTradesRemaining - The Number of Open/Close trades a user + could put on before Pattern Day Trading is detected. A value of "-1" + means that the user can put on unlimited day trades. + Leverage - GrossPositionValue / NetLiquidation + $LEDGER - Single flag to relay all cash balance tags*, only in base + currency. + $LEDGER:CURRENCY - Single flag to relay all cash balance tags*, only in + the specified currency. + $LEDGER:ALL - Single flag to relay all cash balance tags* in all + currencies.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_ACCOUNT_SUMMARY) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + make_field(groupName) \ + + make_field(tags) + + self.sendMsg(msg) + + + def cancelAccountSummary(self, reqId:int): + """Cancels the request for Account Window Summary tab data. + + reqId:int - The ID of the data request being canceled.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_ACCOUNT_SUMMARY) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def reqPositions(self): + """Requests real-time position data for all accounts.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_POSITIONS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support positions request.") + return + + VERSION = 1 + + msg = make_field(OUT.REQ_POSITIONS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def cancelPositions(self): + """Cancels real-time position updates.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_POSITIONS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support positions request.") + return + + + VERSION = 1 + + msg = make_field(OUT.CANCEL_POSITIONS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + def reqPositionsMulti(self, reqId:int, account:str, modelCode:str): + """Requests positions for account and/or model. + Results are delivered via EWrapper.positionMulti() and + EWrapper.positionMultiEnd() """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support positions multi request.") + return + + VERSION = 1 + + msg = make_field(OUT.REQ_POSITIONS_MULTI) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + make_field(account) \ + + make_field(modelCode) + + self.sendMsg(msg) + + + def cancelPositionsMulti(self, reqId:int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support cancel positions multi request.") + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_POSITIONS_MULTI) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + self.sendMsg(msg) + + + def reqAccountUpdatesMulti(self, reqId: int, account:str, modelCode:str, + ledgerAndNLV:bool): + """Requests account updates for account and/or model.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support account updates multi request.") + return + + VERSION = 1 + + msg = make_field(OUT.REQ_ACCOUNT_UPDATES_MULTI) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + make_field(account) \ + + make_field(modelCode) \ + + make_field(ledgerAndNLV) + + self.sendMsg(msg) + + + def cancelAccountUpdatesMulti(self, reqId:int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support cancel account updates multi request.") + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_ACCOUNT_UPDATES_MULTI) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + self.sendMsg(msg) + + ######################################################################### + ################## Daily PnL + ######################################################################### + + + def reqPnL(self, reqId: int, account: str, modelCode: str): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_PNL: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support PnL request.") + return + + msg = make_field(OUT.REQ_PNL) \ + + make_field(reqId) \ + + make_field(account) \ + + make_field(modelCode) + + self.sendMsg(msg) + + def cancelPnL(self, reqId: int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_PNL: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support PnL request.") + return + + msg = make_field(OUT.CANCEL_PNL) \ + + make_field(reqId) + + self.sendMsg(msg) + + def reqPnLSingle(self, reqId: int, account: str, modelCode: str, conid: int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_PNL: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support PnL request.") + return + + msg = make_field(OUT.REQ_PNL_SINGLE) \ + + make_field(reqId) \ + + make_field(account) \ + + make_field(modelCode) \ + + make_field(conid) + + self.sendMsg(msg) + + def cancelPnLSingle(self, reqId: int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_PNL: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support PnL request.") + return + + msg = make_field(OUT.CANCEL_PNL_SINGLE) \ + + make_field(reqId) + + self.sendMsg(msg) + + ######################################################################### + ################## Executions + ######################################################################### + + + def reqExecutions(self, reqId:int, execFilter:ExecutionFilter): + """When this function is called, the execution reports that meet the + filter criteria are downloaded to the client via the execDetails() + function. To view executions beyond the past 24 hours, open the + Trade Log in TWS and, while the Trade Log is displayed, request + the executions again from the API. + + reqId:int - The ID of the data request. Ensures that responses are + matched to requests if several requests are in process. + execFilter:ExecutionFilter - This object contains attributes that + describe the filter criteria used to determine which execution + reports are returned. + + NOTE: Time format must be 'yyyymmdd-hh:mm:ss' Eg: '20030702-14:55'""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 3 + + # send req open orders msg + flds = [] + flds += [make_field(OUT.REQ_EXECUTIONS), + make_field(VERSION)] + + if self.serverVersion() >= MIN_SERVER_VER_EXECUTION_DATA_CHAIN: + flds += [make_field( reqId),] + + # Send the execution rpt filter data (srv v9 and above) + flds += [make_field( execFilter.clientId), + make_field(execFilter.acctCode), + make_field(execFilter.time), + make_field(execFilter.symbol), + make_field(execFilter.secType), + make_field(execFilter.exchange), + make_field(execFilter.side)] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Contract Details + ######################################################################### + + + def reqContractDetails(self, reqId:int , contract:Contract): + """Call this function to download all details for a particular + underlying. The contract details will be received via the contractDetails() + function on the EWrapper. + + reqId:int - The ID of the data request. Ensures that responses are + make_fieldatched to requests if several requests are in process. + contract:Contract - The summary description of the contract being looked + up.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_SEC_ID_TYPE: + if contract.secIdType or contract.secId: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support secIdType and secId parameters.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support tradingClass parameter in reqContractDetails.") + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + if contract.primaryExchange: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support primaryExchange parameter in reqContractDetails.") + return + + VERSION = 8 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_CONTRACT_DATA), + make_field( VERSION)] + + if self.serverVersion() >= MIN_SERVER_VER_CONTRACT_DATA_CHAIN: + flds += [make_field( reqId),] + + # send contract fields + flds += [make_field(contract.conId), # srv v37 and above + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier)] # srv v15 and above + + if self.serverVersion() >= MIN_SERVER_VER_PRIMARYEXCH: + flds += [make_field(contract.exchange), + make_field(contract.primaryExchange)] + elif self.serverVersion() >= MIN_SERVER_VER_LINKING: + if (contract.primaryExchange and + (contract.exchange == "BEST" or contract.exchange == "SMART")): + flds += [make_field(contract.exchange + ":" + contract.primaryExchange),] + else: + flds += [make_field(contract.exchange),] + + flds += [make_field( contract.currency), + make_field( contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass), ] + flds += [make_field(contract.includeExpired),] # srv v31 and above + + if self.serverVersion() >= MIN_SERVER_VER_SEC_ID_TYPE: + flds += [make_field( contract.secIdType), + make_field( contract.secId)] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Market Depth + ######################################################################### + + def reqMktDepthExchanges(self): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support market depth exchanges request.") + return + + msg = make_field(OUT.REQ_MKT_DEPTH_EXCHANGES) + + self.sendMsg(msg) + + def reqMktDepth(self, reqId:TickerId, contract:Contract, + numRows:int, isSmartDepth:bool, mktDepthOptions:TagValueList): + """Call this function to request market depth for a specific + contract. The market depth will be returned by the updateMktDepth() and + updateMktDepthL2() events. + + Requests the contract's market depth (order book). Note this request must be + direct-routed to an exchange and not smart-routed. The number of simultaneous + market depth requests allowed in an account is calculated based on a formula + that looks at an accounts equity, commissions, and quote booster packs. + + reqId:TickerId - The ticker id. Must be a unique value. When the market + depth data returns, it will be identified by this tag. This is + also used when canceling the market depth + contract:Contact - This structure contains a description of the contract + for which market depth data is being requested. + numRows:int - Specifies the numRowsumber of market depth rows to display. + isSmartDepth:bool - specifies SMART depth request + mktDepthOptions:TagValueList - For internal use only. Use default value + XYZ.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass or contract.conId > 0: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support conId and tradingClass parameters in reqMktDepth.") + return + + if self.serverVersion() < MIN_SERVER_VER_SMART_DEPTH and isSmartDepth: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support SMART depth request.") + return + + VERSION = 5 + + # send req mkt depth msg + flds = [] + flds += [make_field(OUT.REQ_MKT_DEPTH), + make_field(VERSION), + make_field(reqId)] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.conId),] + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), # srv v15 and above + make_field(contract.exchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + + flds += [make_field(numRows),] # srv v19 and above + + if self.serverVersion() >= MIN_SERVER_VER_SMART_DEPTH: + flds += [make_field(isSmartDepth),] + + # send mktDepthOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + #current doc says this part if for "internal use only" -> won't support it + if mktDepthOptions: + raise NotImplementedError("not supported") + mktDataOptionsStr = "" + flds += [make_field(mktDataOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + def cancelMktDepth(self, reqId:TickerId, isSmartDepth:bool): + """After calling this function, market depth data for the specified id + will stop flowing. + + reqId:TickerId - The ID that was specified in the call to + reqMktDepth(). + isSmartDepth:bool - specifies SMART depth request""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_SMART_DEPTH and isSmartDepth: + self.wrapper.error( reqId, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support SMART depth cancel.") + return + + VERSION = 1 + + # send cancel mkt depth msg + flds = [] + flds += [make_field(OUT.CANCEL_MKT_DEPTH), + make_field(VERSION), + make_field(reqId)] + + if self.serverVersion() >= MIN_SERVER_VER_SMART_DEPTH: + flds += [make_field(isSmartDepth)] + + msg = "".join(flds) + + self.sendMsg(msg) + + + ######################################################################### + ################## News Bulletins + ######################################################################### + + def reqNewsBulletins(self, allMsgs:bool): + """Call this function to start receiving news bulletins. Each bulletin + will be returned by the updateNewsBulletin() event. + + allMsgs:bool - If set to TRUE, returns all the existing bulletins for + the currencyent day and any new ones. If set to FALSE, will only + return new bulletins. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_NEWS_BULLETINS) \ + + make_field(VERSION) \ + + make_field(allMsgs) + + self.sendMsg(msg) + + + def cancelNewsBulletins(self): + """Call this function to stop receiving news bulletins.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_NEWS_BULLETINS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + ######################################################################### + ################## Financial Advisors + ######################################################################### + + def reqManagedAccts(self): + """Call this function to request the list of managed accounts. The list + will be returned by the managedAccounts() function on the EWrapper. + + Note: This request can only be made when connected to a FA managed account.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_MANAGED_ACCTS) \ + + make_field(VERSION) + + return self.sendMsg(msg) + + + def requestFA(self, faData:FaDataType): + """Call this function to request FA configuration information from TWS. + The data returns in an XML string via a "receiveFA" ActiveX event. + + faData:FaDataType - Specifies the type of Financial Advisor + configuration data beingingg requested. Valid values include: + 1 = GROUPS + 2 = PROFILE + 3 = ACCOUNT ALIASES""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_FA) \ + + make_field(VERSION) \ + + make_field(int(faData)) + + return self.sendMsg(msg) + + + def replaceFA(self, faData:FaDataType , cxml:str): + """Call this function to modify FA configuration information from the + API. Note that this can also be done manually in TWS itself. + + faData:FaDataType - Specifies the type of Financial Advisor + configuration data beingingg requested. Valid values include: + 1 = GROUPS + 2 = PROFILE + 3 = ACCOUNT ALIASES + cxml: str - The XML string containing the new FA configuration + information. """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REPLACE_FA) \ + + make_field(VERSION) \ + + make_field(int(faData)) \ + + make_field(cxml) \ + + return self.sendMsg(msg) + + + ######################################################################### + ################## Historical Data + ######################################################################### + + def reqHistoricalData(self, reqId:TickerId , contract:Contract, endDateTime:str, + durationStr:str, barSizeSetting:str, whatToShow:str, + useRTH:int, formatDate:int, keepUpToDate:bool, chartOptions:TagValueList): + """Requests contracts' historical data. When requesting historical data, a + finishing time and date is required along with a duration string. The + resulting bars will be returned in EWrapper.historicalData() + + reqId:TickerId - The id of the request. Must be a unique value. When the + market data returns, it whatToShowill be identified by this tag. This is also + used when canceling the market data. + contract:Contract - This object contains a description of the contract for which + market data is being requested. + endDateTime:str - Defines a query end date and time at any point during the past 6 mos. + Valid values include any date/time within the past six months in the format: + yyyymmdd HH:mm:ss ttt + + where "ttt" is the optional time zone. + durationStr:str - Set the query duration up to one week, using a time unit + of seconds, days or weeks. Valid values include any integer followed by a space + and then S (seconds), D (days) or W (week). If no unit is specified, seconds is used. + barSizeSetting:str - Specifies the size of the bars that will be returned (within IB/TWS listimits). + Valid values include: + 1 sec + 5 secs + 15 secs + 30 secs + 1 min + 2 mins + 3 mins + 5 mins + 15 mins + 30 mins + 1 hour + 1 day + whatToShow:str - Determines the nature of data beinging extracted. Valid values include: + + TRADES + MIDPOINT + BID + ASK + BID_ASK + HISTORICAL_VOLATILITY + OPTION_IMPLIED_VOLATILITY + useRTH:int - Determines whether to return all data available during the requested time span, + or only data that falls within regular trading hours. Valid values include: + + 0 - all data is returned even where the market in question was outside of its + regular trading hours. + 1 - only data within the regular trading hours is returned, even if the + requested time span falls partially or completely outside of the RTH. + formatDate: int - Determines the date format applied to returned bars. validd values include: + + 1 - dates applying to bars returned in the format: yyyymmdd{space}{space}hh:mm:dd + 2 - dates are returned as a long integer specifying the number of seconds since + 1/1/1970 GMT. + chartOptions:TagValueList - For internal use only. Use default value XYZ. """ + + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), + NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass or contract.conId > 0: + self.wrapper.error(reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support conId and tradingClass parameters in reqHistoricalData.") + return + + VERSION = 6 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.REQ_HISTORICAL_DATA),] + + if self.serverVersion() < MIN_SERVER_VER_SYNT_REALTIME_BARS: + flds += [make_field(VERSION),] + + flds += [make_field(reqId),] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.conId),] + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field( contract.tradingClass),] + flds += [make_field(contract.includeExpired), # srv v31 and above + make_field(endDateTime), # srv v20 and above + make_field(barSizeSetting), # srv v20 and above + make_field(durationStr), + make_field(useRTH), + make_field(whatToShow), + make_field(formatDate)] # srv v16 and above + + # Send combo legs for BAG requests + if contract.secType == "BAG": + flds += [make_field(len(contract.comboLegs)),] + for comboLeg in contract.comboLegs: + flds += [make_field( comboLeg.conId), + make_field( comboLeg.ratio), + make_field( comboLeg.action), + make_field( comboLeg.exchange)] + + if self.serverVersion() >= MIN_SERVER_VER_SYNT_REALTIME_BARS: + flds += [make_field(keepUpToDate), ] + + # send chartOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + chartOptionsStr = "" + if chartOptions: + for tagValue in chartOptions: + chartOptionsStr += str(tagValue) + flds += [make_field( chartOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + def cancelHistoricalData(self, reqId:TickerId): + """Used if an internet disconnect has occurred or the results of a query + are otherwise delayed and the application is no longer interested in receiving + the data. + + reqId:TickerId - The ticker ID. Must be a unique value.""" + + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_HISTORICAL_DATA) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + # Note that formatData parameter affects intraday bars only + # 1-day bars always return with date in YYYYMMDD format + + def reqHeadTimeStamp(self, reqId:TickerId, contract:Contract, + whatToShow: str, useRTH: int, formatDate: int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_HEAD_TIMESTAMP: + self.wrapper.error(reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support head time stamp requests.") + return + + flds = [] + flds += [make_field(OUT.REQ_HEAD_TIMESTAMP), + make_field(reqId), + make_field(contract.conId), + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol), + make_field(contract.tradingClass), + make_field(contract.includeExpired), + make_field(useRTH), + make_field(whatToShow), + make_field(formatDate) ] + + msg = "".join(flds) + self.sendMsg(msg) + + def cancelHeadTimeStamp(self, reqId: TickerId): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_CANCEL_HEADTIMESTAMP: + self.wrapper.error(reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support head time stamp requests.") + return + + flds = [] + flds += [make_field(OUT.CANCEL_HEAD_TIMESTAMP), + make_field(reqId) ] + + msg = "".join(flds) + self.sendMsg(msg) + + def reqHistogramData(self, tickerId: int, contract: Contract, + useRTH: bool, timePeriod: str): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_HISTOGRAM: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support histogram requests..") + return + + flds = [] + flds += [make_field(OUT.REQ_HISTOGRAM_DATA), + make_field(tickerId), + make_field(contract.conId), + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol), + make_field(contract.tradingClass), + make_field(contract.includeExpired), + make_field(useRTH), + make_field(timePeriod)] + + msg = "".join(flds) + self.sendMsg(msg) + + def cancelHistogramData(self, tickerId: int): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_HISTOGRAM: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support histogram requests..") + return + + msg = make_field(OUT.CANCEL_HISTOGRAM_DATA) + make_field(tickerId) + + self.sendMsg(msg) + + def reqHistoricalTicks(self, reqId: int, contract: Contract, startDateTime: str, + endDateTime: str, numberOfTicks: int, whatToShow: str, useRth: int, + ignoreSize: bool, miscOptions: TagValueList): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_HISTORICAL_TICKS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support historical ticks requests..") + return + + flds = [] + flds += [make_field(OUT.REQ_HISTORICAL_TICKS), + make_field(reqId), + make_field(contract.conId), + make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol), + make_field(contract.tradingClass), + make_field(contract.includeExpired), + make_field(startDateTime), + make_field(endDateTime), + make_field(numberOfTicks), + make_field(whatToShow), + make_field(useRth), + make_field(ignoreSize)] + + miscOptionsString = "" + if miscOptions: + for tagValue in miscOptions: + miscOptionsString += str(tagValue) + flds += [make_field(miscOptionsString),] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Market Scanners + ######################################################################### + + + def reqScannerParameters(self): + """Requests an XML string that describes all possible scanner queries.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.REQ_SCANNER_PARAMETERS) \ + + make_field(VERSION) + + self.sendMsg(msg) + + + + def reqScannerSubscription(self, reqId:int, + subscription:ScannerSubscription, + scannerSubscriptionOptions:TagValueList, + scannerSubscriptionFilterOptions:TagValueList): + """reqId:int - The ticker ID. Must be a unique value. + scannerSubscription:ScannerSubscription - This structure contains + possible parameters used to filter results. + scannerSubscriptionOptions:TagValueList - For internal use only. + Use default value XYZ.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_SCANNER_GENERIC_OPTS and scannerSubscriptionFilterOptions is not None: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support API scanner subscription generic filter options") + return + + VERSION = 4 + + flds = [] + flds += [make_field(OUT.REQ_SCANNER_SUBSCRIPTION)] + + if self.serverVersion() < MIN_SERVER_VER_SCANNER_GENERIC_OPTS: + flds += [make_field(VERSION)] + + flds +=[make_field(reqId), + make_field_handle_empty(subscription.numberOfRows), + make_field(subscription.instrument), + make_field(subscription.locationCode), + make_field(subscription.scanCode), + make_field_handle_empty(subscription.abovePrice), + make_field_handle_empty(subscription.belowPrice), + make_field_handle_empty(subscription.aboveVolume), + make_field_handle_empty(subscription.marketCapAbove), + make_field_handle_empty(subscription.marketCapBelow), + make_field(subscription.moodyRatingAbove), + make_field(subscription.moodyRatingBelow), + make_field(subscription.spRatingAbove), + make_field(subscription.spRatingBelow), + make_field(subscription.maturityDateAbove), + make_field(subscription.maturityDateBelow), + make_field_handle_empty(subscription.couponRateAbove), + make_field_handle_empty(subscription.couponRateBelow), + make_field(subscription.excludeConvertible), + make_field_handle_empty(subscription.averageOptionVolumeAbove), # srv v25 and above + make_field(subscription.scannerSettingPairs), # srv v25 and above + make_field(subscription.stockTypeFilter)] # srv v27 and above + + # send scannerSubscriptionFilterOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_SCANNER_GENERIC_OPTS: + scannerSubscriptionFilterOptionsStr = "" + if scannerSubscriptionFilterOptions: + for tagValueOpt in scannerSubscriptionFilterOptions: + scannerSubscriptionFilterOptionsStr += str(tagValueOpt) + flds += [make_field(scannerSubscriptionFilterOptionsStr)] + + # send scannerSubscriptionOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + scannerSubscriptionOptionsStr = "" + if scannerSubscriptionOptions: + for tagValueOpt in scannerSubscriptionOptions: + scannerSubscriptionOptionsStr += str(tagValueOpt) + flds += [make_field(scannerSubscriptionOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + + def cancelScannerSubscription(self, reqId:int): + """reqId:int - The ticker ID. Must be a unique value.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_SCANNER_SUBSCRIPTION) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + ######################################################################### + ################## Real Time Bars + ######################################################################### + + + def reqRealTimeBars(self, reqId:TickerId, contract:Contract, barSize:int, + whatToShow:str, useRTH:bool, + realTimeBarsOptions:TagValueList): + """Call the reqRealTimeBars() function to start receiving real time bar + results through the realtimeBar() EWrapper function. + + reqId:TickerId - The Id for the request. Must be a unique value. When the + data is received, it will be identified by this Id. This is also + used when canceling the request. + contract:Contract - This object contains a description of the contract + for which real time bars are being requested + barSize:int - Currently only 5 second bars are supported, if any other + value is used, an exception will be thrown. + whatToShow:str - Determines the nature of the data extracted. Valid + values include: + TRADES + BID + ASK + MIDPOINT + useRTH:bool - Regular Trading Hours only. Valid values include: + 0 = all data available during the time span requested is returned, + including time intervals when the market in question was + outside of regular trading hours. + 1 = only data within the regular trading hours for the product + requested is returned, even if the time time span falls + partially or completely outside. + realTimeBarOptions:TagValueList - For internal use only. Use default value XYZ.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + if contract.tradingClass: + self.wrapper.error( reqId, UPDATE_TWS.code(), + UPDATE_TWS.msg() + " It does not support conId and tradingClass parameter in reqRealTimeBars.") + return + + VERSION = 3 + + flds = [] + flds += [make_field(OUT.REQ_REAL_TIME_BARS), + make_field(VERSION), + make_field(reqId)] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.conId),] + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.lastTradeDateOrContractMonth), + make_field(contract.strike), + make_field(contract.right), + make_field(contract.multiplier), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol)] + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field(contract.tradingClass),] + flds += [make_field(barSize), + make_field(whatToShow), + make_field(useRTH)] + + # send realTimeBarsOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + realTimeBarsOptionsStr = "" + if realTimeBarsOptions: + for tagValueOpt in realTimeBarsOptions: + realTimeBarsOptionsStr += str(tagValueOpt) + flds += [make_field(realTimeBarsOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + def cancelRealTimeBars(self, reqId:TickerId): + """Call the cancelRealTimeBars() function to stop receiving real time bar results. + + reqId:TickerId - The Id that was specified in the call to reqRealTimeBars(). """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(reqId, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 1 + + # send req mkt data msg + flds = [] + flds += [make_field(OUT.CANCEL_REAL_TIME_BARS), + make_field(VERSION), + make_field(reqId)] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Fundamental Data + ######################################################################### + + + def reqFundamentalData(self, reqId:TickerId , contract:Contract, + reportType:str, fundamentalDataOptions:TagValueList): + """Call this function to receive fundamental data for + stocks. The appropriate market data subscription must be set up in + Account Management before you can receive this data. + Fundamental data will be returned at EWrapper.fundamentalData(). + + reqFundamentalData() can handle conid specified in the Contract object, + but not tradingClass or multiplier. This is because reqFundamentalData() + is used only for stocks and stocks do not have a multiplier and + trading class. + + reqId:tickerId - The ID of the data request. Ensures that responses are + matched to requests if several requests are in process. + contract:Contract - This structure contains a description of the + contract for which fundamental data is being requested. + reportType:str - One of the following XML reports: + ReportSnapshot (company overview) + ReportsFinSummary (financial summary) + ReportRatios (financial ratios) + ReportsFinStatements (financial statements) + RESC (analyst estimates) + CalendarReport (company calendar) """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + VERSION = 2 + + if self.serverVersion() < MIN_SERVER_VER_FUNDAMENTAL_DATA: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support fundamental data request.") + return + + if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support conId parameter in reqFundamentalData.") + return + + flds = [] + flds += [make_field(OUT.REQ_FUNDAMENTAL_DATA), + make_field(VERSION), + make_field(reqId)] + + # send contract fields + if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS: + flds += [make_field( contract.conId),] + flds += [make_field(contract.symbol), + make_field(contract.secType), + make_field(contract.exchange), + make_field(contract.primaryExchange), + make_field(contract.currency), + make_field(contract.localSymbol), + make_field(reportType)] + + if self.serverVersion() >= MIN_SERVER_VER_LINKING: + fundDataOptStr = "" + tagValuesCount = len(fundamentalDataOptions) if fundamentalDataOptions else 0 + if fundamentalDataOptions: + for fundDataOption in fundamentalDataOptions: + fundDataOptStr += str(fundDataOption) + flds += [make_field(tagValuesCount), + make_field(fundDataOptStr)] + + msg = "".join(flds) + self.sendMsg(msg) + + + def cancelFundamentalData(self, reqId:TickerId ): + """Call this function to stop receiving fundamental data. + + reqId:TickerId - The ID of the data request.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_FUNDAMENTAL_DATA: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support fundamental data request.") + return + + VERSION = 1 + + msg = make_field(OUT.CANCEL_FUNDAMENTAL_DATA) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + ######################################################################## + ################## News + ######################################################################### + + def reqNewsProviders(self): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + + if self.serverVersion() < MIN_SERVER_VER_REQ_NEWS_PROVIDERS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support news providers request.") + return + + msg = make_field(OUT.REQ_NEWS_PROVIDERS) + + self.sendMsg(msg) + + + def reqNewsArticle(self, reqId: int, providerCode: str, articleId: str, newsArticleOptions: TagValueList): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_NEWS_ARTICLE: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support news article request.") + return + + flds = [] + + flds += [make_field(OUT.REQ_NEWS_ARTICLE), + make_field(reqId), + make_field(providerCode), + make_field(articleId)] + + # send newsArticleOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS: + newsArticleOptionsStr = "" + if newsArticleOptions: + for tagValue in newsArticleOptions: + newsArticleOptionsStr += str(tagValue) + flds += [make_field(newsArticleOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + def reqHistoricalNews(self, reqId: int, conId: int, providerCodes: str, + startDateTime: str, endDateTime: str, totalResults: int, historicalNewsOptions: TagValueList): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_HISTORICAL_NEWS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support historical news request.") + return + + flds = [] + + flds += [make_field(OUT.REQ_HISTORICAL_NEWS), + make_field(reqId), + make_field(conId), + make_field(providerCodes), + make_field(startDateTime), + make_field(endDateTime), + make_field(totalResults)] + + # send historicalNewsOptions parameter + if self.serverVersion() >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS: + historicalNewsOptionsStr = "" + if historicalNewsOptions: + for tagValue in historicalNewsOptionsStr: + historicalNewsOptionsStr += str(tagValue) + flds += [make_field(historicalNewsOptionsStr),] + + msg = "".join(flds) + self.sendMsg(msg) + + + ######################################################################### + ################## Display Groups + ######################################################################### + + + def queryDisplayGroups(self, reqId: int): + """API requests used to integrate with TWS color-grouped windows (display groups). + TWS color-grouped windows are identified by an integer number. Currently that number ranges from 1 to 7 and are mapped to specific colors, as indicated in TWS. + + reqId:int - The unique number that will be associated with the + response """ + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support queryDisplayGroups request.") + return + + VERSION = 1 + + msg = make_field(OUT.QUERY_DISPLAY_GROUPS) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def subscribeToGroupEvents(self, reqId:int, groupId:int): + """reqId:int - The unique number associated with the notification. + groupId:int - The ID of the group, currently it is a number from 1 to 7. + This is the display group subscription request sent by the API to TWS.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support subscribeToGroupEvents request.") + return + + VERSION = 1 + + msg = make_field(OUT.SUBSCRIBE_TO_GROUP_EVENTS) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + make_field(groupId) + + self.sendMsg(msg) + + + def updateDisplayGroup(self, reqId:int, contractInfo:str): + """reqId:int - The requestId specified in subscribeToGroupEvents(). + contractInfo:str - The encoded value that uniquely represents the + contract in IB. Possible values include: + + none = empty selection + contractID@exchange - any non-combination contract. + Examples: 8314@SMART for IBM SMART; 8314@ARCA for IBM @ARCA. + combo = if any combo is selected.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support updateDisplayGroup request.") + return + + VERSION = 1 + + msg = make_field(OUT.UPDATE_DISPLAY_GROUP) \ + + make_field(VERSION) \ + + make_field(reqId) \ + + make_field(contractInfo) + + self.sendMsg(msg) + + + def unsubscribeFromGroupEvents(self, reqId:int): + """reqId:int - The requestId specified in subscribeToGroupEvents().""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support unsubscribeFromGroupEvents request.") + return + + VERSION = 1 + + msg = make_field(OUT.UNSUBSCRIBE_FROM_GROUP_EVENTS) \ + + make_field(VERSION) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def verifyRequest(self, apiName:str, apiVersion:str): + """For IB's internal purpose. Allows to provide means of verification + between the TWS and third party programs.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support verification request.") + return + + if not self.extraAuth: + self.wrapper.error(NO_VALID_ID, BAD_MESSAGE.code(), BAD_MESSAGE.msg() + + " Intent to authenticate needs to be expressed during initial connect request.") + return + + VERSION = 1 + + msg = make_field(OUT.VERIFY_REQUEST) \ + + make_field(VERSION) \ + + make_field(apiName) \ + + make_field(apiVersion) + + self.sendMsg(msg) + + + def verifyMessage(self, apiData:str): + """For IB's internal purpose. Allows to provide means of verification + between the TWS and third party programs.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support verification request.") + return + + VERSION = 1 + + msg = make_field(OUT.VERIFY_MESSAGE) \ + + make_field(VERSION) \ + + make_field(apiData) + + self.sendMsg(msg) + + + def verifyAndAuthRequest(self, apiName:str, apiVersion:str, + opaqueIsvKey:str): + """For IB's internal purpose. Allows to provide means of verification + between the TWS and third party programs.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support verification request.") + return + + if not self.extraAuth: + self.wrapper.error(NO_VALID_ID, BAD_MESSAGE.code(), BAD_MESSAGE.msg() + + " Intent to authenticate needs to be expressed during initial connect request.") + return + + VERSION = 1 + + msg = make_field(OUT.VERIFY_AND_AUTH_REQUEST) \ + + make_field(VERSION) \ + + make_field(apiName) \ + + make_field(apiVersion) \ + + make_field(opaqueIsvKey) + + self.sendMsg(msg) + + + def verifyAndAuthMessage(self, apiData:str, xyzResponse:str): + """For IB's internal purpose. Allows to provide means of verification + between the TWS and third party programs.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_LINKING: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support verification request.") + return + + VERSION = 1 + + msg = make_field(OUT.VERIFY_AND_AUTH_MESSAGE) \ + + make_field(VERSION) \ + + make_field(apiData) \ + + make_field(xyzResponse) + + self.sendMsg(msg) + + + def reqSecDefOptParams(self, reqId:int, underlyingSymbol:str, + futFopExchange:str, underlyingSecType:str, + underlyingConId:int): + """Requests security definition option parameters for viewing a + contract's option chain reqId the ID chosen for the request + underlyingSymbol futFopExchange The exchange on which the returned + options are trading. Can be set to the empty string "" for all + exchanges. underlyingSecType The type of the underlying security, + i.e. STK underlyingConId the contract ID of the underlying security. + Response comes via EWrapper.securityDefinitionOptionParameter()""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support security definition option request.") + return + + flds = [] + flds += [make_field(OUT.REQ_SEC_DEF_OPT_PARAMS), + make_field(reqId), + make_field(underlyingSymbol), + make_field(futFopExchange), + make_field(underlyingSecType), + make_field(underlyingConId)] + + msg = "".join(flds) + self.sendMsg(msg) + + + def reqSoftDollarTiers(self, reqId:int): + """Requests pre-defined Soft Dollar Tiers. This is only supported for + registered professional advisors and hedge and mutual funds who have + configured Soft Dollar Tiers in Account Management.""" + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + msg = make_field(OUT.REQ_SOFT_DOLLAR_TIERS) \ + + make_field(reqId) + + self.sendMsg(msg) + + + def reqFamilyCodes(self): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_FAMILY_CODES: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support family codes request.") + return + + msg = make_field(OUT.REQ_FAMILY_CODES) + + self.sendMsg(msg) + + + def reqMatchingSymbols(self, reqId:int, pattern:str): + + self.logRequest(current_fn_name(), vars()) + + if not self.isConnected(): + self.wrapper.error(NO_VALID_ID, NOT_CONNECTED.code(), NOT_CONNECTED.msg()) + return + + if self.serverVersion() < MIN_SERVER_VER_REQ_MATCHING_SYMBOLS: + self.wrapper.error(NO_VALID_ID, UPDATE_TWS.code(), UPDATE_TWS.msg() + + " It does not support matching symbols request.") + return + + msg = make_field(OUT.REQ_MATCHING_SYMBOLS) \ + + make_field(reqId) \ + + make_field(pattern) + + self.sendMsg(msg) + + diff --git a/vnpy/api/ib/comm.py b/vnpy/api/ib/comm.py new file mode 100644 index 00000000..ebfbf508 --- /dev/null +++ b/vnpy/api/ib/comm.py @@ -0,0 +1,75 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +This module has tools for implementing the IB low level messaging. +""" + + +import struct +import logging + +from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE + +logger = logging.getLogger(__name__) + + +def make_msg(text) -> bytes: + """ adds the length prefix """ + msg = struct.pack("!I%ds" % len(text), len(text), str.encode(text)) + return msg + + +def make_field(val) -> str: + """ adds the NULL string terminator """ + + if val is None: + raise ValueError("Cannot send None to TWS") + + # bool type is encoded as int + if type(val) is bool: + val = int(val) + + field = str(val) + '\0' + return field + + +def make_field_handle_empty(val) -> str: + + if val is None: + raise ValueError("Cannot send None to TWS") + + if UNSET_INTEGER == val or UNSET_DOUBLE == val: + val = "" + + return make_field(val) + + +def read_msg(buf:bytes) -> tuple: + """ first the size prefix and then the corresponding msg payload """ + if len(buf) < 4: + return (0, "", buf) + size = struct.unpack("!I", buf[0:4])[0] + logger.debug("read_msg: size: %d", size) + if len(buf) - 4 >= size: + text = struct.unpack("!%ds" % size, buf[4:4+size])[0] + return (size, text, buf[4+size:]) + else: + return (size, "", buf) + + +def read_fields(buf:bytes) -> tuple: + + if isinstance(buf, str): + buf = buf.encode() + + """ msg payload is made of fields terminated/separated by NULL chars """ + fields = buf.split(b"\0") + + return tuple(fields[0:-1]) #last one is empty; this may slow dow things though, TODO + + + diff --git a/vnpy/api/ib/commission_report.py b/vnpy/api/ib/commission_report.py new file mode 100644 index 00000000..74e6d1b7 --- /dev/null +++ b/vnpy/api/ib/commission_report.py @@ -0,0 +1,21 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +from ibapi.object_implem import Object +from ibapi import utils + +class CommissionReport(Object): + + def __init__(self): + self.execId = "" + self.commission = 0. + self.currency = "" + self.realizedPNL = 0. + self.yield_ = 0. + self.yieldRedemptionDate = 0 # YYYYMMDD format + + def __str__(self): + return "ExecId: %s, Commission: %f, Currency: %s, RealizedPnL: %s, Yield: %s, YieldRedemptionDate: %d" % (self.execId, self.commission, + self.currency, utils.floatToStr(self.realizedPNL), utils.floatToStr(self.yield_), self.yieldRedemptionDate) diff --git a/vnpy/api/ib/common.py b/vnpy/api/ib/common.py new file mode 100644 index 00000000..cc70558c --- /dev/null +++ b/vnpy/api/ib/common.py @@ -0,0 +1,190 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +import sys + +from ibapi.enum_implem import Enum +from ibapi.object_implem import Object + + +NO_VALID_ID = -1 +MAX_MSG_LEN = 0xFFFFFF # 16Mb - 1byte + +UNSET_INTEGER = 2 ** 31 - 1 +UNSET_DOUBLE = sys.float_info.max + +TickerId = int +OrderId = int +TagValueList = list + +FaDataType = int +FaDataTypeEnum = Enum("N/A", "GROUPS", "PROFILES", "ALIASES") + +MarketDataType = int +MarketDataTypeEnum = Enum("N/A", "REALTIME", "FROZEN", "DELAYED", "DELAYED_FROZEN") + +Liquidities = int +LiquiditiesEnum = Enum("None", "Added", "Remove", "RoudedOut") + +SetOfString = set +SetOfFloat = set +ListOfOrder = list +ListOfFamilyCode = list +ListOfContractDescription = list +ListOfDepthExchanges = list +ListOfNewsProviders = list +SmartComponentMap = dict +HistogramDataList = list +ListOfPriceIncrements = list +ListOfHistoricalTick = list +ListOfHistoricalTickBidAsk = list +ListOfHistoricalTickLast = list + +class BarData(Object): + def __init__(self): + self.date = "" + self.open = 0. + self.high = 0. + self.low = 0. + self.close = 0. + self.volume = 0 + self.barCount = 0 + self.average = 0. + + def __str__(self): + return "Date: %s, Open: %f, High: %f, Low: %f, Close: %f, Volume: %d, Average: %f, BarCount: %d" % (self.date, self.open, self.high, + self.low, self.close, self.volume, self.average, self.barCount) + +class RealTimeBar(Object): + def __init__(self, time = 0, endTime = -1, open_ = 0., high = 0., low = 0., close = 0., volume = 0., wap = 0., count = 0): + self.time = time + self.endTime = endTime + self.open_ = open_ + self.high = high + self.low = low + self.close = close + self.volume = volume + self.wap = wap + self.count = count + + def __str__(self): + return "Time: %d, Open: %f, High: %f, Low: %f, Close: %f, Volume: %d, WAP: %f, Count: %d" % (self.time, self.open_, self.high, + self.low, self.close, self.volume, self.wap, self.count) + +class HistogramData(Object): + def __init__(self): + self.price = 0. + self.count = 0 + + def __str__(self): + return "Price: %f, Count: %d" % (self.price, self.count) + +class NewsProvider(Object): + def __init__(self): + self.code = "" + self.name = "" + + def __str__(self): + return "Code: %s, Name: %s" % (self.code, self.name) + +class DepthMktDataDescription(Object): + def __init__(self): + self.exchange = "" + self.secType = "" + self.listingExch = "" + self.serviceDataType = "" + self.aggGroup = UNSET_INTEGER + + def __str__(self): + if (self.aggGroup!= UNSET_INTEGER): + aggGroup = self.aggGroup + else: + aggGroup = "" + return "Exchange: %s, SecType: %s, ListingExchange: %s, ServiceDataType: %s, AggGroup: %s, " % (self.exchange, self.secType, self.listingExch,self.serviceDataType, aggGroup) + +class SmartComponent(Object): + def __init__(self): + self.bitNumber = 0 + self.exchange = "" + self.exchangeLetter = "" + + def __str__(self): + return "BitNumber: %d, Exchange: %s, ExchangeLetter: %s" % (self.bitNumber, self.exchange, self.exchangeLetter) + +class TickAttrib(Object): + def __init__(self): + self.canAutoExecute = False + self.pastLimit = False + self.preOpen = False + + def __str__(self): + return "CanAutoExecute: %d, PastLimit: %d, PreOpen: %d" % (self.canAutoExecute, self.pastLimit, self.preOpen) + +class TickAttribBidAsk(Object): + def __init__(self): + self.bidPastLow = False + self.askPastHigh = False + + def __str__(self): + return "BidPastLow: %d, AskPastHigh: %d" % (self.bidPastLow, self.askPastHigh) + +class TickAttribLast(Object): + def __init__(self): + self.pastLimit = False + self.unreported = False + + def __str__(self): + return "PastLimit: %d, Unreported: %d" % (self.pastLimit, self.unreported) + +class FamilyCode(Object): + def __init__(self): + self.accountID = "" + self.familyCodeStr = "" + + def __str__(self): + return "AccountId: %s, FamilyCodeStr: %s" % (self.accountID, self.familyCodeStr) + +class PriceIncrement(Object): + def __init__(self): + self.lowEdge = 0. + self.increment = 0. + + def __str__(self): + return "LowEdge: %f, Increment: %f" % (self.lowEdge, self.increment) + +class HistoricalTick(Object): + def __init__(self): + self.time = 0 + self.price = 0. + self.size = 0 + + def __str__(self): + return "Time: %d, Price: %f, Size: %d" % (self.time, self.price, self.size) + +class HistoricalTickBidAsk(Object): + def __init__(self): + self.time = 0 + self.tickAttribBidAsk = TickAttribBidAsk() + self.priceBid = 0. + self.priceAsk = 0. + self.sizeBid = 0 + self.sizeAsk = 0 + + def __str__(self): + return "Time: %d, TickAttriBidAsk: %s, PriceBid: %f, PriceAsk: %f, SizeBid: %d, SizeAsk: %d" % (self.time, self.tickAttribBidAsk, self.priceBid, self.priceAsk, self.sizeBid, self.sizeAsk) + +class HistoricalTickLast(Object): + def __init__(self): + self.time = 0 + self.tickAttribLast = TickAttribLast() + self.price = 0. + self.size = 0 + self.exchange = "" + self.specialConditions = "" + + def __str__(self): + return "Time: %d, TickAttribLast: %s, Price: %f, Size: %d, Exchange: %s, SpecialConditions: %s" % (self.time, self.tickAttribLast, self.price, self.size, self.exchange, self.specialConditions) + + diff --git a/vnpy/api/ib/connection.py b/vnpy/api/ib/connection.py new file mode 100644 index 00000000..b46281f4 --- /dev/null +++ b/vnpy/api/ib/connection.py @@ -0,0 +1,122 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +Just a thin wrapper around a socket. +It allows us to keep some other info along with it. +""" + + +import socket +import threading +import logging + +from ibapi.common import * # @UnusedWildImport +from ibapi.errors import * # @UnusedWildImport + + +#TODO: support SSL !! + +logger = logging.getLogger(__name__) + + +class Connection: + def __init__(self, host, port): + self.host = host + self.port = port + self.socket = None + self.wrapper = None + self.lock = threading.Lock() + + + def connect(self): + try: + self.socket = socket.socket() + #TODO: list the exceptions you want to catch + except socket.error: + if self.wrapper: + self.wrapper.error(NO_VALID_ID, FAIL_CREATE_SOCK.code(), FAIL_CREATE_SOCK.msg()) + + try: + self.socket.connect((self.host, self.port)) + except socket.error: + if self.wrapper: + self.wrapper.error(NO_VALID_ID, CONNECT_FAIL.code(), CONNECT_FAIL.msg()) + + self.socket.settimeout(1) #non-blocking + + + def disconnect(self): + self.lock.acquire() + try: + logger.debug("disconnecting") + self.socket.close() + self.socket = None + logger.debug("disconnected") + if self.wrapper: + self.wrapper.connectionClosed() + finally: + self.lock.release() + + + def isConnected(self): + #TODO: also handle when socket gets interrupted/error + return self.socket is not None + + + def sendMsg(self, msg): + + logger.debug("acquiring lock") + self.lock.acquire() + logger.debug("acquired lock") + if not self.isConnected(): + logger.debug("sendMsg attempted while not connected, releasing lock") + self.lock.release() + return 0 + try: + nSent = self.socket.send(msg) + except socket.error: + logger.debug("exception from sendMsg %s", sys.exc_info()) + raise + finally: + logger.debug("releasing lock") + self.lock.release() + logger.debug("release lock") + + logger.debug("sendMsg: sent: %d", nSent) + + return nSent + + + def recvMsg(self): + if not self.isConnected(): + logger.debug("recvMsg attempted while not connected, releasing lock") + return b"" + try: + buf = self._recvAllMsg() + except socket.error: + logger.debug("exception from recvMsg %s", sys.exc_info()) + buf = b"" + else: + pass + + return buf + + + def _recvAllMsg(self): + cont = True + allbuf = b"" + + while cont and self.socket is not None: + buf = self.socket.recv(4096) + allbuf += buf + logger.debug("len %d raw:%s|", len(buf), buf) + + if len(buf) < 4096: + cont = False + + return allbuf + diff --git a/vnpy/api/ib/contract.py b/vnpy/api/ib/contract.py new file mode 100644 index 00000000..706a17dc --- /dev/null +++ b/vnpy/api/ib/contract.py @@ -0,0 +1,205 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" + SAME_POS = open/close leg value is same as combo + OPEN_POS = open + CLOSE_POS = close + UNKNOWN_POS = unknown +""" + + +from ibapi.object_implem import Object + + +(SAME_POS, OPEN_POS, CLOSE_POS, UNKNOWN_POS) = range(4) + + +class ComboLeg(Object): + def __init__(self): + self.conId = 0 # type: int + self.ratio = 0 # type: int + self.action = "" # BUY/SELL/SSHORT + self.exchange = "" + self.openClose = 0 # type: int; LegOpenClose enum values + # for stock legs when doing short sale + self.shortSaleSlot = 0 + self.designatedLocation = "" + self.exemptCode = -1 + + + def __str__(self): + return ",".join(( + str(self.conId), + str(self.ratio), + str(self.action), + str(self.exchange), + str(self.openClose), + str(self.shortSaleSlot), + str(self.designatedLocation), + str(self.exemptCode))) + + +class DeltaNeutralContract(Object): + def __init__(self): + self.conId = 0 # type: int + self.delta = 0. # type: float + self.price = 0. # type: float + + def __str__(self): + return ",".join(( + str(self.conId), + str(self.delta), + str(self.price))) + + +class Contract(Object): + def __init__(self): + self.conId = 0 + self.symbol = "" + self.secType = "" + self.lastTradeDateOrContractMonth = "" + self.strike = 0. # float !! + self.right = "" + self.multiplier = "" + self.exchange = "" + self.primaryExchange = "" # pick an actual (ie non-aggregate) exchange that the contract trades on. DO NOT SET TO SMART. + self.currency = "" + self.localSymbol = "" + self.tradingClass = "" + self.includeExpired = False + self.secIdType = "" # CUSIP;SEDOL;ISIN;RIC + self.secId = "" + + #combos + self.comboLegsDescrip = "" # type: str; received in open order 14 and up for all combos + self.comboLegs = None # type: list + self.deltaNeutralContract = None + + + def __str__(self): + s = ",".join(( + str(self.conId), + str(self.symbol), + str(self.secType), + str(self.lastTradeDateOrContractMonth), + str(self.strike), + str(self.right), + str(self.multiplier), + str(self.exchange), + str(self.primaryExchange), + str(self.currency), + str(self.localSymbol), + str(self.tradingClass), + str(self.includeExpired), + str(self.secIdType), + str(self.secId))) + s += "combo:" + self.comboLegsDescrip + + if self.comboLegs: + for leg in self.comboLegs: + s += ";" + str(leg) + + if self.deltaNeutralContract: + s += ";" + str(self.deltaNeutralContract) + + return s + + +class ContractDetails(Object): + def __init__(self): + self.contract = Contract() + self.marketName = "" + self.minTick = 0. + self.orderTypes = "" + self.validExchanges = "" + self.priceMagnifier = 0 + self.underConId = 0 + self.longName = "" + self.contractMonth = "" + self.industry = "" + self.category = "" + self.subcategory = "" + self.timeZoneId = "" + self.tradingHours = "" + self.liquidHours = "" + self.evRule = "" + self.evMultiplier = 0 + self.mdSizeMultiplier = 0 + self.aggGroup = 0 + self.underSymbol = "" + self.underSecType = "" + self.marketRuleIds = "" + self.secIdList = None + self.realExpirationDate = "" + self.lastTradeTime = "" + # BOND values + self.cusip = "" + self.ratings = "" + self.descAppend = "" + self.bondType = "" + self.couponType = "" + self.callable = False + self.putable = False + self.coupon = 0 + self.convertible = False + self.maturity = "" + self.issueDate = "" + self.nextOptionDate = "" + self.nextOptionType = "" + self.nextOptionPartial = False + self.notes = "" + + def __str__(self): + s = ",".join(( + str(self.contract), + str(self.marketName), + str(self.minTick), + str(self.orderTypes), + str(self.validExchanges), + str(self.priceMagnifier), + str(self.underConId), + str(self.longName), + str(self.contractMonth), + str(self.industry), + str(self.category), + str(self.subcategory), + str(self.timeZoneId), + str(self.tradingHours), + str(self.liquidHours), + str(self.evRule), + str(self.evMultiplier), + str(self.mdSizeMultiplier), + str(self.underSymbol), + str(self.underSecType), + str(self.marketRuleIds), + str(self.aggGroup), + str(self.secIdList), + str(self.realExpirationDate), + str(self.cusip), + str(self.ratings), + str(self.descAppend), + str(self.bondType), + str(self.couponType), + str(self.callable), + str(self.putable), + str(self.coupon), + str(self.convertible), + str(self.maturity), + str(self.issueDate), + str(self.nextOptionDate), + str(self.nextOptionType), + str(self.nextOptionPartial), + str(self.notes))) + return s + + +class ContractDescription(Object): + def __init__(self): + self.contract = Contract() + self.derivativeSecTypes = None # type: list of strings + + diff --git a/vnpy/api/ib/decoder.py b/vnpy/api/ib/decoder.py new file mode 100644 index 00000000..ea52566c --- /dev/null +++ b/vnpy/api/ib/decoder.py @@ -0,0 +1,1512 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +""" +The Decoder knows how to transform a message's payload into higher level +IB message (eg: order info, mkt data, etc). +It will call the corresponding method from the EWrapper so that customer's code +(eg: class derived from EWrapper) can make further use of the data. +""" + +import logging + +from ibapi import order_condition +from ibapi.message import IN +from ibapi.wrapper import * # @UnusedWildImport +from ibapi.order import OrderComboLeg +from ibapi.contract import ContractDescription +from ibapi.contract import ComboLeg +from ibapi.server_versions import * # @UnusedWildImport +from ibapi.utils import * # @UnusedWildImport +from ibapi.softdollartier import SoftDollarTier +from ibapi.ticktype import * # @UnusedWildImport +from ibapi.tag_value import TagValue +from ibapi.scanner import ScanData +from ibapi.errors import BAD_MESSAGE +from ibapi.common import * # @UnusedWildImport + + +logger = logging.getLogger(__name__) + + +class HandleInfo(Object): + def __init__(self, wrap=None, proc=None): + self.wrapperMeth = wrap + self.wrapperParams = None + self.processMeth = proc + if wrap is None and proc is None: + raise ValueError("both wrap and proc can't be None") + + def __str__(self): + s = "wrap:%s meth:%s prms:%s" % (self.wrapperMeth, + self.processMeth, self.wrapperParams) + return s + + +class Decoder(Object): + def __init__(self, wrapper, serverVersion): + self.wrapper = wrapper + self.serverVersion = serverVersion + self.discoverParams() + #self.printParams() + + + def processTickPriceMsg(self, fields): + next(fields) + decode(int, fields) + + reqId = decode(int, fields) + tickType = decode(int, fields) + price = decode(float, fields) + size = decode(int, fields) # ver 2 field + attrMask = decode(int, fields) # ver 3 field + + attrib = TickAttrib() + + attrib.canAutoExecute = attrMask == 1 + + if self.serverVersion >= MIN_SERVER_VER_PAST_LIMIT: + attrib.canAutoExecute = attrMask & 1 != 0 + attrib.pastLimit = attrMask & 2 != 0 + if self.serverVersion >= MIN_SERVER_VER_PRE_OPEN_BID_ASK: + attrib.preOpen = attrMask & 4 != 0 + + self.wrapper.tickPrice(reqId, tickType, price, attrib) + + # process ver 2 fields + sizeTickType = TickTypeEnum.NOT_SET + if TickTypeEnum.BID == tickType: + sizeTickType = TickTypeEnum.BID_SIZE + elif TickTypeEnum.ASK == tickType: + sizeTickType = TickTypeEnum.ASK_SIZE + elif TickTypeEnum.LAST == tickType: + sizeTickType = TickTypeEnum.LAST_SIZE + elif TickTypeEnum.DELAYED_BID == tickType: + sizeTickType = TickTypeEnum.DELAYED_BID_SIZE + elif TickTypeEnum.DELAYED_ASK == tickType: + sizeTickType = TickTypeEnum.DELAYED_ASK_SIZE + elif TickTypeEnum.DELAYED_LAST == tickType: + sizeTickType = TickTypeEnum.DELAYED_LAST_SIZE + + if sizeTickType != TickTypeEnum.NOT_SET: + self.wrapper.tickSize(reqId, sizeTickType, size) + + + def processOrderStatusMsg(self, fields): + + next(fields) + if self.serverVersion < MIN_SERVER_VER_MARKET_CAP_PRICE: + decode(int, fields) + orderId = decode(int, fields) + status = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + filled = decode(float, fields) + else: + filled = decode(int, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + remaining = decode(float, fields) + else: + remaining = decode(int, fields) + + avgFillPrice = decode(float, fields) + + permId = decode(int, fields) # ver 2 field + parentId = decode(int, fields) # ver 3 field + lastFillPrice = decode(float, fields) # ver 4 field + clientId = decode(int, fields) # ver 5 field + whyHeld = decode(str, fields) # ver 6 field + + if self.serverVersion >= MIN_SERVER_VER_MARKET_CAP_PRICE: + mktCapPrice = decode(float, fields) + else: + mktCapPrice = None + + self.wrapper.orderStatus(orderId, status, filled, remaining, + avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice) + + + def processOpenOrder(self, fields): + + next(fields) + + if self.serverVersion < MIN_SERVER_VER_ORDER_CONTAINER: + version = decode(int, fields) + else: + version = self.serverVersion + + order = Order() + order.orderId = decode(int, fields) + + contract = Contract() + + contract.conId = decode(int, fields) # ver 17 field + contract.symbol = decode(str, fields) + contract.secType = decode(str, fields) + contract.lastTradeDateOrContractMonth = decode(str, fields) + contract.strike = decode(float, fields) + contract.right = decode(str, fields) + if version >= 32: + contract.multiplier = decode(str, fields) + contract.exchange = decode(str, fields) + contract.currency = decode(str, fields) + contract.localSymbol = decode(str, fields) # ver 2 field + if version >= 32: + contract.tradingClass = decode(str, fields) + + # read order fields + order.action = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + order.totalQuantity = decode(float, fields) + else: + order.totalQuantity = decode(int, fields) + + order.orderType = decode(str, fields) + if version < 29: + order.lmtPrice = decode(float, fields) + else: + order.lmtPrice = decode(float, fields, SHOW_UNSET) + if version < 30: + order.auxPrice = decode(float, fields) + else: + order.auxPrice = decode(float, fields, SHOW_UNSET) + order.tif = decode(str, fields) + order.ocaGroup = decode(str, fields) + order.account = decode(str, fields) + order.openClose = decode(str, fields) + + order.origin = decode(int, fields) + + order.orderRef = decode(str, fields) + order.clientId = decode(int, fields) # ver 3 field + order.permId = decode(int, fields) # ver 4 field + + order.outsideRth = decode(bool, fields) # ver 18 field + order.hidden = decode(bool, fields) # ver 4 field + order.discretionaryAmt = decode(float, fields) # ver 4 field + order.goodAfterTime = decode(str, fields) # ver 5 field + + _sharesAllocation = decode(str, fields) # deprecated ver 6 field + + order.faGroup = decode(str, fields) # ver 7 field + order.faMethod = decode(str, fields) # ver 7 field + order.faPercentage = decode(str, fields) # ver 7 field + order.faProfile = decode(str, fields) # ver 7 field + + if self.serverVersion >= MIN_SERVER_VER_MODELS_SUPPORT: + order.modelCode = decode(str, fields) + + order.goodTillDate = decode(str, fields) # ver 8 field + + order.rule80A = decode(str, fields) # ver 9 field + order.percentOffset = decode(float, fields, SHOW_UNSET) # ver 9 field + order.settlingFirm = decode(str, fields) # ver 9 field + order.shortSaleSlot = decode(int, fields) # ver 9 field + order.designatedLocation = decode(str, fields) # ver 9 field + if self.serverVersion == MIN_SERVER_VER_SSHORTX_OLD: + decode(int, fields) + elif version >= 23: + order.exemptCode = decode(int, fields) + order.auctionStrategy = decode(int, fields) # ver 9 field + order.startingPrice = decode(float, fields, SHOW_UNSET) # ver 9 field + order.stockRefPrice = decode(float, fields, SHOW_UNSET) # ver 9 field + order.delta = decode(float, fields, SHOW_UNSET) # ver 9 field + order.stockRangeLower = decode(float, fields, SHOW_UNSET) # ver 9 field + order.stockRangeUpper = decode(float, fields, SHOW_UNSET) # ver 9 field + order.displaySize = decode(int, fields) # ver 9 field + + #if( version < 18) { + # # will never happen + # /* order.rthOnly = */ readBoolFromInt() + #} + + order.blockOrder = decode(bool, fields) # ver 9 field + order.sweepToFill = decode(bool, fields) # ver 9 field + order.allOrNone = decode(bool, fields) # ver 9 field + order.minQty = decode(int, fields, SHOW_UNSET) # ver 9 field + order.ocaType = decode(int, fields) # ver 9 field + order.eTradeOnly = decode(bool, fields) # ver 9 field + order.firmQuoteOnly = decode(bool, fields) # ver 9 field + order.nbboPriceCap = decode(float, fields, SHOW_UNSET) # ver 9 field + + order.parentId = decode(int, fields) # ver 10 field + order.triggerMethod = decode(int, fields) # ver 10 field + + order.volatility = decode(float, fields, SHOW_UNSET) # ver 11 field + order.volatilityType = decode(int, fields) # ver 11 field + order.deltaNeutralOrderType = decode(str, fields) # ver 11 field (had a hack for ver 11) + order.deltaNeutralAuxPrice = decode(float, fields, SHOW_UNSET) # ver 12 field + + if version >= 27 and order.deltaNeutralOrderType: + order.deltaNeutralConId = decode(int, fields) + order.deltaNeutralSettlingFirm = decode(str, fields) + order.deltaNeutralClearingAccount = decode(str, fields) + order.deltaNeutralClearingIntent = decode(str, fields) + + if version >= 31 and order.deltaNeutralOrderType: + order.deltaNeutralOpenClose = decode(str, fields) + order.deltaNeutralShortSale = decode(bool, fields) + order.deltaNeutralShortSaleSlot = decode(int, fields) + order.deltaNeutralDesignatedLocation = decode(str, fields) + + order.continuousUpdate = decode(bool, fields) # ver 11 field + + # will never happen + #if( self.serverVersion == 26) { + # order.stockRangeLower = readDouble() + # order.stockRangeUpper = readDouble() + #} + + order.referencePriceType = decode(int, fields) # ver 11 field + + order.trailStopPrice = decode(float, fields, SHOW_UNSET) # ver 13 field + + if version >= 30: + order.trailingPercent = decode(float, fields, SHOW_UNSET) + + order.basisPoints = decode(float, fields, SHOW_UNSET) # ver 14 field + order.basisPointsType = decode(int, fields, SHOW_UNSET) # ver 14 field + contract.comboLegsDescrip = decode(str, fields) # ver 14 field + + if version >= 29: + comboLegsCount = decode(int, fields) + + if comboLegsCount > 0: + contract.comboLegs = [] + for _ in range(comboLegsCount): + comboLeg = ComboLeg() + comboLeg.conId = decode(int, fields) + comboLeg.ratio = decode(int, fields) + comboLeg.action = decode(str, fields) + comboLeg.exchange = decode(str, fields) + comboLeg.openClose = decode(int, fields) + comboLeg.shortSaleSlot = decode(int, fields) + comboLeg.designatedLocation = decode(str, fields) + comboLeg.exemptCode = decode(int, fields) + contract.comboLegs.append(comboLeg) + + orderComboLegsCount = decode(int, fields) + if orderComboLegsCount > 0: + order.orderComboLegs = [] + for _ in range(orderComboLegsCount): + orderComboLeg = OrderComboLeg() + orderComboLeg.price = decode(float, fields, SHOW_UNSET) + order.orderComboLegs.append(orderComboLeg) + + if version >= 26: + smartComboRoutingParamsCount = decode(int, fields) + if smartComboRoutingParamsCount > 0: + order.smartComboRoutingParams = [] + for _ in range(smartComboRoutingParamsCount): + tagValue = TagValue() + tagValue.tag = decode(str, fields) + tagValue.value = decode(str, fields) + order.smartComboRoutingParams.append(tagValue) + + if version >= 20: + order.scaleInitLevelSize = decode(int, fields, SHOW_UNSET) + order.scaleSubsLevelSize = decode(int, fields, SHOW_UNSET) + else: + # ver 15 fields + order.notSuppScaleNumComponents = decode(int, fields, SHOW_UNSET) + order.scaleInitLevelSize = decode(int, fields, SHOW_UNSET) # scaleComponectSize + + order.scalePriceIncrement = decode(float, fields, SHOW_UNSET) # ver 15 field + + if version >= 28 and order.scalePriceIncrement != UNSET_DOUBLE \ + and order.scalePriceIncrement > 0.0: + order.scalePriceAdjustValue = decode(float, fields, SHOW_UNSET) + order.scalePriceAdjustInterval = decode(int, fields, SHOW_UNSET) + order.scaleProfitOffset = decode(float, fields, SHOW_UNSET) + order.scaleAutoReset = decode(bool, fields) + order.scaleInitPosition = decode(int, fields, SHOW_UNSET) + order.scaleInitFillQty = decode(int, fields, SHOW_UNSET) + order.scaleRandomPercent = decode(bool, fields) + + if version >= 24: + order.hedgeType = decode(str, fields) + if order.hedgeType: + order.hedgeParam = decode(str, fields) + + if version >= 25: + order.optOutSmartRouting = decode(bool, fields) + + order.clearingAccount = decode(str, fields) # ver 19 field + order.clearingIntent = decode(str, fields) # ver 19 field + + if version >= 22: + order.notHeld = decode(bool, fields) + + if version >= 20: + deltaNeutralContractPresent = decode(bool, fields) + if deltaNeutralContractPresent: + contract.deltaNeutralContract = DeltaNeutralContract() + contract.deltaNeutralContract.conId = decode(int, fields) + contract.deltaNeutralContract.delta = decode(float, fields) + contract.deltaNeutralContract.price = decode(float, fields) + + if version >= 21: + order.algoStrategy = decode(str, fields) + if order.algoStrategy: + algoParamsCount = decode(int, fields) + if algoParamsCount > 0: + order.algoParams = [] + for _ in range(algoParamsCount): + tagValue = TagValue() + tagValue.tag = decode(str, fields) + tagValue.value = decode(str, fields) + order.algoParams.append(tagValue) + + if version >= 33: + order.solicited = decode(bool, fields) + + orderState = OrderState() + + order.whatIf = decode(bool, fields) # ver 16 field + + orderState.status = decode(str, fields) # ver 16 field + if self.serverVersion >= MIN_SERVER_VER_WHAT_IF_EXT_FIELDS: + orderState.initMarginBefore = decode(str, fields) + orderState.maintMarginBefore = decode(str, fields) + orderState.equityWithLoanBefore = decode(str, fields) + orderState.initMarginChange = decode(str, fields) + orderState.maintMarginChange = decode(str, fields) + orderState.equityWithLoanChange = decode(str, fields) + + orderState.initMarginAfter = decode(str, fields) # ver 16 field + orderState.maintMarginAfter = decode(str, fields) # ver 16 field + orderState.equityWithLoanAfter = decode(str, fields) # ver 16 field + + orderState.commission = decode(float, fields, SHOW_UNSET) # ver 16 field + orderState.minCommission = decode(float, fields, SHOW_UNSET) # ver 16 field + orderState.maxCommission = decode(float, fields, SHOW_UNSET) # ver 16 field + orderState.commissionCurrency = decode(str, fields) # ver 16 field + orderState.warningText = decode(str, fields) # ver 16 field + + if version >= 34: + order.randomizeSize = decode(bool, fields) + order.randomizePrice = decode(bool, fields) + + if self.serverVersion >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK: + if order.orderType == "PEG BENCH": + order.referenceContractId = decode(int, fields) + order.isPeggedChangeAmountDecrease = decode(bool, fields) + order.peggedChangeAmount = decode(float, fields) + order.referenceChangeAmount = decode(float, fields) + order.referenceExchangeId = decode(str, fields) + + conditionsSize = decode(int, fields) + if conditionsSize > 0: + order.conditions = [] + for _ in range(conditionsSize): + conditionType = decode(int, fields) + condition = order_condition.Create(conditionType) + condition.decode(fields) + order.conditions.append(condition) + + order.conditionsIgnoreRth = decode(bool, fields) + order.conditionsCancelOrder = decode(bool, fields) + + order.adjustedOrderType = decode(str, fields) + order.triggerPrice = decode(float, fields) + order.trailStopPrice = decode(float, fields) + order.lmtPriceOffset = decode(float, fields) + order.adjustedStopPrice = decode(float, fields) + order.adjustedStopLimitPrice = decode(float, fields) + order.adjustedTrailingAmount = decode(float, fields) + order.adjustableTrailingUnit = decode(int, fields) + + if self.serverVersion >= MIN_SERVER_VER_SOFT_DOLLAR_TIER: + name = decode(str, fields) + value = decode(str, fields) + displayName = decode(str, fields) + order.softDollarTier = SoftDollarTier(name, value, displayName) + + if self.serverVersion >= MIN_SERVER_VER_CASH_QTY: + order.cashQty = decode(float,fields) + + if self.serverVersion >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE: + order.dontUseAutoPriceForHedge = decode(bool,fields) + + if self.serverVersion >= MIN_SERVER_VER_ORDER_CONTAINER: + order.isOmsContainer = decode(bool, fields) + + if self.serverVersion >= MIN_SERVER_VER_D_PEG_ORDERS: + order.discretionaryUpToLimitPrice = decode(bool, fields) + + self.wrapper.openOrder(order.orderId, contract, order, orderState) + + + def processPortfolioValueMsg(self, fields): + + next(fields) + version = decode(int, fields) + + # read contract fields + contract = Contract() + contract.conId = decode(int, fields) # ver 6 field + contract.symbol = decode(str, fields) + contract.secType = decode(str, fields) + contract.lastTradeDateOrContractMonth = decode(str, fields) + contract.strike = decode(float, fields) + contract.right = decode(str, fields) + + if version >= 7: + contract.multiplier = decode(str, fields) + contract.primaryExchange = decode(str, fields) + + contract.currency = decode(str, fields) + contract.localSymbol = decode(str, fields) # ver 2 field + if version >= 8: + contract.tradingClass = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + position = decode(float, fields) + else: + position = decode(int, fields) + + marketPrice = decode(float, fields) + marketValue = decode(float, fields) + averageCost = decode(float, fields) # ver 3 field + unrealizedPNL = decode(float, fields) # ver 3 field + realizedPNL = decode(float, fields) # ver 3 field + + accountName = decode(str, fields) # ver 4 field + + if version == 6 and self.serverVersion == 39: + contract.primaryExchange = decode(str, fields) + + self.wrapper.updatePortfolio( contract, + position, marketPrice, marketValue, averageCost, + unrealizedPNL, realizedPNL, accountName) + + + def processContractDataMsg(self, fields): + + next(fields) + version = decode(int, fields) + + reqId = -1 + if version >= 3: + reqId = decode(int, fields) + + contract = ContractDetails() + contract.contract.symbol = decode(str, fields) + contract.contract.secType = decode(str, fields) + self.readLastTradeDate(fields, contract, False) + contract.contract.strike = decode(float, fields) + contract.contract.right = decode(str, fields) + contract.contract.exchange = decode(str, fields) + contract.contract.currency = decode(str, fields) + contract.contract.localSymbol = decode(str, fields) + contract.marketName = decode(str, fields) + contract.contract.tradingClass = decode(str, fields) + contract.contract.conId = decode(int, fields) + contract.minTick = decode(float, fields) + if self.serverVersion >= MIN_SERVER_VER_MD_SIZE_MULTIPLIER: + contract.mdSizeMultiplier = decode(int, fields) + contract.contract.multiplier = decode(str, fields) + contract.orderTypes = decode(str, fields) + contract.validExchanges = decode(str, fields) + contract.priceMagnifier = decode(int, fields) # ver 2 field + if version >= 4: + contract.underConId = decode(int, fields) + if version >= 5: + contract.longName = decode(str, fields) + contract.contract.primaryExchange = decode(str, fields) + if version >= 6: + contract.contractMonth = decode(str, fields) + contract.industry = decode(str, fields) + contract.category = decode(str, fields) + contract.subcategory = decode(str, fields) + contract.timeZoneId = decode(str, fields) + contract.tradingHours = decode(str, fields) + contract.liquidHours = decode(str, fields) + if version >= 8: + contract.evRule = decode(str, fields) + contract.evMultiplier = decode(int, fields) + if version >= 7: + secIdListCount = decode(int, fields) + if secIdListCount > 0: + contract.secIdList = [] + for _ in range(secIdListCount): + tagValue = TagValue() + tagValue.tag = decode(str, fields) + tagValue.value = decode(str, fields) + contract.secIdList.append(tagValue) + + if self.serverVersion >= MIN_SERVER_VER_AGG_GROUP: + contract.aggGroup = decode(int, fields) + + if self.serverVersion >= MIN_SERVER_VER_UNDERLYING_INFO: + contract.underSymbol = decode(str, fields) + contract.underSecType = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_MARKET_RULES: + contract.marketRuleIds = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_REAL_EXPIRATION_DATE: + contract.realExpirationDate = decode(str, fields) + + self.wrapper.contractDetails(reqId, contract) + + + def processBondContractDataMsg(self, fields): + + next(fields) + version = decode(int, fields) + + reqId = -1 + if version >= 3: + reqId = decode(int, fields) + + contract = ContractDetails() + contract.contract.symbol = decode(str, fields) + contract.contract.secType = decode(str, fields) + contract.cusip = decode(str, fields) + contract.coupon = decode(int, fields) + self.readLastTradeDate(fields, contract, True) + contract.issueDate = decode(str, fields) + contract.ratings = decode(str, fields) + contract.bondType = decode(str, fields) + contract.couponType = decode(str, fields) + contract.convertible = decode(bool, fields) + contract.callable = decode(bool, fields) + contract.putable = decode(bool, fields) + contract.descAppend = decode(str, fields) + contract.contract.exchange = decode(str, fields) + contract.contract.currency = decode(str, fields) + contract.marketName = decode(str, fields) + contract.contract.tradingClass = decode(str, fields) + contract.contract.conId = decode(int, fields) + contract.minTick = decode(float, fields) + if self.serverVersion >= MIN_SERVER_VER_MD_SIZE_MULTIPLIER: + contract.mdSizeMultiplier = decode(int, fields) + contract.orderTypes = decode(str, fields) + contract.validExchanges = decode(str, fields) + contract.nextOptionDate = decode(str, fields) # ver 2 field + contract.nextOptionType = decode(str, fields) # ver 2 field + contract.nextOptionPartial = decode(bool, fields) # ver 2 field + contract.notes = decode(str, fields) # ver 2 field + if version >= 4: + contract.longName = decode(str, fields) + if version >= 6: + contract.evRule = decode(str, fields) + contract.evMultiplier = decode(int, fields) + if version >= 5: + secIdListCount = decode(int, fields) + if secIdListCount > 0: + contract.secIdList = [] + for _ in range(secIdListCount): + tagValue = TagValue() + tagValue.tag = decode(str, fields) + tagValue.value = decode(str, fields) + contract.secIdList.append(tagValue) + + if self.serverVersion >= MIN_SERVER_VER_AGG_GROUP: + contract.aggGroup = decode(int, fields) + + if self.serverVersion >= MIN_SERVER_VER_MARKET_RULES: + contract.marketRuleIds = decode(str, fields) + + self.wrapper.bondContractDetails(reqId, contract) + + def processScannerDataMsg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + + numberOfElements = decode(int, fields) + + for _ in range(numberOfElements): + data = ScanData() + data.contract = ContractDetails() + + data.rank = decode(int, fields) + data.contract.contract.conId = decode(int, fields) # ver 3 field + data.contract.contract.symbol = decode(str, fields) + data.contract.contract.secType = decode(str, fields) + data.contract.contract.lastTradeDateOrContractMonth = decode(str, fields) + data.contract.contract.strike = decode(float, fields) + data.contract.contract.right = decode(str, fields) + data.contract.contract.exchange = decode(str, fields) + data.contract.contract.currency = decode(str, fields) + data.contract.contract.localSymbol = decode(str, fields) + data.contract.marketName = decode(str, fields) + data.contract.contract.tradingClass = decode(str, fields) + data.distance = decode(str, fields) + data.benchmark = decode(str, fields) + data.projection = decode(str, fields) + data.legsStr = decode(str, fields) + self.wrapper.scannerData(reqId, data.rank, data.contract, + data.distance, data.benchmark, data.projection, data.legsStr) + + self.wrapper.scannerDataEnd(reqId) + + + def processExecutionDataMsg(self, fields): + next(fields) + version = self.serverVersion + + if(self.serverVersion < MIN_SERVER_VER_LAST_LIQUIDITY): + version = decode(int, fields) + + reqId = -1 + if version >= 7: + reqId = decode(int, fields) + + orderId = decode(int, fields) + + # decode contract fields + contract = Contract() + contract.conId = decode(int, fields) # ver 5 field + contract.symbol = decode(str, fields) + contract.secType = decode(str, fields) + contract.lastTradeDateOrContractMonth = decode(str, fields) + contract.strike = decode(float, fields) + contract.right = decode(str, fields) + if version >= 9: + contract.multiplier = decode(str, fields) + contract.exchange = decode(str, fields) + contract.currency = decode(str, fields) + contract.localSymbol = decode(str, fields) + if version >= 10: + contract.tradingClass = decode(str, fields) + + # decode execution fields + execution = Execution() + execution.orderId = orderId + execution.execId = decode(str, fields) + execution.time = decode(str, fields) + execution.acctNumber = decode(str, fields) + execution.exchange = decode(str, fields) + execution.side = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + execution.shares = decode(float, fields) + else: + execution.shares = decode(int, fields) + + execution.price = decode(float, fields) + execution.permId = decode(int, fields) # ver 2 field + execution.clientId = decode(int, fields) # ver 3 field + execution.liquidation = decode(int, fields) # ver 4 field + + if version >= 6: + execution.cumQty = decode(float, fields) + execution.avgPrice = decode(float, fields) + + if version >= 8: + execution.orderRef = decode(str, fields) + + if version >= 9: + execution.evRule = decode(str, fields) + execution.evMultiplier = decode(float, fields) + if self.serverVersion >= MIN_SERVER_VER_MODELS_SUPPORT: + execution.modelCode = decode(str, fields) + if self.serverVersion >= MIN_SERVER_VER_LAST_LIQUIDITY: + execution.lastLiquidity = decode(int, fields) + + self.wrapper.execDetails(reqId, contract, execution) + + + def processHistoricalDataMsg(self, fields): + next(fields) + + if self.serverVersion < MIN_SERVER_VER_SYNT_REALTIME_BARS: + decode(int, fields) + + reqId = decode(int, fields) + startDateStr = decode(str, fields) # ver 2 field + endDateStr = decode(str, fields) # ver 2 field + + itemCount = decode(int, fields) + + for _ in range(itemCount): + bar = BarData() + bar.date = decode(str, fields) + bar.open = decode(float, fields) + bar.high = decode(float, fields) + bar.low = decode(float, fields) + bar.close = decode(float, fields) + bar.volume = decode(int, fields) + bar.average = decode(float, fields) + + if self.serverVersion < MIN_SERVER_VER_SYNT_REALTIME_BARS: + decode(str, fields) + + bar.barCount = decode(int, fields) # ver 3 field + + self.wrapper.historicalData(reqId, bar) + + # send end of dataset marker + self.wrapper.historicalDataEnd(reqId, startDateStr, endDateStr) + + def processHistoricalDataUpdateMsg(self, fields): + next(fields) + reqId = decode(int, fields) + bar = BarData() + bar.barCount = decode(int, fields) + bar.date = decode(str, fields) + bar.open = decode(float, fields) + bar.close = decode(float, fields) + bar.high = decode(float, fields) + bar.low = decode(float, fields) + bar.average = decode(float, fields) + bar.volume = decode(int, fields) + self.wrapper.historicalDataUpdate(reqId, bar) + + def processRealTimeBarMsg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + + bar = RealTimeBar() + bar.time = decode(int, fields) + bar.open = decode(float, fields) + bar.high = decode(float, fields) + bar.low = decode(float, fields) + bar.close = decode(float, fields) + bar.volume = decode(int, fields) + bar.wap = decode(float, fields) + bar.count = decode(int, fields) + + self.wrapper.realtimeBar(reqId, bar.time, bar.open, bar.high, bar.low, bar.close, bar.volume, bar.wap, bar.count) + + def processTickOptionComputationMsg(self, fields): + optPrice = None + pvDividend = None + gamma = None + vega = None + theta = None + undPrice = None + + next(fields) + version = decode(int, fields) + reqId = decode(int, fields) + tickTypeInt = decode(int, fields) + + impliedVol = decode(float, fields) + delta = decode(float, fields) + + if impliedVol < 0: # -1 is the "not computed" indicator + impliedVol = None + if delta == -2: # -2 is the "not computed" indicator + delta = None + + if version >= 6 or \ + tickTypeInt == TickTypeEnum.MODEL_OPTION or \ + tickTypeInt == TickTypeEnum.DELAYED_MODEL_OPTION: + + optPrice = decode(float, fields) + pvDividend = decode(float, fields) + + if optPrice == -1: # -1 is the "not computed" indicator + optPrice = None + if pvDividend == -1: # -1 is the "not computed" indicator + pvDividend = None + + if version >= 6: + gamma = decode(float, fields) + vega = decode(float, fields) + theta = decode(float, fields) + undPrice = decode(float, fields) + + if gamma == -2: # -2 is the "not yet computed" indicator + gamma = None + if vega == -2: # -2 is the "not yet computed" indicator + vega = None + if theta == -2: # -2 is the "not yet computed" indicator + theta = None + if undPrice == -1: # -1 is the "not computed" indicator + undPrice = None + + self.wrapper.tickOptionComputation(reqId, tickTypeInt, impliedVol, + delta, optPrice, pvDividend, gamma, vega, theta, undPrice) + + + + def processDeltaNeutralValidationMsg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + + deltaNeutralContract = DeltaNeutralContract() + + deltaNeutralContract.conId = decode(int, fields) + deltaNeutralContract.delta = decode(float, fields) + deltaNeutralContract.price = decode(float, fields) + + self.wrapper.deltaNeutralValidation(reqId, deltaNeutralContract) + + + def processMarketDataTypeMsg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + marketDataType = decode(int, fields) + + self.wrapper.marketDataType(reqId, marketDataType) + + def processCommissionReportMsg(self, fields): + next(fields) + decode(int, fields) + + commissionReport = CommissionReport() + commissionReport.execId = decode(str, fields) + commissionReport.commission = decode(float, fields) + commissionReport.currency = decode(str, fields) + commissionReport.realizedPNL = decode(float, fields) + commissionReport.yield_ = decode(float, fields) + commissionReport.yieldRedemptionDate = decode(int, fields) + + self.wrapper.commissionReport(commissionReport) + + + def processPositionDataMsg(self, fields): + next(fields) + version = decode(int, fields) + + account = decode(str, fields) + + # decode contract fields + contract = Contract() + contract.conId = decode(int, fields) + contract.symbol = decode(str, fields) + contract.secType = decode(str, fields) + contract.lastTradeDateOrContractMonth = decode(str, fields) + contract.strike = decode(float, fields) + contract.right = decode(str, fields) + contract.multiplier = decode(str, fields) + contract.exchange = decode(str, fields) + contract.currency = decode(str, fields) + contract.localSymbol = decode(str, fields) + if version >= 2: + contract.tradingClass = decode(str, fields) + + if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: + position = decode(float, fields) + else: + position = decode(int, fields) + + avgCost = 0. + if version >= 3: + avgCost = decode(float, fields) + + self.wrapper.position(account, contract, position, avgCost) + + + def processPositionMultiMsg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + account = decode(str, fields) + + # decode contract fields + contract = Contract() + contract.conId = decode(int, fields) + contract.symbol = decode(str, fields) + contract.secType = decode(str, fields) + contract.lastTradeDateOrContractMonth = decode(str, fields) + contract.strike = decode(float, fields) + contract.right = decode(str, fields) + contract.multiplier = decode(str, fields) + contract.exchange = decode(str, fields) + contract.currency = decode(str, fields) + contract.localSymbol = decode(str, fields) + contract.tradingClass = decode(str, fields) + position = decode(float, fields) + avgCost = decode(float, fields) + modelCode = decode(str, fields) + + self.wrapper.positionMulti(reqId, account, modelCode, contract, position, avgCost) + + + def processSecurityDefinitionOptionParameterMsg(self, fields): + next(fields) + + reqId = decode(int, fields) + exchange = decode(str, fields) + underlyingConId = decode(int, fields) + tradingClass = decode(str, fields) + multiplier = decode(str, fields) + + expCount = decode(int, fields) + expirations = set() + for _ in range(expCount): + expiration = decode(str, fields) + expirations.add(expiration) + + strikeCount = decode(int, fields) + strikes = set() + for _ in range(strikeCount): + strike = decode(float, fields) + strikes.add(strike) + + self.wrapper.securityDefinitionOptionParameter(reqId, exchange, + underlyingConId, tradingClass, multiplier, expirations, strikes) + + + def processSecurityDefinitionOptionParameterEndMsg(self, fields): + next(fields) + + reqId = decode(int, fields) + self.wrapper.securityDefinitionOptionParameterEnd(reqId) + + + def processSoftDollarTiersMsg(self, fields): + next(fields) + + reqId = decode(int, fields) + nTiers = decode(int, fields) + + tiers = [] + for _ in range(nTiers): + tier = SoftDollarTier() + tier.name = decode(str, fields) + tier.val = decode(str, fields) + tier.displayName = decode(str, fields) + tiers.append(tier) + + self.wrapper.softDollarTiers(reqId, tiers) + + + def processFamilyCodesMsg(self, fields): + next(fields) + + nFamilyCodes = decode(int, fields) + familyCodes = [] + for _ in range(nFamilyCodes): + famCode = FamilyCode() + famCode.accountID = decode(str, fields) + famCode.familyCodeStr = decode(str, fields) + familyCodes.append(famCode) + + self.wrapper.familyCodes(familyCodes) + + + def processSymbolSamplesMsg(self, fields): + next(fields) + + reqId = decode(int, fields) + nContractDescriptions = decode(int, fields) + contractDescriptions = [] + for _ in range(nContractDescriptions): + conDesc = ContractDescription() + conDesc.contract.conId = decode(int, fields) + conDesc.contract.symbol = decode(str, fields) + conDesc.contract.secType = decode(str, fields) + conDesc.contract.primaryExchange = decode(str, fields) + conDesc.contract.currency = decode(str, fields) + + nDerivativeSecTypes = decode(int, fields) + conDesc.derivativeSecTypes = [] + for _ in range(nDerivativeSecTypes): + derivSecType = decode(str, fields) + conDesc.derivativeSecTypes.append(derivSecType) + contractDescriptions.append(conDesc) + + self.wrapper.symbolSamples(reqId, contractDescriptions) + + def processSmartComponents(self,fields): + next(fields) + reqId = decode(int, fields) + n = decode(int, fields) + + smartComponentMap = [] + for _ in range(n): + smartComponent = SmartComponent() + smartComponent.bitNumber = decode(int, fields) + smartComponent.exchange = decode(str, fields) + smartComponent.exchangeLetter = decode(str, fields) + smartComponentMap.append(smartComponent) + + self.wrapper.smartComponents(reqId, smartComponentMap) + + def processTickReqParams(self,fields): + next(fields) + tickerId = decode(int, fields) + minTick = decode(float, fields) + bboExchange = decode(str, fields) + snapshotPermissions = decode(int, fields) + self.wrapper.tickReqParams(tickerId, minTick, bboExchange, snapshotPermissions) + + def processMktDepthExchanges(self,fields): + next(fields) + depthMktDataDescriptions = [] + nDepthMktDataDescriptions = decode(int, fields) + + if nDepthMktDataDescriptions > 0: + for _ in range(nDepthMktDataDescriptions): + desc = DepthMktDataDescription() + desc.exchange = decode(str, fields) + desc.secType = decode(str, fields) + if self.serverVersion >= MIN_SERVER_VER_SERVICE_DATA_TYPE: + desc.listingExch = decode(str, fields) + desc.serviceDataType = decode(str, fields) + desc.aggGroup = decode(int, fields) + else: + decode(int,fields) #boolean notSuppIsL2 + depthMktDataDescriptions.append(desc) + + self.wrapper.mktDepthExchanges(depthMktDataDescriptions) + + def processHeadTimestamp(self,fields): + next(fields) + reqId = decode(int, fields) + headTimestamp = decode(str, fields) + self.wrapper.headTimestamp(reqId,headTimestamp) + + def processTickNews(self,fields): + next(fields) + tickerId = decode( int, fields) + timeStamp = decode(int, fields) + providerCode = decode(str, fields) + articleId = decode(str, fields) + headline = decode(str, fields) + extraData = decode(str, fields) + self.wrapper.tickNews(tickerId, timeStamp, providerCode, articleId, headline, extraData) + + def processNewsProviders(self,fields): + next(fields) + newsProviders = [] + nNewsProviders = decode(int, fields) + if nNewsProviders > 0: + for _ in range(nNewsProviders): + provider = NewsProvider() + provider.code = decode(str, fields) + provider.name = decode(str, fields) + newsProviders.append(provider) + + self.wrapper.newsProviders(newsProviders) + + def processNewsArticle(self,fields): + next(fields) + reqId = decode(int, fields) + articleType = decode(int, fields) + articleText = decode(str, fields) + self.wrapper.newsArticle(reqId, articleType, articleText) + + def processHistoricalNews(self,fields): + next(fields) + requestId = decode(int, fields) + time = decode(str, fields) + providerCode = decode(str, fields) + articleId = decode(str, fields) + headline = decode(str, fields) + self.wrapper.historicalNews(requestId, time, providerCode, articleId, headline) + + def processHistoricalNewsEnd(self,fields): + next(fields) + reqId = decode(int, fields) + hasMore = decode(bool, fields) + self.wrapper.historicalNewsEnd(reqId, hasMore) + + def processHistogramData(self,fields): + next(fields) + reqId = decode(int, fields) + numPoints = decode(int, fields) + + histogram = [] + for _ in range(numPoints): + dataPoint = HistogramData() + dataPoint.price = decode(float,fields) + dataPoint.count = decode(int,fields) + histogram.append(dataPoint) + + self.wrapper.histogramData(reqId, histogram) + + def processRerouteMktDataReq(self, fields): + next(fields) + reqId = decode(int, fields) + conId = decode(int, fields) + exchange = decode(str, fields) + + self.wrapper.rerouteMktDataReq(reqId, conId, exchange) + + def processRerouteMktDepthReq(self, fields): + next(fields) + reqId = decode(int, fields) + conId = decode(int, fields) + exchange = decode(str, fields) + + self.wrapper.rerouteMktDepthReq(reqId, conId, exchange) + + def processMarketRuleMsg(self, fields): + next(fields) + marketRuleId = decode(int, fields) + + nPriceIncrements = decode(int, fields) + priceIncrements = [] + + if nPriceIncrements > 0: + for _ in range(nPriceIncrements): + prcInc = PriceIncrement() + prcInc.lowEdge = decode(float, fields) + prcInc.increment = decode(float, fields) + priceIncrements.append(prcInc) + + self.wrapper.marketRule(marketRuleId, priceIncrements) + + def processPnLMsg(self, fields): + next(fields) + reqId = decode(int, fields) + dailyPnL = decode(float, fields) + unrealizedPnL = None + realizedPnL = None + + if self.serverVersion >= MIN_SERVER_VER_UNREALIZED_PNL: + unrealizedPnL = decode(float, fields) + + if self.serverVersion >= MIN_SERVER_VER_REALIZED_PNL: + realizedPnL = decode(float, fields) + + self.wrapper.pnl(reqId, dailyPnL, unrealizedPnL, realizedPnL) + + def processPnLSingleMsg(self, fields): + next(fields) + reqId = decode(int, fields) + pos = decode(int, fields) + dailyPnL = decode(float, fields) + unrealizedPnL = None + realizedPnL = None + + if self.serverVersion >= MIN_SERVER_VER_UNREALIZED_PNL: + unrealizedPnL = decode(float, fields) + + if self.serverVersion >= MIN_SERVER_VER_REALIZED_PNL: + realizedPnL = decode(float, fields) + + value = decode(float, fields) + + self.wrapper.pnlSingle(reqId, pos, dailyPnL, unrealizedPnL, realizedPnL, value) + + def processHistoricalTicks(self, fields): + next(fields) + reqId = decode(int, fields) + tickCount = decode(int, fields) + + ticks = [] + + for _ in range(tickCount): + historicalTick = HistoricalTick() + historicalTick.time = decode(int, fields) + next(fields) # for consistency + historicalTick.price = decode(float, fields) + historicalTick.size = decode(int, fields) + ticks.append(historicalTick) + + done = decode(bool, fields) + + self.wrapper.historicalTicks(reqId, ticks, done) + + def processHistoricalTicksBidAsk(self, fields): + next(fields) + reqId = decode(int, fields) + tickCount = decode(int, fields) + + ticks = [] + + for _ in range(tickCount): + historicalTickBidAsk = HistoricalTickBidAsk() + historicalTickBidAsk.time = decode(int, fields) + mask = decode(int, fields) + tickAttribBidAsk = TickAttribBidAsk() + tickAttribBidAsk.askPastHigh = mask & 1 != 0 + tickAttribBidAsk.bidPastLow = mask & 2 != 0 + historicalTickBidAsk.tickAttribBidAsk = tickAttribBidAsk + historicalTickBidAsk.priceBid = decode(float, fields) + historicalTickBidAsk.priceAsk = decode(float, fields) + historicalTickBidAsk.sizeBid = decode(int, fields) + historicalTickBidAsk.sizeAsk = decode(int, fields) + ticks.append(historicalTickBidAsk) + + done = decode(bool, fields) + + self.wrapper.historicalTicksBidAsk(reqId, ticks, done) + + def processHistoricalTicksLast(self, fields): + next(fields) + reqId = decode(int, fields) + tickCount = decode(int, fields) + + ticks = [] + + for _ in range(tickCount): + historicalTickLast = HistoricalTickLast() + historicalTickLast.time = decode(int, fields) + mask = decode(int, fields) + tickAttribLast = TickAttribLast() + tickAttribLast.pastLimit = mask & 1 != 0 + tickAttribLast.unreported = mask & 2 != 0 + historicalTickLast.tickAttribLast = tickAttribLast + historicalTickLast.price = decode(float, fields) + historicalTickLast.size = decode(int, fields) + historicalTickLast.exchange = decode(str, fields) + historicalTickLast.specialConditions = decode(str, fields) + ticks.append(historicalTickLast) + + done = decode(bool, fields) + + self.wrapper.historicalTicksLast(reqId, ticks, done) + + def processTickByTickMsg(self, fields): + next(fields) + reqId = decode(int, fields) + tickType = decode(int, fields) + time = decode(int, fields) + + if tickType == 0: + # None + pass + elif tickType == 1 or tickType == 2: + # Last or AllLast + price = decode(float, fields) + size = decode(int, fields) + mask = decode(int, fields) + + tickAttribLast = TickAttribLast() + tickAttribLast.pastLimit = mask & 1 != 0 + tickAttribLast.unreported = mask & 2 != 0 + exchange = decode(str, fields) + specialConditions = decode(str, fields) + + self.wrapper.tickByTickAllLast(reqId, tickType, time, price, size, tickAttribLast, + exchange, specialConditions) + elif tickType == 3: + # BidAsk + bidPrice = decode(float, fields) + askPrice = decode(float, fields) + bidSize = decode(int, fields) + askSize = decode(int, fields) + mask = decode(int, fields) + tickAttribBidAsk = TickAttribBidAsk() + tickAttribBidAsk.bidPastLow = mask & 1 != 0 + tickAttribBidAsk.askPastHigh = mask & 2 != 0 + + self.wrapper.tickByTickBidAsk(reqId, time, bidPrice, askPrice, bidSize, + askSize, tickAttribBidAsk) + elif tickType == 4: + # MidPoint + midPoint = decode(float, fields) + + self.wrapper.tickByTickMidPoint(reqId, time, midPoint) + + def processOrderBoundMsg(self, fields): + next(fields) + reqId = decode(int, fields) + apiClientId = decode(int, fields) + apiOrderId = decode(int, fields) + + self.wrapper.orderBound(reqId, apiClientId, apiOrderId) + + def processMarketDepthL2Msg(self, fields): + next(fields) + decode(int, fields) + reqId = decode(int, fields) + + position = decode(int, fields) + marketMaker = decode(str, fields) + operation = decode(int, fields) + side = decode(int, fields) + price = decode(float, fields) + size = decode(int, fields) + isSmartDepth = False + + if self.serverVersion >= MIN_SERVER_VER_SMART_DEPTH: + isSmartDepth = decode(bool, fields) + + self.wrapper.updateMktDepthL2(reqId, position, marketMaker, + operation, side, price, size, isSmartDepth) + + ###################################################################### + + def readLastTradeDate(self, fields, contract: ContractDetails, isBond: bool): + lastTradeDateOrContractMonth = decode(str, fields) + if lastTradeDateOrContractMonth is not None: + splitted = lastTradeDateOrContractMonth.split() + if len(splitted) > 0: + if isBond: + contract.maturity = splitted[0] + else: + contract.contract.lastTradeDateOrContractMonth = splitted[0] + + if len(splitted) > 1: + contract.lastTradeTime = splitted[1] + + if isBond and len(splitted) > 2: + contract.timeZoneId = splitted[2] + + ###################################################################### + + def discoverParams(self): + meth2handleInfo = {} + for handleInfo in self.msgId2handleInfo.values(): + meth2handleInfo[handleInfo.wrapperMeth] = handleInfo + + methods = inspect.getmembers(EWrapper, inspect.isfunction) + for (_, meth) in methods: + #logger.debug("meth %s", name) + sig = inspect.signature(meth) + handleInfo = meth2handleInfo.get(meth, None) + if handleInfo is not None: + handleInfo.wrapperParams = sig.parameters + + #for (pname, param) in sig.parameters.items(): + # logger.debug("\tparam %s %s %s", pname, param.name, param.annotation) + + + def printParams(self): + for (_, handleInfo) in self.msgId2handleInfo.items(): + if handleInfo.wrapperMeth is not None: + logger.debug("meth %s", handleInfo.wrapperMeth.__name__) + if handleInfo.wrapperParams is not None: + for (pname, param) in handleInfo.wrapperParams.items(): + logger.debug("\tparam %s %s %s", pname, param.name, param.annotation) + + + def interpretWithSignature(self, fields, handleInfo): + if handleInfo.wrapperParams is None: + logger.debug("%s: no param info in %s", fields, handleInfo) + return + + nIgnoreFields = 2 #bypass msgId and versionId faster this way + if len(fields) - nIgnoreFields != len(handleInfo.wrapperParams) - 1: + logger.error("diff len fields and params %d %d for fields: %s and handleInfo: %s", + len(fields), len(handleInfo.wrapperParams), fields, + handleInfo) + return + + fieldIdx = nIgnoreFields + args = [] + for (pname, param) in handleInfo.wrapperParams.items(): + if pname != "self": + logger.debug("field %s ", fields[fieldIdx]) + try: + arg = fields[fieldIdx].decode('UTF-8') + except UnicodeDecodeError: + arg = fields[fieldIdx].decode('latin-1') + logger.debug("arg %s type %s", arg, param.annotation) + if param.annotation is int: + arg = int(arg) + elif param.annotation is float: + arg = float(arg) + + args.append(arg) + fieldIdx += 1 + + method = getattr(self.wrapper, handleInfo.wrapperMeth.__name__) + logger.debug("calling %s with %s %s", method, self.wrapper, args) + method(*args) + + def interpret(self, fields): + if len(fields) == 0: + logger.debug("no fields") + return + + sMsgId = fields[0] + nMsgId = int(sMsgId) + + handleInfo = self.msgId2handleInfo.get(nMsgId, None) + + if handleInfo is None: + logger.debug("%s: no handleInfo", fields) + return + + try: + if handleInfo.wrapperMeth is not None: + logger.debug("In interpret(), handleInfo: %s", handleInfo) + self.interpretWithSignature(fields, handleInfo) + elif handleInfo.processMeth is not None: + handleInfo.processMeth(self, iter(fields)) + except BadMessage: + theBadMsg = ",".join(fields) + self.wrapper.error(NO_VALID_ID, BAD_MESSAGE.code(), + BAD_MESSAGE.msg() + theBadMsg) + raise + + + msgId2handleInfo = { + IN.TICK_PRICE: HandleInfo(proc=processTickPriceMsg), + IN.TICK_SIZE: HandleInfo(wrap=EWrapper.tickSize), + IN.ORDER_STATUS: HandleInfo(proc=processOrderStatusMsg), + IN.ERR_MSG: HandleInfo(wrap=EWrapper.error), + IN.OPEN_ORDER: HandleInfo(proc=processOpenOrder), + IN.ACCT_VALUE: HandleInfo(wrap=EWrapper.updateAccountValue), + IN.PORTFOLIO_VALUE: HandleInfo(proc=processPortfolioValueMsg), + IN.ACCT_UPDATE_TIME: HandleInfo(wrap=EWrapper.updateAccountTime), + IN.NEXT_VALID_ID: HandleInfo(wrap=EWrapper.nextValidId, ), + IN.CONTRACT_DATA: HandleInfo(proc=processContractDataMsg), + IN.EXECUTION_DATA: HandleInfo(proc=processExecutionDataMsg), + IN.MARKET_DEPTH: HandleInfo(wrap=EWrapper.updateMktDepth), + IN.MARKET_DEPTH_L2: HandleInfo(proc=processMarketDepthL2Msg), + IN.NEWS_BULLETINS: HandleInfo(wrap=EWrapper.updateNewsBulletin), + IN.MANAGED_ACCTS: HandleInfo(wrap=EWrapper.managedAccounts), + IN.RECEIVE_FA: HandleInfo(wrap=EWrapper.receiveFA), + IN.HISTORICAL_DATA: HandleInfo(proc=processHistoricalDataMsg), + IN.HISTORICAL_DATA_UPDATE: HandleInfo(proc=processHistoricalDataUpdateMsg), + IN.BOND_CONTRACT_DATA: HandleInfo(proc=processBondContractDataMsg), + IN.SCANNER_PARAMETERS: HandleInfo(wrap=EWrapper.scannerParameters), + IN.SCANNER_DATA: HandleInfo(proc=processScannerDataMsg), + IN.TICK_OPTION_COMPUTATION: HandleInfo(proc=processTickOptionComputationMsg), + IN.TICK_GENERIC: HandleInfo(wrap=EWrapper.tickGeneric), + IN.TICK_STRING: HandleInfo(wrap=EWrapper.tickString), + IN.TICK_EFP: HandleInfo(wrap=EWrapper.tickEFP), + IN.CURRENT_TIME: HandleInfo(wrap=EWrapper.currentTime), + IN.REAL_TIME_BARS: HandleInfo(proc=processRealTimeBarMsg), + IN.FUNDAMENTAL_DATA: HandleInfo(wrap=EWrapper.fundamentalData), + IN.CONTRACT_DATA_END: HandleInfo(wrap=EWrapper.contractDetailsEnd), + IN.OPEN_ORDER_END: HandleInfo(wrap=EWrapper.openOrderEnd), + IN.ACCT_DOWNLOAD_END: HandleInfo(wrap=EWrapper.accountDownloadEnd), + IN.EXECUTION_DATA_END: HandleInfo(wrap=EWrapper.execDetailsEnd), + IN.DELTA_NEUTRAL_VALIDATION: HandleInfo(proc=processDeltaNeutralValidationMsg), + IN.TICK_SNAPSHOT_END: HandleInfo(wrap=EWrapper.tickSnapshotEnd), + IN.MARKET_DATA_TYPE: HandleInfo(wrap=EWrapper.marketDataType), + IN.COMMISSION_REPORT: HandleInfo(proc=processCommissionReportMsg), + IN.POSITION_DATA: HandleInfo(proc=processPositionDataMsg), + IN.POSITION_END: HandleInfo(wrap=EWrapper.positionEnd), + IN.ACCOUNT_SUMMARY: HandleInfo(wrap=EWrapper.accountSummary), + IN.ACCOUNT_SUMMARY_END: HandleInfo(wrap=EWrapper.accountSummaryEnd), + IN.VERIFY_MESSAGE_API: HandleInfo(wrap=EWrapper.verifyMessageAPI), + IN.VERIFY_COMPLETED: HandleInfo(wrap=EWrapper.verifyCompleted), + IN.DISPLAY_GROUP_LIST: HandleInfo(wrap=EWrapper.displayGroupList), + IN.DISPLAY_GROUP_UPDATED: HandleInfo(wrap=EWrapper.displayGroupUpdated), + IN.VERIFY_AND_AUTH_MESSAGE_API: HandleInfo(wrap=EWrapper.verifyAndAuthMessageAPI), + IN.VERIFY_AND_AUTH_COMPLETED: HandleInfo(wrap=EWrapper.verifyAndAuthCompleted), + IN.POSITION_MULTI: HandleInfo(proc=processPositionMultiMsg), + IN.POSITION_MULTI_END: HandleInfo(wrap=EWrapper.positionMultiEnd), + IN.ACCOUNT_UPDATE_MULTI: HandleInfo(wrap=EWrapper.accountUpdateMulti), + IN.ACCOUNT_UPDATE_MULTI_END: HandleInfo(wrap=EWrapper.accountUpdateMultiEnd), + IN.SECURITY_DEFINITION_OPTION_PARAMETER: HandleInfo(proc=processSecurityDefinitionOptionParameterMsg), + IN.SECURITY_DEFINITION_OPTION_PARAMETER_END: HandleInfo(proc=processSecurityDefinitionOptionParameterEndMsg), + IN.SOFT_DOLLAR_TIERS: HandleInfo(proc=processSoftDollarTiersMsg), + IN.FAMILY_CODES: HandleInfo(proc=processFamilyCodesMsg), + IN.SYMBOL_SAMPLES: HandleInfo(proc=processSymbolSamplesMsg), + IN.SMART_COMPONENTS: HandleInfo(proc=processSmartComponents), + IN.TICK_REQ_PARAMS: HandleInfo(proc=processTickReqParams), + IN.MKT_DEPTH_EXCHANGES: HandleInfo(proc=processMktDepthExchanges), + IN.HEAD_TIMESTAMP: HandleInfo(proc=processHeadTimestamp), + IN.TICK_NEWS: HandleInfo(proc=processTickNews), + IN.NEWS_PROVIDERS: HandleInfo(proc=processNewsProviders), + IN.NEWS_ARTICLE: HandleInfo(proc=processNewsArticle), + IN.HISTORICAL_NEWS: HandleInfo(proc=processHistoricalNews), + IN.HISTORICAL_NEWS_END: HandleInfo(proc=processHistoricalNewsEnd), + IN.HISTOGRAM_DATA: HandleInfo(proc=processHistogramData), + IN.REROUTE_MKT_DATA_REQ: HandleInfo(proc=processRerouteMktDataReq), + IN.REROUTE_MKT_DEPTH_REQ: HandleInfo(proc=processRerouteMktDepthReq), + IN.MARKET_RULE: HandleInfo(proc=processMarketRuleMsg), + IN.PNL: HandleInfo(proc=processPnLMsg), + IN.PNL_SINGLE: HandleInfo(proc=processPnLSingleMsg), + IN.HISTORICAL_TICKS: HandleInfo(proc=processHistoricalTicks), + IN.HISTORICAL_TICKS_BID_ASK: HandleInfo(proc=processHistoricalTicksBidAsk), + IN.HISTORICAL_TICKS_LAST: HandleInfo(proc=processHistoricalTicksLast), + IN.TICK_BY_TICK: HandleInfo(proc=processTickByTickMsg), + IN.ORDER_BOUND: HandleInfo(proc=processOrderBoundMsg) + } + + + diff --git a/vnpy/api/ib/enum_implem.py b/vnpy/api/ib/enum_implem.py new file mode 100644 index 00000000..cc5ff395 --- /dev/null +++ b/vnpy/api/ib/enum_implem.py @@ -0,0 +1,22 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" + Simple enum implementation +""" + + +class Enum: + def __init__(self, *args): + self.idx2name = {} + for (idx, name) in enumerate(args): + setattr(self, name, idx) + self.idx2name[idx] = name + + def to_str(self, idx): + return self.idx2name.get(idx, "NOTFOUND") + + diff --git a/vnpy/api/ib/errors.py b/vnpy/api/ib/errors.py new file mode 100644 index 00000000..b45a1558 --- /dev/null +++ b/vnpy/api/ib/errors.py @@ -0,0 +1,41 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +This is the interface that will need to be overloaded by the customer so +that his/her code can receive info from the TWS/IBGW. +""" + + +class CodeMsgPair: + def __init__(self, code, msg): + self.errorCode = code + self.errorMsg = msg + + def code(self): + return self.errorCode + + def msg(self): + return self.errorMsg + + +ALREADY_CONNECTED = CodeMsgPair(501, "Already connected.") +CONNECT_FAIL = CodeMsgPair(502, +"""Couldn't connect to TWS. Confirm that \"Enable ActiveX and Socket EClients\" +is enabled and connection port is the same as \"Socket Port\" on the +TWS \"Edit->Global Configuration...->API->Settings\" menu. Live Trading ports: +TWS: 7496; IB Gateway: 4001. Simulated Trading ports for new installations +of version 954.1 or newer: TWS: 7497; IB Gateway: 4002""") +UPDATE_TWS = CodeMsgPair(503, "The TWS is out of date and must be upgraded.") +NOT_CONNECTED = CodeMsgPair(504, "Not connected") +UNKNOWN_ID = CodeMsgPair(505, "Fatal Error: Unknown message id.") +UNSUPPORTED_VERSION = CodeMsgPair(506, "Unsupported version") +BAD_LENGTH = CodeMsgPair(507, "Bad message length") +BAD_MESSAGE = CodeMsgPair(508, "Bad message") +SOCKET_EXCEPTION = CodeMsgPair(509, "Exception caught while reading socket - ") +FAIL_CREATE_SOCK = CodeMsgPair(520, "Failed to create socket") +SSL_FAIL = CodeMsgPair(530, "SSL specific error: ") + diff --git a/vnpy/api/ib/execution.py b/vnpy/api/ib/execution.py new file mode 100644 index 00000000..fabc83d0 --- /dev/null +++ b/vnpy/api/ib/execution.py @@ -0,0 +1,50 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + + +from ibapi.object_implem import Object + +class Execution(Object): + + def __init__(self): + self.execId = "" + self.time = "" + self.acctNumber = "" + self.exchange = "" + self.side = "" + self.shares = 0. + self.price = 0. + self.permId = 0 + self.clientId = 0 + self.orderId = 0 + self.liquidation = 0 + self.cumQty = 0. + self.avgPrice = 0. + self.orderRef = "" + self.evRule = "" + self.evMultiplier = 0. + self.modelCode = "" + self.lastLiquidity = 0 + + def __str__(self): + return "ExecId: %s, Time: %s, Account: %s, Exchange: %s, Side: %s, Shares: %f, Price: %f, PermId: %d, " \ + "ClientId: %d, OrderId: %d, Liquidation: %d, CumQty: %f, AvgPrice: %f, OrderRef: %s, EvRule: %s, " \ + "EvMultiplier: %f, ModelCode: %s, LastLiquidity: %d" % (self.execId, self.time, self.acctNumber, + self.exchange, self.side, self.shares, self.price, self.permId, self.clientId, self.orderId, self.liquidation, + self.cumQty, self.avgPrice, self.orderRef, self.evRule, self.evMultiplier, self.modelCode, self.lastLiquidity) + +class ExecutionFilter(Object): + + # Filter fields + def __init__(self): + self.clientId = 0 + self.acctCode = "" + self.time = "" + self.symbol = "" + self.secType = "" + self.exchange = "" + self.side = "" + diff --git a/vnpy/api/ib/message.py b/vnpy/api/ib/message.py new file mode 100644 index 00000000..8b8660ce --- /dev/null +++ b/vnpy/api/ib/message.py @@ -0,0 +1,171 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +High level IB message info. +""" + + +# field types +INT = 1 +STR = 2 +FLT = 3 + + +# incoming msg id's +class IN: + TICK_PRICE = 1 + TICK_SIZE = 2 + ORDER_STATUS = 3 + ERR_MSG = 4 + OPEN_ORDER = 5 + ACCT_VALUE = 6 + PORTFOLIO_VALUE = 7 + ACCT_UPDATE_TIME = 8 + NEXT_VALID_ID = 9 + CONTRACT_DATA = 10 + EXECUTION_DATA = 11 + MARKET_DEPTH = 12 + MARKET_DEPTH_L2 = 13 + NEWS_BULLETINS = 14 + MANAGED_ACCTS = 15 + RECEIVE_FA = 16 + HISTORICAL_DATA = 17 + BOND_CONTRACT_DATA = 18 + SCANNER_PARAMETERS = 19 + SCANNER_DATA = 20 + TICK_OPTION_COMPUTATION = 21 + TICK_GENERIC = 45 + TICK_STRING = 46 + TICK_EFP = 47 + CURRENT_TIME = 49 + REAL_TIME_BARS = 50 + FUNDAMENTAL_DATA = 51 + CONTRACT_DATA_END = 52 + OPEN_ORDER_END = 53 + ACCT_DOWNLOAD_END = 54 + EXECUTION_DATA_END = 55 + DELTA_NEUTRAL_VALIDATION = 56 + TICK_SNAPSHOT_END = 57 + MARKET_DATA_TYPE = 58 + COMMISSION_REPORT = 59 + POSITION_DATA = 61 + POSITION_END = 62 + ACCOUNT_SUMMARY = 63 + ACCOUNT_SUMMARY_END = 64 + VERIFY_MESSAGE_API = 65 + VERIFY_COMPLETED = 66 + DISPLAY_GROUP_LIST = 67 + DISPLAY_GROUP_UPDATED = 68 + VERIFY_AND_AUTH_MESSAGE_API = 69 + VERIFY_AND_AUTH_COMPLETED = 70 + POSITION_MULTI = 71 + POSITION_MULTI_END = 72 + ACCOUNT_UPDATE_MULTI = 73 + ACCOUNT_UPDATE_MULTI_END = 74 + SECURITY_DEFINITION_OPTION_PARAMETER = 75 + SECURITY_DEFINITION_OPTION_PARAMETER_END = 76 + SOFT_DOLLAR_TIERS = 77 + FAMILY_CODES = 78 + SYMBOL_SAMPLES = 79 + MKT_DEPTH_EXCHANGES = 80 + TICK_REQ_PARAMS = 81 + SMART_COMPONENTS = 82 + NEWS_ARTICLE = 83 + TICK_NEWS = 84 + NEWS_PROVIDERS = 85 + HISTORICAL_NEWS = 86 + HISTORICAL_NEWS_END = 87 + HEAD_TIMESTAMP = 88 + HISTOGRAM_DATA = 89 + HISTORICAL_DATA_UPDATE = 90 + REROUTE_MKT_DATA_REQ = 91 + REROUTE_MKT_DEPTH_REQ = 92 + MARKET_RULE = 93 + PNL = 94 + PNL_SINGLE = 95 + HISTORICAL_TICKS = 96 + HISTORICAL_TICKS_BID_ASK = 97 + HISTORICAL_TICKS_LAST = 98 + TICK_BY_TICK = 99 + ORDER_BOUND = 100 + +# outgoing msg id's +class OUT: + REQ_MKT_DATA = 1 + CANCEL_MKT_DATA = 2 + PLACE_ORDER = 3 + CANCEL_ORDER = 4 + REQ_OPEN_ORDERS = 5 + REQ_ACCT_DATA = 6 + REQ_EXECUTIONS = 7 + REQ_IDS = 8 + REQ_CONTRACT_DATA = 9 + REQ_MKT_DEPTH = 10 + CANCEL_MKT_DEPTH = 11 + REQ_NEWS_BULLETINS = 12 + CANCEL_NEWS_BULLETINS = 13 + SET_SERVER_LOGLEVEL = 14 + REQ_AUTO_OPEN_ORDERS = 15 + REQ_ALL_OPEN_ORDERS = 16 + REQ_MANAGED_ACCTS = 17 + REQ_FA = 18 + REPLACE_FA = 19 + REQ_HISTORICAL_DATA = 20 + EXERCISE_OPTIONS = 21 + REQ_SCANNER_SUBSCRIPTION = 22 + CANCEL_SCANNER_SUBSCRIPTION = 23 + REQ_SCANNER_PARAMETERS = 24 + CANCEL_HISTORICAL_DATA = 25 + REQ_CURRENT_TIME = 49 + REQ_REAL_TIME_BARS = 50 + CANCEL_REAL_TIME_BARS = 51 + REQ_FUNDAMENTAL_DATA = 52 + CANCEL_FUNDAMENTAL_DATA = 53 + REQ_CALC_IMPLIED_VOLAT = 54 + REQ_CALC_OPTION_PRICE = 55 + CANCEL_CALC_IMPLIED_VOLAT = 56 + CANCEL_CALC_OPTION_PRICE = 57 + REQ_GLOBAL_CANCEL = 58 + REQ_MARKET_DATA_TYPE = 59 + REQ_POSITIONS = 61 + REQ_ACCOUNT_SUMMARY = 62 + CANCEL_ACCOUNT_SUMMARY = 63 + CANCEL_POSITIONS = 64 + VERIFY_REQUEST = 65 + VERIFY_MESSAGE = 66 + QUERY_DISPLAY_GROUPS = 67 + SUBSCRIBE_TO_GROUP_EVENTS = 68 + UPDATE_DISPLAY_GROUP = 69 + UNSUBSCRIBE_FROM_GROUP_EVENTS = 70 + START_API = 71 + VERIFY_AND_AUTH_REQUEST = 72 + VERIFY_AND_AUTH_MESSAGE = 73 + REQ_POSITIONS_MULTI = 74 + CANCEL_POSITIONS_MULTI = 75 + REQ_ACCOUNT_UPDATES_MULTI = 76 + CANCEL_ACCOUNT_UPDATES_MULTI = 77 + REQ_SEC_DEF_OPT_PARAMS = 78 + REQ_SOFT_DOLLAR_TIERS = 79 + REQ_FAMILY_CODES = 80 + REQ_MATCHING_SYMBOLS = 81 + REQ_MKT_DEPTH_EXCHANGES = 82 + REQ_SMART_COMPONENTS = 83 + REQ_NEWS_ARTICLE = 84 + REQ_NEWS_PROVIDERS = 85 + REQ_HISTORICAL_NEWS = 86 + REQ_HEAD_TIMESTAMP = 87 + REQ_HISTOGRAM_DATA = 88 + CANCEL_HISTOGRAM_DATA = 89 + CANCEL_HEAD_TIMESTAMP = 90 + REQ_MARKET_RULE = 91 + REQ_PNL = 92 + CANCEL_PNL = 93 + REQ_PNL_SINGLE = 94 + CANCEL_PNL_SINGLE = 95 + REQ_HISTORICAL_TICKS = 96 + REQ_TICK_BY_TICK_DATA = 97 + CANCEL_TICK_BY_TICK_DATA = 98 diff --git a/vnpy/api/ib/news.py b/vnpy/api/ib/news.py new file mode 100644 index 00000000..9b79461f --- /dev/null +++ b/vnpy/api/ib/news.py @@ -0,0 +1,10 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +# TWS New Bulletins constants +NEWS_MSG = 1 # standard IB news bulleting message +EXCHANGE_AVAIL_MSG = 2 # control message specifing that an exchange is available for trading +EXCHANGE_UNAVAIL_MSG = 3 # control message specifing that an exchange is unavailable for trading + diff --git a/vnpy/api/ib/object_implem.py b/vnpy/api/ib/object_implem.py new file mode 100644 index 00000000..7f700b90 --- /dev/null +++ b/vnpy/api/ib/object_implem.py @@ -0,0 +1,14 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +class Object(object): + + def __str__(self): + return "Object" + + def __repr__(self): + return str(id(self)) + ": " + self.__str__() + + diff --git a/vnpy/api/ib/order.py b/vnpy/api/ib/order.py new file mode 100644 index 00000000..0aa703ad --- /dev/null +++ b/vnpy/api/ib/order.py @@ -0,0 +1,226 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE +from ibapi.object_implem import Object +from ibapi.softdollartier import SoftDollarTier + +# enum Origin +(CUSTOMER, FIRM, UNKNOWN) = range(3) + +# enum AuctionStrategy +(AUCTION_UNSET, AUCTION_MATCH, + AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT) = range(4) + + +class OrderComboLeg(Object): + def __init__(self): + self.price = UNSET_DOUBLE # type: float + + def __str__(self): + return "%f" % self.price + + +class Order(Object): + def __init__(self): + self.softDollarTier = SoftDollarTier("", "", "") + # order identifier + self.orderId = 0 + self.clientId = 0 + self.permId = 0 + + # main order fields + self.action = "" + self.totalQuantity = 0 + self.orderType = "" + self.lmtPrice = UNSET_DOUBLE + self.auxPrice = UNSET_DOUBLE + + # extended order fields + self.tif = "" # "Time in Force" - DAY, GTC, etc. + self.activeStartTime = "" # for GTC orders + self.activeStopTime = "" # for GTC orders + self.ocaGroup = "" # one cancels all group name + self.ocaType = 0 # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK + self.orderRef = "" + self.transmit = True # if false, order will be created but not transmited + self.parentId = 0 # Parent order Id, to associate Auto STP or TRAIL orders with the original order. + self.blockOrder = False + self.sweepToFill = False + self.displaySize = 0 + self.triggerMethod = 0 # 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last, 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point + self.outsideRth = False + self.hidden = False + self.goodAfterTime = "" # Format: 20060505 08:00:00 {time zone} + self.goodTillDate = "" # Format: 20060505 08:00:00 {time zone} + self.rule80A = "" # Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N' + self.allOrNone = False + self.minQty = UNSET_INTEGER #type: int + self.percentOffset = UNSET_DOUBLE # type: float; REL orders only + self.overridePercentageConstraints = False + self.trailStopPrice = UNSET_DOUBLE # type: float + self.trailingPercent = UNSET_DOUBLE # type: float; TRAILLIMIT orders only + + # financial advisors only + self.faGroup = "" + self.faProfile = "" + self.faMethod = "" + self.faPercentage = "" + + # institutional (ie non-cleared) only + self.designatedLocation = "" #used only when shortSaleSlot=2 + self.openClose = "O" # O=Open, C=Close + self.origin = CUSTOMER # 0=Customer, 1=Firm + self.shortSaleSlot = 0 # type: int; 1 if you hold the shares, 2 if they will be delivered from elsewhere. Only for Action=SSHORT + self.exemptCode = -1 + + # SMART routing only + self.discretionaryAmt = 0 + self.eTradeOnly = True + self.firmQuoteOnly = True + self.nbboPriceCap = UNSET_DOUBLE # type: float + self.optOutSmartRouting = False + + # BOX exchange orders only + self.auctionStrategy = AUCTION_UNSET # type: int; AUCTION_MATCH, AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT + self.startingPrice = UNSET_DOUBLE # type: float + self.stockRefPrice = UNSET_DOUBLE # type: float + self.delta = UNSET_DOUBLE # type: float + + # pegged to stock and VOL orders only + self.stockRangeLower = UNSET_DOUBLE # type: float + self.stockRangeUpper = UNSET_DOUBLE # type: float + + self.randomizePrice = False + self.randomizeSize = False + + # VOLATILITY ORDERS ONLY + self.volatility = UNSET_DOUBLE # type: float + self.volatilityType = UNSET_INTEGER # type: int # 1=daily, 2=annual + self.deltaNeutralOrderType = "" + self.deltaNeutralAuxPrice = UNSET_DOUBLE # type: float + self.deltaNeutralConId = 0 + self.deltaNeutralSettlingFirm = "" + self.deltaNeutralClearingAccount = "" + self.deltaNeutralClearingIntent = "" + self.deltaNeutralOpenClose = "" + self.deltaNeutralShortSale = False + self.deltaNeutralShortSaleSlot = 0 + self.deltaNeutralDesignatedLocation = "" + self.continuousUpdate = False + self.referencePriceType = UNSET_INTEGER # type: int; 1=Average, 2 = BidOrAsk + + # COMBO ORDERS ONLY + self.basisPoints = UNSET_DOUBLE # type: float; EFP orders only + self.basisPointsType = UNSET_INTEGER # type: int; EFP orders only + + # SCALE ORDERS ONLY + self.scaleInitLevelSize = UNSET_INTEGER # type: int + self.scaleSubsLevelSize = UNSET_INTEGER # type: int + self.scalePriceIncrement = UNSET_DOUBLE # type: float + self.scalePriceAdjustValue = UNSET_DOUBLE # type: float + self.scalePriceAdjustInterval = UNSET_INTEGER # type: int + self.scaleProfitOffset = UNSET_DOUBLE # type: float + self.scaleAutoReset = False + self.scaleInitPosition = UNSET_INTEGER # type: int + self.scaleInitFillQty = UNSET_INTEGER # type: int + self.scaleRandomPercent = False + self.scaleTable = "" + + # HEDGE ORDERS + self.hedgeType = "" # 'D' - delta, 'B' - beta, 'F' - FX, 'P' - pair + self.hedgeParam = "" # 'beta=X' value for beta hedge, 'ratio=Y' for pair hedge + + # Clearing info + self.account = "" # IB account + self.settlingFirm = "" + self.clearingAccount = "" #True beneficiary of the order + self.clearingIntent = "" # "" (Default), "IB", "Away", "PTA" (PostTrade) + + # ALGO ORDERS ONLY + self.algoStrategy = "" + + self.algoParams = None #TagValueList + self.smartComboRoutingParams = None #TagValueList + + self.algoId = "" + + # What-if + self.whatIf = False + + # Not Held + self.notHeld = False + self.solicited = False + + # models + self.modelCode = "" + + # order combo legs + + self.orderComboLegs = None # OrderComboLegListSPtr + + self.orderMiscOptions = None # TagValueList + + # VER PEG2BENCH fields: + self.referenceContractId = 0 + self.peggedChangeAmount = 0. + self.isPeggedChangeAmountDecrease = False + self.referenceChangeAmount = 0. + self.referenceExchangeId = "" + self.adjustedOrderType = "" + + self.triggerPrice = UNSET_DOUBLE + self.adjustedStopPrice = UNSET_DOUBLE + self.adjustedStopLimitPrice = UNSET_DOUBLE + self.adjustedTrailingAmount = UNSET_DOUBLE + self.adjustableTrailingUnit = 0 + self.lmtPriceOffset = UNSET_DOUBLE + + self.conditions = [] # std::vector> + self.conditionsCancelOrder = False + self.conditionsIgnoreRth = False + + # ext operator + self.extOperator = "" + + # native cash quantity + self.cashQty = UNSET_DOUBLE + + self.mifid2DecisionMaker = "" + self.mifid2DecisionAlgo = "" + self.mifid2ExecutionTrader = "" + self.mifid2ExecutionAlgo = "" + + self.dontUseAutoPriceForHedge = False + + self.isOmsContainer = False + + self.discretionaryUpToLimitPrice = False + + def __str__(self): + s = "%s,%d,%s:" % (self.orderId, self.clientId, self.permId) + + s += " %s %s %d@%f" % ( + self.orderType, + self.action, + self.totalQuantity, + self.lmtPrice) + + s += " %s" % self.tif + + if self.orderComboLegs: + s += " CMB(" + for leg in self.orderComboLegs: + s += str(leg) + "," + s += ")" + + if self.conditions: + s += " COND(" + for cond in self.conditions: + s += str(cond) + "," + s += ")" + + return s diff --git a/vnpy/api/ib/order_condition.py b/vnpy/api/ib/order_condition.py new file mode 100644 index 00000000..c28926aa --- /dev/null +++ b/vnpy/api/ib/order_condition.py @@ -0,0 +1,282 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +from ibapi import comm +from ibapi.common import UNSET_DOUBLE +from ibapi.object_implem import Object +from ibapi.enum_implem import Enum +from ibapi.utils import decode + +#TODO: add support for Rebate, P/L, ShortableShares conditions + + + +class OrderCondition(Object): + Price = 1 + Time = 3 + Margin = 4 + Execution = 5 + Volume = 6 + PercentChange = 7 + + def __init__(self, condType): + self.condType = condType + self.isConjunctionConnection = True + + def type(self): + return self.condType + + def And(self): + self.isConjunctionConnection = True + return self + + def Or(self): + self.isConjunctionConnection = False + return self + + def decode(self, fields): + connector = decode(str, fields) + self.isConjunctionConnection = connector == "a" + + def make_fields(self): + flds = [] + flds.append(comm.make_field("a" if self.isConjunctionConnection else "o")) + return flds + + def __str__(self): + return "" if self.isConjunctionConnection else "" + + +class ExecutionCondition(OrderCondition): + + def __init__(self, secType=None, exch=None, symbol=None): + OrderCondition.__init__(self, OrderCondition.Execution) + self.secType = secType + self.exchange = exch + self.symbol = symbol + + def decode(self, fields): + OrderCondition.decode(self, fields) + self.secType = decode(str, fields) + self.exchange = decode(str, fields) + self.symbol = decode(str, fields) + + def make_fields(self): + flds = OrderCondition.make_fields(self) + \ + [comm.make_field(self.secType), + comm.make_field(self.exchange), + comm.make_field(self.symbol)] + return flds + + def __str__(self): + return "trade occurs for " + self.symbol + " symbol on " + \ + self.exchange + " exchange for " + self.secType + " security type" + + +class OperatorCondition(OrderCondition): + def __init__(self, condType=None, isMore=None): + OrderCondition.__init__(self, condType) + self.isMore = isMore + + def valueToString(self) -> str: + raise NotImplementedError("abstractmethod!") + + def setValueFromString(self, text: str) -> None: + raise NotImplementedError("abstractmethod!") + + def decode(self, fields): + OrderCondition.decode(self, fields) + self.isMore = decode(bool, fields) + text = decode(str, fields) + self.setValueFromString(text) + + def make_fields(self): + flds = OrderCondition.make_fields(self) + \ + [comm.make_field(self.isMore), + comm.make_field(self.valueToString()), ] + return flds + + def __str__(self): + sb = ">= " if self.isMore else "<= " + return " %s %s" % (sb, self.valueToString()) + + +class MarginCondition(OperatorCondition): + def __init__(self, isMore=None, percent=None): + OperatorCondition.__init__(self, OrderCondition.Margin, isMore) + self.percent = percent + + def decode(self, fields): + OperatorCondition.decode(self, fields) + + def make_fields(self): + flds = OperatorCondition.make_fields(self) + return flds + + def valueToString(self) -> str: + return str(self.percent) + + def setValueFromString(self, text: str) -> None: + self.percent = float(text) + + def __str__(self): + return "the margin cushion percent %s " % ( + OperatorCondition.__str__(self)) + + +class ContractCondition(OperatorCondition): + def __init__(self, condType=None, conId=None, exch=None, isMore=None): + OperatorCondition.__init__(self, condType, isMore) + self.conId = conId + self.exchange = exch + + def decode(self, fields): + OperatorCondition.decode(self, fields) + self.conId = decode(int, fields) + self.exchange = decode(str, fields) + + def make_fields(self): + flds = OperatorCondition.make_fields(self) + \ + [comm.make_field(self.conId), + comm.make_field(self.exchange), ] + return flds + + def __str__(self): + return "%s on %s is %s " % (self.conId, self.exchange, + OperatorCondition.__str__(self)) + + +class TimeCondition(OperatorCondition): + def __init__(self, isMore=None, time=None): + OperatorCondition.__init__(self, OrderCondition.Time, isMore) + self.time = time + + def decode(self, fields): + OperatorCondition.decode(self, fields) + + def make_fields(self): + flds = OperatorCondition.make_fields(self) + return flds + + def valueToString(self) -> str: + return self.time + + def setValueFromString(self, text: str) -> None: + self.time = text + + def __str__(self): + return "time is %s " % (OperatorCondition.__str__(self)) + + +class PriceCondition(ContractCondition): + TriggerMethodEnum = Enum( + "Default", # = 0, + "DoubleBidAsk", # = 1, + "Last", # = 2, + "DoubleLast", # = 3, + "BidAsk", # = 4, + "N/A1", + "N/A2", + "LastBidAsk", #= 7, + "MidPoint") #= 8 + + def __init__(self, triggerMethod=None, conId=None, exch=None, isMore=None, + price=None): + ContractCondition.__init__(self, OrderCondition.Price, conId, exch, + isMore) + self.price = price + self.triggerMethod = triggerMethod + + def decode(self, fields): + ContractCondition.decode(self, fields) + self.triggerMethod = decode(int, fields) + + def make_fields(self): + flds = ContractCondition.make_fields(self) + \ + [comm.make_field(self.triggerMethod), ] + return flds + + def valueToString(self) -> str: + return str(self.price) + + def setValueFromString(self, text: str) -> None: + self.price = float(text) + + def __str__(self): + return "%s price of %s " % ( + PriceCondition.TriggerMethodEnum.to_str(self.triggerMethod), + ContractCondition.__str__(self)) + + +class PercentChangeCondition(ContractCondition): + def __init__(self, conId=None, exch=None, isMore=None, + changePercent=UNSET_DOUBLE): + ContractCondition.__init__(self, OrderCondition.PercentChange, conId, + exch, isMore) + self.changePercent = changePercent + + def decode(self, fields): + ContractCondition.decode(self, fields) + + def make_fields(self): + flds = ContractCondition.make_fields(self) + return flds + + def valueToString(self) -> str: + return str(self.changePercent) + + def setValueFromString(self, text: str) -> None: + self.changePercent = float(text) + + def __str__(self): + return "percent change of %s " % ( + ContractCondition.__str__(self)) + + +class VolumeCondition(ContractCondition): + def __init__(self, conId=None, exch=None, isMore=None, volume=None): + ContractCondition.__init__(self, OrderCondition.Volume, conId, exch, + isMore) + self.volume = volume + + def decode(self, fields): + ContractCondition.decode(self, fields) + + def make_fields(self): + flds = ContractCondition.make_fields(self) + return flds + + def valueToString(self) -> str: + return str(self.volume) + + def setValueFromString(self, text: str) -> None: + self.volume = int(text) + + def __str__(self): + return "volume of %s " % ( + ContractCondition.__str__(self)) + + +def Create(condType): + cond = None + + if OrderCondition.Execution == condType: + cond = ExecutionCondition() + elif OrderCondition.Margin == condType: + cond = MarginCondition() + elif OrderCondition.PercentChange == condType: + cond = PercentChangeCondition() + elif OrderCondition.Price == condType: + cond = PriceCondition() + elif OrderCondition.Time == condType: + cond = TimeCondition() + elif OrderCondition.Volume == condType: + cond = VolumeCondition() + + return cond + + + diff --git a/vnpy/api/ib/order_state.py b/vnpy/api/ib/order_state.py new file mode 100644 index 00000000..0f8b11b9 --- /dev/null +++ b/vnpy/api/ib/order_state.py @@ -0,0 +1,28 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +from ibapi.common import UNSET_DOUBLE + + +class OrderState: + + def __init__(self): + self.status= "" + + self.initMarginBefore= "" + self.maintMarginBefore= "" + self.equityWithLoanBefore= "" + self.initMarginChange= "" + self.maintMarginChange= "" + self.equityWithLoanChange= "" + self.initMarginAfter= "" + self.maintMarginAfter= "" + self.equityWithLoanAfter= "" + + self.commission = UNSET_DOUBLE # type: float + self.minCommission = UNSET_DOUBLE # type: float + self.maxCommission = UNSET_DOUBLE # type: float + self.commissionCurrency = "" + self.warningText = "" diff --git a/vnpy/api/ib/reader.py b/vnpy/api/ib/reader.py new file mode 100644 index 00000000..9c7c525f --- /dev/null +++ b/vnpy/api/ib/reader.py @@ -0,0 +1,51 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +The EReader runs in a separate threads and is responsible for receiving the +incoming messages. +It will read the packets from the wire, use the low level IB messaging to +remove the size prefix and put the rest in a Queue. +""" + +import logging +from threading import Thread + +from ibapi import comm + + +logger = logging.getLogger(__name__) + + +class EReader(Thread): + def __init__(self, conn, msg_queue): + super().__init__() + self.conn = conn + self.msg_queue = msg_queue + + def run(self): + buf = b"" + while self.conn.isConnected(): + + data = self.conn.recvMsg() + logger.debug("reader loop, recvd size %d", len(data)) + buf += data + + while len(buf) > 0: + (size, msg, buf) = comm.read_msg(buf) + #logger.debug("resp %s", buf.decode('ascii')) + logger.debug("size:%d msg.size:%d msg:|%s| buf:%s|", size, + len(msg), buf, "|") + + if msg: + self.msg_queue.put(msg) + else: + logger.debug("more incoming packet(s) are needed ") + break + + logger.debug("EReader thread finished") + + diff --git a/vnpy/api/ib/scanner.py b/vnpy/api/ib/scanner.py new file mode 100644 index 00000000..6249fa39 --- /dev/null +++ b/vnpy/api/ib/scanner.py @@ -0,0 +1,57 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +from ibapi.object_implem import Object +from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE + + +class ScanData(Object): + def __init__(self, contract = None, rank = 0, distance = "", benchmark = "", projection = "", legsStr = ""): + self.contract = contract + self.rank = rank + self.distance = distance + self.benchmark = benchmark + self.projection = projection + self.legsStr = legsStr + + def __str__(self): + return "Rank: %d, Symbol: %s, SecType: %s, Currency: %s, Distance: %s, Benchmark: %s, Projection: %s, Legs String: %s" % (self.rank, + self.contract.symbol, self.contract.secType, self.contract.currency, self.distance, + self.benchmark, self.projection, self.legsStr) + +NO_ROW_NUMBER_SPECIFIED = -1 + +class ScannerSubscription(Object): + + def __init__(self): + self.numberOfRows = NO_ROW_NUMBER_SPECIFIED + self.instrument = "" + self.locationCode = "" + self.scanCode = "" + self.abovePrice = UNSET_DOUBLE + self.belowPrice = UNSET_DOUBLE + self.aboveVolume = UNSET_INTEGER + self.marketCapAbove = UNSET_DOUBLE + self.marketCapBelow = UNSET_DOUBLE + self.moodyRatingAbove = "" + self.moodyRatingBelow = "" + self.spRatingAbove = "" + self.spRatingBelow = "" + self.maturityDateAbove = "" + self.maturityDateBelow = "" + self.couponRateAbove = UNSET_DOUBLE + self.couponRateBelow = UNSET_DOUBLE + self.excludeConvertible = False + self.averageOptionVolumeAbove = UNSET_INTEGER + self.scannerSettingPairs = "" + self.stockTypeFilter = "" + + + def __str__(self): + s = "Instrument: %s, LocationCode: %s, ScanCode: %s" % (self.instrument, self.locationCode, self.scanCode) + + return s + diff --git a/vnpy/api/ib/server_versions.py b/vnpy/api/ib/server_versions.py new file mode 100644 index 00000000..4bbcf7be --- /dev/null +++ b/vnpy/api/ib/server_versions.py @@ -0,0 +1,104 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +""" +The known server versions. +""" + +#MIN_SERVER_VER_REAL_TIME_BARS = 34 +#MIN_SERVER_VER_SCALE_ORDERS = 35 +#MIN_SERVER_VER_SNAPSHOT_MKT_DATA = 35 +#MIN_SERVER_VER_SSHORT_COMBO_LEGS = 35 +#MIN_SERVER_VER_WHAT_IF_ORDERS = 36 +#MIN_SERVER_VER_CONTRACT_CONID = 37 +MIN_SERVER_VER_PTA_ORDERS = 39 +MIN_SERVER_VER_FUNDAMENTAL_DATA = 40 +MIN_SERVER_VER_DELTA_NEUTRAL = 40 +MIN_SERVER_VER_CONTRACT_DATA_CHAIN = 40 +MIN_SERVER_VER_SCALE_ORDERS2 = 40 +MIN_SERVER_VER_ALGO_ORDERS = 41 +MIN_SERVER_VER_EXECUTION_DATA_CHAIN = 42 +MIN_SERVER_VER_NOT_HELD = 44 +MIN_SERVER_VER_SEC_ID_TYPE = 45 +MIN_SERVER_VER_PLACE_ORDER_CONID = 46 +MIN_SERVER_VER_REQ_MKT_DATA_CONID = 47 +MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT = 49 +MIN_SERVER_VER_REQ_CALC_OPTION_PRICE = 50 +MIN_SERVER_VER_SSHORTX_OLD = 51 +MIN_SERVER_VER_SSHORTX = 52 +MIN_SERVER_VER_REQ_GLOBAL_CANCEL = 53 +MIN_SERVER_VER_HEDGE_ORDERS = 54 +MIN_SERVER_VER_REQ_MARKET_DATA_TYPE = 55 +MIN_SERVER_VER_OPT_OUT_SMART_ROUTING = 56 +MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS = 57 +MIN_SERVER_VER_DELTA_NEUTRAL_CONID = 58 +MIN_SERVER_VER_SCALE_ORDERS3 = 60 +MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE = 61 +MIN_SERVER_VER_TRAILING_PERCENT = 62 +MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE = 66 +MIN_SERVER_VER_POSITIONS = 67 +MIN_SERVER_VER_ACCOUNT_SUMMARY = 67 +MIN_SERVER_VER_TRADING_CLASS = 68 +MIN_SERVER_VER_SCALE_TABLE = 69 +MIN_SERVER_VER_LINKING = 70 +MIN_SERVER_VER_ALGO_ID = 71 +MIN_SERVER_VER_OPTIONAL_CAPABILITIES = 72 +MIN_SERVER_VER_ORDER_SOLICITED = 73 +MIN_SERVER_VER_LINKING_AUTH = 74 +MIN_SERVER_VER_PRIMARYEXCH = 75 +MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE = 76 +MIN_SERVER_VER_FRACTIONAL_POSITIONS = 101 +MIN_SERVER_VER_PEGGED_TO_BENCHMARK = 102 +MIN_SERVER_VER_MODELS_SUPPORT = 103 +MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ = 104 +MIN_SERVER_VER_EXT_OPERATOR = 105 +MIN_SERVER_VER_SOFT_DOLLAR_TIER = 106 +MIN_SERVER_VER_REQ_FAMILY_CODES = 107 +MIN_SERVER_VER_REQ_MATCHING_SYMBOLS = 108 +MIN_SERVER_VER_PAST_LIMIT = 109 +MIN_SERVER_VER_MD_SIZE_MULTIPLIER = 110 +MIN_SERVER_VER_CASH_QTY = 111 +MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES = 112 +MIN_SERVER_VER_TICK_NEWS = 113 +MIN_SERVER_VER_REQ_SMART_COMPONENTS = 114 +MIN_SERVER_VER_REQ_NEWS_PROVIDERS = 115 +MIN_SERVER_VER_REQ_NEWS_ARTICLE = 116 +MIN_SERVER_VER_REQ_HISTORICAL_NEWS = 117 +MIN_SERVER_VER_REQ_HEAD_TIMESTAMP = 118 +MIN_SERVER_VER_REQ_HISTOGRAM = 119 +MIN_SERVER_VER_SERVICE_DATA_TYPE = 120 +MIN_SERVER_VER_AGG_GROUP = 121 +MIN_SERVER_VER_UNDERLYING_INFO = 122 +MIN_SERVER_VER_CANCEL_HEADTIMESTAMP = 123 +MIN_SERVER_VER_SYNT_REALTIME_BARS = 124 +MIN_SERVER_VER_CFD_REROUTE = 125 +MIN_SERVER_VER_MARKET_RULES = 126 +MIN_SERVER_VER_PNL = 127 +MIN_SERVER_VER_NEWS_QUERY_ORIGINS = 128 +MIN_SERVER_VER_UNREALIZED_PNL = 129 +MIN_SERVER_VER_HISTORICAL_TICKS = 130 +MIN_SERVER_VER_MARKET_CAP_PRICE = 131 +MIN_SERVER_VER_PRE_OPEN_BID_ASK = 132 +MIN_SERVER_VER_REAL_EXPIRATION_DATE = 134 +MIN_SERVER_VER_REALIZED_PNL = 135 +MIN_SERVER_VER_LAST_LIQUIDITY = 136 +MIN_SERVER_VER_TICK_BY_TICK = 137 +MIN_SERVER_VER_DECISION_MAKER = 138 +MIN_SERVER_VER_MIFID_EXECUTION = 139 +MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE = 140 +MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE = 141 +MIN_SERVER_VER_WHAT_IF_EXT_FIELDS = 142 +MIN_SERVER_VER_SCANNER_GENERIC_OPTS = 143 +MIN_SERVER_VER_API_BIND_ORDER = 144 +MIN_SERVER_VER_ORDER_CONTAINER = 145 +MIN_SERVER_VER_SMART_DEPTH = 146 +MIN_SERVER_VER_REMOVE_NULL_ALL_CASTING = 147 +MIN_SERVER_VER_D_PEG_ORDERS = 148 + +# 100+ messaging */ +# 100 = enhanced handshake, msg length prefixes + +MIN_CLIENT_VER = 100 +MAX_CLIENT_VER = MIN_SERVER_VER_D_PEG_ORDERS diff --git a/vnpy/api/ib/softdollartier.py b/vnpy/api/ib/softdollartier.py new file mode 100644 index 00000000..67771dad --- /dev/null +++ b/vnpy/api/ib/softdollartier.py @@ -0,0 +1,17 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +from ibapi.object_implem import Object + + +class SoftDollarTier(Object): + def __init__(self, name = "", val = "", displayName = ""): + self.name = name + self.val = val + self.displayName = displayName + + def __str__(self): + return "Name: %s, Value: %s, DisplayName: %s" % (self.name, self.val, self.displayName) diff --git a/vnpy/api/ib/tag_value.py b/vnpy/api/ib/tag_value.py new file mode 100644 index 00000000..f84ab2d9 --- /dev/null +++ b/vnpy/api/ib/tag_value.py @@ -0,0 +1,24 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + +""" +Simple class mapping a tag to a value. Both of them are strings. +They are used in a list to convey extra info with the requests. +""" + +from ibapi.object_implem import Object + + +class TagValue(Object): + def __init__(self, tag:str=None, value:str=None): + self.tag = str(tag) + self.value = str(value) + + def __str__(self): + # this is not only used for Python dump but when encoding to send + # so don't change it lightly ! + return "%s=%s;" % (self.tag, self.value) + + diff --git a/vnpy/api/ib/ticktype.py b/vnpy/api/ib/ticktype.py new file mode 100644 index 00000000..c6224339 --- /dev/null +++ b/vnpy/api/ib/ticktype.py @@ -0,0 +1,109 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +TickType type +""" + +from ibapi.enum_implem import Enum + + +# TickType +TickType = int +TickTypeEnum = Enum("BID_SIZE", + "BID", + "ASK", + "ASK_SIZE", + "LAST", + "LAST_SIZE", + "HIGH", + "LOW", + "VOLUME", + "CLOSE", + "BID_OPTION_COMPUTATION", + "ASK_OPTION_COMPUTATION", + "LAST_OPTION_COMPUTATION", + "MODEL_OPTION", + "OPEN", + "LOW_13_WEEK", + "HIGH_13_WEEK", + "LOW_26_WEEK", + "HIGH_26_WEEK", + "LOW_52_WEEK", + "HIGH_52_WEEK", + "AVG_VOLUME", + "OPEN_INTEREST", + "OPTION_HISTORICAL_VOL", + "OPTION_IMPLIED_VOL", + "OPTION_BID_EXCH", + "OPTION_ASK_EXCH", + "OPTION_CALL_OPEN_INTEREST", + "OPTION_PUT_OPEN_INTEREST", + "OPTION_CALL_VOLUME", + "OPTION_PUT_VOLUME", + "INDEX_FUTURE_PREMIUM", + "BID_EXCH", + "ASK_EXCH", + "AUCTION_VOLUME", + "AUCTION_PRICE", + "AUCTION_IMBALANCE", + "MARK_PRICE", + "BID_EFP_COMPUTATION", + "ASK_EFP_COMPUTATION", + "LAST_EFP_COMPUTATION", + "OPEN_EFP_COMPUTATION", + "HIGH_EFP_COMPUTATION", + "LOW_EFP_COMPUTATION", + "CLOSE_EFP_COMPUTATION", + "LAST_TIMESTAMP", + "SHORTABLE", + "FUNDAMENTAL_RATIOS", + "RT_VOLUME", + "HALTED", + "BID_YIELD", + "ASK_YIELD", + "LAST_YIELD", + "CUST_OPTION_COMPUTATION", + "TRADE_COUNT", + "TRADE_RATE", + "VOLUME_RATE", + "LAST_RTH_TRADE", + "RT_HISTORICAL_VOL", + "IB_DIVIDENDS", + "BOND_FACTOR_MULTIPLIER", + "REGULATORY_IMBALANCE", + "NEWS_TICK", + "SHORT_TERM_VOLUME_3_MIN", + "SHORT_TERM_VOLUME_5_MIN", + "SHORT_TERM_VOLUME_10_MIN", + "DELAYED_BID", + "DELAYED_ASK", + "DELAYED_LAST", + "DELAYED_BID_SIZE", + "DELAYED_ASK_SIZE", + "DELAYED_LAST_SIZE", + "DELAYED_HIGH", + "DELAYED_LOW", + "DELAYED_VOLUME", + "DELAYED_CLOSE", + "DELAYED_OPEN", + "RT_TRD_VOLUME", + "CREDITMAN_MARK_PRICE", + "CREDITMAN_SLOW_MARK_PRICE", + "DELAYED_BID_OPTION", + "DELAYED_ASK_OPTION", + "DELAYED_LAST_OPTION", + "DELAYED_MODEL_OPTION", + "LAST_EXCH", + "LAST_REG_TIME", + "FUTURES_OPEN_INTEREST", + "AVG_OPT_VOLUME", + "DELAYED_LAST_TIMESTAMP", + "SHORTABLE_SHARES", + "NOT_SET") + + + diff --git a/vnpy/api/ib/utils.py b/vnpy/api/ib/utils.py new file mode 100644 index 00000000..be4acfa5 --- /dev/null +++ b/vnpy/api/ib/utils.py @@ -0,0 +1,115 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +Collection of misc tools +""" + + +import sys +import logging +import inspect + +from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE + + +logger = logging.getLogger(__name__) + + +# I use this just to visually emphasize it's a wrapper overriden method +def iswrapper(fn): + return fn + + +class BadMessage(Exception): + def __init__(self, text): + self.text = text + + +class LogFunction(object): + def __init__(self, text, logLevel): + self.text = text + self.logLevel = logLevel + + def __call__(self, fn): + def newFn(origSelf, *args, **kwargs): + if logger.getLogger().isEnabledFor(self.logLevel): + argNames = [argName for argName in inspect.getfullargspec(fn)[0] if argName != 'self'] + logger.log(self.logLevel, + "{} {} {} kw:{}".format(self.text, fn.__name__, + [nameNarg for nameNarg in zip(argNames, args) if nameNarg[1] is not origSelf], kwargs)) + fn(origSelf, *args) + return newFn + + +def current_fn_name(parent_idx = 0): + #depth is 1 bc this is already a fn, so we need the caller + return sys._getframe(1 + parent_idx).f_code.co_name + + +def setattr_log(self, var_name, var_value): + #import code; code.interact(local=locals()) + logger.debug("%s %s %s=|%s|", self.__class__, id(self), var_name, var_value) + super(self.__class__, self).__setattr__(var_name, var_value) + + +SHOW_UNSET = True +def decode(the_type, fields, show_unset = False): + try: + s = next(fields) + except StopIteration: + raise BadMessage("no more fields") + + logger.debug("decode %s %s", the_type, s) + + if the_type is str: + if type(s) is str: + return s + elif type(s) is bytes: + return s.decode() + else: + raise TypeError("unsupported incoming type " + type(s) + " for desired type 'str") + + orig_type = the_type + if the_type is bool: + the_type = int + + if show_unset: + if s is None or len(s) == 0: + if the_type is float: + n = UNSET_DOUBLE + elif the_type is int: + n = UNSET_INTEGER + else: + raise TypeError("unsupported desired type for empty value" + the_type) + else: + n = the_type(s) + else: + n = the_type(s or 0) + + if orig_type is bool: + n = False if n == 0 else True + + return n + + + +def ExerciseStaticMethods(klass): + + import types + #import code; code.interact(local=dict(globals(), **locals())) + for (_, var) in inspect.getmembers(klass): + #print(name, var, type(var)) + if type(var) == types.FunctionType: + print("Exercising: %s:" % var) + print(var()) + print() + +def floatToStr(val): + return str(val) if val != UNSET_DOUBLE else ""; + + + diff --git a/vnpy/api/ib/wrapper.py b/vnpy/api/ib/wrapper.py new file mode 100644 index 00000000..47869df8 --- /dev/null +++ b/vnpy/api/ib/wrapper.py @@ -0,0 +1,701 @@ +""" +Copyright (C) 2018 Interactive Brokers LLC. All rights reserved. This code is subject to the terms +and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. +""" + + +""" +This is the interface that will need to be overloaded by the customer so +that his/her code can receive info from the TWS/IBGW. + +NOTE: the methods use type annotations to describe the types of the arguments. +This is used by the Decoder to dynamically and automatically decode the +received message into the given EWrapper method. This method can only be +used for the most simple messages, but it's still huge helper. +Also this method currently automatically decode a 'version' field in the +message. However having a 'version' field is a legacy thing, newer +message use the 'unified version': the agreed up min version of both +server and client. + +""" + +import logging + +from ibapi.common import * # @UnusedWildImport +from ibapi.utils import * # @UnusedWildImport +from ibapi.contract import (Contract, ContractDetails, DeltaNeutralContract) +from ibapi.order import Order +from ibapi.order_state import OrderState +from ibapi.execution import Execution +from ibapi.ticktype import * # @UnusedWildImport +from ibapi.commission_report import CommissionReport + + +logger = logging.getLogger(__name__) + + +class EWrapper: + def __init__(self): + pass + + def logAnswer(self, fnName, fnParams): + if logger.isEnabledFor(logging.INFO): + if 'self' in fnParams: + prms = dict(fnParams) + del prms['self'] + else: + prms = fnParams + logger.info("ANSWER %s %s", fnName, prms) + + + def error(self, reqId:TickerId, errorCode:int, errorString:str): + """This event is called when there is an error with the + communication or when TWS wants to send a message to the client.""" + + self.logAnswer(current_fn_name(), vars()) + logger.error("ERROR %s %s %s", reqId, errorCode, errorString) + + + def winError(self, text:str, lastError:int): + self.logAnswer(current_fn_name(), vars()) + + + def connectAck(self): + """ callback signifying completion of successful connection """ + self.logAnswer(current_fn_name(), vars()) + + + def marketDataType(self, reqId:TickerId, marketDataType:int): + """TWS sends a marketDataType(type) callback to the API, where + type is set to Frozen or RealTime, to announce that market data has been + switched between frozen and real-time. This notification occurs only + when market data switches between real-time and frozen. The + marketDataType( ) callback accepts a reqId parameter and is sent per + every subscription because different contracts can generally trade on a + different schedule.""" + + self.logAnswer(current_fn_name(), vars()) + + + def tickPrice(self, reqId:TickerId , tickType:TickType, price:float, + attrib:TickAttrib): + """Market data tick price callback. Handles all price related ticks.""" + + self.logAnswer(current_fn_name(), vars()) + + + def tickSize(self, reqId:TickerId, tickType:TickType, size:int): + """Market data tick size callback. Handles all size-related ticks.""" + + self.logAnswer(current_fn_name(), vars()) + + + def tickSnapshotEnd(self, reqId:int): + """When requesting market data snapshots, this market will indicate the + snapshot reception is finished. """ + + self.logAnswer(current_fn_name(), vars()) + + + def tickGeneric(self, reqId:TickerId, tickType:TickType, value:float): + self.logAnswer(current_fn_name(), vars()) + + + def tickString(self, reqId:TickerId, tickType:TickType, value:str): + self.logAnswer(current_fn_name(), vars()) + + + def tickEFP(self, reqId:TickerId, tickType:TickType, basisPoints:float, + formattedBasisPoints:str, totalDividends:float, + holdDays:int, futureLastTradeDate:str, dividendImpact:float, + dividendsToLastTradeDate:float): + self.logAnswer(current_fn_name(), vars()) + """ market data call back for Exchange for Physical + tickerId - The request's identifier. + tickType - The type of tick being received. + basisPoints - Annualized basis points, which is representative of + the financing rate that can be directly compared to broker rates. + formattedBasisPoints - Annualized basis points as a formatted string + that depicts them in percentage form. + impliedFuture - The implied Futures price. + holdDays - The number of hold days until the lastTradeDate of the EFP. + futureLastTradeDate - The expiration date of the single stock future. + dividendImpact - The dividend impact upon the annualized basis points + interest rate. + dividendsToLastTradeDate - The dividends expected until the expiration + of the single stock future.""" + + self.logAnswer(current_fn_name(), vars()) + + + def orderStatus(self, orderId:OrderId , status:str, filled:float, + remaining:float, avgFillPrice:float, permId:int, + parentId:int, lastFillPrice:float, clientId:int, + whyHeld:str, mktCapPrice: float): + """This event is called whenever the status of an order changes. It is + also fired after reconnecting to TWS if the client has any open orders. + + orderId: OrderId - The order ID that was specified previously in the + call to placeOrder() + status:str - The order status. Possible values include: + PendingSubmit - indicates that you have transmitted the order, but have not yet received confirmation that it has been accepted by the order destination. NOTE: This order status is not sent by TWS and should be explicitly set by the API developer when an order is submitted. + PendingCancel - indicates that you have sent a request to cancel the order but have not yet received cancel confirmation from the order destination. At this point, your order is not confirmed canceled. You may still receive an execution while your cancellation request is pending. NOTE: This order status is not sent by TWS and should be explicitly set by the API developer when an order is canceled. + PreSubmitted - indicates that a simulated order type has been accepted by the IB system and that this order has yet to be elected. The order is held in the IB system until the election criteria are met. At that time the order is transmitted to the order destination as specified. + Submitted - indicates that your order has been accepted at the order destination and is working. + Cancelled - indicates that the balance of your order has been confirmed canceled by the IB system. This could occur unexpectedly when IB or the destination has rejected your order. + Filled - indicates that the order has been completely filled. + Inactive - indicates that the order has been accepted by the system (simulated orders) or an exchange (native orders) but that currently the order is inactive due to system, exchange or other issues. + filled:int - Specifies the number of shares that have been executed. + For more information about partial fills, see Order Status for Partial Fills. + remaining:int - Specifies the number of shares still outstanding. + avgFillPrice:float - The average price of the shares that have been executed. This parameter is valid only if the filled parameter value is greater than zero. Otherwise, the price parameter will be zero. + permId:int - The TWS id used to identify orders. Remains the same over TWS sessions. + parentId:int - The order ID of the parent order, used for bracket and auto trailing stop orders. + lastFilledPrice:float - The last price of the shares that have been executed. This parameter is valid only if the filled parameter value is greater than zero. Otherwise, the price parameter will be zero. + clientId:int - The ID of the client (or TWS) that placed the order. Note that TWS orders have a fixed clientId and orderId of 0 that distinguishes them from API orders. + whyHeld:str - This field is used to identify an order held when TWS is trying to locate shares for a short sell. The value used to indicate this is 'locate'. + + """ + + self.logAnswer(current_fn_name(), vars()) + + + def openOrder(self, orderId:OrderId, contract:Contract, order:Order, + orderState:OrderState): + """This function is called to feed in open orders. + + orderID: OrderId - The order ID assigned by TWS. Use to cancel or + update TWS order. + contract: Contract - The Contract class attributes describe the contract. + order: Order - The Order class gives the details of the open order. + orderState: OrderState - The orderState class includes attributes Used + for both pre and post trade margin and commission data.""" + + self.logAnswer(current_fn_name(), vars()) + + + def openOrderEnd(self): + """This is called at the end of a given request for open orders.""" + + self.logAnswer(current_fn_name(), vars()) + + + def connectionClosed(self): + """This function is called when TWS closes the sockets + connection with the ActiveX control, or when TWS is shut down.""" + + self.logAnswer(current_fn_name(), vars()) + + + def updateAccountValue(self, key:str, val:str, currency:str, + accountName:str): + """ This function is called only when ReqAccountUpdates on + EEClientSocket object has been called. """ + + self.logAnswer(current_fn_name(), vars()) + + + def updatePortfolio(self, contract:Contract, position:float, + marketPrice:float, marketValue:float, + averageCost:float, unrealizedPNL:float, + realizedPNL:float, accountName:str): + """This function is called only when reqAccountUpdates on + EEClientSocket object has been called.""" + + self.logAnswer(current_fn_name(), vars()) + + + def updateAccountTime(self, timeStamp:str): + self.logAnswer(current_fn_name(), vars()) + + + def accountDownloadEnd(self, accountName:str): + """This is called after a batch updateAccountValue() and + updatePortfolio() is sent.""" + + self.logAnswer(current_fn_name(), vars()) + + + def nextValidId(self, orderId:int): + """ Receives next valid order id.""" + + self.logAnswer(current_fn_name(), vars()) + + + def contractDetails(self, reqId:int, contractDetails:ContractDetails): + """Receives the full contract's definitons. This method will return all + contracts matching the requested via EEClientSocket::reqContractDetails. + For example, one can obtain the whole option chain with it.""" + + self.logAnswer(current_fn_name(), vars()) + + + def bondContractDetails(self, reqId:int, contractDetails:ContractDetails): + """This function is called when reqContractDetails function + has been called for bonds.""" + + self.logAnswer(current_fn_name(), vars()) + + + def contractDetailsEnd(self, reqId:int): + """This function is called once all contract details for a given + request are received. This helps to define the end of an option + chain.""" + + self.logAnswer(current_fn_name(), vars()) + + + def execDetails(self, reqId:int, contract:Contract, execution:Execution): + """This event is fired when the reqExecutions() functions is + invoked, or when an order is filled. """ + + self.logAnswer(current_fn_name(), vars()) + + + def execDetailsEnd(self, reqId:int): + """This function is called once all executions have been sent to + a client in response to reqExecutions().""" + + self.logAnswer(current_fn_name(), vars()) + + + + def updateMktDepth(self, reqId:TickerId , position:int, operation:int, + side:int, price:float, size:int): + """Returns the order book. + + tickerId - the request's identifier + position - the order book's row being updated + operation - how to refresh the row: + 0 = insert (insert this new order into the row identified by 'position') + 1 = update (update the existing order in the row identified by 'position') + 2 = delete (delete the existing order at the row identified by 'position'). + side - 0 for ask, 1 for bid + price - the order's price + size - the order's size""" + + self.logAnswer(current_fn_name(), vars()) + + + def updateMktDepthL2(self, reqId:TickerId , position:int, marketMaker:str, + operation:int, side:int, price:float, size:int, isSmartDepth:bool): + """Returns the order book. + + tickerId - the request's identifier + position - the order book's row being updated + marketMaker - the exchange holding the order + operation - how to refresh the row: + 0 = insert (insert this new order into the row identified by 'position') + 1 = update (update the existing order in the row identified by 'position') + 2 = delete (delete the existing order at the row identified by 'position'). + side - 0 for ask, 1 for bid + price - the order's price + size - the order's size + isSmartDepth - is SMART Depth request""" + + self.logAnswer(current_fn_name(), vars()) + + + def updateNewsBulletin(self, msgId:int, msgType:int, newsMessage:str, + originExch:str): + """ provides IB's bulletins + msgId - the bulletin's identifier + msgType - one of: 1 - Regular news bulletin 2 - Exchange no longer + available for trading 3 - Exchange is available for trading + message - the message + origExchange - the exchange where the message comes from. """ + + self.logAnswer(current_fn_name(), vars()) + + + def managedAccounts(self, accountsList:str): + """Receives a comma-separated string with the managed account ids.""" + self.logAnswer(current_fn_name(), vars()) + + + def receiveFA(self, faData:FaDataType , cxml:str): + """ receives the Financial Advisor's configuration available in the TWS + + faDataType - one of: + Groups: offer traders a way to create a group of accounts and apply + a single allocation method to all accounts in the group. + Profiles: let you allocate shares on an account-by-account basis + using a predefined calculation value. + Account Aliases: let you easily identify the accounts by meaningful + names rather than account numbers. + faXmlData - the xml-formatted configuration """ + + self.logAnswer(current_fn_name(), vars()) + + def historicalData(self, reqId: int, bar: BarData): + """ returns the requested historical data bars + + reqId - the request's identifier + date - the bar's date and time (either as a yyyymmss hh:mm:ssformatted + string or as system time according to the request) + open - the bar's open point + high - the bar's high point + low - the bar's low point + close - the bar's closing point + volume - the bar's traded volume if available + count - the number of trades during the bar's timespan (only available + for TRADES). + WAP - the bar's Weighted Average Price + hasGaps -indicates if the data has gaps or not. """ + + self.logAnswer(current_fn_name(), vars()) + + + def historicalDataEnd(self, reqId:int, start:str, end:str): + """ Marks the ending of the historical bars reception. """ + self.logAnswer(current_fn_name(), vars()) + + + def scannerParameters(self, xml:str): + """ Provides the xml-formatted parameters available to create a market + scanner. + + xml - the xml-formatted string with the available parameters.""" + self.logAnswer(current_fn_name(), vars()) + + + def scannerData(self, reqId:int, rank:int, contractDetails:ContractDetails, + distance:str, benchmark:str, projection:str, legsStr:str): + """ Provides the data resulting from the market scanner request. + + reqid - the request's identifier. + rank - the ranking within the response of this bar. + contractDetails - the data's ContractDetails + distance - according to query. + benchmark - according to query. + projection - according to query. + legStr - describes the combo legs when the scanner is returning EFP""" + + self.logAnswer(current_fn_name(), vars()) + + + def scannerDataEnd(self, reqId:int): + """ Indicates the scanner data reception has terminated. + + reqId - the request's identifier""" + + self.logAnswer(current_fn_name(), vars()) + + + def realtimeBar(self, reqId: TickerId, time:int, open_: float, high: float, low: float, close: float, + volume: int, wap: float, count: int): + + """ Updates the real time 5 seconds bars + + reqId - the request's identifier + bar.time - start of bar in unix (or 'epoch') time + bar.endTime - for synthetic bars, the end time (requires TWS v964). Otherwise -1. + bar.open_ - the bar's open value + bar.high - the bar's high value + bar.low - the bar's low value + bar.close - the bar's closing value + bar.volume - the bar's traded volume if available + bar.WAP - the bar's Weighted Average Price + bar.count - the number of trades during the bar's timespan (only available + for TRADES).""" + + self.logAnswer(current_fn_name(), vars()) + + def currentTime(self, time:int): + """ Server's current time. This method will receive IB server's system + time resulting after the invokation of reqCurrentTime. """ + + self.logAnswer(current_fn_name(), vars()) + + + def fundamentalData(self, reqId:TickerId , data:str): + """This function is called to receive fundamental + market data. The appropriate market data subscription must be set + up in Account Management before you can receive this data.""" + + self.logAnswer(current_fn_name(), vars()) + + + def deltaNeutralValidation(self, reqId:int, deltaNeutralContract:DeltaNeutralContract): + """Upon accepting a Delta-Neutral RFQ(request for quote), the + server sends a deltaNeutralValidation() message with the DeltaNeutralContract + structure. If the delta and price fields are empty in the original + request, the confirmation will contain the current values from the + server. These values are locked when the RFQ is processed and remain + locked until the RFQ is canceled.""" + + self.logAnswer(current_fn_name(), vars()) + + + + def commissionReport(self, commissionReport:CommissionReport): + """The commissionReport() callback is triggered as follows: + - immediately after a trade execution + - by calling reqExecutions().""" + + self.logAnswer(current_fn_name(), vars()) + + + def position(self, account:str, contract:Contract, position:float, + avgCost:float): + """This event returns real-time positions for all accounts in + response to the reqPositions() method.""" + + self.logAnswer(current_fn_name(), vars()) + + + def positionEnd(self): + """This is called once all position data for a given request are + received and functions as an end marker for the position() data. """ + + self.logAnswer(current_fn_name(), vars()) + + + def accountSummary(self, reqId:int, account:str, tag:str, value:str, + currency:str): + """Returns the data from the TWS Account Window Summary tab in + response to reqAccountSummary().""" + + self.logAnswer(current_fn_name(), vars()) + + + def accountSummaryEnd(self, reqId:int): + """This method is called once all account summary data for a + given request are received.""" + + self.logAnswer(current_fn_name(), vars()) + + + def verifyMessageAPI(self, apiData:str): + """ Deprecated Function """ + self.logAnswer(current_fn_name(), vars()) + + + def verifyCompleted(self, isSuccessful:bool, errorText:str): + + self.logAnswer(current_fn_name(), vars()) + + + def verifyAndAuthMessageAPI(self, apiData:str, xyzChallange:str): + + self.logAnswer(current_fn_name(), vars()) + + + def verifyAndAuthCompleted(self, isSuccessful:bool, errorText:str): + + self.logAnswer(current_fn_name(), vars()) + + + def displayGroupList(self, reqId:int, groups:str): + """This callback is a one-time response to queryDisplayGroups(). + + reqId - The requestId specified in queryDisplayGroups(). + groups - A list of integers representing visible group ID separated by + the | character, and sorted by most used group first. This list will + not change during TWS session (in other words, user cannot add a + new group; sorting can change though).""" + + self.logAnswer(current_fn_name(), vars()) + + + def displayGroupUpdated(self, reqId:int, contractInfo:str): + """This is sent by TWS to the API client once after receiving + the subscription request subscribeToGroupEvents(), and will be sent + again if the selected contract in the subscribed display group has + changed. + + requestId - The requestId specified in subscribeToGroupEvents(). + contractInfo - The encoded value that uniquely represents the contract + in IB. Possible values include: + none = empty selection + contractID@exchange = any non-combination contract. + Examples: 8314@SMART for IBM SMART; 8314@ARCA for IBM @ARCA. + combo = if any combo is selected. """ + + self.logAnswer(current_fn_name(), vars()) + + + def positionMulti(self, reqId:int, account:str, modelCode:str, + contract:Contract, pos:float, avgCost:float): + """same as position() except it can be for a certain + account/model""" + + self.logAnswer(current_fn_name(), vars()) + + + def positionMultiEnd(self, reqId:int): + """same as positionEnd() except it can be for a certain + account/model""" + + self.logAnswer(current_fn_name(), vars()) + + + def accountUpdateMulti(self, reqId:int, account:str, modelCode:str, + key:str, value:str, currency:str): + """same as updateAccountValue() except it can be for a certain + account/model""" + + self.logAnswer(current_fn_name(), vars()) + + + def accountUpdateMultiEnd(self, reqId:int): + """same as accountDownloadEnd() except it can be for a certain + account/model""" + + self.logAnswer(current_fn_name(), vars()) + + + def tickOptionComputation(self, reqId:TickerId, tickType:TickType , + impliedVol:float, delta:float, optPrice:float, pvDividend:float, + gamma:float, vega:float, theta:float, undPrice:float): + """This function is called when the market in an option or its + underlier moves. TWS's option model volatilities, prices, and + deltas, along with the present value of dividends expected on that + options underlier are received.""" + + self.logAnswer(current_fn_name(), vars()) + + + def securityDefinitionOptionParameter(self, reqId:int, exchange:str, + underlyingConId:int, tradingClass:str, multiplier:str, + expirations:SetOfString, strikes:SetOfFloat): + """ Returns the option chain for an underlying on an exchange + specified in reqSecDefOptParams There will be multiple callbacks to + securityDefinitionOptionParameter if multiple exchanges are specified + in reqSecDefOptParams + + reqId - ID of the request initiating the callback + underlyingConId - The conID of the underlying security + tradingClass - the option trading class + multiplier - the option multiplier + expirations - a list of the expiries for the options of this underlying + on this exchange + strikes - a list of the possible strikes for options of this underlying + on this exchange """ + + self.logAnswer(current_fn_name(), vars()) + + + def securityDefinitionOptionParameterEnd(self, reqId:int): + """ Called when all callbacks to securityDefinitionOptionParameter are + complete + + reqId - the ID used in the call to securityDefinitionOptionParameter """ + + self.logAnswer(current_fn_name(), vars()) + + + def softDollarTiers(self, reqId:int, tiers:list): + """ Called when receives Soft Dollar Tier configuration information + + reqId - The request ID used in the call to EEClient::reqSoftDollarTiers + tiers - Stores a list of SoftDollarTier that contains all Soft Dollar + Tiers information """ + + self.logAnswer(current_fn_name(), vars()) + + + def familyCodes(self, familyCodes:ListOfFamilyCode): + """ returns array of family codes """ + self.logAnswer(current_fn_name(), vars()) + + + def symbolSamples(self, reqId:int, + contractDescriptions:ListOfContractDescription): + """ returns array of sample contract descriptions """ + self.logAnswer(current_fn_name(), vars()) + + def mktDepthExchanges(self, depthMktDataDescriptions:ListOfDepthExchanges): + """ returns array of exchanges which return depth to UpdateMktDepthL2""" + self.logAnswer(current_fn_name(), vars()) + + def tickNews(self, tickerId: int, timeStamp:int, providerCode:str, articleId:str, headline:str, extraData:str): + """ returns news headlines""" + self.logAnswer(current_fn_name(), vars()) + + def smartComponents(self, reqId:int, smartComponentMap:SmartComponentMap): + """returns exchange component mapping""" + self.logAnswer(current_fn_name(), vars()) + + def tickReqParams(self, tickerId:int, minTick:float, bboExchange:str, snapshotPermissions:int): + """returns exchange map of a particular contract""" + self.logAnswer(current_fn_name(), vars()) + + def newsProviders(self, newsProviders:ListOfNewsProviders): + """returns available, subscribed API news providers""" + self.logAnswer(current_fn_name(), vars()) + + def newsArticle(self, requestId:int, articleType:int, articleText:str): + """returns body of news article""" + self.logAnswer(current_fn_name(), vars()) + + def historicalNews(self, requestId:int, time:str, providerCode:str, articleId:str, headline:str): + """returns historical news headlines""" + self.logAnswer(current_fn_name(), vars()) + + def historicalNewsEnd(self, requestId:int, hasMore:bool): + """signals end of historical news""" + self.logAnswer(current_fn_name(), vars()) + + def headTimestamp(self, reqId:int, headTimestamp:str): + """returns earliest available data of a type of data for a particular contract""" + self.logAnswer(current_fn_name(), vars()) + + def histogramData(self, reqId:int, items:HistogramData): + """returns histogram data for a contract""" + self.logAnswer(current_fn_name(), vars()) + + def historicalDataUpdate(self, reqId: int, bar: BarData): + """returns updates in real time when keepUpToDate is set to True""" + self.logAnswer(current_fn_name(), vars()) + + def rerouteMktDataReq(self, reqId: int, conId: int, exchange: str): + """returns reroute CFD contract information for market data request""" + self.logAnswer(current_fn_name(), vars()) + + def rerouteMktDepthReq(self, reqId: int, conId: int, exchange: str): + """returns reroute CFD contract information for market depth request""" + self.logAnswer(current_fn_name(), vars()) + + def marketRule(self, marketRuleId: int, priceIncrements: ListOfPriceIncrements): + """returns minimum price increment structure for a particular market rule ID""" + self.logAnswer(current_fn_name(), vars()) + + def pnl(self, reqId: int, dailyPnL: float, unrealizedPnL: float, realizedPnL: float): + """returns the daily PnL for the account""" + self.logAnswer(current_fn_name(), vars()) + + def pnlSingle(self, reqId: int, pos: int, dailyPnL: float, unrealizedPnL: float, realizedPnL: float, value: float): + """returns the daily PnL for a single position in the account""" + self.logAnswer(current_fn_name(), vars()) + + def historicalTicks(self, reqId: int, ticks: ListOfHistoricalTick, done: bool): + """returns historical tick data when whatToShow=MIDPOINT""" + self.logAnswer(current_fn_name(), vars()) + + def historicalTicksBidAsk(self, reqId: int, ticks: ListOfHistoricalTickBidAsk, done: bool): + """returns historical tick data when whatToShow=BID_ASK""" + self.logAnswer(current_fn_name(), vars()) + + def historicalTicksLast(self, reqId: int, ticks: ListOfHistoricalTickLast, done: bool): + """returns historical tick data when whatToShow=TRADES""" + self.logAnswer(current_fn_name(), vars()) + + def tickByTickAllLast(self, reqId: int, tickType: int, time: int, price: float, + size: int, tickAttribLast: TickAttribLast, exchange: str, + specialConditions: str): + """returns tick-by-tick data for tickType = "Last" or "AllLast" """ + self.logAnswer(current_fn_name(), vars()) + + def tickByTickBidAsk(self, reqId: int, time: int, bidPrice: float, askPrice: float, + bidSize: int, askSize: int, tickAttribBidAsk: TickAttribBidAsk): + """returns tick-by-tick data for tickType = "BidAsk" """ + self.logAnswer(current_fn_name(), vars()) + + def tickByTickMidPoint(self, reqId: int, time: int, midPoint: float): + """returns tick-by-tick data for tickType = "MidPoint" """ + self.logAnswer(current_fn_name(), vars()) + + def orderBound(self, reqId: int, apiClientId: int, apiOrderId: int): + """returns orderBound notification""" + self.logAnswer(current_fn_name(), vars()) diff --git a/vnpy/gateway/ib/ib_gateway.py b/vnpy/gateway/ib/ib_gateway.py index df76c465..d63ed25f 100644 --- a/vnpy/gateway/ib/ib_gateway.py +++ b/vnpy/gateway/ib/ib_gateway.py @@ -6,16 +6,16 @@ from datetime import datetime from queue import Empty from threading import Thread -from ibapi import comm -from ibapi.client import EClient -from ibapi.common import MAX_MSG_LEN, NO_VALID_ID, OrderId, TickAttrib, TickerId -from ibapi.contract import Contract, ContractDetails -from ibapi.execution import Execution -from ibapi.order import Order -from ibapi.order_state import OrderState -from ibapi.ticktype import TickType -from ibapi.wrapper import EWrapper -from ibapi.errors import BAD_LENGTH +from vnpy.api.ib import comm +from vnpy.api.ib.client import EClient +from vnpy.api.ib.common import MAX_MSG_LEN, NO_VALID_ID, OrderId, TickAttrib, TickerId +from vnpy.api.ib.contract import Contract, ContractDetails +from vnpy.api.ib.execution import Execution +from vnpy.api.ib.order import Order +from vnpy.api.ib.order_state import OrderState +from vnpy.api.ib.ticktype import TickType +from vnpy.api.ib.wrapper import EWrapper +from vnpy.api.ib.errors import BAD_LENGTH from vnpy.trader.gateway import BaseGateway from vnpy.trader.object import (