見出し画像

仮想通貨bot 勉強記録㉙

~フィルターで無駄なトレードを減らす~

今この記事を書いてる時刻が 5/4 10:00くらい なんですが、ついにbotを動かし始めました!!!!!
noteの更新もしようと思ってたけど、せっかくGWでしかも自粛要請が出てるので、これを機に色々進めました。

この勉強記録はbotを動かすところまで書こうと思ってるので、メモついでに、見てくれている人もbotを動かせるようにできたらいいなと思っておりまする。('ω')

◆前回までのあらすじ

図1

トレイリングストップを実装して、利益増/損失減を効果を体感しました。

◆今回やること

図1

・フィルターでトレンドに則ったトレードに絞る

図2

相場には上げ相場、下げ相場、レンジ相場の3つがあると思うんですけど、ロングするときは上げ相場の時/ショートするときは下げ相場の時 というルールを付けます。これにより、トレードが損切りにあう確率を減らし、無駄な損失を減らしたい。

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

# -*- coding: utf-8 -*-
"""
Created on Tue May  4 14:41:48 2021

@author: Mamu
"""

from datetime import datetime
import pybybit
import time
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

#====================API設定====================
apis = [
'プライベートキー',
'シークレットキー'
]

bybit = pybybit.API(*apis, testnet=True)
#===============================================

#--------------------設定値------------------------
#====================bot設定====================
stop_range       = 2                                  # ATRの何倍を損切りラインにするか
volatility_term  = 28                                 # ATRを算出するための足の数
trade_risk       = 0.05                                # 1トレードあたり口座の何%まで損失を許容するか
levarage         = 3                                  # レバレッジ倍率の設定
start_funds      = 1000                               # シミュレーション時の初期資金
entry_times      = 5                                  # ポジションの分割数
entry_range      = 0.4                                # 分割ポジションの値幅
stop_config      = "TRAILING"                         # OFF / ON / TRAILING
trail_ratio      = 0.5                                # 価格が1レンジ動くたびに何レンジ損切り位置をトレイルするか
trail_until_breakeven = True                          # 損益ゼロの位置までしかトレイルしない

#//////////////////////////////////
filter_VER = "D"  # OFFでフィルター無効
#//////////////////////////////////

#====================バックテスト用====================
slippage         = 0.001                              # 手数料やスリッページ(0.075%初期値)
wait             = 0                                  # 待機時間
start            = '2019/06/01 09:00'                 # ローソク足取得開始時刻
n                = 30                                 # ローソク足取得リクエスト回数
LOT_MODE         = "adjustable"                       # fixedなら$1000固定、adjustableなら可変ロット

#====================パラメータ====================
chart_min_list   = [ 240 ]                            # テストに使う時間軸(1 3 5 15 30 60 120 240 360 720 "D" "M" "W")
buy_term_list    = [ 20 ]                             # テストに使う上値ブレイクアウトの期間
sell_term_list   = [ 40 ]                             # テストに使う下値ブレイクアウトの期間
judge_price_list = [ {"BUY":"close","SELL":"close"} ] # ブレイクアウト判定に終値を使用


#--------------------補助ツール--------------------------------
#====================APIから価格データ取得(ローソク足の本数指定)====================
def get_price_from_API(chart_min,start,n):
   price = []
   get_start = int(datetime.strptime(start,'%Y/%m/%d %H:%M').timestamp()) # タイムスタンプ変換
   #200*n本のローソク足を取得して、price[]に入れる
   for o in range(n):

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

       #priceに取得したデータを入れる
       price += k["result"]

       #200本x足の長さ分だけタイムスタンプを進める
       if chart_min =="D":
           get_start += 200*60*1440
       else:
           get_start += 200*60*chart_min

   return price

#====================ローソク足データをリスト化する====================
def get_price_amount(chart_min_list):
   price_list = {}                                                       # ローソク足を入れる変数
   for chart_min in chart_min_list:                                      # for文(chart_min_listの数だけ処理を行う)
       print("{0}分足取得中".format([chart_min]))
       price_list[chart_min] = get_price_from_API(chart_min,start,n) # chart_min分足のローソク足取得リクエストをn回行う

   return price_list


#====================時間と高値・安値をログに記録====================
def log_price( data,flag,price ):
   flag["records"]["log"].append("時間: " + datetime.fromtimestamp(data["open_time"]).strftime('%Y/%m/%d %H:%M') + " 始値" + str(data["open"]) + " 高値: " + str(data["high"]) + " 安値: " + str(data["low"]) + " 終値: " + str(data["close"]) + "\n")
   flag["records"]["close_price"].append(float(data["close"]))
   flag["records"]["open_time"].append(datetime.fromtimestamp(data["open_time"]).strftime('%Y/%m/%d %H:%M'))
   flag["records"]["log"].append("200MA:{}\n".format(calculate_SMA( price,200,before=None )))

   return flag


#--------------------資金管理関数---------------------
#====================平均ボラティリティを計算====================
def calculate_volatility( last_data,flag ):

   high_sum = sum(float(i["high"]) for i in last_data[-1 * volatility_term :]) # 設定した足の数の高値の合計を算出
   low_sum  = sum(float(i["low"])  for i in last_data[-1 * volatility_term :]) # 設定した足の数の安値の合計を算出
   volatility = round((high_sum - low_sum) / volatility_term)                  # ボラティリティを算出
   flag["records"]["log"].append("\n現在の{0}期間の平均ボラティリティは{1}$です\n".format( volatility_term, volatility ))

   return volatility


#====================注文ロットを計算====================
def calculate_lot(last_data,data,flag ):
   # 固定ロットでのテスト時
   if LOT_MODE == "fixed":
       flag["records"]["log"].append("固定ロットでテスト中のため、$1000を注文します\n")
       lot = 1000
       volatility = calculate_volatility( last_data,flag )
       stop = stop_range * volatility
       flag["position"]["ATR"] = round( volatility )
       return lot,stop,flag

   balance = flag["records"]["funds"]                                                    # 残高を取得

   # 初回エントリー時
   if flag["add-position"]["count"] == 0:

       volatility = calculate_volatility( last_data,flag )                               # ボラティリティを計算
       stop       = stop_range * volatility                                              # 損切り値幅を計算
       calc_lot   = int(( balance * trade_risk / (stop / float(data["close"]) )))        # 許容リスクから逆算したロット

       flag["add-position"]["unit-size"]  = int( calc_lot / entry_times )                # 1回ごとのポジションサイズ
       flag["add-position"]["unit-range"] = round( volatility * entry_range )            # ポジションを分割する値幅
       flag["add-position"]["stop"]       = stop                                         # 損切り価格
       flag["position"]["ATR"] = round( volatility )                                     # ATRの設定

       flag["records"]["log"].append("現在のアカウント残高は{}$です\n".format( round( balance,2 ) ))
       flag["records"]["log"].append("許容リスクから購入できる枚数は最大{}$までです\n".format( calc_lot ))
       flag["records"]["log"].append("{0}回に分けて{1}$ずつ注文します\n".format( entry_times, flag["add-position"]["unit-size"] ))

   # 2回目以降のエントリー
   else:
       balance = (balance * levarage - flag["position"]["lot"])                           # 証拠金から1回目のロットを引く

   stop     = flag["add-position"]["stop"]                                                # 初回エントリー時の損切り値幅を設定
   able_lot = int( balance * levarage )                                                   # 設定可能な最大ロット
   lot      = min(able_lot,flag["add-position"]["unit-size"])                             # 実際に設定するロットは小さい方

   if able_lot > flag["add-position"]["unit-size"]:
       flag["records"]["log"].append("ロットを{}$にします\n".format(flag["add-position"]["unit-size"]))
   else:
       flag["records"]["log"].append("ロットを{}$にします\n".format(able_lot))

   return lot,stop,flag


