Interactive Brokers
Interactive Brokers (IB) is a trading platform providing market access across a wide range of financial instruments, including stocks, options, futures, currencies, bonds, funds, and cryptocurrencies. PoseiTrader offers an adapter to integrate with IB using their Trader Workstation (TWS) API through their Python library, ibapi.
The TWS API serves as an interface to IB's
standalone trading applications: TWS and IB
Gateway. Both can be downloaded from the IB
website. If you haven't installed TWS or IB
Gateway yet, refer to the
Initial Setup
guide. In PoseiTrader, you'll establish a
connection to one of these applications via the
InteractiveBrokersClient
.
Alternatively, you can start with a dockerized version of the IB Gateway, which is particularly useful when deploying trading strategies on a hosted cloud platform. This requires having Docker installed on your machine, along with the docker Python package, which PoseiTrader conveniently includes as an extra package.
The standalone TWS and IB Gateway applications require manually inputting username, password, and trading mode (live or paper) at startup. The dockerized version of the IB Gateway handles these steps programmatically.
Installation
To install PoseiTrader with Interactive Brokers (and Docker) support:
pip install --upgrade "posei_trader[ib,docker]"
To build from source with all extras (including IB and Docker):
uv sync --all-extras
Because IB does not provide wheels for
ibapi
, PoseiTrader
repackages
it for release on PyPI.
Examples
You can find live example scripts here.
Getting Started
Before implementing your trading strategies,
please ensure that either TWS (Trader Workstation)
or IB Gateway is currently running. You have the
option to log in to one of these standalone
applications using your personal credentials or
alternatively, via
DockerizedIBGateway
.
Establish Connection to an Existing Gateway or TWS
Should you choose to connect to a pre-existing
Gateway or TWS, it is crucial that you specify the
host
and port
parameters
in both the
InteractiveBrokersDataClientConfig
and
InteractiveBrokersExecClientConfig
to
guarantee a successful connection.
Establish Connection to DockerizedIBGateway
In this case, it's essential to supply
dockerized_gateway
with an instance
of DockerizedIBGatewayConfig
in both
the
InteractiveBrokersDataClientConfig
and
InteractiveBrokersExecClientConfig
.
It's important to stress, however, that
host
and port
parameters
aren't necessary in this context. The
following example provides a clear illustration of
how to establish a connection to a Dockerized
Gateway, which is judiciously managed internally
by the Factories.
from posei_trader.adapters.interactive_brokers.config import DockerizedIBGatewayConfig
from posei_trader.adapters.interactive_brokers.gateway import DockerizedIBGateway
gateway_config = DockerizedIBGatewayConfig(
username="test",
password="test",
trading_mode="paper",
)
# This may take a short while to start up, especially the first time
gateway = DockerizedIBGateway(
config=gateway_config
)
gateway.start()
# Confirm you are logged in
print(gateway.is_logged_in(gateway.container))
# Inspect the logs
print(gateway.container.logs())
Note: To supply credentials to
the Interactive Brokers Gateway, either pass the
username
and password
to
the DockerizedIBGatewayConfig
, or set
the following environment variables:
TWS_USERNAME
TWS_PASSWORD
Overview
The adapter includes several major components:
-
InteractiveBrokersClient
: Executes TWS API requests usingibapi
. -
HistoricInteractiveBrokersClient
: Provides methods for retrieving instruments and historical data, useful for backtesting. -
InteractiveBrokersInstrumentProvider
: Retrieves or queries instruments for trading. -
InteractiveBrokersDataClient
: Connects to the Gateway for streaming market data. -
InteractiveBrokersExecutionClient
: Handles account information and executes trades.
The Interactive Brokers Client
The InteractiveBrokersClient
serves
as the central component of the IB adapter,
overseeing a range of critical functions. These
include establishing and maintaining connections,
handling API errors, executing trades, and
gathering various types of data such as market
data, contract/instrument data, and account
details.
To ensure efficient management of these diverse
responsibilities, the
InteractiveBrokersClient
is divided
into several specialized mixin classes. This
modular approach enhances manageability and
clarity. The key subcomponents are:
-
InteractiveBrokersClientConnectionMixin
: This class is dedicated to managing the connection with TWS/Gateway. -
InteractiveBrokersClientErrorMixin
: It focuses on addressing all encountered errors and warnings. -
InteractiveBrokersClientAccountMixin
: Responsible for handling requests related to account information and positions. -
InteractiveBrokersClientContractMixin
: Handles retrieving contracts (instruments) data. -
InteractiveBrokersClientMarketDataMixin
: Handles market data requests, subscriptions and data processing. -
InteractiveBrokersClientOrderMixin
: Oversees all aspects of order placement and management.
To troubleshoot TWS API incoming message
issues, consider starting at the
InteractiveBrokersClient._process_message
method, which acts as the primary gateway for
processing all messages received from the API.
Symbology
The InteractiveBrokersInstrumentProvider supports
three methods for constructing InstrumentId
instances, which can be configured via the
symbology_method
enum in
InteractiveBrokersInstrumentProviderConfig
.
Simplified Symbology
When symbology_method is set to
IB_SIMPLIFIED
(the default setting),
the system utilizes the following parsing rules
for symbology:
-
Forex: The format is
{symbol}/{currency}.{exchange}
, where the currency pair is constructed asEUR/USD.IDEALPRO
. -
Stocks: The format is
{localSymbol}.{primaryExchange}
. Any spaces in localSymbol are replaced with -, e.g.,BF-B.NYSE
. -
Futures: The format is
{localSymbol}.{exchange}
. Single digit years are expanded to two digits, e.g.,ESM24.CME
. -
Options: The format is
{localSymbol}.{exchange}
, with all spaces removed from localSymbol, e.g.,AAPL230217P00155000.SMART
. -
Index: The format is
^{localSymbol}.{exchange}
, e.g.,^SPX.CBOE
.
Raw Symbology
Setting symbology_method to
IB_RAW
enforces stricter parsing
rules that align directly with the fields defined
in the ibapi. The format for each security type is
as follows:
-
CFDs:
{localSymbol}={secType}.IBCFD
-
Commodities:
{localSymbol}={secType}.IBCMDTY
-
Default for Other Types:
{localSymbol}={secType}.{exchange}
This configuration ensures that the symbology is explicitly defined and matched with the Interactive Brokers API requirements, providing clear and consistent instrument identification. While this format may lack visual clarity, it is robust and supports instruments from any region, especially those with non-standard symbology where simplified parsing may fail.
Databento Symbology
Setting symbology_method to
DATABENTO
, the system utilized the
symbology rules defined by
DatabentoInstrumentProvider
. Note
that this symbology is only compatible with venues
supported by Databento and there is not automatic
fall-back to other symbology methods to avoid any
conflicts.
Instruments & Contracts
In IB, a PoseiTrader Instrument
is
equivalent to a
Contract. Contracts can be either a
basic contract
or a more
detailed
version (ContractDetails). The adapter models
these using IBContract
and
IBContractDetails
classes. The latter
includes critical data like order types and
trading hours, which are absent in the basic
contract. As a result,
IBContractDetails
can be converted to
an Instrument
while
IBContract
cannot.
To search for contract information, use the IB Contract Information Center.
It's typically suggested to utilize
symbology_method=SymbologyMethod.IB_SIMPLIFIED
(which is the default setting). This provides a
cleaner and more intuitive use of
InstrumentId
by employing
load_ids
in the
InteractiveBrokersInstrumentProviderConfig
, following the guidelines established in the
Simplified Symbology section. In order to load
multiple Instruments, such as Options Instrument
without having to specify each strike explicitly,
you would need to utilize
load_contracts
with provided
instances of IBContract
.
for_loading_instrument_expiry = IBContract(
secType="IND",
symbol="SPX",
exchange="CBOE",
build_options_chain=True,
lastTradeDateOrContractMonth='20240718',
)
for_loading_instrument_range = IBContract(
secType="IND",
symbol="SPX",
exchange="CBOE",
build_options_chain=True,
min_expiry_days=0,
max_expiry_days=30,
)
Note: The
secType
andsymbol
should be specified for the Underlying Contract.
Some more examples of building IBContracts:
from posei_trader.adapters.interactive_brokers.common import IBContract
# Stock
IBContract(secType='STK', exchange='SMART', primaryExchange='ARCA', symbol='SPY')
# Bond
IBContract(secType='BOND', secIdType='ISIN', secId='US03076KAA60')
# Option
IBContract(secType='STK', exchange='SMART', primaryExchange='ARCA', symbol='SPY', lastTradeDateOrContractMonth='20251219', build_options_chain=True)
# CFD
IBContract(secType='CFD', symbol='IBUS30')
# Future
IBContract(secType='CONTFUT', exchange='CME', symbol='ES', build_futures_chain=True)
# Forex
IBContract(secType='CASH', exchange='IDEALPRO', symbol='EUR', currency='GBP')
# Crypto
IBContract(secType='CRYPTO', symbol='ETH', exchange='PAXOS', currency='USD')
Historical Data & Backtesting
When developing strategies with the IB adapter,
the first step usually involves acquiring
historical data for backtesting. The
HistoricInteractiveBrokersClient
offers methods to request and save this data.
Here's an example of retrieving and saving instrument and bar data. A more comprehensive example is available here.
import datetime
from posei_trader.adapters.interactive_brokers.common import IBContract
from posei_trader.adapters.interactive_brokers.historic import HistoricInteractiveBrokersClient
from posei_trader.persistence.catalog import ParquetDataCatalog
async def main():
contract = IBContract(
secType="STK",
symbol="AAPL",
exchange="SMART",
primaryExchange="NASDAQ",
)
client = HistoricInteractiveBrokersClient()
instruments = await client.request_instruments(
contracts=[contract],
)
bars = await client.request_bars(
bar_specifications=["1-HOUR-LAST", "30-MINUTE-MID"],
end_date_time=datetime.datetime(2023, 11, 6, 16, 30),
tz_name="America/New_York",
duration="1 D",
contracts=[contract],
)
catalog = ParquetDataCatalog("./catalog")
catalog.write_data(instruments)
catalog.write_data(bars)
Live Trading
Engaging in live or paper trading requires
constructing and running a
TradingNode
. This node incorporates
both InteractiveBrokersDataClient
and
InteractiveBrokersExecutionClient
,
which depend on the
InteractiveBrokersInstrumentProvider
to operate.
InstrumentProvider
The
InteractiveBrokersInstrumentProvider
class functions as a bridge for accessing
financial instrument data from IB. Configurable
through
InteractiveBrokersInstrumentProviderConfig
, it enables the customization of various
instrument type parameters. Additionally, this
provider offers specialized methods to build and
retrieve the entire futures and options chains.
from posei_trader.adapters.interactive_brokers.config import InteractiveBrokersInstrumentProviderConfig
from posei_trader.adapters.interactive_brokers.config import SymbologyMethod
instrument_provider_config = InteractiveBrokersInstrumentProviderConfig(
symbology_method=SymbologyMethod.IB_SIMPLIFIED,
build_futures_chain=False, # Set to True if fetching futures
build_options_chain=False, # Set to True if fetching options
min_expiry_days=10, # Relevant for futures/options with expiration
max_expiry_days=60, # Relevant for futures/options with expiration
load_ids=frozenset(
[
"EUR/USD.IDEALPRO",
"BTC/USD.PAXOS",
"SPY.ARCA",
"V.NYSE",
"YMH24.CBOT",
"CLZ27.NYMEX",
"ESZ27.CME",
],
),
load_contracts=frozenset(
[
IBContract(secType='STK', symbol='SPY', exchange='SMART', primaryExchange='ARCA'),
IBContract(secType='STK', symbol='AAPL', exchange='SMART', primaryExchange='NASDAQ')
]
),
)
Integration with Databento Data Client
To integrate with
DatabentoDataClient
, set the
symbology_method
in
InteractiveBrokersInstrumentProviderConfig
to SymbologyMethod.DATABENTO
. This
ensures seamless compatibility with Databento
symbology, eliminating the need for manual
translations or mappings within your strategy.
When using this configuration:
-
InteractiveBrokersInstrumentProvider
will not publish instruments to the cache to prevent conflicts. -
Instruments Cache management must be handled
exclusively by
DatabentoDataClient
.
Data Client
InteractiveBrokersDataClient
interfaces with IB for streaming and retrieving
market data. Upon connection, it configures the
market data type
and loads instruments based on the settings in
InteractiveBrokersInstrumentProviderConfig
. This client can subscribe to and unsubscribe
from various market data types, including quotes,
trades, and bars.
Configurable through
InteractiveBrokersDataClientConfig
,
it enables adjustments for handling revised bars,
trading hours preferences, and market data types
(e.g.,
IBMarketDataTypeEnum.REALTIME
or
IBMarketDataTypeEnum.DELAYED_FROZEN
).
from posei_trader.adapters.interactive_brokers.config import IBMarketDataTypeEnum
from posei_trader.adapters.interactive_brokers.config import InteractiveBrokersDataClientConfig
data_client_config = InteractiveBrokersDataClientConfig(
ibg_port=4002,
handle_revised_bars=False,
use_regular_trading_hours=True,
market_data_type=IBMarketDataTypeEnum.DELAYED_FROZEN, # Default is REALTIME if not set
instrument_provider=instrument_provider_config,
dockerized_gateway=dockerized_gateway_config,
)
Execution Client
The
InteractiveBrokersExecutionClient
facilitates executing trades, accessing account
information, and processing order and
trade-related details. It encompasses a range of
methods for order management, including reporting
order statuses, placing new orders, and modifying
or canceling existing ones. Additionally, it
generates position reports, although fill reports
are not yet implemented.
from posei_trader.adapters.interactive_brokers.config import InteractiveBrokersExecClientConfig
from posei_trader.config import RoutingConfig
exec_client_config = InteractiveBrokersExecClientConfig(
ibg_port=4002,
account_id="DU123456", # Must match the connected IB Gateway/TWS
dockerized_gateway=dockerized_gateway_config,
instrument_provider=instrument_provider_config,
routing=RoutingConfig(
default=True,
)
)
Full Configuration
Setting up a complete trading environment
typically involves configuring a
TradingNodeConfig
, which includes
data and execution client configurations.
Additional configurations are specified in
LiveDataEngineConfig
to accommodate
IB-specific requirements. A
TradingNode
is then instantiated from
these configurations, and factories for creating
InteractiveBrokersDataClient
and
InteractiveBrokersExecutionClient
are
added. Finally, the node is built and run.
You can find additional examples here: https://github.com/poseisysstems/tree/develop/examples/live/interactive_brokers
from posei_trader.adapters.interactive_brokers.common import IB
from posei_trader.adapters.interactive_brokers.common import IB_VENUE
from posei_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveDataClientFactory
from posei_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveExecClientFactory
from posei_trader.config import LiveDataEngineConfig
from posei_trader.config import LoggingConfig
from posei_trader.config import TradingNodeConfig
from posei_trader.live.node import TradingNode
# ... [continuing from prior example code] ...
# Configure the trading node
config_node = TradingNodeConfig(
trader_id="TESTER-001",
logging=LoggingConfig(log_level="INFO"),
data_clients={IB: data_client_config},
exec_clients={IB: exec_client_config},
data_engine=LiveDataEngineConfig(
time_bars_timestamp_on_close=False, # Use opening time as `ts_event`, as per IB standard
validate_data_sequence=True, # Discards bars received out of sequence
),
)
node = TradingNode(config=config_node)
node.add_data_client_factory(IB, InteractiveBrokersLiveDataClientFactory)
node.add_exec_client_factory(IB, InteractiveBrokersLiveExecClientFactory)
node.build()
node.portfolio.set_specific_venue(IB_VENUE)
if __name__ == "__main__":
try:
node.run()
finally:
# Stop and dispose of the node with SIGINT/CTRL+C
node.dispose()