InvestmentTechHack

資金管理(取引量の調整)が成績に及ぼす影響のバックテスト|日経225 FX 4時間足

Posted on August 12th, 2018Updated on October 30th, 2020

どんな記事

このバックテストでは、次のことがわかります。

  • サイジングの有効性 データによる裏付け
  • 基準にするATR は加工したほうが良いのか
  • 単利の資金管理 を含めるとどうなるのか

今回のバックテストを行うにあたって、これまでのバックテストに単利の資金管理を含める開発を行いました。サイジングの検証は、資金管理を含めないと意味をなさない からです。

この辺りから 〝チャートを眺めて行うバックテストではできない領域〟 に入ってくると思います。以前まで行っていた検証に比べるとまだまだ機能が劣りますが(鋭意開発中)、何より計算が速い。機能をフルに盛り込んでも以前の100倍くらいの速さで行うことができそうです。楽しみ。

2018年8月19日 修正

値幅による損益の統計の記述を一部修正しました。

バックテストに使用したツールと価格データ

ここは過去に書いた記事とまったく一緒なので引用します。

使用したツール

google colaboratory

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万円)

となるように

UnitValue(ATR×Lot×Currency)\frac{UnitValue}{( ATR \times Lot \times Currency)} で1ティックごとに算出しています。

UnitValue:資金の1%(10万円)。ATR:そのティックのATR(ATR20かUM-ATR)。Currency:クロス円の場合は1、ドルストレートの場合はそのティックのUSDJPY。

これだけじゃわからない方は以前の記事をお読みください。

エントリー

今回は、次の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_UMSO_Close_3_UM
SO_Entry_3_20SO_Close_3_20
SO_Entry_2_UMSO_Close_2_UM
SO_Entry_2_20SO_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」で算出する。

言葉で書くとややこしいので、次のグラフをご覧いただくのが良いと思います。

ATR Upper Middle ATR

上がATR20、下がUM-ATRです。UM-ATRは、「ATR20 > 過去1800本最高値と最安値の中間」のときはATR20を、「ATR20 < 中間の値」のときは中間の値を使います。「なぜ、こんなことをするのか」については、次回の記事で解説したいと思います。

テストするパターン

ここも前回と一緒です。

EntryFilterExit1Exit2
1Cycle63noneCycle36none
2Cycle63noneCycle36SO_Entry_3_UM
3Cycle63noneCycle36SO_Entry_3_20
4Cycle63noneCycle36SO_Entry_2_UM
5Cycle63noneCycle36SO_Entry_2_20
6Cycle63noneCycle36SO_Close_3_UM
7Cycle63noneCycle36SO_Close_3_20
8Cycle63noneCycle36SO_Close_2_UM
9Cycle63noneCycle36SO_Close_2_20
10Cycle63UpperCycle63Cycle36none
11Cycle63UpperCycle63Cycle36SO_Entry_3_UM
12Cycle63UpperCycle63Cycle36SO_Entry_3_20
13Cycle63UpperCycle63Cycle36SO_Entry_2_UM
14Cycle63UpperCycle63Cycle36SO_Entry_2_20
15Cycle63UpperCycle63Cycle36SO_Close_3_UM
16Cycle63UpperCycle63Cycle36SO_Close_3_20
17Cycle63UpperCycle63Cycle36SO_Close_2_UM
18Cycle63UpperCycle63Cycle36SO_Close_2_20

バックテストの実施と考察

バックテストの関数