#====================増し玉を行う====================
def add_position(last_data,data,flag):
   if flag["position"]["exist"] == False:                                    # ポジションが無かったら実行しない
       return flag

   # 固定ロット(1BTC)でのテスト時は何もしない
   if LOT_MODE == "fixed":
       return flag

   # 最初(1回目)のエントリー価格を記録
   if flag["add-position"]["count"] == 0:                                    # ポジションの追加が0回目の時
       flag["add-position"]["first-entry-price"] = flag["position"]["price"] # 初回エントリー価格
       flag["add-position"]["last-entry-price"]  = flag["position"]["price"] # 前回(初回)のエントリー価格
       flag["add-position"]["count"] += 1                                    # ポジションの追加回数を+1

   while True:

       # 以下の場合は、追加ポジションを取らない
       if flag["add-position"]["count"] >= entry_times:                      # ポジションを追加した回数が設定値(entry_times)以上の場合
           return flag

       # この関数の中で使う変数を用意
       first_entry_price = flag["add-position"]["first-entry-price"]         # 初回エントリー価格
       last_entry_price  = flag["add-position"]["last-entry-price"]          # 前回のエントリー価格
       current_price     = float(data["close"])                              # 現在の価格
       unit_range        = flag["add-position"]["unit-range"]                # ポジションの分割値幅

       should_add_position = False                                           # 増し玉の指示変数を初期化

       if flag["position"]["side"] == "BUY" and (current_price - last_entry_price) > unit_range:    # 買いエントリー時、前回のエントリー価格 - 現在価格が基準レンジより大きければ増し玉の指示を出す
           should_add_position = True
       elif flag["position"]["side"] == "SELL" and (last_entry_price - current_price) > unit_range: # 売りエントリー時、現在価格 - 前回のエントリー価格が基準レンジより大きければ増し玉の指示を出す
           should_add_position = True
       else:
           break

       # 基準レンジ分進んでいれば追加注文を出す
       if should_add_position == True:                                       # 増し玉の指示が出ている場合
           flag["records"]["log"].append("\n前回のエントリー価格{0}$からブレイクアウトの方向に{1}ATR({2}$)以上動きました\n".format( last_entry_price, entry_range, round( unit_range ) ))
           flag["records"]["log"].append("{0}/{1}回目の追加注文を出します\n".format(flag["add-position"]["count"] + 1, entry_times))

           # 注文サイズを計算
           lot,stop,flag = calculate_lot( last_data,data,flag )

           # 追加注文を出す
           if flag["position"]["side"] == "BUY":
               entry_price = first_entry_price + (flag["add-position"]["count"] * unit_range)     # バックテスト用
               #entry_price = round((1 + slippage) * entry_price)                                  # スリッページを考慮

               flag["records"]["log"].append("現在のポジションに追加して、{0}$で{1}$の買い注文を出します\n".format(entry_price,lot))

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

           if flag["position"]["side"] == "SELL":
               entry_price = first_entry_price - (flag["add-position"]["count"] * unit_range)     # バックテスト用
               #entry_price = round((1 - slippage) * entry_price)                                  # スリッページを考慮

               flag["records"]["log"].append("現在のポジションに追加して、{0}$で{1}$の売り注文を出します\n".format(entry_price,lot))

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

           # ポジション全体の情報を更新する
           flag["position"]["stop"]  = stop                                                                                                                        #損切り値幅は初回エントリー時から変えない
           flag["position"]["price"] = int(round(( flag["position"]["price"] * flag["position"]["lot"] + entry_price * lot ) / ( flag["position"]["lot"] + lot ))) #平均ポジションを算出
           flag["position"]["lot"]   = (flag["position"]["lot"] + lot)                                                                                             #合計ロットを算出

           if flag["position"]["side"] == "BUY":
               flag["records"]["log"].append("{0}$の位置にストップを更新します\n".format(flag["position"]["price"] - stop))

           elif flag["position"]["side"] == "SELL":
               flag["records"]["log"].append("{0}$の位置にストップを更新します\n".format(flag["position"]["price"] + stop))

           flag["records"]["log"].append("現在のポジションの取得単価は{}$です\n".format(flag["position"]["price"]))
           flag["records"]["log"].append("現在のポジションサイズは{}$です\n\n".format(flag["position"]["lot"]))

           flag["add-position"]["count"] += 1                                                     #ポジションの追加回数をカウント
           flag["add-position"]["last-entry-price"] = entry_price                                 #前回のエントリー価格に、今回のエントリー価格を上書き

   return flag


#====================トレイリングストップ====================
def trail_stop( data,flag ):

   data["close"] = float(data["close"])

   # ポジションの追加取得(増し玉)が終わるまでは何もしない
   if flag["add-position"]["count"] < entry_times and LOT_MODE != "fixed":
       return flag

   last_stop  = flag["position"]["stop"]             # 前回のストップ幅
   first_stop = flag["position"]["ATR"] * stop_range # 最初のストップ幅

   # 終値がエントリー価格からいくら離れたか計算する
   if flag["position"]["side"] == "BUY" and data["close"] > flag["position"]["price"]:
       moved_range = round(data["close"] - flag["position"]["price"])
   elif flag["position"]["side"] == "SELL" and data["close"] < flag["position"]["price"]:
       moved_range = round(flag["position"]["price"] - data["close"])
   else:
       moved_range = 0

   # 動いたレンジ幅に合わせてストップ位置を更新する
   if moved_range > flag["position"]["ATR"]:
       number = int(np.floor(moved_range / flag["position"]["ATR"]))
       flag["position"]["stop"] = round(first_stop - ( number * flag["position"]["ATR"] * trail_ratio ))

   # 損益0ラインまでしかトレイルしない場合
   if trail_until_breakeven and flag["position"]["stop"] < 0:
       flag["position"]["stop"] = 0

   # ストップがエントリー方向と逆に動いたら更新しない
   if flag["position"]["stop"] > last_stop:
       flag["position"]["stop"] = last_stop

   # ストップが動いた場合のみログ出力
   if flag["position"]["stop"] != last_stop:
       if flag["position"]["side"] == "BUY":
           flag["records"]["log"].append("トレイリングストップの発動:ストップ位置を{}$に動かします\n".format( round(flag["position"]["price"] - flag["position"]["stop"]) ))
       else:
           flag["records"]["log"].append("トレイリングストップの発動:ストップ位置を{}$に動かします\n".format( round(flag["position"]["price"] + flag["position"]["stop"]) ))

   return flag

#--------------------売買ロジック--------------------
#====================ロジック判定====================
def donchian( data,last_data,buy_term,sell_term,judge_price ):

   highest = float(max(i["high"] for i in last_data[(-1*buy_term) :])) # 0~buy_termまでで、最も大きい高値をhighestとする
   lowest  = float(min(i["low"]  for i in last_data[(-1*sell_term):])) # 0~sell_termまでで、最も小さい安値をlowestとする

   if   float(data[ judge_price["BUY"]])  > highest:                   # data["close"]がhighestを上回ったら買いサインを出す
       return {"side":"BUY","price" :highest}

   elif float(data[ judge_price["SELL"]]) < lowest :                   # data["close"]がlowestを下回ったら売りサインを出す
       return {"side":"SELL","price":lowest }

   else:                                                        # 上記以外は売買サインを出さない
       return {"side" : None , "price":0}


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

   signal = donchian( data,last_data,buy_term,sell_term,judge_price )

   if signal["side"] == "BUY":
       lot,stop,flag = calculate_lot( last_data,data,flag )

       flag["records"]["log"].append("過去{0}足の最高値{1}$を、直近の価格が{2}$でブレイクしました\n".format(buy_term,signal["price"],data[judge_price["BUY"]]))

       #/////////////////////////////////////////////////////////////////////////////////////
       # フィルター条件を確認
       if filter( last_data,data,signal ) == False:
           flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
           return flag
       #/////////////////////////////////////////////////////////////////////////////////////

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

       flag["records"]["log"].append(str(data["close"]) + "$で買いの指値注文を出します\n")

       flag["order"]["lot"]   = lot
       flag["order"]["stop"]  = stop
       flag["order"]["exist"] = True
       flag["order"]["side"]  = "BUY"
       flag["order"]["price"] = float(data["close"])
       flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(flag["order"]["price"]) - stop))

   if signal["side"] == "SELL":
       lot,stop,flag = calculate_lot( last_data,data,flag )

       flag["records"]["log"].append("過去{0}足の最安値{1}$を、直近の価格が{2}$でブレイクしました\n".format(sell_term,signal["price"],data[judge_price["SELL"]]))

       #/////////////////////////////////////////////////////////////////////////////////////
       # フィルター条件を確認
       if filter( last_data,data,signal ) == False:
           flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
           return flag
       #/////////////////////////////////////////////////////////////////////////////////////

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

       flag["records"]["log"].append(str(data["close"]) + "$で売りの指値注文を出します\n")

       flag["order"]["lot"]   = lot
       flag["order"]["stop"]  = stop
       flag["order"]["exist"] = True
       flag["order"]["side"]  = "SELL"
       flag["order"]["price"] = float(data["close"])
       flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(flag["order"]["price"]) + stop))

   return flag


