見出し画像

仮想通貨bot 勉強記録㉑

~ドンチャンの損益確認コードを作る~

◆前回までのあらすじ

図1

画像7

ドンチャンチャネルブレイクアウトのロジックを作ってみました。

◆今回やること

図1

・損益確認コードの作成

図2

勉強記録⑲と同じように、損益確認コードを作りました!

作成したコードはこちら↓

from datetime import datetime
from rich import print as pp
import pybybit
import time
import json
import numpy as np

#====================API設定====================
apis = [
'プライベートキー',
'シークレットキー'
]
bybit = pybybit.API(*apis, testnet=True)
#===============================================

#====================バックテスト用の初期設定値====================
lot        = 1000                       # 1トレードのロット
slippage   = 0.00075                    # 手数料やスリッページ(0.075%初期値)
chart_min  = 30                         # (1 3 5 15 30 60 120 240 360 720 "D" "M" "W")
test_start = 0                          # 何番目のデータからテストするか
test_end   = int(60*24*14/chart_min)    # 何番目のデータまでテストするか(この例この例だと14日間)
path       = "backdata/**********.json" # 読み込むファイルのパス
term       = 20                         # 過去n期間
wait       = 0                          # 待機時間


#====================APIから価格データ取得====================
def get_price_from_API(chart_min):

   #取得開始時刻の設定
   get_start = {
       "year"   : int(2021), #年
       "month"  : int(3),    #月
       "day"    : int(1),    #日
       "hour"   : int(00),   #時
       "minute" : int(00)    #分
             }

   get_start = int(datetime(**get_start).timestamp())

   #取得終了時刻の設定
   get_end = {
       "year"   : int(2021), #年
       "month"  : int(4),    #月
       "day"    : int(1),    #日
       "hour"   : int(00),   #時
       "minute" : int(00)    #分
           }

   get_end = int(datetime(**get_end).timestamp())

   #データを格納する変数
   price = []

   #tがnowより小さければ実行
   while get_start < get_end:

      #pybybitでローソク足取得
      k = bybit.rest.inverse.public_kline_list(
             symbol = "BTCUSD",
             interval= chart_min,
             from_ = get_start
             ).json()

      #klinesに取得したデータを入れる
      price += k["result"]
      #200本x足の長さ分だけタイムスタンプを進める
      get_start += 200*60*chart_min

   return price

#====================ファイルから価格データを読み込む====================
def get_price_from_file(path):
   file = open(path,'r',encoding='utf-8')
   price = json.load(file)

   return price
#====================画面出力関====================
def print_price(data):
   pp( " 時間: " + datetime.fromtimestamp(data['open_time']).strftime('%Y/%m/%d %H:%M')
     + " 始値: " + str(data['open'])
     + " 終値: " + str(data['close']))

#====================時間と始値・終値をログに記録====================
def log_price( data,flag ):
  log = "時間: " + datetime.fromtimestamp(data["open_time"]).strftime('%Y/%m/%d %H:%M') + " 始値: " + str(data["open"]) + " 終値: " + str(data["close"]) + "\n"
  flag["records"]["log"].append(log)
  return flag


#====================ロジック判定====================
def donchian( data,last_data ):

   highest = max(i["high"] for i in last_data)
   if data["high"] > highest:
       return {"side":"BUY","price":highest}

   lowest = min(i["low"] for i in last_data)
   if data["low"] < lowest:
       return {"side":"SELL","price":lowest}

   return {"side" : None , "price":0}


#====================買い・売り注文====================
def entry_signal( data,last_data,flag ):

   signal = donchian( data,last_data )

   if signal["side"] == "BUY":
       log = "過去{0}足の最高値{1}$を、直近の高値が{2}$でブレイク\n".format(term,signal["price"],data["high"]) + str(data["close"]) + "$で買い指値\n"
       flag["records"]["log"].append(log)

       # ここに買い注文のコードを入れる

       flag["order"]["exist"] = True
       flag["order"]["side"] = "BUY"
       flag["order"]["price"] = float(data["close"])

   if signal["side"] == "SELL":
       log = "過去{0}足の最安値{1}$を、直近の安値が{2}$でブレイク\n".format(term,signal["price"],data["low"]) + str(data["close"]) + "$で売り指値\n"
       flag["records"]["log"].append(log)

       # ここに売り注文のコードを入れる

       flag["order"]["exist"] = True
       flag["order"]["side"] = "SELL"
       flag["order"]["price"] = data["close"]

   return flag


#====================注文状況確認====================
def check_order( flag ):

   """ここに注文状況確認コード"""

   flag["order"]["exist"] = False
   flag["position"]["exist"] = True
   flag["position"]["side"] = flag["order"]["side"]
   flag["position"]["price"] = flag["order"]["price"]

   return flag


