直近2年間の激しい相場変動を勝ち抜いたロジックが完成しました。インフレや金利変動に強い設計を追求しています。

バックテスト資産推移

これが直近2年間を生き抜いた資産推移の証拠です。(※本ロジックは ユーロドルの5分足 専用です)

項目 実績値
プロフィットファクター (PF) 1.24
勝率 45.7%
総取引数 116 回
純利益 (0.1ロット/1万通貨時) $154.83
最大ドローダウン 1.35%

なぜこのロジックは生き残れたのか

結論から述べます。トレンド方向への押し目買いと戻り売りに絞ったからです。

理由は3点あります。1つ目はマルチタイムフレーム分析で大きな流れを把握していることです。2つ目はADXを用いて強いトレンド相場での逆張りを避けている点です。3つ目はロンドン市場の活発な時間帯のみに限定して取引していることです。

具体例を挙げます。15分足が上昇トレンドのときだけ、5分足の調整局面で買いを仕掛けます。これにより、根拠のない逆張りを排除できました。

結論として、環境認識とタイミングの組み合わせが機能した結果と言えます。

🤖
最大ドローダウンが1.35%という低さに抑えられている点が、精神的な余裕を生みますね。

戦略の技術的詳細

ロジックの具体的な条件を整理しました。

項目 設定・条件
メイン時間足 5分足
トレンドフィルタ 15分足 SMA(200)
相場環境判定 ADX(14) < 30 (レンジ〜緩やかなトレンド)
エントリー条件(Buy) 15分足上昇中 + (BB下限突破 または RSI < 35) + ストキャスGC
エントリー条件(Sell) 15分足下降中 + (BB上限突破 または RSI > 65) + ストキャスDC
利確 / 損切 TP 30pips / SL 15pips
特殊機能 トレーリングストップ (8pipsから開始)
取引時間 JST 15:00 〜 20:00
この戦略の核心は「上位足のトレンドに従いながら、下位足の反発を狙う」というシンプルな構造にあります。

公開Pythonコード

AIが生成したベースロジックのコードです。

🔍 プロはどうやってダマシを回避しているのか?

今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。

👉 プロのフィルターロジックが詰まった実績EA(Pips_miner_EA)を参考にする

from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd

