Nautilus Trader - Integrations

Nautilus uses a modular adapter pattern: every venue/data-provider integration is a plug-in that translates the venue’s raw API into Nautilus’s unified domain model (native venue symbols + UNIX-epoch-nanosecond timestamps). An adapter is composed of three pieces - InstrumentProvider (loads contract/symbol metadata), DataClient (subscribes/requests market data), and ExecutionClient (submits/modifies/cancels orders) - plus the underlying transport (HttpClient for REST, WebSocketClient for streaming). The framework standardizes the lifecycle (connect → load instruments → reconcile account/positions → stream → reconnect on drop) so strategies remain venue-agnostic. Most adapters are marked stable; Coinbase is currently beta.

IBKR Integration (DETAILED)

Cortana MK2 currently runs on IBKR Gateway port 4002 with paper account DUP696099, using ib_async. Nautilus ships a first-class IBKR adapter that fully replaces direct ib_async usage for both data and execution.

Supported features

Order types - full coverage:

  • Market, Limit, Stop-Market, Stop-Limit
  • Market-If-Touched, Limit-If-Touched
  • Trailing Stop Market, Trailing Stop Limit
  • Market-on-Close / Limit-on-Close (TimeInForce.AT_THE_CLOSE)
  • Bracket (parent-child)
  • OCO via explicit IBOrderTags (ContingencyType.OCO does not auto-create OCA groups - gotcha)
  • Contingent orders with 6 condition types: price, time, volume, execution, margin, percent change

Market data - quote ticks, trade ticks (microsecond precision), OHLCV bars (1-second through 1-month), Level-2 depth (where IB permits). Four IBMarketDataTypeEnum modes: REALTIME, DELAYED, DELAYED_FROZEN, FROZEN.

Account / position data - real-time balance + margin updates, real-time position + P&L stream, multi-account support (one execution client per account), position mode (net vs. separate long/short), leverage/margin mode config, account reconciliation on startup. Option-exercise tracking is opt-in via track_option_exercise_from_position_update=True.

Contract specs - equities + ETFs + equity options, futures + continuous futures (CONTFUT), options on futures (FOP), forex (IDEALPRO), crypto (PAXOS), indices + index options, bonds (ISIN/CUSIP), CFDs, commodities.

Authentication / configuration

Two connection paths:

  1. Existing TWS/Gateway instance - point ibg_host + ibg_port at a TWS or Gateway you start yourself.
  2. Dockerized IB Gateway (recommended for automation) - DockerizedIBGatewayConfig(username, password, trading_mode, read_only_api, timeout) boots the gateway in a container.

Default ports:

ModeTWSGateway
Paper74974002
Live74964001

Cortana’s 4002 matches Gateway paper. Account ID DUP696099 follows the IB paper convention (DU prefix; the P is non-standard but IB does mint accounts with letters after DU).

Critical config keys:

  • ibg_host (default 127.0.0.1)
  • ibg_port (required)
  • ibg_client_id (1–32, must be unique per execution client)
  • account_id (e.g., DUP696099)
  • IBMarketDataTypeEnum for data client
  • SymbologyMethod.IB_SIMPLIFIED (e.g., EUR/USD.IDEALPRO) vs. IB_RAW (e.g., AAPL=STK.SMART)

Env vars: TWS_USERNAME, TWS_PASSWORD, TWS_ACCOUNT, IB_MAX_CONNECTION_ATTEMPTS.

Paper vs live

AspectPaperLive
Port7497 (TWS) / 4002 (Gateway)7496 (TWS) / 4001 (Gateway)
AccountDU… prefixalphanumeric (e.g., U987654)
Data modeDELAYED_FROZEN is sufficienttypically REALTIME
Credentialssame IB loginsame IB login

Mode is a config-only switch - no code changes. You can run paper and live as two execution clients in the same TradingNode (different ibg_client_id + account_id).

Reconciliation / position sync

  1. Connect over socket → run handshake.
  2. Pull account info, balances, positions (InteractiveBrokersClientAccountMixin).
  3. Load instruments listed in load_ids / load_contracts.
  4. Stream position + P&L updates continuously through _process_message() (the central API-message gateway). Cache + portfolio update on each tick.
  5. Watchdog loop monitors the socket; auto-reconnect with IB_MAX_CONNECTION_ATTEMPTS retries.
  6. fetch_all_open_orders=True pulls orders placed by any API client on the account (useful for surviving restarts; default False).

