やっていくログ

にんげんをやっていきましょう

BitmexRektを題材に Tweetの取得・可視化・BTC取引botの話

この記事は eeic Advent calender 2018 その2 の第5日目の記事になっています。

4日目 EEIC走馬灯(落第学生三番煎じver) - acryloshobonileのブログ
6日目 eeic走馬灯(デバイス系かつ怠惰学生ver.)前編 - 自由帳

先輩方の走馬灯の間にお邪魔してすみませんというお気持ち…。
(強電系走馬灯、来年以降書いてみたい)

0. はじめに

電気電子工学科 B3 の者です。プログラミングは学科に入ってから始めました。普段は電力系の授業を受けたり実験をしたりしています。今回のコレはおべんきょうのための個人的な試みですが、アドベントカレンダーその2が空いてそうだったので記事としてまとめ、投稿させていただきます。
バックテストまでの全体のコードはGitLabにあげておきます。メインの処理は ipynb ファイルに記述しています。上手くないのでデータ保持の形式まわりがアレですが…。

1. BitmexRektについて

BitMEXとは、仮想通貨のレバレッジ取引を行う取引所です。Bitcoin をはじめいくつかの通貨が扱われており、取引量は世界最大級です。
一般にレバレッジ取引においては、ロスカットと呼ばれる自動精算システムがあります。(証拠金維持率)=100×(総資産評価額)/(必要証拠金) と定義し、証拠金維持率が一定の水準を下回ると、ロスカットが行われます。BitMEX では、ポジションが逆行して証拠金維持率が0%になるとロスカットされます。
BitmexRektとは、BitMEX において行われるロスカットを監視し、ツイートする bot のことです(図1)。ロスカットは一方的で大きな値動きがあるときに発生しやすいため、ロスカット発生情報は取引の参考とされています。
今回はこの BitmexRekt のツイートを取得し、得たデータをいじっていきます。

f:id:calpeace:20181213172254p:plain
図1 BitmexRektのツイート例

2. Twitterapiを使うために

2018年7月ごろに Twitterapi 制限が厳格化され、api を使うには Developer account としての登録が必要になりました。ここ で述べていますが、英作文をしなきゃいけないのでちょっと面倒です。アカウントを登録したら、app 登録を行って、api key など4種類取得します。

3. ツイートの取得

処理をpython-twitterというライブラリにすべてお任せします。
GetUserTimeline という関数を使うと、あるユーザーのツイートを取得することができるようなので、それを使います。
ツイートには id が振られており、それをもとに取得するツイートの範囲を指定することもできます。引数 max_id を使うと、そのid以前のツイートのみ取得することができます。
その他の機能については公式のドキュメントを参照してください。

今回は XBT/USD という Bitcoin 無期限取引のペアのみ抽出することとします。
Liquidated long は買い注文が清算されたことを表し、価格の下落が起きていることを示唆します。
Liquidated short は売り注文が清算されたことを表し、価格の上昇が起きていることを示唆します。
ロスカットが発生した時刻、方向、枚数を1セットのデータとして保存します。ここで方向については、買い注文の清算を-、売り注文の清算を+と定めます。
実際のコードは以下のように適当に。1サイクルで200ツイートが取得上限です。とりあえず600ツイート手に入れます。各種 key は予め読み込んでおきました。

api = twitter.Api(consumer_key=CONSUMER_KEY,
                  consumer_secret=CONSUMER_SECRET,
                  access_token_key=ACCESS_TOKEN_KEY,
                  access_token_secret=ACCESS_TOKEN_SECRET)
liquidated_list=[]
last_id=0

for i in range(3):
    if i==0:
        tweets = api.GetUserTimeline(screen_name="BitmexRekt",count=200)
    else:
        tweets = api.GetUserTimeline(screen_name="BitmexRekt",max_id=last_id-1,count=200)

    for tweet in tweets:
        word_list=tweet.text.split()
        if 'XBTUSD:' in word_list:
            if word_list[1]=='long':
                amount=(-1)*int(word_list[5].replace(",",""))
                at_price=float(word_list[7])
                liquidated_list.append([tweet.created_at,amount,at_price])
            elif word_list[1]=='short':
                amount=int(word_list[5].replace(",",""))
                at_price=float(word_list[7])
                liquidated_list.append([tweet.created_at,amount,at_price])
        last_id=tweet.id

#以下は word_list の format
#Liquidated short on XBTUSD: Buy 11,305 @ 3823.5

