4

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
Nazim Kerimbekov
  • 4,712
  • 8
  • 34
  • 58
MLguy
  • 1,776
  • 3
  • 15
  • 28
  • It's not clear how you this code could work with asyncio. If you have `app.run()`, it means that the asyncio event loop is simply not running. Ideally one would hook the asyncio event loop into the `ibapi` event loop or the other way around, but that is fairly involved. A more realistic option is to run `TwsApp` in a separate thread, and use `call_later_threadsafe` and similar primitives to schedule the corresponding coroutines on the asyncio level. – user4815162342 Feb 17 '18 at 08:55

0 Answers0