概要:なぜこのロジックは「激動の2年」を生き残れたのか

直近2年間の相場は、急激なインフレとそれに伴う各国の金利変動により、トレンドの発生と崩壊が極めて激しい局面が続きました。多くのEAがレンジ相場での往復ビンタや、トレンド転換時の深いドローダウンに耐えられず脱落する中、本ロジックがプロフィットファクター(PF) 1.28、勝率50%という堅実な数字を維持できた理由は、「極めて厳格な環境認識(フィルター)」と「高リスクリワード設計」の組み合わせにあります。

技術的な強み

  1. 3重の時間軸フィルター (Multi-Timeframe Analysis) 単一の時間軸で判断せず、1時間足で「大方向(パーフェクトオーダー)」を確認し、15分足で「トレンドの強度(ADX)」を測定、5分足で「エントリータイミング(RSI/MACD)」を計るという階層的な構造を持っています。これにより、トレンドがない相場でのエントリーを徹底的に排除しています。

  2. ボラティリティの選別と時間帯制限 NY市場のオープン前後(JST 21:00 - 23:00)にのみ限定して動作させます。この時間帯は流動性が高く、方向感が出やすいため、トレンドフォロー戦略との相性が極めて良好です。

  3. 「押し目・戻り」の定量化 単なるトレンドフォローではなく、RSIを用いて「トレンド方向への一時的な調整(プルバック)」を待ち、MACDのヒストグラム反転でモメンタムの回帰を確認してからエントリーします。これにより、高値掴み・安値掴みのリスクを低減させています。

  4. 数学的な優位性(リスクリワード比) 勝率50%でありながら利益を残せるのは、SL 30pipsに対しTP 100pipsという、リスクリワード比 1:3.33 の設計になっているためです。さらにトレーリングストップを実装することで、トレンドが伸びた際の利益を最大化し、PFを底上げしています。

Pythonコードの公開

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

自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動を観察することが、一番の勉強になります。

👉 プロのフィルターロジックが詰まった実績EA(一本勝ち)を参考にする
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd

class NyTrendPullbackStrategy(BaseStrategy):
    """
    NY Trend Specialized Pullback Strategy
    - 1h: EMA 50/200 Perfect Order for major trend identification
    - 15min: ADX > 25 to filter out range markets
    - 5min: RSI for short-term adjustment (Pullback) + MACD reversal for entry
    - Goal: Maximize Risk-Reward ratio by entering at 'value' areas within a strong trend.
    """
    
    def __init__(self):
        # Optimized for high PF: wider TP and trailing stop to capture large moves
        super().__init__(
            name="NyTrendPullbackStrategy_V3", 
            default_tp_pips=100.0, 
            default_sl_pips=30.0, 
            enable_trailing_stop=True, 
            trail_start_pips=25.0
        )
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15min", "1h"]

    def calculate_indicators(self, df):
        # --- 5min Timeframe Indicators ---
        # MACD (12, 26, 9) -> returns DataFrame [MACD, Signal, Hist]
        macd_df = df.ta.macd(close='Close', fast=12, slow=26, signal=9)
        df['MACD_hist'] = macd_df.iloc[:, 2]
        
        # RSI (14) - used to detect pullbacks
        df['RSI'] = df.ta.rsi(close='Close', length=14)
        
        # ATR - for volatility check
        df['ATR'] = df.ta.atr(high='High', low='Low', close='Close', length=14)

        # --- Multi-Timeframe (MTF) Analysis ---
        # 15min: ADX for trend strength (Must be > 25)
        df_15m = df.resample('15min').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'})
        adx_15m = df_15m.ta.adx(high='High', low='Low', close='Close', length=14)
        # Extract ADX column (index 0) and map back to 5min index
        df['ADX_15m'] = adx_15m.iloc[:, 0].reindex(df.index, method='ffill')
        
        # 1h: EMA 50 and EMA 200 for Perfect Order
        df_1h = df.resample('1h').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'})
        ema_50_1h = df_1h.ta.ema(close='Close', length=50)
        ema_200_1h = df_1h.ta.ema(close='Close', length=200)
        df['EMA_50_1h'] = ema_50_1h.reindex(df.index, method='ffill')
        df['EMA_200_1h'] = ema_200_1h.reindex(df.index, method='ffill')

        return df

    def generate_signal(self, df):
        # Get latest and previous row for momentum acceleration check
        row = df.iloc[-1]
        prev_row = df.iloc[-2]
        
        # --- 1. Time Filter (JST 21:00 - 23:00) ---
        current_hour = df.index[-1].hour
        if not (21 <= current_hour <= 23):
            return None

        # --- 2. Market Condition Filter ---
        # ADX > 25: Ensure strong trend exists to avoid whipsaws in range
        if row['ADX_15m'] < 25:
            return None
        
        if row['ATR'] <= 0:
            return None

        # --- 3. MTF Trend Alignment (Perfect Order) ---
        # Bullish Trend: EMA 50 > EMA 200 AND Price > EMA 50
        long_trend = (row['EMA_50_1h'] > row['EMA_200_1h']) and (row['Close'] > row['EMA_50_1h'])
        # Bearish Trend: EMA 50 < EMA 200 AND Price < EMA 50
        short_trend = (row['EMA_50_1h'] < row['EMA_200_1h']) and (row['Close'] < row['EMA_50_1h'])

        # --- 4. Entry Logic (Pullback + Momentum Reversal) ---
        # BUY Signal:
        # - Major trend is Bullish
        # - Short-term RSI drops below 45 (Pullback)
        # - MACD Histogram increases from previous bar (Momentum reversal)
        buy_condition = (
            long_trend and 
            (row['RSI'] < 45) and 
            (row['MACD_hist'] > prev_row['MACD_hist'])
        )

        # SELL Signal:
        # - Major trend is Bearish
        # - Short-term RSI rises above 55 (Pullback/Correction)
        # - MACD Histogram decreases from previous bar (Momentum reversal)
        sell_condition = (
            short_trend and 
            (row['RSI'] > 55) and 
            (row['MACD_hist'] < prev_row['MACD_hist'])
        )

        if buy_condition:
            return 'BUY'
        elif sell_condition:
            return 'SELL'
        
        return None

