💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
自動売買の開発は困難な道です。多くの人が最適化の罠に陥り、実運用で破綻します。今回は10年のバックテストを破綻せず生き抜いたロジックを公開します。成績は決して派手ではありません。しかし、過酷な相場を耐え抜いた強靭な土台を持っています。

私はかつてブレイクアウト手法に心血を注ぎました。しかし、ロンドン時間直後の「だまし」に何度も資金を削られました。単純な高値更新では勝てないこと。そして、ボラティリティの定量化が不可欠であることに気づいたのです。試行錯誤の末に辿り着いたのが、この厳格なフィルタリングロジックです。

🤖
派手な利益を追うのではなく、まずは「生き残ること」に特化した設計にしました。

このコードをベースにすれば、MT5のEA開発にかかる数百時間を節約できます。ゼロから構築する苦労は不要です。自分だけのオリジナルEAを作るための、最高の研究素材として活用してください。

<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>

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

項目 実績値
プロフィットファクター (PF) 1.51
勝率 51.7%
総取引数 145 回
純利益 (0.1ロット/1万通貨時) ¥78780.87
最大ドローダウン 2.71%
リカバリーファクター 2.91
期待利得 (Expected Payoff) 0.73

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

このロジックは生存能力に特化しています。その代償として、取引回数が極端に少ない点が弱点です。10年で145回という数字は、機会損失が非常に大きいことを意味します。

PF 1.51という数字も、プロの視点では平凡な成績です。ADXの閾値を30まで引き上げたため、多くのチャンスを捨てました。また、固定ピップスの利確設定は、相場のボラティリティ変化に柔軟に対応できていません。結果として、大きなトレンドを最後まで取り切れない局面が散見されます。

ロジックの技術的詳細

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

項目 設定・条件 役割
環境認識 (1h) EMA(50) > EMA(200) かつ 価格 > EMA(50) 上位足のパーフェクトオーダー判定
トレンド強度 (1h) ADX(14) > 30 明確なトレンド発生の確認
レンジ算出 (5m) 09:00 〜 15:00 JST の高値・安値 アジアレンジの定義
執行時間 (5m) 16:00 〜 19:00 JST ロンドン市場オープン後の捕捉
ボラティリティ ATR(14) > ATR_SMA(80) * 1.8 異常な価格変動(スパイク)の検知
エントリー レンジ突破 + ATRバッファ(10%) だましを排除したブレイクアウト
クオンツ視点のポイント:単なる価格のブレイクではなく、「トレンド強度」と「ボラティリティの急増」が同時に起きた瞬間のみを抽出しています。これが低ドローダウンの正体です。

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

この無料コードをベースに、以下の改造を試みる価値があります。

  • 利確の動的化:固定の85pipsではなく、ATRに基づいた変動利確を導入してください。
  • フィルターの緩和:ADXの閾値を25まで下げ、取引回数を増やして期待値を検証すること。
  • 時間軸の追加:15分足でのトレンドフィルターを重ねれば、さらに精度が高まるはずです。

プロのEAは、ここに独自の「通貨強弱」や「経済指標フィルタ」を組み込んでいます。この堅牢な土台に、あなただけの視点を加えてみてください。

Pythonコード

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

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

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

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

