仮想通貨bot 勉強記録㉙
~フィルターで無駄なトレードを減らす~
今この記事を書いてる時刻が 5/4 10:00くらい なんですが、ついにbotを動かし始めました!!!!!
noteの更新もしようと思ってたけど、せっかくGWでしかも自粛要請が出てるので、これを機に色々進めました。
この勉強記録はbotを動かすところまで書こうと思ってるので、メモついでに、見てくれている人もbotを動かせるようにできたらいいなと思っておりまする。('ω')
◆前回までのあらすじ
トレイリングストップを実装して、利益増/損失減を効果を体感しました。
◆今回やること
・フィルターでトレンドに則ったトレードに絞る
相場には上げ相場、下げ相場、レンジ相場の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)
・解説
今回のコードは簡単です!
フィルター条件を計算・設定して、エントリー時にフィルターを適用するだけです。
今回はフィルターに移動平均線を使います。
・calculate_SMA( last_data,value,before=None )
#====================単純移動平均を計算====================
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です。
過去の移動平均の算出
if before is not None:
SMA = sum(float(i["close"]) for i in last_data[-1*value + before: before]) / value
※beforeはマイナスの値を入れます!
beforeを-nにすると、n期間前の移動平均を算出します。
最新の移動平均の算出
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 )
#====================指数移動平均を計算====================
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つです。
EMAって計算式めんどくさいんですよね~
SMAと同じくbeforeに期間を指定すると、指定した期間分だけ前のEMAを返します。
ちなみに、EMAは前回のEMAを使って計算するので、初回のEMAにはSMAを使います。
詳しくはこちら!↓(丸投げ)
・filter( last_data,data,signal )
#====================エントリーフィルター====================
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つのフィルター条件を作成しています。
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を返します。
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を返します。
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を返します。
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 )
#====================買い・売り注文====================
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 )
#====================成行決済&ドテン注文====================
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
決済&ドテン注文の関数です。
エントリー関数と同じように、買い注文コードの直前にフィルターを確認しています。
・設定値
#====================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"にするとフィルター無効です。
以上!
◆実行
フィルターの実装ができたので、フィルター無し~フィルター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"} ] # ブレイクアウト判定に終値を使用
・フィルター無し
バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数 : 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
バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数 : 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
バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数 : 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
バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数 : 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
バックテスト結果
==============================
--------買いエントリ成績--------
トレード回数 : 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
==============================
・比較結果
最もトレードをフィルタリングしているのはDのフィルターです。
損益レシオが最も高かったのはBのフィルターですね。
最大DDを最も小さくできたのはDのフィルターです。
今回はフィルターの実装が目的であって、このロジックに対してどんなフィルターが有効かではないので、これ以上深堀はしませんが、
いい感じのフィルターを見つけられたら僕に教えてください。
絶対ですよ!!!!!
今回はここまで!