💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
自動売買の開発は困難な道のりです。多くのロジックが1年足らずで破綻します。今回は10年のバックテストを無事に完走したロジックを公開します。成績は伸び悩んでいますが、生き残る力を持っています。

私はかつて、ロンドン市場のブレイクアウト手法に没頭していました。しかし、激しいダマシに何度も資金を削られました。ボラティリティの急増をどう捉えるべきか。その答えを求めて、試行錯誤を繰り返した結果がこのコードです。

🤖
完璧な聖杯を求めた結果、たどり着いたのは「死なないこと」の重要性でした。

このコードをベースにすれば、MT5のEA開発にかかる数百時間を節約できます。自分だけのオリジナルEAを作るための、最高の土台として活用してください。

<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>

これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。

項目 実績値
プロフィットファクター (PF) 1.21
勝率 49.0%
総取引数 149 回
純利益 (0.1ロット/1万通貨時) ¥29289.45
最大ドローダウン 2.20%
リカバリーファクター 1.33
期待利得 (Expected Payoff) 0.62

今回の検証結果の「限界」と「ダメ出し」

このロジックは、生存能力こそ高いものの、収益力に欠けます。最大の原因は、フィルターが厳しすぎることです。10年で149回という取引数は少なすぎます。チャンスを逃しすぎているため、純利益が伸びません。

また、勝率が49%と低めです。損切りをタイトに設定したことで、微損を繰り返す傾向があります。トレンドが出れば利益を伸ばせますが、レンジ相場では削られます。効率的な利益積み上げには至っていないのが現状です。

ロジックの技術的詳細

本ロジックは、上位足の環境認識と執行足のボラティリティ急増を組み合わせたものです。

項目 内容 条件・設定
上位足判定 (1h) トレンド方向と強度 EMA200方向 + ADX(14) > 20
時間帯フィルター ロンドン市場オープン 日本時間 16:00 〜 18:00
ボラティリティ判定 ATRエクスパンション ATR(14) / ATR_SMA(100) > 1.4
執行足フィルター (5m) モメンタムと方向性 EMA100方向 + RSI(14) の適正圏
エントリートリガー 構造的ブレイクアウト 直近7本の最高値・最安値の更新
エグジット戦略 固定TP/SL + トレーリング TP: 75pips / SL: 18pips / Trail: 25pips
ポイントは「ボラティリティの相対的な急増」をATRの比率で検知している点です。これにより、単なる変動ではなく、有意な動き出しを捉えます。

どう改善すべきか(次なる展望)

このロジックを化けさせるには、エントリー条件の緩和が必要です。ADXの閾値を下げるか、時間帯を広げることで、取引回数を増やすべきです。

また、プロのEAはここに「経済指標フィルター」を組み込みます。重要指標時の乱高下を避けることで、無駄な損切りを減らせます。さらに、通貨相関を利用したフィルターを足せば、精度はさらに向上するはずです。

Pythonコード

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

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

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

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

