Lớp phủ giao dịch

Hiển thị vị thế, lệnh, điểm đánh dấu tín hiệu và vùng giao dịch ngay trên biểu đồ. Được thiết kế để tích hợp với cả quy trình giao dịch thủ công lẫn giao dịch thuật toán.

Tắt giao dịch

Lớp phủ giao dịch bật sẵn theo mặc định; menu đặt lệnh khi bấm chuột phải thì không (kể từ 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 } } })

Khi không bật menu này, thao tác bấm chuột phải mặc định của trình duyệt vẫn hoạt động bình thường trên biểu đồ.

Vị thế

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

Lệnh

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

Kéo đường giá để sửa lệnh; đăng ký nghe thay đổi qua chart.on('orderModify', ...).

Khớp lệnh thật (kết nối một adapter)

Mặc định biểu đồ chỉ phát ra ý định đặt lệnh/vị thế (orderPlace, orderModify, orderCancel, positionModify, positionClose) cho backend của bạn — nó không bao giờ tự giao dịch. Khi kết nối một ExecutionAdapter, biểu đồ sẽ chuyển các ý định đó vào adapter và hiển thị các lệnh/vị thế chính thức mà adapter phát lại (adapter là nguồn dữ liệu chuẩn duy nhất).

import { PaperExecutionAdapter } from '@tradecanvas/chart'

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

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

Cài đặt ExecutionAdapter (nó tương tự DataAdapter) để nối với một broker / OMS thật: placeOrder, modifyOrder, cancelOrder, modifyPosition, closePosition, cùng các sự kiện orders / positions / fill / error. PaperExecutionAdapter là môi trường khớp lệnh ảo dùng cho demo và test.

Thao tác với lệnh và vị thế ngay trên biểu đồ

Đường lệnh và đường vị thế có các nút nhỏ ở đầu bên phải: × huỷ lệnh hoặc đóng vị thế, ⇅ đảo chiều vị thế, còn dấu × trên đường dừng lỗ hoặc chốt lời sẽ gỡ mức đó. Các nút này phát ra cùng những ý định như API (orderCancel, positionClose, positionReverse, positionModify với null), nên adapter đã kết nối sẽ xử lý chúng, còn ứng dụng không kết nối adapter thì nhận các sự kiện. Nút chỉ thực hiện khi bạn nhả ra ngay trên nút: nhấn rồi trượt ra ngoài thì không có tác dụng gì. Tắt bất kỳ nút nào bằng lineButtons trong 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

Adapter nào đảo chiều được trong một bước thì cài đặt reversePosition; nếu không có, biểu đồ sẽ đóng vị thế rồi gửi một lệnh thị trường theo chiều ngược lại. Lệnh nhận stopLoss, takeProfit và timeInForce ('gtc' hoặc 'day'); các giá trị này được chuyển sang vị thế mà lệnh mở ra.

Dành cho người viết adapter: trong PositionModifyIntent, stopLoss: null (hoặc takeProfit: null) nghĩa là gỡ mức đó, còn khi thiếu trường thì giữ nguyên. Code viết kiểu intent.stopLoss ?? position.stopLoss sẽ giữ lại mức dừng lỗ mà người dùng đã gỡ.

Lệnh khớp trên biểu đồ

Mỗi lần khớp hiện thành một dấu nhỏ trên nến của nó: tô đặc khi mở vị thế, để rỗng khi đóng vị thế. Biểu đồ ghi lại các lần khớp mà adapter báo về và phát ra executionFill kèm lý do khớp ('order', 'close', 'reverse', 'stopLoss', 'takeProfit') cùng khoản lãi/lỗ đã chốt.

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

Menu chuột phải và nút “+” cạnh trục giá

Bấm chuột phải trên biểu đồ sẽ phát ra chartContextMenu kèm vùng được bấm ('plot', 'pane', 'priceAxis' hoặc 'timeAxis') cùng giá và thời gian tại đó. Với features.priceAxisAddButton, một nút “+” chạy theo con trỏ chữ thập dọc trục giá; bấm vào nó sẽ phát ra priceAxisAdd kèm mức giá đó.

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 dựng các menu của nó trên những sự kiện này. Bấm chuột phải vào vùng biểu đồ để đặt cảnh báo, mua và bán tại mức giá đó (lệnh giới hạn khi mức giá nằm ở phía lệnh phải chờ so với giá thị trường, lệnh dừng khi nằm ở phía còn lại), mở phiếu đặt lệnh, vẽ đường ngang, đặt lại khung nhìn và thao tác với hình vẽ; vào trục giá để chuyển chế độ thang; vào trục thời gian để đặt lại khung nhìn và đi tới một ngày. Thêm mục của riêng bạn bằng chartMenuItems (xem Tham chiếu API).

