直近2年間の激しいインフレ相場を勝ち抜いた自動売買ロジックを公開します。 このロジックはプロフィットファクター1.21を記録しました。 厳しい金利変動局面でも資産を守り抜いた実戦的な戦略です。

バックテスト資産推移 【結論】これが直近2年間を生き抜いた資産推移の証拠です。

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

結論から述べます。 上位足のトレンド方向を厳格に定義し、短期的な調整局面のみを狙ったからです。

理由は3点あります。 1つ目はマルチタイムフレーム(MTF)分析による方向性の固定です。 2つ目はADXを用いたトレンド強度の選別です。 3つ目はMACDヒストグラムによる精密な反転検知です。

具体例を挙げます。 1時間足のEMA200と15分足のEMA20が同じ方向を向いたときのみ取引します。 さらにADXが20未満のレンジ相場は完全に排除しました。 これにより、方向感のない相場での往復ビンタを回避しています。

結論として、フィルターの厳しさが生存率を高めたと言えます。 無駄なエントリーを削り、期待値の高い局面だけに絞り込む設計です。

🤖
勝ち続けるコツは「何に入らないか」を決めることにある。AIはこの真理を導き出しましたね。

戦略の技術的詳細

本ロジックの判定基準を整理しました。

項目 条件 目的
時間帯 16:00 〜 24:00 (JST) 流動性の高い欧州・NY時間を狙う
トレンド強度 ADX(14) $\ge$ 20 レンジ相場での損失を回避する
大局トレンド 1h Close $\gtrless$ EMA200 長期的な方向性を固定する
中期トレンド 15m Close $\gtrless$ EMA20 中期的な押し目・戻りを確認する
エントリー MACDヒストグラムの反転 最適な反発タイミングを捉える
過熱感 RSI(14) 30〜70の間 買われすぎ・売られすぎを避ける
特筆すべきはトレーリングストップの導入です。 15pipsの利益が出た時点で追従を開始し、利益を最大化させています。

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 PullbackTrendMaster(BaseStrategy):
    def __init__(self):
        # 利確を広めに設定し、トレーリングストップで利益を最大化
        # 損切りはATR的な概念に基づき、ある程度の余裕を持たせる
        super().__init__(
            name="Pullback_Trend_Master",
            default_tp_pips=60.0,
            default_sl_pips=25.0,
            enable_trailing_stop=True,
            trail_start_pips=15.0
        )
        self.base_timeframe = "5min"
        self.vision_timeframes = ["5min", "15min", "1h"]

    def calculate_indicators(self, df):
        """
        押し目・戻りを検知するための高度なインジケーターセット
        """
        # --- 5分足(ベース足)の指標 ---
        # ADX: トレンドの強度 (20以上でトレンド相場)
        df.ta.adx(length=14, append=True)
        
        # MACD: 押し目・戻りの検知に使用 (ヒストグラムの反転を狙う)
        df.ta.macd(fast=12, slow=26, signal=9, append=True)
        
        # EMA: 短期的な方向性とサポート/レジスタンス
        df.ta.ema(length=20, append=True)
        
        # RSI: 過熱感の判定 (天井・底でのエントリーを回避)
        df.ta.rsi(length=14, append=True)

        # --- マルチタイムフレーム(MTF)分析 ---
        # 未来のカンニングを完全に排除 (.shift(1))
        
        # 15分足のトレンド方向 (EMA20)
        df_15m_close = df['Close'].resample('15min').last().shift(1)
        ema_15m = ta.ema(df_15m_close, length=20)
        df['EMA_15m'] = ema_15m.reindex(df.index, method='ffill')
        df['Close_15m'] = df_15m_close.reindex(df.index, method='ffill')

        # 1時間足のトレンド方向 (EMA200で大局的なトレンドを固定)
        df_1h_close = df['Close'].resample('1h').last().shift(1)
        ema_1h = ta.ema(df_1h_close, length=200)
        df['EMA_1h'] = ema_1h.reindex(df.index, method='ffill')
        df['Close_1h'] = df_1h_close.reindex(df.index, method='ffill')

        return df

    def generate_signal(self, df):
        """
        【戦略】上位足のトレンド方向に沿って、短期的な押し目/戻りを確認してエントリー
        """
        if len(df) < 200:
            return None

        last = df.iloc[-1]
        prev = df.iloc[-2] # 反転を検知するために1本前のデータを参照
        
        # --- 1. エントリー時間帯フィルター (16:00 - 24:00 JST) ---
        current_hour = df.index[-1].hour
        if not (16 <= current_hour < 24):
            return None

        # --- 2. 相場環境フィルター (ADX) ---
        # 弱すぎるトレンド相場ではエントリーしない
        if last.get('ADX_14', 0) < 20:
            return None

        # --- 3. MTFトレンド整合性チェック (論理積 AND で厳格化) ---
        # 1時間足(大局)と15分足(中期)が完全に一致している局面のみを狙う
        is_bullish_mtf = (last['Close_1h'] > last['EMA_1h']) and (last['Close_15m'] > last['EMA_15m'])
        is_bearish_mtf = (last['Close_1h'] < last['EMA_1h']) and (last['Close_15m'] < last['EMA_15m'])

        # --- 4. エントリートリガー (5分足での押し目・戻りの検知) ---
        # MACDヒストグラムの値を抽出
        macd_hist = last.get('MACDh_12_26_9', 0)
        prev_macd_hist = prev.get('MACDh_12_26_9', 0)
        rsi = last.get('RSI_14', 50)
        
        # BUYシグナル:
        # ① MTFが強気 
        # ② RSIが買われすぎ(70以上)ではない
        # ③ MACDヒストグラムが「負から正に転換」した瞬間(押し目からの反発)
        # ④ 価格が短期EMA20の上にある
        if is_bullish_mtf and rsi < 70:
            if prev_macd_hist < 0 and macd_hist > 0:
                if last['Close'] > last.get('EMA_20', float('inf')):
                    return 'BUY'

        # SELLシグナル:
        # ① MTFが弱気 
        # ② RSIが売られすぎ(30以下)ではない
        # ③ MACDヒストグラムが「正から負に転換」した瞬間(戻りからの反落)
        # ④ 価格が短期EMA20の下にある
        if is_bearish_mtf and rsi > 30:
            if prev_macd_hist > 0 and macd_hist < 0:
                if last['Close'] < last.get('EMA_20', float('-inf')):
                    return 'SELL'

        return None