#====================成行決済&ドテン注文====================
def close_position( data,last_data,flag,buy_term,sell_term,judge_price,price ):
   if flag["position"]["exist"] == False:                              # ポジションが無ければ何もしない
       return flag

   flag["position"]["count"] += 1                                      # ポジションの保有期間をカウント
   signal = donchian( data,last_data,buy_term,sell_term,judge_price )  # 売買サインを確認

   if flag["position"]["side"] == "BUY" and  signal["side"] == "SELL": # 買いポジションかつ売りサインの場合

           flag["records"]["log"].append("\n過去{0}足の最安値{1}$を、直近の価格が{2}$でブレイクしました\n".format(sell_term,signal["price"],data[judge_price["SELL"]]))
           flag["records"]["log"].append(str(data["close"]) + "$あたりで成行注文を出してポジションを決済します\n")

           # 成行決済注文コードを入れる

           records( flag,data,data["close"] )                          # トレード結果を記録
           flag["position"]["exist"] = False                           # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                               # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                           # 増し玉のカウントをリセット

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

           #/////////////////////////////////////////////////////////////////////////////////////
           # フィルター条件を確認
           if filter( last_data,data,signal ) == False:
               flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
               return flag
           #/////////////////////////////////////////////////////////////////////////////////////

           # 売り指値注文のコードを入れる

           flag["order"]["lot"]   = lot
           flag["order"]["stop"]  = stop
           flag["order"]["exist"] = True
           flag["order"]["side"]  = "SELL"
           flag["order"]["price"] = float(data["close"])
           flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(data["close"]) + flag["order"]["stop"]))


   if flag["position"]["side"] == "SELL" and  signal["side"] == "BUY": # 売りポジション且つ買いサインの場合

           flag["records"]["log"].append("過去{0}足の最高値{1}$を、直近の価格が{2}$でブレイクしました\n".format(buy_term,signal["price"],data[judge_price["BUY"]]))
           flag["records"]["log"].append(str(data["close"]) + "$あたりで成行注文を出してポジションを決済します\n")

           # 成行決済注文コードを入れる

           records( flag,data,data["close"] )                          # トレード結果を記録
           flag["position"]["exist"] = False                           # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                               # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                           # 増し玉のカウントをリセット

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

           #/////////////////////////////////////////////////////////////////////////////////////
           # フィルター条件を確認
           if filter( last_data,data,signal ) == False:
               flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
               return flag
           #/////////////////////////////////////////////////////////////////////////////////////

           # 買い指値注文のコードを入れる

           flag["order"]["lot"]   = lot
           flag["order"]["stop"]  = stop
           flag["order"]["exist"] = True
           flag["order"]["side"]  = "BUY"
           flag["order"]["price"] = float(data["close"])
           flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(data["close"]) - flag["order"]["stop"]))

   return flag


#====================損切確認====================
def stop_position( data,flag,last_data,chart_min ):

   #トレイリングストップ
   if stop_config == "TRAILING":                                         # stop_config == "TRAILING"ならトレイリングストップ実行
       flag = trail_stop( data,flag )

   if flag["position"]["side"] == "BUY":                                 # 買いポジションの時

       stop_price = flag["position"]["price"] - flag["position"]["stop"] # 損切り価格を設定

       if float(data["low"]) < stop_price:
           flag["records"]["log"].append("{0}$の損切ラインに引っかかりました。\n".format( stop_price ))
           flag["records"]["log"].append(str(round(stop_price,2)) + "$あたりで成行注文を出してポジションを決済します\n")

           # 決済の成行注文コードを入れる

           records( flag,data,stop_price,"STOP" )                        # トレード結果を記録
           flag["position"]["exist"] = False                             # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                                 # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                             # 増し玉のカウントをリセット


   if flag["position"]["side"] == "SELL":

       stop_price = flag["position"]["price"] + flag["position"]["stop"]

       if float(data["high"]) > stop_price:
           flag["records"]["log"].append("{0}$の損切ラインに引っかかりました。\n".format( stop_price ))
           flag["records"]["log"].append(str(round(stop_price,2)) + "$あたりで成行注文を出してポジションを決済します\n")

           # 決済の成行注文コードを入れる

           records( flag,data,stop_price,"STOP" )                        # トレード結果を記録
           flag["position"]["exist"] = False                             # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                                 # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                             # 増し玉のカウントをリセット

   return flag


#/////////////////////////////////////////////////////
#--------------------フィルター--------------------
#====================単純移動平均を計算====================
def calculate_SMA( last_data,value,before=None ):
   if before is not None:
       SMA = sum(float(i["close"]) for i in last_data[-1*value + before: before]) / value
   else:
       SMA = sum(float(i["close"]) for i in last_data[-1*value:]) / value
   return round(SMA)


#====================指数移動平均を計算====================
def calculate_EMA( last_data,value,before=None ):
   if before is not None:
       MA = sum(float(i["close"]) for i in last_data[-2*value + before : -1*value + before]) / value
       EMA = (float(last_data[-1*value + before]["close"]) * 2 / (value+1)) + (MA * (value-1) / (value+1))
       for i in range(value-1):
           EMA = (float(last_data[-1*value+before+1 + i]["close"]) * 2 /(value+1)) + (EMA * (value-1) / (value+1))
   else:
       MA = sum(float(i["close"]) for i in last_data[-2*value: -1*value]) / value
       EMA = (float(last_data[-1*value]["close"]) * 2 / (value+1)) + (MA * (value-1) / (value+1))
       for i in range(value-1):
           EMA = (float(last_data[-1*value+1 + i]["close"]) * 2 /(value+1)) + (EMA * (value-1) / (value+1))
   return round(EMA)


#====================エントリーフィルター====================
def filter( last_data,data,signal ):

   data["close"] = float(data["close"])

   if filter_VER == "OFF":
       return True

   if filter_VER == "A":
       if len(last_data) < 200:
           return True
       if data["close"] > float(last_data[-200]["close"]) and signal["side"] == "BUY":
           return True
       if data["close"] < float(last_data[-200]["close"]) and signal["side"] == "SELL":
           return True

   if filter_VER == "B":
       if len(last_data) < 200:
           return True
       if data["close"] > calculate_SMA(last_data,200) and signal["side"] == "BUY":
           return True
       if data["close"] < calculate_SMA(last_data,200) and signal["side"] == "SELL":
           return True

   if filter_VER == "C":
       if len(last_data) < 20:
           return True
       if calculate_SMA(last_data,20) > calculate_SMA(last_data,20,-1) and signal["side"] == "BUY":
           return True
       if calculate_SMA(last_data,20) < calculate_SMA(last_data,20,-1) and signal["side"] == "SELL":
           return True

   if filter_VER == "D":
       if len(last_data) < 700:
           return True
       if calculate_EMA(last_data,350) < calculate_EMA(last_data,25) and signal["side"] == "BUY":
           return True
       if calculate_EMA(last_data,350) > calculate_EMA(last_data,25) and signal["side"] == "SELL":
           return True

   return False
#/////////////////////////////////////////////////////

