Manage orders
Overview
This is the second part of a two-part tutorial on integrating trading into your platform using the Broker API. At this point, the trading UI is set up. Users can view Account Manager, open Order Ticket, and click the Buy/Sell buttons. But while the interface is ready, no trading logic is in place yet.
This section introduces the core of order management: how to handle placing, modifying, and canceling orders. To make that possible, you need to store orders in your implementation and return them when requested. The library itself doesn’t keep any trading state. It expects your code to manage and update order data, then notify the UI through the appropriate methods.
1. Store and return orders
To allow the library to display orders — both when the chart loads and during user interaction — you need to store this data locally in your Broker API implementation.
The library expects your implementation to return existing orders via the orders
method.
This method returns initial data about the user’s trading history, before any new actions are taken.
Even in a mock setup, you’ll need to store orders locally and provide access to them when the library asks for it.
// Stores orders indexed by ID
private readonly _orderById: SimpleMap<Order> = {};
// Returns all existing orders
private _orders(): Order[] {
return Object.values(this._orderById);
}
orders(): Promise<Order[]> {
return Promise.resolve(this._orders());
}
2. Return positions and executions
The library also calls positions
and executions
on startup.
While this tutorial doesn’t use them, you should define these methods to avoid errors.
positions(): Promise<Position[]> {
return Promise.resolve([]);
}
executions(symbol: string): Promise<Execution[]> {
return Promise.resolve([]);
}
You can later extend this logic using the same approach — store items in memory, and return them when needed.
Want to support positions?
Check out our Positions guide for implementation tips.
3. Place orders
To support order placement from the UI, implement the placeOrder
method.
The library calls this method when the user submits a new order, for example, by clicking the Buy/Sell button in the Order Ticket.
// A counter to generate unique IDs for new orders
private _orderIdCounter: number = 1;
async placeOrder(order: PreOrder): Promise<PlaceOrderResult> {
const newOrder: PlacedOrder = {
id: `${this._orderIdCounter++}`, // ID must be unique
limitPrice: order.limitPrice,
qty: order.qty,
side: order.side || Side.Buy,
status: OrderStatus.Working,
stopPrice: order.stopPrice,
symbol: order.symbol,
type: order.type || OrderType.Market,
takeProfit: order.takeProfit,
stopLoss: order.stopLoss,
}
// Save the new order in local storage
this._orderById[newOrder.id] = newOrder;
// Notify the library about the new order so it appears in the UI
this._host.orderUpdate(newOrder);
return {}
}
When this method is triggered:
- Create an order on your backend using the
PreOrder
data provided by the library. - Assign a unique ID and store the order.
- Each order must have a unique ID so the library can track, update, and manage it properly.
- In this sample, we use a simple counter
_orderIdCounter
that increments with every new order. This approach is subjective. You're free to use any ID generation strategy that fits your backend. However, uniqueness is mandatory: the same ID may later be reused to reference the corresponding position once the order is filled.
- Notify the library by calling
orderUpdate
to reflect the new order in the UI.- The library waits up to 10 seconds for updates through the Trading Host. If no update is received, the library will return a timeout issue.
- All trading updates — including orders, positions, or executions — must be delivered via the Trading Host methods such as
orderUpdate
,executionUpdate
, andpositionUpdate
. The library does not manage internal state. It only reflects what your implementation sends.
Once this is done, users will be able to place orders from the UI and see them immediately appear on the chart and in the Account Manager.
Want the breakdown for the order flow?
For a step-by-step explanation of the entire order flow, from creating an order to creating a position, see the dedicated article How components work together.
4. Modify orders
Implement modifyOrder
to let users modify active orders.
Without it, the library will display a toast message stating that it failed to modify the order.
Once the method is implemented, order modification becomes enabled in the UI. However, market orders cannot be modified by design.
async modifyOrder(order: Order, confirmId?: string | undefined): Promise<void> {
// Look up the original order using its ID
const originalOrder = this._orderById[order.id];
// If no such order exists, do nothing
if (originalOrder === undefined) {
return;
}
const handler = () => {
// Apply the modification, for example, updating the quantity
originalOrder.qty = order.qty;
// Notify the library about the order update so it appears in the UI
this._host.orderUpdate(originalOrder);
return Promise.resolve();
}
return handler();
}
When this method is triggered:
- Update the order details on your backend.
- After the update is complete, call
orderUpdate
to reflect the changes in the UI. The library waits up to 10 seconds for updates through the Trading Host. If no update is received, the library will return a timeout issue.
5. Cancel orders
To support canceling orders from the Account Manager, implement the cancelOrder
method.
cancelOrder(orderId: string): Promise<void> {
// Look up the original order using its ID
const originalOrder = this._orderById[orderId];
const handler = () => {
// Change order status to canceled
originalOrder.status = OrderStatus.Canceled;
// Notify the library about the order update so it appears in the UI
this._host.orderUpdate(originalOrder);
return Promise.resolve();
}
return handler();
}
When this method is triggered:
- Cancel the order and update its status to canceled on your backend.
- After the update is complete, call
orderUpdate
to reflect the changes in the UI. The library waits up to 10 seconds for updates through the Trading Host. If no update is received, the library will return a timeout issue.
Want to understand how order statuses work?
Orders move through different statuses after placement. These can be grouped into:
- Transitional statuses: placing, working, inactive.
- Final statuses: filled, canceled, rejected.
Understanding these is key to accurate order status management. For a step-by-step explanation of the entire order flow, including status changes, see the dedicated article How components work together.
Full implementation example
You’ve now implemented all required methods the library expects from the Broker API implementation. This covers placing, modifying, and canceling orders.
Click to reveal broker.ts
broker.ts
import {
AccountId,
AccountManagerInfo,
AccountMetainfo,
ActionMetaInfo,
ConnectionStatus,
DefaultContextMenuActionsParams,
Execution,
IBrokerConnectionAdapterHost,
IBrokerTerminal,
InstrumentInfo,
IsTradableResult,
Order,
OrderStatus,
OrderType,
PlacedOrder,
PlaceOrderResult,
Position,
PreOrder,
Side,
StandardFormatterName,
TradeContext,
} from '../node_modules/trading_platform/charting_library/charting_library';;
interface SimpleMap<TValue> {
[key: string]: TValue;
}
import { IDatafeedQuotesApi } from 'trading_platform';
export const enum CommonAccountManagerColumnId {
/** You should use this column ID if you want to fix the column in the positions and orders tables. */
Symbol = 'symbol',
}
export abstract class AbstractBrokerMinimal implements IBrokerTerminal {
protected readonly _host: IBrokerConnectionAdapterHost;
protected readonly _quotesProvider: IDatafeedQuotesApi;
constructor(host: IBrokerConnectionAdapterHost, quotesProvider: IDatafeedQuotesApi) {
this._host = host;
this._quotesProvider = quotesProvider;
}
abstract orders(): Promise<Order[]>;
abstract positions(): Promise<Position[]>;
abstract modifyOrder(order: Order, confirmId?: string): Promise<void>;
abstract cancelOrder(orderId: string): Promise<void>;
abstract chartContextMenuActions(
context: TradeContext,
options?: DefaultContextMenuActionsParams
): Promise<ActionMetaInfo[]>;
abstract isTradable(symbol: string): Promise<boolean | IsTradableResult>;
abstract connectionStatus(): ConnectionStatus;
abstract executions(symbol: string): Promise<Execution[]>;
abstract symbolInfo(symbol: string): Promise<InstrumentInfo>;
abstract accountManagerInfo(): AccountManagerInfo;
abstract accountsMetainfo(): Promise<AccountMetainfo[]>;
abstract currentAccount(): AccountId;
abstract placeOrder(order: PreOrder): Promise<PlaceOrderResult>;
}
export class BrokerMinimal extends AbstractBrokerMinimal {
/** Initializes an empty map to store orders indexed by their IDs */
private readonly _orderById: SimpleMap<Order> = {};
/** A counter to generate unique IDs for new orders */
private _orderIdCounter: number = 1;
/** Retrieves all orders stored in the `_orderById` map and returns an array containing all orders */
private _orders(): Order[] {
return Object.values(this._orderById);
}
orders(): Promise<Order[]> {
return Promise.resolve(this._orders());
}
positions(): Promise<Position[]> {
return Promise.resolve([]);
}
async modifyOrder(order: Order, confirmId?: string | undefined): Promise<void> {
// Look up the original order using its ID
const originalOrder = this._orderById[order.id];
// If no such order exists, do nothing
if (originalOrder === undefined) {
return;
}
const handler = () => {
// Apply the modification, for example, updating the quantity
originalOrder.qty = order.qty;
// Notify the library about the order update so it appears in the UI
this._host.orderUpdate(originalOrder);
return Promise.resolve();
}
return handler();
}
cancelOrder(orderId: string): Promise<void> {
// Look up the original order using its ID
const order = this._orderById[orderId];
const handler = () => {
// Change order status to canceled
order.status = OrderStatus.Canceled;
// Notify the library about the order update so it appears in the UI
this._host.orderUpdate(order);
return Promise.resolve();
}
return handler();
}
chartContextMenuActions(
context: TradeContext,
options?: DefaultContextMenuActionsParams | undefined
): Promise<ActionMetaInfo[]> {
return this._host.defaultContextMenuActions(context);
}
async isTradable(symbol: string): Promise<boolean | IsTradableResult> {
return Promise.resolve(true);
}
connectionStatus(): ConnectionStatus {
return ConnectionStatus.Connected;
}
executions(symbol: string): Promise<Execution[]> {
return Promise.resolve([]);
}
async symbolInfo(symbol: string): Promise<InstrumentInfo> {
const mintick = await this._host.getSymbolMinTick(symbol);
const pipSize = mintick; // Pip size can differ from minTick
const accountCurrencyRate = 1; // Account currency rate
const pointValue = 1; // USD value of 1 point of price
return {
qty: {
min: 1,
max: 1e12,
step: 1,
},
pipValue: pipSize * pointValue * accountCurrencyRate || 1,
pipSize: pipSize,
minTick: mintick,
description: '',
};
}
accountManagerInfo(): AccountManagerInfo {
return {
accountTitle: 'Trading Sample',
summary: [],
orderColumns: [
{
label: 'Symbol',
formatter: StandardFormatterName.Symbol,
id: CommonAccountManagerColumnId.Symbol,
dataFields: ['symbol', 'symbol', 'message'],
},
{
label: 'Side',
id: 'side',
dataFields: ['side'],
formatter: StandardFormatterName.Side,
},
{
label: 'Type',
id: 'type',
dataFields: ['type', 'parentId', 'stopType'],
formatter: StandardFormatterName.Type,
},
{
label: 'Qty',
alignment: 'right',
id: 'qty',
dataFields: ['qty'],
help: 'Size in lots',
formatter: StandardFormatterName.FormatQuantity,
},
{
label: 'Status',
id: 'status',
dataFields: ['status'],
formatter: StandardFormatterName.Status,
},
{
label: 'Order ID',
id: 'id',
dataFields: ['id'],
},
],
positionColumns: [
{
label: 'Symbol',
formatter: StandardFormatterName.Symbol,
id: CommonAccountManagerColumnId.Symbol,
dataFields: ['symbol', 'symbol', 'message'],
},
{
label: 'Side',
id: 'side',
dataFields: ['side'],
formatter: StandardFormatterName.Side,
},
{
label: 'Qty',
alignment: 'right',
id: 'qty',
dataFields: ['qty'],
help: 'Size in lots',
formatter: StandardFormatterName.FormatQuantity,
},
],
pages: [],
};
}
async accountsMetainfo(): Promise<AccountMetainfo[]> {
return [
{
id: '1' as AccountId,
name: 'Test account',
},
];
}
currentAccount(): AccountId {
return '1' as AccountId;
}
async placeOrder(order: PreOrder): Promise<PlaceOrderResult> {
const newOrder: PlacedOrder = {
id: `${this._orderIdCounter++}`, // ID must be unique
limitPrice: order.limitPrice,
qty: order.qty,
side: order.side || Side.Buy,
status: OrderStatus.Working,
stopPrice: order.stopPrice,
symbol: order.symbol,
type: order.type || OrderType.Market,
takeProfit: order.takeProfit,
stopLoss: order.stopLoss,
}
// Save the new order in local storage
this._orderById[newOrder.id] = newOrder;
// Notify the library about the new order so it appears in the UI
this._host.orderUpdate(newOrder);
return {}
}
}
Similarly, you can extend this logic to support positions and executions: track their uniqueness, update their state in real time, and send timely updates to the library when anything changes. Let’s explore what you can do next.