見出し画像

bitFlyerの約定履歴をリアルタイムAPI(JSON-RPC)を用いて日付ごとにCSVファイルで自動保存するPython3スクリプト

目次

はじめに
本noteが提供するコード情報
前提
ソースコード解説
実際のソースコード (executions_realtime.py)
よくある質問

はじめに

トレードbot作成の戦略構築・バックテスト・検証には約定履歴が必要になるケースがあります。特に、mmbotなどの高速なトレードにおいては、OHLCV情報だけでは正確なバックテストができるわけもなく、約定履歴ベースの検証は避けて通れません。

約定履歴はbitFlyerのREST APIにより500件ずつ取得できます。ただし、REST APIはRealtime APIに比べて情報の送受信が遅く、また、APIリミット・オーバーヘッドも存在するため、Realtime APIを用いて自動保存する仕組みを事前に構築しておくと良いでしょう。

ちなみに、mmbot爆速バックテストプログラム「MMBT」 (まだバックテストで消耗してるの?bitFlyerFX約定履歴をCSVファイルからPandasデータフレームへと読み込み、mmbotのバックテストを8000倍の速度で実行するPython3スクリプト) でも本noteで出力した約定履歴を使うことができます。

また、板情報の取得についても別のnoteにて紹介しています。

本noteが提供するコード情報

本noteでは、Realtime API (JSON-RPC)を用いて約定履歴を取得し、CSVファイルに出力するためのPython3スクリプトを提供します。70行ほどのコードです。

ああああさんが書いている「[Python Websocket JSON-RPC OHLCV] bitFlyer の約定情報の遅延計測結果」のコードを参考にしています。

違いは、本noteでは

・最新5000件のCSVファイル (executions_realtime.csv)
・日ごとにローテーションされるCSVファイル (exec_MM-DD.csv)

の2つを同時に出力していることです。

前者はトレードbotでの実稼働用、後者はバックテスト用に使うことができます。ちなみに、後者のCSVファイルの1日あたりの件数は300万件程度で、ファイルサイズは約150MB程度です。

前提

pandasおよびwebsocketモジュールがインストールされていること(インストールされていない場合はpip installでインストールしてください。私の環境では、
pandas==0.22.0
websocket==0.2.1
websocket-client==0.47.0
を用いています)。

ソースコード解説

冒頭でloggerを用いて2種類のCSVファイルに対するファイルハンドラを定義しています。本プログラムでは上述の2種類のファイルが出力されます。

続いて、websocketを用いたJSON-RPCの通信を行っています。on_message関数の中では、JSONデータを辞書リストへ変換し、それをloggerを用いてCSVファイルへと追記しています。

また、100カウントごとに

・最新5000件のCSVファイルを読み込み、5000件まで削り再保存
・日ごとのCSVファイルのファイルローテーション

を行っています。

※初回起動時は「exec_date,side,price,size,id」という内容(1行目=ヘッダ)でexecutions_realtime.csvを作成しておいてください。2行目以降はプログラム実行後に更新されていきます。

画像1

正常に起動したときの画面表示はこちら。

画像2

以下、実際のコード部分は有料パートとなります。※note売上は良質な情報を発信している方のnote購入・サポートに充てさせていただきます。

ここからは有料パートです。まずはご購入いただき、どうもありがとうございます。今後、的を絞った、より良い情報を発信していくための励みになります。引き続きよろしくお願いします。

実際のソースコード (executions_realtime.py)

#!/usr/bin/python3
from logging import getLogger,INFO,StreamHandler,FileHandler
import datetime
import json
import websocket
import pandas as pd
CSV_FILE_NAME = 'executions_realtime.csv'

logger = getLogger(__name__)

handler = StreamHandler()
handler.setLevel(INFO)
logger.setLevel(INFO)
logger.addHandler(handler)

fh = FileHandler(CSV_FILE_NAME) # 最新5000件のCSVファイル
logger.addHandler(fh) # 最新5000件のCSVファイルハンドラを追加

fh2 = FileHandler('exec_' + str(datetime.date.today())[5:10] + '.csv') # 日ごとのCSVファイル
logger.addHandler(fh2) # 日ごとのCSVファイルハンドラを追加

def get_exec_date(d): # exec_dateをJSTに変換し整形
    exec_date = d["exec_date"].replace('T', ' ')[:-1]
    return datetime.datetime(int(exec_date[:4]), int(exec_date[5:7]), int(exec_date[8:10]), int(exec_date[11:13]), int(exec_date[14:16]), int(exec_date[17:19]), int(exec_date[20:-1])) + datetime.timedelta(hours=9)

