[Python OHLCV] bitFlyer の過去の約定情報からオレオレOHLCVデータを作成する
バックテスト用のデータを集めたい方向け。
より具体的に書くと、cryptowatchなどで目的のデータが取得できない方(1分足データを1ヶ月分集めたい、cryptowatchに無い解像度のデータが欲しい、など)向けのネタです。
関連note
@wanna_be_freeさんが改造版を作ってくださいました。
今回のnoteと前回までのnoteとの違い
前回まで:Realtime API を使って、最新の OHLCV データを作成(bot用)
今回:Public API を使って、過去の約定情報から OHLCV データを作成(主にバックテスト用)
補足1
知らない方がいたのでまとめました
API ドキュメント(Public API getexecutions)
補足2
コードのコメントにも書いてるけど、たまに約定履歴が空っぽ(欠損)のときがあります。
「bFのDBにはデータがあるけど何らかの理由で送られていない」もしくは「bFのDBにもデータは無い(DBの保存まわりで何かバグがあってIDが飛んでいる or 保存できてないデータがある)」などが考えられます。我々がどうにかできる領域ではないですが、念のために書いておきます。
コード
「自分の手元で動けばいいや」だったのでクソコード、察してください。
・1秒足のOHLCVを作成
・データ補完は無し
・対象期間と開始ID(※厳密には開始直前ID)を指定して実行
・日付が変わったらファイルハンドラ変更
・ファイル出力先は data/ohlcv、自動でディレクトリ作成とかしてないので事前に掘っておいてください
・環境に応じてwhileループ末尾のsleepの有効化や、sleep時間の調整を行ってください(※自分の環境では、sleepを挟まなくてもAPI Rate Limitに引っ掛からない処理時間、応答時間だった)
import ccxt
from datetime import datetime, timedelta
import dateutil.parser
from time import sleep
from logging import getLogger,INFO,FileHandler
logger = getLogger(__name__)
SYMBOL_BTCFX = 'FX_BTC_JPY'
ITV_SLEEP = 0.001
def get_exec_datetime(d):
exec_date = d["exec_date"].replace('T', ' ')[:-1]
return dateutil.parser.parse(exec_date) + timedelta(hours=9)
def get_executions(bf, afterId, beforeId, count):
executions = []
while True:
try:
executions = bf.fetch2(path='executions', api='public', method='GET', params={"product_code": SYMBOL_BTCFX, "after": afterId, "before": beforeId, "count": count})
break
except Exception as e:
print("{}: API call error".format(datetime.now()))
sleep(1)
return executions
bf = ccxt.bitflyer()
dateSince = datetime(2018, 6, 15, 0, 0, 0)
# dateSince = datetime(2017, 12, 1, 0, 0, 0)
dateUntil = datetime(2018, 7, 23, 0, 0, 0)
count = 500
afterId = 256645520
beforeId = afterId + count + 1
handler = FileHandler('data/ohlcv/' + dateSince.strftime("%Y%m%d") + '.csv')
handler.setLevel(INFO)
logger.setLevel(INFO)
logger.addHandler(handler)
print("{}: Program start.".format(datetime.now()))
op, hi, lo, cl, vol = 0.0, 0.0, 0.0, 0.0, 0.0
exec_cnt = 0
loop = True
loop_cnt = 0
while loop:
# 約定履歴を取得
exs = get_executions(bf, afterId, beforeId, count)
# たまに約定履歴がごっそりと無いことがある
if(len(exs) == 0):
print("no execs, ID count up: {} - {}, {}".format(afterId, beforeId, count))
afterId += count
beforeId += count
continue
afterId = exs[0]["id"]
beforeId = afterId + count + 1
date = get_exec_datetime(exs[-1])
datePrev = date
for ex in reversed(exs):
date = get_exec_datetime(ex)
if(dateSince <= date and date <= dateUntil):
price = ex["price"]
size = ex["size"]
if(op == 0.0):
op, hi, lo, cl, vol = price, price, price, price, size
# 日付が変わったらファイルハンドラ変更
if(date.day != datePrev.day):
print("{}: {} finish, {} data. {} start, 1st id {}".format(datetime.now(), datePrev.strftime("%Y%m%d"), exec_cnt, date.strftime("%Y%m%d"), ex["id"]))
handler.close()
logger.removeHandler(handler)
handler = FileHandler('data/ohlcv/' + date.strftime("%Y%m%d") + '.csv')
handler.setLevel(INFO)
logger.setLevel(INFO)
logger.addHandler(handler)
exec_cnt = 0
# 秒が変わったらOHLCVリセット
# ※ここをいじれば好きな解像度にできるはず、このコードは1秒足でデータ作成(抜けの補完はしてないので注意)
if(date.second != datePrev.second or (date.minute != datePrev.minute and date.second == datePrev.second)):
logger.info("{date},{op},{hi},{lo},{cl},{vol}".format(
date=date.strftime("%Y-%m-%d %H:%M:%S"),
op=int(op),
hi=int(hi),
lo=int(lo),
cl=int(cl),
vol=vol,
)
)
op, hi, lo, cl, vol = price, price, price, price, size
if(price > hi):
hi = price
if(price < lo):
lo = price
cl = price
vol += size
exec_cnt += 1
if(date > dateUntil):
loop = False
print("{}: Collected all data, next ID {}".format(datetime.now(), ex["id"]))
break
datePrev = date
# print("{}: loop end[{}]".format(loop_cnt+1, datetime.now()))
# sleep(ITV_SLEEP)
loop_cnt += 1
おまけ1(分かってる範囲の開始約定ID)
171201: 79731694〜
171202: 80356660〜
171203: 81011036〜
171204: 81605880〜
171205: 82301259〜
171206: 82878859〜
171207: 83593562〜
171208: 84598538〜
171209: 85711217〜
171210: 86487237〜
171211: 87653280〜
171212: 88626522〜
171213: 89462988〜
171214: 90217337〜
171215: 90924122〜
171216: 91650750〜
171217: 92359400〜
171218: 93260268〜
171219: 94075916〜
171220: 94820051〜
171221: 95694182〜
171222: 96458043〜
171223: 97458371〜
171224: 98424122〜
171225: 99192994〜
171226: 99818196〜
171227: 100460797〜
171228: 101181394〜
171229: 101967374〜
171230: 102660635〜
171231: 103288367〜
180101: 104010064〜
180102: 104630546〜
180103: 105213236〜
180104: 105883745〜
180105: 106521174〜
180106: 107164596〜
180107: 107852897〜
180108: 108450691〜
180109: 109173540〜
180110: 109907550〜
180111: 110544510〜
...
180601: 240189414〜
180602: 241271658〜
180603: 242285092〜
180604: 243244542〜
180605: 244261710〜
180606: 245298430〜
180607: 246365919〜
180608: 247539360〜
180609: 248661153〜
180610: 249606112〜
180611: 250861819〜
180612: 252480422〜
180613: 253701792〜
180614: 255086546〜
180615: 256645522〜
180616: 258109194〜
180617: 259429236〜
180618: 260698624〜
180619: 262043619〜
180620: 263459485〜
180621: 264974429〜
180622: 266399914〜
180623: 268215747〜
180624: 270160901〜
180625: 271977464〜
180626: 273838552〜
180627: 275383205〜
180628: 276986183〜
180629: 278541425〜
180630: 280515599〜
180701: 282493146〜
180702: 284143351〜
180703: 285962080〜
180704: 287646529〜
180705: 289426021〜
180706: 291204744〜
180707: 292809209〜
180708: 294197983〜
180709: 295541153〜
180710: 296966785〜
180711: 298753360〜
180712: 300401183〜
180713: 302088321〜
180714: 303485049〜
180715: 304862265〜
180716: 306151954〜
180717: 307848047〜
180718: 309615180〜
180719: 311764799〜
180720: 313791660〜
180721: 315779414〜
180722: 317365251〜
180723: 318448718〜
180724: 320069213〜
適当にPlaygroundで探すのが手っ取り早いです。
おまけ2(実行時間)
2018/06/14〜2018/07/22のデータを集めるのにかかった時間は、約12時間でした。
※日数よりも、データ件数に影響を受けるので、過去に遡るほど実行時間は短くなるはずです。
※マシンやNWで処理時間は変わると思います。
real 731m23.594s
user 123m18.852s
sys 2m48.753s
おまけ3(取得済データ)
公開(共有)停止しました。
おまけ4(取得データの利用)
pandas🐼使いましょう。parse_datesオプションで、'YYYY-mm-dd HH:MM:SS'のstringをdatetime型に変換してくれます。あとは良きにはからってください。
import pandas as pd
df = pd.read_csv("20171201.csv", header=None, names=('T','O','H','L','C','V'), parse_dates=['T'])
おわりに
有料(¥100)にしてるけど、これで内容は全部です。募金してくれる人がいれば、ジュース代としていただけると嬉しい。コードは、インデントくずれが起きたりするようなので、コピペ時には注意してください。
マガジン
コメント用note(未購入者向け)
干し芋
ここから先は
¥ 100
Amazonギフトカード5,000円分が当たる
サポート頂けると励みになります BTC,BCH: 39kcicufyycWVf8gcGxgsFn2B8Nd7reNUA LTC: LUFGHgdx1qqashDw4WxDcSYQPzd9w9f3iL MONA: MJXExiB7T7FFXKYf9SLqykrtGYDFn3gnaM