#--------------------バックテスト関数----------------------------------------------
#====================注文状況確認====================
def 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"]
   flag["position"]["stop"]  = flag["order"]["stop"]
   flag["position"]["lot"]   = flag["order"]["lot"]

   return flag

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

   entry_price = float(flag["position"]["price"])                                                       # エントリー価格
   exit_price  = float(exit_price)                                                                       # クローズ価格
   trade_cost  = flag["position"]["lot"] * slippage                                                     # トレードコスト

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

   # 決済日時,ポジションの保有期間を記録
   flag["records"]["date"].append(datetime.fromtimestamp(data["open_time"]).strftime('%Y/%m/%d %H:%M'))
   flag["records"]["holding-periods"].append(flag["position"]["count"])

   # 損切りにかかった回数をカウント
   if close_type == "STOP":
       flag["records"]["stop-count"].append(1)
   else:
       flag["records"]["stop-count"].append(0)

   # 値幅の計算
   buy_Price_range  = exit_price - entry_price                                                           # 買いポジション時の獲得値幅
   sell_Price_range = entry_price - exit_price                                                          # 売りポジション字の獲得値幅

   # 利益率の計算
   buy_return = buy_Price_range/entry_price                                                             # 買いポジション時の獲得リターン
   sell_return = sell_Price_range/entry_price                                                           # 売りポジション時の獲得リターン

   #損益の確認
   if flag["position"]["side"] == "BUY":                                                                # 買いポジションの時
       flag["records"]["return"].append( buy_return )                                                   # 獲得リターンを記録
       flag["records"]["side"].append( flag["position"]["side"] )                                       # 買いか売りかを記録

       flag["records"]["profit"].append((buy_return-slippage)*flag["position"]["lot"])                  # 獲得利益を記録

       flag["records"]["funds"] += (buy_return-slippage)*flag["position"]["lot"]                        # 証拠金に獲得利益を加算

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

       #損益の確認
   if flag["position"]["side"] == "SELL":                                                               # 売りポジションの時
       flag["records"]["return"].append( sell_return )                                                  # 獲得リターンを記録
       flag["records"]["side"].append( flag["position"]["side"] )                                       # 買いか売りかを記録

       flag["records"]["profit"].append((sell_return-slippage)*flag["position"]["lot"])                  # 獲得利益を記録

       flag["records"]["funds"] += (sell_return-slippage)*flag["position"]["lot"]                        # 証拠金に獲得利益を加算

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

   return flag


#====================損益曲線をプロット====================
def plot(close_price,records,buy_term,sell_term,judge_price,interval):

   plt.subplot(1,1,1)
   plt.plot( records.Date, records.Funds )  # X軸、Y軸の値を指定
   plt.xlabel("Date")                       # X軸のラベル名
   plt.ylabel("Balance")                    # Y軸のラベル名
   plt.xticks(rotation=50)                  # X軸の目盛りを50度回転
   plt.title("buy_term:{0},sell_term:{1},\n".format(buy_term,sell_term)+"judge:{0},Interval:{1}".format(judge_price,interval))

   # plt.subplot(2,1,2)
   # plt.plot( close_price.Open_Time, close_price.Close_Price )  # X軸、Y軸の値を指定
   # plt.xlabel("Open_Time")                       # X軸のラベル名
   # plt.ylabel("Close_Price")                    # Y軸のラベル名
   # plt.xticks(rotation=50)                  # X軸の目盛りを50度回転

   # リターン分布の相対度数表を作る
   # plt.subplot(2,1,2)
   # plt.hist( records.Rate,50,rwidth=0.9)
   # plt.axvline( x=0,linestyle="dashed",label="Return = 0" )
   # plt.axvline( records.Rate.mean(), color="orange", label="AverageReturn" )
   # plt.legend() # 凡例

   plt.show()                               #グラフの表示


#====================ファイルを出力====================
def File_output(df,flag):

   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"])

   #pandasのdfをcsvで出力
   df.to_csv("log/donchian-{0}-records.csv".format(datetime.now().strftime("%Y-%m-%d-%H-%M")))