Known limitations / gotchas

  • IB pacing limits - too many historical-data requests trigger a multi-minute API lockout. Batch instruments, throttle requests, tune request_timeout_secs (default 60s).
  • Data permissions - many feeds require a paid IB market-data subscription. Use DELAYED_FROZEN for dev.
  • Symbology - IB_SIMPLIFIED is human-readable; IB_RAW mirrors IB API fields exactly. Pick one and stick to it.
  • Contract discovery - undocumented contracts can’t materialize as Instrument objects. Use IB’s Contract Information Center to find conIds.
  • Bar revisions - IB occasionally rewrites a published bar. Set handle_revised_bars=True if your strategy needs the corrected version.
  • TWS UTC requirement - TWS/Gateway must be configured to emit UTC timestamps before Nautilus connects. This is a TWS GUI setting, not a Nautilus knob.
  • OCA / OCO - must be configured via IBOrderTags(ocaGroup=…, ocaType=…). Setting ContingencyType.OCO on a Nautilus order does not create the IB-side OCA group automatically.
  • Multi-client - every execution client needs a unique ibg_client_id. Collisions silently break order routing.
  • connection_timeout=300 - startup window is 5 minutes; if Gateway takes longer to log in (e.g., 2FA push), bump it.

Strategy wiring (basic)

from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.model.identifiers import InstrumentId, Venue
 
class MyStrategy(Strategy):
    def on_start(self):
        self.request_instrument(
            venue=Venue("IB"),
            instrument_id=InstrumentId.from_str("SPY.ARCA"),
        )
 
    def on_instrument(self, instrument):
        self.subscribe_quote_ticks(instrument.id)
        self.subscribe_trade_ticks(instrument.id)
 
    def on_quote_tick(self, tick):
        order = self.order_factory.market(
            instrument_id=tick.instrument_id,
            order_side=OrderSide.BUY,
            quantity=tick.instrument.make_qty(10),
        )
        self.submit_order(order)
 
    def on_order_filled(self, event):
        self.log.info(f"Filled: {event.order.id}")

Sample config - minimal paper

from nautilus_trader.adapters.interactive_brokers.config import (
    InteractiveBrokersDataClientConfig,
    InteractiveBrokersExecClientConfig,
    InteractiveBrokersInstrumentProviderConfig,
    SymbologyMethod,
    IBMarketDataTypeEnum,
)
from nautilus_trader.config import TradingNodeConfig, RoutingConfig
 
instrument_config = InteractiveBrokersInstrumentProviderConfig(
    symbology_method=SymbologyMethod.IB_SIMPLIFIED,
    load_ids=frozenset(["SPY.ARCA"]),
)
 
data_config = InteractiveBrokersDataClientConfig(
    ibg_host="127.0.0.1",
    ibg_port=4002,                        # Cortana's port
    ibg_client_id=1,
    market_data_type=IBMarketDataTypeEnum.DELAYED_FROZEN,
    instrument_provider=instrument_config,
)
 
exec_config = InteractiveBrokersExecClientConfig(
    ibg_host="127.0.0.1",
    ibg_port=4002,
    ibg_client_id=1,
    account_id="DUP696099",               # Cortana's paper account
    instrument_provider=instrument_config,
    routing=RoutingConfig(default=True),
)
 
node_config = TradingNodeConfig(
    trader_id="CORTANA-PAPER",
    data_clients={"IB": data_config},
    exec_clients={"IB": exec_config},
)

Sample config - options chain via Dockerized Gateway

from nautilus_trader.adapters.interactive_brokers.common import IBContract
from nautilus_trader.adapters.interactive_brokers.config import DockerizedIBGatewayConfig
 
gateway_config = DockerizedIBGatewayConfig(
    username="…", password="…",
    trading_mode="paper",
    read_only_api=False,
)
 
instrument_config = InteractiveBrokersInstrumentProviderConfig(
    load_contracts=frozenset([
        IBContract(
            secType="STK", symbol="SPY",
            exchange="SMART", primaryExchange="ARCA",
            build_options_chain=True,
            min_expiry_days=0,
            max_expiry_days=1,            # 0DTE-style window
        ),
    ]),
)
 
exec_config = InteractiveBrokersExecClientConfig(
    ibg_client_id=1,
    account_id=os.environ["TWS_ACCOUNT"],
    instrument_provider=instrument_config,
    dockerized_gateway=gateway_config,
    routing=RoutingConfig(default=True),
)

Migration assessment for Cortana MK2

