Trading overlay

Render positions, orders, signal markers, and trade zones directly on the chart. Designed to integrate with both manual and algorithmic trading flows.

Disabling trading

Trading is opt-out, not opt-in. Projects that don't need trading affordances have two off-switches:

// Drop the entire trading subsystem (no orders, no positions, no overlay, no menu)
new Chart(host, { features: { trading: false } })

// Keep positions and orders visible, but remove the right-click order menu
new Chart(host, { features: { tradingContextMenu: false } })

With either flag set, native browser right-click works on the chart as expected (previously the custom menu suppressed it unconditionally — fixed in 0.8.1).

Positions

chart.addPosition({
  id: 'pos-1',
  side: 'long',
  entry: 65_200,
  quantity: 0.5,
  closedQuantity: 0.1,   // partial-close band on the left edge
  stopLoss: 64_800,
  takeProfit: 66_000,
})

Orders

chart.addOrder({
  id: 'ord-1',
  side: 'sell',
  type: 'limit',
  price: 65_500,
  quantity: 0.25,
})

Drag the price line to modify; subscribe via chart.on('orderModify', ...).

Live execution (connect an adapter)

By default the chart emits order/position intents (orderPlace, orderModify, orderCancel, positionModify, positionClose) for your backend — it never trades itself. Connect an ExecutionAdapter and the chart instead routes those intents into the adapter and renders the authoritative orders/positions it emits back (the adapter is the single source of truth).

import { PaperExecutionAdapter } from '@tradecanvas/chart'

chart.connectExecution(new PaperExecutionAdapter({ markPrice: 64_000 }))

chart.on('executionError', (e) => toast(e.payload.message))
// chart.disconnectExecution()

Implement ExecutionAdapter (it mirrors DataAdapter) to wire a real broker / OMS: placeOrder, modifyOrder, cancelOrder, modifyPosition, closePosition, plus orders / positions / fill / error events. PaperExecutionAdapter is a virtual-fill sandbox for demos and tests.

Drag-to-create orders

Start a single draggable order line, drag it to a price, and confirm — the order type (limit vs stop) is inferred from where you drop it relative to the current price. Pairs with connectExecution so a confirmed draft fills immediately.

chart.startOrderDraft('buy')   // draggable line at the latest close
chart.confirmOrderDraft()      // emits orderPlace -> a connected adapter fills it
chart.cancelOrderDraft()

Bracket orders (drag to place)

Start a draggable bracket — entry plus stop-loss and take-profit zones — then drag the three lines to tune entry, risk, and reward. Confirm with Enter (or the Place button), cancel with Esc. In the widget, the green/red toolbar arrows start a long/short bracket. The chart emits a single bracketPlace event for your backend to act on — it never places orders itself.

chart.startBracket('buy')          // entry defaults to the latest close
chart.startBracket('sell', 64_800) // or pin the entry price

chart.on('bracketPlace', (e) => {
  const { side, entry, stopLoss, takeProfit, riskReward } = e.payload
  // submit to your OMS, then reflect fills back via chart.setOrders/setPositions
})

chart.confirmBracket()  // same as Enter
chart.cancelBracket()   // same as Esc

Depth ladder (click to trade)

An opt-in depth-of-market ladder renders the order book as price rows with bid/ask size columns — click an ask cell to buy, a bid cell to sell at that price. Enable with depthLadder: true and feed the book via widget.setDepth; clicks emit orderPlace intents for your OMS (the chart never trades itself). The same data also drives the on-chart depth overlay.

const widget = new ChartWidget(host, { depthLadder: true })

widget.setDepth({
  bids: [{ price: 64_190, volume: 3.1 }, { price: 64_185, volume: 5.4 }],
  asks: [{ price: 64_205, volume: 2.0 }, { price: 64_210, volume: 8.7 }],
})

widget.getChart().on('orderPlace', (e) => {
  // { side, type: 'limit', price } — submit to your backend
})

Liquidity heatmap

Accumulate order-book snapshots into a heatmap behind the candles — each snapshot is a vertical strip where resting size lights up per price level (bids green, asks red). Liquidity walls that persist over time stand out. Toggle from the settings sheet (or chart.setDepthHeatmapVisible); widget.setDepth records a snapshot on every book update.

chart.setDepthHeatmapVisible(true)
chart.setDepthHeatmapConfig({ opacity: 0.7, capacity: 240 })

// each book update both draws the overlay/ladder and records a heatmap column
widget.setDepth(orderBook)
// low-level: chart.pushDepthSnapshot(orderBook) · chart.clearDepthHeatmap()

Signal markers

Bot or signal-trading integrations can place directional arrows on the overlay.

chart.addSignalMarker({
  id: 'sig-12',
  time: bar.time,
  price: bar.close,
  direction: 'long',
  confidence: 0.86,
  source: 'momentum-bot',
  label: 'EMA cross',
})

Trade zones

Visualize entry → exit rectangles with P&L coloring and direction badges.

chart.addTradeZone({
  id: 'tz-1',
  side: 'long',
  entryTime: openedAt,
  exitTime: closedAt,
  entryPrice: 65_100,
  exitPrice: 65_800,
  status: 'closed',
})

Position label tokens

Customize the on-chart label per position. positionLabel accepts a template string or a function returning a string.

new ChartWidget(host, {
  trading: true,
  positionLabel: '{side} {qty} @ {entry} · {pnlSign}{pnlPct}%',
})

Available tokens: {side}, {qty}, {openQty}, {closedQty}, {entry}, {price}, {pnl}, {pnlPct}, {pnlSign}.

P&L gradient stops

new ChartWidget(host, {
  trading: true,
  pnlThresholds: [
    { pnlPct: -0.02, color: '#ef4444' },
    { pnlPct: 0,     color: '#94a3b8' },
    { pnlPct: 0.02,  color: '#10b981' },
  ],
})