#====================バックテストの集計====================
def backtest( flag,buy_term,sell_term,judge_price,interval ):

   # 成績を記録したpandas DataFrameを作成
   records = pd.DataFrame({
       "Date"          :  pd.to_datetime(flag["records"]["date"]), # 決済日時
       "Side"          :  flag["records"]["side"],                 # ポジションの側
       "Stop"          :  flag["records"]["stop-count"],           # 損切りを行った回数
       "Rate"          :  flag["records"]["return"],               # 獲得レート
       "Periods"       :  flag["records"]["holding-periods"],      # ポジション保有期間
       "Slippage"      :  flag["records"]["slippage"],             # 手数料等
       "Profit"        :  flag["records"]["profit"],               # 獲得損益
   })

   close_price = pd.DataFrame({
       "Close_Price"   :  flag["records"]["close_price"],
       "Open_Time"     :  pd.to_datetime(flag["records"]["open_time"])
       })

   # 総利益の列を追加
   records["Gross"] = records.Profit.cumsum()                      # その行までのrecords.Profitの総和

   # 資産推移の列を追加する
   records["Funds"] = records.Gross + start_funds                 # 初期資金+records.Gross

   # ドローダウンの列を追加
   records["Drawdown"]     = records.Funds.cummax().subtract( records.Funds )
   records["DrawdownRate"] = records.Drawdown / records.Funds.cummax() * 100

   # 連敗回数をカウントする
   consecutive_defeats = []                                        # 連敗回数を記録する配列
   defeats = 0                                                     # 初期化
   for r in flag["records"]["return"]:                             # リターンがマイナスなら連敗回数を+1
       if r < 0:
           defeats += 1
       else:                                                       # リターンがプラスなら連敗回数をリセット
           consecutive_defeats.append( defeats )
           defeats = 0

   # 買いエントリーと売りエントリーだけをそれぞれ抽出する
   buy_records = records[records.Side.isin(["BUY"])]
   sell_records = records[records.Side.isin(["SELL"])]

   # 月別のデータを集計する
   # records["月別集計"] = pd.to_datetime( records.Date.apply(lambda x: x.strftime('%Y/%m')))
   # grouped = records.groupby("月別集計")

   # month_records = pd.DataFrame({
   #     "Number"   :  grouped.Profit.count(),
   #     "Gross"    :  grouped.Profit.sum(),
   #     "Funds"    :  grouped.Funds.last(),
   #     "Rate"     :  round(grouped.Rate.mean(),2),
   #     "Drawdown" :  grouped.Drawdown.max(),
   #     "Periods"  :  grouped.Periods.mean()
   #     })


   print("\nバックテスト結果")
   print("==============================")
   print("--------買いエントリ成績--------")
   print("トレード回数       :  {}回".format(len(buy_records) ))
   print("勝率            :  {}%".format(round(len(buy_records[buy_records.Profit>0]) / len(buy_records) * 100,1)))
   print("平均リターン       :  {}%".format(round(buy_records.Rate.mean()*100,2)))
   print("総損益          :  {}$".format(round( buy_records.Profit.sum() ,2)))
   print("平均保有期間     :  {}足".format(round(buy_records.Periods.mean(),1) ))
   print("損切りの回数       :  {}回".format( buy_records.Stop.sum() ))

   print("\n--------売りエントリ成績--------")
   print("トレード回数       :  {}回".format( len(sell_records) ))
   print("勝率            :  {}%".format(round(len(sell_records[sell_records.Profit>0]) / len(sell_records) * 100,1)))
   print("平均リターン       :  {}%".format(round(sell_records.Rate.mean()*100,2)))
   print("総損益          :  {}$".format(round( sell_records.Profit.sum() ,2)))
   print("平均保有期間     :  {}足".format(round(sell_records.Periods.mean(),1) ))
   print("損切りの回数       :  {}回".format( sell_records.Stop.sum() ))

   print("\n------------総合成績--------------")
   print("全トレード数       :  {}回".format(len(records) ))
   print("勝率            :  {}%".format(round(len(records[records.Profit>0]) / len(records) * 100,1)))
   print("平均リターン       :  {}%".format(round(records.Rate.mean()*100,2)))

   #///////////////////////////////////////////////
   print("標準偏差           :  {}%".format(round(records.Rate.std()*100,2)))
   print("平均利益率         :  {}%".format(round(records[records.Profit>0].Rate.mean()*100,2) ))
   print("平均損失率         :  {}%".format(round(records[records.Profit<0].Rate.mean()*100,2) ))
   #///////////////////////////////////////////////

   print("平均保有期間     :  {}足".format(round(records.Periods.mean(),1) ))
   print("損切りの回数       :  {}回".format( records.Stop.sum() ))
   print("最大連敗回数       :  {}回".format( max(consecutive_defeats) ))
   print("最大の勝ちトレード  :  {}$".format((round(records.Profit.max(),2))))
   print("最大の負けトレード  :  {}$".format((round(records.Profit.min(),2))))
   print("最大ドローダウン    :  {0}$ / {1}%".format(round(-1 * records.Drawdown.max()), round( records.DrawdownRate.loc[records.Drawdown.idxmax()] )))
   print("利益合計         :  {}$".format((round(records[records.Profit>0].Profit.sum(),2))))
   print("損失合計         :  {}$".format(round(records[records.Profit<0].Profit.sum(),2),))
   print("手数料合計       :  {}$".format(round(-1 * records.Slippage.sum(),1)))
   print("最終損益         :  {}$\n".format((round(records.Profit.sum()-(records.Slippage.sum()) ,2))))
   print("初期資金           :  {}$".format( start_funds ))
   print("最終資金           :  {}$".format( round(records.Funds.iloc[-1] ,2)))
   print("運用成績           :  {}%".format( round(records.Funds.iloc[-1] / start_funds * 100 ,2) ))

   print("-----------------------------------")
   print("各成績指標")
   print("-----------------------------------")
   print("MARレシオ          :  {}".format(round( (records.Funds.iloc[-1] / start_funds -1)*100 / records.DrawdownRate.max(),2 )))
   print("シャープレシオ       :  {}".format( round(records.Rate.mean()/records.Rate.std(),2) ))
   print("プロフィットファクター   :  {}".format( round(records[records.Profit>0].Profit.sum()/abs(records[records.Profit<0].Profit.sum()),2) ))
   print("損益レシオ         :  {}".format(round( records[records.Profit>0].Rate.mean()/abs(records[records.Profit<0].Rate.mean()) ,2)))

   # print("\n--------------月別成績------------")
   # for index , row in monthly_records.iterrows():
   #     print("===================================")
   #     print( "{0}年{1}月".format( index.year, index.month ) )
   #     print("-----------------------------------")
   #     print("トレード数          :  {}回".format( row.Count.astype(int) ))
   #     print("月間損益          :  {}$".format( row.Profit.astype(int) ))
   #     print("平均リターン        :  {}%".format( round(row.Rate*100 ,2)))
   #     print("月間最大ドローダウン  :  {}$".format( -1 * row.Drawdown.astype(int) ))
   #     print("平均保有期間      :  {}足".format( round(row.Periods.astype(float),1) ))

   #///////////////////////////////////////////////
   # 際立った損益を表示
   # n = 10
   # print("-----------------------------------")
   # print("+{}%を超えるトレードの回数  :  {}回".format(n,len(records[records.Rate>(n/100)]) ))
   # print("-----------------------------------")
   # for index,row in records[records.Rate>(n/100)].iterrows():
   #     print( "{0}  |  {1}%  |  {2}".format(row.Date,round(row.Rate*100,1),row.Side ))
   # print("-----------------------------------")
   # print("-{}%を下回るトレードの回数  :  {}回".format(n,len(records[records.Rate< (n/-100)]) ))
   # print("-----------------------------------")
   # for index,row in records[records.Rate < (n/-100)].iterrows():
       # print( "{0}  |  {1}%  |  {2}".format(row.Date,round(row.Rate*100,1),row.Side  ))
   #///////////////////////////////////////////////

   print("==============================")

   # バックテスト結果を配列に記録
   result = {
       "Trade-count"  : len(records),                                                                                     # トレード回数
       "Win-rate"     : round(len(records[records.Profit>0]) / len(records) * 100,1),                                     # 勝率
       "Return-ave"   : round(records.Rate.mean(),2),                                                                     # 平均リターン
       "DD-rate-max"  : -1 * round( records.DrawdownRate.loc[records.Drawdown.idxmax()] ),                                # 最大ドローダウンレート
       "Gross"        : records.Profit.sum()-(records.Slippage.sum()),                                                    # 最終損益
       "PF"           : round( -1 * (records[records.Profit>0].Profit.sum() / records[records.Profit<0].Profit.sum()) ,2) # プロフィットファクター
   }

   plot(close_price,records,buy_term,sell_term,judge_price,interval)                                                                  # グラフを表示

   return result


#====================テスト&集計====================
def aggregate(volatility_term,trail_ratio):
   # chart_min_listのローソク足リストを取得
   price_list = get_price_amount(chart_min_list)

   # テストごとの各パラメーターの組み合わせと結果を記録する配列を準備
   param = {
       "buy_term"     : [],
       "sell_term"    : [],
       "chart_min"    : [],
       "judge_price"  : []
       }

   all_result = {
       "count"        : [],
       "winRate"      : [],
       "returnRate"   : [],
       "Drawdown"     : [],
       "ProfitFactor" : [],
       "Gross"        : []
       }


   # 総当たりのためのfor文の準備
   combinations = [(chart_min, buy_term, sell_term, judge_price)
       for chart_min in chart_min_list
       for buy_term  in buy_term_list
       for sell_term in sell_term_list
       for judge_price in judge_price_list]

   # 総当たり処理
   for chart_min, buy_term, sell_term, judge_price in combinations:
       price = price_list[ chart_min ]
       last_data = []
       need_term = max(buy_term,sell_term,volatility_term)
       i = 0

       # flag変数の初期化
       flag = {
           "order":{
               "exist" : False,
               "side"  : "",
               "price" : 0,
               "count" : 0,
               "ATR"   : 0,
               "lot"   : 0,
               "stop"  : 0
           },
           "position":{
               "exist" : False,
               "side"  : "",
               "price" : 0,
               "count" : 0,
               "ATR"   : 0,
               "lot"   : 0,
               "stop"  : 0
           },
           "records":{
               "date"              :[],
               "return"            :[],
               "side"              :[],
               "lot"               :[],
               "stop-count"        :[],
               "profit"            :[],
               "funds"             :start_funds,
               "holding-periods"   :[],
               "slippage"          :[],
               "log"               :[],
               "close_price"       :[],
               "open_time"         :[]
           },
           "add-position":{
               "count"             :0, # エントリーの回数をカウント
               "first-entry-price" :0, # 最初のエントリー価格
               "last-entry-price"  :0, # 前回のエントリー価格
               "unit-range"        :0, # 買い増しの幅
               "unit-size"         :0, # 1回あたりのポジションサイズ
               "stop"              :0, # 初回エントリーのストップ幅
           }
       }

       # トレイリングの比率に0~1以上の数値を設定できないようにする
       if trail_ratio > 1:
           trail_ratio = 1
       elif trail_ratio < 0:
           trail_ratio = 0

       # price全数でバックテストを行う(ローソク足を6000本取得していたら6000回)
       while i < len(price):

           # ドンチャンの判定に使う期間分の安値・高値データを準備する
           if len(last_data) < need_term:
               last_data.append(price[i])
               time.sleep(wait)
               i += 1
               continue

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

           # バックテスト実施
           if flag["order"]["exist"]:                                                     # 注文がある場合
               flag = check_order( flag )                                                  # 注文状況を確認

           elif flag["position"]["exist"]:                                                # ポジションがある場合
               if stop_config != "OFF":
                   flag = stop_position( data,flag,last_data,chart_min )                       # 損切り条件の確認
               flag = close_position( data,last_data,flag,buy_term,sell_term,judge_price,price ) # 決済条件の確認
               flag = add_position( last_data,data,flag )                                  # 増し玉を行う

           else:                                                                          #それ以外の場合
               flag = entry_signal( last_data,data,flag,buy_term,sell_term,judge_price,price )   #エントリー条件の確認

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


       print("テスト期間 ")
       print("==============================")
       print("開始時点  : " + str(datetime.fromtimestamp(float(price[0]["open_time"]))))
       print("終了時点  : " + str(datetime.fromtimestamp(float(price[-1]["open_time"]))))
       print("時間足    : {0}".format(chart_min))
       print("パラメータ1 : " + str(buy_term)  + "期間 / 買い" )
       print("パラメータ2 : " + str(sell_term) + "期間 / 売り" )
       print("パラメータ3 : " + str(judge_price) + "")
       print(str(len(price)) + "件のローソク足データで検証")
       print("==============================")

       result = backtest( flag,buy_term,sell_term,judge_price,chart_min )                  # バックテスト結果を呼ぶ

       # 今回のループで使ったパラメータの組み合わせを配列に記録する
       param["buy_term"].append( buy_term )
       param["sell_term"].append( sell_term )
       param["chart_min"].append( chart_min )

       if judge_price["BUY"] == "high":
           param["judge_price"].append( "high/low" )
       else:
           param["judge_price"].append( "close" )

       # 今回のループのバックテスト結果を配列に記録する
       all_result["count"].append( result["Trade-count"] )
       all_result["winRate"].append( result["Win-rate"] )
       all_result["returnRate"].append( result["Return-ave" ] )
       all_result["Drawdown"].append( result["DD-rate-max"] )
       all_result["ProfitFactor"].append( result["PF"] )
       all_result["Gross"].append( result["Gross"] )

   return param,all_result,flag