こうするとこんな感じのデータが格納されます。時刻、清算枚数、清算価格の順になっています。

 ['Thu Dec 13 09:04:55 +0000 2018', 47757, 3397.5],
 ['Thu Dec 13 09:04:50 +0000 2018', 83828, 3393.5],
 ['Thu Dec 13 09:04:44 +0000 2018', 43445, 3387.0],
 ['Thu Dec 13 09:04:37 +0000 2018', 225990, 3383.5],
 ['Thu Dec 13 09:04:32 +0000 2018', 223023, 3377.0],
 ['Thu Dec 13 08:27:10 +0000 2018', -499, 3358.0],
 ['Thu Dec 13 07:59:55 +0000 2018', 58351, 3374.5],
 ['Thu Dec 13 07:59:50 +0000 2018', 2682, 3373.5],
 ['Thu Dec 13 06:00:05 +0000 2018', 32335, 3368.0],
 ['Thu Dec 13 06:00:02 +0000 2018', 36405, 3364.0],
 ['Thu Dec 13 05:59:47 +0000 2018', 5010, 3360.5],
 ['Thu Dec 13 05:59:41 +0000 2018', 107903, 3358.5],
 ['Thu Dec 13 05:51:39 +0000 2018', -35209, 3338.5],
 ['Thu Dec 13 05:50:29 +0000 2018', -6002, 3342.5],
 ['Thu Dec 13 05:50:22 +0000 2018', -158472, 3344.5],
 ['Thu Dec 13 05:29:11 +0000 2018', -72500, 3346.5],
 ['Thu Dec 13 05:29:04 +0000 2018', -68622, 3348.0],

4. 実際の値動きとともにグラフを描画

cryptowatchapi を使うと各取引所の直近の価格データを手に入れることができます。
価格データの抽出は get_ohlcv.py の中に記述してあります。取引所と単位時間(1min, 3min, 5min, 15min, 30min, 60min 等)を与えると時刻、始値、高値、安値、終値を返す関数を用意してあります。
※12/18 追記
稀に欠損値 all 0 が返ってくることがある模様…?

取得したツイートから得たロスカット情報は、longとshortに分けて保持します。
また、グラフの開始時刻を価格データのスタート時刻にするため、それより昔のロスカットデータを捨てて整理します(価格データは1分足だと500分ぶんしかないので、だいたいロスカットデータのほうが余る)。
この辺の実装は冗長なだけなので具体的なコードは割愛します。

価格とロスカット情報を2段に分けてプロットさせると図 2 のようになります。青は long のロスカット、赤は short のロスカットを表しています。2段目のグラフの縦軸は清算枚数を表しています。おおまかに、価格の下落により long のロスカットが発生し、上昇により short のロスカットが発生している様子が見て取れます。
また、価格とロスカット情報を1つにまとめてプロットさせると図 3, 4 のようになります。ロスカット情報を表す点は、濃度が清算枚数と対応しています。これを見ると価格の上下とロスカットの対応の様子がより分かりやすいかと思います。

f:id:calpeace:20181213200925p:plain
図2 BitMEX価格とロスカット散布図

f:id:calpeace:20181213201002p:plain
図3 BitMEX価格とロスカット散布図

f:id:calpeace:20181213203729p:plain
図4 BitMEX価格とロスカット散布図(別時間帯)

5. 取引 bot 化に向けたバックテストと課題

4まででは、過去のロスカットを抽出し可視化してみました。
ここで、リアルタイムでロスカットツイートを取得することにより、取引 bot のシグナルとして利用できるのではないかという目論見が立ちます。
そこで、得たロスカットデータをもとに、簡単なロジックのバックテストを行ってみました。
バックテストのコードもメインの ipynb ファイル内に記述してあります。

取引ルールは以下のようにします。

long のロスカットが発生したら short で entry、short のロスカットが発生したら long で entry。
価格は、ロスカットが発生した時刻の分足の終値で entry、次の分足の終値で exit とします。
コードでは、この辺の処理がしやすいように、途中のデータ整理で時刻の秒を切り捨てています。
(ex. hh:mm:ss = 19:22:30 にロスカットが発生したら、19:22の分足の終値で entry、19:23の分足の終値で exit する。言い換えれば 19:23:00に entry、19:24:00に exit)

ここで、
profit factor = (総利益)/(総損失の絶対値)
max drawdown : グラフの最大へこみ
と定義します。
PF は大きいほうがよく、drawdown の絶対値は小さいほうがよいです。

まずはロスカットデータの元である取引所 BitMEX でのバックテスト結果を2例示します(図5, 6)。
500分ぶんの価格データを利用したバックテストで、図 5 と図 6 はそれぞれ別の日に採取したデータを用いています。
ロスカットが多いときはモリモリ、少ないときも着実に積み重ねている様子が見て取れます。

f:id:calpeace:20181213210455p:plain
図5 BitMEXにおけるバックテスト
f:id:calpeace:20181213210523p:plain
図6 BitMEXにおけるバックテスト(2)

次に、国内の取引所 bitFlyerレバレッジ取引価格データを使ってバックテストします。
シグナルは BitMEX のロスカットデータですが、価格は相互に追従しあうものだと思われるため、試してみました。
結果を2例示します(図7, 8)。グラフはBitMEXのバックテスト結果とよく似ています。強いですね。

f:id:calpeace:20181213211325p:plain
図7 bitFlyerにおけるバックテスト
f:id:calpeace:20181213211353p:plain
図8 bitFlyerにおけるバックテスト(2)

しかし、果たしてそうでしょうか。

