ホーム » FX自動売買基礎と応用 » 【MT5 API シストレ開発➂】バックテスト応用編

【MT5 API シストレ開発➂】バックテスト応用編


この記事は、システムトレードを自身のPCで実装・運用するためのシリーズ第3回です。

前回はバックテストを行うためのライブラリBacktraderの基本操作を解説しましたが、今回は実際の戦略開発でよく使う機能を紹介します。

マルチタイムフレーム

この記事におけるマルチタイムフレームとは、複数の時間軸を用いることを指します。

例えば、「日足でトレンドの発生を検出し、1時間足で注文のシグナルを作成する」というように、長期と短期の時間軸を両方扱う戦略を作成できます。


# データ投入
cerebro.adddata(data)

事前に準備したデータは、上記のコードでバックテストに利用できると前回の記事で解説しましたが、同様のコードで複数のデータも投入できます。

複数のデータを投入するための実際のコードを確認していきましょう。


# 1時間足
data_h1 = bt.feeds.PandasData(
    dataname=rates_df_h1,
    open='open',
    high='high',
    low='low',
    close='close',
    volume='tick_volume',
    openinterest=None
)

# 日足
data_d1 = bt.feeds.PandasData(
    dataname=rates_df_d1,
    open='open',
    high='high',
    low='low',
    close='close',
    volume='tick_volume',
    openinterest=None
)

このように2つの時間軸で、それぞれBacktrader用のデータを作成します。


# データ投入
cerebro.adddata(data_h1, name='H1')
cerebro.adddata(data_d1, name='D1')

作成した2つのデータをadddataで追加することで、バックテストする戦略内で2つの時間軸を扱えるようになります。

以下は上位時間軸のインジケーターでトレンド判定を行い、下位時間軸のインジケーターで注文のタイミングをとる戦略の例です。


class MultiTimeframeStrategy(bt.Strategy):

    params = dict(
        fast_period=10,
        slow_period=30,
        trend_period=20,
    )

    def __init__(self):
        # データ参照
        self.data_h1 = self.datas[0]  # 1時間足(最初に入れたデータ)
        self.data_d1 = self.datas[1]  # 日足(2番目に入れたデータ)

        # 日足トレンド判定用SMA
        self.d1_sma = bt.ind.SMA(
            self.data_d1.close,
            period=self.p.trend_period
        )

        # 1時間足エントリー用SMA
        self.h1_fast = bt.ind.SMA(
            self.data_h1.close,
            period=self.p.fast_period
        )
        self.h1_slow = bt.ind.SMA(
            self.data_h1.close,
            period=self.p.slow_period
        )

        self.h1_cross = bt.ind.CrossOver(
            self.h1_fast,
            self.h1_slow
        )

    def next(self):
        # 日足データがまだ揃っていない場合は何もしない
        if len(self.data_d1) < self.p.trend_period:
            return

        # 日足トレンド判定
        uptrend = self.data_d1.close[0] > self.d1_sma[0]

        if not self.position:
            if uptrend and self.h1_cross > 0:
                self.buy(data=self.data_h1)

        else:
            # 逆クロスで手仕舞い
            if self.h1_cross < 0:
                self.close(data=self.data_h1)

日足の終値がSMAより上にあるときに、1時間足のSMAがゴールデンクロスしたら買い注文を出し、デッドクロスしたら決済するコードです。

コード内で呼び出している「datas[0]」「datas[1]」の対応はadddataした順序に依存します。

日足を先に投入している場合は、順序が逆になりdatas[0]が日足となるため、注意が必要です。

このようなコードを応用することで、上位時間軸でフィルタリングし、下位時間軸でタイミングをとるような戦略を構築することが可能です。

【MT5 API シストレ開発➂】バックテスト応用編_01

バックテストを実行し、結果を可視化すると下位時間軸(上)と上位時間軸(下)の2つのチャートが出力され、マルチタイムフレームで正しく処理されていることを確認できます。

マルチアセット

マルチタイムフレームでは、複数の時間軸のデータをadddataで追加し、バックテストを行いました。

Backtraderでは、同様に複数のデータを追加することで、複数の銘柄を扱う戦略を作成できます。


