From 7f7761e80daf0d3776e982bea1ab2e90ec452520 Mon Sep 17 00:00:00 2001 From: erdewit Date: Fri, 28 Sep 2018 20:46:31 +0200 Subject: [PATCH] flake8 compliant code formatting --- .gitignore | 2 + dist.sh | 7 + ib_insync/client.py | 31 ++- ib_insync/contract.py | 56 ++-- ib_insync/event.py | 20 +- ib_insync/flexreport.py | 14 +- ib_insync/ib.py | 543 +++++++++++++++++++++----------------- ib_insync/ibcontroller.py | 92 +++---- ib_insync/objects.py | 118 +++++---- ib_insync/order.py | 48 ++-- ib_insync/ticker.py | 16 +- ib_insync/util.py | 59 ++--- ib_insync/wrapper.py | 233 +++++++++------- 13 files changed, 701 insertions(+), 538 deletions(-) create mode 100755 dist.sh diff --git a/.gitignore b/.gitignore index 9fdfc490b..6b377e918 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ ib_insync/__pycache__ dist build +examples +.vscode .idea .settings .spyproject diff --git a/dist.sh b/dist.sh new file mode 100755 index 000000000..e9ffc2635 --- /dev/null +++ b/dist.sh @@ -0,0 +1,7 @@ +#!/bin/bash +pushd docs +make html +popd +rm -Rf dist/* +python3 setup.py sdist bdist_wheel +python3 -m twine upload dist/* diff --git a/ib_insync/client.py b/ib_insync/client.py index 50618643a..7559cd465 100644 --- a/ib_insync/client.py +++ b/ib_insync/client.py @@ -143,13 +143,14 @@ def connect(self, host, port, clientId, timeout=2): that is not in use elsewhere. When timeout is not zero, asyncio.TimeoutError - is raised if the connection is not established within the timeout period. + is raised if the connection is not established + within the timeout period. """ util.syncAwait(self.connectAsync(host, port, clientId, timeout)) async def connectAsync(self, host, port, clientId, timeout=2): self._logger.info( - f'Connecting to {host}:{port} with clientId {clientId}...') + f'Connecting to {host}:{port} with clientId {clientId}...') self.host = host self.port = port self.clientId = clientId @@ -191,7 +192,9 @@ def sendMsg(self, msg): if not self._isThrottling: self._isThrottling = True self._logger.warn('Started to throttle requests') - loop.call_at(times[0] + Client.RequestsInterval, self.sendMsg, None) + loop.call_at( + times[0] + Client.RequestsInterval, + self.sendMsg, None) else: if self._isThrottling: self._isThrottling = False @@ -205,7 +208,8 @@ def _onSocketConnected(self): self._logger.info('Connected') # start handshake msg = b'API\0' - msg += self._prefix(b'v%d..%d' % (100, 142)) + versionRange = (100, 142) + msg += self._prefix(b'v%d..%d' % versionRange) self.conn.sendMsg(msg) self.decoder = ibapi.decoder.Decoder(self.wrapper, None) @@ -232,7 +236,8 @@ def _onSocketHasData(self, data): self._numMsgRecv += 1 if debug: - self._logger.debug('<<< %s', ','.join(f.decode() for f in fields)) + self._logger.debug( + '<<< %s', ','.join(f.decode() for f in fields)) if not self.serverVersion_ and len(fields) == 2: # this concludes the handshake @@ -248,7 +253,7 @@ def _onSocketHasData(self, data): # decode and handle the message try: self._decode(fields) - except: + except Exception: self._logger.exception('Decode failed') if self._tcpDataProcessed: @@ -312,22 +317,26 @@ def _decode(self, fields): # bypass the ibapi decoder for ticks for more efficiency if msgId == 2: _, _, reqId, tickType, size = fields - self.wrapper.tickSize(int(reqId), int(tickType), int(size)) + self.wrapper.tickSize( + int(reqId), int(tickType), int(size)) return elif msgId == 1: if self._priceSizeTick: _, _, reqId, tickType, price, size, _ = fields - self._priceSizeTick(int(reqId), int(tickType), + self._priceSizeTick( + int(reqId), int(tickType), float(price), int(size)) return elif msgId == 12: _, _, reqId, position, operation, side, price, size = fields - self.wrapper.updateMktDepth(int(reqId), int(position), + self.wrapper.updateMktDepth( + int(reqId), int(position), int(operation), int(side), float(price), int(size)) return elif msgId == 46: _, _, reqId, tickType, value = fields - self.wrapper.tickString(int(reqId), int(tickType), value.decode()) + self.wrapper.tickString( + int(reqId), int(tickType), value.decode()) return # snoop for nextValidId and managedAccounts response, @@ -372,7 +381,7 @@ def _onConnectionCreated(self, future): def connect(self): loop = asyncio.get_event_loop() coro = loop.create_connection(lambda: Socket(self), - self.host, self.port) + self.host, self.port) future = asyncio.ensure_future(coro) future.add_done_callback(self._onConnectionCreated) return future diff --git a/ib_insync/contract.py b/ib_insync/contract.py index 58164108b..5a11007bc 100644 --- a/ib_insync/contract.py +++ b/ib_insync/contract.py @@ -1,4 +1,4 @@ -import ibapi.contract +import ibapi.contract from ib_insync.objects import Object @@ -13,7 +13,7 @@ class Contract(Object): arguments. To simplify working with contracts, there are also more specialized contracts that take optional positional arguments. Some examples:: - + Contract(conId=270639) Stock('AMD', 'SMART', 'USD') Stock('INTC', 'SMART', 'USD', primaryExchange='NASDAQ') @@ -25,8 +25,8 @@ class Contract(Object): """ defaults = {'secType': '', **ibapi.contract.Contract().__dict__} __slots__ = list(defaults.keys()) + \ - ['comboLegsCount', 'underCompPresent', 'deltaNeutralContractPresent', - 'secIdListCount'] # bug in decoder.py + ['comboLegsCount', 'underCompPresent', 'deltaNeutralContractPresent', + 'secIdListCount'] # bug in decoder.py @staticmethod def create(**kwargs): @@ -56,13 +56,13 @@ def create(**kwargs): def isHashable(self): """ See if this contract can be hashed by conId. - + Note: Bag contracts always get conId=28812380 and ContFutures get the same conId as the front contract, so these contract types are not hashable. """ return self.conId and self.conId != 28812380 and \ - self.secType not in ('BAG', 'CONTFUT') + self.secType not in ('BAG', 'CONTFUT') def __eq__(self, other): return self.isHashable() and isinstance(other, Contract) and \ @@ -89,17 +89,20 @@ class Stock(Contract): __slots__ = () def __init__(self, symbol='', exchange='', currency='', **kwargs): - Contract.__init__(self, secType='STK', symbol=symbol, - exchange=exchange, currency=currency, **kwargs) + Contract.__init__( + self, secType='STK', symbol=symbol, + exchange=exchange, currency=currency, **kwargs) class Option(Contract): __slots__ = () - def __init__(self, symbol='', lastTradeDateOrContractMonth='', + def __init__( + self, symbol='', lastTradeDateOrContractMonth='', strike='', right='', exchange='', multiplier='', currency='', **kwargs): - Contract.__init__(self, 'OPT', symbol=symbol, + Contract.__init__( + self, 'OPT', symbol=symbol, lastTradeDateOrContractMonth=lastTradeDateOrContractMonth, strike=strike, right=right, exchange=exchange, multiplier=multiplier, currency=currency, **kwargs) @@ -108,10 +111,12 @@ def __init__(self, symbol='', lastTradeDateOrContractMonth='', class Future(Contract): __slots__ = () - def __init__(self, symbol='', lastTradeDateOrContractMonth='', + def __init__( + self, symbol='', lastTradeDateOrContractMonth='', exchange='', localSymbol='', multiplier='', currency='', **kwargs): - Contract.__init__(self, 'FUT', symbol=symbol, + Contract.__init__( + self, 'FUT', symbol=symbol, lastTradeDateOrContractMonth=lastTradeDateOrContractMonth, exchange=exchange, localSymbol=localSymbol, multiplier=multiplier, currency=currency, **kwargs) @@ -120,9 +125,11 @@ def __init__(self, symbol='', lastTradeDateOrContractMonth='', class ContFuture(Contract): __slots__ = () - def __init__(self, symbol='', exchange='', localSymbol='', multiplier='', + def __init__( + self, symbol='', exchange='', localSymbol='', multiplier='', currency='', **kwargs): - Contract.__init__(self, 'CONTFUT', symbol=symbol, + Contract.__init__( + self, 'CONTFUT', symbol=symbol, exchange=exchange, localSymbol=localSymbol, multiplier=multiplier, currency=currency, **kwargs) @@ -130,13 +137,15 @@ def __init__(self, symbol='', exchange='', localSymbol='', multiplier='', class Forex(Contract): __slots__ = () - def __init__(self, pair='', exchange='IDEALPRO', + def __init__( + self, pair='', exchange='IDEALPRO', symbol='', currency='', **kwargs): if pair: assert len(pair) == 6 symbol = symbol or pair[:3] currency = currency or pair[3:] - Contract.__init__(self, 'CASH', symbol=symbol, + Contract.__init__( + self, 'CASH', symbol=symbol, exchange=exchange, currency=currency, **kwargs) def __repr__(self): @@ -161,7 +170,8 @@ class Index(Contract): __slots__ = () def __init__(self, symbol='', exchange='', currency='', **kwargs): - Contract.__init__(self, 'IND', symbol=symbol, + Contract.__init__( + self, 'IND', symbol=symbol, exchange=exchange, currency=currency, **kwargs) @@ -169,7 +179,8 @@ class CFD(Contract): __slots__ = () def __init__(self, symbol='', exchange='', currency='', **kwargs): - Contract.__init__(self, 'CFD', symbol=symbol, + Contract.__init__( + self, 'CFD', symbol=symbol, exchange=exchange, currency=currency, **kwargs) @@ -177,7 +188,8 @@ class Commodity(Contract): __slots__ = () def __init__(self, symbol='', exchange='', currency='', **kwargs): - Contract.__init__(self, 'CMDTY', symbol=symbol, + Contract.__init__( + self, 'CMDTY', symbol=symbol, exchange=exchange, currency=currency, **kwargs) @@ -191,10 +203,12 @@ def __init__(self, **kwargs): class FuturesOption(Contract): __slots__ = () - def __init__(self, symbol='', lastTradeDateOrContractMonth='', + def __init__( + self, symbol='', lastTradeDateOrContractMonth='', strike='', right='', exchange='', multiplier='', currency='', **kwargs): - Contract.__init__(self, 'FOP', symbol=symbol, + Contract.__init__( + self, 'FOP', symbol=symbol, lastTradeDateOrContractMonth=lastTradeDateOrContractMonth, strike=strike, right=right, exchange=exchange, multiplier=multiplier, currency=currency, **kwargs) diff --git a/ib_insync/event.py b/ib_insync/event.py index db6b61d05..b39375a11 100644 --- a/ib_insync/event.py +++ b/ib_insync/event.py @@ -7,7 +7,7 @@ class Event: """ Enable event passing between loosely coupled compenents. - + An event contains a list of callables (the listener slots) that are called in order when the event is emitted. """ @@ -21,9 +21,9 @@ def connect(self, c, weakRef=True, hiPriority=False): """ Connect the callable c to this event. The ``+=`` operator can be used as a synonym for this method. - + When ``weakRef=True`` the callable can be garbage collected upon which - it will be automatically disconnected from this event; + it will be automatically disconnected from this event; When ``weakRef=False`` a strong reference to the callable will be kept. With ``hiPriority=True`` the callable will be placed in the first slot, @@ -31,7 +31,7 @@ def connect(self, c, weakRef=True, hiPriority=False): """ if c in self: raise ValueError(f'Duplicate callback: {c}') - + obj, func = self._split(c) if weakRef and hasattr(obj, '__weakref__'): ref = weakref.ref(obj, self._onFinalize) @@ -49,7 +49,7 @@ def disconnect(self, c): """ Disconnect the callable from this event. The ``-=`` operator can be used as a synonym for this method. - + It's okay (i.e. not considered an error) if the callable is already not connected. """ @@ -97,7 +97,7 @@ def init(obj, eventNames): __iadd__ = connect __isub__ = disconnect __call__ = emit - + def __repr__(self): return f'Event<{self.name}, {self.slots}>' @@ -122,11 +122,11 @@ def _split(self, c): """ Split given callable in (object, function) tuple. """ - if type(c) is types.FunctionType: + if isinstance(c, types.FunctionType): t = (None, c) - elif type(c) is types.MethodType: + elif isinstance(c, types.MethodType): t = (c.__self__, c.__func__) - elif type(c) is types.BuiltinMethodType: + elif isinstance(c, types.BuiltinMethodType): if type(c.__self__) is type: # built-in method t = (c.__self__, c) @@ -138,7 +138,7 @@ def _split(self, c): else: raise ValueError(f'Invalid callable: {c}') return t - + def _onFinalize(self, ref): for slot in self.slots: if slot[1] is ref: diff --git a/ib_insync/flexreport.py b/ib_insync/flexreport.py index 6f76774d2..4af5314d0 100644 --- a/ib_insync/flexreport.py +++ b/ib_insync/flexreport.py @@ -11,23 +11,24 @@ _logger = logging.getLogger('ib_insync.flexreport') -class FlexError(Exception): pass +class FlexError(Exception): + pass class FlexReport: """ Download and parse IB account statements via the Flex Web Service. https://www.interactivebrokers.com/en/software/am/am/reports/flex_web_service_version_3.htm - + To obtain a ``token`` in account management, go to Reports -> Settings -> Flex Web Service. Tip: choose a 1 year expiry. - + To obtain a ``queryId``: Create and save a query with Report -> Activity -> Flex Queries or Report -> Trade Confirmations -> Flex Queries. Find the query ID (not the query name). - + A large query can take a few minutes. In the weekends the query servers can be down. """ @@ -53,7 +54,7 @@ def topics(self): def extract(self, topic: str, parseNumbers=True) -> list: """ Extract items of given topic and return as list of objects. - + The topic is a string like TradeConfirm, ChangeInDividendAccrual, Order, etc. """ @@ -82,7 +83,8 @@ def download(self, token, queryId): """ Download report for the given ``token`` and ``queryId``. """ - url = ('https://gdcdyn.interactivebrokers.com' + url = ( + 'https://gdcdyn.interactivebrokers.com' f'/Universal/servlet/FlexStatementService.SendRequest?' f't={token}&q={queryId}&v=3') resp = urlopen(url) diff --git a/ib_insync/ib.py b/ib_insync/ib.py index 4d6c6b9e1..06a62263a 100644 --- a/ib_insync/ib.py +++ b/ib_insync/ib.py @@ -2,8 +2,7 @@ import logging import datetime import time -from typing import List, Iterator, Callable -from collections.abc import Awaitable # @UnusedImport +from typing import List, Iterator, Callable, Awaitable import ibapi from ibapi.account_summary_tags import AccountSummaryTags @@ -12,39 +11,46 @@ from ib_insync.wrapper import Wrapper from ib_insync.contract import Contract from ib_insync.ticker import Ticker -from ib_insync.order import Order, OrderStatus, Trade, \ - LimitOrder, StopOrder, MarketOrder -from ib_insync.objects import * +from ib_insync.order import ( + Order, OrderStatus, Trade, LimitOrder, StopOrder, MarketOrder) +from ib_insync.objects import ( + AccountValue, PortfolioItem, Position, Fill, Execution, BracketOrder, + TradeLogEntry, OrderState, ExecutionFilter, TagValue, PnL, PnLSingle, + ContractDetails, ContractDescription, OptionChain, OptionComputation, + NewsTick, NewsBulletin, NewsArticle, NewsProvider, HistoricalNews, + BarList, BarDataList, RealTimeBarList, DepthMktDataDescription, + ScannerSubscription, ScanData, HistogramData) import ib_insync.util as util from ib_insync.event import Event __all__ = ['IB'] -def api(f): return f # visual marker for API request methods +def api(f): + return f # visual marker for API request methods class IB: """ Provides both a blocking and an asynchronous interface - to the IB Python API, using asyncio networking and event loop. - + to the IB Python API, using asyncio networking and event loop. + The IB class offers direct access to the current state, such as orders, executions, positions, tickers etc. This state is automatically kept in sync with the TWS/IBG application. - + This class has most request methods of EClient, with the same names and parameters (except for the reqId parameter which is not needed anymore). Request methods that return a result come in two versions: - + * Blocking: Will block until complete and return the result. The current state will be kept updated while the request is ongoing; - + * Asynchronous: All methods that have the "Async" postfix. Implemented as coroutines or methods that return a Future and intended for advanced users. - + **The One Rule:** While some of the request methods are blocking from the perspective @@ -54,11 +60,11 @@ class IB: the user code spends much time in a calculation, or uses time.sleep() with a long delay, the framework will stop spinning, messages accumulate and things may go awry. - + The one rule when working with the IB class is therefore that - + **user code may not block for too long**. - + To be clear, the IB request methods are okay to use and do not count towards the user operation time, no matter how long the request takes to finish. @@ -69,101 +75,104 @@ class IB: the other extreme, there is very little incoming data and there is no desire for accurate timestamps, then the user code can block for hours. - + If a user operation takes a long time then it can be farmed out - to a different process. + to a different process. Alternatively the operation can be made such that it periodically calls IB.sleep(0); This will let the framework handle any pending work and return when finished. The operation should be aware that the current state may have been updated during the sleep(0) call. - + For introducing a delay, never use time.sleep() but use :py:meth:`.sleep` instead. - + Events: * ``connectedEvent()``: Is emitted after connecting and synchronzing with TWS/gateway. - + * ``disconnectedEvent()``: Is emitted after disconnecting from TWS/gateway. * ``updateEvent()``: Is emitted after a network packet has been handeled. - + * ``pendingTickersEvent(tickers: List[Ticker])``: Emits the set of tickers that have been updated during the last - update and for which there are new ticks, tickByTicks or domTicks. - + update and for which there are new ticks, tickByTicks or domTicks. + * ``barUpdateEvent(bars: BarDataList, hasNewBar: bool)``: Emits the bar list that has been updated in real time. If a new bar has been added then hasNewBar is True, when the last bar has changed it is False. - + * ``newOrderEvent(trade: Trade)``: Emits a newly placed trade. - + * ``orderModifyEvent(trade: Trade)``: Emits when order is modified. - + * ``cancelOrderEvent(trade: Trade)``: Emits a trade directly after requesting for it to be cancelled. - + * ``openOrderEvent(trade: Trade)``: Emits the trade with open order. - + * ``orderStatusEvent(trade: Trade)``: Emits the changed order status of the ongoing trade. - + * ``execDetailsEvent(trade: Trade, fill: Fill)``: Emits the fill together with the ongoing trade it belongs to. - - * ``commissionReportEvent(trade: Trade, fill: Fill, report: CommissionReport)``: + + * ``commissionReportEvent(trade: Trade, fill: Fill, + report: CommissionReport)``: The commission report is emitted after the fill that it belongs to. - + * ``updatePortfolioEvent(item: PortfolioItem)``: A portfolio item has changed. - + * ``positionEvent(position: Position)``: A position has changed. - + * ``accountValueEvent(value: AccountValue)``: An account value has changed. - + * ``accountSummaryEvent(value: AccountValue)``: An account value has changed. - + * ``pnlEvent(entry: PnL)``: A profit- and loss entry is updated. - + * ``pnlSingleEvent(entry: PnLSingle)``: A profit- and loss entry for a single position is updated. - + * ``tickNewsEvent(news: NewsTick)``: Emit a new news headline. - - * ``errorEvent(reqId: int, errorCode: int, errorString: str, contract: Contract)``: + + * ``errorEvent(reqId: int, errorCode: int, errorString: str, + contract: Contract)``: Emits the reqId/orderId and TWS error code and string (see https://interactivebrokers.github.io/tws-api/message_codes.html) together with the contract the error applies to (or None if no contract applies). - + * ``timeoutEvent(idlePeriod: float)``: Is emitted if no data is received for longer than the timeout period specified with ``setTimeout``. The value emitted is the period in seconds since the last update. - + Note that it is not advisible to place new requests inside an event handler as it may lead to too much recursion. """ - events = ('connectedEvent', 'disconnectedEvent', 'updateEvent', - 'pendingTickersEvent', 'barUpdateEvent', - 'newOrderEvent', 'orderModifyEvent', 'cancelOrderEvent', - 'openOrderEvent', 'orderStatusEvent', - 'execDetailsEvent', 'commissionReportEvent', - 'updatePortfolioEvent', 'positionEvent', 'accountValueEvent', - 'accountSummaryEvent', 'pnlEvent', 'pnlSingleEvent', - 'tickNewsEvent', 'errorEvent', 'timeoutEvent') + events = ( + 'connectedEvent', 'disconnectedEvent', 'updateEvent', + 'pendingTickersEvent', 'barUpdateEvent', + 'newOrderEvent', 'orderModifyEvent', 'cancelOrderEvent', + 'openOrderEvent', 'orderStatusEvent', + 'execDetailsEvent', 'commissionReportEvent', + 'updatePortfolioEvent', 'positionEvent', 'accountValueEvent', + 'accountSummaryEvent', 'pnlEvent', 'pnlSingleEvent', + 'tickNewsEvent', 'errorEvent', 'timeoutEvent') RequestTimeout = None # timeout in seconds for requests @@ -189,14 +198,15 @@ def __repr__(self): self.client.isConnected() else 'not connected') return f'<{self.__class__.__name__} {conn}>' - def connect(self, host: str='127.0.0.1', port: int='7497', + def connect( + self, host: str='127.0.0.1', port: int='7497', clientId: int=1, timeout: float=2): """ Connect to a TWS or IB gateway application running at host:port. After the connect the client is immediately ready to serve requests. - + Setting clientId=0 will merge manual TWS trading with this client. - + This method is blocking. """ self._run(self.connectAsync(host, port, clientId, timeout)) @@ -205,7 +215,7 @@ def connect(self, host: str='127.0.0.1', port: int='7497', def disconnect(self) -> None: """ Disconnect from a TWS or IB gateway application. - This will clear all session state. + This will clear all session state. """ self.wrapper.reset() if not self.client.isConnected(): @@ -232,7 +242,7 @@ def isConnected(self) -> bool: timeRange = staticmethod(util.timeRange) waitUntil = staticmethod(util.waitUntil) - def _run(self, *awaitables): + def _run(self, *awaitables: List[Awaitable]): return util.run(*awaitables, timeout=self.RequestTimeout) def waitOnUpdate(self, timeout: float=0) -> True: @@ -263,7 +273,7 @@ def loopUntil(self, condition=None, timeout: float=0) -> Iterator: def setCallback(self, eventName: str, callback: Callable) -> None: """ Depreciated: Use events instead. - + Set an optional callback to be invoked after an event. Unsetting is done by supplying None as callback. """ @@ -274,7 +284,7 @@ def setTimeout(self, timeout: float=60): Set timeout in seconds for receiving messages from TWS/IBG. This will emit the ``timeout`` callback if there is no new data for too long. - + The timeout fires once per connected session but can be set again after firing or after a reconnect. """ @@ -301,7 +311,7 @@ def accountSummary(self, account: str='') -> List[AccountValue]: """ List of account values for the given account, or of all accounts if account is left blank. - + This method is blocking on first run, non-blocking after that. """ if not self.wrapper.acctSummary: @@ -335,7 +345,7 @@ def pnl(self, account='', modelCode='') -> List[PnL]: """ List of subscribed ``PnL`` objects (profit and loss), optionally filtered by account and/or modelCode. - + The ``PnL`` objects are kept live updated. """ return [v for v in self.wrapper.pnls.values() if @@ -347,7 +357,7 @@ def pnlSingle(self, account='', modelCode='', conId='') -> List[PnLSingle]: List of subscribed ``PnLSingle`` objects (profit and loss for single positions), optionally filtered by account, modelCode and/or conId. - + The ``PnLSingle`` objects are kept live updated """ return [v for v in self.wrapper.pnlSingles.values() if @@ -372,8 +382,8 @@ def orders(self) -> List[Order]: """ List of all orders from this session. """ - return list(trade.order - for trade in self.wrapper.trades.values()) + return list( + trade.order for trade in self.wrapper.trades.values()) def openOrders(self) -> List[Order]: """ @@ -434,7 +444,8 @@ def newsBulletins(self) -> List[NewsBulletin]: """ return list(self.wrapper.newsBulletins.values()) - def reqTickers(self, *contracts: List[Contract], + def reqTickers( + self, *contracts: List[Contract], regulatorySnapshot: bool=False) -> List[Ticker]: """ Request and return a list of snapshot tickers for the given contracts. @@ -442,8 +453,9 @@ def reqTickers(self, *contracts: List[Contract], This method is blocking. """ - return self._run(self.reqTickersAsync(*contracts, - regulatorySnapshot=regulatorySnapshot)) + return self._run( + self.reqTickersAsync( + *contracts, regulatorySnapshot=regulatorySnapshot)) def qualifyContracts(self, *contracts: List[Contract]) -> List[Contract]: """ @@ -456,7 +468,8 @@ def qualifyContracts(self, *contracts: List[Contract]) -> List[Contract]: """ return self._run(self.qualifyContractsAsync(*contracts)) - def bracketOrder(self, action: str, quantity: float, + def bracketOrder( + self, action: str, quantity: float, limitPrice: float, takeProfitPrice: float, stopLossPrice: float, **kwargs) -> BracketOrder: """ @@ -473,22 +486,22 @@ def bracketOrder(self, action: str, quantity: float, assert action in ('BUY', 'SELL') reverseAction = 'BUY' if action == 'SELL' else 'SELL' parent = LimitOrder( - action, quantity, limitPrice, - orderId=self.client.getReqId(), - transmit=False, - **kwargs) + action, quantity, limitPrice, + orderId=self.client.getReqId(), + transmit=False, + **kwargs) takeProfit = LimitOrder( - reverseAction, quantity, takeProfitPrice, - orderId=self.client.getReqId(), - transmit=False, - parentId=parent.orderId, - **kwargs) + reverseAction, quantity, takeProfitPrice, + orderId=self.client.getReqId(), + transmit=False, + parentId=parent.orderId, + **kwargs) stopLoss = StopOrder( - reverseAction, quantity, stopLossPrice, - orderId=self.client.getReqId(), - transmit=True, - parentId=parent.orderId, - **kwargs) + reverseAction, quantity, stopLossPrice, + orderId=self.client.getReqId(), + transmit=True, + parentId=parent.orderId, + **kwargs) return BracketOrder(parent, takeProfit, stopLoss) @staticmethod @@ -496,7 +509,7 @@ def oneCancelsAll(orders: List[Order], ocaGroup: str, ocaType: int) -> List[Order]: """ Place the trades in the same OCA group. - + https://interactivebrokers.github.io/tws-api/oca.html """ for o in orders: @@ -508,7 +521,7 @@ def whatIfOrder(self, contract: Contract, order: Order) -> OrderState: """ Retrieve commission and margin impact without actually placing the order. The given order will not be modified in any way. - + This method is blocking. """ return self._run(self.whatIfOrderAsync(contract, order)) @@ -524,13 +537,12 @@ def placeOrder(self, contract: Contract, order: Order) -> Trade: self.client.placeOrder(orderId, contract, order) now = datetime.datetime.now(datetime.timezone.utc) key = self.wrapper.orderKey( - self.wrapper.clientId, orderId, order.permId) + self.wrapper.clientId, orderId, order.permId) trade = self.wrapper.trades.get(key) if trade: # this is a modification of an existing order assert trade.orderStatus.status not in OrderStatus.DoneStates - logEntry = TradeLogEntry(now, - trade.orderStatus.status, 'Modify') + logEntry = TradeLogEntry(now, trade.orderStatus.status, 'Modify') trade.log.append(logEntry) self._logger.info(f'placeOrder: Modify order {trade}') trade.modifyEvent.emit(trade) @@ -541,7 +553,7 @@ def placeOrder(self, contract: Contract, order: Order) -> Trade: orderStatus = OrderStatus(status=OrderStatus.PendingSubmit) logEntry = TradeLogEntry(now, orderStatus.status, '') trade = Trade( - contract, order, orderStatus, [], [logEntry]) + contract, order, orderStatus, [], [logEntry]) self.wrapper.trades[key] = trade self._logger.info(f'placeOrder: New order {trade}') self.newOrderEvent.emit(trade) @@ -554,7 +566,8 @@ def cancelOrder(self, order: Order) -> Trade: """ self.client.cancelOrder(order.orderId) now = datetime.datetime.now(datetime.timezone.utc) - key = self.wrapper.orderKey(order.clientId, order.orderId, order.permId) + key = self.wrapper.orderKey( + order.clientId, order.orderId, order.permId) trade = self.wrapper.trades.get(key) if trade: if trade.orderStatus.status not in OrderStatus.DoneStates: @@ -592,7 +605,7 @@ def reqCurrentTime(self) -> datetime.datetime: def reqAccountUpdates(self, account: str='') -> None: """ This is called at startup - no need to call again. - + Request account and portfolio values of the account and keep updated. Returns when both account values and portfolio are filled. @@ -630,7 +643,7 @@ def reqAutoOpenOrders(self, autoBind: bool=True): Bind manual TWS orders so that they can be managed from this client. The clientId must be 0 and the TWS API setting "Use negative numbers to bind automatic orders" must be checked. - + https://interactivebrokers.github.io/tws-api/open_orders.html https://interactivebrokers.github.io/tws-api/modifying_orders.html """ @@ -640,7 +653,7 @@ def reqAutoOpenOrders(self, autoBind: bool=True): def reqOpenOrders(self) -> List[Order]: """ Request and return a list a list of open orders. - + This method can give stale information where a new open order is not reported or an already filled or cancelled order is reported as open. It is recommended to use the more reliable and much faster @@ -651,8 +664,8 @@ def reqOpenOrders(self) -> List[Order]: return self._run(self.reqOpenOrdersAsync()) @api - def reqExecutions(self, - execFilter: ExecutionFilter=None) -> List[Fill]: + def reqExecutions( + self, execFilter: ExecutionFilter=None) -> List[Fill]: """ It is recommended to use :py:meth:`.fills` or :py:meth:`.executions` instead. @@ -677,12 +690,12 @@ def reqPositions(self) -> List[Position]: @api def reqPnL(self, account: str, modelCode: str='') -> PnL: """ - Start a subscription for profit and loss events for the given + Start a subscription for profit and loss events for the given account and optional modelCode. - + Returns a ``PnL`` object that is kept live updated. The result can also be queried from :py:meth:`.pnl`. - + https://interactivebrokers.github.io/tws-api/pnl.html """ key = (account, modelCode) @@ -705,18 +718,20 @@ def cancelPnL(self, account, modelCode: str=''): self.client.cancelPnL(reqId) self.wrapper.pnls.pop(reqId, None) else: - self._logger.error('cancelPnL: No subscription for ' - f'account {account}, modelCode {modelCode}') + self._logger.error( + 'cancelPnL: No subscription for ' + f'account {account}, modelCode {modelCode}') @api - def reqPnLSingle(self, account: str, modelCode: str, + def reqPnLSingle( + self, account: str, modelCode: str, conId: int) -> PnLSingle: """ Start a subscription for profit and loss events for single positions. - + Returns a ``PnLSingle`` object that is kept live updated. The result can also be queried from :py:meth:`.pnlSingle`. - + https://interactivebrokers.github.io/tws-api/pnl.html """ key = (account, modelCode, conId) @@ -740,8 +755,9 @@ def cancelPnLSingle(self, account: str, modelCode: str, conId: int): self.client.cancelPnLSingle(reqId) self.wrapper.pnlSingles.pop(reqId, None) else: - self._logger.error('cancelPnLSingle: No subscription for ' - f'account {account}, modelCode {modelCode}, conId {conId}') + self._logger.error( + 'cancelPnLSingle: No subscription for ' + f'account {account}, modelCode {modelCode}, conId {conId}') @api def reqContractDetails(self, contract: Contract) -> List[ContractDetails]: @@ -749,10 +765,10 @@ def reqContractDetails(self, contract: Contract) -> List[ContractDetails]: Get a list of contract details that match the given contract. If the returned list is empty then the contract is not known; If the list has multiple values then the contract is ambiguous. - + The fully qualified contract is available in the the ContractDetails.contract attribute. - + This method is blocking. https://interactivebrokers.github.io/tws-api/contract_details.html @@ -764,7 +780,7 @@ def reqMatchingSymbols(self, pattern: str) -> List[ContractDescription]: """ Request contract descriptions of contracts that match the given pattern. - + This method is blocking. https://interactivebrokers.github.io/tws-api/matching_symbols.html @@ -775,17 +791,18 @@ def reqMarketRule(self, marketRuleId: int): """ Request list of price increments corresponding to given market rule (the market rule id is given by :py:meth:`.reqContractDetails`). - + https://interactivebrokers.github.io/tws-api/minimum_increment.html """ return self._run(self.reqMarketRuleAsync(marketRuleId)) @api - def reqRealTimeBars(self, contract, barSize, whatToShow, + def reqRealTimeBars( + self, contract, barSize, whatToShow, useRTH, realTimeBarsOptions=None) -> RealTimeBarList: """ Request realtime 5 second bars. - + https://interactivebrokers.github.io/tws-api/realtime_bars.html """ reqId = self.client.getReqId() @@ -797,8 +814,8 @@ def reqRealTimeBars(self, contract, barSize, whatToShow, bars.useRTH = useRTH bars.realTimeBarsOptions = realTimeBarsOptions self.wrapper.startBars(reqId, contract, bars) - self.client.reqRealTimeBars(reqId, contract, barSize, whatToShow, - useRTH, realTimeBarsOptions) + self.client.reqRealTimeBars( + reqId, contract, barSize, whatToShow, useRTH, realTimeBarsOptions) return bars @api @@ -810,7 +827,8 @@ def cancelRealTimeBars(self, bars: RealTimeBarList) -> None: self.wrapper.endBars(bars) @api - def reqHistoricalData(self, contract: Contract, endDateTime: object, + def reqHistoricalData( + self, contract: Contract, endDateTime: object, durationStr: str, barSizeSetting: str, whatToShow: str, useRTH: bool, formatDate: int=1, keepUpToDate: bool=False, @@ -819,16 +837,17 @@ def reqHistoricalData(self, contract: Contract, endDateTime: object, The endDateTime can be set to '' to indicate the current time, or it can be given as a datetime.date or datetime.datetime, or it can be given as a string in 'yyyyMMdd HH:mm:ss' format. - + If formatDate=2 is used for an intraday request the returned date field will be a timezone-aware datetime.datetime with UTC timezone. - + This method is blocking. https://interactivebrokers.github.io/tws-api/historical_bars.html """ - return self._run(self.reqHistoricalDataAsync(contract, endDateTime, - durationStr, barSizeSetting, whatToShow, + return self._run( + self.reqHistoricalDataAsync( + contract, endDateTime, durationStr, barSizeSetting, whatToShow, useRTH, formatDate, keepUpToDate, chartOptions)) @api @@ -840,7 +859,8 @@ def cancelHistoricalData(self, bars: BarDataList) -> None: self.wrapper.endBars(bars) @api - def reqHistoricalTicks(self, contract: Contract, + def reqHistoricalTicks( + self, contract: Contract, startDateTime: str, endDateTime: str, numberOfTicks: int, whatToShow: str, useRth: bool, ignoreSize: bool=False, miscOptions: List[TagValue]=None): @@ -848,12 +868,13 @@ def reqHistoricalTicks(self, contract: Contract, Request historical ticks. This method is blocking. - + https://interactivebrokers.github.io/tws-api/historical_time_and_sales.html """ - return self._run(self.reqHistoricalTicksAsync(contract, - startDateTime, endDateTime, numberOfTicks, whatToShow, useRth, - ignoreSize, miscOptions)) + return self._run( + self.reqHistoricalTicksAsync( + contract, startDateTime, endDateTime, numberOfTicks, + whatToShow, useRth, ignoreSize, miscOptions)) @api def reqMarketDataType(self, marketDataType: int) -> None: @@ -863,27 +884,31 @@ def reqMarketDataType(self, marketDataType: int) -> None: * 2 = Frozen * 3 = Delayed * 4 = Delayed frozen - + https://interactivebrokers.github.io/tws-api/market_data_type.html """ self.client.reqMarketDataType(marketDataType) @api - def reqHeadTimeStamp(self, contract: Contract, whatToShow: str, + def reqHeadTimeStamp( + self, contract: Contract, whatToShow: str, useRTH: bool, formatDate: int=1) -> datetime.datetime: """ - Get the datetime of earliest available historical data for the contract. - + Get the datetime of earliest available historical data + for the contract. + If formatDate=2 then the result is returned as a timezone-aware datetime.datetime with UTC timezone. """ - return self._run(self.reqHeadTimeStampAsync(contract, whatToShow, - useRTH, formatDate)) + return self._run( + self.reqHeadTimeStampAsync( + contract, whatToShow, useRTH, formatDate)) @api - def reqMktData(self, contract: Contract, genericTickList: str='', - snapshot: bool=False, regulatorySnapshot: bool=False, - mktDataOptions: List[TagValue]=None) -> Ticker: + def reqMktData( + self, contract: Contract, genericTickList: str='', + snapshot: bool=False, regulatorySnapshot: bool=False, + mktDataOptions: List[TagValue]=None) -> Ticker: """ Subscribe to tick data or request a snapshot. Returns the Ticker that holds the market data. The ticker will @@ -894,8 +919,9 @@ def reqMktData(self, contract: Contract, genericTickList: str='', """ reqId = self.client.getReqId() ticker = self.wrapper.startTicker(reqId, contract, 'mktData') - self.client.reqMktData(reqId, contract, genericTickList, - snapshot, regulatorySnapshot, mktDataOptions) + self.client.reqMktData( + reqId, contract, genericTickList, snapshot, + regulatorySnapshot, mktDataOptions) return ticker def cancelMktData(self, contract: Contract): @@ -908,16 +934,17 @@ def cancelMktData(self, contract: Contract): if reqId: self.client.cancelMktData(reqId) else: - self._logger.error('cancelMktData: ' - f'No reqId found for contract {contract}') + self._logger.error( + 'cancelMktData: ' f'No reqId found for contract {contract}') @api - def reqTickByTickData(self, contract: Contract, tickType: str, + def reqTickByTickData( + self, contract: Contract, tickType: str, numberOfTicks: int=0, ignoreSize: bool=False) -> Ticker: """ Subscribe to tick-by-tick data and return the Ticker that holds the ticks in ticker.tickByTicks. - + The tickType is one of 'Last', 'AllLast', 'BidAsk' or 'MidPoint'. """ reqId = self.client.getReqId() @@ -925,8 +952,8 @@ def reqTickByTickData(self, contract: Contract, tickType: str, if ibapi.__version__ == '9.73.6': self.client.reqTickByTickData(reqId, contract, tickType) else: - self.client.reqTickByTickData(reqId, contract, tickType, - numberOfTicks, ignoreSize) + self.client.reqTickByTickData( + reqId, contract, tickType, numberOfTicks, ignoreSize) return ticker def cancelTickByTickData(self, contract: Contract, tickType: str): @@ -939,25 +966,26 @@ def cancelTickByTickData(self, contract: Contract, tickType: str): if reqId: self.client.cancelTickByTickData(reqId) else: - self._logger.error('cancelMktData: ' - f'No reqId found for contract {contract}') + self._logger.error( + f'cancelMktData: No reqId found for contract {contract}') @api def reqMktDepthExchanges(self) -> List[DepthMktDataDescription]: """ Get those exchanges that have have multiple market makers - (and have ticks returned with marketMaker info). + (and have ticks returned with marketMaker info). """ return self._run(self.reqMktDepthExchangesAsync()) @api - def reqMktDepth(self, contract: Contract, numRows: int=5, - mktDepthOptions=None) -> Ticker: + def reqMktDepth( + self, contract: Contract, numRows: int=5, + mktDepthOptions=None) -> Ticker: """ Subscribe to market depth data (a.k.a. DOM, L2 or order book). Returns the Ticker that holds the market depth ticks in ticker.domBids and ticker.domAsks. - + https://interactivebrokers.github.io/tws-api/market_depth.html """ reqId = self.client.getReqId() @@ -976,46 +1004,51 @@ def cancelMktDepth(self, contract: Contract): if reqId: self.client.cancelMktDepth(reqId) else: - self._logger.error('cancelMktDepth: ' - f'No reqId found for contract {contract}') + self._logger.error( + f'cancelMktDepth: No reqId found for contract {contract}') @api - def reqHistogramData(self, contract: Contract, + def reqHistogramData( + self, contract: Contract, useRTH: bool, period: str) -> List[HistogramData]: """ Get histogram data of the contract over the period. - + This method is blocking. https://interactivebrokers.github.io/tws-api/histograms.html """ - return self._run(self.reqHistogramDataAsync( - contract, useRTH, period)) + return self._run( + self.reqHistogramDataAsync(contract, useRTH, period)) @api - def reqFundamentalData(self, contract: Contract, reportType: str, + def reqFundamentalData( + self, contract: Contract, reportType: str, fundamentalDataOptions=None) -> str: """ Get Reuters' fundamental data of the contract in XML format. - + This method is blocking. https://interactivebrokers.github.io/tws-api/reuters_fundamentals.html """ - return self._run(self.reqFundamentalDataAsync(contract, reportType, - fundamentalDataOptions)) + return self._run( + self.reqFundamentalDataAsync( + contract, reportType, fundamentalDataOptions)) @api - def reqScannerData(self, subscription: ScannerSubscription, + def reqScannerData( + self, subscription: ScannerSubscription, scannerSubscriptionOptions=None) -> List[ScanData]: """ Do a market scan. - + This method is blocking. https://interactivebrokers.github.io/tws-api/market_scanners.html """ - return self._run(self.reqScannerSubscriptionAsync( + return self._run( + self.reqScannerSubscriptionAsync( subscription, scannerSubscriptionOptions)) @api @@ -1028,56 +1061,65 @@ def reqScannerParameters(self) -> str: return self._run(self.reqScannerParametersAsync()) @api - def calculateImpliedVolatility(self, contract: Contract, - optionPrice: float, underPrice: float, - implVolOptions=None) -> OptionComputation: + def calculateImpliedVolatility( + self, contract: Contract, + optionPrice: float, underPrice: float, + implVolOptions=None) -> OptionComputation: """ Calculate the volatility given the option price. - + This method is blocking. https://interactivebrokers.github.io/tws-api/option_computations.html """ - return self._run(self.calculateImpliedVolatilityAsync( + return self._run( + self.calculateImpliedVolatilityAsync( contract, optionPrice, underPrice, implVolOptions)) @api - def calculateOptionPrice(self, contract: Contract, + def calculateOptionPrice( + self, contract: Contract, volatility: float, underPrice: float, optPrcOptions=None) -> OptionComputation: """ Calculate the option price given the volatility. - + This method is blocking. https://interactivebrokers.github.io/tws-api/option_computations.html """ - return self._run(self.calculateOptionPriceAsync( + return self._run( + self.calculateOptionPriceAsync( contract, volatility, underPrice, optPrcOptions)) @api - def reqSecDefOptParams(self, underlyingSymbol: str, + def reqSecDefOptParams( + self, underlyingSymbol: str, futFopExchange: str, underlyingSecType: str, underlyingConId: str) -> List[OptionChain]: """ Get the option chain. - + This method is blocking. https://interactivebrokers.github.io/tws-api/options.html """ - return self._run(self.reqSecDefOptParamsAsync(underlyingSymbol, - futFopExchange, underlyingSecType, underlyingConId)) + return self._run( + self.reqSecDefOptParamsAsync( + underlyingSymbol, futFopExchange, + underlyingSecType, underlyingConId)) @api - def exerciseOptions(self, contract, exerciseAction, exerciseQuantity, + def exerciseOptions( + self, contract, exerciseAction, exerciseQuantity, account, override) -> None: """ https://interactivebrokers.github.io/tws-api/options.html """ reqId = self.client.getReqId() - self.client.exerciseOptions(reqId, contract, exerciseAction, - exerciseQuantity, account, override) + self.client.exerciseOptions( + reqId, contract, exerciseAction, exerciseQuantity, + account, override) @api def reqNewsProviders(self) -> List[NewsProvider]: @@ -1089,7 +1131,8 @@ def reqNewsProviders(self) -> List[NewsProvider]: return self._run(self.reqNewsProvidersAsync()) @api - def reqNewsArticle(self, providerCode: str, articleId: str, + def reqNewsArticle( + self, providerCode: str, articleId: str, newsArticleOptions: List[TagValue]=None) -> NewsArticle: """ Get the body of a news article. @@ -1098,11 +1141,13 @@ def reqNewsArticle(self, providerCode: str, articleId: str, https://interactivebrokers.github.io/tws-api/news.html """ - return self._run(self.reqNewsArticleAsync(providerCode, articleId, - newsArticleOptions)) + return self._run( + self.reqNewsArticleAsync( + providerCode, articleId, newsArticleOptions)) @api - def reqHistoricalNews(self, conId: int, providerCodes: str, + def reqHistoricalNews( + self, conId: int, providerCodes: str, startDateTime: str, endDateTime: str, totalResults: int, historicalNewsOptions=None) -> HistoricalNews: """ @@ -1110,9 +1155,10 @@ def reqHistoricalNews(self, conId: int, providerCodes: str, This method is blocking. """ - return self._run(self.reqHistoricalNewsAsync(conId, providerCodes, - startDateTime, endDateTime, totalResults, - historicalNewsOptions)) + return self._run( + self.reqHistoricalNewsAsync( + conId, providerCodes, startDateTime, endDateTime, + totalResults, historicalNewsOptions)) @api def reqNewsBulletins(self, allMessages: bool) -> None: @@ -1133,13 +1179,13 @@ def cancelNewsBulletins(self) -> None: def requestFA(self, faDataType: int) -> str: """ faDataType: - + * 1 = Groups; * 2 = Profiles; * 3 = Account Aliases. This method is blocking. - + https://interactivebrokers.github.io/tws-api/financial_advisor_methods_and_orders.html """ return self._run(self.requestFAAsync(faDataType)) @@ -1158,10 +1204,10 @@ async def connectAsync(self, host, port, clientId, timeout=2): await self.client.connectAsync(host, port, clientId, timeout) accounts = self.client.getAccounts() await asyncio.gather( - self.reqAccountUpdatesAsync(accounts[0]), - *(self.reqAccountUpdatesMultiAsync(a) for a in accounts), - self.reqPositionsAsync(), - self.reqExecutionsAsync()) + self.reqAccountUpdatesAsync(accounts[0]), + *(self.reqAccountUpdatesMultiAsync(a) for a in accounts), + self.reqPositionsAsync(), + self.reqExecutionsAsync()) if clientId == 0: # autobind manual orders self.reqAutoOpenOrders(True) @@ -1170,15 +1216,16 @@ async def connectAsync(self, host, port, clientId, timeout=2): async def qualifyContractsAsync(self, *contracts): detailsLists = await asyncio.gather( - *(self.reqContractDetailsAsync(c) for c in contracts)) + *(self.reqContractDetailsAsync(c) for c in contracts)) result = [] for contract, detailsList in zip(contracts, detailsLists): if not detailsList: self._logger.error(f'Unknown contract: {contract}') elif len(detailsList) > 1: possibles = [details.contract for details in detailsList] - self._logger.error(f'Ambiguous contract: {contract}, ' - f'possibles are {possibles}') + self._logger.error( + f'Ambiguous contract: {contract}, ' + f'possibles are {possibles}') else: c = detailsList[0].contract expiry = c.lastTradeDateOrContractMonth @@ -1202,8 +1249,8 @@ async def reqTickersAsync(self, *contracts, regulatorySnapshot=False): futures.append(future) ticker = self.wrapper.startTicker(reqId, contract, 'snapshot') tickers.append(ticker) - self.client.reqMktData(reqId, contract, '', - True, regulatorySnapshot, []) + self.client.reqMktData( + reqId, contract, '', True, regulatorySnapshot, []) await asyncio.gather(*futures) for ticker in tickers: self.wrapper.endTicker(ticker, 'snapshot') @@ -1235,8 +1282,8 @@ def reqAccountUpdatesMultiAsync(self, account, modelCode=''): def reqAccountSummaryAsync(self): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId) - self.client.reqAccountSummary(reqId, groupName='All', - tags=AccountSummaryTags.AllTags) + self.client.reqAccountSummary( + reqId, groupName='All', tags=AccountSummaryTags.AllTags) return future def reqOpenOrdersAsync(self): @@ -1277,7 +1324,8 @@ def reqMarketRuleAsync(self, marketRuleId): self.client.reqMarketRule(marketRuleId) return future - def reqHistoricalDataAsync(self, contract, endDateTime, + def reqHistoricalDataAsync( + self, contract, endDateTime, durationStr, barSizeSetting, whatToShow, useRTH, formatDate=1, keepUpToDate=False, chartOptions=None): reqId = self.client.getReqId() @@ -1296,29 +1344,30 @@ def reqHistoricalDataAsync(self, contract, endDateTime, if keepUpToDate: self.wrapper.startBars(reqId, contract, bars) end = util.formatIBDatetime(endDateTime) - self.client.reqHistoricalData(reqId, contract, end, - durationStr, barSizeSetting, whatToShow, - useRTH, formatDate, keepUpToDate, chartOptions) + self.client.reqHistoricalData( + reqId, contract, end, durationStr, barSizeSetting, + whatToShow, useRTH, formatDate, keepUpToDate, chartOptions) return future - def reqHistoricalTicksAsync(self, contract, startDateTime, endDateTime, + def reqHistoricalTicksAsync( + self, contract, startDateTime, endDateTime, numberOfTicks, whatToShow, useRth, ignoreSize=False, miscOptions=None): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId, contract) start = util.formatIBDatetime(startDateTime) end = util.formatIBDatetime(endDateTime) - self.client.reqHistoricalTicks(reqId, contract, start, end, - numberOfTicks, whatToShow, useRth, - ignoreSize, miscOptions or []) + self.client.reqHistoricalTicks( + reqId, contract, start, end, numberOfTicks, whatToShow, useRth, + ignoreSize, miscOptions or []) return future - def reqHeadTimeStampAsync(self, contract, whatToShow, - useRTH, formatDate): + def reqHeadTimeStampAsync( + self, contract, whatToShow, useRTH, formatDate): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId, contract) - self.client.reqHeadTimeStamp(reqId, contract, whatToShow, - useRTH, formatDate) + self.client.reqHeadTimeStamp( + reqId, contract, whatToShow, useRTH, formatDate) return future def reqMktDepthExchangesAsync(self): @@ -1332,20 +1381,20 @@ def reqHistogramDataAsync(self, contract, useRTH, period): self.client.reqHistogramData(reqId, contract, useRTH, period) return future - def reqFundamentalDataAsync(self, contract, reportType, - fundamentalDataOptions=None): + def reqFundamentalDataAsync( + self, contract, reportType, fundamentalDataOptions=None): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId, contract) - self.client.reqFundamentalData(reqId, contract, reportType, - fundamentalDataOptions) + self.client.reqFundamentalData( + reqId, contract, reportType, fundamentalDataOptions) return future - async def reqScannerSubscriptionAsync(self, subscription, - scannerSubscriptionOptions=None): + async def reqScannerSubscriptionAsync( + self, subscription, scannerSubscriptionOptions=None): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId) - self.client.reqScannerSubscription(reqId, subscription, - scannerSubscriptionOptions) + self.client.reqScannerSubscription( + reqId, subscription, scannerSubscriptionOptions) await future self.client.cancelScannerSubscription(reqId) return future.result() @@ -1355,12 +1404,12 @@ def reqScannerParametersAsync(self): self.client.reqScannerParameters() return future - async def calculateImpliedVolatilityAsync(self, contract, optionPrice, - underPrice, implVolOptions): + async def calculateImpliedVolatilityAsync( + self, contract, optionPrice, underPrice, implVolOptions): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId, contract) - self.client.calculateImpliedVolatility(reqId, contract, optionPrice, - underPrice, implVolOptions) + self.client.calculateImpliedVolatility( + reqId, contract, optionPrice, underPrice, implVolOptions) try: await asyncio.wait_for(future, 4) return future.result() @@ -1370,12 +1419,12 @@ async def calculateImpliedVolatilityAsync(self, contract, optionPrice, finally: self.client.cancelCalculateImpliedVolatility(reqId) - async def calculateOptionPriceAsync(self, contract, volatility, - underPrice, optPrcOptions): + async def calculateOptionPriceAsync( + self, contract, volatility, underPrice, optPrcOptions): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId, contract) - self.client.calculateOptionPrice(reqId, contract, volatility, - underPrice, optPrcOptions) + self.client.calculateOptionPrice( + reqId, contract, volatility, underPrice, optPrcOptions) try: await asyncio.wait_for(future, 4) return future.result() @@ -1385,12 +1434,14 @@ async def calculateOptionPriceAsync(self, contract, volatility, finally: self.client.cancelCalculateOptionPrice(reqId) - def reqSecDefOptParamsAsync(self, underlyingSymbol, - futFopExchange, underlyingSecType, underlyingConId): + def reqSecDefOptParamsAsync( + self, underlyingSymbol, futFopExchange, + underlyingSecType, underlyingConId): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId) - self.client.reqSecDefOptParams(reqId, underlyingSymbol, - futFopExchange, underlyingSecType, underlyingConId) + self.client.reqSecDefOptParams( + reqId, underlyingSymbol, futFopExchange, + underlyingSecType, underlyingConId) return future def reqNewsProvidersAsync(self): @@ -1398,21 +1449,22 @@ def reqNewsProvidersAsync(self): self.client.reqNewsProviders() return future - def reqNewsArticleAsync(self, providerCode, articleId, - newsArticleOptions): + def reqNewsArticleAsync( + self, providerCode, articleId, newsArticleOptions): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId) - self.client.reqNewsArticle(reqId, providerCode, articleId, - newsArticleOptions) + self.client.reqNewsArticle( + reqId, providerCode, articleId, newsArticleOptions) return future - async def reqHistoricalNewsAsync(self, conId, providerCodes, - startDateTime, endDateTime, totalResults, - historicalNewsOptions=None): + async def reqHistoricalNewsAsync( + self, conId, providerCodes, startDateTime, endDateTime, + totalResults, historicalNewsOptions=None): reqId = self.client.getReqId() future = self.wrapper.startReq(reqId) - self.client.reqHistoricalNews(reqId, conId, providerCodes, - startDateTime, endDateTime, totalResults, historicalNewsOptions) + self.client.reqHistoricalNews( + reqId, conId, providerCodes, startDateTime, endDateTime, + totalResults, historicalNewsOptions) try: await asyncio.wait_for(future, 4) return future.result() @@ -1430,9 +1482,9 @@ async def requestFAAsync(self, faDataType): if __name__ == '__main__': -# import uvloop -# asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) - from ib_insync.contract import Stock, Forex, Index, Option, Future, CFD + # import uvloop + # asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + from ib_insync.contract import Stock, Forex, Index, Option asyncio.get_event_loop().set_debug(True) util.logToConsole(logging.DEBUG) ib = IB() @@ -1458,15 +1510,18 @@ async def requestFAAsync(self, faDataType): print(aex, eurusd, intc) print(ib.reqContractDetails(wrongContract)) if 0: - sub = ScannerSubscription(instrument='FUT.US', - locationCode='FUT.GLOBEX', scanCode='TOP_PERC_GAIN') + sub = ScannerSubscription( + instrument='FUT.US', locationCode='FUT.GLOBEX', + scanCode='TOP_PERC_GAIN') print(ib.reqScannerData(sub, [])) print(len(ib.reqScannerParameters())) if 0: - print(ib.calculateImpliedVolatility(option, - optionPrice=6.1, underPrice=525)) - print(ib.calculateOptionPrice(option, - volatility=0.14, underPrice=525)) + print( + ib.calculateImpliedVolatility( + option, optionPrice=6.1, underPrice=525)) + print( + ib.calculateOptionPrice( + option, volatility=0.14, underPrice=525)) if 0: ib.qualifyContracts(amd) ticker = ib.reqTickers(amd) @@ -1479,12 +1534,12 @@ async def requestFAAsync(self, faDataType): if 0: print(ib.reqContractDetails(aapl)) bars = ib.reqHistoricalData( - aapl, '', '1 D', '1 hour', 'MIDPOINT', False, 1, False, None) + aapl, '', '1 D', '1 hour', 'MIDPOINT', False, 1, False, None) print(len(bars)) print(bars[0]) if 0: bars = ib.reqHistoricalData( - aapl, '', '1 D', '1 hour', 'MIDPOINT', False, 1, True, None) + aapl, '', '1 D', '1 hour', 'MIDPOINT', False, 1, True, None) prevBar = None while ib.waitOnUpdate(): currBar = bars[-1] if bars else None @@ -1537,7 +1592,7 @@ async def requestFAAsync(self, faDataType): start = datetime.datetime(2017, 7, 24, 16, 0, 0) end = '' ticks = ib.reqHistoricalTicks( - eurusd, start, end, 100, 'MIDPOINT', True, False, []) + eurusd, start, end, 100, 'MIDPOINT', True, False, []) print(ticks) if 0: start = datetime.time(10, 10, 10) diff --git a/ib_insync/ibcontroller.py b/ib_insync/ibcontroller.py index 3aa0bd298..b5ac02219 100644 --- a/ib_insync/ibcontroller.py +++ b/ib_insync/ibcontroller.py @@ -17,18 +17,18 @@ class IBC(Object): """ Programmatic control over starting and stopping TWS/Gateway using IBC (https://github.com/IbcAlpha/IBC). - + This is not intended to be run in a notebook. - + Arguments: - + * ``twsVersion`` (required): The major version number for TWS or gateway. * ``tradingMode``: 'live' or 'paper'. * ``userid``: IB account username. It is recommended to set the real username/password in a secured IBC config file. * ``password``: IB account password. * ``twsPath``: Path to the TWS installation folder. - + ======= ============== Default ======================= @@ -36,9 +36,9 @@ class IBC(Object): OS X ~/Applications Windows C:\\\\Jts ======= ============== - + * ``twsSettingsPath``: Path to the TWS settings folder. - + ======== ============= Default ======================= @@ -46,9 +46,9 @@ class IBC(Object): OS X ~/Jts Windows Not available ======== ============= - + * ``ibcPath``: Path to the IBC installation folder. - + ======== ============= Default ======================= @@ -56,9 +56,9 @@ class IBC(Object): OS X /opt/ibc Windows C:\\\\IBC ======== ============= - + * ``ibcIni``: Path to the IBC configuration file. - + ======== ============= Default ======================= @@ -66,24 +66,24 @@ class IBC(Object): OS X ~/ibc/config.ini Windows %%HOMEPATH%%\\\\Documents\\\\IBC\\\\config.ini ======== ============= - + * ``javaPath``: Path to Java executable. Default is to use the Java VM included with TWS/gateway. * ``fixuserid``: FIX account user id (gateway only). * ``fixpassword``: FIX account password (gateway only). - + To use IBC on Windows, the proactor (or quamash) event loop must have been set: - + .. code-block:: python - + import asyncio asyncio.set_event_loop(asyncio.ProactorEventLoop()) - + Example usage: - + .. code-block:: python - + ibc = IBC(969, gateway=True, tradingMode='live', userid='edemo', password='demouser') ibc.start() @@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs): Object.__init__(self, *args, **kwargs) if not self.ibcPath: self.ibcPath = '/opt/ibc' if os.sys.platform != 'win32' \ - else 'C:\\IBC' + else 'C:\\IBC' self._proc = None self._monitor = None self._logger = logging.getLogger('ib_insync.IBC') @@ -145,7 +145,8 @@ async def startAsync(self): # create shell command win32 = os.sys.platform == 'win32' - cmd = [f'{self.ibcPath}\\scripts\\StartIBC.bat' if win32 else + cmd = [ + f'{self.ibcPath}\\scripts\\StartIBC.bat' if win32 else f'{self.ibcPath}/scripts/ibcstart.sh'] for k, v in self.dict().items(): arg = IBC._Args[k][2 if win32 else 1] @@ -158,8 +159,8 @@ async def startAsync(self): cmd.append(str(v)) # run shell command - self._proc = await asyncio.create_subprocess_exec(*cmd, - stdout=asyncio.subprocess.PIPE) + self._proc = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE) self._monitor = asyncio.ensure_future(self.monitorAsync()) async def terminateAsync(self): @@ -185,14 +186,14 @@ async def monitorAsync(self): class IBController(Object): """ For new installations it is recommended to use IBC instead. - + Programmatic control over starting and stopping TWS/Gateway using IBController (https://github.com/ib-controller/ib-controller). - + On Windows the the proactor (or quamash) event loop must have been set: - + .. code-block:: python - + import asyncio asyncio.set_event_loop(asyncio.ProactorEventLoop()) @@ -261,8 +262,8 @@ async def startAsync(self): ext = 'bat' if os.sys.platform == 'win32' else 'sh' cmd = f'{d["IBC_PATH"]}/Scripts/DisplayBannerAndLaunch.{ext}' env = {**os.environ, **d} - self._proc = await asyncio.create_subprocess_exec(cmd, env=env, - stdout=asyncio.subprocess.PIPE) + self._proc = await asyncio.create_subprocess_exec( + cmd, env=env, stdout=asyncio.subprocess.PIPE) self._monitor = asyncio.ensure_future(self.monitorAsync()) async def stopAsync(self): @@ -308,15 +309,15 @@ class Watchdog(Object): """ Start, connect and watch over the TWS or gateway app and try to keep it up and running. - + The idea is to wait until there is no traffic coming from the app for a certain amount of time (the ``appTimeout`` parameter). This triggers a historical request to be placed just to see if the app is still alive and well. If yes, then continue, if no then restart the whole app and reconnect. Restarting will also occur directly on error 1100. - + Arguments: - + * ``controller`` (required): IBC or IBController instance; * ``ib`` (required): IB instance to be used. Do no connect this instance as Watchdog takes care of that. @@ -325,17 +326,17 @@ class Watchdog(Object): * ``appStartupTime``: Time (in seconds) that the app is given to start up; Make sure that it is given ample time; * ``appTimeout``: Timeout (in seconds) for network traffic idle time; - * ``retryDelay``: Time (in seconds) to restart app after a previous failure; - If left empty then Watchdog creates the IB instance. - + * ``retryDelay``: Time (in seconds) to restart app after a + previous failure; + Note: ``util.patchAsyncio()`` must have been called before. - + This is not intended to be run in a notebook. Example usage: - + .. code-block:: python - + util.patchAsyncio() ibc = IBC(973, gateway=True, tradingMode='paper') @@ -344,7 +345,7 @@ class Watchdog(Object): app.start() print(app.ib.accountValues()) IB.run() - + Events: * ``startingEvent(watchdog)`` * ``startedEvent(watchdog)`` @@ -354,8 +355,9 @@ class Watchdog(Object): * ``hardTimeoutEvent(watchdog)`` """ - events = ['startingEvent', 'startedEvent', 'stoppingEvent', 'stoppedEvent', - 'softTimeoutEvent', 'hardTimeoutEvent'] + events = [ + 'startingEvent', 'startedEvent', 'stoppingEvent', 'stoppedEvent', + 'softTimeoutEvent', 'hardTimeoutEvent'] defaults = dict( controller=None, @@ -368,7 +370,7 @@ class Watchdog(Object): appTimeout=20, retryDelay=2) __slots__ = list(defaults.keys()) + events + [ - '_watcher', '_logger', '_isRunning', '_isRestarting'] + '_watcher', '_logger', '_isRunning', '_isRestarting'] def __init__(self, *args, **kwargs): Object.__init__(self, *args, **kwargs) @@ -399,7 +401,7 @@ def start(self): self._connect() self.ib.setTimeout(self.appTimeout) self.startedEvent.emit(self) - except: + except Exception: self.controller.terminate() self._scheduleRestart() @@ -417,8 +419,8 @@ def _stop(self): self._scheduleRestart() def _connect(self): - self.ib.connect(self.host, self.port, self.clientId, - self.connectTimeout) + self.ib.connect( + self.host, self.port, self.clientId, self.connectTimeout) def _disconnect(self): self.ib.disconnect() @@ -444,13 +446,13 @@ async def _watchAsync(self): self.softTimeoutEvent.emit(self) contract = Forex('EURUSD') probe = self.ib.reqHistoricalDataAsync( - contract, '', '30 S', '5 secs', 'MIDPOINT', False) + contract, '', '30 S', '5 secs', 'MIDPOINT', False) try: bars = await asyncio.wait_for(probe, 4) if not bars: raise Exception() self.ib.setTimeout(self.appTimeout) - except: + except Exception: # hard timeout, flush everything and start anew self._logger.error('Hard timeout') self.hardTimeoutEvent.emit(self) diff --git a/ib_insync/objects.py b/ib_insync/objects.py index 1d842fc3b..4771acfc4 100644 --- a/ib_insync/objects.py +++ b/ib_insync/objects.py @@ -10,7 +10,7 @@ import ibapi.commission_report # order conditions are imported as-is from ibapi -from ibapi.order_condition import (OrderCondition, ExecutionCondition, +from ibapi.order_condition import (OrderCondition, ExecutionCondition, # noqa OperatorCondition, MarginCondition, ContractCondition, TimeCondition, PriceCondition, PercentChangeCondition, VolumeCondition) @@ -32,7 +32,7 @@ 'OrderCondition ExecutionCondition OperatorCondition MarginCondition ' 'ContractCondition TimeCondition PriceCondition PercentChangeCondition ' 'VolumeCondition' - ).split() +).split() nan = float('nan') @@ -40,7 +40,7 @@ class Object: """ Base object, with: - + * __slots__ to avoid typos; * A general constructor; * A general string representation; @@ -97,10 +97,10 @@ def diff(self, other): """ diff = {} for k in self.__class__.defaults: - l = getattr(self, k) - r = getattr(other, k) - if l != r: - diff[k] = (l, r) + left = getattr(self, k) + right = getattr(other, k) + if left != right: + diff[k] = (left, right) return diff def nonDefaults(self): @@ -131,7 +131,7 @@ class ContractDetails(Object): defaults['contract'] = None defaults.pop('summary', None) __slots__ = list(defaults.keys()) + \ - ['secIdListCount'] # bug in ibapi decoder + ['secIdListCount'] # bug in ibapi decoder # backwards compatibility with ibapi v9.73.06 @property @@ -156,9 +156,9 @@ class ComboLeg(Object): class DeltaNeutralContract(Object): defaults = dict( - conId=0, - delta=0.0, - price=0.0) + conId=0, + delta=0.0, + price=0.0) __slots__ = defaults.keys() @@ -260,7 +260,7 @@ class BarList(list): * ``updateEvent(bars, hasNewBar)`` """ events = ('updateEvent',) - + __slots__ = events def __init__(self, *args): @@ -275,91 +275,119 @@ def __hash__(self): class BarDataList(BarList): - __slots__ = ('reqId', 'contract', 'endDateTime', 'durationStr', - 'barSizeSetting', 'whatToShow', 'useRTH', 'formatDate', - 'keepUpToDate', 'chartOptions') + __slots__ = ( + 'reqId', 'contract', 'endDateTime', 'durationStr', + 'barSizeSetting', 'whatToShow', 'useRTH', 'formatDate', + 'keepUpToDate', 'chartOptions') class RealTimeBarList(BarList): - __slots__ = ('reqId', 'contract', 'barSize', 'whatToShow', 'useRTH', - 'realTimeBarsOptions') + __slots__ = ( + 'reqId', 'contract', 'barSize', 'whatToShow', 'useRTH', + 'realTimeBarsOptions') -AccountValue = namedtuple('AccountValue', +AccountValue = namedtuple( + 'AccountValue', 'account tag value currency modelCode') -TickData = namedtuple('TickData', +TickData = namedtuple( + 'TickData', 'time tickType price size') -HistoricalTick = namedtuple('HistoricalTick', +HistoricalTick = namedtuple( + 'HistoricalTick', 'time price size') -HistoricalTickBidAsk = namedtuple('HistoricalTickBidAsk', +HistoricalTickBidAsk = namedtuple( + 'HistoricalTickBidAsk', 'time mask priceBid priceAsk sizeBid sizeAsk') -HistoricalTickLast = namedtuple('HistoricalTickLast', +HistoricalTickLast = namedtuple( + 'HistoricalTickLast', 'time mask price size exchange specialConditions') -TickByTickAllLast = namedtuple('TickByTickAllLast', +TickByTickAllLast = namedtuple( + 'TickByTickAllLast', 'tickType time price size attribs exchange specialConditions') -TickByTickBidAsk = namedtuple('TickByTickBidAsk', +TickByTickBidAsk = namedtuple( + 'TickByTickBidAsk', 'time bidPrice askPrice bidSize askSize attribs') -TickByTickMidPoint = namedtuple('TickByTickMidPoint', +TickByTickMidPoint = namedtuple( + 'TickByTickMidPoint', 'time midPoint') -MktDepthData = namedtuple('MktDepthData', +MktDepthData = namedtuple( + 'MktDepthData', 'time position marketMaker operation side price size') -DOMLevel = namedtuple('DOMLevel', +DOMLevel = namedtuple( + 'DOMLevel', 'price size marketMaker') -BracketOrder = namedtuple('BracketOrder', +BracketOrder = namedtuple( + 'BracketOrder', 'parent takeProfit stopLoss') -TradeLogEntry = namedtuple('TradeLogEntry', +TradeLogEntry = namedtuple( + 'TradeLogEntry', 'time status message') -PriceIncrement = namedtuple('PriceIncrement', +PriceIncrement = namedtuple( + 'PriceIncrement', 'lowEdge increment') - -ScanData = namedtuple('ScanData', + +ScanData = namedtuple( + 'ScanData', 'rank contractDetails distance benchmark projection legsStr') -TagValue = namedtuple('TagValue', +TagValue = namedtuple( + 'TagValue', 'tag value') -PortfolioItem = namedtuple('PortfolioItem', ( +PortfolioItem = namedtuple( + 'PortfolioItem', 'contract position marketPrice marketValue averageCost ' - 'unrealizedPNL realizedPNL account')) + 'unrealizedPNL realizedPNL account') -Position = namedtuple('Position', +Position = namedtuple( + 'Position', 'account contract position avgCost') -Fill = namedtuple('Fill', +Fill = namedtuple( + 'Fill', 'contract execution commissionReport time') -OptionComputation = namedtuple('OptionComputation', +OptionComputation = namedtuple( + 'OptionComputation', 'impliedVol delta optPrice pvDividend gamma vega theta undPrice') -OptionChain = namedtuple('OptionChain', +OptionChain = namedtuple( + 'OptionChain', 'exchange underlyingConId tradingClass multiplier expirations strikes') -Dividends = namedtuple('Dividends', +Dividends = namedtuple( + 'Dividends', 'past12Months next12Months nextDate nextAmount') -NewsArticle = namedtuple('NewsArticle', +NewsArticle = namedtuple( + 'NewsArticle', 'articleType articleText') -HistoricalNews = namedtuple('HistoricalNews', +HistoricalNews = namedtuple( + 'HistoricalNews', 'time providerCode articleId headline') -NewsTick = namedtuple('NewsTick', +NewsTick = namedtuple( + 'NewsTick', 'timeStamp providerCode articleId headline extraData') -NewsBulletin = namedtuple('NewsBulletin', +NewsBulletin = namedtuple( + 'NewsBulletin', 'msgId msgType message origExchange') -ConnectionStats = namedtuple('ConnectionStats', +ConnectionStats = namedtuple( + 'ConnectionStats', 'startTime duration numBytesRecv numBytesSent numMsgRecv numMsgSent') diff --git a/ib_insync/order.py b/ib_insync/order.py index 70861e57c..0c5a2ff96 100644 --- a/ib_insync/order.py +++ b/ib_insync/order.py @@ -3,14 +3,15 @@ from .objects import Object from ib_insync.event import Event -__all__ = ('Trade OrderStatus Order ' - 'LimitOrder MarketOrder StopOrder StopLimitOrder').split() +__all__ = ( + 'Trade OrderStatus Order ' + 'LimitOrder MarketOrder StopOrder StopLimitOrder').split() class Trade(Object): """ Trade keeps track of an order, its status and all its fills. - + Events: * ``statusEvent(trade)`` * ``modifyEvent(trade)`` @@ -20,9 +21,10 @@ class Trade(Object): * ``cancelEvent(trade)`` * ``cancelledEvent(trade)`` """ - events = ('statusEvent', 'modifyEvent', 'fillEvent', - 'commissionReportEvent', 'filledEvent', - 'cancelEvent', 'cancelledEvent') + events = ( + 'statusEvent', 'modifyEvent', 'fillEvent', + 'commissionReportEvent', 'filledEvent', + 'cancelEvent', 'cancelledEvent') defaults = dict( contract=None, @@ -83,7 +85,8 @@ class OrderStatus(Object): PendingCancel = 'PendingCancel' PreSubmitted = 'PreSubmitted' Submitted = 'Submitted' - ApiPending = 'ApiPending' # undocumented, can be returned from req(All)OpenOrders + # ApiPending is undocumented, can be returned from req(All)OpenOrders + ApiPending = 'ApiPending' ApiCancelled = 'ApiCancelled' Cancelled = 'Cancelled' Filled = 'Filled' @@ -96,14 +99,14 @@ class OrderStatus(Object): class Order(Object): """ Order for trading contracts. - + https://interactivebrokers.github.io/tws-api/available_orders.html """ defaults = ibapi.order.Order().__dict__ __slots__ = list(defaults.keys()) + [ - 'sharesAllocation', 'orderComboLegsCount', 'algoParamsCount', - 'smartComboRoutingParamsCount', 'conditionsSize', - 'conditionType'] # bugs in decoder.py + 'sharesAllocation', 'orderComboLegsCount', 'algoParamsCount', + 'smartComboRoutingParamsCount', 'conditionsSize', + 'conditionType'] # bugs in decoder.py def __init__(self, *args, **kwargs): Object.__init__(self, *args, **kwargs) @@ -131,31 +134,34 @@ class LimitOrder(Order): __slots__ = () def __init__(self, action, totalQuantity, lmtPrice, **kwargs): - Order.__init__(self, orderType='LMT', action=action, - totalQuantity=totalQuantity, lmtPrice=lmtPrice, **kwargs) + Order.__init__( + self, orderType='LMT', action=action, + totalQuantity=totalQuantity, lmtPrice=lmtPrice, **kwargs) class MarketOrder(Order): __slots__ = () def __init__(self, action, totalQuantity, **kwargs): - Order.__init__(self, orderType='MKT', action=action, - totalQuantity=totalQuantity, **kwargs) + Order.__init__( + self, orderType='MKT', action=action, + totalQuantity=totalQuantity, **kwargs) class StopOrder(Order): __slots__ = () def __init__(self, action, totalQuantity, stopPrice, **kwargs): - Order.__init__(self, orderType='STP', action=action, - totalQuantity=totalQuantity, auxPrice=stopPrice, **kwargs) + Order.__init__( + self, orderType='STP', action=action, + totalQuantity=totalQuantity, auxPrice=stopPrice, **kwargs) class StopLimitOrder(Order): __slots__ = () def __init__(self, action, totalQuantity, lmtPrice, stopPrice, **kwargs): - Order.__init__(self, orderType='STP LMT', action=action, - totalQuantity=totalQuantity, lmtPrice=lmtPrice, - auxPrice=stopPrice, **kwargs) - + Order.__init__( + self, orderType='STP LMT', action=action, + totalQuantity=totalQuantity, lmtPrice=lmtPrice, + auxPrice=stopPrice, **kwargs) diff --git a/ib_insync/ticker.py b/ib_insync/ticker.py index 68cd235d4..b298e282f 100644 --- a/ib_insync/ticker.py +++ b/ib_insync/ticker.py @@ -10,21 +10,21 @@ class Ticker(Object): """ Current market data such as bid, ask, last price, etc. for a contract. - + Streaming level-1 ticks of type ``TickData`` are stored in the ``ticks`` list. - + Streaming level-2 ticks of type ``MktDepthData`` are stored in the ``domTicks`` list. The order book (DOM) is available as lists of ``DOMLevel`` in ``domBids`` and ``domAsks``. - + Streaming tick-by-tick ticks are stored in ``tickByTicks``. - + For options the ``OptionComputation`` values for the bid, ask, resp. last price are stored in the ``bidGreeks``, ``askGreeks`` resp. ``lastGreeks`` attributes. There is also ``modelGreeks`` that conveys the greeks as calculated by Interactive Brokers' option model. - + Events: * ``updateEvent(ticker)`` """ @@ -100,14 +100,14 @@ def midpoint(self): def marketPrice(self): """ Return the first available one of - + * last price if within current bid/ask; * average of bid and ask (midpoint); * close price. """ midpoint = self.midpoint() - price = self.last if (isNan(midpoint) or - self.bid <= self.last <= self.ask) else nan + price = self.last if ( + isNan(midpoint) or self.bid <= self.last <= self.ask) else nan if isNan(price): price = midpoint if isNan(price) or price == -1: diff --git a/ib_insync/util.py b/ib_insync/util.py index 943df4365..e8bada8b2 100644 --- a/ib_insync/util.py +++ b/ib_insync/util.py @@ -5,8 +5,7 @@ import signal import asyncio import time -from typing import List, Iterator -from collections.abc import Awaitable +from typing import List, Iterator, Awaitable from ib_insync.objects import Object, DynamicObject @@ -69,8 +68,8 @@ def barplot(bars, title='', upColor='blue', downColor='red'): from matplotlib.patches import Rectangle if isinstance(bars, pd.DataFrame): - ohlcTups = [tuple(v) for v in - bars[['open', 'high', 'low', 'close']].values] + ohlcTups = [ + tuple(v) for v in bars[['open', 'high', 'low', 'close']].values] else: ohlcTups = [(b.open, b.high, b.low, b.close) for b in bars] @@ -86,25 +85,25 @@ def barplot(bars, title='', upColor='blue', downColor='red'): color = downColor bodyHi, bodyLo = open_, close line = Line2D( - xdata=(n, n), - ydata=(low, bodyLo), - color=color, - linewidth=1) + xdata=(n, n), + ydata=(low, bodyLo), + color=color, + linewidth=1) ax.add_line(line) line = Line2D( - xdata=(n, n), - ydata=(high, bodyHi), - color=color, - linewidth=1) + xdata=(n, n), + ydata=(high, bodyHi), + color=color, + linewidth=1) ax.add_line(line) rect = Rectangle( - xy=(n - 0.3, bodyLo), - width=0.6, - height=bodyHi - bodyLo, - edgecolor=color, - facecolor=color, - alpha=0.4, - antialiased=True + xy=(n - 0.3, bodyLo), + width=0.6, + height=bodyHi - bodyLo, + edgecolor=color, + facecolor=color, + alpha=0.4, + antialiased=True ) ax.add_patch(rect) @@ -128,7 +127,7 @@ def logToFile(path, level=logging.INFO, ibapiLevel=logging.ERROR): logger.addFilter(f) logger.setLevel(level) formatter = logging.Formatter( - '%(asctime)s %(name)s %(levelname)s %(message)s') + '%(asctime)s %(name)s %(levelname)s %(message)s') handler = logging.FileHandler(path) handler.setFormatter(formatter) logger.addHandler(handler) @@ -143,11 +142,12 @@ def logToConsole(level=logging.INFO, ibapiLevel=logging.ERROR): logger.addFilter(f) logger.setLevel(level) formatter = logging.Formatter( - '%(asctime)s %(name)s %(levelname)s %(message)s') + '%(asctime)s %(name)s %(levelname)s %(message)s') handler = logging.StreamHandler() handler.setFormatter(formatter) - logger.handlers = [h for h in logger.handlers - if type(h) is not logging.StreamHandler] + logger.handlers = [ + h for h in logger.handlers + if type(h) is not logging.StreamHandler] logger.addHandler(handler) @@ -319,10 +319,10 @@ def patchAsyncio(): Patch asyncio to use pure Python implementation of Future and Task, to deal with nested event loops in syncAwait. """ - Task = asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = \ - asyncio.tasks._PyTask + asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = \ + asyncio.tasks._PyTask asyncio.Future = asyncio.futures._CFuture = asyncio.futures.Future = \ - asyncio.futures._PyFuture + asyncio.futures._PyFuture def syncAwait(future): @@ -350,7 +350,7 @@ def syncAwait(future): def _syncAwaitAsyncio(future): # https://bugs.python.org/issue22239 assert asyncio.Task is asyncio.tasks._PyTask, \ - 'To allow nested event loops, use util.patchAsyncio()' + 'To allow nested event loops, use util.patchAsyncio()' loop = asyncio.get_event_loop() preserved_ready = list(loop._ready) loop._ready.clear() @@ -426,8 +426,7 @@ def useQt(): import quamash if isinstance(asyncio.get_event_loop(), quamash.QEventLoop): return - if not qt.QApplication.instance(): - _ = qt.QApplication(sys.argv) + assert qt.QApplication.instance() loop = quamash.QEventLoop() asyncio.set_event_loop(loop) @@ -462,7 +461,7 @@ def parseIBDatetime(s): dt = datetime.date(y, m, d) elif s.isdigit(): dt = datetime.datetime.fromtimestamp( - int(s), datetime.timezone.utc) + int(s), datetime.timezone.utc) else: dt = datetime.datetime.strptime(s, '%Y%m%d %H:%M:%S') return dt diff --git a/ib_insync/wrapper.py b/ib_insync/wrapper.py index ff69de0bf..7c9d6bf22 100644 --- a/ib_insync/wrapper.py +++ b/ib_insync/wrapper.py @@ -9,7 +9,16 @@ from ib_insync.contract import Contract from ib_insync.ticker import Ticker from ib_insync.order import Order, OrderStatus, Trade -from ib_insync.objects import * +from ib_insync.objects import ( + AccountValue, PortfolioItem, Position, OrderState, TradeLogEntry, + ContractDetails, ContractDescription, PriceIncrement, OptionChain, + Execution, Fill, CommissionReport, RealTimeBar, BarData, Dividends, + NewsTick, NewsArticle, NewsBulletin, NewsProvider, HistoricalNews, + TickData, HistoricalTick, HistoricalTickBidAsk, HistoricalTickLast, + TickAttrib, TickByTickAllLast, TickByTickBidAsk, TickByTickMidPoint, + MktDepthData, DOMLevel, DepthMktDataDescription, + OptionComputation, ScanData, HistogramData, + TagValue, ComboLeg, SoftDollarTier) import ib_insync.util as util __all__ = ['Wrapper'] @@ -30,7 +39,7 @@ def __init__(self, ib): self.reset() def reset(self): - self.accountValues = {} # (account, tag, currency, modelCode) -> AccountValue + self.accountValues = {} # (acc, tag, curr, modelCode) -> AccountValue self.acctSummary = {} # (account, tag, currency) -> AccountValue self.portfolio = defaultdict(dict) # account -> conId -> PortfolioItem self.positions = defaultdict(dict) # account -> conId -> Position @@ -70,8 +79,9 @@ def _getContract(self, ibContract): """ contract = Contract.create(**ibContract.__dict__) if ibContract.comboLegs: - contract.comboLegs = [ComboLeg(**leg.__dict__) - for leg in ibContract.comboLegs] + contract.comboLegs = [ + ComboLeg(**leg.__dict__) + for leg in ibContract.comboLegs] return contract def startReq(self, key, contract=None, container=None): @@ -109,8 +119,9 @@ def startTicker(self, reqId, contract, tickType): """ ticker = self.tickers.get(id(contract)) if not ticker: - ticker = Ticker(contract=contract, ticks=[], tickByTicks=[], - domBids=[], domAsks=[], domTicks=[]) + ticker = Ticker( + contract=contract, ticks=[], tickByTicks=[], + domBids=[], domAsks=[], domTicks=[]) self.tickers[id(contract)] = ticker self.reqId2Ticker[reqId] = ticker self._reqId2Contract[reqId] = contract @@ -153,7 +164,7 @@ def handleEvent(self, eventName, *args): event.emit(*args) if cb: cb(*args) - except: + except Exception: self._logger.exception('Event %s(%s)', eventName, args) def setTimeout(self, timeout): @@ -211,8 +222,8 @@ def accountDownloadEnd(self, _account): self._endReq('accountValues') @iswrapper - def accountUpdateMulti(self, reqId, account, modelCode, tag, - val, currency): + def accountUpdateMulti( + self, reqId, account, modelCode, tag, val, currency): key = (account, tag, currency, modelCode) acctVal = AccountValue(account, tag, val, currency, modelCode) self.accountValues[key] = acctVal @@ -234,12 +245,13 @@ def accountSummaryEnd(self, reqId): self._endReq(reqId) @iswrapper - def updatePortfolio(self, contract, posSize, marketPrice, marketValue, + def updatePortfolio( + self, contract, posSize, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, account): contract = self._getContract(contract) portfItem = PortfolioItem( - contract, posSize, marketPrice, marketValue, - averageCost, unrealizedPNL, realizedPNL, account) + contract, posSize, marketPrice, marketValue, + averageCost, unrealizedPNL, realizedPNL, account) portfolioItems = self.portfolio[account] if posSize == 0: portfolioItems.pop(contract.conId, None) @@ -278,8 +290,8 @@ def pnl(self, reqId, dailyPnL, unrealizedPnL, realizedPnL): self.handleEvent('pnlEvent', pnl) @iswrapper - def pnlSingle(self, reqId, pos, dailyPnL, - unrealizedPnL, realizedPnL, value): + def pnlSingle( + self, reqId, pos, dailyPnL, unrealizedPnL, realizedPnL, value): pnlSingle = self.pnlSingles.get(reqId) if not pnlSingle: return @@ -307,7 +319,7 @@ def openOrder(self, orderId, contract, order, orderState): else: if order.softDollarTier: order.softDollarTier = SoftDollarTier( - **order.softDollarTier.__dict__) + **order.softDollarTier.__dict__) key = self.orderKey(order.clientId, order.orderId, order.permId) trade = self.trades.get(key) if trade: @@ -331,19 +343,21 @@ def openOrderEnd(self): self._endReq('openOrders') @iswrapper - def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, + def orderStatus( + self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice=0.0, lastLiquidity=0): key = self.orderKey(clientId, orderId, permId) trade = self.trades.get(key) if trade: oldStatus = trade.orderStatus.status - new = dict(status=status, filled=filled, - remaining=remaining, avgFillPrice=avgFillPrice, - permId=permId, parentId=parentId, - lastFillPrice=lastFillPrice, clientId=clientId, - whyHeld=whyHeld, mktCapPrice=mktCapPrice, - lastLiquidity=lastLiquidity) + new = dict( + status=status, filled=filled, + remaining=remaining, avgFillPrice=avgFillPrice, + permId=permId, parentId=parentId, + lastFillPrice=lastFillPrice, clientId=clientId, + whyHeld=whyHeld, mktCapPrice=mktCapPrice, + lastLiquidity=lastLiquidity) curr = trade.orderStatus.dict() isChanged = curr != {**curr, **new} if isChanged: @@ -368,8 +382,9 @@ def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, elif status == OrderStatus.Cancelled: trade.cancelledEvent.emit(trade) else: - self._logger.error('orderStatus: No order found for ' - 'orderId %s and clientId %s', orderId, clientId) + self._logger.error( + 'orderStatus: No order found for ' + 'orderId %s and clientId %s', orderId, clientId) @iswrapper def execDetails(self, reqId, contract, execution): @@ -380,7 +395,7 @@ def execDetails(self, reqId, contract, execution): # bug in TWS: executions of manual orders have orderId=2**31 - 1 execution.orderId = 0 key = self.orderKey( - execution.clientId, execution.orderId, execution.permId) + execution.clientId, execution.orderId, execution.permId) trade = self.trades.get(key) if trade and contract.conId == trade.contract.conId: contract = trade.contract @@ -389,7 +404,7 @@ def execDetails(self, reqId, contract, execution): execId = execution.execId execution = Execution(**execution.__dict__) execution.time = util.parseIBDatetime(execution.time). \ - astimezone(datetime.timezone.utc) + astimezone(datetime.timezone.utc) isLive = reqId not in self._futures time = self.lastTime if isLive else execution.time fill = Fill(contract, execution, CommissionReport(), time) @@ -398,9 +413,10 @@ def execDetails(self, reqId, contract, execution): self.fills[execId] = fill if trade: trade.fills.append(fill) - logEntry = TradeLogEntry(self.lastTime, - trade.orderStatus.status, - f'Fill {execution.shares}@{execution.price}') + logEntry = TradeLogEntry( + self.lastTime, + trade.orderStatus.status, + f'Fill {execution.shares}@{execution.price}') trade.log.append(logEntry) if isLive: self.handleEvent('execDetailsEvent', trade, fill) @@ -422,14 +438,15 @@ def commissionReport(self, commissionReport): fill = self.fills.get(commissionReport.execId) if fill: report = fill.commissionReport.update( - **commissionReport.__dict__) + **commissionReport.__dict__) self._logger.info(f'commissionReport: {report}') - key = self.orderKey(fill.execution.clientId, - fill.execution.orderId, fill.execution.permId) + key = self.orderKey( + fill.execution.clientId, + fill.execution.orderId, fill.execution.permId) trade = self.trades.get(key) if trade: - self.handleEvent('commissionReportEvent', - trade, fill, report) + self.handleEvent( + 'commissionReportEvent', trade, fill, report) trade.commissionReportEvent.emit(trade, fill, report) else: # this is not a live execution and the order was filled @@ -437,8 +454,8 @@ def commissionReport(self, commissionReport): pass else: report = CommissionReport(**commissionReport.__dict__) - self._logger.error('commissionReport: ' - 'No execution found for %s', report) + self._logger.error( + 'commissionReport: ' 'No execution found for %s', report) @iswrapper def contractDetails(self, reqId, contractDetails): @@ -457,20 +474,21 @@ def contractDetailsEnd(self, reqId): @iswrapper def symbolSamples(self, reqId, contractDescriptions): cds = [ContractDescription( - **cd.__dict__) for cd in contractDescriptions] + **cd.__dict__) for cd in contractDescriptions] for cd in cds: cd.contract = self._getContract(cd.contract) self._endReq(reqId, cds) @iswrapper def marketRule(self, marketRuleId, priceIncrements): - result = [PriceIncrement(pi.lowEdge, pi.increment) - for pi in priceIncrements] + result = [ + PriceIncrement(pi.lowEdge, pi.increment) + for pi in priceIncrements] self._endReq(f'marketRule-{marketRuleId}', result) @iswrapper - def realtimeBar(self, reqId, time, open_, high, low, close, volume, - wap, count): + def realtimeBar( + self, reqId, time, open_, high, low, close, volume, wap, count): dt = datetime.datetime.fromtimestamp(time, datetime.timezone.utc) bar = RealTimeBar(dt, -1, open_, high, low, close, volume, wap, count) bars = self.reqId2Bars[reqId] @@ -479,7 +497,7 @@ def realtimeBar(self, reqId, time, open_, high, low, close, volume, bars.updateEvent(bars, True) @iswrapper - def historicalData(self, reqId , bar): + def historicalData(self, reqId, bar): bar = BarData(**bar.__dict__) bar.date = util.parseIBDatetime(bar.date) self._results[reqId].append(bar) @@ -515,27 +533,31 @@ def headTimestamp(self, reqId, headTimestamp): @iswrapper def historicalTicks(self, reqId, ticks, done): - self._results[reqId] += [HistoricalTick( + self._results[reqId] += [ + HistoricalTick( datetime.datetime.fromtimestamp(t.time, datetime.timezone.utc), - t.price, t.size) for t in ticks if t.size] + t.price, t.size) + for t in ticks if t.size] if done: self._endReq(reqId) @iswrapper def historicalTicksBidAsk(self, reqId, ticks, done): - self._results[reqId] += [HistoricalTickBidAsk( + self._results[reqId] += [ + HistoricalTickBidAsk( datetime.datetime.fromtimestamp(t.time, datetime.timezone.utc), t.mask, t.priceBid, t.priceAsk, t.sizeBid, t.sizeAsk) - for t in ticks] + for t in ticks] if done: self._endReq(reqId) @iswrapper def historicalTicksLast(self, reqId, ticks, done): - self._results[reqId] += [HistoricalTickLast( + self._results[reqId] += [ + HistoricalTickLast( datetime.datetime.fromtimestamp(t.time, datetime.timezone.utc), t.mask, t.price, t.size, t.exchange, t.specialConditions) - for t in ticks if t.size] + for t in ticks if t.size] if done: self._endReq(reqId) @@ -650,28 +672,30 @@ def tickSnapshotEnd(self, reqId): self._endReq(reqId) @iswrapper - def tickByTickAllLast(self, reqId, tickType, time, price, size, - attribs, exchange, specialConditions): + def tickByTickAllLast( + self, reqId, tickType, time, price, size, attribs, + exchange, specialConditions): ticker = self.reqId2Ticker.get(reqId) if not ticker: self._logger.error(f'tickByTickAllLast: Unknown reqId: {reqId}') return attribs = TickAttrib(**attribs.__dict__) - tick = TickByTickAllLast(tickType, self.lastTime, price, size, - attribs, exchange, specialConditions) + tick = TickByTickAllLast( + tickType, self.lastTime, price, size, attribs, + exchange, specialConditions) ticker.tickByTicks.append(tick) self.pendingTickers.add(ticker) @iswrapper - def tickByTickBidAsk(self, reqId, time, bidPrice, askPrice, - bidSize, askSize, attribs): + def tickByTickBidAsk( + self, reqId, time, bidPrice, askPrice, bidSize, askSize, attribs): ticker = self.reqId2Ticker.get(reqId) if not ticker: self._logger.error(f'tickByTickBidAsk: Unknown reqId: {reqId}') return attribs = TickAttrib(**attribs.__dict__) - tick = TickByTickBidAsk(self.lastTime, bidPrice, askPrice, - bidSize, askSize, attribs) + tick = TickByTickBidAsk( + self.lastTime, bidPrice, askPrice, bidSize, askSize, attribs) ticker.tickByTicks.append(tick) self.pendingTickers.add(ticker) @@ -692,10 +716,10 @@ def tickString(self, reqId, tickType, value): return try: if tickType == 48: - # RTVolume string format: - # price;size;time in ms since epoch;total volume;VWAP;single trade - # example: - # 701.28;1;1348075471534;67854;701.46918464;true + # RTVolume string format: + # price;size;ms since epoch;total volume;VWAP;single trade + # example: + # 701.28;1;1348075471534;67854;701.46918464;true price, size, _, rtVolume, vwap, _ = value.split(';') if rtVolume: ticker.rtVolume = int(rtVolume) @@ -723,11 +747,13 @@ def tickString(self, reqId, tickType, value): else: # example value: '0.83,0.92,20130219,0.23' past12, next12, date, amount = value.split(',') - ticker.dividends = Dividends(float(past12), float(next12), - util.parseIBDatetime(date), float(amount)) + ticker.dividends = Dividends( + float(past12), float(next12), + util.parseIBDatetime(date), float(amount)) except ValueError: - self._logger.error(f'tickString with tickType {tickType}: ' - f'malformed value: {value!r}') + self._logger.error( + f'tickString with tickType {tickType}: ' + f'malformed value: {value!r}') @iswrapper def tickGeneric(self, reqId, tickType, value): @@ -748,43 +774,47 @@ def tickReqParams(self, reqId, minTick, bboExchange, snapshotPermissions): @iswrapper def mktDepthExchanges(self, depthMktDataDescriptions): - result = [DepthMktDataDescription(**d.__dict__) - for d in depthMktDataDescriptions] + result = [ + DepthMktDataDescription(**d.__dict__) + for d in depthMktDataDescriptions] self._endReq('mktDepthExchanges', result) @iswrapper def updateMktDepth(self, reqId, position, operation, side, price, size): - self.updateMktDepthL2(reqId, position, '', operation, side, price, size) + self.updateMktDepthL2( + reqId, position, '', operation, side, price, size) @iswrapper - def updateMktDepthL2(self, reqId, position, marketMaker, operation, - side, price, size): + def updateMktDepthL2( + self, reqId, position, marketMaker, operation, side, price, size): # operation: 0 = insert, 1 = update, 2 = delete # side: 0 = ask, 1 = bid ticker = self.reqId2Ticker[reqId] ticker.time = self.lastTime - l = ticker.domBids if side else ticker.domAsks + dom = ticker.domBids if side else ticker.domAsks if operation == 0: - l.insert(position, DOMLevel(price, size, marketMaker)) + dom.insert(position, DOMLevel(price, size, marketMaker)) elif operation == 1: - l[position] = DOMLevel(price, size, marketMaker) + dom[position] = DOMLevel(price, size, marketMaker) elif operation == 2: - if position < len(l): - level = l.pop(position) + if position < len(dom): + level = dom.pop(position) price = level.price size = 0 - tick = MktDepthData(self.lastTime, position, marketMaker, - operation, side, price, size) + tick = MktDepthData( + self.lastTime, position, marketMaker, operation, side, price, size) ticker.domTicks.append(tick) self.pendingTickers.add(ticker) @iswrapper - def tickOptionComputation(self, reqId, tickType, impliedVol, - delta, optPrice, pvDividend, gamma, vega, theta, undPrice): - comp = OptionComputation(impliedVol, - delta, optPrice, pvDividend, gamma, vega, theta, undPrice) + def tickOptionComputation( + self, reqId, tickType, impliedVol, delta, optPrice, pvDividend, + gamma, vega, theta, undPrice): + comp = OptionComputation( + impliedVol, delta, optPrice, pvDividend, + gamma, vega, theta, undPrice) ticker = self.reqId2Ticker.get(reqId) if ticker: # reply from reqMktData @@ -801,7 +831,8 @@ def tickOptionComputation(self, reqId, tickType, impliedVol, # reply from calculateImpliedVolatility or calculateOptionPrice self._endReq(reqId, comp) else: - self._logger.error(f'tickOptionComputation: Unknown reqId: {reqId}') + self._logger.error( + f'tickOptionComputation: Unknown reqId: {reqId}') @iswrapper def fundamentalData(self, reqId, data): @@ -812,8 +843,9 @@ def scannerParameters(self, xml): self._endReq('scannerParams', xml) @iswrapper - def scannerData(self, reqId, rank, contractDetails, distance, - benchmark, projection, legsStr): + def scannerData( + self, reqId, rank, contractDetails, distance, benchmark, + projection, legsStr): cd = ContractDetails(**contractDetails.__dict__) if cd.contract: cd.contract = self._getContract(cd.contract) @@ -830,10 +862,12 @@ def histogramData(self, reqId, items): self._endReq(reqId, result) @iswrapper - def securityDefinitionOptionParameter(self, reqId, exchange, - underlyingConId, tradingClass, multiplier, expirations, strikes): - chain = OptionChain(exchange, underlyingConId, - tradingClass, multiplier, expirations, strikes) + def securityDefinitionOptionParameter( + self, reqId, exchange, underlyingConId, tradingClass, + multiplier, expirations, strikes): + chain = OptionChain( + exchange, underlyingConId, tradingClass, multiplier, + expirations, strikes) self._results[reqId].append(chain) @iswrapper @@ -842,14 +876,17 @@ def securityDefinitionOptionParameterEnd(self, reqId): @iswrapper def newsProviders(self, newsProviders): - newsProviders = [NewsProvider(code=p.code, name=p.name) - for p in newsProviders] + newsProviders = [ + NewsProvider(code=p.code, name=p.name) + for p in newsProviders] self._endReq('newsProviders', newsProviders) @iswrapper - def tickNews(self, _reqId, timeStamp, providerCode, articleId, + def tickNews( + self, _reqId, timeStamp, providerCode, articleId, headline, extraData): - news = NewsTick(timeStamp, providerCode, articleId, headline, extraData) + news = NewsTick( + timeStamp, providerCode, articleId, headline, extraData) self.newsTicks.append(news) self.handleEvent('tickNewsEvent', news) @@ -886,8 +923,9 @@ def error(self, reqId, errorCode, errorString): # https://interactivebrokers.github.io/tws-api/message_codes.html warningCodes = {165, 202, 399, 434, 10167} isWarning = errorCode in warningCodes or 2100 <= errorCode < 2200 - msg = (f'{"Warning" if isWarning else "Error"} ' - f'{errorCode}, reqId {reqId}: {errorString}') + msg = ( + f'{"Warning" if isWarning else "Error"} ' + f'{errorCode}, reqId {reqId}: {errorString}') contract = self._reqId2Contract.get(reqId) if contract: msg += f', contract: {contract}' @@ -916,8 +954,9 @@ def error(self, reqId, errorCode, errorString): for side, l in ((0, ticker.domAsks), (1, ticker.domBids)): for position in reversed(l): level = l.pop(position) - tick = MktDepthData(self.lastTime, position, - '', 2, side, level.price, 0) + tick = MktDepthData( + self.lastTime, position, '', 2, + side, level.price, 0) ticker.domTicks.append(tick) self.handleEvent('errorEvent', reqId, errorCode, errorString, contract) @@ -956,8 +995,8 @@ def waitOnUpdate(self, timeout=0): def _emitPendingTickers(self): if self.pendingTickers: self.handleEvent('pendingTickersEvent', list(self.pendingTickers)) - for ticker in (t for t in self.pendingTickers - if t.updateEvent.slots): + for ticker in ( + t for t in self.pendingTickers if t.updateEvent.slots): ticker.updateEvent.emit(ticker) def _clearPendingTickers(self):