Skip to content

Guides

Orders and positions

Put a working order, a position and its protective legs on the chart, draggable, cancellable, and reported back to your page when the reader acts. The chart draws; your venue decides.

The model, before the code

One idea does most of the work here and getting it wrong produces a chart that quietly lies: the frame has no venue, no account and no idea what your exchange said.

So every interaction is a request. The reader drags a line, the chart reports where they dropped it and leaves it there. Whether that becomes an amended order is your page’s decision, and the chart only reflects it once you say so.

The round trip
reader drags the line
   → chart fires  orderMove { handle, price }
   → your page    amendOrder(id, price)
   → your venue   accepted?  →  handle.set({ price })     ← confirm
                  rejected?  →  handle.set({ price: old }) ← put it back
A chart that snapped the line to the new price on its own would be showing a resting order that does not exist. That is the failure this shape exists to prevent, and it is why nothing here is optimistic.

A venue you can make say no

The paragraph above is the whole model and it is hard to believe from a code block, so here it is running. Draw the lines, then drag the dashed bid with the pretend venue set to accepts and again with it set to rejects. Inside the frame the two drags are identical, which is the point: the chart is not the thing deciding.

your venue

Drag the dashed bid line. On accepts it stays where you let go. On rejects it snaps back, and the line below shows the call that put it there. The chart did neither on its own.

What crossed the boundary
Nothing yet. Draw the lines, then drag one.

The snap back on a rejection is handle.set({ price }) from the page, printed in the log as it happens. Delete that one line and a rejected amendment leaves a line on the chart at a price your venue never accepted, with nothing on screen saying so.

The three things you can draw

What it isMethod
Order lineA working order at a price, a resting bid, a stop, a take-profit you placed.createOrderLine
Position lineAn open position at its entry, with its protective legs and live P&L.createPositionLine
Execution markerA fill that already happened, pinned to one bar.createExecutionShape

All three are drawn IN the price scale rather than on top of the plot, so they move with the chart, survive a pan and a zoom, and can be dragged. Every property of each is on Objects.

A working order

editable makes it draggable; cancellable gives it an ✕. Neither does anything to your venue, they decide which events you can receive.

Place it, then handle what the reader does
const chart = vpEmbed.chart(document.querySelector('.vp-chart'));
const orders = new Map();

const bid = await chart.createOrderLine({
  price: 42000,
  text: 'Bid · 0.5',
  quantity: '0.5',
  editable: true,        // the reader may drag it
  cancellable: true,     // …and cancel it
  lineColor: '#22c55e',
  lineStyle: 2,          // 0 solid · 1 dotted · 2 dashed · 3 large dash · 4 sparse
});
orders.set(bid.handle, myOrderId);

chart.on('orderMove', async ({ handle, price }) => {
  const id = orders.get(handle);
  const ok = await amendOrder(id, price);        // YOUR venue
  if (!ok) await bid.set({ price: priceOf(id) }); // rejected, put it back
});

chart.on('orderCancel', async ({ handle }) => {
  await cancelOrder(orders.get(handle));
  await bid.remove();
});

A position with a full bracket

Entry, stop and target as one object. The legs are properties of the position rather than three separate lines, so they move together and cannot drift apart.

Entry, stop, target
const pos = await chart.createPositionLine({
  price: 41850,              // entry
  text: 'Long 0.5',
  quantity: '0.5',
  stopLoss: 41200,           // protective stop
  takeProfit: 43000,         // target
  breakeven: 41850,          // optional: where the trade stops losing
  closeable: true,           // the close control, on by default
  reversible: true,          // the reverse control, OFF by default
});

chart.on('positionClose', ({ handle }) => closePosition(orders.get(handle)));
chart.on('positionReverse', ({ handle }) => reversePosition(orders.get(handle)));
// A press on the body that never travelled is a CLICK, not a drag: open
// whatever ticket you keep for the position rather than moving anything.
chart.on('positionModify', ({ handle }) => openTicket(orders.get(handle)));
A stop and a target are just prices to the chart, it does not know which side of the market they should be on, and will not correct you. That check belongs on your page, where the instrument's tick size and your risk rules live.

Letting the reader drag a bracket out

A position line can grow two small handles that a reader pulls away from the pill and releases at a price. It is the same kind of thing as setTrading: a REQUEST, not a fill. Nothing is drawn as a result, because the chart does not know whether your venue accepted it.

Off until you ask
const pos = await chart.createPositionLine({
  price: 79649,
  text: 'Long 0.5',
  quantity: '0.5',
  side: 'long',              // which way it points
  canAddTakeProfit: true,    // both are OFF by default
  canAddStopLoss: true,
});

chart.on('positionAddTakeProfit', async ({ handle, price }) => {
  // 'price' is where the reader RELEASED, not the position's entry.
  const ok = await placeTakeProfit(orders.get(handle), price);
  if (!ok) return;                       // nothing on the chart moved
  // Answer by drawing the leg AND withdrawing the handle that made it.
  await pos.set({ takeProfit: price, canAddTakeProfit: false });
});
Set side whenever you offer the handles. It is what makes the gesture honest: a long takes profit above its entry and stops below it, so the draft CLAMPS to the entry and draws an arrow when the pointer is in the half that cannot work. With side unset the drag constrains nothing, and a reader can place a take-profit into a loss and find out from your venue's rejection.
Turn a handle OFF once that half of the bracket exists. Leaving it on invites a second take-profit on one position, which is an order your venue will refuse and the reader will read as the chart being broken. That is the whole reason they are two properties rather than one switch.
While the drag is in flight the chart washes the span between the entry and the level under the pointer in that leg's colour: red down to a stop, green up to a target, so the reader is looking at how much they are risking rather than only at where the line landed. It is part of the gesture and disappears on release; protectFillOpacity: 0 turns it off. Styling has the knob.

Both handles, both leg colours, the leg names and the button labels are styled the same way everything else on the line is. Styling has the rounded, branded version of the object above.

Place lines after dataLoaded

A price needs a plot to sit in. Before the first bars are drawn the chart has a symbol and an empty canvas, so a line placed then has no scale to be positioned against.

The one ordering rule
chart.on('dataLoaded', () => {
  void drawMyOrders();     // bars exist, the scale is real
});

Letting the reader start an order

Everything above draws orders YOU already have. setTrading is the other direction: it adds Buy and Sell rows to the price scale's ⊕ menu, so a reader can pick a level and ask for an order that does not exist yet.

Opt in, then handle the request
await chart.setTrading({ quantity: '0.5', unitLabel: 'BTC' });

chart.on('orderSubmit', ({ side, type, price, quantity, symbol }) => {
  // A REQUEST. 'type' is limit or stop, derived from which side of the
  // market the level is on. Nothing has been sent anywhere.
  void confirmWithTheUser({ side, type, price, quantity, symbol });
});

await chart.setTrading(null);   // withdraw the rows again
The frame never assumes its host trades. Without setTrading the ⊕ menu offers alerts and drawings only, and orderSubmit can never fire. A chart on a news article should not be offering to buy anything.

Clearing up

CallWhat it removes
handle.remove()One line. Removing one already gone is not an error.
chart.removeAllShapes()Every DRAWING you placed, not your lines, and never the reader's own.
chart.setTrading(null)The Buy / Sell rows, leaving everything drawn in place.
There is deliberately no removeAllLines. Your lines correspond to real orders and positions, so a single call that erased the lot is far more likely to be a bug than an intention, and the chart would then disagree with your book with nothing on screen saying so.

Every property and every event

This page is the shape of the thing. The exhaustive lists are Objects for what you pass, Methods you call for what you can ask, and Events for what comes back.