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. NautilusTrader 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 NautilusTrader, 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 NautilusTrader 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 the latest nautilus_trader
package along with the ibapi
and optional docker
dependencies using pip:
pip install -U "nautilus_trader[ib,docker]"
To install from source using poetry:
poetry install --extras ib --extras docker
Because IB does not provide wheels for ibapi
, NautilusTrader repackages it for release on PyPI.
Examples
You can find working 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 nautilus_trader.adapters.interactive_brokers.config import DockerizedIBGatewayConfig
from nautilus_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 NautilusTrader 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 nautilus_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 nautilus_trader.adapters.interactive_brokers.common import IBContract
from nautilus_trader.adapters.interactive_brokers.historic import HistoricInteractiveBrokersClient
from nautilus_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 nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersInstrumentProviderConfig
from nautilus_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 nautilus_trader.adapters.interactive_brokers.config import IBMarketDataTypeEnum
from nautilus_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 nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersExecClientConfig
from nautilus_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.
For a comprehensive example, refer to this script.
from nautilus_trader.adapters.interactive_brokers.common import IB_VENUE
from nautilus_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveDataClientFactory
from nautilus_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveExecClientFactory
from nautilus_trader.config import LiveDataEngineConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.config import TradingNodeConfig
from nautilus_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()