Thời gian thực & phát lại

Stream dữ liệu trực tiếp, gộp tick thành nến và phát lại dữ liệu lịch sử với tốc độ điều chỉnh được.

Adapter có sẵn

Tất cả đều miễn phí, không cần API key — kết nối adapter nào cũng theo cùng một cách:

import { BinanceAdapter, CoinbaseAdapter, BybitAdapter, KrakenAdapter } from '@tradecanvas/chart'

chart.connect({ adapter: new BinanceAdapter(),  symbol: 'BTCUSDT', timeframe: '1m' })
chart.connect({ adapter: new BybitAdapter(),    symbol: 'BTCUSDT', timeframe: '1m' })
chart.connect({ adapter: new KrakenAdapter(),   symbol: 'BTC/USD', timeframe: '5m' })
chart.connect({ adapter: new CoinbaseAdapter(), symbol: 'BTC-USD', timeframe: '15m' })

Interface DataAdapter

interface DataAdapter {
  name: string
  connect(config): void
  disconnect(): void
  getConnectionState(): ConnectionState
  fetchHistory(symbol, timeframe, limit?): Promise<OHLCBar[]>
  on(event, listener): void   // 'bar' | 'tick' | 'snapshot' | 'connectionChange' | 'error'
  off(event, listener): void
  dispose(): void

  // Optional
  fetchHistoryBefore?(symbol, timeframe, before, limit): Promise<OHLCBar[]>
  searchSymbols?(query, options?): Promise<SymbolInfo[]>
  resolveSymbol?(symbol): Promise<SymbolInfo | null>
  subscribeQuotes?(symbols, onQuotes): () => void      // a watchlist's rows
  fetchNews?(symbol, limit?): Promise<NewsItem[]>       // the symbol info panel
  fetchTrades?(symbol, limit?): Promise<Trade[]>        // tick charts, with
  subscribeTrades?(symbol, onTrades): () => void        // subscribeTrades
}

Nguồn dữ liệu bất kỳ trong ~20 dòng

Kế thừa WebSocketAdapter (dữ liệu trực tiếp + lịch sử qua REST) hoặc PollingAdapter (nguồn chỉ có REST). Lớp cơ sở lo vòng đời kết nối, kết nối lại, giải mã và phát sự kiện — bạn chỉ cần cung cấp một URL và một hàm phân tích dữ liệu.

import { WebSocketAdapter } from '@tradecanvas/chart'

const myAdapter = new WebSocketAdapter({
  name: 'myexchange',
  wsUrl: (c) => 'wss://api.myexchange.com/ws/' + c.symbol + '@kline_' + c.timeframe,
  fetchHistory: (symbol, tf, limit) => fetch('/candles?...').then((r) => r.json()),
  parseMessage: (raw) => ({ bar: toBar(raw), closed: raw.k.x }),
})

Gộp tick

Gộp tick thô thành nến OHLC theo từng khung thời gian:

import { TickAggregator } from '@tradecanvas/chart'

const agg = new TickAggregator('1m')
agg.processTick({ time, price, volume })

const current = agg.getCurrentBar()         // forming bar
const closed = agg.flushClosedBars()        // bars that have rolled over

Giá của nhiều mã

Nguồn dữ liệu có subscribeQuotes gửi giá (giá cuối, thay đổi trong ngày, cao nhất, thấp nhất, khối lượng, giá mua và giá bán) của nhiều mã cùng lúc cho đến khi bạn dừng. BinanceAdapter gửi một ảnh chụp 24 giờ, rồi đến luồng mini-ticker; MockAdapter tự tạo giá giả. Danh mục theo dõi của widget lấy giá cho các dòng từ đây.

const stop = new BinanceAdapter().subscribeQuotes(['BTCUSDT', 'ETHUSDT'], (quotes) => {
  for (const q of quotes) console.log(q.symbol, q.last, q.changePercent)
})
stop()

readQuote({ symbol: 'AAPL', last: 190, prevClose: 188 })   // → change 2, changePercent 1.06

Biểu đồ tick

Khung thời gian tick — '100T' — vẽ mỗi nến từ 100 giao dịch. Nó được dựng từ các giao dịch của nguồn dữ liệu: các giao dịch gần đây cho phần lịch sử (fetchTrades), rồi các giao dịch trực tiếp (subscribeTrades). BinanceAdapter stream các giao dịch gộp (aggregate trades). Biểu đồ tick không có đồng hồ đếm ngược và không tải thêm lịch sử cũ hơn; nguồn không có giao dịch sẽ từ chối khung thời gian tick.

chart.connect({ adapter: new BinanceAdapter(), symbol: 'BTCUSDT', timeframe: '100T' })
await chart.setTimeframe('500T')

// Your own trades into tick bars
const builder = new TickBarBuilder(100)
builder.push([{ time, price, volume }])   // → [{ bar, closed }]

Trạng thái thị trường và tin tức

marketStatus(info, now) cho biết thị trường của một mã có mở tại một thời điểm hay không, dựa trên giờ giao dịch theo múi giờ của sàn (tính cả lúc đổi giờ), và khi nào điều đó thay đổi. Mỗi phiên có thể ghi rõ những ngày trong tuần mà nó mở. fetchNews của nguồn dữ liệu cung cấp tiêu đề tin cho bảng thông tin mã của widget; readNews chỉ giữ các tin có tiêu đề và thời gian, và chỉ giữ liên kết tới trang web.

