Skip to main content

Account Manager

Account Manager showing an account summary, page tabs, and a Positions table
Metadata declares the available pages and columns; standard rows come from TradingState while custom tables use broker table methods.

The framework-neutral accountPanel surface renders account summary and activity from a shared TradingControllerApi. Adapter metadata declares the pages, columns, formatters, and actions. Standard pages project broker-owned TradingState; custom pages can load and subscribe to their own rows.

Mount the panel

Mount the framework-neutral surface from the authorized SDK products. The host owns the shared trading controller and the returned surface mount.

account-manager.ts
import type {
TradeScriptSdkProducts,
TradingControllerApi,
} from '@tradescript/pro/sdk';

declare const sdk: TradeScriptSdkProducts;
declare const trading: TradingControllerApi;

export function mountAccountManager(element: HTMLElement): () => void {
const panel = sdk.accountPanel.mount({
mount: element,
trading,
});

return () => panel.destroy();
}

React hosts can use the thin adapter instead:

AccountActivity.tsx
import type {
TradeScriptSdkProducts,
TradingControllerApi,
} from '@tradescript/pro/sdk';
import { TradingAccountPanel } from '@tradescript/pro/react/widgets/account-panel';

declare const sdk: TradeScriptSdkProducts;
declare const trading: TradingControllerApi;

export function AccountActivity() {
return <TradingAccountPanel sdk={sdk} controller={trading} />;
}

Reuse the controller already connected to the chart or order ticket. Destroying the surface mount, or unmounting the React component, releases only panel-owned UI and subscriptions; it does not disconnect or destroy the controller.

Standard pages

PageData sourceEmpty state
PositionsOpen, non-flat rows from TradingState.positionsNo open positions
Individual positionsOpen rows from TradingState.individualPositionsNo individual positions
OrdersTradingState.ordersNo working orders
HistoryClosed TradingState.orders, ordersHistory, and getOrdersHistoryNo historical orders
ExecutionsTradingState.executionsNo executions
BalancesTradingState.cryptoBalancesNo balances
TransactionsTradingState.cashTransactionsNo transactions
NotificationsTradingState.messagesNo notifications

Metadata must supply a standard page or its column definition before pageVisibility can show it. Visibility does not create a missing page. When supportsPositionNetting is true and both position definitions exist, the panel folds Individual positions into a Net/Individual control on the Positions page instead of rendering a second tab.

The panel resolves getFeatures and getAccountManagerInfo for the mounted controller. While they load, it shows an explicit loading state. Missing metadata or supportsAccountPanel: false produces an unsupported state. Account events update rows and summary values, but do not refetch metadata; declare the complete page model needed across the controller's accounts.

Declare pages and actions

Use the authorization-bound helper on the SDK trading facade, then return its metadata from the adapter. This safe starting point disables mutation actions that the example adapter does not implement.

broker.ts
import type {
TradeScriptSdkProducts,
TradingBrokerAdapter,
TradingState,
} from '@tradescript/pro/sdk';

declare const sdk: TradeScriptSdkProducts;
declare const state: TradingState;
declare const submitOrder: TradingBrokerAdapter['placeOrder'];

const accountManagerInfo = sdk.trading.createDefaultTradingAccountManagerInfo({
supportsOrders: true,
supportsPositions: true,
supportsOrderHistory: true,
supportsExecutions: true,
supportsOrderCancellation: false,
supportsClosePosition: false,
supportsIndividualPositionClose: false,
supportsIndividualPositionBrackets: false,
});

export const broker: TradingBrokerAdapter = {
getState: async () => state,
placeOrder: submitOrder,
getFeatures: async () => ({ supportsAccountPanel: true }),
getAccountManagerInfo: () => accountManagerInfo,
};

supportsOrderCancellation and supportsClosePosition default to enabled when omitted. Individual-position Close and Brackets actions also default to enabled when that page is included. Set those flags to false unless the corresponding adapter mutation or host dialog flow exists. Batch Cancel and Reverse are added only when explicitly enabled.

TradingAccountManagerInfo can declare summary fields, account-menu actions, standard or custom pages, multiple tables, sorting, status filters, row and table actions, visibility, and named or custom formatters. Import the TradingAccountManager* types from @tradescript/pro/sdk instead of copying the metadata shape.

Accounts and panel state

The account selector reads TradingState.accounts and selects activeAccountId (falling back to an account marked isActive, then the first account). To make another account selectable, implement setActiveAccount on the adapter. After that method resolves, the controller publishes the new active account to the panel.

Standard rows are filtered to the active account where their contracts carry accountId. Every custom-table request also receives the active accountId; switching accounts reloads its rows and replaces its subscriptions.

Panel view state has explicit host seams:

ConcernSurface options
Initial pagedefaultPageId
Controlled sortingsortState, onSortStateChange
Controlled columns and summary fieldsvisibilityState, onVisibilityStateChange
Local visibility persistencepersistStateKey
CSV exportonExport (otherwise the panel downloads the file)
Semantic panel controlaccountPanelController, onReady

Custom tables

Declare source: 'custom' and omit inline rows when the adapter owns the data. Paginated and incremental rows require a stable id; stable ids are also recommended for snapshot rows.

activity-table.ts
import type {
TradingAccountManagerColumn,
TradingAccountManagerIdentifiedRow,
TradingAccountManagerInfo,
TradingAccountManagerTable,
TradingAccountManagerTableUpdate,
TradingBrokerAdapter,
TradingState,
} from '@tradescript/pro/sdk';

