bitflyerビットコイン現物・先物の裁定取引BOTを作成してみた

#はじめに
今回、仮想通貨取引所Bitflyerで取引を行う、自動裁定取引BOTを作成しました。
裁定取引はビットコイン「現物」・「先物」間で行います。

##裁定取引とは？
同じ金融商品または、将来的に価格が一致することが望まれる複数の取引市場にて、
価格差が生じた際、その差を利用して、それぞれの取引市場間で反対の注文を出し、
利益をあげる取引手法です。

例えば、
ビットコイン先物が150円/BTC
ビットコイン現物が100円/BTCの場合
先物を1BTC空売りし、現物を1BTC買います。
両者の値段は、満期のSQ値確定時に値段が一致することが、将来的に保証されているため、
その差益150-100=50円を利益として得ることができます。
![picture_pc_0f22de645d6af2a876d60170ab8d87e3.jpg](https://qiita-image-store.s3.amazonaws.com/0/230272/938215d3-ab73-4a38-04d5-965e6ca5bd15.jpeg)

価格変動リスクを抑えられるため、
「理論的にはローリスク」であると言われています。

##Qiitaの人気記事
自動裁定システム(BOT)には、以下の人気記事があります。
[ビットコイン自動裁定取引システムを開発・トレードした結果](https://qiita.com/bitrinjani/items/3ed756da9baf7d171306)

取り上げられているのは[r2 Arbitrager](https://github.com/bitrinjani/r2)です。

r2 Arbitragerの取引手法は「取引所間の価格差」を利用したものです。
Typescriptで作成され、非常に精巧に設計されています。

本プログラムは上記のプログラムに強いインスピレーションを受け、
異なる裁定取引の手法も自動化できないかと思ったことが、発端になっています。

#FAQ
**本記事は「自動裁定取引BOTの仕様」に焦点を絞ってまとめています。**

1. 仮想通貨の裁定取引について詳しく知りたい。
2. 裁定取引は儲かるのか？
3. 裁定取引を行ってみてわかる、現実的な注意点・デメリットは何か？

これらの内容については、noteの別記事にまとめました。
[仮想通貨ビットコインでの裁定取引あれこれ/裁定取引BOTに関するFAQ](https://note.mu/avocadowasabi/n/n6c4e625f5e89)

また、「pythonのインストールはどうすればいいのか」など初歩的な疑問については、
そのnoteにて、FAQとして回答することにしました。
回答自体も、ウェブサイトのアドレスを指示する程度のシンプルなものであり、
回答しかねる場合もあることをご承知おきください。

バグ報告などは[GitHub](https://github.com/AvocadoWasabi/BitflyerArbitragerBOT)にて。
議論などは、各々のプラットフォームで回答したいと思います。


#ソースコード公開先
[GitHub - AvocadoWasabi/BitflyerArbitragerBOT](https://github.com/AvocadoWasabi/BitflyerArbitragerBOT)
##ライセンス
BOTはライブラリとして、

>[Pybitflyer](https://github.com/yagays/pybitflyer) : MIT ライセンス,
[websocket-client](https://github.com/websocket-client/websocket-client) : LGPL ライセンス

の2つをそれぞれ改変し同封しています。
各ライブラリ部分はそれぞれのライセンスに準拠します。
メインプログラム部分についてはMIT ライセンスに準拠します。
したがって、改変・複製・販売は自由ですが、無保証です。
各プログラム・ライブラリ作成者の著作権表示とライセンス表記は忘れずに行ってください。
その他については、上記ライセンスに従ってください。

**作者がプログラムを販売することはありません**

#動作環境・必要設定

##OS・言語のバージョン
Windows10, Python3.6.4上で動作確認しています。

Dockerは、用意ができ次第公開予定です。

##PCの時間
サーバー側との時間のズレをできるだけ避けるため、
ntpサーバーなどで、PCの日付・時刻をできるだけ合わせてください。

##ライブラリ
以下の通り、requests,sixをインストールしてください。
pybitflyer, websocket-clientは同封のものを使用してください。

```
pip install requests
pip install six
```

##APIの取得
Bitflyerの取引所APIを利用するためのAPI_KEY,API_SECRETを取得してください。
権限は、API権限設定の項目「トレード」に関して以下の権限をすべてONにしておきます。
それ以外はセキュリティのためOFFを推奨します。

**APIKEYは厳重に管理してください。**

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/230272/8002b094-6ad6-9004-4120-844aeb46241d.png)


##口座の状態
取引最大規模(最大ポジション)を、後述のconfig.jsonで設定します。
その取引を可能にする資金を口座に用意してください。
**ロスカット回避のため、先物のレバレッジは1倍を推奨します。**
その場合、現物の口座の資金を**a**とした場合、先物証拠金は**1.8a**を推奨します。
また、現物口座には取引手数料分のビットコインを余分に入れておいてください。

例:
最大ポジション ￥20,000(片側につき)を取りたい場合。
現物口座：￥20,000
先物口座：￥38,000(￥18,000は証拠金維持率維持のため)
その他：現物取引手数料分のビットコインを所有しておく

##インストール
1. Python3.6.4が動作する環境を構築します
2. GitHubより本プログラムをダウンロードするか、git cloneします
3. APIKEYを取得、口座資金を現物・先物に用意します
4. config_default.jsonをリネームし、config.jsonにします
5. config.jsonの設定を次項を参考に行う(api_key, api_secretは必須)
6. config.jsonの各項目を設定します
7. main.pyを実行します

#設定項目
取引を含む設定はconfig.jsonにて、JSON形式で行います。
**デフォルトではconfig_default.jsonになっているため、リネームしてください。**

config.jsonが読み込まれるのは起動時・リブート時となります。
動作中に変更を加えても、設定は反映されませんのでご注意ください。

|項目|説明|
|:--|:--|
|demo_mode|trueにすると、Bitflyerの実際に取引せず、仮想的に取引を行います|
|demo_trade_interval|demo_mode時、取引間隔を設定します。demo_modeでないときは無関係|
|message_language|メッセージの言語"JP"または"EN"|
|api_key|BitflyerのAPI_KEYを入力|
|api_secret|BitflyerのAPI_SECRETを入力|
|swap_point_rate|先物のスワップポイントのレートを入力|
|position_value|現物・先物**片側それぞれ**ついての、1回の裁定取引での限界取引量(単位BTC)|
|max_position_value|裁定取引に用いる最大取引量(単位BTC)|
|exepect_profit_thleshold|いくら以上の期待利益を裁定取引機会とみなすか|
|close_pair_before_sq|満期SQ値確定を待たず、利益がでる状態なら利確する|
|closing_margin_time|利確条件が設定値だけ維持されたら、利確する|
|maturity_time|満期、先物の取引停止時間|
|maturity_time_margin|満期より設定値分前から満期とみなす(サーバーとPCの時間のズレによる不具合回避のため)|
|itayose_time|現物板寄せ時間|
|sq_time|SQ値確定時間|
|std_stream_logging_interval|画面に表示されるログの表示間隔|
|arbitrage_loop_cooling_time|裁定取引機会発生時の取引間隔|
|websocket_retry_after|ReaktimeAPI呼び出しに使っているwebsocket接続の切断時、再接続を何秒後に行うか|
|arbitrage_debug_mode|デバッグ用に裁定取引機会を擬似的に作成したデータを受信します。**DEMO-MODEをオンにしておかないと、実際に取引されてしまうので注意が必要です**|
|maturity_debug_mode|デバッグ用に設定された時間を、先物の満期・板寄せ・レリブート時間とします|
|debug_maturity_start|デバッグ用満期時間|
|debug_itayose_time|デバッグ用板寄せ時間|
|debug_reboot_time|デバッグ用リブート時間|

#設計
##各クラス・駆動部
Pythonのマルチプロセスよる並行実行を骨子とし、各駆動部・クラスを組み立てています。

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/230272/16106dfa-e174-690e-d3b1-8ff9de6bbf00.png)


##何を裁定取引機会とみなすか？
「取引手数料(オープン・クローズ時)・スワップポイントを差し引いても、
現物・先物間に価格差から利益を得られると期待できる場合」を
「裁定取引機会」として、自動取引しています。

なお、価格変動リスクを抑えるための純粋な裁定取引にするため、
取引機会は「現物安・先物高」時の現物買・先物売のみに絞っています。
(「現物高・先物安」時、現物売・先物買は行わない)

##ポジションオープンの流れ
1.RealtimeAPIより取得したTicker(価格)情報を更新
2.先物・現物・1回分の設定取引量のうち、最小のsizeを計算
各先物について、

```
期待利益 = 先物bestbid -現物bestask -先物の満期までのスワップポイント -ポジションオープン・クローズ分の手数料
```

を計算し、それが設定した閾値より上の場合、裁定取引機会があるとみなし、裁定取引処理に移る

3.裁定取引:最大取引量制限に該当しないかチェック、その制限内で可能な限り裁定取引を行う
4.裁定取引:先物を成行注文する
5.裁定取引:注文が成立したかチェック、成立しない場合は、裁定取引を中断する。
6.裁定取引:現物を成行注文する
7.裁定取引:注文が成立したかチェック、成立しない場合は、先物の反対売買を成立するまで行う
8.裁定取引が完了したら各注文を「取引ペア」として格納する

##クローズポジションの流れ
###先物が満期になり、現物の板寄せが始まった時、利益確定する
1.先物が満期になった時、「取引ペア」を固定(FIX)し、以後そのペアは板寄せまで保存される
2.現物の板寄せが始まったとき、取引ペアの現物側を反対売買する。
3.取引ペアの先物側については何も処理を行わない
4.格納していた取引ペアを解消し、削除する。
5.取引ペアの先物側はSQ値確定による差金決済がサーバー側で行われる

###裁定取引後、「現物高・先物安」の価格差がうまれた時、利益確定する(設定で変えられます)
1.上記の状態が発生しているかチェックする。発生していれば以下の処理に移る
2.満期でないことをチェックし、チェックが通れば以下のクローズ処理に移る。
3.現物を反対売買する。注文ができなれば、できるまで再試行する。
4.先物を反対売買する。注文ができなれば、できるまで再試行する。
5.格納していた取引ペアを解消し、削除する。

##プロセス停止・リブート
先物が満期になった際に、価格情報取得のマルチプロセスを停止しています。
その後、新しい先物の板情報が取得された際に、
すべてのマルチプロセスを再起動し、板情報更新に対応するようにしています。

##中断
プログラムを中断すると、現物・先物のポジションはそのままになりますので、
注意が必要です。
反対売買をしてポジションをクローズしたい場合は、取引所サイトで手動のクローズをお願いします。

##ログ出力
main.pyと同じフォルダに`yyyymmdd.log`の形式でログ出力されます(日時は起動時です)。
取引ログだけ閲覧したい場合は、`yyyymmdd_high_level.log`を閲覧してください。
前者は、loggerのレベルがINFO以上。後者はWARNING以上を記録しています。

##Realtime API呼び出し
価格情報の取得にはBitflyer Public APIを定期的に呼び出すのではなく、
Bitflyer Realtime APIの呼び出しを行っております。
これによって、リアルタイムに価格情報の取得が可能になるため、
裁定取引機会をより確実に捉える事が可能になりました。

Realtime APIの呼び出しについては、以下の記事にまとめました。
[BitlflyerのRealtimeAPIをPythonで呼び出す](https://qiita.com/Avocado/items/55a0dd6aed0d2bbf7b94)

#動作画面
##平常時
設定した期間をおいて、以下のログが表示されます。
1. ログ表示時間
2. 取引ペア`[現物BTC_JPY][先物product_code] size:片側のポジション`
3. 現物・先物の板のproduct_codeに続いて、best_ask,best_bid,それぞれのサイズ
4. ログ表示時に裁定取引した際に、どれだけのポジションが取れ、利益が望めるか？
(裁定取引機会ではないので、利益はマイナスで正常です)

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/230272/f2b32cf6-7b6c-f11b-9a4a-99303e298622.png)

###product_codeとプログラム内の板指定について
product_codeはBitflyer APIで、現物・先物の各板に紐づけされた英数字です。
例えば、`BTC_JPY`はビットコイン現物の板を指します。
`BTC_JPY24AUG2018`はビットコイン先物で満期が8月24日である板を指します。
これを指定することにより、API上での取引情報取得などが可能になっています。

プログラム内では、以下のように各板にキーを付け、そこにproduct_codeを紐づけています

```
spot_trading:現物
weekly:１週間以内に満期になる先物
biweekly:２週間以内に満期になる先物
3month：満期が3カ月の先物
```

##実際の取引ログ
約5.6万円(ポジション…現物：約2万円、先物：約2万円(+証拠金約1.6万円))の資金に対して、
合計7円の裁定取引機会が発生しています。
この後、資金が足りず取引はできませんでしたが、
約45円の利益を上げる裁定取引機会もあったことが、ログに残っています。

```
(前略)
2018-08-11 05:47:26,130 CRITICAL 裁定取引機会を見つけました
2018-08-11 05:47:26,131  WARNING 作成予定ペア[spot_trading:698301 : weekly:700610] Position:0.01- Expect profit:6.292137674882056]
2018-08-11 05:47:26,132 CRITICAL 裁定取引を試みます
2018-08-11 05:47:26,133  WARNING 取引可能量:0.02
2018-08-11 05:47:26,135  WARNING 裁定取引の注文をします
2018-08-11 05:47:27,187  WARNING 裁定取引ペアを作成しました
2018-08-11 05:47:28,188 CRITICAL 裁定取引を完了しました
2018-08-11 05:47:28,222 CRITICAL 裁定取引機会を見つけました
2018-08-11 05:47:28,224  WARNING 作成予定ペア[spot_trading:698470 : weekly:700610] Position:0.01- Expect profit:4.6021376748824565]
2018-08-11 05:47:28,225  WARNING 作成予定ペア[spot_trading:698470 : biweekly:702440] Position:0.01- Expect profit:3.260628065865213]
2018-08-11 05:47:28,226 CRITICAL 裁定取引を試みます
2018-08-11 05:47:28,227  WARNING 取引可能量:0.01
2018-08-11 05:47:28,228  WARNING 裁定取引の注文をします
2018-08-11 05:47:29,135  WARNING 裁定取引ペアを作成しました
(中略)
2018-08-11 05:50:34,497 CRITICAL 裁定取引機会を見つけました
2018-08-11 05:50:34,505  WARNING 作成予定ペア[spot_trading:681606 : weekly:687817] Position:0.01- Expect profit:45.61886280658287]
2018-08-11 05:50:34,506  WARNING 作成予定ペア[spot_trading:681606 : biweekly:687273] Position:0.01- Expect profit:21.01742199765807]
2018-08-11 05:50:34,507    ERROR 最大取引量以上になったため裁定取引を行いません
(後略)
```

この時のビットコイン現物相場です。
大きく値下がりし、相場が動いていたことがわかります。

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/230272/30a8a15d-6e6e-91f5-7b99-31637421ae52.png)



#実際に裁定取引BOTを作ってみて
ここでは技術的な観点からのみ列挙します。
裁定取引自体のリスクなどは、前述の[note](https://note.mu/avocadowasabi/n/n6c4e625f5e89)を参照してください。

##「裁定取引」とAPIの安定度
裁定機会取引機会発生時は、チェック対象の相場が大きく動くことが少なからずあります。
それは取引注文が殺到し、サーバーがAPI経由の取引注文を受け付けないことを意味します。
また、正常に受け付けられたように見えても、実際には取引されていないこともありました。

裁定取引を行うということは、買い注文と売り注文の2つについて、
同時にAPIを呼び出すということであり、
瞬発的に負荷の高い行為を行うということを意味します。
本プログラムでは、それを避け、一つ一つの注文を逐次行う形式にしていますが、
その形式ですと、短時間の市場変動の影響が避けられない、擬似的な裁定取引になり、
損益の入る余地があります。

取引APIの登場によって、アマチュアでも自動裁定取引を行えるようになりましたが、
それはAPIとサーバーの安定度を逐次チェックしながらということになります。

##Pythonのマルチプロセス処理
学習コストが低く、かなり気楽に利用できました。
投資BOTを作成する際は、Realtime API処理部分と取引判断部分を分離して、
マルチプロセスにするケースも少なくないと思いますが、
Pythonはその面で耐えうると感じました。

ただ、全体的な負荷は決して軽くはなく、
ラズベリーパイ3 model Bで実行すると4coreあるうちの1coreは100%のままになり、
CPU負荷には偏りがあります。
プログラム設計上、CPU負荷分散に関しては課題が残ります。

##ライセンスを詳しく調べる機会になった
[MIT・LGPLが混在する改変ライブラリを使用したプログラムのライセンスはどうなるのか？](https://qiita.com/Avocado/items/47487b6325dcbb3faa80)
この記事は、まさにこのBOTを公開するにあたり、生まれた疑問です。

#謝辞
・[r2 arbitrager](https://github.com/bitrinjani/r2)には、その設計をはじめ大きな刺激となりました。
その完成度の高さには及びませんが、
今回のBOT作成のきっかけとなりました。

・[pybitflyer](https://github.com/yagays/pybitflyer)により、プログラム内でのPublic/Private APIの呼び出しが標準化されました。

・websocket client(https://github.com/websocket-client/websocket-client)により、
Realtime APIの呼び出しが非常に容易になりました。

以上、お礼申し上げます。