class MeanReversionStrategy(BaseStrategy):
    """
    改善版ミーン・リバージョン戦略 v3
    KeyErrorを完全に回避するため、pandas_taの戻り値を位置ベースで抽出して明示的に命名。
    取引頻度を確保しつつ、MTFフィルタで優位性を担保したロジック。
    """
    def __init__(self):
        # トレーリングストップを有効にし、利益を最大限に伸ばす
        super().__init__(
            name="Robust_MeanReversion_London_v3",
            default_tp_pips=30.0,
            default_sl_pips=15.0,
            enable_trailing_stop=True,
            trail_start_pips=8.0
        )
        self.base_timeframe = "5min"
        self.vision_timeframes = ["5min", "15min", "1h"]

    def calculate_indicators(self, df):
        # --- 1. メイン時間足 (5min) のインジケーター ---
        # pandas_taの自動命名によるKeyErrorを避けるため、戻り値を直接受け取り、明示的なカラム名で代入する
        
        # ボリンジャーバンド (20, 2.0) -> [BBL, BBM, BBU] の順で返る
        bbands = df.ta.bbands(length=20, std=2)
        if bbands is not None:
            df['BBL'] = bbands.iloc[:, 0]
            df['BBU'] = bbands.iloc[:, 2]

        # RSI (14)
        df['RSI'] = df.ta.rsi(length=14)

        # ストキャスティクス (14, 3, 3) -> [STOCHk, STOCHd] の順で返る
        stoch = df.ta.stoch(k=14, d=3, smooth_k=3)
        if stoch is not None:
            df['STOCHk'] = stoch.iloc[:, 0]
            df['STOCHd'] = stoch.iloc[:, 1]

        # ADX (14) -> [ADX, DMP, DMN] の順で返る
        adx_df = df.ta.adx(length=14)
        if adx_df is not None:
            df['ADX'] = adx_df.iloc[:, 0]

        # --- 2. マルチタイムフレーム(MTF)分析 ---
        # 未来のカンニング(ルックアヘッドバイアス)を完全に防止するため、必ず .shift(1) を追加
        # 15分足のトレンド判定 (SMA 200)
        resampled_15 = df['Close'].resample('15min').last().shift(1)
        sma_15 = ta.sma(resampled_15, length=200)
        # 元のDataFrameのインデックスに合わせてリインデックス(ベクトル演算用)
        df['SMA_15'] = sma_15.reindex(df.index, method='ffill')

        return df

    def generate_signal(self, df):
        """
        最新の行に基づいて売買シグナルを生成
        """
        if len(df) < 200:
            return None

        # 最新データの抽出
        last = df.iloc[-1]
        
        # 必要なカラムが存在するかチェック(計算失敗時の安全策)
        required_cols = ['BBL', 'BBU', 'RSI', 'STOCHk', 'STOCHd', 'ADX', 'SMA_15']
        if any(col not in df.columns for col in required_cols):
            return None

        # 1. 時間帯フィルタ (JST 15:00 - 20:00)
        # ロンドン市場のオープンから欧州市場の活発な時間帯をカバー
        current_hour = df.index[-1].hour
        if not (15 <= current_hour <= 19): 
            return None

        # 2. 相場環境フィルタ (ADX)
        # ADX < 30 でレンジ相場、または緩やかなトレンド相場と判定して逆張りを許可
        is_range = last['ADX'] < 30

        # 3. MTFトレンドフィルタ (15分足 SMA200)
        # 15分足のトレンド方向にのみ逆張りを仕掛ける(押し目買い・戻り売り)
        is_bullish_mtf = last['Close'] > last['SMA_15']
        is_bearish_mtf = last['Close'] < last['SMA_15']

        # --- エントリーロジック ---

        # BUY シグナル:
        # - レンジ相場である
        # - 15分足が上昇トレンドである
        # - (価格がBB下限を突破) または (RSIが売られすぎ水準 < 35)
        # - ストキャスティクスでゴールデンクロスが発生(反転のトリガー)
        if is_range and is_bullish_mtf:
            overextended_buy = (last['Close'] < last['BBL']) or (last['RSI'] < 35)
            trigger_buy = last['STOCHk'] > last['STOCHd']
            
            if overextended_buy and trigger_buy:
                return 'BUY'

        # SELL シグナル:
        # - レンジ相場である
        # - 15分足が下降トレンドである
        # - (価格がBB上限を突破) または (RSIが買われすぎ水準 > 65)
        # - ストキャスティクスでデッドクロスが発生(反転のトリガー)
        if is_range and is_bearish_mtf:
            overextended_sell = (last['Close'] > last['BBU']) or (last['RSI'] > 65)
            trigger_sell = last['STOCHk'] < last['STOCHd']
            
            if overextended_sell and trigger_sell:
                return 'SELL'

        return None

MT5用MQL5ソースコード

上記ロジックをMT5で動作するように翻訳しました。そのままコンパイルして利用可能です。

//+------------------------------------------------------------------+
//|                               Robust_MeanReversion_London_v3.mq5 |
//|                                  Copyright 2026, System Trader   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

// 入力パラメータ
input double   InpLotSize = 0.1;          // ロットサイズ
input int      InpTP = 300;               // 利確 (points)
input int      InpSL = 150;               // 損切 (points)
input int      InpTrailStart = 80;        // トレーリング開始 (points)
input int      InpSMA_Period = 200;       // 15分足SMA期間
input int      InpADX_Period = 14;        // ADX期間
input int      InpRSI_Period = 14;        // RSI期間
input int      InpBB_Period = 20;         // BB期間
input double   InpBB_Dev = 2.0;           // BB偏差

