The excellent ib_insync provides a higher level synchronous interface to the asynchronous IB API using the built-in asyncio module. However, there are situations where one might prefer to avoid relying on 3rd party modules. My attempt is to provide to the community a self-contained reproducible example of how to use the native ibapi
with the Python built-in asyncio
module to handle callbacks. For example, let say that one wants to add a method to convert a vector of weights to an actual number of shares to be traded. This would require invoking reqAccountSummary
(to check the amount of cash in the account) and reqMktData
(to get a snapshot of market prices) within the same method and to wait for the callbacks, accountSummaryEnd
and tickSnapshotEnd
respectively.
It now follows a (simpler) example where I invoke reqContractDetails
and wait for the callback contractDetailsEnd
within the same method runSyncContractReq
. The same logic can, in theory, be applied to all requests with an associated callback. Unfortunately, this is not quite working yet since the program "awaits" the future callback forever.
I hope that a member of the community could help to make the example to work correctly in order to help novices with ibapi
and asyncio
like me.
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
import asyncio
class TwsApp(EWrapper, EClient):
def __init__(self):
self._nextValidId = None
EWrapper.__init__(self)
EClient.__init__(self, wrapper=self)
# self.future stores IDs associated with asyncio.Future() objects.
self.future = dict()
def get_nextValidId(self):
"""Returns a valid ID useful to send requests to TWS or place orders."""
reqId = self._nextValidId
self._nextValidId += 1
return reqId
def nextValidId(self, orderId: int):
"""This method receives valid OrderIds form TWS whenever calling
self.run() or self.reqIds()."""
self._nextValidId = orderId
self.runSyncContractReq()
async def reqContractDetails(self, reqId, contractDetails):
print('reqContractDetails STARTED')
super().reqContractDetails(reqId, contractDetails)
print('reqContractDetails AWAITING for Future to be done.')
await self.future[reqId]
print('reqContractDetails AWAITED!')
def contractDetails(self, reqId: int, contractDetails):
print('contractDetails RECEIVED!')
def contractDetailsEnd(self, reqId):
print('contractDetailsEnd STARTED')
self.future[reqId] = self.future[reqId].set_result('done')
print('contractDetailsEnd FINISHED')
def runSyncContractReq(self):
# Prepare data.
contract = Contract()
contract.conId = 756733
contract.exchange = 'SMART'
idReq = self.get_nextValidId()
# asyncio.
self.future[idReq] = asyncio.Future()
loop = asyncio.get_event_loop()
print('Running asyncio.ensure_future')
asyncio.ensure_future(self.reqContractDetails(idReq, contract))
print('Running loop.run_until_complete')
loop.run_until_complete(self.future[idReq])
loop.close()
if __name__ == '__main__':
app = TwsApp()
app.connect(host='127.0.0.1', port=7497, clientId=0)
app.run()
### OUTPUT
Running asyncio.ensure_future
Running loop.run_until_complete
reqContractDetails STARTED
reqContractDetails AWAITING for Future to be done.
# However, contract details are somehow returned once the application is stopped.
ERROR:root:ERROR -1 2104 Market data farm connection is OK:usfuture
ERROR:root:ERROR -1 2104 Market data farm connection is OK:afarm
ERROR:root:ERROR -1 2104 Market data farm connection is OK:usfarm
ERROR:root:ERROR -1 2106 HMDS data farm connection is OK:ushmds
contractDetails RECEIVED!
contractDetailsEnd STARTED
contractDetailsEnd FINISHED