declare const state: TradingState;
declare const submitOrder: TradingBrokerAdapter['placeOrder'];

const columns: TradingAccountManagerColumn[] = [
{ id: 'time', label: 'Time', dataFields: ['time'], formatter: 'datetime' },
{ id: 'event', label: 'Event', dataFields: ['event'] },
];

const table: TradingAccountManagerTable = {
id: 'activity',
title: 'Activity',
source: 'custom',
updateMode: 'incremental',
initialSorting: { columnId: 'time', direction: 'desc' },
columns,
};

const info: TradingAccountManagerInfo = {
pages: [{ id: 'activity', title: 'Activity', tables: [table] }],
};

const rows: TradingAccountManagerIdentifiedRow[] = [
{ id: 'connected', time: Date.now(), event: 'Connected' },
];
const listeners = new Set<(update: TradingAccountManagerTableUpdate) => void>();

export const broker: TradingBrokerAdapter = {
getState: async () => state,
placeOrder: submitOrder,
getFeatures: async () => ({ supportsAccountPanel: true }),
getAccountManagerInfo: () => info,
getAccountManagerTableRows: async () => rows,
subscribeAccountManagerTableUpdates(_request, callback) {
listeners.add(callback);
return () => { listeners.delete(callback); };
},
};

The table's declared mode selects its live contract:

ModeInitial readLive updates
Snapshot (default)getAccountManagerTableRowssubscribeAccountManagerTableRows replaces the complete row list
IncrementalgetAccountManagerTableRowssubscribeAccountManagerTableUpdates sends explicit replace, upsert, or delete operations
PaginatedgetAccountManagerTablePageThe subscription selected by updateMode, when supplied

Both subscription methods return Unsubscribe. The panel calls it on unmount and before replacing a table subscription after an account or definition change. It never infers update semantics from payload shape.

Cursor pagination

Declare pagination on a custom table, place that table in metadata, and expose getAccountManagerTablePage on the same adapter.

ledger-table.ts
import type {
TradingAccountManagerColumn,
TradingAccountManagerIdentifiedRow,
TradingAccountManagerInfo,
TradingAccountManagerTable,
TradingAccountManagerTableCursor,
TradingAccountManagerTablePage,
TradingAccountManagerTablePageRequest,
TradingBrokerAdapter,
TradingState,
} from '@tradescript/pro/sdk';

declare const state: TradingState;
declare const submitOrder: TradingBrokerAdapter['placeOrder'];
declare function loadLedger(request: {
accountId?: string;
cursor?: TradingAccountManagerTableCursor;
limit?: number;
}): Promise<{
rows: TradingAccountManagerIdentifiedRow[];
nextCursor?: TradingAccountManagerTableCursor;
}>;

const columns: TradingAccountManagerColumn[] = [
{ id: 'time', label: 'Time', dataFields: ['time'], formatter: 'datetime' },
{ id: 'event', label: 'Event', dataFields: ['event'] },
];

const ledgerTable: TradingAccountManagerTable = {
id: 'ledger',
title: 'Ledger',
source: 'custom',
columns,
pagination: { pageSize: 50, loadMoreLabel: 'Load more' },
};

const info: TradingAccountManagerInfo = {
pages: [{ id: 'ledger', title: 'Ledger', tables: [ledgerTable] }],
};

async function getAccountManagerTablePage(
request: TradingAccountManagerTablePageRequest,
): Promise<TradingAccountManagerTablePage> {
const result = await loadLedger({
accountId: request.accountId,
cursor: request.cursor,
limit: request.limit,
});

return result.nextCursor !== undefined
? { rows: result.rows, nextCursor: result.nextCursor, hasMore: true }
: { rows: result.rows, hasMore: false };
}

export const broker: TradingBrokerAdapter = {
getState: async () => state,
placeOrder: submitOrder,
getFeatures: async () => ({ supportsAccountPanel: true }),
getAccountManagerInfo: () => info,
getAccountManagerTablePage,
};

hasMore: true requires nextCursor. The controller returns the cursor unchanged on the next request; 0 and the empty string are valid cursors. The panel merges pages by stable row id so repeated ids update rather than duplicate rows.

Action routing and formatting

Action kind determines ownership:

Action kindOwner
cancel-order, cancel-orders, net close-positionMatching trading-controller mutation
reverse-positionreversePosition, or the custom-action lane when supplied
Individual close-positioncloseIndividualPosition, or the custom-action lane when supplied
modify-order, close-partial-position, edit-position-brackets, or requiresDialog: trueHost onTerminalActionRequest; the panel does not mutate
select-symbolHost onSymbolSelect
focus-executionPanel/chart focus; supply chart and optionally focus callbacks
customexecuteAccountManagerAction(actionId, { source, row })

Do not declare an action without its owner. Custom account actions receive the active account as row; custom row actions receive that row; custom table actions receive the table's current rows.

Prefer built-in formatter names for money, price, quantity, dates, status, side, and P&L. Price formatters use row metadata first, then exact symbol and account rules from TradingState.symbolInfos. Custom formatters provide text for export and may return an element for display.

Verify the integration

  • One emitted standard collection appears on the matching page.
  • Replacing that collection with an empty list shows the declared empty state.
  • Account selection changes the active account and reloads custom rows once.
  • Each visible action reaches its documented controller or host owner.
  • Unmounting and account switching release every table subscription.
  • Pagination forwards each cursor once without duplicating stable row ids.

Next steps