[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:  79731694171202:  80356660171203:  81011036171204:  81605880171205:  82301259171206:  82878859171207:  83593562171208:  84598538171209:  85711217171210:  86487237171211:  87653280171212:  88626522171213:  89462988171214:  90217337171215:  90924122171216:  91650750171217:  92359400171218:  93260268171219:  94075916171220:  94820051171221:  95694182171222:  96458043171223:  97458371171224:  98424122171225:  99192994171226:  99818196171227: 100460797171228: 101181394171229: 101967374171230: 102660635171231: 103288367180101: 104010064180102: 104630546180103: 105213236180104: 105883745180105: 106521174180106: 107164596180107: 107852897180108: 108450691180109: 109173540180110: 109907550180111: 110544510...
180601: 240189414180602: 241271658180603: 242285092180604: 243244542180605: 244261710180606: 245298430180607: 246365919180608: 247539360180609: 248661153180610: 249606112180611: 250861819180612: 252480422180613: 253701792180614: 255086546180615: 256645522180616: 258109194180617: 259429236180618: 260698624180619: 262043619180620: 263459485180621: 264974429180622: 266399914180623: 268215747180624: 270160901180625: 271977464180626: 273838552180627: 275383205180628: 276986183180629: 278541425180630: 280515599180701: 282493146180702: 284143351180703: 285962080180704: 287646529180705: 289426021180706: 291204744180707: 292809209180708: 294197983180709: 295541153180710: 296966785180711: 298753360180712: 300401183180713: 302088321180714: 303485049180715: 304862265180716: 306151954180717: 307848047180718: 309615180180719: 311764799180720: 313791660180721: 315779414180722: 317365251180723: 318448718180724: 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(未購入者向け)


干し芋


続きをみるには

残り 0字

¥ 100

期間限定 PayPay支払いすると抽選でお得に!

サポート頂けると励みになります BTC,BCH: 39kcicufyycWVf8gcGxgsFn2B8Nd7reNUA LTC: LUFGHgdx1qqashDw4WxDcSYQPzd9w9f3iL MONA: MJXExiB7T7FFXKYf9SLqykrtGYDFn3gnaM