暗号通貨取引所毎の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(共通部分のコード)
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
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())
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())
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())
追記
・以前、公開していたbitflyerとbitmexの個別の記事は削除しました。
更新履歴
・2019/08/31 公開
・2019/08/03 命名規則の統一
・2019/10/18 注意追加
・2020/01/22 記事をnoteからqiitaに移動