Bitcoin のリアルタイムチャートを Chart.js で表示する

(*English translation is here)

Chart.js は動的で美しいチャートを手軽に作ることができるポピュラーな JavaScript ライブラリです。先日、リアルタイムストリーミングデータの表示に便利な自動スクロール機能を実装した chartjs-plugin-streaming プラグインを作りました。センサーデータのモニタリング等の IoT 関連の用途に最適ですが、何かリアルなストリーミングデータないかなー、と探していたところ、仮想通貨の取引所が配信している取引価格の表示にもぴったりな感じなので、早速試してみました。

最近は WebSocket を使って効率的にストリーミング配信を行う取引所も増えてきており、中には認証を必要とする場合もあるのですが、本記事では特に認証の不要なパブリック API を提供している取引所の取引チャートを作ってみましょう。

最初の例: Bitfinex WebSocket API

まずは必要なライブラリを組み込みます。

下記を<head>〜</head>にでも書いておきます。

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.js"></script>
<script type="text/javascript" src="https://github.com/nagix/chartjs-plugin-streaming/releases/download/v1.2.0/chartjs-plugin-streaming.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.js"></script>

次に、ページに Canvas を設置します。チャートを区別できるように、id をつけておきます。

<canvas id="Bitfinex"></canvas>

そして、データをバッファリングしておくためのオブジェクト buf を用意します。先ほど設置した Canvas の id と同じ名前のプロパティに、2つの空の配列要素を持つ配列を用意しておきます。1つ目の要素は買い値用、2つ目は売り値用です。

<script type="text/javascript">
var buf = {};
buf['Bitfinex'] = [[], []];
</script>

いよいよ実際のデータを取りに行く番です。WebSocket を使って、対米ドル取引で最大手の取引所 Bitfinex(香港)のリアルタイム取引データを取得します。Bitfinex が用意している URI wss://api.bitfinex.com/ws に次のような内容のリクエストを送信することで、ビットコイン/米ドルのペアの取引データを購読(Subscribe)することができます。(Bitfinex WebSocket API の詳細はこちら

{
    "event": "subscribe", // 購読リクエスト
    "channel": "trades",  // 取引データ
    "pair": "BTCUSD"      // ビットコイン/米ドル
}

コールバック関数を介して取得できるデータは次のような形式です。

[
    5,             // 0: チャンネル ID
    'te',          // 1: メッセージタイプ
    '1234-BTCUSD', // 2: シーケンス ID
    1443659698,    // 3: タイムスタンプ
    236.42,        // 4: 価格
    0.49064538     // 5: 取引量(買いならプラス、売りならマイナス)
]

必要なデータは売り買いの種別、タイムスタンプ(X軸)、価格(Y軸)だけなので、次のようなコードで更新があるたびにバッファにデータを追加していきます。

var ws = new WebSocket('wss://api.bitfinex.com/ws/');
ws.onopen = function() {
    ws.send(JSON.stringify({      // 購読リクエストを送信
        "event": "subscribe",
        "channel": "trades",
        "pair": "BTCUSD"
    }));
};
ws.onmessage = function(msg) {     // メッセージ更新時のコールバック
    var response = JSON.parse(msg.data);
    if (response[1] === 'te') {    // メッセージタイプ 'te' だけを見る
        buf['Bitfinex'][response[5] > 0 ? 0 : 1].push({
            x: response[3] * 1000, // タイムスタンプ(ミリ秒)
            y: response[4]         // 価格(米ドル)
        });
    }
}

最後にチャートの設定です。Chart.js のチャートのカスタマイズについては公式ドキュメントを参考にしてもらうとして、ここでのポイントは chartjs-plugin-streaming プラグインにより追加される 'realtime' タイプの目盛りを X 軸に設定していることと、定期的に(デフォルトでは1秒に1回)呼び出される onRefresh コールバック関数の中で、バッファに溜まっているデータを丸々チャートに追加しているところです。

var id = 'Bitfinex';
var ctx = document.getElementById(id).getContext('2d');
var chart = new Chart(ctx, {
    type: 'line',
    data: {
        datasets: [{
            data: [],
            label: 'Buy',                     // 買い取引データ
            borderColor: 'rgb(255, 99, 132)', // 線の色
            backgroundColor: 'rgba(255, 99, 132, 0.5)', // 塗りの色
            fill: false,                      // 塗りつぶさない
            lineTension: 0                    // 直線
        }, {
            data: [],
            label: 'Sell',                    // 売り取引データ
            borderColor: 'rgb(54, 162, 235)', // 線の色
            backgroundColor: 'rgba(54, 162, 235, 0.5)', // 塗りの色
            fill: false,                      // 塗りつぶさない
            lineTension: 0                    // 直線
        }]
    },
    options: {
        title: {
            text: 'BTC/USD (' + id + ')', // チャートタイトル
            display: true
        },
        scales: {
            xAxes: [{
                type: 'realtime' // X軸に沿ってスクロール
            }]
        },
        plugins: {
            streaming: {
                duration: 300000, // 300000ミリ秒(5分)のデータを表示
                onRefresh: function(chart) { // データ更新用コールバック
                    Array.prototype.push.apply(
                        chart.data.datasets[0].data, buf[id][0]
                    );            // 買い取引データをチャートに追加
                    Array.prototype.push.apply(
                        chart.data.datasets[1].data, buf[id][1]
                    );            // 売り取引データをチャートに追加
                    buf[id] = [[], []]; // バッファをクリア
                }
            }
        }
    }
});

出来上がったチャートはこのような感じ。ゆっくりスクロールしているのがわかるでしょうか。

Bitfinex

Bitstamp WebSocket API

