Docs·Charting Library·Core API

Drawing

Programmatically add markers, trendlines, horizontals, and execution arrows to the chart. The drawing API your bot dashboard, backtest replay, and signal annotation flows hang off.

Two distinct drawing surfaces live on the chart instance:

  1. User drawings — the trendlines/horizontals/fibs the trader places by hand using the toolbar. Persisted by the engine, recalled on the next load, fully interactive (drag, resize, edit, delete via UI).
  2. Programmatic drawings — markers, trendlines, and horizontals you add via API. Useful for: marking bot executions, annotating backtest replays, drawing strategy signals, plotting alerts.

Both surfaces use the same render layer. This page covers (2) — the API. (1) is fully UI-driven and doesn't need code.

Markers

The most common programmatic drawing. A marker is a single triangle at a (time, price) point, colored by side (bid / ask).

chart.addMarker(timestamp, price, isBid)

ArgumentTypeDescription
timestampnumberUnix seconds where to place the marker on the time axis.
pricenumberPrice level on the Y axis.
isBidbooleantrue = green up-triangle (buy / fill on bid). false = red down-triangle (sell / fill on ask).

Returns a numeric marker ID you can use later to remove or select the marker.

JavaScript
// Mark every bot fill on the chart
botFills.forEach((fill) => {
  chart.addMarker(fill.ts, fill.price, fill.side === 'buy')
})

// Mark a real-time fill the moment it arrives
ws.on('fill', (fill) => {
  const id = chart.addMarker(fill.ts, fill.price, fill.side === 'buy')
  pendingFills.set(fill.id, id)
})

chart.removeMarker(id)

Removes a single marker by ID.

JavaScript
chart.removeMarker(markerId)

chart.clearMarkers()

Removes all programmatically-added markers. Does not touch user drawings.

chart.selectMarker(id) / chart.deselectMarker() / chart.getSelectedMarker()

Programmatically select a marker (the engine highlights it). Useful for hover-driven annotation systems where hovering a row in your fill list should glow the corresponding marker on the chart.

JavaScript
onFillHover(fillId) {
  const markerId = pendingFills.get(fillId)
  if (markerId) chart.selectMarker(markerId)
}
onFillBlur() {
  chart.deselectMarker()
}

chart.onMarkerSelected(callback)

Fires when the user clicks a marker (interactive selection). Callback receives the marker ID:

JavaScript
chart.onMarkerSelected((id) => {
  // Show fill details popup, etc.
})

Trendlines

chart.addTrendline(x1, y1, x2, y2, r, g, b, lineWidth, dashed, pane)

Draws a line from (x1, y1) to (x2, y2) on the chart.

ArgumentTypeDescription
x1numberUnix seconds, start of line.
y1numberPrice, start of line.
x2numberUnix seconds, end of line.
y2numberPrice, end of line.
r, g, bnumber (0–255)Color channels.
lineWidthnumberLine width in CSS pixels. Typical 1.0–2.0.
dashedbooleantrue = dashed, false = solid.
panenumber0 = main candle pane. Higher values for indicator subpanes.

Returns a drawing ID.

JavaScript
// Draw an entry → exit line for each closed trade
trades.forEach((t) => {
  chart.addTrendline(
    t.entryTs, t.entryPrice,
    t.exitTs,  t.exitPrice,
    t.pnl > 0 ? 38 : 246, t.pnl > 0 ? 166 : 70, t.pnl > 0 ? 154 : 93,
    1.5,
    false,
    0,
  )
})

Horizontal lines

chart.addHorizontalLine(x, price, r, g, b, lineWidth, dashed, pane)

Single horizontal line at price, anchored at x (the engine extends the line across the full visible time axis).

JavaScript
// Mark a key resistance level
chart.addHorizontalLine(
  Date.now() / 1000, 68500,
  240, 185, 11,   // amber
  1.5,
  false,
  0,
)

Visibility

chart.setDrawingsVisible(visible)

Hide all programmatic drawings at once (useful for screenshot mode).

chart.setPositionMarkersVisible(visible)

Hide just the position-marker overlays (used by the integrated trade panel). Programmatic markers from addMarker are unaffected.

Coordinate conversion

Sometimes you have a screen pixel and need to know what (time, price) it maps to (e.g. user clicks somewhere, you want to place a marker right there).

chart.screenToTimePrice(sx, sy)

Returns { ts, price } for a given screen position, or null if the click was outside the candle pane.

JavaScript
canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect()
  const sx = e.clientX - rect.left
  const sy = e.clientY - rect.top
  const pt = chart.screenToTimePrice(sx, sy)
  if (pt) {
    chart.addMarker(pt.ts, pt.price, true)
  }
})

chart.timePriceToScreen(ts, price)

Inverse direction — useful for positioning HTML overlays (tooltips, popovers) on top of the canvas at a known (time, price) point.

Drawing-tool events

For products that build their own annotation flows on top of the engine's drawing tools, the chart instance exposes the same lifecycle events the built-in toolbar uses:

JavaScript
chart.onDrawingComplete(() => {
  // User finished placing a drawing (e.g. clicked the second point of a trendline).
})

chart.onDrawingCancel(() => {
  // User pressed Esc to cancel an in-progress drawing.
})

chart.onDrawingSelected((id, cx, cy) => {
  // User clicked an existing drawing. cx/cy = canvas pixel where the click landed.
})

chart.onDrawingDblClick((id, sx, sy, cx, cy) => {
  // User double-clicked a drawing — typical pattern: open an "edit drawing" popup
  // at (sx, sy) on screen, with the drawing's ID for the form to bind to.
})

The IDs returned from addMarker / addTrendline / addHorizontalLine are stable for the lifetime of the chart instance. Persist them in your own store if you need them to survive a chart reload.

Common pitfalls

Markers appear at the wrong time. The timestamp argument is Unix seconds, not milliseconds. If you're feeding Date.now(), divide by 1000.

Markers drift left as new candles arrive. You're placing them at the wrong end of the time axis. The engine anchors markers in data space (timestamp + price) — they don't move when the chart pans, they only move if the data shifts. If your markers are drifting, double-check that you're passing the real fill timestamp, not the screen pixel.

Adding 10 000 markers at once is slow. The single-shot addMarker API allocates one marker per call. For large bulk imports (e.g. loading a full backtest history), batch the work behind a chart.stop() / chart.start() pair so the render loop only paints the final state.

Next