ホーム » FX自動売買基礎と応用 » 【MT5 API シストレ開発④】バックテストの評価方法

【MT5 API シストレ開発④】バックテストの評価方法


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

前回は、バックテストを行うためのライブラリ「Backtrader」を使ったマルチタイムフレームやマルチアセットの戦略の作り方と、パラメーターの最適化について解説しました。

今回の記事では、バックテストした戦略を評価する方法について解説します。

適切な評価の重要性

前回までの記事でも触れましたが、システムトレード開発では「本当なら優位性がない戦略」であるにもかかわらず、あたかも優位性が高いかのようなバックテスト結果となる現象が頻繁に発生します。

優位性がある戦略とは、相場の価格変化に隠れた「繰り返し再現される特性・パターン」を捉えられている戦略のことを指します。

過去データの「再現されないノイズ」に合わせて、戦略のルールを作ったり、パラメーターを最適化してしまう状態をカーブフィッティングオーバーフィッティングといい、これらは戦略の過大評価につながってしまいます。

オーバーフィッティングをできる限り避けるためには、バックテストとその評価方法を正しく理解することが重要です。

それにより、「本当は優位性のない戦略」に大切な資金を投入してしまう事故を防ぐことができます。

pipsベースの損益

まず、バックテスト結果を適切に評価・比較するために、損益の「尺度」を揃える必要があります。

FX戦略では通貨ペアごとに損益が発生する通貨単位が異なるため、そのままでは戦略同士・期間同士の比較が直感的にできないことがあります。

  • ●EURUSD → 利益は「USD」
  • ●USDJPY → 利益は「JPY」
  • ●GBPCHF → 利益は「CHF」

このような比較不能な状態を避けるために、共通の尺度としてよく使われるのが、為替レートの最小変動単位を表す「pips」です。

  • ●円が絡まない通貨ペア(例:EURUSD)
     ○小数点第4位 = 1pips
     ○1pips = 0.0001
  • ●円が絡む通貨ペア(例:USDJPY)
     ○小数点第2位 = 1pips
     ○1pips = 0.01

このようにpipsに変換しておけば、通貨ペアが違っても「どれだけ値幅を取れた戦略か」を同じ尺度で比較できます。

取引コスト

バックテスト結果を適切に評価するには、戦略だけでなく実運用で避けられない取引コストを前提に損益を見積もる必要があります。

バックテストで考えなければならない取引コストは、取引手数料、スプレッド、スリッページなどを指します。

また、FXではマイナスのスワップポイントが発生することも多いため、マイナス方向のスワップポイントを拾うことがある戦略においては、取引コストと同様に処理する必要があります。

一回のトレードでの取引コストは少額に感じるかもしれませんが、実際には勝てる戦略が勝てなくなるケースもしばしば発生するほど、影響の大きな要素と考えておくことが重要です。

バックテスト時の対策

スプレッドやスワップポイントに関しては、高品質な過去データが手に入る場合、バックテストでも現実に近い検証になるかもしれません。

しかし、スリッページをバックテストで再現することは難しいです。

また、スプレッドなどが過去データの状況を再現できていたとしても、現在の取引環境と大きく異なる場合、その検証結果が本当に役立つかどうかは疑問が残るところです。

バックテストの目的は、相場の価格変化に隠れた「繰り返し再現される特性・パターン」を見つけることであり、過去の取引を忠実に再現することではありません。

対策案1:大きめの取引コストを想定する

バックテストでは、現在の取引環境で必要な取引コストよりも厳しい設定で行い、それに耐えうる戦略かどうかを評価します。

対策案2:戦略ごとに許容取引コストを決める

あえてバックテストの取引コストをゼロにして検証し、戦略の平均的な期待値から、「どの程度の取引コストまでなら耐えられるのか」を示す許容取引コストを測ります。

リアルタイムでの取引を行っていくときに、実際に発生する取引コストの平均額が、許容取引コストを下回っているかをこまめにチェックします。

戦略の堅牢性の評価

わずかな取引環境の変化でパフォーマンスが大きく左右される戦略は、バックテストで成績が良くても、実際の取引環境では通用しないかもしれません。

また、設定値が少し変わるだけでバックテストの結果も大きく変わる戦略は、その設定が過去データのノイズにカーブフィッティングしている可能性も考えられます。

前回の記事で、最適化の目的は「過去データにおける最も良いパラメーターを見つけること」と説明しましたが、最適化には他にも目的があり、それが戦略の堅牢性のチェックです。

【MT5 API シストレ開発④】バックテストの評価方法_01

この2つは堅牢性の高い戦略(左図)と低い戦略(右図)の最適化結果の例です。

堅牢性の高い戦略の最適化結果を見ると、最も良い結果の周辺のパラメーターも含めて全体的に損益分岐点を上回っており、「少しくらいならパラメーターがズレても負けない戦略」であることを示しています。

一方、堅牢性の低い戦略では、周辺のパラメーターの多くが損益分岐点を下回っており、「パラメーターが少しズレたら負ける戦略」となっています。

最適化結果の最良パラメーターのみに注目するのではなく、結果を全体的に評価して「堅牢性が高いか」を判断する視点も重要です。


results = []

for run in opt_results:
    strat = run[0]
    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)

x = plot_df["rsi_period"].to_numpy()
y = plot_df["final_value"].to_numpy()

