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ức | Cô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.