見出し画像

bitflyerのRealtimeAPI(JSON-RPC)を利用して、超高速に何度も情報を取得する!

ちょっとぶりです。ボットトレード初心者のモッチオです。
ここのところGWでゆっくりしたり、と思えば犯罪に巻き込まれたりなんだリで、肝心のbot作成・運用技術の研鑽がおろそかになってました。頑張らねば!
さて、わたくし、主戦場はちょっと前までBitMEXだったのですが、せっかくドテン君が作ってくれた運用資金を裁量で見事に焼いてしまい、追加資金を送ろうとしたらBitMEXのアドレスではなくて「ドテンくる」に書き換えられたアドレスだったりで、ちょっとMEXはゲンが悪いところあるなーということで、GW半ばよりbitflyerに戦いの場を移しております。

先日、BitMEXでWebsoketプロトコルを利用して高速に情報を取得するnoteを書かせていただいていましたが、今後bitflyerで戦っていくなら、やはり、bitflyerでも出来るだけ高速に情報を取得したい!何度も取得したい!と・・・思いますよね!やっぱり!

bitflyerの高速アクセス用API「Realtime API」

bitflyerで取引情報への高速アクセスを提供しているのが「Realtime API」です。

bitflyerのAPIドキュメントを見てみると、3種類ほどあるようです。
・Socket.IO 2.0 (WebSocket)
・JSON-RPC 2.0 over WebSocket
・PubNub (廃止を検討中)

PubNubは廃止を検討中となるので、避けたほうがよさそうです。
でもちょっと調べてみると、取得できる情報にはそれぞれ違いは無いようですね。じゃぁ、というわけで、今回もWebSocketを利用している「JSON-RPC」を使ってみることにしました。
さらにドキュメントを見ていくと、「様々な言語を含む、より詳しい使用例は Realtime API Playground を参照してください。」とあります。

JavaScriptをはじめとして、C#、Java、Swiftなどの言語でのサンプルコードが用意されているようですね。
って・・・・Pythonないじゃん!

そうしたわけで・・・

bitflyerで高速に情報を取得するpythonライブラリを書いちゃいました!(RealtimeAPI JSON-RPCを利用)

WebSocket自体は、言語に左右されず利用できるプロトコルですので、pythonでwebsokectを利用して、Realtime API (JSON-RPC)経由で情報を取得するライブラリを作成しました。
しかも今回はGitHubにリポジトリを作成して、アップロード公開しちゃいました。私、コードリポジトリはVisual Source SafeとSubVersionしか使ったことなかったので、Gitの勉強もさせて頂きました。今回はすごく頑張った!

もし、Cloud9でドテン君を運用されているのであれば、下記のコマンドでインストールできます。

sudo pip-3.6 install git+https://github.com/mottio-cancer/py_bitflyer_jsonrpc.git

その他の環境でも、同様で、ご使用されているpipに上記のgitのアドレスを指定してあげてください。

ご利用方法・解説です

GitHubのページに大まかな使用方法は書いてあるのですが、こちらのnoteでは、先のBitMEX WebSocketと同様に、ドテン君Cloud9環境で動作するコードで解説したいと思います。

※※ご注意!※※
下記テストコードの中では、BTC現物を最小単位で買って売る処理が入っています!
一瞬で売り買いするので、ほぼ価格差は出ないと思いますが、ご了承の上、動作させてください。

テストコード:test_py_bitflyer_JSONRPC.py

#!/usr/bin/python3
# coding: utf-8
import sys
from datetime import datetime as dt
import time
import json
import pybitflyer
import py_bitflyer_jsonrpc

API_KEY = "BitflyerのAPI-KEYを入れてください"
SECRET = "BitflyerのSECRET-KEYを入れていください"

# 通貨セット(product_id)を指定します
# 'BTC_JPY','FX_BTC_JPY','ETH_BTC','BTCJPY28APR2017','BTCJPY05MAY2017','BTCJPY29JUN2018'
# など指定できます
symbol = 'BTC_JPY' 

