I've been working on a PyQt6-based GUI application that aims to connect to Interactive Brokers' TWS Desktop application using the ib_insync library to connect with IB's API. The core requirements are:
The GUI application must establish a connection to TWS through a network socket at port 7497 and host 127.0.0.1.
The window title of the application should dynamically reflect the connection status: Display "TWS-Trigger-Connected" when there's an active connection to TWS. Switch to "TWS-Trigger-Disconnected" when the connection drops.
Furthermore, it's essential that the connection remains active and persistent as long as the GUI is running, and that the connection is gracefully disconnected when the GUI window is closed.
Once connection is maintained, start building the additional logic into the application so that the user can eventually just press a push button to transmit orders to IBKR.
Note
- I had managed to achieve point 1. and point 2. but when it comes to point 3. the network connection to port 7497 seems to timeout due to inactivity (takes about 55 seconds). Since this is a GUI application requiring user input, it is expected that there will be long periods of inactivity from the user and so the network connection would undoubtedly have to be maintained for the duration that the GUI is running.
What I've tried so far To ensure the connection remains alive, I tried to integrate the Watchdog class from ib_insync into the application. According to the API documentation for ib_insync (https://ib-insync.readthedocs.io/api.html) Watchdog is designed to monitor the health of the connection, and if it detects any disruptions, it attempts to restore the connection.
Here's what I tried:
- I initialised Watchdog after confirming the initial connection with TWS.
- I set up a separate thread to periodically check the connection status and subsequently update the GUI based on the connection health.
- My expectation was straightforward: If TWS remains connected, the window title should consistently show "TWS-Trigger-Connected." If, for some reason, the connection drops, I anticipated Watchdog to jump into action, try to restore the connection, and the window title should reflect "TWS-Trigger-Disconnected" during this downtime.
However, I've been encountering issues:
-The Watchdog functionality didn't seem to be kicking in as expected. Even when I manually disrupt the connection (Unchecking "Enable ActiveX and Socket Connections" in the TWS API settings), the console does not show any of the print statements I embedded in the Watchdog Class to troubleshoot what could be going wrong.
- TBH I am not sure if Watchdog even connects to IB in the first place? Or if I am using the right tool for the job?
Despite these efforts, the main problem remains: the persistent connection isn't as robust as I'd like, and I'm not sure if I'm utilising Watchdog correctly or if there's an inherent issue with my approach. If someone could please advise - I'm always willing to learn new things!
#I've included the code without Watchdog to demonstrate that Point 1. and 2. are satisfied at a basic level but Point 3. unfortunately hasn't been successful (tried ChatGPT as well, and it has knowledge gaps when it comes to ib_insync and how the different components fit together - it also can't simulate the API connection because it is not capable of running an instance of TWS)
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
import socket
from ib_insync import IB
from PyQt6.QtCore import QThread
#--------------------Connecting to IBKR------------------
print('\nConnecting to interactive brokers...\n')
class ConnectionCheckThread(QThread):
connectionStatusSignal = QtCore.pyqtSignal(bool) # Define the signal
def __init__(self, ui):
super().__init__()
self.ui = ui
def run(self):
while True:
connected = self.ui.is_currently_connected()
self.connectionStatusSignal.emit(connected) # Emit the signal
self.sleep(1) # Sleep for 1 second
class Ui_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ib = IB() # Create IB object
self.setupUi(self)
self.connection_check_thread = ConnectionCheckThread(self)
self.connection_check_thread.connectionStatusSignal.connect(self.update_status)
self.connection_check_thread.start()
self.is_currently_connected()
def setupUi(self, MainWindow):
MainWindow.setObjectName("TWS - TRIGGER")
MainWindow.resize(904, 849)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.frame = QtWidgets.QFrame(self.centralwidget, frameShape=QtWidgets.QFrame.Shape.StyledPanel, frameShadow=QtWidgets.QFrame.Shadow.Raised)
self.gridLayout_3 = QtWidgets.QGridLayout(self.frame)
self.input_pane = QtWidgets.QFrame(self.frame, autoFillBackground=True)
self.gridLayout_2 = QtWidgets.QGridLayout(self.input_pane)
label_names = ['AccBalanceLabel', 'comboBox', 'comboBox_2', 'GSL', 'tp', 'tp_2', 'tp_3']
for i, name in enumerate(label_names):
label = QtWidgets.QLabel(parent=self.input_pane, objectName=name)
setattr(self, name, label)
self.gridLayout_2.addWidget(label, i, 0, 1, 1)
for _ in range(13 - len(label_names)):
self.gridLayout_2.addItem(QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding))
pushButton_styles = [
("pushButton", "background-color: rgb(85, 170, 127);\ncolor: rgb(255, 255, 255);"),
("pushButton_2", "background-color: rgb(170, 0, 0);\ncolor: rgb(255, 255, 255);")
]
for i, (name, style) in enumerate(pushButton_styles):
btn = QtWidgets.QPushButton(parent=self.input_pane, objectName=name)
btn.setStyleSheet(style)
setattr(self, name, btn)
self.gridLayout_2.addWidget(btn, len(label_names) + i, 0)
self.comboBox = QtWidgets.QComboBox(parent=self.input_pane)
self.comboBox.addItems(["Select Symbol...", "MES", "M6E"])
self.gridLayout_2.addWidget(self.comboBox, len(label_names) + 5, 0)
self.comboBox_2 = QtWidgets.QComboBox(parent=self.input_pane)
self.comboBox_2.addItems(["Contracts...", "1", "5", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"])
self.gridLayout_2.addWidget(self.comboBox_2, len(label_names) + 6, 0)
self.gridLayout_3.addWidget(self.input_pane, 0, 0)
output_pane = QtWidgets.QFrame(parent=self.frame, autoFillBackground=True)
gridLayout_4 = QtWidgets.QGridLayout(output_pane)
self.CurrentOrders = QtWidgets.QTableWidget(parent=output_pane, columnCount=5, horizontalScrollBarPolicy=QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
for col in range(5):
self.CurrentOrders.horizontalHeader().setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeMode.Stretch)
gridLayout_4.addWidget(self.CurrentOrders, 0, 0)
self.gridLayout_3.addWidget(output_pane, 0, 1)
self.verticalLayout.addWidget(self.frame)
MainWindow.setCentralWidget(self.centralwidget)
MainWindow.setStatusBar(QtWidgets.QStatusBar(parent=MainWindow))
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("TWS-TRIGGER", "TWS-TRIGGER"))
self.tp.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:700;\">Take Profit (switch to trailing stop):</span></p></body></html>"))
self.pushButton.setText(_translate("MainWindow", " Buy"))
self.tp_3.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:700; color:#999999;\">Fees:</span></p></body></html>"))
self.tp_2.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:700; color:#999999;\">Commissions:</span></p></body></html>"))
self.AccBalanceLabel.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:700;\">Account Balance:</span></p></body></html>"))
self.GSL.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:700;\">Guaranteed Stop Loss:</span></p></body></html>"))
self.pushButton_2.setText(_translate("MainWindow", "Sell"))
self.CurrentOrders.setHorizontalHeaderLabels([
_translate("MainWindow", "Time In"),
_translate("MainWindow", "Symbol"),
_translate("MainWindow", "Contracts"),
_translate("MainWindow", "Current Value"),
_translate("MainWindow", "Action")
])
#Connecting to IBKR and checking if the connection is online
def is_currently_connected(self):
host = '127.0.0.1'
port = 7497
try:
# Attempt to connect to the TWS socket
with socket.create_connection((host, port), timeout=1):
return True # If successful, return connected status
except OSError:
return False # If connection fails, return disconnected status
def update_status(self, connected):
if connected:
self.setWindowTitle("TWS-TRIGGER - Connected")
else:
self.setWindowTitle("TWS-TRIGGER - Disconnected")
def main():
app = QtWidgets.QApplication(sys.argv)
ui = Ui_MainWindow()
ui.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()