CabbageDevelopment / qasync

Python library for using asyncio in Qt-based applications.
BSD 2-Clause "Simplified" License
307 stars 43 forks source link

Need help using ib_insync with qasync #34

Open romanrdgz opened 3 years ago

romanrdgz commented 3 years ago

First of all, thanks for your work and your efforts to get asyncio to work with Pyqt5. I am missing more examples and a tutorial here, and that's probably my main issue. I am currently having trouble combining ib_insync with pyqt5. The former is an async library to connect to the API of Interactive brokers, which can be used without asyncio knowledge by nesting loops and applying a monkey-patch. Nevertheless, when combining with PyQt and my application gets more complex, things start to fall apart, so I'm trying to go to manual async control of ib_insync.

Hence, I wrote a small example script which runs a Pyqt5 application with 2 buttons. One will connect to the brokerage using ib_insync's connectAsync function. My first problem is located right here, as despite having wrote the example based on yours, Python is telling me the following error: ...\Python38-32\lib\site-packages\qasync\__init__.py:275: RuntimeWarning: coroutine 'MainWindow.connect_to_tws' was never awaited

My second problem is that ib_insync also contains blocking calls, such as qualifyContracts, which is to be tested with the second button once connected.

I am aware that this is a very specific appplication that would require you to have ib_insync and a brokerage account with IB, but it would really help me if you could have a look at my code in order to detect what am I doing wrong:

import asyncio
from ib_insync import IB, util, Stock
import functools
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import qasync
from qasync import asyncSlot, asyncClose, QApplication, QThreadExecutor
import sys
import traceback

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()
        uic.loadUi('mainwindow.ui', self)
        self.loop = asyncio.get_event_loop()

        # Connect widgets signals
        self.connect_button.pressed.connect(self.connect_button_action)
        self.refresh_portfolio_button.pressed.connect(self.portfolio_button_action)

    async def connect_to_tws(self):
        self.ib_client = IB()
        self.ib_client.client.setConnectOptions('+PACEAPI')
        self.ib_client.client.MaxRequests = 0 # Disables throttling
        self.ib_client.connectAsync('127.0.0.1', 7496, clientId=21)
        self.connect_button.setEnabled(False)
        self.refresh_portfolio_button.setEnabled(True)

    @asyncClose
    async def closeEvent(self, event):
        await self.session.close()

    @asyncSlot()
    async def connect_button_action(self):
        # Connect to IB
        with QThreadExecutor(1) as exec:
            await self.loop.run_in_executor(exec, self.connect_to_tws)

    @asyncSlot()
    async def portfolio_button_action(self):
        print('button_action_event')
        try:
            self.portfolio_table.setEnabled(False)
            self.portfolio_progress_bar.setVisible(True)

            tasks = list()
            tasks.append(self.loop.create_task(self.get_portfolio_data()))
            self.loop.run_until_complete(asyncio.wait(tasks))
        except Exception as e:
            traceback.print_tb(e.__traceback__)
            print(e)

    async def get_portfolio_data(self):
        futures = [self.loop.run_in_executor(None, self.blocking_call)]
        results = await asyncio.gather(*futures)
        return results

    def blocking_call(self):
        contract = Stock('ADBE', 'SMART', 'USD')
        print('Thread: before qualifyContracts call')
        self.ib_client.qualifyContracts(contract)
        print('Thread: after qualifyContracts call')
        return contract

    def on_portfolio_update_completed(self):
        self.portfolio_table.setEnabled(True) # TODO: data to be loaded to the table. Doesn't matter for the test purpose
        self.portfolio_progress_bar.setVisible(False)
        print('Finished thread task')

async def main():
    def close_future(future, loop):
        loop.call_later(10, future.cancel)
        future.cancel("Close Application")

    loop = asyncio.get_event_loop()
    future = asyncio.Future()

    window = None
    try:
        app = QApplication.instance()
        app.setStyle('fusion')
        if hasattr(app, 'aboutToQuit'):
            getattr(app, 'aboutToQuit').connect(functools.partial(close_future, future, loop))
        window = MainWindow()
        window.show()
    except Exception as e:
        print(traceback.format_exc())
        print(e)
        if window is not None and window.ib is not None and window.ib.isConnected():
            window.ib.disconnect()
            print('Disconnected from IB')
        sys.exit(0)

    await future
    return True

if __name__ == '__main__':
    try:
        qasync.run(main())
    except asyncio.exceptions.CancelledError:
        print(e)
        sys.exit(0)

Thanks in advance

fredy0219 commented 1 year ago

I have a similar problem here. Anybody can help?

hosaka commented 1 year ago

I'm gonna mark this as non-issue, until someone comes along who can help with the code.