[ccxt]BitMEXでのfetch_ohlcvの使い方と注意点
「ccxt」は様々な取引所APIに対応していて統一されたインターフェースで使いやすいため、使われている方も多いと思います。
本記事ではccxtを使ってBitMEXのOHLCVデータを取得する方法と注意点について解説します。
[2018/05/10 修正]
[BitMEX APIリクエストとの比較]fetch_ohlcvとREST APIリクエスト(udf)の取得結果の違いについて誤りがありましたので訂正しました。
双方の違いはtimestampの基準時刻と時刻単位のみとなります。
[fetch_ohlcv関数定義]
https://github.com/ccxt/ccxt/blob/master/python/ccxt/bitmex.py
def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
[@param(引数)]
symbol : 通貨ペア('BTC/USD' etc.)
timeframe : 時間足('1m', '5m', '1h', '1d')
since : データ取得開始時刻(Unix Timeミリ秒)
limit : 取得件数(未指定:100、MAX:500)
params : その他設定パラメータ(Dict形式)
※paramsには引数にない様々なデータ取得の指定を行うことができます。
今回は利用機会の多い以下のパラメータを使います。
reverse : True(New->Old)、False(Old->New) 未指定時はFlase
partial : True(最新の未確定足を含む)、False(含まない) 未指定はTrue
(後ほど解説しますが、partialには問題があります。)
[return(戻り値)]
List(2次元) : [[timestamp, open, high, low, close, volume], ・・・]
[データ取得例]
サンプルとして5分前から現在までの1分足OHLCVデータを取得します。
まず、現在時刻から5分前のUnix Timeを算出し、ミリ秒に変換します。
from datetime import datetime
import calendar, ccxt
# 現在時刻のUTC naiveオブジェクト
now = datetime.utcnow()
# UTC naiveオブジェクト -> UnixTime
unixtime = calendar.timegm(now.utctimetuple())
# 5分前のUnixTime(ミリ秒)
since = (unixtime - 60 * 5) * 1000
UTC現在時刻:
2018/05/07 02:12:19 (UnixTime:1525659139)
5分前のUnixTime(ミリ秒):
since = 1525658839000
ccxtのBitMEX APIオブジェクトを生成し、データ取得を行います。
なお、以下のデータ取得の中には指定とは異なる結果がになるものが、いくつか含まれます。
本記事の最後にそれらを考慮した取得関数の一例を参考に記しています。
①sinceで取得開始時刻を指定する(正常)
# BitMEX APIオブジェクト生成
bitmex = ccxt.bitmex()
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since)
sinceで指定した時刻以降の1分足OHLCVデータが取得できました。
また、最終行には現在動いている最新足がその時の値で取得されます。
②並び順を新しいデータ順する(正常)
これはparamsにreverse=Trueを指定することで簡単にできます。
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since, params={'reverse': True})
③取得件数を指定する(異常)
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since, limit=3)
limit=3を指定しても2件しか取得されません。
これは不具合と思われます。
なお、reserse=Trueにてlimitを指定すると正しい件数が取得されます。
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since, limit=3, params={'reverse': True})
(本現象の対策を含めた参考例を最後に記しています。)
④最新の未確定足を含まないで取得する(異常)
paramsのpartial=Falseを指定することで未確定足を除外して取得するはずですが、含まれてしまいます。
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since, params={'partial': False})
そのため、partial=Trueやpartial指定なしの状態と取得結果は同じになってしまいます。
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since)
ohlcvs = bitmex.fetch_ohlcv(symbol='BTC/USD', timeframe='1m', since=since, params={'partial': True})
本現象はrequestにpartialがTrue/Falseどちらでも含まれている場合はpartial=Trueの状態でデータが取得されてしまうという不具合のようです。
ccxtのsourceではpartialを省略してもデフォルトでpartial=Trueが設定されてしまうため、実質的にpartial=Falseでの取得はできない形になっています。
(本現象の対策を含めた参考例を最後に記しています。)
[BitMEX APIリクエストとの比較]
BitMEX APIではリクエストにてOHLCVデータを取得することができます。
上記と同条件でデータ取得した結果を確認してみます。
from datetime import datetime
import calendar, requests
# 現在時刻のUTC naiveオブジェクト
now = datetime.utcnow()
# UTC naiveオブジェクト -> Unix time
unixtime = calendar.timegm(now.utctimetuple())
# 5分前のUnixTime
since = unixtime - 60 * 5
param = {"period": 1, "from": since, "to": unixtime}
url = "https://www.bitmex.com/api/udf/history?symbol=XBTUSD&resolution={period}&from={from}&to={to}".format(**param)
res = requests.get(url)
data = res.json()
ohlcvs = []
for i in range(len(data["t"])):
ohlcvs.append([data["t"][i], data["o"][i], data["h"][i], data["l"][i], data["c"][i], data["v"][i]])
こちらの取得結果ではccxtとは以下の2点が異なります。
・TimeStampが足の開始時刻(ccxtは足の終了時刻)
・TimeStampが秒単位(ccxtはミリ秒単位)
上記の違いがあるため、両者を混用される場合や移行する場合には注意が必要です。
[fetch_ohlcvの改善例]
ccxtのfetch_ohlcvデータ取得においてlimitやpartial指定にて求める結果と異なる取得が行われていますので、そちらを改善するサンプルを以下に記します。
from datetime import datetime
import calendar, ccxt
#-------------------------------------------------
# メイン処理
#-------------------------------------------------
def main():
# 現在時刻のUTC naiveオブジェクト
now = datetime.utcnow()
# UTC naiveオブジェクト -> UnixTime
unixtime = calendar.timegm(now.utctimetuple())
# 5分前のUnixTime(ミリ秒)
since = (unixtime - 60 * 5) * 1000
# BitMEX APIオブジェクト生成
bitmex = ccxt.bitmex()
# 改善版 fetch_ohlcvでOHLVデータ取得
ohlcvs = fetch_ohlcv_edit(bitmex, symbol='BTC/USD', timeframe='1m', since=since)
#-------------------------------------------------
# 改善版 fetch_ohlcv
#-------------------------------------------------
def fetch_ohlcv_edit(bitmex, symbol, timeframe='1m', since=None, limit=None, params={}):
# timeframe1期間あたりの秒数
period = {'1m': 1 * 60, '5m': 5 * 60, '1h': 60 * 60, '1d': 24 * 60 * 60}
if bitmex is None:
return None
if timeframe not in period.keys():
return None
# 未確定の最新時間足のtimestampを取得(ミリ秒)
now = datetime.utcnow()
unixtime = calendar.timegm(now.utctimetuple())
current_timestamp = (unixtime - (unixtime % period[timeframe]) + period[timeframe]) * 1000
# partialフラグ
is_partial = True
if 'partial' in params.keys():
is_partial = params['partial']
# reverseフラグ
is_reverse = False
if 'reverse' in params.keys():
is_reverse = params['reverse']
# 取得件数(未指定は100件)
fetch_count = 100 if limit is None else limit
count = fetch_count
# 取得後に最新足を除外するため、1件多く取得
if is_partial == False:
count += 1
# 取得件数が足りないため、1件多く取得
if is_reverse == False:
count += 1
# 1page最大500件のため、オーバーしている場合、500件に調整
if count > 500:
count = 500
# OHLCVデータ取得
ohlcvs = bitmex.fetch_ohlcv(symbol, timeframe, since, count, params)
# partial=Falseの場合、未確定の最新足を除去する
if is_partial == False:
if is_reverse == True:
# 先頭行のtimestampが最新足と一致したら除去
if ohlcvs[0][0] == current_timestamp:
ohlcvs = ohlcvs[1:]
else:
# 最終行のtimestampが最新足と一致したら除去
if ohlcvs[-1][0] == current_timestamp:
ohlcvs = ohlcvs[:-1]
# 取得件数をlimit以下になるように調整
while len(ohlcvs) > fetch_count:
if is_reverse == True:
ohlcvs = ohlcvs[1:]
else:
ohlcvs = ohlcvs[:-1]
return ohlcvs
# メイン処理開始
if __name__ == '__main__':
main()
上記の改善版fetch_ohlcv(fetch_ohlcv_edit)は引数にccxtのBitMEX APIオブジェクトを付加して呼び出すことで、reverseやpartialの指定にあった結果が取得できます。
また、partialに関してはccxtのpublicGetTradeBucketed関数にてrequestからpartialを除外して呼び出せば未確定の最新足は取得されなくなりますが、時間足が切り替わった直後の10数秒間は確定した最新足が取得できないという別の不具合が発生します。
そのため、上記の改善例ではpartial=Trueで取得した後に不要なOHLCVデータを除去する方針をとっています。
そもそもreverse=Trueやpartial=Trueが不要なら、BitMEX APIリクエストで取得する方がシンプルかもしれません。
また、udf/historyは1度のリクエストで最大で1万件程データ取得が行えるため、まとめて大量に取得する場合はfetch_ohlcvよりも効率的です。
それぞれのデータ取得方法の特徴や違いを理解し、要件によって使い分けることをお勧めします。