# ユーロドル
data_eurusd = bt.feeds.PandasData(
    dataname=rates_df_eurusd,
    open='open',
    high='high',
    low='low',
    close='close',
    volume='tick_volume',
    openinterest=None
)

# ポンドドル
data_gbpusd = bt.feeds.PandasData(
    dataname=rates_df_gbpusd,
    open='open',
    high='high',
    low='low',
    close='close',
    volume='tick_volume',
    openinterest=None
)

まず取り扱いたい複数の銘柄のデータを作成します。


cerebro.adddata(data_eurusd, name='EURUSD')
cerebro.adddata(data_gbpusd, name='GBPUSD')

マルチタイムフレームのときと同じように、作成したデータはadddataで追加することで、複数の銘柄(この場合はユーロドルとポンドドルの2つ)を扱う戦略のバックテストを実行できます。

以下は、ユーロドルとポンドドルの両方を扱った戦略の例です。


class PairRSIStrategy(bt.Strategy):
    params = dict(
        rsi_period=5,
        upper=70,
        lower=30,
        mid=50,
        size=1.0,
    )

    def __init__(self):
        self.eurusd = self.datas[0] # EURUSD
        self.gbpusd = self.datas[1] # GBPUSD

        # RSI(データごと)
        self.rsi_eur = bt.ind.RSI(self.eurusd.close, period=self.p.rsi_period)
        self.rsi_gbp = bt.ind.RSI(self.gbpusd.close, period=self.p.rsi_period)

    def next(self):
        pos_eur = self.getposition(self.eurusd).size
        pos_gbp = self.getposition(self.gbpusd).size

        # ===== エントリー条件 =====
        eur_over_gbp_under = (
            self.rsi_eur[0] >= self.p.upper and
            self.rsi_gbp[0] <= self.p.lower
        )

        gbp_over_eur_under = (
            self.rsi_gbp[0] >= self.p.upper and
            self.rsi_eur[0] <= self.p.lower
        )

        # ポジションが無いときだけ建てる
        if pos_eur == 0 and pos_gbp == 0:
            if eur_over_gbp_under:
                self.sell(data=self.eurusd, size=self.p.size)
                self.buy(data=self.gbpusd, size=self.p.size)

            elif gbp_over_eur_under:
                self.sell(data=self.gbpusd, size=self.p.size)
                self.buy(data=self.eurusd, size=self.p.size)

        # ===== 決済条件 =====
        else:
            # どちらか一方でも 50 をまたいだら全決済
            cross_mid = (
                (self.rsi_eur[-1] < self.p.mid <= self.rsi_eur[0]) or
                (self.rsi_eur[-1] > self.p.mid >= self.rsi_eur[0]) or
                (self.rsi_gbp[-1] < self.p.mid <= self.rsi_gbp[0]) or
                (self.rsi_gbp[-1] > self.p.mid >= self.rsi_gbp[0])
            )

            if cross_mid:
                self.close(data=self.eurusd)
                self.close(data=self.gbpusd)

  • ●ユーロドルのRSIが70以上かつポンドドルのRSIが30以下
     → ユーロドル売り、ポンドドル買い
  • ●ユーロドルのRSIが30以下かつポンドドルのRSIが70以上
     → ユーロドル買い、ポンドドル売り
  • ●どちらか一方でもRSIが50をまたぐ
     → 両ポジションを全決済

というような簡易的なペアトレーディング戦略を作成しました。

このように2つ以上の銘柄を監視し、条件を定義することでトレード戦略を構築できます。

例えば、VIX指数を追加してトレード判定のみに利用する戦略も同じ要領で作成可能です。

先ほどの戦略のバックテスト結果を見ていきましょう。

【MT5 API シストレ開発➂】バックテスト応用編_02

マルチタイムフレームと同様に、使用した2つの銘柄が上下に表示され、それぞれ動作している様子を確認できます。

最適化

ユーロドルとポンドドルを使った戦略をバックテストしましたが、このときのRSIはパラメーターを「5」に固定していました。

さて、このRSIのパラメーターは本当に5が適切な値だったのでしょうか?

過去のデータにおいて、どのパラメーターが最適だったのかを調査するために、システムトレード開発では最適化というプロセスを実行できます。