#====================表にまとめて、出力====================
def pandas(volatility_term):
   param,all_result,flag = aggregate(volatility_term,trail_ratio)

   # 全てのパラメータによるバックテスト結果をPandasで1つの表にする
   df = pd.DataFrame({
   "Interval"      : param["chart_min"],
   "Buy_term"      : param["buy_term"],
   "Sell_term"     : param["sell_term"],
   "Judge_price"   : param["judge_price"],

   "Trade-count"   : all_result["count"],
   "Win-Rate"      : all_result["winRate"],
   "Reture-Ave"    : all_result["returnRate"],
   "DrawDownRate"  : all_result["Drawdown"],
   "PF"            : all_result["ProfitFactor"],
   "Gross"         : all_result["Gross"]
   })

   # トレード回数が100に満たない記録は消す
   df.drop( df[ df["Trade-count"] < 100].index, inplace=True )

  #File_output(df,flag)

pandas(volatility_term)

・解説

図1

今回のコードは簡単です!
フィルター条件を計算・設定して、エントリー時にフィルターを適用するだけです。

今回はフィルターに移動平均線を使います。

・calculate_SMA( last_data,value,before=None )

図2

#====================単純移動平均を計算====================
def calculate_SMA( last_data,value,before=None ):
   if before is not None:
       SMA = sum(float(i["close"]) for i in last_data[-1*value + before: before]) / value
   else:
       SMA = sum(float(i["close"]) for i in last_data[-1*value:]) / value
   return round(SMA)

単純移動平均線の計算です。
単純移動平均線は名前の通りシンプルで、例えばSMA20なら20期間分の終値の平均値です。

引数はlast_data・value(計算期間)・beforeで、beforeは指定しなければNONEです。

図3

過去の移動平均の算出

if before is not None:
       SMA = sum(float(i["close"]) for i in last_data[-1*value + before: before]) / value       

※beforeはマイナスの値を入れます!
before-nにすると、n期間前の移動平均を算出します。

図3

最新の移動平均の算出

SMA = sum(float(i["close"]) for i in last_data[-1*value:]) / value

beforeを指定しない or 0 を入れると、最新の移動平均を算出します。
last_data-valueから最後までの値を合計し、valueで割っています。
めっちゃ簡単ですね。

・def calculate_EMA( last_data,value,before=None )

図2

#====================指数移動平均を計算====================
def calculate_EMA( last_data,value,before=None ):
   if before is not None:
       MA = sum(float(i["close"]) for i in last_data[-2*value + before : -1*value + before]) / value
       EMA = (float(last_data[-1*value + before]["close"]) * 2 / (value+1)) + (MA * (value-1) / (value+1))
       for i in range(value-1):
           EMA = (float(last_data[-1*value+before+1 + i]["close"]) * 2 /(value+1)) + (EMA * (value-1) / (value+1))
   else:
       MA = sum(float(i["close"]) for i in last_data[-2*value: -1*value]) / value
       EMA = (float(last_data[-1*value]["close"]) * 2 / (value+1)) + (MA * (value-1) / (value+1))
       for i in range(value-1):
           EMA = (float(last_data[-1*value+1 + i]["close"]) * 2 /(value+1)) + (EMA * (value-1) / (value+1))
   return round(EMA)

指数移動平均の計算です。
指数移動平均はより新しい値を重視して算出するので、単純平均よりも制度が高いと言われています。

引数はSMAと同じ3つです。

図3

EMAって計算式めんどくさいんですよね~

SMAと同じくbeforeに期間を指定すると、指定した期間分だけ前のEMAを返します。
ちなみに、EMAは前回のEMAを使って計算するので、初回のEMAにはSMAを使います。

詳しくはこちら!↓(丸投げ)

・filter( last_data,data,signal )

図2

#====================エントリーフィルター====================
def filter( last_data,data,signal ):

   data["close"] = float(data["close"])

   if filter_VER == "OFF":
       return True

   if filter_VER == "A":
       if len(last_data) < 200:
           return True
       if data["close"] > float(last_data[-200]["close"]) and signal["side"] == "BUY":
           return True
       if data["close"] < float(last_data[-200]["close"]) and signal["side"] == "SELL":
           return True

   if filter_VER == "B":
       if len(last_data) < 200:
           return True
       if data["close"] > calculate_SMA(last_data,200) and signal["side"] == "BUY":
           return True
       if data["close"] < calculate_SMA(last_data,200) and signal["side"] == "SELL":
           return True

   if filter_VER == "C":
       if len(last_data) < 20:
           return True
       if calculate_SMA(last_data,20) > calculate_SMA(last_data,20,-1) and signal["side"] == "BUY":
           return True
       if calculate_SMA(last_data,20) < calculate_SMA(last_data,20,-1) and signal["side"] == "SELL":
           return True

   if filter_VER == "D":
       if len(last_data) < 700:
           return True
       if calculate_EMA(last_data,350) < calculate_EMA(last_data,25) and signal["side"] == "BUY":
           return True
       if calculate_EMA(last_data,350) > calculate_EMA(last_data,25) and signal["side"] == "SELL":
           return True

   return False

4つのフィルター条件を作成しています。

図3

filter_VER == "A"のとき

   if filter_VER == "A":
       if len(last_data) < 200:
           return True
       if data["close"] > float(last_data[-200]["close"]) and signal["side"] == "BUY":
           return True
       if data["close"] < float(last_data[-200]["close"]) and signal["side"] == "SELL":
           return True