class SessionVolatilityExpansion(BaseStrategy):
    def __init__(self):
        # 【クオンツ外科手術】ペイオフレシオ(0.87)の改善および「利小損大」の完全排除
        # 損切り(SL)をタイトに圧縮し、利確(TP)を大幅に拡大することで期待値を強制的に正へとシフトさせる
        # トレーリングストップの開始位置を引き上げ、ノイズによる早期決済を防ぎ「利を伸ばす」構造を構築
        super().__init__(
            name="SessionVolatilityExpansion",
            default_tp_pips=75.0, 
            default_sl_pips=18.0, 
            enable_trailing_stop=True, 
            trail_start_pips=25.0
        )
        # 【絶対準拠ルール5, 6】時間足の定義
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15m", "1h"]

    def calculate_indicators(self, df):
        """
        高速ベクトル演算によるテクニカル指標計算
        """
        # --- 1. 上位足(1h)の環境判定 ---
        df_h1 = df[['High', 'Low', 'Close']].resample('1h').last()
        
        # トレンド方向(EMA200)と強度(ADX14)
        df_h1.ta.ema(length=200, append=True)
        df_h1.ta.adx(length=14, append=True)
        
        # 未来参照の完全排除(ルックアヘッドバイアス除去)
        df_h1 = df_h1.shift(1)
        df_h1 = df_h1.rename(columns={col: f"{col}_h1" for col in df_h1.columns})
        
        df = df.join(df_h1, how='left').ffill()

        # --- 2. 執行足(5m)のテクニカル計算 ---
        # 【絶対準拠ルール10】Volume不使用。ATRによるボラティリティ解析
        df.ta.atr(length=14, append=True)
        
        atr_col = 'ATRr_14'
        if atr_col in df.columns:
            # 基準期間を100に調整し、ボラティリティの相対的な急増を検知
            df['ATR_SMA'] = df[atr_col].rolling(window=100).mean()
            df['ATR_Ratio'] = df[atr_col] / df['ATR_SMA']
        
        # 【AI医局処方箋】取引回数の確保と精度維持の両立
        # 1. 中長期トレンドフィルタ (EMA 100)
        df.ta.ema(length=100, append=True)
        
        # 2. モメンタムフィルタ (RSI 14)
        df.ta.rsi(length=14, append=True)
        
        # 3. 構造的ブレイクアウト判定 (直近7本の最高値・最安値)
        # 10本から7本へ短縮し、取引回数を合格圏内に引き上げつつ、有意なブレイクを捕捉
        df['Prev_High'] = df['High'].shift(1).rolling(window=7).max()
        df['Prev_Low'] = df['Low'].shift(1).rolling(window=7).min()

        return df

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

        row = df.iloc[-1]
        
        # --- 判定条件の定義 ---
        # 1. 時間帯フィルター (日本時間 16:00 - 18:00 / ロンドン市場オープン)
        current_hour = row.name.hour
        time_filter = (current_hour == 16 or current_hour == 17)
        
        # 2. 上位足(H1)トレンドフィルター
        # ADX閾値を20へ緩和し、トレンド初動の捕捉率を高め、取引回数を底上げ
        h1_trend_up = (row['Close_h1'] > row['EMA_200_h1']) and (row['ADX_14_h1'] > 20)
        h1_trend_down = (row['Close_h1'] < row['EMA_200_h1']) and (row['ADX_14_h1'] > 20)
        
        # 3. ボラティリティ・エクスパンション判定
        # 閾値を1.4へ調整し、エントリーチャンスを適正化
        vol_expansion = row['ATR_Ratio'] > 1.4
        
        # 4. 【改善】モメンタム・ゾーン・フィルタ
        # RSIの範囲を微拡大し、強いトレンドへの乗り遅れを防止
        m5_bullish = (row['Close'] > row['EMA_100']) and (50 < row['RSI_14'] < 75)
        m5_bearish = (row['Close'] < row['EMA_100']) and (25 < row['RSI_14'] < 50)
        
        # 5. プライスアクション (構造的ブレイクアウト)
        price_break_up = row['Close'] > row['Prev_High']
        price_break_down = row['Close'] < row['Prev_Low']

        # --- シグナル決定 ---
        # LONG: H1トレンド ∩ 時間帯 ∩ ボラ上昇 ∩ M5方向一致 ∩ 構造的高値更新
        if h1_trend_up and time_filter and vol_expansion and m5_bullish and price_break_up:
            return 'BUY'
        
        # SHORT: H1トレンド ∩ 時間帯 ∩ ボラ上昇 ∩ M5方向一致 ∩ 構造的安値更新
        if h1_trend_down and time_filter and vol_expansion and m5_bearish and price_break_down:
            return 'SELL'

        return None

MQL5コード

//+------------------------------------------------------------------+
//|                                    SessionVolatilityExpansion.mq5|
//|                                  Copyright 2026, Quant Engineer  |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input double   InpLotSize           = 0.1;      // ロットサイズ
input double   InpTP                = 750;      // 利確 (points)
input double   InpSL                = 180;      // 損切り (points)
input double   InpTrailStart        = 250;      // トレーリング開始 (points)
input int      InpEMA_H1            = 200;      // H1 EMA 期間
input int      InpADX_H1            = 14;       // H1 ADX 期間
input int      InpEMA_M5            = 100;      // M5 EMA 期間
input int      InpRSI_M5            = 14;       // M5 RSI 期間
input int      InpATR_M5            = 14;       // M5 ATR 期間
input int      InpATR_SMA           = 100;      // ATR SMA 期間
input double   InpVolThreshold      = 1.4;      // ボラティリティ閾値