#====================成行決済&ドテン注文====================
def close_position( data,last_data,flag ):

   flag["position"]["count"] += 1

   signal = donchian( data,last_data )

   if flag["position"]["side"] == "BUY":
       if signal["side"] == "SELL":
           log = "過去{0}足の最安値{1}$を、直近の安値が{2}$でブレイク\n".format(term,signal["price"],data["low"]) + "成行注文を出してポジションを決済します\n"
           flag["records"]["log"].append(log)

           # 決済の成行注文コードを入れる
           records( flag,data )
           flag["position"]["exist"] = False
           flag["position"]["count"] = 0

           log = "さらに" + str(data["close"]) + "$で売りの指値注文を入れてドテンします\n"
           flag["records"]["log"].append(log)

           # ここに売り注文のコードを入れる

           flag["order"]["exist"] = True
           flag["order"]["side"] = "SELL"
           flag["order"]["price"] = float(data["close"])

   if flag["position"]["side"] == "SELL":
       if signal["side"] == "BUY":
           log = "過去{0}足の最高値{1}$を、直近の高値が{2}$でブレイク\n".format(term,signal["price"],data["high"]) + "成行注文を出してポジションを決済します\n"
           flag["records"]["log"].append(log)

           # 決済の成行注文コードを入れる
           records( flag,data )
           flag["position"]["exist"] = False
           flag["position"]["count"] = 0

           log = "さらに" + str(data["close"]) + "$で買いの指値注文を入れてドテンします\n"
           flag["records"]["log"].append(log)

           # ここに買い注文のコードを入れる

           flag["order"]["exist"] = True
           flag["order"]["side"] = "BUY"
           flag["order"]["price"] = float(data["close"])

   return flag

#====================トレードパフォーマンス確認====================
def records(flag,data):

   entry_price = float(flag["position"]["price"])
   exit_price = round(float(data["close"]))
   trade_cost  = lot * slippage

   log ="スリッページ・手数料として" + str(trade_cost) + "$を考慮\n"
   flag["records"]["log"].append(log)
   flag["records"]["slippage"].append(trade_cost)

   # 値幅の計算
   buy_profit = exit_price - entry_price
   sell_profit = entry_price - exit_price

   # 利益率の計算
   buy_return = buy_profit/entry_price
   sell_return = sell_profit/entry_price

   #利益・損失の確認
   if flag["position"]["side"] == "BUY":
       flag["records"]["buy-count"] += 1
       flag["records"]["buy-profit"].append( buy_profit )
       flag["records"]["buy-return"].append( buy_return )

       if buy_profit  > 0:
           flag["records"]["buy-winning"] += 1
           log = str(round(buy_return * lot , 4 )) + "$の利益です\n"
           flag["records"]["log"].append(log)
       else:
           log = str(abs(round(buy_return * lot , 4 ))) + "$の損失です\n"
           flag["records"]["log"].append(log)

   if flag["position"]["side"] == "SELL":
       flag["records"]["sell-count"] += 1
       flag["records"]["sell-profit"].append( sell_profit )
       flag["records"]["sell-return"].append( sell_return )

       if sell_profit > 0:
           flag["records"]["sell-winning"] += 1
           log = str(round( sell_return * lot , 4 )) + "$の利益です\n"
           flag["records"]["log"].append(log)
       else:
           log = str(abs(round( sell_return * lot , 4 ))) + "$の損失です\n"
           flag["records"]["log"].append(log)

   return flag


#====================バックテストの集計====================
def backtest(flag):

   total_return_buy = np.sum((flag["records"]["buy-return"])*lot)
   total_return_sell = np.sum((flag["records"]["sell-return"])*lot)


   print("\nバックテスト結果")
   print("==============================")
   print("--------買いエントリの成績--------")
   print("トレード回数   :  {}回".format(flag["records"]["buy-count"]))
   print("勝率        :  {}%".format(round(flag["records"]["buy-winning"] / flag["records"]["buy-count"] * 100,1)))
   print("平均リターン   : {}%".format(round(np.average(flag["records"]["buy-return"])*100,4)))
   print("総損益      :  {}$".format(round((total_return_buy),2)))

   print("\n--------売りエントリの成績--------")
   print("トレード回数   :  {}回".format(flag["records"]["sell-count"] ))
   print("勝率        :  {}%".format(round(flag["records"]["sell-winning"] / flag["records"]["sell-count"] * 100,1)))
   print("平均リターン   :  {}%".format(round(np.average(flag["records"]["sell-return"])*100,4)))
   print("総損益      :  {}$".format(round((total_return_sell),2)))

   print("\n------------総合成績-------------")
   print("総損益      :  {}$".format(round((total_return_buy + total_return_sell),2)))
   print("手数料合計  :  {}$".format( np.sum(flag["records"]["slippage"]) ))
   print("==============================")

   return False

   file =  open("log/donchian-{0}-log.txt".format(datetime.now().strftime("%Y-%m-%d-%H-%M")),'wt',encoding='utf-8')
   file.writelines(flag["records"]["log"])


