Skip to main content

Example: Interactive Brokers

This is a worked example, not a shipped adapter. It shows how one broker's API maps onto MarketDataFeed and TradingBrokerAdapter, and which of that broker's constraints have to be designed around rather than discovered in production.

IBKR is a useful example because it supplies both market data and execution through the same account, so a single desk can get a working chart from one relationship. Verify current details against IBKR's own documentation before building — ports, limits, and entitlements change.

Choose an authorization path

This is the first decision, and it determines your deployment more than your code.

PathRequires a running desktop sessionAimed atTrade-off
TWS API via Trader WorkstationYes — the full desktop app, logged inIndividuals, developersEasiest to start; a GUI app becomes production infrastructure
TWS API via IB GatewayYes — a lightweight session, still logged inSmall funds, automated desksFar lower resource use than TWS, same API surface
Web APINoTeams avoiding a desktop dependencyREST and WebSocket over standard auth; no GUI process to babysit
FIX CTCINoInstitutional clientsLowest latency, co-located; see FIX protocol

The bridge service is the piece people underestimate. The TWS API is an asynchronous, callback-driven socket protocol with request ids — not a request/response HTTP API. Something has to hold that connection, correlate callbacks back to requests, and expose the result as the HTTP and WebSocket surface your gateway serves.

Authorizing through the desktop app

The TWS API is an interface to a running TWS or IB Gateway instance. There is no API without a logged-in session — the desktop app is the authorization.

Enable API access

In TWS or IB Gateway, open Edit → Global Configuration → API → Settings and:

  • Enable "ActiveX and Socket Clients". Nothing connects until this is on.
  • Turn off "Read-Only API" when the chart must place orders. It is enabled by default as a safety measure, and leaving it on is the correct choice for a chart that only displays data.
  • Add the bridge service's address to trusted IPs when it does not run on the same host.
  • Set a Master Client ID if several clients connect and all of them need to see order status updates, not just the client that submitted the order.

Ports

ApplicationLivePaper
Trader Workstation74967497
IB Gateway40014002

Point paper and live at different ports and treat them as different environments end to end. The broker adapter should declare which one it is through executionEnvironment, so the SDK never has to infer a live account from an account name.

Keep the session alive

A logged-in desktop session is now production infrastructure, and it has a schedule:

  • Both TWS and IB Gateway require a daily restart. With auto-restart configured under Global Configuration → Lock and Exit, the session restarts through the week and needs a manual restart and re-authentication once a week, on Sunday.
  • Select "Never lock Trader Workstation" for API hosts, or the session locks and stops serving.
  • Expect a connectivity loss during IBKR's daily server maintenance. The session reconnects on its own; your bridge must survive that gap and tell the chart, rather than silently serving stale data.
  • Two-factor authentication through IBKR Mobile applies to the login. Plan who performs the weekly re-authentication before you depend on it.
  • One username cannot be logged into two trading applications at once. Running separate gateways — paper and live, or one per strategy — means creating additional usernames on the account.

Because the session can be down, the adapter must report status honestly:

import type { TradingConnectionStatus, TradingEvent } from '@tradescript/pro/sdk';

export function publishSessionStatus(
status: TradingConnectionStatus,
emit: (event: TradingEvent) => void,
): void {
emit({
type: 'connection-status',
status,
info: { status, connectionType: 'hybrid', message: 'IB Gateway session' },
});
}

A chart that shows disconnected during the Sunday restart window is correct. A chart that keeps showing the last known positions as if they were live is not.

Mapping the API

IBKR call or callbackSDK method
reqHistoricalDataloadBars
reqRealTimeBars (5-second bars)subscribeRealTimeBars
reqMktDatagetQuotes, subscribeQuotes
reqMktDepthgetDepth, subscribeDepth
reqTickByTickDatagetTimeAndSales, subscribeTimeAndSales
reqMatchingSymbolssearchSymbols
reqContractDetailsresolveSymbol, getInstrumentDetails
placeOrderplaceOrder
cancelOrdercancelOrder
openOrder, orderStatusorders events
reqPositions, positionpositions events
reqExecutions, execDetailsexecutions events
reqAccountSummaryaccounts in TradingState

IBKR's real-time bars are 5-second bars. For a 1-minute chart, aggregate them in the bridge and emit one bar per minute boundary — the chart replaces a bar whose time matches the current one, so emitting the developing minute repeatedly is correct and cheap.

Pacing and limits

These are the constraints that make a server-side cache mandatory rather than optional. They are counted against the IBKR session, so every browser tab shares one budget.

LimitValue
Simultaneous open historical data requests50
Identical historical requestsNot within 15 seconds
Historical requests for the same contract, exchange, and tick typeFewer than 6 within 2 seconds
Historical requests overallNo more than 60 in any 10-minute period
BID_ASK historical requestsCount double
Bars of 30 seconds or lessUnavailable older than six months
Concurrent streaming market data lines100 by default
Client-to-TWS message rate50 messages per second

Sixty historical requests per ten minutes is roughly one request every ten seconds. A user scrolling back through a chart can exceed that alone. The gateway must therefore:

  • Serve closed bars from cache and never re-request a range it already holds.
  • Queue and rate-limit history requests centrally, with one queue per session.
  • Deduplicate identical in-flight requests instead of issuing them twice.
  • Track the 100-line streaming budget and release lines when charts unsubscribe.

Surface pacing rejections as typed errors. A pacing violation that returns an empty bar array teaches the chart that no data exists for that range, and it will stop paging.

The broker adapter

Order placement is the same shape as any other broker: the chart submits an intent, your server revalidates, IBKR decides.

import type {
TradingBrokerAdapter,
TradingEvent,
TradingState,
} from '@tradescript/pro/sdk';

const listeners = new Set<(event: TradingEvent) => void>();

export const broker: TradingBrokerAdapter = {
executionEnvironment: 'paper',

async getState(): Promise<TradingState> {
const response = await fetch('/api/ibkr/state');
if (!response.ok) throw new Error(`State unavailable: ${response.status}`);
return response.json() as Promise<TradingState>;
},

subscribe(callback) {
listeners.add(callback);
return () => listeners.delete(callback);
},

async placeOrder(draft, context) {
const response = await fetch('/api/ibkr/orders', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ draft, accountId: context.accountId }),
});
if (!response.ok) {
return { accepted: false, status: 'rejected', message: await response.text() };
}
return { accepted: true, status: 'submitted' };
},
};

Map orderStatus and execDetails callbacks into orders and executions events through the subscribers, exactly as in Broker integration. The chart never invents a fill; it shows what the broker reported.

Checklist

  • Session host runs unattended, with auto-restart and lock disabled, and someone owns the weekly re-authentication.
  • Paper and live use different ports, different usernames, and a declared executionEnvironment.
  • Read-only API stays on for data-only deployments.
  • History is cached server-side; the pacing budget is enforced by a central queue.
  • Streaming lines are counted and released on unsubscribe.
  • Disconnects are published as connection-status, not hidden behind stale state.
  • Market data subscriptions are entitled on the IBKR account for every instrument the chart offers.

Next steps