const info = {
  symbol: 'AAPL',
  timezone: 'America/New_York',
  sessions: [{ start: '09:30', end: '16:00', days: [1, 2, 3, 4, 5] }],
}
marketStatus(info, Date.now())   // { state: 'closed', next: <next open, ms> } · 'open' · 'always' (24/7)

Kết nối lại

ReconnectManager giãn cách các lần thử lại theo cấp số nhân (exponential backoff), có mức trần và dừng hẳn sau lần thử cuối.

import { ReconnectManager } from '@tradecanvas/chart'

const rm = new ReconnectManager({ maxRetries: 6, baseDelay: 500, maxDelay: 30_000 })

rm.start()
rm.schedule(() => connectWebsocket())   // call on disconnect; backs off automatically
rm.onConnected()                        // call on a successful connect to reset

Chế độ phát lại

Đẩy một DataSeries lịch sử tiến lên với tốc độ điều chỉnh được. Tách rời khỏi Chart nên có thể dùng cho cả phát lại trên giao diện lẫn backtest headless.

import { ReplayController } from '@tradecanvas/chart'

const replay = new ReplayController({
  data: historicalBars,
  speed: 10,      // bars per second
  startIndex: 0,
})

// Seed the chart with everything before startIndex
chart.setData(replay.getPrefix())

// Wire each emitted bar into the chart
replay.on('bar', ({ bar }) => chart.appendBar(bar))
replay.on('finished', () => console.log('done'))

replay.start()
// replay.pause(); replay.resume(); replay.step(5); replay.seek(200); replay.setSpeed(20)

Trong chế độ phát lại của widget, bấm vào bất kỳ nến nào đã hiện để đưa con trỏ phát lại tới đó. Rộng hơn, biểu đồ giờ phát ra các sự kiện click và barClick cho những cú bấm chuột trái thông thường (nhấn và nhả mà không kéo):

chart.on('barClick', (e) => {
  const { bar, barIndex, point } = e.payload
})

Phát lại trên biểu đồ, theo bước nhỏ hơn

chart.replayStart() phát lại chính chuỗi dữ liệu của biểu đồ. Truyền các nến nhỏ hơn vào steps (nến 5 phút dưới một biểu đồ khung giờ) và mỗi bước sẽ làm nến đang hình thành lớn dần từ chúng, đúng như nó đã hình thành trên thị trường; các nến đã đóng hiện đúng như trong chuỗi. startIndex và replaySeekToBar đếm theo nến của biểu đồ. Trong ChartWidget, menu Bước trên thanh phát lại đưa ra các khung thời gian nhỏ hơn mà nguồn dữ liệu có (hoặc dựng được từ các nến bạn đã nạp).

const steps = await adapter.fetchHistory('BTCUSDT', '5m', 2000)
chart.replayStart({ steps, startIndex: 120, paused: true, speed: 5 })
chart.replayResume()
chart.getReplayBarIndex()      // the chart bar forming now
chart.replaySeekToBar(150)     // to the end of bar 150
chart.replaySeekToTime(t)      // to the bars that opened before t
chart.on('replayStep', (e) => e.payload)    // { barIndex, time, until }
chart.on('replayState', (e) => e.payload)   // { state: 'playing' | 'paused' | 'stopped' }
chart.replayStop()             // back to the live series

Giao dịch giấy trong lúc phát lại

Một adapter khớp lệnh có setMarkPrice(price, time) (như PaperExecutionAdapter) sẽ giao dịch trên phiên phát lại: trong lúc phát lại, biểu đồ chuyển cho nó từng mức giá và thời điểm được phát lại, nên lệnh và mức dừng lỗ khớp khi phát lại đi qua chúng, và các lần khớp nằm trên các nến được phát lại. Giá thấp và giá cao của mỗi bước đều được tính, và một lần nhảy tới trước sẽ đi qua mọi bước trên đường. Nó chỉ đi tới: sau khi tua lùi, nó chờ đến khi phát lại vượt qua điểm xa nhất nó đã thấy. Trong lúc đó giá trực tiếp không làm nó thay đổi; khi phát lại kết thúc, nó quay về giá trực tiếp, và các lần khớp quay về theo giờ thực. Cảnh báo giá vẫn theo dõi thị trường trực tiếp trong suốt thời gian đó; cảnh báo theo đường chỉ báo chờ đến khi phát lại kết thúc.

chart.connectExecution(new PaperExecutionAdapter())
chart.replayStart({ startIndex: 300, paused: true })
// place orders from the chart or the order ticket, then play

API

Phương thứcCông dụng
start()Bắt đầu phát từ vị trí hiện tại.
pause() / resume()Tạm dừng/tiếp tục bộ hẹn giờ.
step(n)Phát N nến một cách đồng bộ, không qua bộ hẹn giờ.
seek(index)Nhảy tới vị trí mới mà không phát các nến ở giữa.
setSpeed(bps)Số nến mỗi giây (giới hạn ở mức >= 0.01).
destroy()Xoá bộ hẹn giờ + các listener.

Xem Phân tích để dùng phát lại cùng bộ backtest.