Verdict: Nautilus’s IBKR adapter would replace ib_async cleanly with low custom work. Concretely:

  • Direct fit - Gateway-port-4002 + paper-account-DUP696099 map 1:1 to ibg_port=4002 + account_id="DUP696099". No translation layer needed.
  • 0DTE options coverage - IBContract(build_options_chain=True, min_expiry_days=0, max_expiry_days=1) materializes the 0DTE chain natively. Equity-options orders, bracket parents, OCA groups (via IBOrderTags) are first-class.
  • Position-sync invariant - Nautilus’s reconciliation on startup + continuous position stream replaces the bespoke reconciler in GH #46 (project_pm_ibkr_exit_invariant.md). This is a large maintenance win.
  • Dual-TP defense-in-depth (feedback_dual_tp_defense_in_depth.md) - Nautilus orders carry both broker-side LMT and an in-process state machine, so the existing software-fallback pattern is preserved.
  • Caveats / custom work needed:
    • OCA gotcha: Cortana’s bracket-style exits will need IBOrderTags populated, not just ContingencyType.OCO.
    • TWS UTC: Gateway settings must be flipped before connect - make this a launchd preflight check.
    • Watchdog overlap: Cortana already has a launchd-driven watchdog (feedback_watchdog_to_telegram.md); Nautilus’s auto-reconnect partially overlaps. Decide which is authoritative - probably let Nautilus handle reconnect, keep launchd for process supervision.
    • ib_async callsites: every IB.connect() / placeOrder() / reqMktData() becomes submit_order() / subscribe_*(). Mechanical but pervasive.
    • Symbology choice: pick IB_SIMPLIFIED for readability or IB_RAW for parity with existing IB conId tracking - one-time decision.

Net: clean swap, weeks not months. The bigger lift is plumbing UW (below) into Nautilus’s data engine, not replacing IBKR plumbing.

Other Integrations (BRIEF)

Cryptocurrency CEX

  • Binance - spot, USDM, COINM derivatives.
  • Bybit - spot + perpetuals + USDC options.
  • OKX - spot, perps, futures, options.
  • Coinbase - spot (currently beta).
  • Kraken - spot + futures.
  • BitMEX - perps + futures.
  • Deribit - crypto options + perps (the institutional crypto-options venue).

DEX / on-chain

  • dYdX v4 - perps on Cosmos-based dYdX chain.
  • Hyperliquid - perps on Hyperliquid L1.
  • Polymarket - prediction-market binary outcomes (event-driven).

Other derivatives

  • AX Exchange - perpetuals venue.

Traditional brokerage / multi-asset

  • Interactive Brokers - covered above; the only adapter that spans equities + options + futures + FX + bonds + CFDs in one connector.

Alternative markets

  • Betfair - sports-betting exchange (treated as a venue with quotes + orders).

Data-only providers

  • Databento - historical + live multi-asset normalized data.
  • Tardis - historical crypto market-data (high-fidelity tick-by-tick).

What about Unusual Whales?

Nautilus has no native UW adapter. UW is niche options-flow + GEX/charm/vanna data - not on Nautilus’s roadmap. A Cortana migration would build a custom data-only adapter following Nautilus’s adapter pattern:

  1. UnusualWhalesInstrumentProvider - minimal; UW doesn’t define instruments, IB does. Provider can be a no-op or proxy IB instruments.
  2. UnusualWhalesDataClient - implements:
    • connect() opens the WebSocket (UW streaming endpoint) + holds an HttpClient for REST fallback / historical pulls (e.g., /api/net-flow/expiry).
    • subscribe_* methods for the custom data types Cortana cares about (flow, GEX, charm/vanna magnitudes, alerts).
    • Push each WS message into the Nautilus DataEngine as a custom data type (subclass of Data). Nautilus supports user-defined data classes that flow through the same engine plumbing as bars/ticks; strategies consume them via subscribe_data(YourCustomType) and on_data().
  3. No ExecutionClient - UW is read-only. Execution stays on the IBKR adapter.
  4. Wiring - register the UW client under a new data_clients={"UW": uw_data_config} entry; strategies can subscribe_data(UnusualWhalesFlowTick) alongside their IB quote-tick subscriptions in the same engine.

Effort estimate: a couple of weeks for a clean adapter. Existing Cortana UW code (REST + WS handlers, schema for flow/GEX/charm) ports almost directly into the new client - the work is structural (fitting Nautilus’s DataEngine contract) rather than business-logic.

Two known UW issues - strike-format 404 (#54) and WebSocket timestamp unit mismatch (#59) - would naturally get fixed inside the adapter layer rather than scattered across callsites.

See also

  • nautilus-concepts.md (DataEngine, ExecutionEngine sections)
  • nautilus-developer-guide.md (writing custom adapters)
  • project_pm_ibkr_exit_invariant.md - the broker-truth invariant Nautilus’s reconciliation enforces by default
  • feedback_dual_tp_defense_in_depth.md - defense-in-depth pattern survives migration