try:
    # Realtime API接続用オブジェクトを生成
    rpc = py_bitflyer_jsonrpc.BitflyerJSON_RPC(symbol=symbol)
    
    # 板のスナップショットを取得します
    snapshot = rpc.get_board_snapshot()
    print("現在の板のスナップショットです:")
    json.dump(snapshot, sys.stdout, indent=2)
    
    # Ticker情報を取得します
    ticker = rpc.get_ticker()
    print("\n\n最新のTicker情報です:")
    json.dump(ticker, sys.stdout, indent=2)
    
    # 成約情報を取得します
    executions = rpc.get_execution()
    print("\n\nプログラム起動から現在までの約定情報です(最大1000件):")
    json.dump(executions, sys.stdout, indent=2)
    
    # 注文のIDを指定して、注文が成約されたのを確認するまで待ちます
    # REST API操作用オブジェクト生成
    bfapi = pybitflyer.API(api_key=API_KEY, api_secret=SECRET)
    # 成行注文を実行(0.001BTCのみ)
    o = bfapi.sendchildorder(
        product_code=symbol, 
        child_order_type="MARKET", 
        minute_to_expire= 60, 
        side='BUY', 
        size=0.001)

    start = time.time()
    while not rpc.get_execution(o['child_order_acceptance_id']):
        print("\n注文ID[{0}]の約定を待っています".format(o['child_order_acceptance_id'] ))
        time.sleep(0.1)

    end = time.time()
    oparation_times = end - start
    
    print("注文ID[{0}]の約定には{1}秒かかりました。".format(o['child_order_acceptance_id'], oparation_times))

    # 反対方向への成行注文を実行(0.001BTCのみ)
    o = bfapi.sendchildorder(
        product_code=symbol, 
        child_order_type="MARKET", 
        minute_to_expire= 60, 
        side='SELL', 
        size=0.001)

    start = time.time()
    while not rpc.get_execution(o['child_order_acceptance_id']):
        print("\n注文ID[{0}]の約定を待っています".format(o['child_order_acceptance_id'] ))
        time.sleep(0.1)

    end = time.time()
    oparation_times = end - start
    
    print("注文ID[{0}]の約定には{1}秒かかりました。".format(o['child_order_acceptance_id'], oparation_times))

except Exception as e:
    print(e)

ご利用方法・解説です

実はBitflyerではリアルタイムで取得できる情報は、「板情報」と「板情報の更新差分情報」、「Ticker情報」、「注文の成約情報」の4つのみとなっています。BitMEXではポジション情報など、REST APIとほぼ変わらない多様な情報を取得できるので、そのあたりは残念なところです。
しかし、やはりリアルタイムのAPI、短時間に繰り返し何度呼び出しても、ちゃんと結果が帰ってきます!よっし!スキャルボット作るぞ!

さて、実行権限を付けて実行すると、1,2秒経ってから、どわわっっとコンソールに情報が出力されます。しかし、板情報のスナップショットが大きすぎて、コンソールの表示限界を突破してしまいました。

なので、コンソールに出力するはずの情報を、ファイル「test_result.txt」に書き出すようにしましょう。

$ ./test_py_bitflyer_JSONRPC.py > test_result.txt

同じフォルダに「test_result.txt」が作成されて、実行結果が出力されているはずです。
それでは、テストコードの記述と、実行結果とを併せて説明させていただきます。

オブジェクトの生成

    # 通貨セット(product_id)を指定します
# 'BTC_JPY','FX_BTC_JPY','ETH_BTC','BTCJPY28APR2017','BTCJPY05MAY2017','BTCJPY29JUN2018'
# など指定できます
symbol = 'BTC_JPY' 
オブ

・・・省略・・・

    # Realtime API接続用オブジェクトを生成
    rpc = py_bitflyer_jsonrpc.BitflyerJSON_RPC(symbol=symbol)

ここでは、Realtime API JSON-RPCを経由して情報を取得するためのオブジェクトを生成しています。symbolには、プロダクトコード(通貨セットです)を指定します。今回はオーソドックスに現物「BTC_JPY」を指定しています。

板情報のスナップショット

    # 板のスナップショットを取得します
    snapshot = rpc.get_board_snapshot()
    print("現在の板のスナップショットです:")
    json.dump(snapshot, sys.stdout, indent=2)

作成したオブジェクトの「get_board_snapshot()」関数を呼び出して、板情報のその時点でのスナップショットを取得しています。
このスナップショット、先のコンソールを埋め尽くしたところでもわかる通り、結構な分量があります。そのせいか、bitflyerのAPIからも数秒に一度しか配信されてきません。
じゃぁ最新の情報じゃないじゃん!と思いました?
ご安心あれ!こちらはリアルタイムに配信される「板の更新情報」を利用して、常に内容を書き換えてあります。
ですので、0.1秒間隔などで取得してもらっても全然大丈夫です。
※余談ですが、起動時に1,2秒ほど止まっているように見えるのは、このスナップ情報が最初に配信されるのを待っているからです。

なお、出力された板情報は、下記の形で取得されています。

{
  "mid_price": 926972,
  "bids": [
    {
      "price": 926652,
      "size": 0.093
    },
    ・・・省略・・・
  ],
  "asks": [
    {
      "price": 927292,
      "size": 0.007
    },
    ・・・省略・・・
  ]
}

辞書オブジェクトの中に、”mid_price”(平均値)、"bids"(売り板情報の配列)、"asks"(買い板情報の配列)が入っている形ですね。この辺りはbitflyerの「 Realtime API Playground」などでも確認して頂けます。

Ticker情報

    # Ticker情報を取得します
    ticker = rpc.get_ticker()
    print("\n\n最新のTicker情報です:")
    json.dump(ticker, sys.stdout, indent=2)

「get_ticker()」関数を呼び出して、Ticker情報を取得しています。
内容は次の通り

