Skip to main content

Trading terminal

Complete trading terminal with chart, watchlist, order entry, depth, tape, and account surfaces
The composed workspace places chart, watchlist, tickets, account activity, tape, and depth inside one widget panel arrangement.

sdk.tradingTerminal.mount() is the framework-neutral composition root for a complete trading workspace. It is the same surface used by the React wrapper. Supply raw market data or one host-owned composite adapter. Add a broker or trading controller only when the terminal includes order, account, or option ticket surfaces.

Mount the terminal

Give the host an explicit height. The terminal and its widget panel arrangement fill the mount; the panel container's defaultHeight applies only when vertical resizing is enabled.

terminal.ts
import type {
MarketDataFeed,
SdkSymbolInfo,
TradeScriptSdkProducts,
TradingBrokerAdapter,
TradingTerminalApi,
} from '@tradescript/pro/sdk';

declare const sdk: TradeScriptSdkProducts;
declare const datafeed: MarketDataFeed;
declare const broker: TradingBrokerAdapter;

const symbol: SdkSymbolInfo = {
ticker: 'AAPL',
canonicalSymbol: 'provider:AAPL',
exchange: 'NASDAQ',
type: 'stock',
};

const host = document.querySelector<HTMLElement>('#terminal');
if (host === null) throw new Error('Missing #terminal mount');
host.style.height = '720px';

let terminal: TradingTerminalApi | undefined;
let connection: Promise<void> = Promise.resolve();

const mounted = sdk.tradingTerminal.mount({
mount: host,
datafeed,
broker,
symbol,
interval: '1D',
panels: { orderTicket: true, accountPanel: true },
onTerminalReady(api) {
terminal = api;
if (!api.trading) throw new Error('Trading terminal did not create its trading controller');
const trading = api.trading;
connection = (async () => {
await trading.connect();
await trading.getState();
})();
void connection.catch((error: unknown) => {
console.error('Trading connection failed', error);
});
},
});

export async function removeTerminal(): Promise<void> {
try {
await connection;
} catch {
// The connection error was already reported above.
}
try {
await terminal?.trading?.disconnect();
} finally {
terminal = undefined;
mounted.destroy();
}
}

The terminal creates the composite adapter and trading controller in this shape. Call removeTerminal() before the route removes the mount so the broker session can close before synchronous surface destruction.

React alternative

BrokerTradingTerminal accepts the same neutral surface options plus React render extensions. Keep asynchronous broker shutdown in the route action that causes unmount, not in a React effect cleanup function.

TerminalRoute.tsx
import { useRef } from 'react';
import type {
MarketDataFeed,
SdkSymbolInfo,
TradeScriptSdkProducts,
TradingBrokerAdapter,
TradingTerminalApi,
} from '@tradescript/pro/sdk';
import { BrokerTradingTerminal } from '@tradescript/pro/react/widgets/trading-terminal';

declare const sdk: TradeScriptSdkProducts;
declare const datafeed: MarketDataFeed;
declare const broker: TradingBrokerAdapter;
declare const symbol: SdkSymbolInfo;

export function TerminalRoute({ onLeave }: { onLeave: () => void }) {
const terminal = useRef<TradingTerminalApi | undefined>(undefined);
const connection = useRef<Promise<void>>(Promise.resolve());

async function leave(): Promise<void> {
try {
await connection.current;
} catch {
// The connection error was already reported below.
}
try {
await terminal.current?.trading?.disconnect();
} finally {
onLeave();
}
}

return (
<>
<BrokerTradingTerminal
sdk={sdk}
datafeed={datafeed}
broker={broker}
symbol={symbol}
interval="1D"
style={{ height: 720 }}
onTerminalReady={(api) => {
terminal.current = api;
if (!api.trading) throw new Error('Trading terminal did not create its trading controller');
const trading = api.trading;
connection.current = (async () => {
await trading.connect();
await trading.getState();
})();
void connection.current.catch((error: unknown) => {
console.error('Trading connection failed', error);
});
}}
/>
<button type="button" onClick={() => void leave()}>Leave terminal</button>
</>
);
}

Choose one authority shape

The authority inputs are exclusive. adapter cannot be combined with datafeed, broker, trading, or symbolLink; without an adapter, never supply both a broker and a controller.

SupplyController and adapter ownerShutdown responsibility
datafeedTerminal creates a market-data-only adapterDestroy the mount
datafeed + brokerTerminal creates bothDisconnect the returned controller, then destroy the mount
datafeed + tradingHost owns the controller; terminal creates the composite adapterDestroy the mount; disconnect the controller only after its last host consumer
adapterHost owns the adapter and its injected authoritiesDestroy every mount first, then destroy or disconnect host authorities at their composition boundary