次は欧州での取引の中心となっているイギリスの取引所 Bitstamp の例です。Bitstamp ではデータのストリーミングに Pub/Sub メッセージングライブラリ Pusher を利用しています。このため、データの取得コードはよりシンプルです。(Bitstamp WebSocket API の詳細はこちら

コールバック関数を介して取得できるデータは次のような形式です。

{
    id: 17044523,            // 取引固有 ID
    amount: 1,               // 取引量
    price: 2496.21,          // 価格
    type: 1,                 // 取引タイプ(0: 買い、1: 売り)
    timestamp: "1499472674", // タイムスタンプ
    buy_order_id: 47485421,  //	買い注文 ID
    sell_order_id: 47485426  //	売り注文 ID
}

データの取得コードは次の通りです。こちらも売り買いの種別、タイムスタンプ、価格のみを見ています。

buf['Bitstamp'] = [[], []]; // バッファを用意
var pusher = new Pusher('de504dc5763aeef9ff52'); // Bitstamp 専用のキーを指定
var channel = pusher.subscribe('live_trades'); // ライブ取引データを購読
channel.bind('trade', function (data) { // データ更新時のコールバック
    buf['Bitstamp'][data.type].push({
        x: data.timestamp * 1000, // タイムスタンプ(ミリ秒)
        y: data.price             // 価格(米ドル)
    });
});

チャートの設定は id の部分を変えるだけで全く同じです。完成したチャートはこの通り。

Bitstamp

BTC-E WebSocket API

続いてこちらも有名なブルガリアの取引所 BTC-E。BTC-E でも Pusher を使った配信が行われています。(BTC-E WebSocket API の詳細はこちら

データの形式は次の通り。

[
    [
        "buy",       // 0: 取引タイプ
        "2476.999",  // 1: 価格
        "0.08863539" // 2: 取引量
    ]
]

BTC-E ではタイムスタンプが流れてこないので、Date.now() で現在の時刻をセットしています。

buf['BTC-E'] = [[], []]; // バッファを用意
var pusher = new Pusher('c354d4d129ee0faa5c92'); // BTC-E 専用のキー
var channel = pusher.subscribe('btc_usd.trades'); // 対米ドル取引データを購読
channel.bind('trades', function (dataset) { // データ更新時のコールバック
    dataset.forEach(function(data) {
        buf['BTC-E'][data[0] === 'buy' ? 0 : 1].push({
            x: Date.now(), // タイムスタンプ(ミリ秒)
            y: data[1]     // 価格(米ドル)
        });
    });
});

BTC-E

BitMEX WebSocket API

ハイレバレッジ取引で有名な香港の BitMEX です。ここは素の WebSocket API です。リクエストとデータの形式は次の通りです。(BitMEX WebSocket API の詳細はこちら

{
    "op": "subscribe", // 購読リクエスト
    "args": [
        "trade:XBTUSD" // ビットコイン/米ドル
    ]
}
{
    table: "trade",
    action: "insert",
    data: [
        {
            timestamp: "2017-07-09T01:39:30.866Z", // タイムスタンプ
            symbol: "XBTUSD",   // 通貨ペアシンボル
            side: "Buy",        // 取引タイプ
            size: 34,           // 取引量
            price: 2548.9,      // 価格
            tickDirection: "ZeroPlusTick", // Tick の方向
            trdMatchID: "34d6de97-5d54-3431-e505-ffc3bc8c58ef",
            grossValue: 2039284,      // 総価値
            homeNotional: 0.02039284, // 想定元本(ビットコイン)
            foreignNotional: 52       // 想定元本(米ドル)
        }
    ]
}

コードとチャートはこのようになります。

buf['BitMEX'] = [[], []]; // バッファを用意
var ws = new WebSocket('wss://www.bitmex.com/realtime');
ws.onopen = function() {
    ws.send(JSON.stringify(    // 購読リクエストを送信
       "op": "subscribe",
       "args": [
            "trade:XBTUSD"
        ]
    }));
};
ws.onmessage = function(msg) { // メッセージ更新時のコールバック
    var response = JSON.parse(msg.data);
    response.data.forEach(function(data) {
        buf['BitMEX'][data.side === 'Buy' ? 0 : 1].push({
            x: data.timestamp, // タイムスタンプ
            y: data.price      // 価格(米ドル)
        });
    });
}

BitMEX

CoinCheck WebSocket API

最後は日本の取引所 CoinCheck です。対日本円のデータのみが配信されているので、それを表示してみましょう。リクエストとデータの形式は次の通りです。(CoinCheck WebSocket API の詳細は こちら

{
    "type": "subscribe",        // 購読リクエスト
    "channel": "btc_jpy-trades" // ビットコイン/日本円
}
[
    9856377,    // 取引 ID
    "btc_jpy",  // 通貨ペア
    "289544.0", // 価格
    "0.0367",   // 取引量
    "sell"      // 取引タイプ
]

コードとチャートは次の通りです。

buf['CoinCheck'] = [[], []];
var ws = new WebSocket('wss://ws-api.coincheck.com/');
ws.onopen = function() {
    ws.send(JSON.stringify({        // 購読リクエストを送信
        "type": "subscribe",
        "channel": "btc_jpy-trades"
    }));
};
ws.onmessage = function(msg) { // メッセージ更新時のコールバック
    var response = JSON.parse(msg.data);
    buf['CoinCheck'][response[4] === 'buy' ? 0 : 1].push({
        x: Date.now(), // タイムスタンプ(ミリ秒)
        y: response[2] // 価格(日本円)
    });
}

CoinCheck

これ以外にも、多くの取引所がリアルタイムデータを配信する API を提供しています。それぞれ微妙に形式が異なりますが、最小限の変更でチャートの表示ができますので、ご興味をお持ちの方は参考にしてください。