class LondonBreakoutStrategy(BaseStrategy):
    def __init__(self):
        # 【クオンツ処方箋:RFおよびPFの劇的改善】
        # リカバリーファクター(RF)を3.0以上に引き上げるため、リスクリワード比を最適化。
        # 利確(TP)を85pipsへ伸長し、損切(SL)を30pipsにタイト化することで、
        # 1トレードあたりの期待利得(Payoff)を最大化し、少ないドローダウンで純利を積み上げる構成とする。
        super().__init__(
            name="LondonBreakoutMomentum_QuantOptimized", 
            default_tp_pips=85.0, 
            default_sl_pips=30.0, 
            enable_trailing_stop=True, 
            trail_start_pips=20.0
        )
        # 執行足:5分足で精密なエントリータイミングを捕捉
        self.base_timeframe = "5min"
        # 画像認識AI用時間足
        self.vision_timeframes = ["5min", "15min", "1h"]

    def calculate_indicators(self, df):
        """
        高速ベクトル処理によるテクニカル計算。
        上位足のトレンド判定に二重EMAフィルタを導入し、偽シグナル(False Signal)を徹底排除。
        """
        # --- 1. 上位足(1h)の環境認識 (ルックアヘッドバイアスの完全排除) ---
        resampled_1h = df.resample('1h').agg({
            'Open': 'first', 
            'High': 'max', 
            'Low': 'min', 
            'Close': 'last'
        }).shift(1)
        
        df_1h_close = resampled_1h['Close'].reindex(df.index, method='ffill')
        df_1h_high = resampled_1h['High'].reindex(df.index, method='ffill')
        df_1h_low = resampled_1h['Low'].reindex(df.index, method='ffill')
        
        # 【改善】EMA(50)とEMA(200)の二重フィルタ。
        # 短期・長期の方向性が完全に一致した「パーフェクトオーダー」状態のみを抽出。
        df['ema_1h_fast'] = ta.ema(df_1h_close, length=50)
        df['ema_1h_slow'] = ta.ema(df_1h_close, length=200)
        
        # ADX(14)によるトレンド強度の定量化
        adx_df = ta.adx(df_1h_high, df_1h_low, df_1h_close, length=14)
        df['adx_1h'] = adx_df['ADX_14'] if adx_df is not None else None

        # --- 2. 5分足の指標計算 (出来高不使用・ATRベース) ---
        df['atr'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
        # ATRの平滑化期間を調整し、ボラティリティの変化に対する感度を最適化
        df['atr_sma'] = ta.sma(df['atr'], length=80)

        # --- 3. アジアレンジの算出 (09:00 - 15:00 JST) ---
        asian_mask = (df.index.hour >= 9) & (df.index.hour < 15)
        df['temp_high'] = df['High'].where(asian_mask)
        df['temp_low'] = df['Low'].where(asian_mask)
        
        # transformを用いて日付ごとのレンジをベクトル展開
        df['asian_high'] = df.groupby(df.index.date)['temp_high'].transform('max')
        df['asian_low'] = df.groupby(df.index.date)['temp_low'].transform('min')
        
        df.drop(columns=['temp_high', 'temp_low'], inplace=True)
        
        return df

    def generate_signal(self, df):
        """
        最新のデータに基づき売買シグナルを判定。
        フィルタ閾値を厳格化し、勝率×ペイオフ期待値スコアの向上を図る。
        """
        if len(df) < 200:
            return None

        last = df.iloc[-1]
        current_hour = last.name.hour

        # 【時間的制約】ロンドンオープン後の取引窓 (16:00 - 19:00 JST)
        if not (16 <= current_hour <= 19):
            return None

        # 判定用変数の抽出
        close = last['Close']
        atr = last['atr']
        atr_sma = last['atr_sma']
        a_high = last['asian_high']
        a_low = last['asian_low']
        ema_fast = last['ema_1h_fast']
        ema_slow = last['ema_1h_slow']
        adx_1h = last['adx_1h']

        # NaNチェック(欠損値がある場合はエントリー不可)
        if pd.isna([atr, atr_sma, a_high, a_low, ema_fast, ema_slow, adx_1h]).any():
            return None

        # --- 【クオンツ・パラメータ調整】 ---
        
        # 1. ボラティリティ・スパイク判定の厳格化
        # 閾値を1.5 -> 1.8倍へ引き上げ、低ボラティリティ局面でのだましを排除
        volatility_expansion = atr > (atr_sma * 1.8)
        
        # 2. トレンド強度の閾値引き上げ
        # ADX 25 -> 30。明確なトレンドが発生している局面のみに限定しPFを向上
        strong_trend = adx_1h > 30

        # 3. パーフェクトオーダー条件 (上位足の方向性一致)
        # 単なるEMA超えではなく、EMAの並び順まで条件に加える
        bullish_trend = (close > ema_fast) and (ema_fast > ema_slow)
        bearish_trend = (close < ema_fast) and (ema_fast < ema_slow)

        # 4. ブレイクアウトの確信度向上 (ATRバッファの導入)
        # 価格がレンジ高値/安値をATRの10%分明確に突き抜けたことを確認
        breakout_buffer = atr * 0.1
        long_breakout = close > (a_high + breakout_buffer)
        short_breakout = close < (a_low - breakout_buffer)

        # 【最終エントリー判定】
        # Long: 上位足上昇トレンド 且つ トレンド強 且つ 高値ブレイク 且つ ボラティリティ急増
        if bullish_trend and strong_trend and long_breakout and volatility_expansion:
            return 'BUY'

        # Short: 上位足下降トレンド 且つ トレンド強 且つ 安値ブレイク 且つ ボラティリティ急増
        if bearish_trend and strong_trend and short_breakout and volatility_expansion:
            return 'SELL'

        return None

MQL5コード

//+------------------------------------------------------------------+
//|                                LondonBreakoutMomentum_Quant.mq5  |
//|                                  Copyright 2026, Quant Engineer  |
//|                                             https://your-site.com|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link      "https://your-site.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input double   InpTP = 85.0;              // Take Profit (pips)
input double   InpSL = 30.0;              // Stop Loss (pips)
input double   InpTrailingStart = 20.0;   // Trailing Start (pips)
input int      InpEMA_Fast = 50;          // Fast EMA Period (1h)
input int      InpEMA_Slow = 200;         // Slow EMA Period (1h)
input int      InpADX_Period = 14;        // ADX Period (1h)
input int      InpATR_Period = 14;        // ATR Period (5m)
input int      InpATR_SMA_Period = 80;    // ATR SMA Period (5m)

//--- グローバル変数
int      handle_ema_fast;
int      handle_ema_slow;
int      handle_adx;
int      handle_atr;
CTrade   trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // 1時間足のハンドル取得
    handle_ema_fast = iMA(_Symbol, PERIOD_H1, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
    handle_ema_slow = iMA(_Symbol, PERIOD_H1, InpEMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
    handle_adx      = iADX(_Symbol, PERIOD_H1, InpADX_Period);
    
    // 5分足のハンドル取得
    handle_atr      = iATR(_Symbol, PERIOD_M5, InpATR_Period);
    
    if(handle_ema_fast == INVALID_HANDLE || handle_ema_slow == INVALID_HANDLE || 
       handle_adx == INVALID_HANDLE || handle_atr == INVALID_HANDLE)
    {
        Print("ハンドル取得に失敗しました");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    MqlDateTime dt;
    TimeCurrent(dt);
    
    // 執行時間判定 (16:00 - 19:00 JST) ※サーバー時間に合わせて調整が必要
    // ここでは簡易的にHourで判定
    if(!(dt.hour >= 16 && dt.hour <= 19)) return;

    if(PositionsTotal() > 0) 
    {
        ManageTrailingStop();
        return;
    }

    // 指標データの取得
    double ema_fast[], ema_slow[], adx[], atr[];
    ArraySetAsSeries(ema_fast, true);
    ArraySetAsSeries(ema_slow, true);
    ArraySetAsSeries(adx, true);
    ArraySetAsSeries(atr, true);

    if(CopyBuffer(handle_ema_fast, 0, 1, 1, ema_fast) <= 0) return;
    if(CopyBuffer(handle_ema_slow, 0, 1, 1, ema_slow) <= 0) return;
    if(CopyBuffer(handle_adx, 0, 1, 1, adx) <= 0) return;
    if(CopyBuffer(handle_atr, 0, 0, InpATR_SMA_Period + 1, atr) <= 0) return;

    // ATR SMAの計算
    double atr_sum = 0;
    for(int i=0; i<InpATR_SMA_Period; i++) atr_sum += atr[i];
    double atr_sma = atr_sum / InpATR_SMA_Period;

    // アジアレンジの計算 (09:00 - 15:00 JST)
    double asian_high = -1.0;
    double asian_low = 999999.0;
    
    MqlRates rates[];
    ArraySetAsSeries(rates, true);
    int copied = CopyRates(_Symbol, PERIOD_M5, 0, 288, rates); // 直近24時間分
    
    for(int i=0; i<copied; i++)
    {
        MqlDateTime r_dt;
        TimeToStruct(rates[i].time, r_dt);
        if(r_dt.hour >= 9 && r_dt.hour < 15)
        {
            if(rates[i].high > asian_high) asian_high = rates[i].high;
            if(rates[i].low < asian_low) asian_low = rates[i].low;
        }
    }

    double close = iClose(_Symbol, PERIOD_M5, 0);
    double current_atr = atr[0];
    double breakout_buffer = current_atr * 0.1;

    // 条件判定
    bool volatility_expansion = current_atr > (atr_sma * 1.8);
    bool strong_trend = adx[0] > 30;
    bool bullish_trend = (close > ema_fast[0]) && (ema_fast[0] > ema_slow[0]);
    bool bearish_trend = (close < ema_fast[0]) && (ema_fast[0] < ema_slow[0]);
    bool long_breakout = close > (asian_high + breakout_buffer);
    bool short_breakout = close < (asian_low - breakout_buffer);

    // エントリー実行
    if(bullish_trend && strong_trend && long_breakout && volatility_expansion)
    {
        double sl = close - InpSL * _Point * 10;
        double tp = close + InpTP * _Point * 10;
        trade.Buy(0.1, _Symbol, close, sl, tp, "LondonBreakout_Long");
    }
    else if(bearish_trend && strong_trend && short_breakout && volatility_expansion)
    {
        double sl = close + InpSL * _Point * 10;
        double tp = close - InpTP * _Point * 10;
        trade.Sell(0.1, _Symbol, close, sl, tp, "LondonBreakout_Short");
    }
}

//+------------------------------------------------------------------+
//| Trailing Stop Management                                         |
//+------------------------------------------------------------------+
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);
                double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
                double current_sl = PositionGetDouble(POSITION_SL);
                if(price - open_price > InpTrailingStart * _Point * 10)
                {
                    double new_sl = price - InpTrailingStart * _Point * 10;
                    if(new_sl > current_sl) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
                double current_sl = PositionGetDouble(POSITION_SL);
                if(open_price - price > InpTrailingStart * _Point * 10)
                {
                    double new_sl = price + InpTrailingStart * _Point * 10;
                    if(new_sl < current_sl || current_sl == 0) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}