For a market-data-only terminal, explicitly disable the trading surfaces that default on: orderTicket, accountPanel, and accountSummary. The depth ladder remains usable as a read-only market-depth surface, and the option chain remains usable for browsing and contract selection. Their execution controls are unavailable until a trading authority is supplied. In this composition, TradingTerminalApi.trading is undefined.

Destroying a terminal-created adapter releases controllers it created. It does not destroy a host-injected trading controller. Similarly, destroying a host-owned adapter releases only the authorities that adapter itself created.

Panels and status regions

The chart and optional work surfaces below are Dockview panels. They are movable and belong to the saved panel arrangement.

Dockview panelDefaultVisibility overrideConfiguration
ChartAlways presentchartOptions
Order ticketOnpanels.orderTicketorderTicketOptions
Account ManagerOnpanels.accountPanelaccountPanelOptions
WatchlistOn when watchlistAdapter is suppliedpanels.watchlistwatchlistAdapter, watchlistOptions
Depth ladderOffpanels.depthLadderdepthLadderOptions
Market depthOffpanels.marketDepthmarketDepthOptions
Time & SalesOffpanels.timeAndSalestimeAndSalesOptions
Option chainOffpanels.optionChainoptionChainOptions
Option ticketOffpanels.optionTicketoptionTicketOptions
Agent chatOffpanels.agentChatagentChatOptions
Agent ConsoleOffpanels.agentConsoleagentConsoleOptions

Three terminal regions sit outside Dockview and are not movable or serialized with the panel arrangement.

Agent Chat and Agent Console are independent Dockview panels. Enable Chat only, Console only, both, or neither with their respective panel flags. Explicit panels.agentChat: true mounts Chat even when its provider has not been configured; in that setup state its model selector, composer, and send action are disabled, and it performs no model discovery, network request, or fallback egress. Agent Console requires the customer-owned controller supplied through agentConsoleOptions.

Agent panelspanelsAdditional configuration
Neither{ agentChat: false, agentConsole: false }None
Chat only{ agentChat: true, agentConsole: false }agentChatOptions.provider enables model chat; omit it for setup state
Console only{ agentChat: false, agentConsole: true }agentConsoleOptions.controller
Both{ agentChat: true, agentConsole: true }Customer Chat provider when ready, plus the Console controller

The fresh complete Agent Workspace uses this topology:

Top rowWatchlistChartOrder ticketAgent Console
Bottom rowAccountAgent ChatMarket depthTime & Sales

Chat and Account are distinct groups. Agent Console occupies the top-right cell with Time & Sales directly below it. Partial configurations keep Chat as a normal independent panel; if Account is absent, Chat falls back to its own 260px bottom split. defaultLayout and restored panel state take precedence over the fresh topology.

The SDK receives no model credentials. See Agent chat to configure the customer provider, access picker, and page-bound TradeScript tools, and Agent Console to configure the customer-owned MCP connection controller.

RegionDefaultConfiguration
Account summary in the status barOnpanels.accountSummary, accountSummaryOptions
Market-session statusOffstatusBarOptions.session
Account action dialogOpens on an account action requestaccountActionDialogOptions

Required capabilities

Enable only surfaces supported by the supplied authorities.

SurfaceMarket-data contractTrading contract
ChartloadBars; realtime methods when live updates are enabled
Order ticketgetQuotes and subscribeQuotes for a live quotegetState, placeOrder, and getTradingSymbolInfo for symbol-specific rules; preview and modify methods for those enabled flows
Account summarygetState with account balance fields
Account ManagergetState; getAccountManagerInfo plus matching table methods for broker-declared custom pages
WatchlistA watchlistAdapter, plus quote snapshot and stream methods
Depth ladder and market depthDeclared depth support with subscribeDepth; getDepth supplies the initial snapshotOptional for the depth ladder; order entry needs symbol rules and placement operations
Time & SalesgetTimeAndSales and subscribeTimeAndSales
Option chain and option ticketgetOptionContracts and getOptionQuotesOptional for the option chain; the option ticket needs symbol rules, preview, and placement; add resolveOptionContract when the venue requires broker-native contract identity

Operation support comes from the real feed and broker contracts. A disabled surface should not be used as a fallback for a missing capability.

Configure children

Terminal child options are typed subsets of the standalone surface props. The terminal removes authority fields such as market data, trading, symbol links, theme, controllers, and hotkey ownership, then injects its own values. The React wrapper additionally adapts React render extensions to retained DOM extensions.

ConfiguredTerminal.tsx
import type {
SdkSymbolInfo,
TradeScriptAdapterApi,
TradeScriptSdkProducts,
TradingControllerApi,
} from '@tradescript/pro/sdk';
import { BrokerTradingTerminal } from '@tradescript/pro/react/widgets/trading-terminal';

declare const sdk: TradeScriptSdkProducts;
declare const adapter: TradeScriptAdapterApi & {
readonly trading: TradingControllerApi;
};
declare const activeSymbol: SdkSymbolInfo;
declare const pinnedTapeSymbol: SdkSymbolInfo;