MQL5実装コード

MT5でそのまま利用できる形式に翻訳しました。

//+------------------------------------------------------------------+
//|                                        PullbackTrendMaster.mq5   |
//|                                  Copyright 2026, AI Researcher   |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AI Researcher"
#property link      "https://yourblog.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input int      InpTP = 600;            // Take Profit (points)
input int      InpSL = 250;            // Stop Loss (points)
input int      InpTrailStart = 150;    // Trailing Start (points)
input int      InpADXPeriod = 14;      // ADX Period
input int      InpRSIPeriod = 14;      // RSI Period
input int      InpEMA_Fast = 20;       // Short-term EMA
input int      InpEMA_Mid = 20;        // 15m EMA
input int      InpEMA_Slow = 200;      // 1h EMA

//--- グローバル変数
CTrade trade;
int handleADX, handleMACD, handleRSI, handleEMA5, handleEMA15, handleEMA60;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   handleADX = iADX(_Symbol, PERIOD_M5, InpADXPeriod);
   handleMACD = iMACD(_Symbol, PERIOD_M5, 12, 26, 9, PRICE_CLOSE);
   handleRSI = iRSI(_Symbol, PERIOD_M5, InpRSIPeriod, PRICE_CLOSE);
   handleEMA5 = iMA(_Symbol, PERIOD_M5, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
   handleEMA15 = iMA(_Symbol, PERIOD_M15, InpEMA_Mid, 0, MODE_EMA, PRICE_CLOSE);
   handleEMA60 = iMA(_Symbol, PERIOD_H1, InpEMA_Slow, 0, MODE_EMA, PRICE_CLOSE);

   if(handleADX == INVALID_HANDLE || handleMACD == INVALID_HANDLE || handleRSI == INVALID_HANDLE)
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   //--- 時間フィルター (16:00 - 24:00 JST)
   MqlDateTime dt;
   TimeCurrent(dt);
   if(dt.hour < 16 || dt.hour >= 24) return;

   //--- インジケーター値の取得
   double adx[], macd_main[], macd_sig[], rsi[], ema5[], ema15[], ema60[];
   double closeM5[], closeM15[], closeH1[];

   CopyBuffer(handleADX, 0, 0, 2, adx);
   CopyBuffer(handleMACD, 0, 0, 2, macd_main);
   CopyBuffer(handleRSI, 0, 0, 2, rsi);
   CopyBuffer(handleEMA5, 0, 0, 2, ema5);
   CopyBuffer(handleEMA15, 0, 0, 2, ema15);
   CopyBuffer(handleEMA60, 0, 0, 2, ema60);
   
   CopyClose(_Symbol, PERIOD_M5, 0, 1, closeM5);
   CopyClose(_Symbol, PERIOD_M15, 0, 1, closeM15);
   CopyClose(_Symbol, PERIOD_H1, 0, 1, closeH1);

   //--- エントリー条件判定
   bool isBullishMTF = (closeH1[0] > ema60[0]) && (closeM15[0] > ema15[0]);
   bool isBearishMTF = (closeH1[0] < ema60[0]) && (closeM15[0] < ema15[0]);

   if(PositionsTotal() == 0 && adx[0] >= 20)
   {
      // BUY: MTF強気 & RSI < 70 & MACD反転 & 価格 > EMA20
      if(isBullishMTF && rsi[0] < 70 && macd_main[1] < 0 && macd_main[0] > 0 && closeM5[0] > ema5[0])
      {
         double sl = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point;
         double tp = SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpTP * _Point;
         trade.Buy(0.1, _Symbol, 0, sl, tp, "Pullback Buy");
      }
      // SELL: MTF弱気 & RSI > 30 & MACD反転 & 価格 < EMA20
      else if(isBearishMTF && rsi[0] > 30 && macd_main[1] > 0 && macd_main[0] < 0 && closeM5[0] < ema5[0])
      {
         double sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point;
         double tp = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpTP * _Point;
         trade.Sell(0.1, _Symbol, 0, sl, tp, "Pullback Sell");
      }
   }

   //--- トレーリングストップ
   ManageTrailingStop();
}

void ManageTrailingStop()
{
   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) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            if(openPrice - currentPrice > InpTrailStart * _Point)
            {
               double newSL = currentPrice + InpTrailStart * _Point;
               if(newSL < stopLoss || stopLoss == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}