【Python実践編】ビットコインのアービトラージ（裁定取引）コード例

***
[8/27追記]
投資関連のPythonプラグラム等を自由にシェアできるサービスのベータ版を作成しました。
興味がある方は覗いてみてください↓

[inbaseシェア|EA・bot・プログラムのシェアサービス](https://share.in-base.com/)
***
この投稿では、Python3を使って**仮想通貨の裁定取引**を行います。
今回は、**コインチェック**と**GMOコイン**の価格差を利用してサヤ抜きを行うことを目指します。

以前自分のブログで、 

[【Pythonデモコード】仮想通貨のアービトラージ（裁定取引）botの作り方](https://in-base.com/arbitrage-bitcoin-python)

という記事を公開したのですが、こちらはあくまでもシュミレーションで実際に売買が作動することはありませんでした。
今回は実際に取引所のAPIを操作するところまでコートに組み込んでみました。

なおコインチェックと GMO コインの口座開設から API キーの発行までは下の記事で公開している手順と全く同じです。
一応画像付きで解説しているので、まだ API キーを発行していない人は参考にしてみてください。
（必要なライブラリなども下記記事内で言及しています）


・[【コピペOK】仮想通貨をPythonで自動売買する方法を初心者向けに解説【coincheck】](https://in-base.com/crypt-auto-trading-coincheck)

・[【Python3】MACDによるビットコインの自動売買botの作り方【GMOコイン編】]
(https://in-base.com/crypt-auto-trading-gmo-macd)

**なお僕自身、この記事を投稿している段階でこのコードを本番環境でまだ動かしていないので注意してください。**
この記事は自分自身への忘備録的な意味も込めて公開しています。
また、例によってエラー処理などは組み込んでいないのでそこは注意です。


## アービトラージコード本体

さて、とりあえずコード本体を掲載します。

```python3
from coincheck import market,order,account
import time
import pandas as pd
from gmocoiner import GMOCoin
import json

#interval_sec秒毎にビットコイン価格観測
interval_sec = 3
#7時点毎に価格差の時系列の平均など計算
duration=7
#変動幅がsigma以上でポジション取得判定
sigma=1

#botを何秒くらい動かすか
working_time=300

#セキュリティの問題で、APIキーはコードに直接書かない方が良い
gmo_api_key = input("GMOのAPIキーを入力してください")
gmo_secret = input("GMOのAPIシークレットを入力してください")

cc_access_key=input("コインチェックのAPIのアクセスキーを入力してください")
cc_secret_key=input("コインチェックのAPIのシークレットキーを入力してください")



def price_data_collecting(how_many_samples=20):
    print("まずは今から"+str(how_many_samples*interval_sec)+"秒間、価格データを収集します。")
    gmo_price_list=[]
    cc_price_list=[]
    for i in range(how_many_samples):
        gmo_price_list.append(float(gmo.ticker(symbol='BTC_JPY').json()["data"][0]["last"]))
        cc_price_list.append(m1.ticker()["last"])
        time.sleep(interval_sec)

    print("収集が完了しました。")
    return gmo_price_list,cc_price_list


#coincheckのAPIラッパーインスタンス
m1=market.Market()
o1 = order.Order(secret_key=cc_secret_key,access_key=cc_access_key)

#gmoのAPIラッパーインスタンス
gmo = GMOCoin(gmo_api_key, gmo_secret, late_limit=True, logger=None)

#初めは、相場のことが何も分からないので、しばらく何もせず価格データを集める
gmo_sample_prices,coincheck_sample_prices = price_data_collecting()

#収集したデータをデータフレームに放り込む
df = pd.DataFrame()
df["coincheck"]=coincheck_sample_prices
df["gmo"]=gmo_sample_prices

#現在ポジションを取っているか
have_position = False


for i in range(int(working_time/interval_sec)):

    #直近価格を取得し、先程のdfに加える
    gmo_last_price = float(gmo.ticker(symbol='BTC_JPY').json()["data"][0]["last"])
    coincheck_last_price = m1.ticker()["last"]
    df=df.append({'gmo': gmo_last_price,'coincheck':coincheck_last_price}, ignore_index=True)

    #2つの取引所の価格差に関する計算
    df["c-g"]=df["coincheck"]-df["gmo"]
    df["c-g_mean"]=df["c-g"].rolling(window=duration).mean()
    df["c-g_std"]=df["c-g"].rolling(window=duration).std()

    df["c-g_upper_limit"]=df["c-g_mean"]+sigma*df["c-g_std"]
    df["c-g_lower_limit"]=df["c-g_mean"]-sigma*df["c-g_std"]

    #coincheckがGMOコインよりも価格が高いときのみポジション取得を検討する仕様。逆をしないのは、単に簡便さのため。
    should_take_potision = df.iloc[i]["c-g"] > df.iloc[i]["c-g_upper_limit"]
    should_release_position = df.iloc[i]["c-g"] < df.iloc[i]["c-g_lower_limit"]

    if (not have_position) and should_take_potision:
        print("coincheckで買い、gmoで売却（ポジション取得）します. 価格差:"+str(df.iloc[i]["c-g"]))
        #コメントアウトしている部分が売買注文を出す部分
        # order_resp=gmo.order("BTC_JPY","SELL","MARKET","0.02").json()
        # o1.sell_btc_jpy(rate=str(coincheck_last_price),amount=0.02)
        have_position=True

    elif have_position and should_release_position:
        print("coincheckで売却、gmoで買い戻し（ポジションを解消）します. 価格差:"+str(df.iloc[i]["c-g"]))
        #コメントアウトしている部分が売買注文を出す部分
        # order_resp=gmo.order("BTC_JPY","BUY","MARKET","0.02").json()
        # o1.buy_btc_jpy(rate=str(coincheck_last_price),amount=0.02)
        have_position=False

    else:
        print("何もしません")
        pass

    #先頭行を削除してdfの長さを一定に保つ（長時間の運用時のメモリ対策）
    df=df.drop(df.index[0])
    time.sleep(interval_sec)


```


## 裁定取引コードの解説

簡単にコードの動きを解説しておきます。
今回のコードは、完全な裁定取引そのものを意図したものではありません。
ここで言う完全な裁定取引というのは、
**①価格差が開いた時に高い方を売り、低い方を買う**
**②価格差が0になった時にそれぞれ反対売買する**
ことを指すとします。

そうではなくて、今回のプログラムは
**①価格差が開いた時に高い方を売り、低い方を買う**
**②価格差が縮まった時にそれぞれ反対売買する**
としています。
こうする理由は、価格差が完全に消滅するのを待っていたら、相当な時間がかかる可能性があるからです。

今回の場合は、**過去duration（デフォルトで7）時点の平均に対してsigma(デフォルトで1)以上価格差が高くなっている時**にポジションを取りに行きます。

その後、**逆に平均に対してsigma以上価格差が縮んだ時**に反対売買する仕組みになっています。

つまり、**完全な裁定取引とは違い、数学的な意味で絶対に損失が出ないわけではありません。**
完全な最低取引のみ行いたいという人は、コードを少し弄って運用すると良いでしょう。

durationやsigmaといったパラメーターも、何か根拠があったわけではなくてとりあえず雰囲気で決めただけです。
ここも色々いじってみてください。

## その他プログラムの注意点

そういえば今回使用しているコインチェックの API ラッパーがなぜか成り行き注文に対応していないという問題があったと思います（曖昧）。
なのでコードの中ではコインチェックの注文は成り行きではなくて、直近の売買価格での指値注文となってしまっています。

もちろん元の API 自体は成り行きでも注文を出せるので、必要な人は API ラッパーのクラスを継承して成行注文用のメソッドを装備してあげるとよいでしょう。

あと今回のコードでは、**ポジションを取るときは常にコインチェックを買って GMO で売る**という動きをするようになっています。
コメントにも書きましたが逆をしないのは、単にコードの行数を減らしてスッキリさせるために過ぎません。
こちらも必要であれば適宜逆の動きも実装していくと良いでしょう。

（関連記事）
・[ディープラーニングAIで仮想通貨を自動売買してみる](https://in-base.com/crypt-auto-trading-using-deep-learning/)