ここが一番重要だったりするのですが、ものすごく長くなってしまうので割愛します。こちらのサイトのコードを参考に作成しました((´・ω・`;)ヒィィッ すいません「バックテストを試してみました」

参考として、ストラテジー部分を掲載します。

Strategy_CycleEMA_Base(Google
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)

Strategy(Google
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分程度で計算できてしまいます。すごい。

run backtesting バックテストを実行

EntryFilterExit1Exit2
1Cycle63noneCycle36none
2Cycle63noneCycle36SO_Entry_3_UM
3Cycle63noneCycle36SO_Entry_3_20
4Cycle63noneCycle36SO_Entry_2_UM
5Cycle63noneCycle36SO_Entry_2_20
6Cycle63noneCycle36SO_Close_3_UM
7Cycle63noneCycle36SO_Close_3_20
8Cycle63noneCycle36SO_Close_2_UM
9Cycle63noneCycle36SO_Close_2_20
10Cycle63UpperCycle63Cycle36none
11Cycle63UpperCycle63Cycle36SO_Entry_3_UM
12Cycle63UpperCycle63Cycle36SO_Entry_3_20
13Cycle63UpperCycle63Cycle36SO_Entry_2_UM
14Cycle63UpperCycle63Cycle36SO_Entry_2_20
15Cycle63UpperCycle63Cycle36SO_Close_3_UM
16Cycle63UpperCycle63Cycle36SO_Close_3_20
17Cycle63UpperCycle63Cycle36SO_Close_2_UM
18Cycle63UpperCycle63Cycle36SO_Close_2_20

バックテストの結果

取引回数勝率平均利益/atr平均損失/atr累計損益/atrRR比率期待値/atr
1538730.02%2.964-1.1534532.570.083
2541129.88%2.965-1.1374902.610.089
3542729.78%4.013-1.5137322.650.133
4552829.40%2.958-1.0975342.70.095
5574928.21%4.006-1.3947552.870.129
6583830.95%2.815-1.1375112.480.087
7649831.75%3.435-1.4397092.390.108
8758933.31%2.333-1.0466072.230.080
9992534.79%2.495-1.2117782.060.078
10340133.28%3.107-1.3734082.260.118
11345932.70%3.106-1.3473822.310.109
12352132.15%4.208-1.7575722.390.161
13361431.43%3.105-1.2544242.480.116
14387229.55%4.183-1.5106732.770.172
15381633.60%2.940-1.3234182.220.109
16438433.85%3.532-1.6115702.190.130
17518435.03%2.417-1.1435392.110.104
18700235.62%2.548-1.2636602.020.094

結果を踏まえて、順番に解説していきます。

値幅による損益の統計

まずは、値幅による損益の統計です。取引量やATRなどは一切関係なく、純粋な値幅です。日経225なら「100円獲った」とか、USDJPYなら「1.252円獲った」とか、そういうやつです。

値幅で考える場合は、銘柄に寄って呼び値がまったく違うので注意が必要です(日経:10円刻み、USDJPY:0.001円刻み、EURUSD:0.00001USD刻み)。つまり、同じように集計することができないのです。

ここではモデルケースとして、Nikkei225とEURJPYあたりで比較してみようと思います。まずはNikkei225です。

EntryFilterExit1Exit2
1Cycle63noneCycle36none
2Cycle63noneCycle36SO_Entry_3_UM
3Cycle63noneCycle36SO_Entry_3_20
4Cycle63noneCycle36SO_Entry_2_UM
5Cycle63noneCycle36SO_Entry_2_20
6Cycle63noneCycle36SO_Close_3_UM
7Cycle63noneCycle36SO_Close_3_20
8Cycle63noneCycle36SO_Close_2_UM
9Cycle63noneCycle36SO_Close_2_20
10Cycle63UpperCycle63Cycle36none
11Cycle63UpperCycle63Cycle36SO_Entry_3_UM
12Cycle63UpperCycle63Cycle36SO_Entry_3_20
13Cycle63UpperCycle63Cycle36SO_Entry_2_UM
14Cycle63UpperCycle63Cycle36SO_Entry_2_20
15Cycle63UpperCycle63Cycle36SO_Close_3_UM
16Cycle63UpperCycle63Cycle36SO_Close_3_20
17Cycle63UpperCycle63Cycle36SO_Close_2_UM
18Cycle63UpperCycle63Cycle36SO_Close_2_20
銘柄取引回数/共通勝率/共通RR比率/priceRR比率/atr
1Nikkei22523630.93%2.192.55
2Nikkei22523930.54%2.142.48
3Nikkei22523930.54%2.172.49
4Nikkei22524130.29%2.252.62
5Nikkei22524829.44%2.312.61
6Nikkei22525229.76%2.032.32
7Nikkei22525430.31%2.012.36
8Nikkei22527431.39%2.092.48
9Nikkei22529633.78%1.912.25
10Nikkei22516331.29%1.812.04
11Nikkei22516431.10%1.812.05
12Nikkei22516431.10%1.872.11
13Nikkei22516930.18%2.022.29
14Nikkei22517529.71%2.072.3
15Nikkei22517530.86%1.651.85
16Nikkei22517732.77%1.551.78
17Nikkei22519331.61%1.832.1
18Nikkei22521134.60%1.631.85

同じ銘柄とバックテストのパターンなので、当然、取引回数と勝率は同じ結果です。それに対してRR比率を比較すると、単なる値幅の「RR比率/price」に対して、すべてにおいて「RR比率/atr」の方が良い結果になっています。

次に、EURJPYも見てみましょう。

銘柄取引回数/共通勝率/共通RR比率/priceRR比率/atr
1EURJPY72131.35%2.422.55
2EURJPY72531.17%2.442.58
3EURJPY72631.13%2.462.64
4EURJPY73730.66%2.532.68
5EURJPY76729.07%2.682.87
6EURJPY78732.27%2.32.46
7EURJPY88733.15%2.232.38
8EURJPY100934.69%2.132.25
9EURJPY138136.13%1.892
10EURJPY45633.55%2.212.32
11EURJPY46033.26%2.232.36
12EURJPY47332.35%2.282.36
13EURJPY48331.68%2.342.5
14EURJPY52029.81%2.582.77
15EURJPY51134.83%2.082.23
16EURJPY59035.59%2.052.11
17EURJPY68735.81%2.042.17
18EURJPY97036.80%1.881.98

EURJPYも同様に、総じて「RR比率/atr」の方が良い結果になりました。

これは「もみ合い放れ」の利益が大きくなることが要因であると考えています(もみ合い → ATRが小さくなる → 取引量が多くなる → そのもみ合いから放れたときの利益が大きくなる)。

ATRによる資金管理(サイジング)は、バックテストを再現する要

さて、次に資金管理を含めた統計を見ていきます。

EntryFilterExit1Exit2
1Cycle63noneCycle36none
2Cycle63noneCycle36SO_Entry_3_UM
3Cycle63noneCycle36SO_Entry_3_20
4Cycle63noneCycle36SO_Entry_2_UM
5Cycle63noneCycle36SO_Entry_2_20
6Cycle63noneCycle36SO_Close_3_UM
7Cycle63noneCycle36SO_Close_3_20
8Cycle63noneCycle36SO_Close_2_UM
9Cycle63noneCycle36SO_Close_2_20
10Cycle63UpperCycle63Cycle36none
11Cycle63UpperCycle63Cycle36SO_Entry_3_UM
12Cycle63UpperCycle63Cycle36SO_Entry_3_20
13Cycle63UpperCycle63Cycle36SO_Entry_2_UM
14Cycle63UpperCycle63Cycle36SO_Entry_2_20
15Cycle63UpperCycle63Cycle36SO_Close_3_UM
16Cycle63UpperCycle63Cycle36SO_Close_3_20
17Cycle63UpperCycle63Cycle36SO_Close_2_UM
18Cycle63UpperCycle63Cycle36SO_Close_2_20
取引回数勝率平均利益平均損失累計損益RR比率RR比率/atr期待値
1538730.02%297,357-115,93144,577,9332.562.578,124
2541129.88%297,519-114,41647,909,5742.62.618,685
3542729.78%407,273-154,32371,261,8882.642.6512,904
4552829.40%296,688-110,26652,520,8162.692.79,361
5574928.21%406,311-141,87274,666,0192.862.8712,790
6583830.95%282,781-114,35450,594,9612.472.488,569
7649831.75%349,242-146,48171,283,8062.382.3910,902
8758933.31%234,089-105,08760,141,5572.232.237,897
9992534.79%252,189-122,58177,705,9892.062.067,805
10340133.28%311,440-137,88740,237,4062.262.2611,669
11345932.70%311,251-135,44637,248,8082.32.3110,612
12352132.15%426,048-178,38256,844,3122.392.3915,942
13361431.43%310,941-126,01541,466,8312.472.4811,334
14387229.55%423,224-152,90067,669,2082.772.7717,319
15381633.60%295,031-133,01141,445,4702.222.2210,791
16438433.85%358,275-163,23558,299,3842.192.1913,298
17518435.03%242,292-114,75053,523,0752.112.1110,325
18700235.62%257,266-127,49666,868,1162.022.029,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比のグラフを掲載したので、今回は取引量に応じた損益の推移を掲載します。

バックテスト 結果 日経225(Nikkei225) バックテスト 結果 米ドル/円(USDJPY) バックテスト 結果 ユーロ/円(EURJPY) バックテスト 結果 ポンド/円(GBPJPY) バックテスト 結果 豪ドル/円(AUDJPY) バックテスト 結果 ユーロ/米ドル(EURUSD) バックテスト 結果 ポンド/米ドル(GBPUSD) バックテスト 結果 豪ドル/米ドル(AUDUSD)

ストラテジーごとの損益曲線

ここも同様に、取引量に応じた損益の推移を掲載しています。

バックテスト 結果 1 バックテスト 結果 2 バックテスト 結果 3 バックテスト 結果 4 バックテスト 結果 5 バックテスト 結果 6 バックテスト 結果 7 バックテスト 結果 8 バックテスト 結果 9 バックテスト 結果 10 バックテスト 結果 11 バックテスト 結果 12 バックテスト 結果 13 バックテスト 結果 14 バックテスト 結果 15 バックテスト 結果 16 バックテスト 結果 17 バックテスト 結果 18

まとめ

今回のバックテストでは、開発に思いのほか手間取りました。結果的には思い通りのものができましたし、今回の開発は、以降の分散投資を含めたバックテストに不可欠なものだったので、満足はしています。

「ATRによる資金管理(サイジング)が最終的な成績に良い影響を及ぼす」という点は、新たな気付きでした。おそらく「もみ合い期」の損失を軽減するためでしょう。

たとえ4時間足であっても、単一銘柄だと損失で終わる年がけっこうあります。これが、複数銘柄で分散投資をするとどうなるのか、早く開発をすすめて試してみたいものです。

注意点

これらの結果は4時間足のものです。トレードする時間足を変えると、これらの結果もガラッと変わることがありますので十分ご注意ください。

取引回数としてのサンプルは十分、リーマンショックの期間も含まれているので、そこそこ参考になる数字だとは思います。しかし、銘柄数や年数のサンプルとしては、正直ちょっと不十分です。銘柄や年の変化は想像以上にトレード結果に影響を及ぼしますので。

また、バックテストには万全を期していますが、結果の完全性や手法を用いた際の利益を保証するものではありません。予めご了承ください。

タカハシ / 8年目の兼業トレーダー

元・日本料理の板前。現在は、投資やプログラミング、動画コンテンツの撮影・制作・編集などを。更新のお知らせは、各SNSやLINEで。LINEだと1対1でお話することもできます!

このブログと筆者について運用管理表

  • 記事をシェア
© Investment Tech Hack 2021.