https://www.ultra-noob.com/vuedemos/2020-04-28_spread_demo/
※実際にbitFlyerに接続してリアルタイムにデータを受信する
差分の更新情報は非常に多くBest Bit, Best Ask付近のみであればスナップショットをマージせず構築できてしまうのではと考えていたがこの実装ではスプレッドが異常値(マイナス)を示すことが散見された。
そのためスナップショット受信時にPC内部に蓄積しているデータを一旦消去し、スナップショットの情報だけで新規板とすると殆どエラーは出なくなった。
これはスナップショットが更新されるタイミングで、スナップショット情報に本来差分で送信される分が含まれておりこのタイミングの差分情報は送信されていないのでは、というのが私の考えである。
つまり、差分のみ受信(スナップショットを無視)していると板のデータは崩れると考えているが、100%正しいとは言い切れないので知見があれば教えていただければと思う。
データの送受信が早いので通信の関係で差分情報が欠損している可能性もある。
今回もこれまで板情報をデプスチャート化したときのデータと同じ物を利用した。
bitFlyerからは {price: 価格, size: サイズ }
の配列が送られてくるが { 価格 : サイズ }
に変換しており、高値・安値の調査とその数量をコード上処理しやすくしている。
この構造で処理速度の問題はあまり感じていない。
この実装に合わせて謎のチャートに実装されているデプスチャートの内部ロジックもスナップショットで逐次リセットされるように修正した。
板情報を目視していると確かに数百円のスプレッドは頻繁に起こる。が、最速であるはずの差分更新タイミングで見ても一瞬で埋まるようにも見える。
この程度では1ミリもマーケットメイク出来るようには見えない。更に上を目指すにはどのような処理をすればよいのだろうか。
凄腕botterと比べるとまだ入り口にも立っていない印象だ。
vue.jsおよびvue-chart.jsのコードのため非常に理解しにくいが何かの参考になるかもしれないので公開しておく。
Spread.vue
<template>
<div>
<SpreadChart :chart-data="datacollection" :options="options" />
</div>
</template>
<script>
import SpreadChart, { renewSpread } from "./SpreadChart.js";
export default {
components: {
SpreadChart
},
props: ["boardMessage"],
data: () => ({
datacollection: {
labels: [],
datasets: [
{
label: [],
data: []
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [
{
position: "right"
}
]
}
},
maxCnt: 30
}),
methods: {},
watch: {
boardMessage() {
this.datacollection = renewSpread(
this.boardMessage,
this.datacollection,
this.maxCnt
);
}
}
};
</script>
SpreadChart.js
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
mixins: [Line, reactiveProp],
props: ['options'],
mounted() {
this.renderChart(this.data, this.options)
}
}
// bidsとasksが{ 価格: 数量 }の構造版
export const renewSpread = (message, datacollection, maxCnt) => {
let best_bid_price = null
let best_ask_price = null
//bid max
for (const d in message.bids) {
if (best_bid_price === null) {
best_bid_price = d
} else {
best_bid_price = d > best_bid_price ? d : best_bid_price
}
}
//asks min
for (const d in message.asks) {
if (best_ask_price === null) {
best_ask_price = d
} else {
best_ask_price = d < best_ask_price ? d : best_ask_price
}
}
const spread = best_ask_price - best_bid_price
if (spread < 0) {
console.log(`Warning: Minus Spread ${spread}`);
}
//ラベルは「時:分:秒」で表示
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
}
※一部コードのみ
//スナップショット
this.socket.on("lightning_board_snapshot_FX_BTC_JPY", message => {
this.board_data = renewBoard(message, this.board_data, true)
});
//差分
this.socket.on("lightning_board_FX_BTC_JPY", message => {
this.board_data = renewBoard(message, this.board_data) //差分を挿入or削除(ソートされていない)
});
APIから受け取ったmessageをrenewBoard関数を通してPC内部に板を構築している
第三引数のtrueはスナップショットの合図(内部板をリセットするフラグ)
renewBoard
//データの挿入と削除
export const renewBoard = (message, board_data, isSnapshot) => {
board_data.mid_price = message.mid_price
if (isSnapshot) { //スナップショットで板をリセット
board_data.asks = {}
board_data.bids = {}
console.log("info: Snapshot Received")
}
for (const ask of message.asks) {
ask.size === 0 ?
delete board_data.asks[ask.price] :
board_data.asks[ask.price] = ask.size
}
for (const bid of message.bids) {
bid.size === 0 ?
delete board_data.bids[bid.price] :
board_data.bids[bid.price] = bid.size
}
//リアクティブ用に新しいオブジェクトを生成
const return_board_data = {
mid_price: board_data.mid_price,
asks: board_data.asks,
bids: board_data.bids
}
return return_board_data
}