Bitcoinの自動取引botのバックテストをしてみたら思ったより利益出てる

2020/5/24追記
昔書いた拙いコードが今も読まれているようなのでこちらに簡単にまとめなおしましたので合わせて参考にしてください。
https://alumi-labo.com/article/study/ゼロから始めるBitcoinの自動取引①


#はじめに
こんにちは。Python初心者です。
以前Pythonを使ってbitFlyerでの自動取引プログラムの記事を書いたのですが今回はそのバックテストをしていこうと思います。

[以前の記事：[bitFlyerのAPIを利用して自動取引するPythonプログラムを書いてみた]](https://qiita.com/sodiumplus3/items/b69dbd3e51fc2a0f7e01)

#変更していくポイント
基本的に前回書いたものと変わりません。
前回のプログラムでは30秒ごとに直近500本のろうそくデータを取得して、さらに100件の終値のデータに抽出していました。
が、今回はそんなことをする必要はありません。はじめにデータを引っ張ってきてそれの切り出す部分を変えていき、あたかも過去の時点からデータが更新されていくかのようにすれば良いのです。

実は、このデータの取得が大変で、今回はcryptowatchのAPIを用いて過去6000本分のろうそくデータを取得しますが、この自動取引botは５分足を採用しているので、6000本のデータはおよそ２０日分のデータにすぎません。本当は３ヶ月分くらいで試したかったのですが・・・。
どなたか５分足のOHLCデータを得られる良い方法があれば教えていただきたいです。
cryotowatchのAPIでは通常直近500本のデータしか帰ってきませんが、afterというパラメータを指定することで6000件の直近データが帰ってくるようになります。値はなんでもいいです。afterとかいう名前ではありますが別に1を指定したところでunixtime:1時点からのデータが帰ってくるわけではないです。

次に、このデータ取得方法の変更に伴って各関数を少し調整してやります。
一つ一つ説明するのは面倒なので、「ふーん」くらいでいいです。
といっても実際変えるのはATRくらいでした。今回使う戦略はRSIとMACDとATRしか使わないので。

最後に実際に取引を行う部分ですが、実際に注文をする関数やポジションを取得する関数は使わないので消します。かわりに利確や損切りを記録できるようにします。

#コード
以上の変更点を踏まえた実際のコードは以下です。

```python:bitcoin_test.py
# coding: UTF-8

import hashlib
import hmac
import requests
import datetime
import json
from pprint import pprint
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt


#-----------------------------------------------------------------
#取引所関係のmethod

#bitFlyerのAPIを読み込んで終値のデータをpandasのSeriesにして返す関数
def get_price_data():
    response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params = { "periods" : period ,"after" : 1})
    response = response.json()
    close_data = []
    for i in range(6000):
        close_data.append(response["result"][str(period)][i][4])
    arr = np.array(close_data)
    return pd.Series(arr)

#--------------------------------------------------------------------
#テクニカル指標実装系method
    
#EMA
#EMA_periodは期間、nはろうそく何本分前の値か
def EMA(EMA_period,n):
    EMA_data = []
    for i in range(2*EMA_period):
        EMA_data.insert(0,close[data_n-1-i])
    if n == 0:
        arr = np.array(EMA_data)[-EMA_period:]
    else:
        arr = np.array(EMA_data)[-n-EMA_period:-n]
    #print(arr)
    EMA = pd.Series(arr).ewm(span=EMA_period).mean()
    #print(EMA)

    return EMA[EMA_period-1]
    

#MACD
#a=短期EMA_period,b=長期EMA_period,s=シグナル期間
def MACD_and_signal(a,b,s):
    MACD = []
    for i in range(a):
        MACD.insert(0,EMA(a,i)-EMA(b,i))
    arr = np.array(MACD)[-s:]
    Signal = pd.Series(arr).rolling(s).mean()
    
    return MACD,Signal

    
#ATR
#nは期間、n=14が普通
def ATR(n):
    data = []
    for i in range(2*n-1):
        p1 = response[data_n-i-1][2]-response[data_n-i-1][3] #当日高値-当日安値
        p2 = response[data_n-i-1][2]-response[data_n-i-2][4] #当日高値-前日終値
        p3 = response[data_n-i-1][3]-response[data_n-i-2][4] #当日安値-前日終値
        tr = max(abs(p1),abs(p2),abs(p3))
        data.insert(0,tr)
    arr = np.array(data)[-n:]
    #print(arr)
    ATR = pd.Series(arr).ewm(span=n).mean()
    #print(ATR)
    return ATR[n-1]


#RSI
#pは期間
def RSI(p):
    RSI_period = p
    #RSI_data = 
    diff = close.diff(1)
    positive = diff.clip_lower(0).ewm(alpha=1.0/RSI_period).mean()
    negative = diff.clip_upper(0).ewm(alpha=1.0/RSI_period).mean()
    RSI = 100-100/(1-positive/negative)
    return RSI
    


#--------------------------------------------------------------------
#評価値系

#RSIとMACDによる売りサイン
def sell_signal():
    if * * *:
    return True
    else: return False
        
        
#RSIとMACDによる買いサイン
def buy_signal():
    if * * *:
    return True
    else: return False

#--------------------------------------------------------------
#ここからアルゴリズム

#設定
#何秒足か
period = 60 
#終値配列の長さ
data_n = 100
#flag
flag = {
    "check":True,
	"sell_position":False,
	"buy_position":False
}
close_data = get_price_data()
response_data = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params = { "periods" : period , "after" : 1})
response_data = response_data.json()
i = profit = loss = count1 = count2 = drawdown = start = 0
asset_list = [0]


while i < 5500:
    while(flag["check"]):
        response = []
        closelist = []
        for j in range(data_n):
            response.append(response_data["result"][str(period)][i+j+start])
            closelist.append(close_data[i+j+start])
        arr = np.array(closelist)
        close = pd.Series(arr)
        print(response[data_n-1][0])
        print(close[data_n-1])
    
    
    
        if buy_signal():
            print("買い注文をします")
            print("ATR:"+str(int(ATR(14))))
            price = close[data_n-1]
            width = int(2*ATR(14))
            flag["buy_position"] = True
            flag["check"] = False
        #else:print("買い注文しません")
        
        if sell_signal():
            print("売り注文をします")
            print("ATR:"+str(int(ATR(14))))
            price = close[data_n-1]
            width = int(2*ATR(14))
            flag["sell_position"] = True
            flag["check"] = False
        #else:print("売り注文しません")
        
        i += 1
    
    
    while(flag["sell_position"]):
        response = []
        closelist = []
        for j in range(data_n):
            response.append(response_data["result"][str(period)][i+j+start])
            closelist.append(close_data[i+j+start])
        arr = np.array(closelist)
        close = pd.Series(arr)
        print(response[data_n-1][0])
        print(close[data_n-1])
        if response[data_n-1][3] < price-width:
            print("利確:+"+str(width))
            count1 += 1
            profit += width
            flag["sell_position"] = False
            flag["check"] = True
        if response[data_n-1][2] > price+width:
            print("損切り:-"+str(width))
            count2 += 1
            loss += width
            flag["sell_position"] = False
            flag["check"] = True
        i += 1
        
    while(flag["buy_position"]):
        response = []
        closelist = []
        for j in range(data_n):
            response.append(response_data["result"][str(period)][i+j+start])
            closelist.append(close_data[i+j+start])
        arr = np.array(closelist)
        close = pd.Series(arr)
        print(response[data_n-1][0])
        print(close[data_n-1])
        if response[data_n-1][2] > price+width:
            print("利確:+"+str(width))
            count1 += 1
            profit += width
            flag["buy_position"] = False
            flag["check"] = True
        if response[data_n-1][3] < price-width:
            print("損切り:-"+str(width))
            count2 += 1
            loss += width
            flag["buy_position"] = False
            flag["check"] = True
        i += 1
    
    asset_list.append(profit-loss)
    
    if drawdown > profit - loss:
        drawdown = profit - loss
    


print("利益合計："+str(profit))
print("損失合計："+str(loss))
print("儲け："+str(profit-loss))
print("利確回数："+str(count1))
print("損切り回数："+str(count2))
ts = pd.Series(np.array(asset_list))
ts.plot()
plt.show()
```

最後は横軸に決済回数をとって収益をmatplotlibでグラフ化してます。
ちなみに縦軸は全て1BTCで取引したときの利益になります。

iがループ回数ですがエラー吐かれるとめんどくさいので5500にしてます。多分限界は5899回とかまでいけるんじゃないかなあ。


#検証結果
さて、いよいよ検証結果を見てみましょう。
戦略についてのコードは責任取れないので一部省略してます。
一つ明かすとすると、ある条件でエントリーしてyATRの値幅でポジション持ちます。
今回はy=1,1.5,2で検証してみます。


###y=1のとき
利益合計：83693
損失合計：59737
儲け：23956
利確回数：44
損切り回数：36

![スクリーンショット 2018-05-30 18.25.16.png](https://qiita-image-store.s3.amazonaws.com/0/244328/733dbd3f-85a9-26e6-8b7d-abe15f259737.png)


###y=1.5のとき
利益合計：122937
損失合計：76056
儲け：46881
利確回数：42
損切り回数：33

![スクリーンショット 2018-05-30 18.41.26.png](https://qiita-image-store.s3.amazonaws.com/0/244328/5b225cb2-7205-d634-f2ca-66c756cec1e2.png)


###y=2のとき
利益合計：136952
損失合計：84431
儲け：52521
利確回数：39
損切り回数：26

![値幅2ATR](https://qiita-image-store.s3.amazonaws.com/0/244328/7cfa2f1c-7ca9-e145-6920-7155f06f0bbf.png)


とまあこんな感じになりました。諸事情で1分足でやってます。ご注意を。
どうやら値幅を増やしていくにつれて利益が増しているようなのでy=3,5も追加でやってみました。
![スクリーンショット 2018-05-30 18.47.18.png](https://qiita-image-store.s3.amazonaws.com/0/244328/cc0f13a2-ab21-ce6f-2f98-be906cf666d2.png)
![スクリーンショット 2018-05-30 18.54.46.png](https://qiita-image-store.s3.amazonaws.com/0/244328/3979ff3d-8473-8ad1-7003-a2ec7cfe99b6.png)


さて、この結果を見るとどうやら3ATR付近が最適みたいですね。**あくまでこの数日の場合は、です。**
本当は機械学習を用いるともっと厳密な値がわかるんでしょうが、それはまた次の機会に。

まあそれはともかく・・・

**え、利益、出てない？**

１分足なので約4日分のテストでしかないですし、時期が良かったところもあるんでしょうけど、ここ４日で10万円にレバレッジ15倍かけてたとして各取引数量1BTCでまわしてたら7万近く儲かってたんですかね。だとしたら、えぐいな。

という夢も膨らむ結果なわけですが、これには落とし穴がありまして、実際のIFDOCO注文のときはIFD注文とSTOP注文を成行注文で行っているのが関係してきます。
bitFlyerのAPIは注文遅延や注文拒否がかなり頻繁に起こるため、実際の運用では想定している約定価格からズレが生じることになります。そうするとバックテストの結果ともずれてきます。
あとは、データ数が少なすぎる問題もあります。過去４日間でいくら成績が良かったとしても、これからも同様の成績を出せるとはとても言えないでしょう。
残念。

とはいえ、まだまだ予想がつかない部分が多いので、人柱として自分が実際に5000円ほどの資金で実験してみたいと思います。お楽しみに。

**１ヶ月ほど運用してプラスなるようでしたら完成版のコードを公開しようと思っているので、ぜひいいねやコメントお願いしますm(__)m（励みになります）**

6/11追記：
![スクリーンショット 2018-06-11 13.04.36.png](https://qiita-image-store.s3.amazonaws.com/0/244328/e1ddda39-8538-3bad-f9b5-86eff6dc4e8f.png)
いやあさすがにそんなに人生甘くありませんでしたね。ほんとにこのバックテスト期間だけ成績が異常に良かったようです。1分足データは引き続き貯めていこうと思うのでまた10日後くらいにさらなる検証結果を追記します。

6/20追記：
相変わらず1分足は利益でなさそうです。見込みがなくなったので現金を使って動かすのはやめました。
代わりに30分足を使ったものがバックテストで良かったので中長期トレードとして動かしてます。中長期なので一日何もポジションを持たないこともありますが、ゆったりとしたトレードでエラーが少ないです。長い目での利益を期待しているので、短期の損益を気にしないことが重要ですね。また追記します。
バックテストの結果
![スクリーンショット 2018-06-20 8.22.36.png](https://qiita-image-store.s3.amazonaws.com/0/244328/c087cd25-4e2f-c2f6-4482-bc7516db858d.png)

8/1追記：
これ、思いの外良いロジックだったみたいで、動かしてたらいつのまにか資産が２倍になってました・・・。目指せ自堕落生活。永遠に好きなこと勉強して開発して生きていきたい・・・。


では今回はこの辺で。
最後まで読んでいただきありがとうございました。