買いシグナル且つ今回の終値が200期間前の終値を上回っていたらTrueを返します。(売りシグナルの時は下回っていたらTrue
また、last_dataが200個未満だとエラーが出てしまうので、その時は問答無用でTrueを返します。

図3

filter_VER == "B"のとき

   if filter_VER == "B":
       if len(last_data) < 200:
           return True
       if data["close"] > calculate_SMA(last_data,200) and signal["side"] == "BUY":
           return True
       if data["close"] < calculate_SMA(last_data,200) and signal["side"] == "SELL":
           return True

買いシグナル且つ今回の終値がSMA(200)を上回っていたらTrueを返します。(売りシグナルの時は下回っていたらTrue
フィルターAと同じように、last_dataが200個未満だとエラーが出てしまうので、その時は問答無用でTrueを返します。

図3

filter_VER == "C"のとき

   if filter_VER == "C":
       if len(last_data) < 20:
           return True
       if calculate_SMA(last_data,20) > calculate_SMA(last_data,20,-1) and signal["side"] == "BUY":
           return True
       if calculate_SMA(last_data,20) < calculate_SMA(last_data,20,-1) and signal["side"] == "SELL":
           return True

買いシグナル且つ今回のSMA(20)が前回のSMA(20)を上回っていたらTrueを返します。(売りシグナルの時は下回っていたらTrue
last_dataが20個未満だとエラーが出てしまうので、その時は問答無用でTrueを返します。

図3

filter_VER == "D"のとき

   if filter_VER == "D":
       if len(last_data) < 700:
           return True
       if calculate_EMA(last_data,350) < calculate_EMA(last_data,25) and signal["side"] == "BUY":
           return True
       if calculate_EMA(last_data,350) > calculate_EMA(last_data,25) and signal["side"] == "SELL":
           return True

買いシグナル且つ今回のEMA(25)が今回のEMA(350)を上回っていたらTrueを返します。(売りシグナルの時は下回っていたらTrue
EMAの計算にはvalueの2倍の量のlast_dataを使用するので、last_dataが350*2=700未満の時は問答無用でTrueを返します。

これでフィルターに関する関数はすべてです。
あとは、エントリー時にフィルターを通すため、エントリーするときのコードを修正します。

・entry_signal(last_data,data,flag,buy_term,sell_term,judge_price,price )

図2

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

   signal = donchian( data,last_data,buy_term,sell_term,judge_price )

   if signal["side"] == "BUY":
       lot,stop,flag = calculate_lot( last_data,data,flag )

       flag["records"]["log"].append("過去{0}足の最高値{1}$を、直近の価格が{2}$でブレイクしました\n".format(buy_term,signal["price"],data[judge_price["BUY"]]))

       #/////////////////////////////////////////////////////////////////////////////////////
       # フィルター条件を確認
       if filter( last_data,data,signal ) == False:
           flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
           return flag
       #/////////////////////////////////////////////////////////////////////////////////////

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

       flag["records"]["log"].append(str(data["close"]) + "$で買いの指値注文を出します\n")

       flag["order"]["lot"]   = lot
       flag["order"]["stop"]  = stop
       flag["order"]["exist"] = True
       flag["order"]["side"]  = "BUY"
       flag["order"]["price"] = float(data["close"])
       flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(flag["order"]["price"]) - stop))

   if signal["side"] == "SELL":
       lot,stop,flag = calculate_lot( last_data,data,flag )

       flag["records"]["log"].append("過去{0}足の最安値{1}$を、直近の価格が{2}$でブレイクしました\n".format(sell_term,signal["price"],data[judge_price["SELL"]]))

       #/////////////////////////////////////////////////////////////////////////////////////
       # フィルター条件を確認
       if filter( last_data,data,signal ) == False:
           flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
           return flag
       #/////////////////////////////////////////////////////////////////////////////////////

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

       flag["records"]["log"].append(str(data["close"]) + "$で売りの指値注文を出します\n")

       flag["order"]["lot"]   = lot
       flag["order"]["stop"]  = stop
       flag["order"]["exist"] = True
       flag["order"]["side"]  = "SELL"
       flag["order"]["price"] = float(data["close"])
       flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(flag["order"]["price"]) + stop))

   return flag

エントリーの関数です。
買い注文コードの直前に、フィルターを確認するコードを入れています。
filter( last_data,data,signal ) の返り値が Falseだった場合、次の処理に進まずflagを返すようにしています。

・close_position( data,last_data,flag,buy_term,sell_term,judge_price,price )

図2

#====================成行決済&ドテン注文====================
def close_position( data,last_data,flag,buy_term,sell_term,judge_price,price ):
   if flag["position"]["exist"] == False:                              # ポジションが無ければ何もしない
       return flag

   flag["position"]["count"] += 1                                      # ポジションの保有期間をカウント
   signal = donchian( data,last_data,buy_term,sell_term,judge_price )  # 売買サインを確認

   if flag["position"]["side"] == "BUY" and  signal["side"] == "SELL": # 買いポジションかつ売りサインの場合

           flag["records"]["log"].append("\n過去{0}足の最安値{1}$を、直近の価格が{2}$でブレイクしました\n".format(sell_term,signal["price"],data[judge_price["SELL"]]))
           flag["records"]["log"].append(str(data["close"]) + "$あたりで成行注文を出してポジションを決済します\n")

           # 成行決済注文コードを入れる

           records( flag,data,data["close"] )                          # トレード結果を記録
           flag["position"]["exist"] = False                           # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                               # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                           # 増し玉のカウントをリセット

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

           #/////////////////////////////////////////////////////////////////////////////////////
           # フィルター条件を確認
           if filter( last_data,data,signal ) == False:
               flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
               return flag
           #/////////////////////////////////////////////////////////////////////////////////////

           # 売り指値注文のコードを入れる

           flag["order"]["lot"]   = lot
           flag["order"]["stop"]  = stop
           flag["order"]["exist"] = True
           flag["order"]["side"]  = "SELL"
           flag["order"]["price"] = float(data["close"])
           flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(data["close"]) + flag["order"]["stop"]))


   if flag["position"]["side"] == "SELL" and  signal["side"] == "BUY": # 売りポジション且つ買いサインの場合

           flag["records"]["log"].append("過去{0}足の最高値{1}$を、直近の価格が{2}$でブレイクしました\n".format(buy_term,signal["price"],data[judge_price["BUY"]]))
           flag["records"]["log"].append(str(data["close"]) + "$あたりで成行注文を出してポジションを決済します\n")

           # 成行決済注文コードを入れる

           records( flag,data,data["close"] )                          # トレード結果を記録
           flag["position"]["exist"] = False                           # ポジションの所持情報をリセット
           flag["position"]["count"] = 0                               # ポジションの所持期間をリセット
           flag["add-position"]["count"] = 0                           # 増し玉のカウントをリセット

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

           #/////////////////////////////////////////////////////////////////////////////////////
           # フィルター条件を確認
           if filter( last_data,data,signal ) == False:
               flag["records"]["log"].append("フィルターのエントリー条件を満たさなかったため、エントリーしません\n")
               return flag
           #/////////////////////////////////////////////////////////////////////////////////////

           # 買い指値注文のコードを入れる

           flag["order"]["lot"]   = lot
           flag["order"]["stop"]  = stop
           flag["order"]["exist"] = True
           flag["order"]["side"]  = "BUY"
           flag["order"]["price"] = float(data["close"])
           flag["records"]["log"].append("{0}$にストップを入れます\n\n".format(float(data["close"]) - flag["order"]["stop"]))

   return flag

決済&ドテン注文の関数です。
エントリー関数と同じように、買い注文コードの直前にフィルターを確認しています。

・設定値

図2

#====================bot設定====================
stop_range       = 2                                  # ATRの何倍を損切りラインにするか
volatility_term  = 28                                 # ATRを算出するための足の数
trade_risk       = 0.05                                # 1トレードあたり口座の何%まで損失を許容するか
levarage         = 3                                  # レバレッジ倍率の設定
start_funds      = 1000                               # シミュレーション時の初期資金
entry_times      = 5                                  # ポジションの分割数
entry_range      = 0.4                                # 分割ポジションの値幅
stop_config      = "TRAILING"                         # OFF / ON / TRAILING
trail_ratio      = 0.5                                # 価格が1レンジ動くたびに何レンジ損切り位置をトレイルするか
trail_until_breakeven = True                          # 損益ゼロの位置までしかトレイルしない

#//////////////////////////////////
filter_VER = "D"  # OFFでフィルター無効
#//////////////////////////////////

設定の部分で、設定したいフィルターを選んでおきます。
※"OFF"にするとフィルター無効です。

以上!

◆実行

図1

フィルターの実装ができたので、フィルター無し~フィルターDまでの5パターンでバックテスト比較をしてみましょう。

パラメータ等↓

#--------------------設定値------------------------
#====================bot設定====================
stop_range       = 2                                  # ATRの何倍を損切りラインにするか
volatility_term  = 28                                 # ATRを算出するための足の数
trade_risk       = 0.05                                # 1トレードあたり口座の何%まで損失を許容するか
levarage         = 3                                  # レバレッジ倍率の設定
start_funds      = 1000                               # シミュレーション時の初期資金
entry_times      = 5                                  # ポジションの分割数
entry_range      = 0.4                                # 分割ポジションの値幅
stop_config      = "TRAILING"                         # OFF / ON / TRAILING
trail_ratio      = 0.5                                # 価格が1レンジ動くたびに何レンジ損切り位置をトレイルするか
trail_until_breakeven = True                          # 損益ゼロの位置までしかトレイルしない