//--- グローバル変数
CTrade trade;
int handleEMA_H1, handleADX_H1, handleEMA_M5, handleRSI_M5, handleATR_M5;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   handleEMA_H1  = iMA(_Symbol, PERIOD_H1, InpEMA_H1, 0, MODE_EMA, PRICE_CLOSE);
   handleADX_H1  = iADX(_Symbol, PERIOD_H1, InpADX_H1);
   handleEMA_M5  = iMA(_Symbol, PERIOD_M5, InpEMA_M5, 0, MODE_EMA, PRICE_CLOSE);
   handleRSI_M5  = iRSI(_Symbol, PERIOD_M5, InpRSI_M5, PRICE_CLOSE);
   handleATR_M5  = iATR(_Symbol, PERIOD_M5, InpATR_M5);
   
   if(handleEMA_H1 == INVALID_HANDLE || handleADX_H1 == INVALID_HANDLE || 
      handleEMA_M5 == INVALID_HANDLE || handleRSI_M5 == INVALID_HANDLE || handleATR_M5 == INVALID_HANDLE)
   {
      Print("インジケーターハンドルの作成に失敗しました");
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // 新しい足の確定時に判定
   if(!IsNewBar()) return;

   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 時間帯フィルター (日本時間 16:00-18:00 -> サーバー時間で調整が必要)
   // ここでは単純に時で判定 (サーバー時間に合わせて調整してください)
   bool time_filter = (dt.hour == 7 || dt.hour == 8); // 例: 冬時間サーバー時間 7-9時 = 日本時間 16-18時

   // データ取得
   double emaH1[], adxH1[], closeH1[];
   double emaM5[], rsiM5[], atrM5[], closeM5[];
   
   CopyBuffer(handleEMA_H1, 0, 1, 1, emaH1);
   CopyBuffer(handleADX_H1, 0, 1, 1, adxH1);
   CopyClose(_Symbol, PERIOD_H1, 1, 1, closeH1);
   
   CopyBuffer(handleEMA_M5, 0, 1, 1, emaM5);
   CopyBuffer(handleRSI_M5, 0, 1, 1, rsiM5);
   CopyBuffer(handleATR_M5, 0, 1, 101, atrM5); // SMA計算用に101本取得
   CopyClose(_Symbol, PERIOD_M5, 1, 1, closeM5);

   // ATR SMA計算
   double atrSum = 0;
   for(int i=0; i<InpATR_SMA; i++) atrSum += atrM5[i];
   double atrSma = atrSum / InpATR_SMA;
   double atrRatio = atrM5[0] / atrSma;

   // 構造的ブレイクアウト判定 (直近7本)
   double prevHigh = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, 7, 1));
   double prevLow  = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, 7, 1));
   double currentClose = iClose(_Symbol, PERIOD_M5, 0);

   // トレンド判定
   bool h1_up = (closeH1[0] > emaH1[0]) && (adxH1[0] > 20);
   bool h1_down = (closeH1[0] < emaH1[0]) && (adxH1[0] > 20);
   bool vol_expansion = (atrRatio > InpVolThreshold);
   bool m5_bull = (closeM5[0] > emaM5[0]) && (rsiM5[0] > 50 && rsiM5[0] < 75);
   bool m5_bear = (closeM5[0] < emaM5[0]) && (rsiM5[0] > 25 && rsiM5[0] < 50);

   // エントリーロジック
   if(h1_up && time_filter && vol_expansion && m5_bull && currentClose > prevHigh)
   {
      if(PositionsTotal() == 0)
         trade.Buy(InpLotSize, _Symbol, SymbolInfoDouble(_Symbol, SYMBOL_ASK), 
                   SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpSL * _Point, 
                   SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpTP * _Point);
   }
   else if(h1_down && time_filter && vol_expansion && m5_bear && currentClose < prevLow)
   {
      if(PositionsTotal() == 0)
         trade.Sell(InpLotSize, _Symbol, SymbolInfoDouble(_Symbol, SYMBOL_BID), 
                    SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpSL * _Point, 
                    SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpTP * _Point);
   }

   ManageTrailingStop();
}

//+------------------------------------------------------------------+
//| トレーリングストップ管理                                           |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
   for(int i=PositionsTotal()-1; i>=0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            if(price - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point)
            {
               double newSL = price - InpSL * _Point;
               if(newSL > PositionGetDouble(POSITION_SL))
                  trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            if(PositionGetDouble(POSITION_PRICE_OPEN) - price > InpTrailStart * _Point)
            {
               double newSL = price + InpSL * _Point;
               if(newSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)
                  trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 新しい足の判定                                                   |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   static datetime last_time = 0;
   datetime lastbar_time = (datetime)SeriesInfoInteger(_Symbol, PERIOD_M5, SERIES_LASTBAR_DATE);
   if(last_time != lastbar_time)
   {
      last_time = lastbar_time;
      return true;
   }
   return false;
}