おそらく、実際にこのロジックを bot 化して運用しても、ここまでうまくはいかないのではないかと思われます。以下に述べるような問題点があるためです。

  • ロスカットが発生するような場面では、サーバが遅延する

実際に注文を出す際には、注文を出した瞬間の価格で必ず約定するわけではありません。ある程度の価格のブレがあり得ます。
特に、ロスカットが発生するような場面では、価格の激しい上下動があり、注文数も増加するため、取引所のサーバの遅延が起きます。
今回のバックテストでは、必ず終値で約定するとしているため、この影響は無視できないわけです。

  • 売り板と買い板のスプレッド損

売りたい人は少しでも高く売りたい、買いたい人は少しでも安く買いたい、それゆえ売り板と買い板には価格差が生じます。
この価格差はスプレッドと呼ばれます。取引所を実際に眺めてみるとわかりやすいかと思います。
実際に entry する場合には、成行注文/指値注文のどちらかを選択して注文することになります。
成行注文とは、価格はいくらでも構わないので買わせてくれ/売らせてくれ、という注文方式です。
すぐに約定する代わりに、先ほど述べたスプレッドぶん不利な価格で売買していることになります。
指値注文とは、価格を指定して注文する方式です。
スプレッドによる不利益はなさそうですが、約定せずにさらに価格が上昇/下落して、チャンスを失ってしまう可能性があります。
バックテストでは、この影響を無視して必ず終値で約定するとしていましたので、実際の結果はこの影響を受けて変わってくるでしょう。

あくまでもデータで遊んでみた!という体験をまとめたものですので、これを鵜呑みにして取引されても一切責任はとりません。

※12/16 追記
フォワードテストもやってみているので、その結果も書くかもしれない。
※12/20 追記
フォワードテスト第1弾、書いた。

6. 取引 botフォワードテスト

せっかくなので、BitMEXのロスカットデータを生かした bot を作ってみます。
bitFlyer でのレバレッジ取引です。python で書いて AWS Cloud9 上で動かしています。
pybitflyer や ccxt を使ってちょっと慣れればシンプルな売買はすぐ実装できます。
先駆者の数多くの指南記事がありますので、興味があればググってみてください。

第 1 弾のロジックは、5 章でバックテストしたものを微調整して、以下の通りとしました。

(例)

時刻 ロスカット 取引
22:13:00~22:13:59 0
22:14:00~22:14:59 -200
22:15:00~22:15:59 -300 22:15:00 short entry
22:16:00~22:16:59 0 short hold
22:17:00~22:17:59 0 22:17:00 short exit

entry を指値にしたのは、最初に entry と exit どちらも成行にしたものを組んで回してみた結果、あまりに危なっかしかったからです。
また、ロスカットが続いている間は exit しないことに決めたのは、取引回数が増えることによる成行スプレッド損を軽減するためです。
たぶんロスカットが続いている間は利益が出る方向に価格が推移しているので、ポジションを持ちっぱなしでも大丈夫だと考えました。

図10のように、勝とうが負けようが bot は淡々と取引を行っていきます。

f:id:calpeace:20181220003112p:plain
図10 取引の様子

図 11 に、実際の損益推移を示します。ログインボーナスでもらえる微小な BTC 現物の価格変動の影響があるため、bot が止まっているときも損益変動が少しあります。
バックテストではドローダウンがほとんどなかったのに、この結果を見ると命からがら生き延びているだけという感じがします。
ロットを 0.05 としているので、バックテストの結果のおよそ 1/20 が 500 分で得られる収益の理論値なわけですが、とてもじゃないがそのレベルに到達していないようです。
実際に bot を回してみると、サーバーが注文を受け付けなかったり、ラグがあったり、価格が上下にブレて不利な約定価格になっていたり、といった問題に直面します。
これを書いている間に、所有ポジションをチェックするあたりで書き換えるべき点が見つかったので、もうすこし改善の余地はありそうです。
今後もロスカットデータを生かした bot のアイデアを試していきます。良さげなモノがあればここに追記していきたいと思います。

f:id:calpeace:20181220010405p:plainf:id:calpeace:20181220010509p:plain
図11 損益推移(左: 11:00ごろスタート / 右: 09:00ごろスタート)

7. おわりに

f:id:calpeace:20181213214511p:plain
図12 Bitcoin年初来チャート(trading viewより)
昨年の今頃は少なからぬ人々がビットコインビットコインだと言っていた印象ですが、今年はモリモリ下り坂です(図9)。
ただ、取引所を筆頭に api をモリモリ公開してくれたり、ccxt をはじめとする優秀なライブラリがあったりするおかげで、プログラミングの経験が浅い自分のような人でも、おべんきょうの題材とするのには悪くないんじゃないかと感じています。また、自分で組んだ bot が健気に動いている様子を眺めていると、結果がショボかろうと愛しさが湧いてきます。おもしろいものです。

日々控室で同期を見ていると、この学科には様々な分野に長けた強い人が多いなぁと感じます。
そんな強い人々が取引 bot 作りに手を出したらきっとすごいことになるんじゃないかと思いつつ、筆をおきます。

最後までお付き合いいただきありがとうございました。