リアルタイムとリプレイ

ライブデータをストリーミングし、ティックをバーに集計し、過去のデータを速度を制御しながらリプレイします。

組み込みアダプター

すべて無料で、API キーは不要です。どれも同じ方法で接続できます:

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

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
}

約 20 行で任意のフィードに対応

WebSocketAdapter(ライブ + REST での履歴取得)または PollingAdapter (REST のみのフィード)を継承します。接続のライフサイクル、再接続、デコード、イベントの発行は基底クラスが 処理するので、URL とパース関数を用意するだけです。

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

ティックの集計

生のティックを、時間足ごとの OHLC バーにまとめます:

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

複数シンボルのクオート

subscribeQuotes を持つフィードは、止めるまで複数のシンボルのクオート(最新価格、当日の変化、高値、安値、 出来高、買気配と売気配)をまとめて送ります。BinanceAdapter は 24 時間のスナップショットを送ったあと、 ミニティッカーのストリームを流します。MockAdapter はクオートを生成します。ウィジェットのウォッチリストは、 これで各行を埋めます。

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

ティックチャート

ティックの時間足 — '100T' — は 100 約定ごとに 1 本のバーを描きます。フィードの約定から組み立て、 履歴には直近の約定(fetchTrades)、その後はライブの約定(subscribeTrades)を使います。 BinanceAdapter は集約約定(aggregate trades)をストリーミングします。ティックチャートにはカウントダウンがなく、 さらに古い履歴も読み込みません。約定を提供しないフィードはティックの時間足を受け付けません。

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

市場の状態とニュース

marketStatus(info, now) は、取引所のタイムゾーンでの取引時間(夏時間の切り替えを含む)から、ある時点で シンボルの市場が開いているかどうかと、それがいつ変わるかを返します。セッションには開く曜日を指定できます。 フィードの fetchNews はウィジェットのシンボル情報パネルにヘッドラインを渡します。readNews は タイトルと時刻のある項目だけを残し、リンクも 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)

再接続

ReconnectManager は、上限付きの指数バックオフと、最終的な再試行の打ち切りを処理します。

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

リプレイモード

過去の DataSeries を、速度を制御しながら先へ進めます。 Chart から切り離されているため、UI でのリプレイにもヘッドレスのバックテストにも使えます。

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)

ウィジェットのリプレイモードでは、表示済みのバーをクリックすると、リプレイのカーソルが そこへ移動します。より一般的には、チャートは通常の左クリック(ドラッグせずに押して離す操作)に対して click と barClick イベントを発行するようになりました:

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

チャート上のリプレイを細かいステップで

chart.replayStart() は、チャート自身の系列をリプレイします。下位足のバーを steps として渡すと(1 時間足のチャートに 5 分足のバーなど)、各ステップでそれらから形成中のバーが 育っていきます。実際の市場で形成されたときと同じです。確定済みのバーは系列にあるとおりに表示されます。 startIndex と replaySeekToBar はチャートのバーで数えます。 ChartWidget では、リプレイバーのステップメニューに、フィードが持つ下位の時間足 (または読み込んだバーから組み立てられる時間足)が並びます。

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

リプレイ中のペーパー取引

PaperExecutionAdapter のように setMarkPrice(price, time) を持つ実行アダプターは、 リプレイ上で取引します。リプレイ中、チャートはリプレイされる価格と時刻を順に渡すので、注文や損切りは リプレイがその価格を通過したときに約定し、約定はリプレイ中のバーの上に記録されます。各ステップの安値と高値が 考慮され、先へジャンプすると途中のステップをすべて通過します。進むのは前方向だけで、後ろへシークした後は、 リプレイがそれまでに見た最も先の地点を越えるまで待ちます。 その間、ライブ価格の影響は受けません。リプレイが終わるとライブ価格に戻り、約定時刻も実際の時計に戻ります。 価格アラートは、その間もずっとライブの市場を監視し続けます。インジケーターのラインに対するアラートは、 リプレイが終わるまで待機します。

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

API

メソッド用途
start()現在のインデックスから発行を開始します。
pause() / resume()タイマーを一時停止 / 再開します。
step(n)タイマーを使わずに N 本のバーを同期的に発行します。
seek(index)間のバーを発行せずに移動します。
setSpeed(bps)1 秒あたりのバー数(>= 0.01 に制限)。
destroy()タイマーとリスナーを解除します。

バックテスターとリプレイを組み合わせて使う方法は、分析を参照してください。