ib-api-reloaded / ib_async

Python sync/async framework for Interactive Brokers API (replaces ib_insync)
BSD 2-Clause "Simplified" License
223 stars 37 forks source link

How to use async functions? #31

Closed smturro2 closed 1 week ago

smturro2 commented 1 week ago

I'm looking to run IB async requests along with other async libraries. Below is simple example of how I would expect IB to work with asyncio

import asyncio
from ib_async import IB, Stock

async def main():
    symbols = ["AMD"]

    async with IB().connect('1.2.3.4.5.6', 1234, clientId=1) as ib:
        contract_tasks = [ib.reqContractDetailsAsync(Stock(symbl, currency="USD", exchange="SMART"))
                          for symbl in symbols]
        contract_results = await asyncio.gather(*contract_tasks)

    print(contract_results)

if __name__ == "__main__":
    asyncio.run(main())

This example errors out on IB().connect('1.2.3.4.5.6', 1234, clientId=1) as ib and raises RuntimeError: This event loop is already running

smturro2 commented 1 week ago

This change worked for me. Please let me know what the proper implementation is.

This library seems to be creating a new event loop everytime run is called. Doing things this way would make library not properly compatible with asyncio. See https://stackoverflow.com/questions/69035759/using-asyncio-run-is-it-safe-to-run-multiple-times

import asyncio
from ib_async import IB, Stock

async def get_contracts(ib, symbols):
    contract_tasks = [ib.reqContractDetailsAsync(Stock(symbl, currency="USD", exchange="SMART"))
                      for symbl in symbols]
    contract_results = await asyncio.gather(*contract_tasks, return_exceptions=True)
    return contract_results

def main():
    symbols = ["AMD"]

    with IB().connect('1.2.3.4.5.6', 1234, clientId=1) as ib:
        contract_results = ib.run(get_contracts(ib, symbols))
    print(contract_results)

if __name__ == "__main__":
    main()
mattsta commented 1 week ago

You may be missing the connectAsync() call instead of the regular one.

The library supports both an async native workflow or an "async transparent" workflow where it tries to manage the async workflows itself by looking like normal code, but if you mix the two approaches it can break in weird ways.

Just check you are always using methods ending with Async() as much as possible (when they exist).

smturro2 commented 1 week ago

Thanks for the very quick response! yep this is what I was missing. Heres the final code that worked

import asyncio
from ib_async import IB, Stock

async def main():
    symbols = ["AMD"]

    with IB() as ib:
        await ib.connectAsync('1.2.3.4.5.6', 1234, clientId=1)
        contract_tasks = [ib.reqContractDetailsAsync(Stock(symbl, currency="USD", exchange="SMART"))
                          for symbl in symbols]
        contract_results = await asyncio.gather(*contract_tasks)

    print(contract_results)

if __name__ == "__main__":
    asyncio.run(main())