LoginSignup
17
21

More than 3 years have passed since last update.

BTC取引所毎の板情報取得 (python3): bitFlyer, BitMEX, Binance

Last updated at Posted at 2020-01-22

暗号通貨取引所毎のBTCの板情報を取得するコードをまとめた記事です。
この記事はGithubで公開しているコードの一部を切り出して、読みやすく書き換えたものです。
通貨ペアの部分を書き換えれば他の通貨ペアでもそのまま使えるかと思います。

【使い方】
base.py(共通部分のコード)と取引所のコードをコピペして使ってください。
$python3 bitflyer.py
の様にそのまま実行すれば動作確認用の関数test()が呼び出されます。
bids(), asks()はそれぞれbest bid, best askに近い順から[price, size]を返すviewオブジェクトを返却します。
priceは通貨ペアの基軸通貨(bitflyer=円, bitmex=ドル)で、sizeはbtcで統一しています。
基本的な使い方はtest()を参考にしてください。

【実装について(読まなくてもOKなやつ)】
SortedDictは追加した要素が自動的にkeyの値で昇順にソートされるシーケンス型です。要素の追加・削除は計算量O(log(n))であるため、O(1)の組み込みのdictに比べると若干遅いですが、板情報を現在価格から近い順に参照したい場合、追加のsort(通常計算量がO(n log(n))以上)が必要ないため、結果的に高速に動作します。

【依存ライブラリ】
websocket_client, sortedcontainers

【動作確認環境】
Arch Linux(5.2.9-arch1-1-ARCH), Python 3.7.4, websocket-client 0.56.0, sortedcontainers 2.1.0

・base.py(共通部分のコード)

base.py
import threading
import time
import websocket
from sortedcontainers import SortedDict


class Orderbook:
    def __init__(self, url):
        self.url = url
        self.sd_bids = SortedDict()
        self.sd_asks = SortedDict()
        self.thread = threading.Thread(target=lambda: self.run())
        self.thread.daemon = True
        self.thread.start()

    def bids(self):
        return self.sd_bids.values()

    def asks(self):
        return self.sd_asks.values()

    def run(self):
        while True:
            self.sd_bids, self.sd_asks = SortedDict(), SortedDict()
            self.ws = websocket.WebSocketApp(
                self.url,
                on_open=lambda ws: self.on_open(ws),
                on_close=lambda ws: self.on_close(ws),
                on_message=lambda ws, msg: self.on_message(ws, msg),
                on_error=lambda ws, err: self.on_error(ws, err))
            self.ws.run_forever()
            time.sleep(1)

    def on_open(self, ws):
        pass

    def on_close(self, ws):
        pass

    def on_message(self, ws, msg):
        pass

    def on_error(self, ws, err):
        pass


def test(ob):
    # import websocket
    # websocket.enableTrace(True)
    try:
        while True:
            for p, s in ob.asks()[10::-1]:
                print(f'{p:<10}{s:>10.3f}')
            print('==== Order Book ====')
            for p, s in ob.bids()[:10]:
                print(f'{p:<10}{s:>10.3f}')
            print('')
            time.sleep(0.5)
    except KeyboardInterrupt:
        pass
bitflyer.py
import json
from sortedcontainers import SortedDict
from base import Orderbook, test


class BitflyerOrderbook(Orderbook):
    ENDPOINT = 'wss://ws.lightstream.bitflyer.com/json-rpc'
    SYMBOL = 'FX_BTC_JPY'

    def __init__(self):
        super().__init__(self.ENDPOINT)
        self.ch_snapshot = f'lightning_board_snapshot_{self.SYMBOL}'
        self.ch_update = f'lightning_board_{self.SYMBOL}'

    def on_open(self, ws):
        def subscribe(ch):
            ws.send(json.dumps(
                {"method": "subscribe", "params": {"channel": ch}}))
        subscribe(self.ch_snapshot)
        subscribe(self.ch_update)

    def on_message(self, ws, msg):
        msg = json.loads(msg)
        if msg['method'] != 'channelMessage':
            return

        p = msg['params']
        ch = p['channel']
        m = p['message']
        if ch == self.ch_snapshot:
            bids, asks = SortedDict(), SortedDict()
            self.update(bids, m['bids'], -1)
            self.update(asks, m['asks'], 1)
            self.sd_bids, self.sd_asks = bids, asks
        elif ch == self.ch_update:
            self.update(self.sd_bids, m['bids'], -1)
            self.update(self.sd_asks, m['asks'], 1)

    def update(self, sd, d, sign):
        for i in d:
            p, s = int(i['price']), i['size']
            if s == 0:
                sd.pop(p*sign, None)
            else:
                sd[p*sign] = [p, s]


if __name__ == '__main__':
    test(BitflyerOrderbook())
bitmex.py
import json
from base import Orderbook, test


class BitmexOrderbook(Orderbook):
    ENDPOINT = 'wss://www.bitmex.com/realtime'
    SYMBOL = 'XBTUSD'
    ORDERBOOK = 'orderBookL2_25'  # 'orderBookL2'

    def __init__(self):
        super().__init__(
            f'{self.ENDPOINT}?subscribe={self.ORDERBOOK}:{self.SYMBOL}')

    def on_message(self, ws, msg):
        msg = json.loads(msg)
        if 'table' not in msg or msg['table'] != self.ORDERBOOK:
            return

        action, data = msg['action'], msg['data']
        if action in ['partial', 'insert']:
            for d in data:
                sd, key = self.sd_and_key(d)
                price, size = d['price'], d['size']
                sd[key] = [price, size/price]
        elif action == 'update':
            for d in data:
                sd, key = self.sd_and_key(d)
                e = sd[key]
                e[1] = d['size'] / e[0]
        elif action == 'delete':
            for d in data:
                sd, key = self.sd_and_key(d)
                sd.pop(key, None)

    def sd_and_key(self, data):
        if data['side'] == 'Buy':
            return self.sd_bids, data['id']
        else:
            return self.sd_asks, -data['id']


if __name__ == '__main__':
    test(BitmexOrderbook())
binance.py
import json
from sortedcontainers import SortedDict
from base import Orderbook, test


class BitflyerOrderbook(Orderbook):
    ENDPOINT = 'wss://stream.binance.com:9443/ws'  # Binance 現物
    # ENDPOINT = 'wss://fstream.binance.com/ws'  # Binance future
    SYMBOL = 'btcusdt'

    def __init__(self):
        super().__init__(self.ENDPOINT)

    def on_open(self, ws):
        ws.send(json.dumps({
            'method': 'SUBSCRIBE',
            'params': [f'{self.SYMBOL}@depth'], 'id': 1}))

    def on_message(self, ws, msg):
        msg = json.loads(msg)
        if 'id' in msg:
            print(msg)
        else:
            self.update(self.sd_bids, msg['b'], -1)
            self.update(self.sd_asks, msg['a'], 1)

    def update(self, sd, d, sign):
        for i in d:
            p, s = float(i[0]), float(i[1])
            if s == 0:
                sd.pop(p * sign, None)
            else:
                sd[p * sign] = [p, s]


if __name__ == '__main__':
    test(BitflyerOrderbook())

実行サンプル
picture_pc_71746d26f7b533c15b451c524e4a2849.png

追記
・以前、公開していたbitflyerとbitmexの個別の記事は削除しました。

更新履歴
・2019/08/31 公開
・2019/08/03 命名規則の統一
・2019/10/18 注意追加
・2020/01/22 記事をnoteからqiitaに移動

17
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
21