Halaman ini belum diterjemahkan, jadi ditampilkan dalam bahasa Inggris.

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

The trading overlay is on by default; the right-click order menu is not (since 1.3).

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

// Opt in to the right-click "Buy / Sell here" order menu
new Chart(host, { features: { tradingContextMenu: true } })
new ChartWidget(host, { chartOptions: { features: { tradingContextMenu: true } } })

Without the menu, native browser right-click works on the chart as expected.

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.

Act on orders and positions from the chart

Order and position lines carry small buttons at their right end: × cancels an order or closes a position, ⇅ reverses a position, and an × on a stop-loss or take-profit line removes it. They raise the same intents as the API (orderCancel, positionClose, positionReverse, positionModify with null), so a connected adapter acts on them and a host without one gets the events. A button acts when it is released over it: a press that slides off does nothing. Turn any of them off with lineButtons in setTradingConfig.

chart.setTradingConfig({ lineButtons: { reverse: false } })  // keep cancel, close and remove-stops

chart.cancelOrderIntent('ord-1')
chart.closePositionIntent('pos-1')
chart.reversePositionIntent('pos-1')                 // close, then the same size the other way
chart.modifyPositionIntent('pos-1', { stopLoss: null }) // null removes the stop

An adapter that can reverse in one step implements reversePosition; without it the chart closes the position and sends a market order the other way. Orders take a stopLoss, a takeProfit and a timeInForce ('gtc' or 'day') that carry over to the position they open.

For adapter authors: in PositionModifyIntent, stopLoss: null (or takeProfit: null) means remove it, while a missing field means leave it as it is. Code written as intent.stopLoss ?? position.stopLoss would keep a stop the user removed.

Fills on the chart

Each fill shows as a small mark on its bar: solid where it opened a position, hollow where it closed one. The chart records the fills an adapter reports and emits executionFill with why it happened ('order', 'close', 'reverse', 'stopLoss', 'takeProfit') and the profit or loss it realised.

chart.on('executionFill', (e) => {
  const { side, price, quantity, reason, pnl } = e.payload
})

chart.addFill({ orderId: 'o-7', side: 'buy', price: 64_150, quantity: 1, time: Date.now() })
chart.getFills()        // the latest 1000
chart.getRealisedPnl()  // every fill's P&L since the last clearFills
chart.clearFills()
chart.setTradingConfig({ fillMarks: false })  // no marks

Right-click menus and the “+” by the price axis

A right-click on the chart emits chartContextMenu with the part that was clicked ('plot', 'pane', 'priceAxis' or 'timeAxis') and the price and time there. With features.priceAxisAddButton a “+” follows the crosshair along the price axis; pressing it emits priceAxisAdd with its price.

const chart = new Chart(host, { features: { priceAxisAddButton: true } })

chart.on('chartContextMenu', (e) => {
  const { area, x, y, price, time } = e.payload
  openMyMenu(x, y)
})
chart.on('priceAxisAdd', (e) => openMyMenu(e.payload.x, e.payload.y, e.payload.price))

ChartWidget builds its menus on these. Right-click the plot for an alert, a buy and a sell at that price (a limit on the side of the market where it would wait, a stop on the other), an order ticket, a horizontal line, reset view and the drawings; the price axis for its scale switches; the time axis for reset view and go to date. Add your own entries with chartMenuItems (see the API reference).

Order ticket and account panel (ChartWidget)

The widget's receipt button opens an account panel under the chart: open positions with their profit and loss, working orders, and the fills so far with the P&L realised. Each row closes, reverses or cancels. New order opens an order ticket: buy or sell, market, limit or stop, quantity, price, an optional stop-loss and take-profit, and time in force. It checks the order as you fill it in (a buy limit goes below the market, a stop-loss on the losing side of the entry…) and shows the reward:risk. Placing it sends an orderPlace intent.

const widget = new ChartWidget(host, {
  trading: true,         // default
  accountPanel: true,    // default when trading is on
})
widget.getChart().connectExecution(new PaperExecutionAdapter({ markPrice: 64_000 }))
widget.toggleAccountPanel(true)
// The panel follows ordersChange, positionsChange, executionFill and each tick.

Fill marks belong to the symbol on the chart: switching symbol in the widget starts the next one with none.

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',
})

// A marker under the pointer, and a click on one
chart.on('signalMarkerHover', (e) => showNote(e.payload.marker, e.payload.x, e.payload.y))  // marker null: off it
chart.on('signalMarkerClick', (e) => openSignal(e.payload.marker))

ChartWidget shows a note by a marker under the pointer: its label and source, side, price, confidence and time.

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' },
  ],
})