Phiếu đặt lệnh và bảng tài khoản (ChartWidget)

Nút biên lai của widget mở bảng tài khoản bên dưới biểu đồ: các vị thế đang mở kèm lãi/lỗ, các lệnh đang chờ, và các lần khớp từ trước đến giờ kèm lãi/lỗ (P&L) đã chốt. Mỗi dòng có thể đóng, đảo chiều hoặc huỷ. Lệnh mới mở phiếu đặt lệnh: mua hoặc bán, lệnh thị trường, giới hạn hoặc dừng, khối lượng, giá, mức dừng lỗ và chốt lời (tuỳ chọn), và hiệu lực của lệnh. Phiếu kiểm tra lệnh ngay khi bạn nhập (mua giới hạn phải nằm dưới giá thị trường, dừng lỗ phải nằm ở phía lỗ so với điểm vào…) và hiện tỷ lệ lời:lỗ. Đặt lệnh sẽ gửi đi một ý định orderPlace.

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.

Các dấu khớp lệnh gắn với mã đang hiện trên biểu đồ: đổi mã trong widget thì mã tiếp theo bắt đầu mà không có dấu nào.

Kéo để tạo lệnh

Tạo một đường lệnh kéo được, kéo nó tới một mức giá rồi xác nhận — loại lệnh (limit hay stop) được suy ra từ vị trí bạn thả so với giá hiện tại. Dùng cùng connectExecution để bản nháp đã xác nhận được khớp ngay.

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

Lệnh bracket (kéo để đặt)

Tạo một lệnh bracket kéo được — điểm vào lệnh cùng vùng dừng lỗ và chốt lời — rồi kéo ba đường để chỉnh điểm vào, mức rủi ro và lợi nhuận. Xác nhận bằng Enter (hoặc nút Đặt lệnh), huỷ bằng Esc. Trong widget, các mũi tên xanh/đỏ trên thanh công cụ tạo một bracket Long/Short. Biểu đồ phát ra một sự kiện bracketPlace duy nhất để backend của bạn xử lý — nó không bao giờ tự đặt lệnh.

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

Sổ lệnh (bấm để giao dịch)

Sổ lệnh dạng thang độ sâu thị trường (bật khi cần) hiển thị sổ lệnh thành các hàng giá với cột khối lượng bid/ask — bấm vào một ô ask để mua, một ô bid để bán tại mức giá đó. Bật bằng depthLadder: true và cấp dữ liệu sổ lệnh qua widget.setDepth; mỗi cú bấm phát ra ý định orderPlace cho OMS của bạn (biểu đồ không bao giờ tự giao dịch). Cùng dữ liệu đó cũng điều khiển lớp phủ độ sâu trên biểu đồ.

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

Bản đồ nhiệt thanh khoản

Tích luỹ các ảnh chụp sổ lệnh thành một bản đồ nhiệt phía sau nến — mỗi ảnh chụp là một dải dọc, trong đó khối lượng lệnh chờ sáng lên theo từng mức giá (bid màu xanh lá, ask màu đỏ). Những bức tường thanh khoản tồn tại lâu sẽ nổi bật. Bật từ bảng cài đặt (hoặc chart.setDepthHeatmapVisible); widget.setDepth ghi lại một ảnh chụp mỗi khi sổ lệnh cập nhật.

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()

Điểm đánh dấu tín hiệu

Các tích hợp bot hoặc giao dịch theo tín hiệu có thể đặt mũi tên chỉ hướng lên lớp phủ.

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 hiện một ghi chú cạnh điểm đánh dấu đang nằm dưới con trỏ: nhãn và nguồn, chiều, giá, độ tin cậy và thời gian của nó.

Vùng giao dịch

Hiển thị hình chữ nhật từ điểm vào → điểm ra, tô màu theo lãi/lỗ (P&L) và có huy hiệu hướng lệnh.

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

Token cho nhãn vị thế

Tuỳ chỉnh nhãn trên biểu đồ cho từng vị thế. positionLabel nhận một chuỗi mẫu hoặc một hàm trả về chuỗi.

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

Các token có sẵn: {side}, {qty}, {openQty}, {closedQty}, {entry}, {price}, {pnl}, {pnlPct}, {pnlSign}.

Mốc màu chuyển sắc theo P&L

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