Trading API
chart.trading is a data-driven trade layer. Your app pushes exchange state - positions,
resting orders, bracket orders (TP/SL), and trade fills - and the chart renders broker-style
segmented pill groups ([badge][qty][label][✕]) and fill markers, with hover/dragging
states and a drag ghost built in. When the user drags an order or clicks the ✕ segment, the
chart emits a trading:* event for your app to relay to the exchange.
Your app ──setOrders()──▶ chart renders line + pill
│
user drags the order ▼
Your app ◀─'trading:order_modify'── chart ──▶ Exchange APIIt is created on first access (chart.trading), works with any data source, and has no
framework dependencies.
Live example
A long position pill, a resting limit order, TP/SL brackets, and two fill markers - all from
chart.trading. Drag the order lines; the ✕ boxes fire cancel/close events.
const chart = lib.createChart(el);
const bars = lib.generateBars(1700000000, 140, 3600);
chart.addSeries('candlestick').setData(bars);
const last = bars[bars.length - 1].close;
chart.trading.setPositions([
{ id: 'p1', side: 'long', entryPrice: last * 0.985, size: 1.5, pnlText: '+$1,240', pnlPercent: '+2.4%' },
]);
chart.trading.setOrders([
{ id: 'o1', type: 'limit', side: 'buy', price: last * 0.95, size: 1 },
{ id: 'tp', type: 'limit', side: 'sell', price: last * 1.04, size: 1.5, parentId: 'p1', bracketRole: 'tp' },
{ id: 'sl', type: 'stop', side: 'sell', price: last * 0.93, size: 1.5, parentId: 'p1', bracketRole: 'sl' },
]);
chart.trading.setTrades([
{ id: 'f1', side: 'buy', price: bars[40].close, size: 1, timestamp: bars[40].time * 1000, variant: 'chevron' },
{ id: 'f2', side: 'sell', price: bars[95].close, size: 1, timestamp: bars[95].time * 1000, variant: 'bubble' },
]);
chart.trading.on('trading:order_modify', function (e) {
// send an amend to your exchange with e.newPrice
});
chart.timeScale.fitContent(bars.length);
return chart;Quick start
import { createChart } from 'openalgo-charts';
const chart = createChart(el);
chart.addSeries('candlestick').setData(bars);
chart.trading.syncState({
positions: [{ id: 'pos_1', side: 'long', entryPrice: 50000, size: 1, pnlText: '+$100.00' }],
orders: [{ id: 'ord_1', type: 'limit', side: 'buy', price: 48000, size: 0.5 }],
trades: [{ id: 'fill_1', side: 'buy', price: 50000, size: 1, timestamp: Date.now() }],
});
chart.trading.on('trading:order_cancel', ({ orderId }) => exchange.cancel(orderId));Data models
TradingPosition
| Field | Type | Description |
|---|---|---|
id | string | unique id |
side | 'long' | 'short' | direction (line color) |
entryPrice | number | the pill is drawn at this price |
size | number | position size (shown on the pill) |
pnlText | string? | pre-formatted P&L shown on the pill (e.g. '+$100.00') |
pnlPercent | string? | pre-formatted P&L percent |
color | string? | per-position line color override |
readOnly | boolean? | hide the close (✕) button |
variant | 'standard' | 'line-only' | line-only renders just the line + axis tag (no pill) |
TradingOrder
| Field | Type | Description |
|---|---|---|
id | string | unique id |
type | 'limit' | 'stop' | 'stop_limit' | order type (shown on the pill) |
side | 'buy' | 'sell' | direction |
price | number | order price |
size | number | order size |
parentId | string? | link to a position/order (bracket child) |
bracketRole | 'tp' | 'sl' | take-profit / stop-loss (colors + TP/SL label) |
color / lineStyle / lineWidth | string? / 'solid'|'dashed'|'dotted' / number? | line styling |
readOnly | boolean? | hide the cancel (✕) button |
draggable | boolean? | allow drag-to-modify (default: !readOnly) |
variant | 'standard' | 'line-only' | line-only skips the pill |
TradingTrade
| Field | Type | Description |
|---|---|---|
id | string | unique id |
side | 'buy' | 'sell' | marker color |
price | number | marker price |
size | number | fill size (used for count VWAP) |
timestamp | number | execution time in milliseconds |
variant | 'chevron' | 'bubble' | 'count' | marker style (default chevron) |
color / label | string? | per-marker overrides |
API methods
Access via chart.trading.
| Method | Description |
|---|---|
setPositions(positions) / setOrders(orders) / setTrades(trades) | replace all of that kind |
syncState({ positions?, orders?, trades? }) | push several kinds in one call |
upsertOrder(order) | add or update one order |
removeOrder(id) | remove an order (and its bracket children) |
addTrade(trade) | add one fill |
updatePositionPnl(id, pnl, pnlText?, pnlPercent?) | refresh a position’s P&L pill without recreating the line |
getPositions() / getOrders() / getTrades() | current state |
clear() | remove everything |
setSettings(colors) / getSettings() | global trading colors |
on(event, cb) / off(event, cb) | subscribe / unsubscribe (on returns an unsubscribe fn) |
Events
Subscribe with chart.trading.on(event, cb).
| Event | Payload | Fires when |
|---|---|---|
trading:order_modify | { orderId, newPrice, previousPrice } | an order line is dragged and released |
trading:bracket_modify | { parentId, bracketRole, newPrice } | a TP/SL bracket line is dragged |
trading:order_cancel | { orderId } | the ✕ on an order pill is clicked |
trading:position_close | { positionId } | the ✕ on a position pill is clicked |
trading:order_click | { order } | an order pill body is clicked |
trading:position_click | { position } | a position pill body is clicked |
Settings
Global colors override the defaults and re-render existing lines/markers:
chart.trading.setSettings({
longColor: '#00C853', shortColor: '#AA00FF',
orderColor: '#7C4DFF', tpColor: '#00BCD4', slColor: '#FF1744',
buyColor: '#26a69a', sellColor: '#ef5350',
});Patterns
WebSocket sync - push a full snapshot in one frame:
ws.on('account', (a) => chart.trading.syncState({ positions: a.positions, orders: a.orders }));High-frequency P&L - updatePositionPnl only refreshes the pill text:
ws.on('mark', ({ price }) => {
const pnl = (price - entry) * size;
chart.trading.updatePositionPnl('pos_1', pnl, (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2));
});Confirm-then-render modify - keep the exchange as the source of truth:
chart.trading.on('trading:order_modify', async ({ orderId, newPrice }) => {
const ok = await exchange.amend(orderId, newPrice);
if (!ok) chart.trading.setOrders(currentOrders); // revert to server state
});Timestamps on trades are milliseconds (markers snap to the nearest bar). Positions and orders are drawn at their price with the pill anchored near the right (price) axis. For the lower-level order engine, depth ladder, and validation, see On-Chart Trading.