# --- プロット ---
fig, ax = plt.subplots(figsize=(12, 3))
sc = ax.scatter(
    x, y,
    c=y,
    cmap="RdYlGn",
)
ax.grid(True)
cbar = fig.colorbar(sc, ax=ax, pad=0.02)
plt.tight_layout()
plt.show()

上記は前回記事の方法で最適化したあとに、可視化するためのコードです。

連続的か、離散的か?

注意点として、こうした評価方法は最適化するパラメーターが連続的な場合に限られます。

連続的なパラメーターとは、例えば移動平均線の期間や過去リターンの計算範囲など、数値が連続して変化する変数を指します。

対して、連続的でないパラメーターは離散的といい、「何曜日にトレードするか」のように値が飛び飛びのパラメーターを指します。

例えば、取引を許可する曜日を最適化パラメーターとした場合、特定の曜日だけ結果が良くても、単純に「堅牢性が低い戦略」と評価するのではなく、「特定の曜日にのみ発生するアノマリー」という離散的なパラメーターとして評価する方が適切なケースもあります。

アウトオブサンプルテスト

最適化では、戦略のパラメーターを過去データに対して総当たりで何度も評価します。

最適化によって判明した最適パラメーターは、過去データに対して後付けで見つけるものであり、今後の相場でもそのパラメーターが最適である保証はありません。

アウトオブサンプルテストは、見つけた最適パラメーターが、その後の相場で通用するのかをテストする手段となります。

【MT5 API シストレ開発④】バックテストの評価方法_02

最適化は過去データ全てを使って行うのではなく、「最適化する期間」と「テストする期間」に分けて行うことで、過去データで作成した戦略が通用するかを検証できます。

このときの「最適化する期間」をインサンプルと呼び、「テストする期間」をアウトオブサンプルと呼びます。

インサンプルでは、最適パラメーターを見つけたり、戦略の堅牢性を確認したりします。

作成した戦略の評価は、アウトオブサンプルのみで行います。

ウォークフォワード法(WF法)

【MT5 API シストレ開発④】バックテストの評価方法_03

アウトオブサンプルテストでは、テスト期間をシフトしながら行うことで、過去データで作成した戦略が相場で通用するか、現実的な流れに沿って検証できます。

このようなバックテストを、ウォークフォワード法(WF法)といいます。

WF法を行うためのコードを確認していきましょう。


import numpy as np
import pandas as pd
from dataclasses import dataclass

@dataclass
class WalkForwardTimeSplit:
    n_splits: int
    train_span: str | pd.Timedelta
    test_span: str | pd.Timedelta
    gap: str | pd.Timedelta = "0D"
    step: str | pd.Timedelta | None = None  # default = test_span

    def split(self, X: pd.DataFrame, y=None, groups=None):
        if not isinstance(X.index, pd.DatetimeIndex):
            raise TypeError("X.index must be DatetimeIndex")

        X = X.sort_index()
        idx = X.index

        train_span = pd.Timedelta(self.train_span)
        test_span  = pd.Timedelta(self.test_span)
        gap        = pd.Timedelta(self.gap)
        step       = pd.Timedelta(self.step) if self.step is not None else test_span

        t0 = idx[0]

        for i in range(self.n_splits):
            train_start = t0 + i * step
            train_end   = train_start + train_span
            test_start  = train_end + gap
            test_end    = test_start + test_span

            tr_mask = (idx >= train_start) & (idx < train_end)
            te_mask = (idx >= test_start) & (idx < test_end)

            if not tr_mask.any() or not te_mask.any():
                break

            yield np.where(tr_mask)[0], np.where(te_mask)[0]

    def get_n_splits(self, X=None, y=None, groups=None):
        return self.n_splits

このコードは、4本値などの時系列データをWF法に沿って分割するものです。

Backtraderを稼働させる前に元のデータを「最適化で使用する期間」と「テストで使用する期間」に分けておくことで、ウォークフォワードテストを実行できます。

Backtraderに投入する前段階の4本値のデータ(rates_df_eurusd)を、最適化3年、テスト1年のWF法によって分割してみましょう。


cv = WalkForwardTimeSplit(
    n_splits=99,
    train_span="1095D",  # 最適化3年
    test_span="365D", # テスト1年
    gap="0D",
    step="365D",
)

for fold, (tr, te) in enumerate(cv.split(rates_df_eurusd), 1):
    X_train = rates_df_eurusd.iloc[tr]
    X_test  = rates_df_eurusd.iloc[te]
    print(
        f"fold{fold}: "
        f"train {X_train.index[0]} -> {X_train.index[-1]} | "
        f"test {X_test.index[0]} -> {X_test.index[-1]}"
    )

【MT5 API シストレ開発④】バックテストの評価方法_04

「X_train」は最適化に使用する分割後のデータで、「X_test」はテストに使用する分割後のデータです。

このコードでは、それぞれの期間をprintで表示しており、想定通りに分割になっていることが確認できます。

あとは、Backtraderの使い方に沿ってX_trainで最適化してパラメーターを決定し、X_testでテストすることになります。

次回について

シリーズ第4回となるこの記事では、バックテストの評価方法について解説しました。

次回は、作成した戦略をリアルタイムで稼働するためのシステムを提案します。

【MT5 API シストレ開発】

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

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

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

EA運用の注意点

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


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

この記事をシェアする