どんな記事
このバックテストでは、次のことがわかります。
- サイジングの有効性 データによる裏付け
- 基準にするATR は加工したほうが良いのか
- 単利の資金管理 を含めるとどうなるのか
今回のバックテストを行うにあたって、これまでのバックテストに単利の資金管理を含める開発を行いました。サイジングの検証は、資金管理を含めないと意味をなさない からです。
この辺りから 〝チャートを眺めて行うバックテストではできない領域〟 に入ってくると思います。以前まで行っていた検証に比べるとまだまだ機能が劣りますが(鋭意開発中)、何より計算が速い。機能をフルに盛り込んでも以前の100倍くらいの速さで行うことができそうです。楽しみ。
2018年8月19日 修正
値幅による損益の統計の記述を一部修正しました。
バックテストに使用したツールと価格データ
ここは過去に書いた記事とまったく一緒なので引用します。
使用したツール
Python(Google Colaboratory)
Pythonを手軽に試したいなら、Google Colaboratoryがめちゃくちゃオススメです。Google Driveでドキュメントやスプレッドシートを作る感覚でPythonを試すことができます。今回のような、ちゃちゃっと検証したいときに非常に便利です。
使用した価格データ
1分毎の価格データを含むcsvファイル
日経225
MT4のhstファイル
USDJPY、EURJPY、GBPJPY、AUDJPY、EURUSD、GBPUSD、AUDUSD
日経225は独自に保有しているもので、hstファイルはFXDDさんのものを使用しています。
テストする手法
今回も以前のロジックを一部流用しています。
今回のバックテストの目的
今回のバックテストは以下を目的として行った
- 資金管理を含めたバックテストの開発
- ATRの数値には素直に従うべきか(前回の宿題)
- 資金管理(サイジング)が成績に及ぼす影響の確認
開発にともなう変更点
今回の開発にともなって生じたバックテストの変更点
- 資金管理を含めたバックテストの導入
- 検証の期間の変更
これまでの検証期間は、元データが同一であるため「短期の手法の検証期間 > 長期の手法の検証期間」であったが、「長期の手法の検証期間」にあわせることとした - ティックごとのオペレーションの調整
先々の開発を見据えて1ティックごとのオペレーションを調整。それにともない売買回数や成績に若干の差異が生じたが、結果の本質には影響がないと考える(改めて入念な確認をしたため、今回のほうが正しい)
資金管理(サイジング)
今回のバックテストから導入する資金管理(サイジング)です。単利で、すべてのパターンで同一としています。
- 元金:1000万円
- 1エントリーのサイズ:1日のリスクが元金の1%(10万円)
となるように
で1ティックごとに算出しています。
これだけじゃわからない方は以前の記事をお読みください。
エントリー
今回は、次の2パターンのエントリーでバックテストを行いました。
ENTRY_Cycle63_FILTER_none
通常のパターン
ENTRY_Cycle63_FILTER_UpperCycle63
上位足のフィルター有りのパターン
「ENTRY」は4時間足のステージで、「FILTER」は日足相当のステージです。
日足のトレンドを、4時間足のエントリーのフィルターとする
日足のトレンドを確認すると言っても毎回チャートを切り替えるのは手間なので、次の考え方を用います。以下は4時間足であっても、120本の移動平均線は20日相当、240本は40日相当になるということです。
- 4時間 ✕ 6 = 1日(24時間)
- 4時間 ✕ 120 = 20日
- 4時間 ✕ 240 = 40日
また、移動平均のもみ合い時の「だまし」を軽減するために、小次郎講師による移動平均線大循環分析の「ステージ」の考えを用います。
4時間足のトレンド
EMA5とEMA20、EMA40の関係でステージを判断する日足のトレンド
4時間足のEMA5とEMA120、EMA240の関係でステージを判断する上昇トレンド
ステージ6:早仕掛け、ステージ1:本仕掛け下降トレンド
ステージ3:早仕掛け、ステージ4:本仕掛け
決済1
決済1も以前と同じものです。
決済1は、同じく小次郎講師による移動平均線大循環分析の「ステージ」の考えに基づくものです。主に、「利益確定」や、「トレンドがなくなったことを確認した損切り」の決済です。
今回のバックテストでは、ステージによる決済は以下で統一しています。
EXIT1_CycleStage36
通常のパターン(エントリーが遅くなりがち)
買いエントリー(ステージ6か1)はステージ3で決済
売りエントリー(ステージ3か4)はステージ6で決済
決済2
決済2は前回と同じものをつかいます。
決済2(EXIT2)のパターン
none SO_Entry_3_UM SO_Close_3_UM SO_Entry_3_20 SO_Close_3_20 SO_Entry_2_UM SO_Close_2_UM SO_Entry_2_20 SO_Close_2_20
「none」とは
「none」は、ロスカットもトレイリングストップも設定しないパターンです。「ロスカットは必要か」を確認します。
「SO_Entry_N_」「SO_Close_N_」の意味
- 「固定ロスカットとトレイリングストップ」どちらが良いのか
- 「固定ロスカットの有効な幅」
- 「トレイリングストップの有効な幅」
を、確認します。
SO_Entry_N_
エントリーの価格から「N ✕ ATR」マイナス方向にロスカットを設置(固定)
例)SO_Entry_2_:エントリーから2-ATRマイナス方向SO_Close_N_
「N ✕ ATR」マイナス方向にロスカットを設置し、終値がプラス方向に推移するごとに追随させる
例)SO_Close_3_:3-ATRマイナス方向に設置し、プラス方向に追随
「_20」「_UM」とは
この2つのパターンで「ATRの数値には素直に従うべきか」を確認します。
_20(ATR20)
ロスカットやトレイリングストップを「N ✕ 20日ATR」で算出する。_UM(Upper Middle of ATR20)
ロスカットやトレイリングストップの算出に使用するATRを、過去1800本さかのぼった20日ATRの最高値と最安値の中間以上の値とし(UM-ATR)、「N ✕ UM-ATR」で算出する。言葉で書くとややこしいので、次のグラフをご覧いただくのが良いと思います。
上がATR20、下がUM-ATRです。UM-ATRは、「ATR20 > 過去1800本最高値と最安値の中間」のときはATR20を、「ATR20 < 中間の値」のときは中間の値を使います。「なぜ、こんなことをするのか」については、次回の記事で解説したいと思います。
テストするパターン
ここも前回と一緒です。
Entry Filter Exit1 Exit2 1 Cycle63 none Cycle36 none 2 Cycle63 none Cycle36 SO_Entry_3_UM 3 Cycle63 none Cycle36 SO_Entry_3_20 4 Cycle63 none Cycle36 SO_Entry_2_UM 5 Cycle63 none Cycle36 SO_Entry_2_20 6 Cycle63 none Cycle36 SO_Close_3_UM 7 Cycle63 none Cycle36 SO_Close_3_20 8 Cycle63 none Cycle36 SO_Close_2_UM 9 Cycle63 none Cycle36 SO_Close_2_20 10 Cycle63 UpperCycle63 Cycle36 none 11 Cycle63 UpperCycle63 Cycle36 SO_Entry_3_UM 12 Cycle63 UpperCycle63 Cycle36 SO_Entry_3_20 13 Cycle63 UpperCycle63 Cycle36 SO_Entry_2_UM 14 Cycle63 UpperCycle63 Cycle36 SO_Entry_2_20 15 Cycle63 UpperCycle63 Cycle36 SO_Close_3_UM 16 Cycle63 UpperCycle63 Cycle36 SO_Close_3_20 17 Cycle63 UpperCycle63 Cycle36 SO_Close_2_UM 18 Cycle63 UpperCycle63 Cycle36 SO_Close_2_20
バックテストの実施と考察
バックテストの関数
ここが一番重要だったりするのですが、ものすごく長くなってしまうので割愛します。こちらのサイトのコードを参考に作成しました((´・ω・`;)ヒィィッ すいません「バックテストを試してみました」)
参考として、ストラテジー部分を掲載します。
class Strategy_CycleEMA_Base(StrategyBase) :
def __init__(self ,symbol ,np_arr_dic ,df) :
StrategyBase.__init__(self ,symbol ,np_arr_dic ,df)
self.principal = 10000000
self.unit_pct = 0.01
self.ema5 = ta.EMA(self.c ,5)
self.ema20 = ta.EMA(self.c ,20)
self.ema40 = ta.EMA(self.c ,40)
self.ema120 = ta.EMA(self.c ,120)
self.ema240 = ta.EMA(self.c ,240)
def calc_stage(s ,m ,l) :
return np.array(list(map(lambda s ,m ,l :
1 if s > m and m > l else \
2 if m > s and s > l else \
3 if m > l and l > s else \
4 if l > m and m > s else \
5 if l > s and s > m else \
6 if s > l and l > m else \
np.nan ,s ,m ,l)))
self.stage = calc_stage(self.ema5 ,self.ema20 ,self.ema40)
self.upper_stage = calc_stage(self.ema5 ,self.ema120 ,self.ema240)
self.atrL ,self.atrH = ta.MINMAX(self.atr20 ,1800)
self.atrM = (self.atrH + self.atrL) / 2
self.atrUM = np.array(list(map(lambda a ,b :
a if a > b else \
b if a < b else \
a if np.isnan(b)==True and np.isnan(a)==False else \
b if np.isnan(a)==True and np.isnan(b)==False else \
np.nan
,self.atr20 ,self.atrM)))
self.is_nan = np.isnan(self.ema240)
def management(self ,i ,Case=None) :
first = self.first_time
not_nan = not Case == 'is_nan'
Lot = Dict_Symbol[self.symbol]['lot']
Currency = 1 if Dict_Symbol[self.symbol]['currency']=="" else self.np_arr_dic[self.symbol]['Close'][i-1]
LS = self.l_s
b4asset = np.nan if first else self.asset[i-1]
b4mgmt = np.nan if first else self.mgmt[i-1]
ATR = np.nan if first else self.atr[i-1]
ATR_N = self.atrN
Entry = self.entry_price
Close = self.c[i-1]
def mgmt_calc_unit() :
if first : self.mgmt[i]['Unit'] = np.nan
elif Case=='is_nan' : self.mgmt[i]['Unit'] = b4mgmt['Unit']
else :
UnitValue = b4asset['UnitValue'] if Case=='1st' else self.asset[i]['UnitValue']
Unit = round( UnitValue / ( ATR * Lot * Currency) )
self.mgmt[i]['Unit'] = Unit if not_nan else b4mgmt['Unit']
def mgmt_append() :
if not_nan and not first :
Amount = b4mgmt["Amount"]
self.count_nums = np.nan if Amount==0 else self.count_nums + 1
b4_SO_Close = np.nan if Amount==0 else b4mgmt["SO_Close"]
b4_SO_Entry = np.nan if Amount==0 else b4mgmt["SO_Entry"]
pre_SO_Close = np.nan if Amount==0 else Close - ATR * ATR_N * LS
pre_SO_Entry = np.nan if Amount==0 else Entry - ATR * ATR_N * LS
SO_Close = np.nan if Amount==0 else pre_SO_Close if (LS==1 and pre_SO_Close > b4_SO_Close) or (LS==-1 and pre_SO_Close < b4_SO_Close) else b4_SO_Close
SO_Entry = np.nan if Amount==0 else pre_SO_Entry if (LS==1 and pre_SO_Entry > b4_SO_Entry) or (LS==-1 and pre_SO_Entry < b4_SO_Entry) else b4_SO_Entry
self.mgmt[i]['Time'] = self.df.index[i]
self.mgmt[i]['Symol'] = self.symbol
self.mgmt[i]['b4Open'] = np.nan if first else self.o[i-1] if not_nan else b4mgmt['b4Close']
self.mgmt[i]['b4High'] = np.nan if first else self.h[i-1] if not_nan else b4mgmt['b4Close']
self.mgmt[i]['b4Low'] = np.nan if first else self.l[i-1] if not_nan else b4mgmt['b4Close']
self.mgmt[i]['b4Close'] = np.nan if first else self.c[i-1] if not_nan else b4mgmt['b4Close']
self.mgmt[i]['b4ATR'] = np.nan if first else self.atr[i-1] if not_nan else b4mgmt['b4ATR']
self.mgmt[i]['b4EMA5'] = np.nan if first else self.ema5[i-1] if not_nan else b4mgmt['b4EMA5']
self.mgmt[i]['b4EMA20'] = np.nan if first else self.ema20[i-1] if not_nan else b4mgmt['b4EMA20']
self.mgmt[i]['b4EMA40'] = np.nan if first else self.ema40[i-1] if not_nan else b4mgmt['b4EMA40']
self.mgmt[i]['b4EMA120'] = np.nan if first else self.ema120[i-1] if not_nan else b4mgmt['b4EMA120']
self.mgmt[i]['b4EMA240'] = np.nan if first else self.ema240[i-1] if not_nan else b4mgmt['b4EMA240']
self.mgmt[i]['b4Stage'] = np.nan if first else self.stage[i-1] if not_nan else b4mgmt['b4Stage']
self.mgmt[i]['b4UpperStage'] = np.nan if first else self.upper_stage[i-1] if not_nan else b4mgmt['b4UpperStage']
self.mgmt[i]['Amount'] = 0 if first else Amount if not_nan else b4mgmt['Amount']
self.mgmt[i]['CountNums'] = self.count_nums
self.mgmt[i]['EntrySig'] = 0
self.mgmt[i]['ExitSig'] = 0
self.mgmt[i]['L/S'] = LS
self.mgmt[i]['Lot'] = Lot
self.mgmt[i]['EntryPrice'] = Entry
self.mgmt[i]['ExitPrice'] = np.nan
self.mgmt[i]['b4Currency'] = Currency
self.mgmt[i]['SO'] = self.sosig
self.mgmt[i]['SO_Close'] = np.nan if first else SO_Close if not_nan else b4mgmt['SO_Close']
self.mgmt[i]['SO_Entry'] = np.nan if first else SO_Entry if not_nan else b4mgmt['SO_Entry']
self.mgmt[i]['MTM_'] = 0
self.mgmt[i]['MTM'] = 0
self.mgmt[i]['PL_'] = 0
self.mgmt[i]['PL'] = 0
def asset_count_n() :
pass
def asset_append() :
if not_nan and not first :
Total_PL = self.mgmt[i]['PL']
Total_MTM = self.mgmt[i]['MTM']
Cash = b4asset['Cash'] + Total_PL
Net = Cash + Total_MTM
MAX_Cash = Cash if Cash > b4asset['MAX_Cash'] else b4asset['MAX_Cash']
MAX_Net = Net if Net > b4asset['MAX_Net'] else b4asset['MAX_Net']
DD_Cash = Cash - MAX_Cash
DD_Net = Net - MAX_Net
DD_pct_Cash = DD_Cash / MAX_Cash
DD_pct_Net = DD_Net / MAX_Net
UnitValue = self.principal * self.unit_pct
self.asset[i]['Time'] = self.df.index[i]
self.asset[i]['Total_PL'] = 0 if first else Total_PL if not_nan else b4asset['Total_PL']
self.asset[i]['Total_MTM'] = 0 if first else Total_MTM if not_nan else b4asset['Total_MTM']
self.asset[i]['Cash'] = self.principal if first else Cash if not_nan else b4asset['Cash']
self.asset[i]['Net'] = self.principal if first else Net if not_nan else b4asset['Net']
self.asset[i]['MAX_Cash'] = self.principal if first else MAX_Cash if not_nan else b4asset['MAX_Cash']
self.asset[i]['MAX_Net'] = self.principal if first else MAX_Net if not_nan else b4asset['MAX_Net']
self.asset[i]['DD_Cash'] = 0 if first else DD_Cash if not_nan else b4asset['DD_Cash']
self.asset[i]['DD_Net'] = 0 if first else DD_Net if not_nan else b4asset['DD_Net']
self.asset[i]['DD_pct_Cash'] = 0 if first else DD_pct_Cash if not_nan else b4asset['DD_pct_Cash']
self.asset[i]['DD_pct_Net'] = 0 if first else DD_pct_Net if not_nan else b4asset['DD_pct_Net']
self.asset[i]['UnitValue'] = self.principal * self.unit_pct if first else UnitValue if not_nan else b4asset['UnitValue']
if Case=='is_nan' or first:
mgmt_append()
mgmt_calc_unit()
asset_append()
asset_count_n()
if first and not self.is_nan[i+1] : self.first_time = False
elif Case=='1st' :
mgmt_append()
asset_count_n()
elif Case=='2nd' :
self.mark_to_market(i)
asset_append()
mgmt_calc_unit()
asset_count_n()
self.count_correl_risk(i)
def entry_sig(self ,i) :
if self.FILTER=='none' :
buy_filter = sell_filter = True
elif self.FILTER=='UpperCycle63' :
buy_filter = self.mgmt[i]['b4UpperStage'] == 6 or self.mgmt[i]['b4UpperStage'] == 1
sell_filter = self.mgmt[i]['b4UpperStage'] == 3 or self.mgmt[i]['b4UpperStage'] == 4
buy_sig = self.mgmt[i]['b4Stage'] == 6 or self.mgmt[i]['b4Stage'] == 1
sell_sig = self.mgmt[i]['b4Stage'] == 3 or self.mgmt[i]['b4Stage'] == 4
if all([buy_filter ,buy_sig ,self.count_buy()==0]) : self.opsig = self.mgmt[i]['EntrySig'] = 1
elif all([sell_filter ,sell_sig ,self.count_sell()==0]) : self.opsig = self.mgmt[i]['EntrySig'] = -1
def exit_sig(self ,i) :
self.sosig = np.nan
if self.count_buy() > 0 :
clbuy = self.mgmt[i-1]['b4Stage'] < 3 and ( self.mgmt[i]['b4Stage'] == 3 or self.mgmt[i]['b4Stage'] == 4 )
if not self.EXIT2=='none' and self.mgmt[i]['b4Low'] < self.mgmt[i-1][self.EXIT2] : self.sosig = 1
if any([clbuy ,self.sosig==1 ,self.opsig==-1]) : self.clsig = self.mgmt[i]['ExitSig'] = 1
elif self.count_sell() > 0 :
clsell = ( self.mgmt[i-1]['b4Stage'] < 6 and self.mgmt[i-1]['b4Stage'] >= 4 ) and ( self.mgmt[i]['b4Stage'] == 6 or self.mgmt[i]['b4Stage'] == 1 )
if not self.EXIT2=='none' and self.mgmt[i]['b4High'] > self.mgmt[i-1][self.EXIT2] : self.sosig = 1
if any([clsell ,self.sosig==1 ,self.opsig==1]) : self.clsig = self.mgmt[i]['ExitSig'] = 1
def onTick_Base(self ,i) :
self.append_dict(i)
if self.is_nan[i] or self.first_time :
self.management(i ,Case='is_nan')
return
self.management(i ,Case='1st')
self.entry_sig(i)
self.exit_sig(i)
self.exit(i)
self.management(i ,Case='2nd')
self.entry(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_none(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'none'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Entry_3_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'SO_Entry'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Entry_3_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 3
self.EXIT2 = 'SO_Entry'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Entry_2_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 2
self.EXIT2 = 'SO_Entry'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Entry_2_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 2
self.EXIT2 = 'SO_Entry'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Close_3_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'SO_Close'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Close_3_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 3
self.EXIT2 = 'SO_Close'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Close_2_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 2
self.EXIT2 = 'SO_Close'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_none__EXIT1_Cycle36__EXIT2_SO_Close_2_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 2
self.EXIT2 = 'SO_Close'
self.FILTER = 'none'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_none(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'none'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Entry_3_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'SO_Entry'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Entry_3_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 3
self.EXIT2 = 'SO_Entry'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Entry_2_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 2
self.EXIT2 = 'SO_Entry'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Entry_2_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 2
self.EXIT2 = 'SO_Entry'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Close_3_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 3
self.EXIT2 = 'SO_Close'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Close_3_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 3
self.EXIT2 = 'SO_Close'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Close_2_UM(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atrUM
self.atrN = 2
self.EXIT2 = 'SO_Close'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
class ENTRY_Cycle63__FILTER_UpperCycle63__EXIT1_Cycle36__EXIT2_SO_Close_2_20(Strategy_CycleEMA_Base) :
def __init__(self ,symbol ,np_arr_dic ,df) :
Strategy_CycleEMA_Base.__init__(self ,symbol ,np_arr_dic ,df)
self.first_time = True
self.opcl = 'close'
self.atr = self.atr20
self.atrN = 2
self.EXIT2 = 'SO_Close'
self.FILTER = 'UpperCycle63'
def onTick(self ,i) : self.onTick_Base(i)
バックテストの実行と結果
諸々の準備が整ったら、バックテストを実行します。4分程度で計算できてしまいます。すごい。

Entry | Filter | Exit1 | Exit2 | |
---|---|---|---|---|
1 | Cycle63 | none | Cycle36 | none |
2 | Cycle63 | none | Cycle36 | SO_Entry_3_UM |
3 | Cycle63 | none | Cycle36 | SO_Entry_3_20 |
4 | Cycle63 | none | Cycle36 | SO_Entry_2_UM |
5 | Cycle63 | none | Cycle36 | SO_Entry_2_20 |
6 | Cycle63 | none | Cycle36 | SO_Close_3_UM |
7 | Cycle63 | none | Cycle36 | SO_Close_3_20 |
8 | Cycle63 | none | Cycle36 | SO_Close_2_UM |
9 | Cycle63 | none | Cycle36 | SO_Close_2_20 |
10 | Cycle63 | UpperCycle63 | Cycle36 | none |
11 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_UM |
12 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_20 |
13 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_UM |
14 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_20 |
15 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_UM |
16 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_20 |
17 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_UM |
18 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_20 |
バックテストの結果
取引回数 | 勝率 | 平均利益/atr | 平均損失/atr | 累計損益/atr | RR比率 | 期待値/atr | |
---|---|---|---|---|---|---|---|
1 | 5387 | 30.02% | 2.964 | -1.153 | 453 | 2.57 | 0.083 |
2 | 5411 | 29.88% | 2.965 | -1.137 | 490 | 2.61 | 0.089 |
3 | 5427 | 29.78% | 4.013 | -1.513 | 732 | 2.65 | 0.133 |
4 | 5528 | 29.40% | 2.958 | -1.097 | 534 | 2.7 | 0.095 |
5 | 5749 | 28.21% | 4.006 | -1.394 | 755 | 2.87 | 0.129 |
6 | 5838 | 30.95% | 2.815 | -1.137 | 511 | 2.48 | 0.087 |
7 | 6498 | 31.75% | 3.435 | -1.439 | 709 | 2.39 | 0.108 |
8 | 7589 | 33.31% | 2.333 | -1.046 | 607 | 2.23 | 0.080 |
9 | 9925 | 34.79% | 2.495 | -1.211 | 778 | 2.06 | 0.078 |
10 | 3401 | 33.28% | 3.107 | -1.373 | 408 | 2.26 | 0.118 |
11 | 3459 | 32.70% | 3.106 | -1.347 | 382 | 2.31 | 0.109 |
12 | 3521 | 32.15% | 4.208 | -1.757 | 572 | 2.39 | 0.161 |
13 | 3614 | 31.43% | 3.105 | -1.254 | 424 | 2.48 | 0.116 |
14 | 3872 | 29.55% | 4.183 | -1.510 | 673 | 2.77 | 0.172 |
15 | 3816 | 33.60% | 2.940 | -1.323 | 418 | 2.22 | 0.109 |
16 | 4384 | 33.85% | 3.532 | -1.611 | 570 | 2.19 | 0.130 |
17 | 5184 | 35.03% | 2.417 | -1.143 | 539 | 2.11 | 0.104 |
18 | 7002 | 35.62% | 2.548 | -1.263 | 660 | 2.02 | 0.094 |
結果を踏まえて、順番に解説していきます。
値幅による損益の統計
まずは、値幅による損益の統計です。取引量やATRなどは一切関係なく、純粋な値幅です。日経225なら「100円獲った」とか、USDJPYなら「1.252円獲った」とか、そういうやつです。
値幅で考える場合は、銘柄に寄って呼び値がまったく違うので注意が必要です(日経:10円刻み、USDJPY:0.001円刻み、EURUSD:0.00001USD刻み)。つまり、同じように集計することができないのです。
ここではモデルケースとして、Nikkei225とEURJPYあたりで比較してみようと思います。まずはNikkei225です。
Entry | Filter | Exit1 | Exit2 | |
---|---|---|---|---|
1 | Cycle63 | none | Cycle36 | none |
2 | Cycle63 | none | Cycle36 | SO_Entry_3_UM |
3 | Cycle63 | none | Cycle36 | SO_Entry_3_20 |
4 | Cycle63 | none | Cycle36 | SO_Entry_2_UM |
5 | Cycle63 | none | Cycle36 | SO_Entry_2_20 |
6 | Cycle63 | none | Cycle36 | SO_Close_3_UM |
7 | Cycle63 | none | Cycle36 | SO_Close_3_20 |
8 | Cycle63 | none | Cycle36 | SO_Close_2_UM |
9 | Cycle63 | none | Cycle36 | SO_Close_2_20 |
10 | Cycle63 | UpperCycle63 | Cycle36 | none |
11 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_UM |
12 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_20 |
13 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_UM |
14 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_20 |
15 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_UM |
16 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_20 |
17 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_UM |
18 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_20 |
銘柄 | 取引回数/共通 | 勝率/共通 | RR比率/price | RR比率/atr | |
---|---|---|---|---|---|
1 | Nikkei225 | 236 | 30.93% | 2.19 | 2.55 |
2 | Nikkei225 | 239 | 30.54% | 2.14 | 2.48 |
3 | Nikkei225 | 239 | 30.54% | 2.17 | 2.49 |
4 | Nikkei225 | 241 | 30.29% | 2.25 | 2.62 |
5 | Nikkei225 | 248 | 29.44% | 2.31 | 2.61 |
6 | Nikkei225 | 252 | 29.76% | 2.03 | 2.32 |
7 | Nikkei225 | 254 | 30.31% | 2.01 | 2.36 |
8 | Nikkei225 | 274 | 31.39% | 2.09 | 2.48 |
9 | Nikkei225 | 296 | 33.78% | 1.91 | 2.25 |
10 | Nikkei225 | 163 | 31.29% | 1.81 | 2.04 |
11 | Nikkei225 | 164 | 31.10% | 1.81 | 2.05 |
12 | Nikkei225 | 164 | 31.10% | 1.87 | 2.11 |
13 | Nikkei225 | 169 | 30.18% | 2.02 | 2.29 |
14 | Nikkei225 | 175 | 29.71% | 2.07 | 2.3 |
15 | Nikkei225 | 175 | 30.86% | 1.65 | 1.85 |
16 | Nikkei225 | 177 | 32.77% | 1.55 | 1.78 |
17 | Nikkei225 | 193 | 31.61% | 1.83 | 2.1 |
18 | Nikkei225 | 211 | 34.60% | 1.63 | 1.85 |
同じ銘柄とバックテストのパターンなので、当然、取引回数と勝率は同じ結果です。それに対してRR比率を比較すると、単なる値幅の「RR比率/price」に対して、すべてにおいて「RR比率/atr」の方が良い結果になっています。
次に、EURJPYも見てみましょう。
銘柄 | 取引回数/共通 | 勝率/共通 | RR比率/price | RR比率/atr | |
---|---|---|---|---|---|
1 | EURJPY | 721 | 31.35% | 2.42 | 2.55 |
2 | EURJPY | 725 | 31.17% | 2.44 | 2.58 |
3 | EURJPY | 726 | 31.13% | 2.46 | 2.64 |
4 | EURJPY | 737 | 30.66% | 2.53 | 2.68 |
5 | EURJPY | 767 | 29.07% | 2.68 | 2.87 |
6 | EURJPY | 787 | 32.27% | 2.3 | 2.46 |
7 | EURJPY | 887 | 33.15% | 2.23 | 2.38 |
8 | EURJPY | 1009 | 34.69% | 2.13 | 2.25 |
9 | EURJPY | 1381 | 36.13% | 1.89 | 2 |
10 | EURJPY | 456 | 33.55% | 2.21 | 2.32 |
11 | EURJPY | 460 | 33.26% | 2.23 | 2.36 |
12 | EURJPY | 473 | 32.35% | 2.28 | 2.36 |
13 | EURJPY | 483 | 31.68% | 2.34 | 2.5 |
14 | EURJPY | 520 | 29.81% | 2.58 | 2.77 |
15 | EURJPY | 511 | 34.83% | 2.08 | 2.23 |
16 | EURJPY | 590 | 35.59% | 2.05 | 2.11 |
17 | EURJPY | 687 | 35.81% | 2.04 | 2.17 |
18 | EURJPY | 970 | 36.80% | 1.88 | 1.98 |
EURJPYも同様に、総じて「RR比率/atr」の方が良い結果になりました。
これは「もみ合い放れ」の利益が大きくなることが要因であると考えています(もみ合い → ATRが小さくなる → 取引量が多くなる → そのもみ合いから放れたときの利益が大きくなる)。
ATRによる資金管理(サイジング)は、バックテストを再現する要
さて、次に資金管理を含めた統計を見ていきます。
Entry | Filter | Exit1 | Exit2 | |
---|---|---|---|---|
1 | Cycle63 | none | Cycle36 | none |
2 | Cycle63 | none | Cycle36 | SO_Entry_3_UM |
3 | Cycle63 | none | Cycle36 | SO_Entry_3_20 |
4 | Cycle63 | none | Cycle36 | SO_Entry_2_UM |
5 | Cycle63 | none | Cycle36 | SO_Entry_2_20 |
6 | Cycle63 | none | Cycle36 | SO_Close_3_UM |
7 | Cycle63 | none | Cycle36 | SO_Close_3_20 |
8 | Cycle63 | none | Cycle36 | SO_Close_2_UM |
9 | Cycle63 | none | Cycle36 | SO_Close_2_20 |
10 | Cycle63 | UpperCycle63 | Cycle36 | none |
11 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_UM |
12 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_3_20 |
13 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_UM |
14 | Cycle63 | UpperCycle63 | Cycle36 | SO_Entry_2_20 |
15 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_UM |
16 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_3_20 |
17 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_UM |
18 | Cycle63 | UpperCycle63 | Cycle36 | SO_Close_2_20 |
取引回数 | 勝率 | 平均利益 | 平均損失 | 累計損益 | RR比率 | RR比率/atr | 期待値 | |
---|---|---|---|---|---|---|---|---|
1 | 5387 | 30.02% | 297,357 | -115,931 | 44,577,933 | 2.56 | 2.57 | 8,124 |
2 | 5411 | 29.88% | 297,519 | -114,416 | 47,909,574 | 2.6 | 2.61 | 8,685 |
3 | 5427 | 29.78% | 407,273 | -154,323 | 71,261,888 | 2.64 | 2.65 | 12,904 |
4 | 5528 | 29.40% | 296,688 | -110,266 | 52,520,816 | 2.69 | 2.7 | 9,361 |
5 | 5749 | 28.21% | 406,311 | -141,872 | 74,666,019 | 2.86 | 2.87 | 12,790 |
6 | 5838 | 30.95% | 282,781 | -114,354 | 50,594,961 | 2.47 | 2.48 | 8,569 |
7 | 6498 | 31.75% | 349,242 | -146,481 | 71,283,806 | 2.38 | 2.39 | 10,902 |
8 | 7589 | 33.31% | 234,089 | -105,087 | 60,141,557 | 2.23 | 2.23 | 7,897 |
9 | 9925 | 34.79% | 252,189 | -122,581 | 77,705,989 | 2.06 | 2.06 | 7,805 |
10 | 3401 | 33.28% | 311,440 | -137,887 | 40,237,406 | 2.26 | 2.26 | 11,669 |
11 | 3459 | 32.70% | 311,251 | -135,446 | 37,248,808 | 2.3 | 2.31 | 10,612 |
12 | 3521 | 32.15% | 426,048 | -178,382 | 56,844,312 | 2.39 | 2.39 | 15,942 |
13 | 3614 | 31.43% | 310,941 | -126,015 | 41,466,831 | 2.47 | 2.48 | 11,334 |
14 | 3872 | 29.55% | 423,224 | -152,900 | 67,669,208 | 2.77 | 2.77 | 17,319 |
15 | 3816 | 33.60% | 295,031 | -133,011 | 41,445,470 | 2.22 | 2.22 | 10,791 |
16 | 4384 | 33.85% | 358,275 | -163,235 | 58,299,384 | 2.19 | 2.19 | 13,298 |
17 | 5184 | 35.03% | 242,292 | -114,750 | 53,523,075 | 2.11 | 2.11 | 10,325 |
18 | 7002 | 35.62% | 257,266 | -127,496 | 66,868,116 | 2.02 | 2.02 | 9,550 |
「まあ、こんな感じか」といったところですね。十数年かけて、1000万円の資金が5~8倍程度になったことがわかります。
ここで1点特筆すべきなのは「RR比率」です。「RR比率/atr」を並べて掲載しましたが、すべて「誤差0.01」で収まっているのがわかると思います(取引回数や勝率は一緒です)。つまり、資金管理を含めても「ATRで平準化した統計を再現することができた」ということです。
以前の記事で解説した内容を、再現できたということですね。
この手法の想定される利益は
もっとも成績がよかった⑤の想定される利益は、以下のように算出することができます。100万円の運用資金だとすると、477万円の利益で、577万円まで資金が増えるであろうことがわかります。
引用元: 上位足を考慮した移動平均線のバックテスト
つまり、どんなにチャートを見ながら検証して良い手法を見つけたとしても、資金管理(サイジング)を怠ると再現することができず、劣悪な結果になってしまうということです。ATRによる資金管理(サイジング)は「バックテストを再現する要」と言えそうです。
また、資金量が少ないトレーダーは、サイジングを適切に行うことができないという点で不利だと言えそうです。
ATRの数値には素直に従うべきか(前回の宿題)
最後に、前回の宿題について考えてみたいと思います。
ATRの数値には素直に従うべきか
ということは、通常の「ATR20」の方が良いという結論になりそうです――と言いたいところですが、実は、これについては新たな仮説がでてきました。
筆者のトレードでは、ポジションサイジングの計算にATRを使っています。つまり、ATRの大小で取引量が変わり、取引量が変わるということは損益にも影響がでます。ということは、単純な「勝率」「RR比率」「期待値」だけでは、不十分で判断することができないのです。
実はこれは、「資金管理は、バックテストを再現する要」の結果をもって解決しています。
以前の仮説は「取引量が変わることが損益に影響を及ぼす」でしたが、今回の検証で、「資金管理を含めた統計の結果」と「ATRで平準化した統計の結果」が同一である(RR比の誤差0.01のみ)ことがわかりました。これをもって、「通常の『ATR20』の方が良いという結論」になります。
銘柄ごとの損益曲線
前回の記事では同じパターンのATR比のグラフを掲載したので、今回は取引量に応じた損益の推移を掲載します。
ストラテジーごとの損益曲線
ここも同様に、取引量に応じた損益の推移を掲載しています。
まとめ
今回のバックテストでは、開発に思いのほか手間取りました。結果的には思い通りのものができましたし、今回の開発は、以降の分散投資を含めたバックテストに不可欠なものだったので、満足はしています。
「ATRによる資金管理(サイジング)が最終的な成績に良い影響を及ぼす」という点は、新たな気付きでした。おそらく「もみ合い期」の損失を軽減するためでしょう。
たとえ4時間足であっても、単一銘柄だと損失で終わる年がけっこうあります。これが、複数銘柄で分散投資をするとどうなるのか、早く開発をすすめて試してみたいものです。
注意点
これらの結果は4時間足のものです。トレードする時間足を変えると、これらの結果もガラッと変わることがありますので十分ご注意ください。
取引回数としてのサンプルは十分、リーマンショックの期間も含まれているので、そこそこ参考になる数字だとは思います。しかし、銘柄数や年数のサンプルとしては、正直ちょっと不十分です。銘柄や年の変化は想像以上にトレード結果に影響を及ぼしますので。
また、バックテストには万全を期していますが、結果の完全性や手法を用いた際の利益を保証するものではありません。予めご了承ください。

- 記事をシェア