見出し画像

仮想通貨bot 勉強記録⑲

~赤三兵・黒三兵botの損益確認をする~

◆前回までのあらすじ

図1

画像4

画像5

jsonファイルに保存したデータで、バックテスト(動作確認)ができました。

◆今回やること

図1

・バックテストで、ロジックの成績を確認する

図2

動作確認はできたので、次は成績確認です。
バックテストをしてみて、作成したロジックがどれくらい有効なのかを確認します。

めっちゃやりたかったやつです!

作成コード↓

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

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


#====================バックテスト用の初期設定値====================
lot = 100                                    # 1トレードのロット
slippage = 0.00075                           # 手数料やスリッページ(0.075%初期値)
start = 0                                   # 何番目のデータからテストするか
end = 60*24*7                               # 何番目のデータまでテストするか
path = "./1614524400-1617212340-price.json" # 読み込むファイルのパス

#====================価格データを読み込む====================
def get_price_from_file(path):
  file = open(path,'r',encoding='utf-8')
  response = json.load(file)

  # 返り値取得
  return response

#====================画面出力関====================
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 check_candle(data,side):
  try:
      #実体割合の算出
      realbody_rate = abs(float(data["close"]) - float(data["open"])) / float(data["high"])-float(data["low"])
      #実体の大きさの算出
      increase_rate = (float(data["close"]) / float(data["open"]) - 1)

  except ZeroDivisionError:
      return False

  if side == "buy":
      if data["close"] < data["open"] : return False  # ローソク足が赤だったらFalse
      #elif increase_rate < 0.0001 : return False     # 実体の大きさが現在価格の0.01%未満ならFalse
      #elif realbody_rate < 0.1 : return False        # 実体の割合がローソク足の10%未満ならFalse
      else : return True                              # 上記すべて条件が当てはまらなければTrue

  if side == "sell":
      if data["close"] > data["open"] : return False  # ローソク足が緑だったらFalse
      #elif increase_rate > -0.0001 : return False    # 実体の大きさが現在価格の0.01%未満ならFalse
      #elif realbody_rate < 0.1 : return False        # 実体の割合がローソク足の10%未満ならFalse
      else : return True                              # 上記すべて条件が当てはまらなければTrue


#====================ローソク足の連続上昇の判別====================
def check_ascend( data,last_data ):
  #今回の始値が前回の始値を上回っている且つ今回の終値が前回の終値を上回っていればTure
  if data["open"] > last_data["open"] and data["close"] > last_data["close"]:
      return True
  else:
      return False


#====================ローソク足の連続下降の判別====================
def check_descend( data,last_data ):
  #今回の始値が前回の始値を下回っている且つ今回の終値が前回の終値を下回っていればTure
  if data["open"] < last_data["open"] and data["close"] < last_data["close"]:
      return True
  else:
      return False


#====================買いサイン・注文を出す====================
def buy_signal( data,last_data,flag ):
      if flag["buy_signal"] == 0 and check_candle( data,"buy" ):                                     # 陽線1本目
          flag["buy_signal"] = 1
      elif flag["buy_signal"] == 1 and check_candle( data,"buy" ) and check_ascend( data,last_data ):# 陽線2本目
          flag["buy_signal"] = 2
      elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):# 陽線3本目
          log ="3本連続で陽線 なので" + str(data["close"]) + "で買い指値\n"
          flag["records"]["log"].append(log)
          flag["buy_signal"] = 3

          """ここに指値買い注文コード"""

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

      #陽線が途切れたらシグナルリセット
      else:
          flag["buy_signal"] = 0

      return flag


#====================売りサイン・注文を出す====================
def sell_signal( data,last_data,flag ):
  if flag["sell_signal"] == 0 and check_candle( data,"sell" ):#陰線1本目
      flag["sell_signal"] = 1
  elif flag["sell_signal"] == 1 and check_candle( data,"sell" )  and check_descend( data,last_data ):#陰線2本目
      flag["sell_signal"] = 2
  elif flag["sell_signal"] == 2 and check_candle( data,"sell" )  and check_descend( data,last_data ):#陰線3本目
      log ="3本連続で陰線 なので" + str(data["close"]) + "で売り指値\n"
      flag["records"]["log"].append(log)
      flag["sell_signal"] = 3

      """ここに指値売り注文コード"""

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

      #陰線が途切れたらシグナルリセット
  else:
      flag["sell_signal"] = 0

  return flag


