Here at Robot Wealth, we trade with Interactive Brokers (IB) primarily because they offer access to global markets at a reasonable price.
In recent times, IB has put some time and effort into upping its tech game, including development of an API for interacting with its desktop trading applications.
An application that interacts with IB’s desktop trading applications via the API is essentially a message-handling program. So I want to show you a simple but effective architecture for managing the flow of messages and the operations they trigger. If you can understand this framework, then you can basically design any application you can think of.
Before we get to that, I’ll show you how to get started with the IB API – how it works, how to configure it, and options available for using it.
Then I’ll show you a Hello World example, and in later articles, I’ll go through some more interesting and complex use cases.
The IB API is (kind of) different
The IB API is essentially a message protocol for communicating with the IB desktop trading platforms – Trader Workstation (TWS) and Gateway (GW).
If you think about this in terms of the normal client-server framework, your trading code is essentially the “client” and TWS is the “server”, and they communicate using the IB API. TWS acts as an intermediary to IB’s actual servers, and the API provides an interface to TWS.
The most obvious implication is that you need to have TWS or GW running in order to use the API.
This means that you also need to deal with restarts.
Dealing with restarts
TWS and GW were designed to be restarted daily (for example to re-download contract definitions where contracts have been changed or new contracts added). However, they have an auto-restart feature that restarts the application daily without user intervention. With this option enabled, the application can run for up to a week without re-authenticating. However, after the nightly server reset on Saturday night (US), you will have to re-authenticate.
Of course, if you’re trading, you’ll be checking on the platform constantly, but it’s nice not to have to re-authenticate every time it restarts.
To set that option in TWS, from the File menu, choose Global Configuration:
Then, from the Configuration menu, choose “Lock and Exit”. Check the “Auto restart” button, uncheck the “Auto log off” button, and set the time you’d like to have the application restart:
The restart time is in the time zone you have set for TWS. You can see TWS’ current time in the top right hand corner:
If you need to change the time zone, you’ll need to log out, and from the log in screen choose “More options”. You can then change your time zone:
Using the IB API
You have a few options for working with the IB API. You can either use an existing wrapper such as ib_insync, or work with the native API.
The advantage of something like ib_insync is that all of the “plumbing” code is done for you – in particular, it’s a fully asynchronous framework which handles messages to and from TWS.
The native API is a bit more complex to work with, and you’ll have to figure out how to handle messages yourself (async, threading, etc). On the other hand, the native API is officially supported by IB (ib_insync is third-party software, although at the moment is actively maintained), and it removes any limitations on customisation.
In this series, we’ll use the native API.
Installing the native IB API
The first step is installing the native API.
Grab the software installer for your operating system from IB’s github pages. Run the installer and follow the prompts.
You’ll also need Python – Miniconda provides a good, minimal installation, but use whatever you normally work with.
Once the API software is installed, navigate to the installation directory (on Windows, usually C:\TWS API). Then drill down to the pythonclient directory and run the setup.py file with the install
parameter:
Once the installation completes, verify that it worked by doing python -m pip show ibapi
to show the latest installed version:
Configuring TWS to use the API
Next we need to configure TWS.
Enable logging
TWS can be configured to write a separate log file that documents communication via the API. This will make debugging a whole lot simpler, so enable it in TWS as follows:
From the File menu, select “Global Configuration”. Then, select “API” –> “Settings” and check the box for “Create API message file”:
TWS logs are encrypted. To read them, you’ll need to export them from TWS as follows:
From the Help menu, select “Troubleshooting” –> “Diagnostics” –> “API Log Files”:
You can then select the logs you want to export.
See here for more detailed information about the TWS log files.
Configuration settings
You’ll also need to configure the following settings in TWS:
From the File menu, select “Global Configuration” –> “API” –> “Settings”. Then, make the following changes:
- Check the box for “Enable ActiveX and Socket Clients”
- Designate a socket port – 7496 is the default
- Check the box for “Allow connections from localhost only” – this will allow connections only from applications running on the same machine as TWS.
If you want to have multiple TWS instances running on the same machine, you can configure each with a different API socket port number.
You may wish to connect to TWS remotely. In that case, uncheck “Allow connections from localhost only” and add the relevant IP addresses to “Trusted IPs”. If you don’t add these IPs, you’ll have to manually authenticate each connection from a pop-up box in TWS (which kind of defeats the purpose of remote access).
Understanding the messaging framework
A Python client application using the IB API requires two threads of execution:
- One for messages sent from TWS to the client application. This uses the
IBAPI.Ewrapper
interface, which you can use out of the box or override depending on how you want to handle these messages. - One for messages sent from the client application to TWS. This uses the
IBAPI.EClientSocket
class, which you inherit from, and include yourIBApi.EWrapper
in the constructor parameters (so that the application can handle all the messages coming from TWS).
The base class Ewrapper
defines and implements functions for handling messages from TWS by simply logging them. You may wish to inherit from this class and override the methods depending on how you want to handle these messages.
In the Python implementation of EClient
, messages are processed using a Queue
. This happens in an endless loop in Eclient.run
. In other languages, you need a third thread of execution to handle this.
Let’s make this a bit more real with a Hello World example.
Hello World
In this example, we’ll connect to TWS, request our account information, and print it to screen.
Before writing any code, it’s always worth thinking about the architecture of an IB API application, which is inherently a message-handling program.
Remember that we handle our messages to and from TWS in separate threads, so ideally we would have some way of signalling between threads when a message has been received or processed.
To do this, we’ll use threading.Event
, which is a simple way to control the flow of a program hat involves asynchronous operations and multiple threads.
threading.Event
manages an internal flag that can be set to True with the set()
method and reset to False with the clear()
method. Other threads can wait for this flag to be set by calling the wait()
method, which blocks until the flag becomes True.
This mechanism is particularly useful for signaling between threads, allowing one thread to signal another that an event has occurred or a condition has been met – which is exactly what we need to do with our TWS messaging application.
We’ll use the Eclient.reqAccountSummary
method to request our account information. If you look at the documentation for this function, you’ll also find the Ewrapper
methods that we need to override in order to handle the messages coming back from TWS.
We also use the following methods from Eclient
:
connect
– initiates a connection with TWS and triggers theEwrapper.nextValidId
callback once established. We’ll use athreading.Event
to signal when this callback is triggered and our connection is established (messages sent while the connection is being established can be lost).disconnect
– terminates the connection with TWS (but doesn’t cancel in-flight orders), good practice to include this at the completion of your script.run
– processes the message queue in an infinite loop while connected.
Here’s the script for connecting to TWS, requesting our account information, and printing it to screen. I’ve heavily commented it so that you can follow what’s going on if you’re new to this.
# %load ibkr-api/account_summary.py
from threading import Thread, Event
import time
from typing import Any
from ibapi.wrapper import EWrapper
from ibapi.client import EClient
from ibapi.common import *
from ibapi.account_summary_tags import AccountSummaryTags
class ibapp(EClient, EWrapper):
def __init__(self):
EClient.__init__(self, self)
self.done = Event() # use threading.Event to signal between threads
self.connection_ready = Event() # to signal the connection has been established
# override Ewrapper.error
def error(
self, reqId: TickerId, errorCode: int, errorString: str, contract: Any = None
):
print("Error: ", reqId, " ", errorCode, " ", errorString)
if errorCode == 502: # not connected
# set self.done (a threading.Event) to True
self.done.set()
# override Ewrapper.accountSummary - method for receiving account summary
def accountSummary(
self, reqId: int, account: str, tag: str, value: str, currency: str
):
# just print the account information to screen
print(
"AccountSummary. ReqId:",
reqId,
"Account:",
account,
"Tag: ",
tag,
"Value:",
value,
"Currency:",
currency,
)
# override Ewrapper.accountSummaryEnd - notifies when account summary information has been received
def accountSummaryEnd(self, reqId: int):
# print to screen
print("AccountSummaryEnd. ReqId:", reqId)
# set self.done (a threading.Event) to True
self.done.set()
# override Ewrapper.nextValidID - used to signal that the connection between application and TWS is complete
# returns the next valid orderID (for any future transactions)
# if we send messages before the connection has been established, they can be lost
# so wait for this method to be called
def nextValidId(self, orderId: int):
print(f"Connection ready, next valid order ID: {orderId}")
self.connection_ready.set() # signal that the connection is ready
# define our event loop - this will run in its own thread
def run_loop(app):
app.run()
# instantiate an ibapp
app = ibapp()
# connect
app.connect("127.0.0.1", 7496, clientId=0) # clientID identifies our application
# start the application's event loop in a thread
api_thread = Thread(target=run_loop, args=(app,), daemon=True)
api_thread.start()
# wait until the Ewrapper.nextValidId callback is triggered, indicating a successful connection
app.connection_ready.wait()
# request account summary
print("Requesting account summary")
app.reqAccountSummary(0, "All", AccountSummaryTags.AllTags)
# wait for the account summary to finish (ie block until app.done - a threading.Event - becomes true)
app.done.wait()
# disconnect
app.disconnect()
Connection ready, next valid order ID: 1 Requesting account summary Error: -1 2104 Market data farm connection is OK:usfarm.nj Error: -1 2104 Market data farm connection is OK:hfarm Error: -1 2104 Market data farm connection is OK:jfarm Error: -1 2104 Market data farm connection is OK:cashfarm Error: -1 2104 Market data farm connection is OK:eufarmnj Error: -1 2104 Market data farm connection is OK:usfarm Error: -1 2106 HMDS data farm connection is OK:euhmds Error: -1 2106 HMDS data farm connection is OK:cashhmds Error: -1 2106 HMDS data farm connection is OK:fundfarm Error: -1 2106 HMDS data farm connection is OK:ushmds Error: -1 2158 Sec-def data farm connection is OK:secdefnj AccountSummary. ReqId: 0 Account: DU1077784 Tag: AccountType Value: LLC Currency: AccountSummary. ReqId: 0 Account: DU1077784 Tag: Cushion Value: 1 Currency: AccountSummary. ReqId: 0 Account: DU1077784 Tag: LookAheadNextChange Value: 1709821800 Currency: AccountSummary. ReqId: 0 Account: DU1077784 Tag: AccruedCash Value: 0.00 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: AvailableFunds Value: 94012.52 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: BuyingPower Value: 109205.21 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: EquityWithLoanValue Value: 114919.02 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: ExcessLiquidity Value: 95913.11 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: FullAvailableFunds Value: 94012.52 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: FullExcessLiquidity Value: 95913.11 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: FullInitMarginReq Value: 20906.50 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: FullMaintMarginReq Value: 19005.91 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: GrossPositionValue Value: 38555.06 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: InitMarginReq Value: 20906.50 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: LookAheadAvailableFunds Value: 94012.52 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: LookAheadExcessLiquidity Value: 95913.11 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: LookAheadInitMarginReq Value: 20906.50 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: LookAheadMaintMarginReq Value: 19005.91 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: MaintMarginReq Value: 19005.91 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: NetLiquidation Value: 114919.02 Currency: USD AccountSummary. ReqId: 0 Account: DU1077784 Tag: TotalCashValue Value: 76363.96 Currency: USD AccountSummaryEnd. ReqId: 0
The output shows that after the connection was established, we requested our account summary. We get a few lines with error code 2104. These aren’t actually errors, they’re warning messages. From the list of error codes, we can see that 2104 means “Market data farm connection is OK”.
We also get some 2106’s (A historical data farm is connected) and a 2158 (Sec-def data farm connection is OK).
Then, we print out each item in our account summary (this is from a connection to a paper trading account).
Finally, after our account summary has been processed, we print the account summary end message.
Conclusion
In this article, you learned how to set up TWS to use the IB native Python API.
You also learned that an application that uses the API to interact with TWS is, at its heart, a message-handling program.
We saw that using threading.Event
to signal between threads when a message has been received and/or processed is a simple and effective way to manage the flow of information.
We started with the simplest possible use case – connecting to TWS and requesting our account information. In future articles, we’ll explore some more complex use cases.
To consolidate your understanding, consider these next steps:
- Have a look through the IB API documentation to get an idea of the functionality (essentially, anything you can manually do in TWS, you can do via API).
- Also have a look at the source code for Eclient and Ewrapper. On Windows, they live in the directory C:\TWS API\source\pythonclient\ibapi (client.py, wrapper.py).
Good article.
I have some code which I wrote a while back. Hope this is of some us3:
https://pavankishoremullapudy.blogspot.com/search/label/Interactive%20Brokers?m=0
Very nice! Thanks for sharing!!
Hi Kris,
Thanks a lot for sharing this.
Really helpful and I’m looking forward to future articles dealing with IB API.
Thanks so much for reading@
If you haven’t got too many orders to place at once, why not consider Client Portal API (a normal web service) instead of IB API? So you don’t need TWS running etc.
I don’t know much about the Client Portal API, but sounds like it could be a good solution assuming you can implement the order handling logic you need for your application.
Asynchronous programming is ok, and also very popular now. The major problem of the IB API is, you have to socket connect their clients, no matter TWS or the “Gateway”, both are Java app with the outdated JWT UI. And the most stupid thing is, they have to restart daily. People ever wrote something called “IBController” to overcome this, but cannot catch up with IB’s updates. Just hope IB could provide a REST API and allow connecting to their servers 7X24.
I am forced to agree with you Jian!
Loved the article article and good timing – just got IB and looking forward to automate som ops.
Thanks Alex! Glad you liked it.