2020-04-26

【vue-chart.js】bitFlyerの板:スプレッドを可視化【謎のチャート】

スプレッド可視化の最初の一歩。本日もvue-chart.jsを利用する。

Article Image

vue-chart.jsを使った可視化

vue-chart.jsに関しては私の記事で色々実験しているのでvue-chart関連の記事を参考に別のページも回って欲しい。

注意:非実用的記事

この記事は次に書く予定の「板差分からスプレッドを計算する」につなげる記事であり、この記事だけでは実用性にかける。

新しく覚えること

Best ASK - Best Bid = Spread

この計算式だけだ。一応混乱がないように説明すると

  • Best Ask = 売り板の最安値
  • Best Bid = 買い板の最高値

この買い板と売り板の差がスプレッドと呼ばれる。

mmbotはこの幅を細かく売り買いすることで収益を上げる仕組みであるためmmbot制作にスプレッドの理解は必須事項だ(私のBLOGはmm制作がメイン目標ではないので注意)

取引所とAPI

今回も引き続きbitFlyerのRealtime APIからの板情報を元にスプレッドを計算していく。

スプレッドに関してはTickerを使っても計算できるが最も情報が早いのが「板の差分」情報であるため、こちらからスプレッドを計算していくのがベストだ。

また通信にはSocket.ioを利用している。bitFlyerのSocket.ioによるAPI取得はかなり詳しく解説したので自動売買システムのタグ一覧の最初から記事を読んでもらえば理解できるはずだ。

目標:スナップショットからスプレッドを計算

冒頭でも書いたが本日はスナップショットを利用して練習バージョンを作成する。いきなり高速な差分情報を元に計算していくとエラーの温床となるため前段階の記事とした。

今回の実装例(動画)

※今回はSnapshotのみの実装なので非常に更新が遅い。動画は3倍速になっている。

コード

まずはsnapshotを受信する。

  socket.on("lightning_board_snapshot_FX_BTC_JPY", message => {
    this.snapshotMessage = message //テスト用
  });

messageをsnapshotMessageの変数に格納して保存。bitFlyerから受け取ったデータをそのまま送っているだけだ。

this.snapshotMessageはdata部で {} にて空のオブジェクトで定義されている。

コンポーネントとして登録

メインのvueからSpreadという名前の子コンポーネントとして登録した。

この時にsnapshotMessageを引き渡すこと。内部でも同じ変数名を利用する。

  <!-- スプレッドコンポーネント -->
  <Spread :spreadBoardData="snapshotMessage" />

Spread.vue

子コンポーネントの中身

<template>
  <div>
    <SpreadChart :chart-data="datacollection" :options="options" />
  </div>
</template>

<script>
import SpreadChart, { renewSpread } from "./SpreadChart.js";

export default {
  components: {
    SpreadChart
  },
  props: ["spreadBoardData"],
  data: () => ({
    datacollection: {
      labels: [],
      datasets: [
        {
          label: [],
          data: []
        }
      ]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      scales: {
        yAxes: [
          {
            position: "right"
          }
        ]
      }
    },
    maxCnt: 30
  }),
  methods: {
  },
  watch: {
    spreadBoardData() {
      this.datacollection = renewSpread(
        this.spreadBoardData,
        this.datacollection,
        this.maxCnt
      );
    }
  }
};
</script>

ここで注意すべきはdata部にてdatacollectionには必ずキー名を列挙しておくということ。これを怠ってnull等で定義するとVue.jsの仕様によりリアクティブではないデータになってしまう(データを入れてもチャートが更新されない)

またここで一緒にoptionsの初期値を設定しておく。

  • maintainAspectRatio: trueにしてしまうとディスプレイの高さ一杯に描画されるので見にくくなる。基本falseで良い。
  • position: Y軸のラベルを右側に表示するようにしている。

watch部

親から受け取ったsnapshotMessageを監視しこれが更新されたときに内部のコードが逐一実行される仕組み。

ここでは次で説明するrenewSpread関数にメッセージを引き渡している。

SpreadChart.js

以前説明したがvue-chart.jsの仕様でグラフを描画するコードは必ずファイルを分けて読み込む。

ファイルが2つ必要な仕組みについては以前の記事:やたら易しいvue-chart.js解説 にて解説しているので参照してほしい。

合わせてこちらののファイルに先程のrenewSpread関数を定義した。

renewSpread関数

import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
    mixins: [Line, reactiveProp],
    props: ['options'],
    mounted() {
        this.renderChart(this.data, this.options)
    }
}

export const renewSpread = (message, datacollection, maxCnt) => {
    let best_bid_price = message.bids[0].price;
    let best_ask_price = message.asks[0].price;

    //bid max
    for (const d of message.bids) {
        best_bid_price = d.price > best_bid_price ? d.price : best_bid_price;
    }
    //asks min
    for (const d of message.asks) {
        best_ask_price = d.price < best_ask_price ? d.price : best_ask_price;
    }

    if (best_ask_price < best_bid_price) {
        console.log("err: best bid > best ask");
    }

    const spread = best_ask_price - best_bid_price;

    //ラベルは「時:分:秒」で表示
    const date = new Date();
    const time =
        date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();


    datacollection.labels.push(time);
    datacollection.datasets[0].data.push(spread)

    //画面に表示する件数を超えたらshiftで削る
    while (datacollection.labels.length > maxCnt) {
        datacollection.labels.shift()
        datacollection.datasets.forEach((array) => {
            array.data.shift()
        })
    }

    const return_datacollection = {
        labels: datacollection.labels,
        datasets: [
            {
                label: "Spread",
                data: datacollection.datasets[0].data,
                fill: false,
                borderColor: "blue"
            },
        ]
    }

    return return_datacollection

}

bitFlyerからのデータが直接入っているだけのmessageからBest Ask, Best Bidを抽出しspreadという定数に格納している。

ここで最も重要なのはアルゴリズムではなくdatacollectionを引数で受け取っているにもかかわらずわざわざconst return_datacollectionとして全く同じ構造の新しいオブジェクトをreturnしている点だが、これはvue-chart.jsの仕様によるものである。

datacollection = 参照渡しではなく新しいdatacollection というコードにしないと描画がリアクティブにならないので覚えておきたい。

またmaxCntによりしきい値を設け一定の件数を上回り次第左側のグラフデータを1件削って必要以上に表示しないようにしている。

vue-chart.js新しいTips

このコードを書いていて気づいたことを書いておく。

  • optionsはリアクティブではないがdatacollectionが描画される時に変更できる

    optionsは一度設定したら変更出来ないものだと思っていた。変更できないのではなくリアクティブでないだけなので注意だ。

  • data部のdatacollectionの値には何も入れてはいけない

    データセット名が「Spread」のみなのでdatasets[0].labelの値に初め初期値として"Spread"を入れていたが、コード上から上書きしようとするとエラーが出た。詳しくは不明。

所感

今回は内容が簡単であると同時に今までvue-chart.jsを何度も使ってきたので比較的短時間に実装できた。

じっくり見ていると数百円のスプレッドは頻繁にあらわれるためmmbotというものの存在も頷ける。

次は「差分」を使って高速に更新していこうと考えているが、若干コードが複雑になりそうだ。

※この記事の内容は公開されている謎のチャートに実装していない。 ある程度まとまってから公開版への実装を予定している。



この記事をシェア


謎の技術研究部 (謎技研)