// ハンドル
int hSMA_15, hADX, hRSI, hStoch, hBB;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // 15分足のSMAハンドル
   hSMA_15 = iMA(_Symbol, PERIOD_M15, InpSMA_Period, 0, MODE_SMA, PRICE_CLOSE);
   // 5分足のインジケーターハンドル
   hADX    = iADX(_Symbol, PERIOD_M5, InpADX_Period);
   hRSI    = iRSI(_Symbol, PERIOD_M5, InpRSI_Period, PRICE_CLOSE);
   hStoch  = iStochastic(_Symbol, PERIOD_M5, 14, 3, 3, MODE_SMA, STO_LOWHIGH);
   hBB     = iBands(_Symbol, PERIOD_M5, InpBB_Period, 0, InpBB_Dev, PRICE_CLOSE);

   if(hSMA_15 == INVALID_HANDLE || hADX == INVALID_HANDLE || hRSI == INVALID_HANDLE || hStoch == INVALID_HANDLE || hBB == INVALID_HANDLE)
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // 時間帯フィルタ (JST 15:00-20:00 => サーバー時間に合わせて調整が必要)
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   // 例としてサーバー時間 08:00-13:00 (GMT+2/3想定)
   if(!(dt.hour >= 8 && dt.hour <= 12)) return;

   // データ取得
   double sma15[], adx[], rsi[], stochK[], stochD[], bbUpper[], bbLower[], close[];
   CopyBuffer(hSMA_15, 0, 1, 1, sma15);
   CopyBuffer(hADX, 0, 1, 1, adx);
   CopyBuffer(hRSI, 0, 1, 1, rsi);
   CopyBuffer(hStoch, 0, 1, 2, stochK);
   CopyBuffer(hStoch, 1, 1, 2, stochD);
   CopyBuffer(hBB, 1, 1, 1, bbUpper);
   CopyBuffer(hBB, 2, 1, 1, bbLower);
   CopyClose(_Symbol, PERIOD_M5, 1, 1, close);

   bool isRange = (adx[0] < 30);
   bool isBullishMTF = (close[0] > sma15[0]);
   bool isBearishMTF = (close[0] < sma15[0]);

   // ポジション確認
   if(PositionsTotal() == 0)
   {
      // BUY条件
      if(isRange && isBullishMTF)
      {
         if((close[0] < bbLower[0] || rsi[0] < 35) && stochK[0] > stochD[0])
         {
            TradeOpen(ORDER_TYPE_BUY);
         }
      }
      // SELL条件
      if(isRange && isBearishMTF)
      {
         if((close[0] > bbUpper[0] || rsi[0] > 65) && stochK[0] < stochD[0])
         {
            TradeOpen(ORDER_TYPE_SELL);
         }
      }
   }
   
   ApplyTrailingStop();
}

// 注文関数
void TradeOpen(ENUM_ORDER_TYPE type)
{
   MqlTradeRequest request = {};
   MqlTradeResult result = {};
   
   double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = (type == ORDER_TYPE_BUY) ? price - InpSL * _Point : price + InpSL * _Point;
   double tp = (type == ORDER_TYPE_BUY) ? price + InpTP * _Point : price - InpTP * _Point;

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = InpLotSize;
   request.type = type;
   request.price = price;
   request.sl = sl;
   request.tp = tp;
   request.deviation = 10;
   request.magic = 123456;
   request.type_filling = ORDER_FILLING_IOC;

   OrderSend(request, result);
}

// トレーリングストップ関数
void ApplyTrailingStop()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
         double stopLoss = PositionGetDouble(POSITION_SL);
         
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            if(currentPrice - openPrice > InpTrailStart * _Point)
            {
               double newSL = currentPrice - InpTrailStart * _Point;
               if(newSL > stopLoss)
               {
                  MqlTradeRequest req = {}; MqlTradeResult res = {};
                  req.action = TRADE_ACTION_SLTP;
                  req.position = ticket;
                  req.sl = newSL;
                  req.tp = PositionGetDouble(POSITION_TP);
                  OrderSend(req, res);
               }
            }
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            if(openPrice - currentPrice > InpTrailStart * _Point)
            {
               double newSL = currentPrice + InpTrailStart * _Point;
               if(newSL < stopLoss || stopLoss == 0)
               {
                  MqlTradeRequest req = {}; MqlTradeResult res = {};
                  req.action = TRADE_ACTION_SLTP;
                  req.position = ticket;
                  req.sl = newSL;
                  req.tp = PositionGetDouble(POSITION_TP);
                  OrderSend(req, res);
               }
            }
         }
      }
   }
}