#====================メイン====================
def main():
   price = get_price_from_API(chart_min)
   last_data = []

   print("テスト期間 ")
   print("==============================")
   print("開始時点 : " + str(datetime.fromtimestamp(float(price[test_start]["open_time"]))))
   print("終了時点 : " + str(datetime.fromtimestamp(float(price[test_end]["open_time"]))))
   print(str(test_end-test_start) + "件のローソク足データで検証")
   print("==============================")

   flag = {
       "order":{
           "exist" : False,   #オーダーの有り無し
           "side"  : "",      #買いか売りか
           "count" : 0,       #保有期間
           "price" : 0        #オーダー価格
       },
       "position":{
           "exist" : False,   #ポジションの有り無し
           "side"  : "",      #買いか売りか
           "count" : 0,       #保有期間
           "price" : 0        #ポジション価格
       },
       "records":{
          "buy-count": 0,     # 買いエントリのトレード数を記録
          "buy-winning" : 0,  # 勝った数を記録
          "buy-return":[],    # 各トレードでのリターン(利益率)を記録
          "buy-profit": [],   # 各トレードでの利益・損失額を記録

          "sell-count": 0,    # 売りエントリのトレード数を記録
          "sell-winning" : 0, # 勝った数を記録
          "sell-return":[],   # 各トレードでのリターン(利益率)を記録
          "sell-profit":[],   # 各トレードでの利益・損失額を記録

          "slippage":[],      # 各トレードで生じた手数料を記録
          "log":[]            # あとでテキストファイルに出力したい内容を記録
   }
}

   i = 0
   while i < test_end:

       # ドンチャンの判定に使う過去term足分の安値・高値データを準備する
       if len(last_data) < term:
           last_data.append(price[i])
           log_price(price[i],flag)
           time.sleep(wait)
           i += 1

           #if文の条件を満たすまでループ処理
           continue


       data = price[i]
       flag = log_price(data,flag) #iの価格を記

       if flag["order"]["exist"]:
           flag = check_order( flag )
       elif flag["position"]["exist"]:
           flag = close_position( data,last_data,flag )
       else:
           flag = entry_signal( data,last_data,flag )


       # 過去データをterm個ピッタリに保つために先頭を削除
       del last_data[0]

       last_data.append( data )
       i += 1
       time.sleep(wait)

   backtest(flag)

main()

コピペで動くよ!
※この.pyファイルがあるフォルダ内に"log"というフォルダを作成する必要があります

◆解説

図1

ほとんど勉強記録⑲と同じなので、省略しながら解説します。

・flag

図2

   flag = {
       "order":{
           "exist" : False,   #オーダーの有り無し
           "side"  : "",      #買いか売りか
           "count" : 0,       #保有期間
           "price" : 0        #オーダー価格
       },
       "position":{
           "exist" : False,   #ポジションの有り無し
           "side"  : "",      #買いか売りか
           "count" : 0,       #保有期間
           "price" : 0        #ポジション価格
       },
       "records":{
          "buy-count": 0,     # 買いエントリのトレード数を記録
          "buy-winning" : 0,  # 勝った数を記録
          "buy-return":[],    # 各トレードでのリターン(利益率)を記録
          "buy-profit": [],   # 各トレードでの利益・損失額を記録

          "sell-count": 0,    # 売りエントリのトレード数を記録
          "sell-winning" : 0, # 勝った数を記録
          "sell-return":[],   # 各トレードでのリターン(利益率)を記録
          "sell-profit":[],   # 各トレードでの利益・損失額を記録

          "slippage":[],      # 各トレードで生じた手数料を記録
          "log":[]            # あとでテキストファイルに出力したい内容を記録
   }
}

最初にメイン関数内に設定している変数:flagを確認しておきましょう。
オーダーの状況、ポジションの状況、損益等の集計のための記録の3項目があり、そこから更に各項目にブレイクダウンしてます。

基本的にはコメントアウトの通りです。
保有期間はカウントしてますが、集計には使ってません。
集計結果がおかしいときは、flagの中身を確認してみると、修正すべき箇所が分かることが多いと思われまする。

◆実行結果

図1

省略しながら解説って書いたけど、勉強記録⑲と内容かぶり過ぎなので解説終わり!

実行するとこんな感じです↓

・集計結果

図2

画像8

めっちゃマイナスやないかーいッ

まぁでも動作確認だし、ちゃんと集計できてるし、成功です

・ログファイルの出力

図2

画像13

実行すると、コードを保存してあるファルダの下位フォルダにlogが保存されます。(実行しすぎ)
僕の場合だとPydocにコードを保存してるので、Pydoc/logにlogファイルが出力されます。


ちなみに各パラメータをいじると総合成績がプラスになることもあります。

例えば30分足で、2021/3/1から14日間に設定すると、手数料を差し引いても損益はプラスでした。

画像9

実はこの行為(結果が良くなるようにパラメータを調整すること)は、過剰最適化といい、あんまり良くないらしいです。

そりゃ結果が良くなるよう手を加えてたら、バックテストの意味もなくなりますよね。

画像10

今回はここまで!
次回は最終損益結果が出るまでのグラフを作成するか、最近使っているSpyderの紹介をしようかな。


この記事が気に入ったらサポートをしてみませんか?