DocumentationTrading API

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][close]) and fill markers, with hover/dragging states and a drag ghost built in. When the user drags an order or clicks the close 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   v
Your app <--'trading:order_modify'-- chart  --> Exchange API

It is created on first access (chart.trading), works with any data source, and has no framework dependencies.

These events are host intents, not broker acknowledgements. Reconcile with the broker’s authoritative orders after a write, and enforce the host’s capability, replay and confirmation policy before sending it. For non-order thresholds and notifications, use the separate Alerts guide. The transactional alert-drag behavior in 2.4.7 belongs to AlertController.

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 close boxes fire cancel/close events.

live
Rendering live chart…
Position, resting order, TP/SL brackets and fill markers. Order lines support drag previews; cancel and position-close controls emit host intents.
View example code
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

FieldTypeDescription
idstringunique id
side'long' | 'short'direction (line color)
entryPricenumberthe pill is drawn at this price
sizenumberposition size (shown on the pill)
pnlTextstring?pre-formatted P&L shown on the pill (e.g. '+$100.00')
pnlPercentstring?pre-formatted P&L percent
colorstring?per-position line color override
readOnlyboolean?hide the close button
variant'standard' | 'line-only'line-only renders just the line + axis tag (no pill)

TradingOrder

FieldTypeDescription
idstringunique id
type'limit' | 'stop' | 'stop_limit'order type (shown on the pill)
side'buy' | 'sell'direction
pricenumberorder price
sizenumberorder size
parentIdstring?link to a position/order (bracket child)
bracketRole'tp' | 'sl'take-profit / stop-loss (colors + TP/SL label)
color / lineStyle / lineWidthstring? / 'solid'|'dashed'|'dotted' / number?line styling
readOnlyboolean?hide the cancel button
draggableboolean?allow drag-to-modify (default: !readOnly)
variant'standard' | 'line-only'line-only skips the pill

TradingTrade

FieldTypeDescription
idstringunique id
side'buy' | 'sell'marker color
pricenumbermarker price
sizenumberfill size (used for count VWAP)
timestampnumberexecution time in milliseconds
variant'chevron' | 'bubble' | 'count'marker style (default chevron)
color / labelstring?per-marker overrides

API methods

Access via chart.trading.

MethodDescription
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, pnlText?, pnlPercent?)refresh a position’s P&L pill without recreating the line. The older form with a number first, updatePositionPnl(id, pnl, pnlText?, pnlPercent?), still works but is deprecated, removed in 3.0.0: the number was never shown
getPositions() / getOrders() / getTrades()current state
clear()remove everything
setSettings(colors) / getSettings()global trading colors
setTickSchedule(schedule)snap dragged order and bracket lines to a TickSchedule; null (the default) keeps raw pointer prices; anything else throws a TypeError. chart.setTickSchedule sets it too, and the price alerts with it
on(event, cb) / off(event, cb)subscribe / unsubscribe (on returns an unsubscribe fn)

Events

Subscribe with chart.trading.on(event, cb).

EventPayloadFires when
trading:order_modify{ orderId, newPrice, previousPrice }an order line is dragged and released; newPrice is on the tick schedule when one is set
trading:bracket_modify{ parentId, bracketRole, newPrice }a TP/SL bracket line is dragged; newPrice is on the tick schedule when one is set
trading:order_cancel{ orderId }the close box on an order pill is clicked
trading:position_close{ positionId }the close box 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

Price ticks while dragging

Without a schedule, a drag reports the price under the pointer, unrounded. Hand the chart the instrument’s ticks and the dragged line previews, and both modify events carry, the nearest price the instrument can trade at, on the band the price is in:

import { TickSchedule } from 'openalgo-charts';
 
// Host-supplied rules for this instrument (synthetic here): 0.01 below 100, 0.05 from 100.
chart.setTickSchedule(new TickSchedule([{ tick: 0.01 }, { from: 100, tick: 0.05 }]));

A release at 100.33 then emits newPrice: 100.35, and one at 99.874 emits 99.87. chart.setTickSchedule is the call for a host that keeps its own instrument metadata: the chart passes the schedule on to chart.trading, whether the layer exists yet or is built later, and dragged price alerts and chart.snapPrice on the price pane round with it too. chart.trading.setTickSchedule sets the trading layer’s drags alone, so on its own it leaves a dragged alert rounding on the scale’s single tick.

With validated metadata there is nothing to call: instrument.applyTo(chart, interval) hands the chart, and through it the controller, the instrument’s tickSchedule, and a constant-tick instrument clears it, so a symbol switch never leaves the previous instrument’s bands on a drag. Call chart.setTickSchedule after applyTo to override it.

A one-band schedule, [{ tick: 0.05 }], snaps a constant tick. Give validatePrice the same schedule as tickSchedule: its constant tickSize path returns 100.05000000000001 where the schedule returns 100.05. Price limits are not checked here: run validatePrice before sending the amend. See Instrument metadata for the band rules.

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 >= 0 ? '+' : '') + '
});

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.

  • pnl.toFixed(2)); });

**Confirm-then-render modify** - keep the exchange as the source of truth:

```ts
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.