{
  "product_code": "BTC_JPY",
  "timestamp": "2018-05-14T12:43:41.4735995Z",
  "tick_id": 19847164,
  "best_bid": 926652,
  "best_ask": 927292,
  "best_bid_size": 0.093,
  "best_ask_size": 0.007,
  "total_bid_depth": 1944.35177735,
  "total_ask_depth": 3645.38899287,
  "ltp": 926644,
  "volume": 16788.14389391,
  "volume_by_product": 16788.14389391
}

成約情報

    # 成約情報を取得します
    executions = rpc.get_execution()
    print("\n\nプログラム起動から現在までの約定情報です(最大1000件):")
    json.dump(executions, sys.stdout, indent=2)

「get_execution()」関数を呼び出して、プログラムを開始してから受信している注文の成約情報を取得しています。
最大1000件までの成約情報を保持するようにしています。
内容は次の通り。

[
  {
    "id": 224803227,
    "side": "SELL",
    "price": 926422,
    "size": 0.01,
    "exec_date": "2018-05-14T13:15:39.8055627Z",
    "buy_child_order_acceptance_id": "JRF20180514-131539-710731",
    "sell_child_order_acceptance_id": "JRF20180514-131539-211275"
  },
  {
    "id": 224803228,
    "side": "SELL",
    "price": 926412,
    "size": 0.08,
    "exec_date": "2018-05-14T13:15:39.8055627Z",
    "buy_child_order_acceptance_id": "JRF20180514-221513-591152",
    "sell_child_order_acceptance_id": "JRF20180514-131539-211275"
  }
]

「buy_child_order_acceptance_id」「sell_child_order_acceptance_id」は、それぞれ買い側、売り側の注文時に返却される「child_order_acceptance_id」が入っています。

発注した注文が成約されるまで待つ

さて、注文時のIDが、成約されたときに配信されるなら、自分の注文が確かに制約されたのか確認したい・・・ですよね?
もしくは制約されるまで待ってからある処理をしたい・・・というケースはよくありますよね!
それで、あらかじめ「get_execution()」関数の引数に「child_order_acceptance_id」を指定して実行することで、その「child_order_acceptance_id」に関連する成約データだけ返却するように作ってみました。
使いかたはテストコードにある通り、下記のとおりです。

    # 注文のIDを指定して、注文が成約されたのを確認するまで待ちます
    # REST API操作用オブジェクト生成
    bfapi = pybitflyer.API(api_key=API_KEY, api_secret=SECRET)
    # 成行注文を実行(0.001BTCのみ)
    o = bfapi.sendchildorder(
        product_code=symbol, 
        child_order_type="MARKET", 
        minute_to_expire= 60, 
        side='BUY', 
        size=0.001)

    start = time.time()
    while not rpc.get_execution(o['child_order_acceptance_id']):
        print("\n注文ID[{0}]の約定を待っています".format(o['child_order_acceptance_id'] ))
        time.sleep(0.1)

    end = time.time()
    oparation_times = end - start
    
    print("注文ID[{0}]の約定には{1}秒かかりました。".format(o['child_order_acceptance_id'], oparation_times))

REST APIを利用できるpybitflyerを使用して成行注文を実行し、返却された情報の「child_order_acceptance_id」を指定して「get_execution」メソッドを呼び出しています。
このとき、while分の処理継続条件の中に「not rpc.get_execution(o['child_order_acceptance_id'])」と指定していますね。
指定した'child_order_acceptance_id'に関連した成約情報がまだ届いていない場合は空の配列が返却されますので、これはTrue/Falseで解釈すると「False」になります。
get_executionは成約情報が届くまで空配列を返し続けるので、プログラムはそれを待つ挙動になります。

ファイルに出力された情報を見てみましょう。

注文ID[JRF20180514-131540-383072]の約定を待っています
注文ID[JRF20180514-131540-383072]の約定を待っています
注文ID[JRF20180514-131540-383072]の約定には0.20081028938293457秒かかりました。

現物だからでしょうか、割とすぐに成約されて、情報も配信されていますね!今回は2回だけwhile文の中の内容が実行されたようです。
発注から成約の確認まで、約0.2秒でした。

すみません!途中からだいぶ疲れてしまったのか、駆け足での説明でしたが、大丈夫でしたでしょうか。
もし何か問題、わからないことあれば、コメントか「AKAGAMI LOUNGE」でお声をおかけください!

毎回、なんでだよ!っていうくらい長文ですみません!最後まで読んでくださって、本当にありがとうございます!

余談

今回、現物の「BTC_JPY」でテストコードを書きましたが、「FX_BTC_JPY」で利用したときに「板情報のスナップショット」で妙な現象が起きています。
データ取得本の「https://lightning.bitflyer.jp/docs/playgroundrealtime」からしてそうなのですが、bidsとasksの最小値・最大値の差が広すぎます。

        {
            "price": 990668,
            "size": 0.805
        }
    ],
    "asks": [
        {
            "price": 1004800,
            "size": 0.84066004

※2018/5/15 午前3時ごろ取得

取引画面と見比べてみると、「bids」に余計な安すぎる情報が入っていて、かなり食い込んだ形になってしまっているようです。なんで?

この記事が気に入ったらサポートをしてみませんか?