#====================決済====================
def close_position( data,last_data,flag ):

  if flag["position"]["side"] == "BUY":
      if data["close"] < last_data["close"]:
          log = "前回の終値を下回ったので" + str(data["close"]) + "$あたりで成行で決済します\n"
          flag["records"]["log"].append(log)

          """ここに成行売り注文コード"""

          records( flag,data )
          flag["position"]["exist"] = False


  if flag["position"]["side"] == "SELL":
      if data["close"] > last_data["close"]:
          log = "前回の終値を上回ったので" + str(data["close"]) + "$あたりで成行で決済します\n"
          flag["records"]["log"].append(log)

          """ここに成行買い注文コード"""

          records( flag,data )
          flag["position"]["exist"] = False


  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 records(flag,data):

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

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

   # 値幅の計算
   buy_profit = exit_price - entry_price - trade_cost
   sell_profit = entry_price - exit_price - trade_cost
   buy_return = buy_profit/entry_price
   sell_return = sell_profit/exit_price

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

       if buy_profit  > 0:
           flag["records"]["buy-winning"] += 1
           log = str(round(buy_profit / entry_price * lot , 4 )) + "$の利益です\n"
           flag["records"]["log"].append(log)
       else:
           log = str(abs(round(buy_profit / entry_price * 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( round( sell_profit / exit_price  , 4 ))

       if sell_profit > 0:
           flag["records"]["sell-winning"] += 1
           log = str(round( sell_profit / exit_price * lot , 4 )) + "$の利益です\n"
           flag["records"]["log"].append(log)
       else:
           log = str(abs(round( sell_profit / exit_price * 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("==============================")

   file =  open("./{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_file(path)

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


  last_data = price[start] # 初期値となる価格データをセット


  flag = {
      "buy_signal":0,
      "sell_signal":0,
      "order":{
          "exist" : False,
          "side" : "",
          "price" : 0,
      },
      "position":{
          "exist" : False,
          "side" : "",
          "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 = 1
  while i < end:
      if flag["order"]["exist"]:
          flag = check_order( flag )

      data = price[i]
      flag = log_price(data,flag)

      if flag["position"]["exist"]:
          flag = close_position( data,last_data,flag )
      else:
          flag = buy_signal( data,last_data,flag )
          flag = sell_signal( data,last_data,flag )

      last_data["open_time"] = data["open_time"]
      last_data["open"] = data["open"]
      last_data["close"] = data["close"]

      i += 1

  backtest(flag)

main()

◆解説

図1

・import

図2

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

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

前回からの変更点は、import numpy as npです。
初登場のモジュール”numpy”を使う準備をしておきます。

・初期設定

図2

#====================バックテスト用の初期設定値====================
lot = 100                                    # 1トレードのロット
slippage = 0.00075                           # 手数料やスリッページ(0.075%初期値)
start = 0                                   # 何番目のデータからテストするか
end = 60*24*1                               # 何番目のデータまでテストするか
path = "./1614524400-1617212340-price.json" # 読み込むファイルのパス

lot:ポジション数量
slippage:注文価格と約定価格のズレ

スリッページは注文から約定までの時差や、自分の注文数量&板の厚さによって変動します。考慮しておいた方がいいです。

・def get_price_from_file(path)

図2

#====================価格データを読み込む====================
def get_price_from_file(path):
  file = open(path,'r',encoding='utf-8')
  response = json.load(file)

  # 返り値取得
  return response

勉強記録⑱で解説しました。
呼び出すときは変数:pathにファイル名を入れます。

・def print_price(data)

図2

#====================画面出力関====================
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 )

図2

#====================時間と始値・終値をログに記録====================
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

変数:logに時間、始値、終値を入れて、flag["records"]["log"]に追加していきます。

.append()について

()の中身をリストに追加できる命令です。めっちゃ便利らしいので覚えておきましょう。

・def check_candle(data,side)

図2

#====================ローソク足の条件判別====================
def check_candle(data,side):
  try:
      #実体割合の算出
      realbody_rate = abs(float(data["close"]) - float(data["open"])) / float(data["high"])-float(data["low"])
      #実体の大きさの算出
      increase_rate = (float(data["close"]) / float(data["open"]) - 1)

  except ZeroDivisionError:
      return False

  if side == "buy":
      if data["close"] < data["open"] : return False # ローソク足が赤だったらFalse
      #elif increase_rate < 0.0001 : return False    # 実体の大きさが現在価格の0.01%未満ならFalse
      #elif realbody_rate < 0.1 : return False       # 実体の割合がローソク足の10%未満ならFalse
      else : return True                             # 上記すべて条件が当てはまらなければTrue

  if side == "sell":
      if data["close"] > data["open"] : return False  # ローソク足が緑だったらFalse
      #elif increase_rate > -0.0001 : return False    # 実体の大きさが現在価格の0.01%未満ならFalse
      #elif realbody_rate < 0.1 : return False        # 実体の割合がローソク足の10%未満ならFalse
      else : return True                              # 上記すべて条件が当てはまらなければTrue  

勉強記録⑱で解説しました。
が、今回はローソク足条件の実体割合・大きさの判定はしないようにコメントアウトしています。

あと、realbody_rateとincrease_rat例外処理をひとまとめに変更しました。(前回までは別々に行うコードにしてた)

・def check_ascend( data,last_data )

図2

#====================ローソク足の連続上昇の判別====================
def check_ascend( data,last_data ):
  #今回の始値が前回の始値を上回っている且つ今回の終値が前回の終値を上回っていればTure
  if data["open"] > last_data["open"] and data["close"] > last_data["close"]:
      return True
  else:
      return False

勉強記録⑱と全く同じ。

・def check_descend( data,last_data )

図2

#====================ローソク足の連続下降の判別====================
def check_descend( data,last_data ):
  #今回の始値が前回の始値を下回っている且つ今回の終値が前回の終値を下回っていればTure
  if data["open"] < last_data["open"] and data["close"] < last_data["close"]:
      return True
  else:
      return False

これも勉強記録⑱と全く同じ。

・def buy_signal( data,last_data,flag )

図2

#====================買いサイン・注文を出す====================
def buy_signal( data,last_data,flag ):
      if flag["buy_signal"] == 0 and check_candle( data,"buy" ):                                     # 陽線1本目
          flag["buy_signal"] = 1
      elif flag["buy_signal"] == 1 and check_candle( data,"buy" ) and check_ascend( data,last_data ):# 陽線2本目
          flag["buy_signal"] = 2
      elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):# 陽線3本目
          log ="3本連続で陽線 なので" + str(data["close"]) + "で買い指値\n"
          flag["records"]["log"].append(log)
          flag["buy_signal"] = 3

          """ここに指値買い注文コード"""

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

      #陽線が途切れたらシグナルリセット
      else:
          flag["buy_signal"] = 0

      return flag

buy_signalが3になったときの処理を1行追加してます。
flag["order"]["price"] = round(float(data["close"]) * lot)で、終値*lotをflag["order"]["price"]に入れます。

・def sell_signal( data,last_data,flag )

図2

#====================売りサイン・注文を出す====================
def sell_signal( data,last_data,flag ):
  if flag["sell_signal"] == 0 and check_candle( data,"sell" ):#陰線1本目
      flag["sell_signal"] = 1
  elif flag["sell_signal"] == 1 and check_candle( data,"sell" )  and check_descend( data,last_data ):#陰線2本目
      flag["sell_signal"] = 2
  elif flag["sell_signal"] == 2 and check_candle( data,"sell" )  and check_descend( data,last_data ):#陰線3本目
      log ="3本連続で陰線 なので" + str(data["close"]) + "で売り指値"
      flag["records"]["log"].append(log)
      flag["sell_signal"] = 3

      """ここに指値売り注文コード"""

      flag["order"]["exist"] = True
      flag["order"]["side"] = "SELL"
      flag["order"]["price"] = lot

      #陰線が途切れたらシグナルリセット
  else:
      flag["sell_signal"] = 0

  return flag

def buy_signal( data,last_data,flag )と同じ。

・def close_position( data,last_data,flag )

図2

#====================決済====================
def close_position( data,last_data,flag ):

  if flag["position"]["side"] == "BUY":
      if data["close"] < last_data["close"]:
          log = "前回の終値を下回ったので" + str(data["close"]) + "円あたりで成行で決済します\n"
          flag["records"]["log"].append(log)

          """ここに成行売り注文コード"""

          records( flag,data )
          flag["position"]["exist"] = False

  if flag["position"]["side"] == "SELL":
      if data["close"] > last_data["close"]:
          log = "前回の終値を上回ったので" + str(data["close"]) + "円あたりで成行で決済します\n"
          flag["records"]["log"].append(log)

          """ここに成行買い注文コード"""

          records( flag,data )
          flag["position"]["exist"] = False

  return flag

決済時に以下のコードで変数:logに追加したいコメントの設定をし、flag["records"]["log"]に追加する指示を出します。

BUYの時

log = "前回の終値を下回ったので" + str(data["close"]) + "円あたりで成行で決済します\n"
flag["records"]["log"].append(log)

SELLの時

log = "前回の終値を上回ったので" + str(data["close"]) + "円あたりで成行で決済します\n"
          flag["records"]["log"].append(log)

・def check_order( flag )

図2

#====================注文状況確認====================
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

flag["position"]の中に、["price"]の項目を追加しています。オーダーが約定した場合、決済時の値幅の確認用に使います。

・def records(flag,data)

図2

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

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

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

   # 値幅の計算
   buy_profit = exit_price - entry_price - trade_cost
   sell_profit = entry_price - exit_price - trade_cost
   buy_return = buy_profit/entry_price
   sell_return = sell_profit/exit_price

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

       if buy_profit  > 0:
           flag["records"]["buy-winning"] += 1
           log = str(round(buy_profit / entry_price * lot , 4 )) + "$の利益です\n"
           flag["records"]["log"].append(log)
       else:
           log = str(abs(round(buy_profit / entry_price * 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( round( sell_profit / exit_price  , 4 ))

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

   return flag

新規関数です。決済時に、利益の確認をするための関数です。

図3

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

エントリー価格、決済価格、スリッページを設定します。

図3

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

logにスリッページのことを追記する部分です。
flag["records"]["log"]logを追加、flag["records"]["slippage"]append(trade_cost)を追加する指示をします。

図3

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

買いトレード、売りトレードでの獲得値幅の計算をします。

図3

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

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

買いポジションの時の利益・損失の計算をする部分です。
個々に説明していきます。

flag["records"]["buy-count"] += 1

買いエントリの回数を記録するため、flag["records"]["buy-count"] を+ 1します。

flag["records"]["buy-profit"].append( buy_profit )

flag["records"]["buy-profit"]に、先ほど計算した獲得値幅:buy_profitを追加します。

flag["records"]["buy-return"].append( round( buy_profit / entry_price  , 4 ))

flag["records"]["buy-return"]に行ったトレードのリターン:round( buy_profit / entry_price , 4 )を記録します。
内容は”獲得値幅/エントリー価格の、小数点4桁まで表示”です。

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

勝利回数を記録するため、獲得値幅がプラス(利益)のとき、flag["records"]["buy-winning"]を+1します。

また、flag["records"]["log"]log = str(round(buy_profit / entry_price * lot , 4 )) + "$の利益です\n"を追加します。

else:
        log = str(abs(round(buy_profit / entry_price * lot , 4 ))) + "$の損失です\n"
        flag["records"]["log"].append(log)

獲得値幅がマイナス(損失)のとき、flag["records"]["log"]log = str(abs(round(buy_profit / entry_price * lot , 4 ))) + "$の損失です\n"を追加します。

図3

if flag["position"]["side"] == "SELL":
       flag["records"]["sell-count"] += 1
       flag["records"]["sell-profit"].append( sell_profit )
       flag["records"]["sell-return"].append( round( sell_profit / exit_price  , 4 ))

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

売りポジションの場合も買いポジションの時と同じ処理をします。

・def backtest(flag)

図2

#====================バックテストの集計====================
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("==============================")

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

バックテストの結果を集計・表示・txtファイルに保存する関数です。

買いエントリ・売りエントリで集計を分けて、最後に合計して総合成績を出してます。分ける方がロジックの修正すべき点が分かりやすいし、コードも作りやすかったです。

図3

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

事前に買いエントリ・売りエントリ時の総損益を算出しておきます。

図3

   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)))
トレード回数:買いエントリの回数
勝率    :勝利回数/買いエントリの回数*100の小数点第一位表示
平均リターン:買いエントリ時のリターンの平均をnp.averageで算出し、小数点以下第4位表示
総利益   :買いエントリ時の総損益(total_return_buy)を表示

図3

print("\n------------総合成績-------------")
print("総損益      :  {}$".format( np.sum(flag["records"]["sell-profit"]) + np.sum(flag["records"]["buy-profit"]) ))
print("手数料合計  :  {}$".format( np.sum(flag["records"]["slippage"]) ))
print("==============================")
総利益  :買いエントリの損益と売りエントリの損益を合算
手数料合計:np.sumでflag["records"]["slippage"]の合計を算出

・def main() その1

図2

#====================メイン====================
def main():
  price = get_price_from_file("./1614524400-1617212340-price.json")

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


  last_data = price[start] # 初期値となる価格データをセット
変数:priceにあらかじめ作成してあるjsonファイルを読み込ませる

開始時点:jsonファイル[start]番目のローソク足の["open_time"]を表示
終了時点:jsonファイル[end]番目のローソク足の["open_time"]を表示

last_dataに[start]番目のローソク足データを入れる

startとendは初期値として最初に設定した値です。

・def main() その2

図2

 flag = {
      "buy_signal":0,
      "sell_signal":0,
      "order":{
          "exist" : False,
          "side" : "",
          "price" : 0,
      },
      "position":{
          "exist" : False,
          "side" : "",
          "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の中身です。
大きく変わったのは"records"の項目で、サブ関数でカウントしたり算出した記録を保存する変数です。
内容はコメントアウトの通りです。

・def main() その3

図2

  i = 1
  while i < end:
      if flag["order"]["exist"]:
          flag = check_order( flag )

      data = price[i]
      flag = log_price(data,flag)

      if flag["position"]["exist"]:
          flag = close_position( data,last_data,flag )
      else:
          flag = buy_signal( data,last_data,flag )
          flag = sell_signal( data,last_data,flag )

      last_data["open_time"] = data["open_time"]
      last_data["open"] = data["open"]
      last_data["close"] = data["close"]

      i += 1

  backtest(flag)

最後のwhile文の部分です。i

図3

if flag["order"]["exist"]:
          flag = check_order( flag )

flag["order"]["exist"]Trueのとき、flagcheck_order( flag )からの返り値を代入します。
check_order( flag )からの返り値は以下の通りです。

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

図3

data = price[i]
flag = log_price(data,flag)

dataprice[i]を代入し、flaglog_price(data,flag)からの返り値を代入します。
priceは読み込みファイルの[i]番目のローソク足データで、log_price(data,flag)からの返り値は以下の通りです。

flag["records"]["log"].append(log)

.append(log)の中身はlog_price(data,flag)内の変数:log参照

図3

if flag["position"]["exist"]:
    flag = close_position( data,last_data,flag )
else:
    flag = buy_signal( data,last_data,flag )
    flag = sell_signal( data,last_data,flag )

flag["position"]["exist"]がTrueの時、flagにclose_position( data,last_data,flag )からの返り値を代入します。
close_position( data,last_data,flag )からの返り値は以下の通りです。

flag["records"]["log"].append(log)
flag["position"]["exist"] = False

.append(log)の中身はclose_position( data,last_data,flag )内の変数:log参照

図3

last_data["open_time"] = data["open_time"]
last_data["open"] = data["open"]
last_data["close"] = data["close"]

i += 1

最後にlast_data内の各変数にdataの各変数を代入し、iを+1して終了です。

ここまでの処理をiendになるまで繰り返します。

解説終わり!長かった。。。

◆実行結果

図1

実行するとこんな感じ

画像37

総合成績がプラスになっているだと。。。?

こちらの記事を参考にコードを作成したのですが、リターンは買い・売りともにマイナスになっていました。

たぶん、bybit用に改修した部分のどこかが間違っていると思いますが、原因不明。。。それか本当にプラスになるのか。。。?

めちゃくちゃ間違っている個所を探してますが、全然わからないっす。
完全に沼ってしまった。
一緒に試行錯誤してくれる人ほしい。。。

画像38


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