#//////////////////////////////////
filter_VER = "OFF"  # OFFでフィルター無効
#//////////////////////////////////

#====================バックテスト用====================
slippage         = 0.001                              # 手数料やスリッページ(0.075%初期値)
wait             = 0                                  # 待機時間
start            = '2019/06/01 09:00'                 # ローソク足取得開始時刻
n                = 30                                 # ローソク足取得リクエスト回数
LOT_MODE         = "adjustable"                       # fixedなら$1000固定、adjustableなら可変ロット

#====================パラメータ====================
chart_min_list   = [ 240 ]                            # テストに使う時間軸(1 3 5 15 30 60 120 240 360 720 "D" "M" "W")
buy_term_list    = [ 20 ]                             # テストに使う上値ブレイクアウトの期間
sell_term_list   = [ 40 ]                             # テストに使う下値ブレイクアウトの期間
judge_price_list = [ {"BUY":"close","SELL":"close"} ] # ブレイクアウト判定に終値を使用

・フィルター無し

図2

画像24

バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数       :  49回
勝率            :  16.3%
平均リターン       :  1.92%
総損益          :  10173.01$
平均保有期間     :  44.3足
損切りの回数       :  40回
--------売りエントリ成績--------
トレード回数       :  30回
勝率            :  13.3%
平均リターン       :  -1.26%
総損益          :  -1114.62$
平均保有期間     :  18.5足
損切りの回数       :  25回
------------総合成績--------------
全トレード数       :  79回
勝率            :  15.2%
平均リターン       :  0.71%
標準偏差           :  12.92%
平均利益率         :  25.26%
平均損失率         :  -3.68%
平均保有期間     :  34.5足
損切りの回数       :  65回
最大連敗回数       :  6回
最大の勝ちトレード  :  4722.41$
最大の負けトレード  :  -478.37$
最大ドローダウン    :  -2004$ / 17%
利益合計         :  13837.11$
損失合計         :  -4778.72$
手数料合計       :  -250.1$
最終損益         :  8808.27$
初期資金           :  1000$
最終資金           :  10058.39$
運用成績           :  1005.84%
-----------------------------------
各成績指標
-----------------------------------
MARレシオ          :  31.82
シャープレシオ       :  0.06
プロフィットファクター   :  2.9
損益レシオ         :  6.86
==============================

・フィルターA

図2

画像25

バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数       :  38回
勝率            :  18.4%
平均リターン       :  2.69%
総損益          :  8181.92$
平均保有期間     :  43.6足
損切りの回数       :  31回

--------売りエントリ成績--------
トレード回数       :  18回
勝率            :  22.2%
平均リターン       :  0.1%
総損益          :  -265.95$
平均保有期間     :  21.1足
損切りの回数       :  13回

------------総合成績--------------
全トレード数       :  56回
勝率            :  19.6%
平均リターン       :  1.86%
標準偏差           :  13.49%
平均利益率         :  22.18%
平均損失率         :  -3.11%
平均保有期間     :  36.4足
損切りの回数       :  44回
最大連敗回数       :  5回
最大の勝ちトレード  :  3538.13$
最大の負けトレード  :  -372.69$
最大ドローダウン    :  -1562$ / 15%
利益合計         :  10543.33$
損失合計         :  -2627.36$
手数料合計       :  -170.3$
最終損益         :  7745.69$

初期資金           :  1000$
最終資金           :  8915.97$
運用成績           :  891.6%
-----------------------------------
各成績指標
-----------------------------------
MARレシオ          :  44.07
シャープレシオ       :  0.14
プロフィットファクター   :  4.01
損益レシオ         :  7.13
==============================

・フィルターB

図2

画像26

バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数       :  37回
勝率            :  18.9%
平均リターン       :  3.19%
総損益          :  10805.6$
平均保有期間     :  46.7足
損切りの回数       :  30回

--------売りエントリ成績--------
トレード回数       :  21回
勝率            :  19.0%
平均リターン       :  0.69%
総損益          :  6.59$
平均保有期間     :  20.4足
損切りの回数       :  16回

------------総合成績--------------
全トレード数       :  58回
勝率            :  19.0%
平均リターン       :  2.28%
標準偏差           :  13.81%
平均利益率         :  24.53%
平均損失率         :  -2.92%
平均保有期間     :  37.2足
損切りの回数       :  46回
最大連敗回数       :  5回
最大の勝ちトレード  :  4590.17$
最大の負けトレード  :  -488.84$
最大ドローダウン    :  -1930$ / 14%
利益合計         :  13960.16$
損失合計         :  -3147.97$
手数料合計       :  -223.8$
最終損益         :  10588.39$

初期資金           :  1000$
最終資金           :  11812.19$
運用成績           :  1181.22%
-----------------------------------
各成績指標
-----------------------------------
MARレシオ          :  59.51
シャープレシオ       :  0.17
プロフィットファクター   :  4.43
損益レシオ         :  8.39
==============================

・フィルターC

図2

画像27

バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数       :  41回
勝率            :  17.1%
平均リターン       :  1.3%
総損益          :  5539.1$
平均保有期間     :  44.6足
損切りの回数       :  32回

--------売りエントリ成績--------
トレード回数       :  19回
勝率            :  21.1%
平均リターン       :  -0.48%
総損益          :  -339.43$
平均保有期間     :  23.6足
損切りの回数       :  14回

------------総合成績--------------
全トレード数       :  60回
勝率            :  18.3%
平均リターン       :  0.74%
標準偏差           :  13.82%
平均利益率         :  22.44%
平均損失率         :  -4.14%
平均保有期間     :  37.9足
損切りの回数       :  46回
最大連敗回数       :  5回
最大の勝ちトレード  :  4352.14$
最大の負けトレード  :  -291.87$
最大ドローダウン    :  -1553$ / 23%
利益合計         :  8701.39$
損失合計         :  -3501.73$
手数料合計       :  -191.1$
最終損益         :  5008.56$

初期資金           :  1000$
最終資金           :  6199.66$
運用成績           :  619.97%
-----------------------------------
各成績指標
-----------------------------------
MARレシオ          :  22.78
シャープレシオ       :  0.05
プロフィットファクター   :  2.48
損益レシオ         :  5.43
==============================

・フィルターD

図2

画像28

バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数       :  40回
勝率            :  15.0%
平均リターン       :  2.05%
総損益          :  7798.32$
平均保有期間     :  42.5足
損切りの回数       :  34回

--------売りエントリ成績--------
トレード回数       :  13回
勝率            :  30.8%
平均リターン       :  2.4%
総損益          :  266.04$
平均保有期間     :  22.2足
損切りの回数       :  9回

------------総合成績--------------
全トレード数       :  53回
勝率            :  18.9%
平均リターン       :  2.14%
標準偏差           :  13.91%
平均利益率         :  24.91%
平均損失率         :  -3.16%
平均保有期間     :  37.5足
損切りの回数       :  43回
最大連敗回数       :  6回
最大の勝ちトレード  :  3667.43$
最大の負けトレード  :  -369.16$
最大ドローダウン    :  -1305$ / 13%
利益合計         :  10663.77$
損失合計         :  -2599.41$
手数料合計       :  -153.9$
最終損益         :  7910.47$

初期資金           :  1000$
最終資金           :  9064.36$
運用成績           :  906.44%
-----------------------------------
各成績指標
-----------------------------------
MARレシオ          :  58.15
シャープレシオ       :  0.15
プロフィットファクター   :  4.1
損益レシオ         :  7.88
==============================

・比較結果

図2

最もトレードをフィルタリングしているのはDのフィルターです。
損益レシオが最も高かったのはBのフィルターですね。
最大DDを最も小さくできたのはDのフィルターです。

今回はフィルターの実装が目的であって、このロジックに対してどんなフィルターが有効かではないので、これ以上深堀はしませんが、
いい感じのフィルターを見つけられたら僕に教えてください。
絶対ですよ!!!!!

今回はここまで!


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