MQL5コードへの翻訳(MT5用)

以下は、上記のPythonロジックを忠実に再現したMQL5コードです。MT5のメタエディターに貼り付けてコンパイルして使用してください。

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

#include <Trade\Trade.mqh>

//--- Input Parameters
input int      InpTP_Pips = 100;           // Take Profit (Pips)
input int      InpSL_Pips = 30;            // Stop Loss (Pips)
input int      InpTrailStart_Pips = 25;    // Trailing Stop Start (Pips)
input int      InpADX_Threshold = 25;      // ADX Trend Strength Filter
input int      InpRSI_Buy_Level = 45;      // RSI Pullback Buy Level
input int      InpRSI_Sell_Level = 55;     // RSI Pullback Sell Level

//--- Indicator Handles
int hEMA50_H1, hEMA200_H1, hADX_M15, hRSI_M5, hMACD_M5;
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // 1h EMA handles
    hEMA50_H1 = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
    hEMA200_H1 = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE);
    
    // 15m ADX handle
    hADX_M15 = iADX(_Symbol, PERIOD_M15, 14);
    
    // 5m RSI and MACD handles
    hRSI_M5 = iRSI(_Symbol, PERIOD_M5, 14, PRICE_CLOSE);
    hMACD_M5 = iMACD(_Symbol, PERIOD_M5, 12, 26, 9, PRICE_CLOSE);
    
    if(hEMA50_H1 == INVALID_HANDLE || hEMA200_H1 == INVALID_HANDLE || 
       hADX_M15 == INVALID_HANDLE || hRSI_M5 == INVALID_HANDLE || hMACD_M5 == INVALID_HANDLE)
    {
        Print("Indicator Handle Error");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    //--- Time Filter (JST 21:00 - 23:00) 
    // Note: Broker time depends on GMT. Assuming GMT+2/3 (Common for MT5 brokers)
    // JST 21:00-23:00 is roughly 13:00-15:00 Broker Time (GMT+2)
    MqlDateTime dt;
    TimeCurrent(dt);
    if(!(dt.hour >= 13 && dt.hour <= 15)) return;

    //--- Buffers
    double ema50[], ema200[], adx[], rsi[], macd_main[], macd_sig[];
    CopyBuffer(hEMA50_H1, 0, 0, 2, ema50);
    CopyBuffer(hEMA200_H1, 0, 0, 2, ema200);
    CopyBuffer(hADX_M15, 0, 0, 2, adx);
    CopyBuffer(hRSI_M5, 0, 0, 2, rsi);
    CopyBuffer(hMACD_M5, 0, 0, 2, macd_main); // MACD main line
    CopyBuffer(hMACD_M5, 1, 0, 2, macd_sig);  // MACD signal line

    double close_price = iClose(_Symbol, PERIOD_M5, 0);
    double macd_hist_curr = macd_main[0] - macd_sig[0];
    double macd_hist_prev = macd_main[1] - macd_sig[1];

    //--- Trend & Filter Logic
    bool long_trend = (ema50[0] > ema200[0]) && (close_price > ema50[0]);
    bool short_trend = (ema50[0] < ema200[0]) && (close_price < ema50[0]);
    bool strong_trend = (adx[0] > InpADX_Threshold);

    //--- Position Management
    if(PositionsTotal() == 0)
    {
        // BUY: Trend Up + RSI Pullback + MACD Reversal
        if(long_trend && strong_trend && rsi[0] < InpRSI_Buy_Level && macd_hist_curr > macd_hist_prev)
        {
            double sl = close_price - InpSL_Pips * _Point * 10;
            double tp = close_price + InpTP_Pips * _Point * 10;
            trade.Buy(0.1, _Symbol, close_price, sl, tp, "NyTrend_Buy");
        }
        // SELL: Trend Down + RSI Pullback + MACD Reversal
        else if(short_trend && strong_trend && rsi[0] > InpRSI_Sell_Level && macd_hist_curr < macd_hist_prev)
        {
            double sl = close_price + InpSL_Pips * _Point * 10;
            double tp = close_price - InpTP_Pips * _Point * 10;
            trade.Sell(0.1, _Symbol, close_price, sl, tp, "NyTrend_Sell");
        }
    }
    else
    {
        ManageTrailingStop();
    }
}

//+------------------------------------------------------------------+
//| Trailing Stop Logic                                              |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
    for(int i=PositionsTotal()-1; i>=0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            double price_current = PositionGetDouble(POSITION_PRICE_CURRENT);
            double price_open = PositionGetDouble(POSITION_PRICE_OPEN);
            double stop_loss = PositionGetDouble(POSITION_SL);
            ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(type == POSITION_TYPE_BUY)
            {
                if(price_current - price_open > InpTrailStart_Pips * _Point * 10)
                {
                    double new_sl = price_current - InpSL_Pips * _Point * 10;
                    if(new_sl > stop_loss) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                }
            }
            else if(type == POSITION_TYPE_SELL)
            {
                if(price_open - price_current > InpTrailStart_Pips * _Point * 10)
                {
                    double new_sl = price_current + InpSL_Pips * _Point * 10;
                    if(new_sl < stop_loss || stop_loss == 0) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}