export function ConfiguredTerminal() {
return (
<BrokerTradingTerminal
sdk={sdk}
adapter={adapter}
symbol={activeSymbol}
style={{ height: 720 }}
panels={{
orderTicket: true,
accountPanel: true,
marketDepth: true,
timeAndSales: true,
}}
labels={{
accountPanelTitle: 'Portfolio',
marketDepthTitle: 'Order book',
}}
orderTicketOptions={{
defaultQuantity: 10,
directionSelector: { layout: 'split', order: 'buy-sell' },
}}
accountPanelOptions={{ defaultPageId: 'positions' }}
marketDepthOptions={{ levels: 20 }}
timeAndSalesOptions={{ symbol: pinnedTapeSymbol, maxRows: 200 }}
statusBarOptions={{ session: true }}
/>
);
}

The objects may contain callbacks, formatters, styles, and framework-specific render extensions. They are runtime configuration, not serializable widget panel arrangement state.

Active symbol and pinning

The top-level symbol seeds the active symbol and can control later updates. After mount, TradingTerminalApi.setSymbol() and the composite adapter's symbolLink update the same authority. Preserve exact SdkSymbolInfo identity instead of reconstructing a ticker-only object.

The chart, order ticket, and depth ladder follow the active terminal symbol. Watchlist and Account Manager symbol selections update the same link. Market depth, Time & Sales, option chain, and option ticket follow by default, but their option objects may each provide a symbol to pin that panel. interval belongs to the chart and is not a cross-panel authority.

Save the panel arrangement

Configure panelStorage through Widget layout storage. Load the saved WidgetLayoutState before mounting and pass it as defaultLayout. Save later changes with onLayoutChange; do not wait for onTerminalReady and then replace the terminal's initial topology.

persistentTerminal.ts
import {
type ChartStorageAdapter,
type SdkSymbolInfo,
type TradeScriptAdapterApi,
type TradeScriptSdkProducts,
type TradingControllerApi,
type WidgetLayoutStorageAdapter,
} from '@tradescript/pro/sdk';

declare const sdk: TradeScriptSdkProducts;
declare const adapter: TradeScriptAdapterApi & {
readonly trading: TradingControllerApi;
};
declare const symbol: SdkSymbolInfo;
declare const chartStorage: ChartStorageAdapter;
declare const panelStorage: WidgetLayoutStorageAdapter;
const panelContext = {
arrangementId: 'primary-terminal-panels',
userId: 'user-123',
workspaceId: 'main-desk',
};
const defaultLayout = (await panelStorage.load(panelContext)) ?? undefined;
let panelSaveQueue = Promise.resolve();

const host = document.querySelector<HTMLElement>('#persistent-terminal');
if (host === null) throw new Error('Missing #persistent-terminal mount');
host.style.height = '720px';

const mounted = sdk.tradingTerminal.mount({
mount: host,
adapter,
symbol,
terminalId: 'primary-terminal',
defaultLayout,
onLayoutChange(state) {
panelSaveQueue = panelSaveQueue
.then(() => panelStorage.save({ ...panelContext, state }))
.catch((error: unknown) => {
console.error('Panel arrangement save failed', error);
});
},
chartOptions: {
chartId: 'primary-chart',
layoutId: 'primary-chart-layout',
storage: chartStorage,
loadLastChart: true,
},
});

export async function removePersistentTerminal(): Promise<void> {
await panelSaveQueue;
mounted.destroy();
}

Panel arrangement and chart layouts are independent:

  • WidgetLayoutState stores Dockview topology, active panels, and optional resizable container height.
  • Chart storage stores symbols, intervals, indicators, drawings, and chart workspace state under stable chart and chart-layout IDs.
  • A chart restores automatically only when chart storage is configured and chartOptions.loadLastChart is enabled. Its default is false.

Persist each state in its own key or storage authority. Restoring the panel arrangement must not overwrite chart layouts, and chart restoration must not be described as part of Dockview restoration.

See Widget layout storage for the standalone WidgetLayoutContainer flow, exact REST route, stable widget ids, and failure behavior. See Widget panel arrangements for the state contract and restore lifecycle.

Teardown

Surface destruction is synchronous; broker disconnect is asynchronous. For a terminal-created trading controller, await disconnect() before destroying the mount. For injected controllers and adapters, release them at the host composition boundary only after their last surface has unmounted.

Verify the integration

  • Changing the active symbol updates each unpinned linked panel once.
  • A pinned depth, tape, or options panel retains its exact configured symbol.
  • When trading surfaces are enabled, orders, positions, and executions come from the same trading controller.
  • Disabled panels make no unsupported market-data or trading calls.
  • Panel restoration and chart-layout restoration remain independent.
  • Route teardown closes broker sessions and releases surface subscriptions.

Next steps