class RealtimeAPI(object):
    def __init__(self, url, channel):
        self.url = url
        self.channel = channel
        self.ws = websocket.WebSocketApp(self.url, header=None, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close)
        self.i = 0
        websocket.enableTrace(True)

    def run(self):
        self.ws.run_forever()
        print('Web Socket process ended.')

    def on_message(self, ws, message):
        output = json.loads(message)['params'] # JSONデータを辞書リストへ変換
    
        if self.i % 100 == 0: # 100受信ごとにCSVファイルを5000件まで削る
            print()
            df = pd.read_csv(filepath_or_buffer=CSV_FILE_NAME, sep=",") # CSVファイルを読み込む
            df = df.set_index('exec_date')
            df.tail(5000).to_csv(CSV_FILE_NAME, header=True, index=True) # CSVファイルを5000件まで削る
            logger.removeHandler(logger.handlers[2]) # 日ごとのCSVファイルハンドラを除去
            fh2 = FileHandler('exec_' + str(datetime.date.today())[5:10] + '.csv') # 日ごとのCSVファイルローテーション
            logger.addHandler(fh2) # 日ごとのCSVファイルハンドラを追加
        for d in output["message"]:
            logger.info("{},{},{},{},{}".format(get_exec_date(d), d["side"], d["price"], d["size"], d["id"])) # CSVファイルへ行ごとに出力
      
        self.i += 1 # カウンタを1増やす

    def on_error(self, ws, error): # エラー時処理
        pass #logger.error(error)

    def on_close(self, ws): # クローズ時処理
        print('disconnected streaming server')

    def on_open(self, ws): # オープン時処理
        print('connected streaming server')
        output_json = json.dumps({'method' : 'subscribe', 'params' : {'channel' : self.channel}})
        ws.send(output_json)

if __name__ == '__main__':
    json_rpc = RealtimeAPI(url='wss://ws.lightstream.bitflyer.com/json-rpc', channel='lightning_executions_FX_BTC_JPY')
    json_rpc.run()

コメントをできるだけ丁寧に記述したため、コメントとコードを追っていただければ、挙動は理解していただけると思います。

よくある質問

Q. 「Traceback (most recent call last):
File "./executions_realtime.py", line 6, in <module>
import pandas as pd」のようなエラーが出る。

画像3

A. pandasがインストールされていません。「sudo pip install pandas」でインストールしてください。

Q. 「Traceback (most recent call last):
File "./executions_realtime.py", line 66, in <module>
json_rpc = RealtimeAPI(url='wss://ws.lightstream.bitflyer.com/json-rpc', channel='lightning_executions_FX_BTC_JPY')
File "./executions_realtime.py", line 30, in __init__
self.ws = websocket.WebSocketApp(self.url, header=None, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close)
AttributeError: module 'websocket' has no attribute 'WebSocketApp'」のようなエラーが出る。

画像4

A. websocket-clientがインストールされていません。「sudo pip install websocket-client==0.47.0」でインストールしてください。

Q. 「error from callback <bound method RealtimeAPI.on_open of <__main__.RealtimeAPI object at 0x7f28e179b748>>: on_open() missing 1 required positional argument: 'ws'
File "/usr/local/lib/python3.6/site-packages/websocket/_app.py", line 343, in _callback
callback(*args)」のようなエラーが出る。

画像5

A. websocket-clientが新しすぎます。「pip list」で確認すると、0.54.0などになっているのではないでしょうか。「sudo pip install websocket-client==0.47.0」で0.47.0をインストールしてください。

Q. 「error from callback <bound method RealtimeAPI.on_message of <__main__.RealtimeAPI object at 0x7f9d53fbf470>>: No columns to parse from file
File "/usr/local/lib/python3.6/site-packages/websocket/_app.py", line 315, in _callback
callback(self, *args)
File "./executions_realtime.py", line 43, in on_message
df = pd.read_csv(filepath_or_buffer=CSV_FILE_NAME, sep=",") # CSVファイルを読み込む」のようなエラーが出る。

画像6

A. note本文記載の通り、「exec_date,side,price,size,id」という内容でexecutions_realtime.csvを事前に作成しておいてください。

以上となります。本noteに関する質問についてはTwitter(@akagami_v2)のDMにて、できる限りはお受けしたいと思います。では、良いトレードbot作成ライフをお送りください!

AKAGAMI新作教材「PSP」。
1000人以上に支持されたトレード技術教材の次は「男のモテ」がテーマ。
もともと相場価格3〜10万円のお金がかかる美女を、0円で抱くワザとは?



最後まで読んでいただき、どうもありがとうございます。頂いたサポートは、良質な情報を発信している方のnote購入・サポートに充てさせていただきます。