例えば、RSIのパラメーターを3から20まで1ずつ増やしていき、全ての値でバックテストを実行します。

これにより、過去データにおいて最も良い結果をもたらすパラメーターを特定できます。

最適化を実行するには、事前に評価方法を設定する必要があります。


class FinalValue(bt.Analyzer):
    def stop(self):
        self.rets = self.strategy.broker.getvalue()

    def get_analysis(self):
        return self.rets

このコードでは、「バックテスト終了時の口座残高を記録する」という評価指標を定義しています。

その後、パラメーターを1つずつ総当たりでバックテストし、残高を記録していくことで、どの値が最適だったかを判定します。


cerebro = bt.Cerebro(
    runonce=False,
    maxcpus=1
)

cerebro.adddata(data_eurusd, name='EURUSD')
cerebro.adddata(data_gbpusd, name='GBPUSD')

cerebro.optstrategy(
    PairRSIStrategy,
    rsi_period=range(2, 21)  # 2〜20 を総当たり
)

cerebro.broker.setcash(100_000)
cerebro.broker.setcommission(commission=0.0)

cerebro.addanalyzer(FinalValue, _name='final_value')

opt_results = cerebro.run()

最適化を行うためには、これまでの単一バックテストとは違った設定をする必要があります。

cerebro.optstrategyで事前に準備した戦略(ここではPairRSIStrategy)の指定に加えて、「どのパラメーターをどの範囲で最適化するのか」という情報を与えます。

ここでは「rsi_period=range(2, 21)」とあるように、RSIの期間を2~20まで1つずつ総当たりで検証するように指定しています。

cerebro.addanalyzerで事前に準備した「評価の設定」を投入して、最後にcerebro.run()で最適化を実行します。

ここでは、実行結果をopt_resultsに格納しているので、中身を確認してみしましょう。


results = []

for run in opt_results:
    strat = run[0]  # OptReturn
    results.append({
        "rsi_period": strat.params.rsi_period,
        "final_value": strat.analyzers.final_value.get_analysis()
    })

df = pd.DataFrame(results).sort_values("final_value", ascending=False)

上記は最適化結果を格納したopt_resultsをpandasのデータフレームに変換するためのコードです。

【MT5 API シストレ開発➂】バックテスト応用編_03

変換したデータフレームをグラフ化することで、過去期間ではどのパラメーターが良かったかを判断できます。

最適化と戦略の過大評価

この記事では、実践的な戦略に近づけるため、マルチタイムフレームとマルチアセットの作成方法、そしてパラメーターを最適化する方法について紹介しました。

注意点として、最適化ではパラメーターを総当たりで試行し、同一の過去データを何度も評価することから、都合の良い設定を選んでしまうオーバーフィッティング問題を引き起こし、戦略を過大評価する恐れがあります。

戦略の過大評価は、優位性のない戦略を実戦で使ってしまうことにつながるため、バックテストの評価には慎重になる必要があります。

次回について

今回の記事では、バックテストの応用編として、複数の時間軸を扱うマルチタイムフレーム戦略、複数銘柄を扱うマルチアセット戦略、そしてパラメーターの最適化の方法について説明しました。

次回は、バックテストした戦略の評価方法について解説します。

【MT5 API シストレ開発】

本記事の執筆者:藍崎@システムトレーダー

               
本記事の執筆者:藍崎@システムトレーダー 経歴
藍崎@システムトレーダー個人投資家としてEA開発&システムトレード。
トレードに活かすためのデータサイエンス / 統計学 / 数理ファイナンス / 客観的なデータに基づくテクニカル分析 / 機械学習 / MQL5 / Python

EA(自動売買)を学びたい方へオススメコンテンツ

EA運用の注意点

OANDAではEA(自動売買)を稼働するプラットフォームMT4/MT5の基本的な使い方について、画像や動画付きで詳しく解説しています。MT4/MT5のインストールからEAの設定方法までを詳しく解説しているので、初心者の方でもスムーズにEA運用を始めることが可能です。またOANDAの口座をお持ちであれば、独自開発したオリジナルインジケーターを無料で利用することもできます。EA運用をお考えであれば、ぜひ口座開設をご検討ください。


本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。

この記事をシェアする