💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
今回は、8年半のバックテストを破綻せずに生き抜いたロジックをご紹介します。 長期検証をクリアしたものの、成績が伸び悩んだ点に課題が残る設計です。 完璧なシステムではなく、研究用の素材としてご活用ください。

バックテスト資産推移

これが過酷な全期間8年半の市場変動を破綻せず生き抜いた資産推移グラフ(長期検証証拠)です。

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

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

このロジックの最大の弱点は、取引回数の少なさです。 8年半で87回という数字は、機会損失が非常に多いことを意味します。 PF1.20という数値も、控えめな成績と言わざるを得ません。

原因は、フィルター条件を厳しくしすぎたことです。 特に時間帯制限とADXの閾値設定が、多くのチャンスを切り捨てています。 結果として、ドローダウンは極めて低いものの、資産増加のスピードは緩やかになりました。 いわば「守りすぎて攻めきれていない」状態です。

🤖
破綻しないことは証明されましたが、これでは運用効率が悪すぎますね。

ロジックの技術的詳細

本ロジックは、NY時間の初動を狙うモメンタム戦略です。 上位足の環境認識と、下位足のブレイクアウトを組み合わせています。

項目 設定内容
ベース時間足 5分足
環境認識足 1時間足(EMA20 / ADX14)
トレンド判定 ADX > 25 かつ EMA20との位置関係
エントリー条件 30本高値/安値更新 + Supertrend一致 + RSI範囲内
フィルター 日本時間 21:00 〜 23:00 / ATRボラティリティ上昇
キャンドル判定 実体サイズがATRの0.5倍以上かつヒゲが短いこと
決済ルール 固定TP/SL + トレーリングストップ

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

このロジックを「化けさせる」には、フィルターの最適化が必要です。 例えば、時間帯制限をロンドン市場の時間帯まで広げる方法が考えられます。 また、固定のTP/SLではなく、ATRに基づいた変動幅を採用すべきです。

プロのEAは、単なる指標の組み合わせではなく「ボラティリティの質」を判定しています。 現在の固定条件に、通貨強弱などの外部データを加えれば、取引回数とPFを同時に向上させられる可能性があります。

まずは提供するコードをベースに、ご自身で条件を緩めて検証してみてください。 研究素材としての価値は十分にあるはずです。

公開コード(Python)

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

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

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

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

class NYInstitutionalMomentum(BaseStrategy):
    def __init__(self):
        # 【クオンツ外科手術】PF 1.14 -> 1.20+ および リカバリーファクター 1.49 -> 3.00+ への引き上げ
        # 最大DDが非常に低く抑えられているため、リスク許容度を微増させつつ、
        # 期待値を最大化させる「損小利大」の比率をさらに最適化。
        # TPを伸ばし、トレーリングストップの開始点を遅らせることで、トレンドの最大幅を取り切る設計に変更。
        super().__init__(
            name="NY_Institutional_Momentum_Burst_V4",
            default_tp_pips=75.0, 
            default_sl_pips=22.0,
            enable_trailing_stop=True,
            trail_start_pips=25.0 
        )
        # 【絶対準拠ルール5】ベース時間足の定義
        self.base_timeframe = "5m"
        # 【絶対準拠ルール6】画像認識AI用時間足の定義
        self.vision_timeframes = ["5m", "15m", "1h"]

    def calculate_indicators(self, df):
        """
        ベクトル演算による高速計算。
        「だまし」を排除するため、トレンド強度(ADX)の閾値引き上げと、
        価格の「位置関係」を厳格に判定するフィルタを導入。
        """
        # --- 下位足 (5m) 指標計算 ---
        # ボラティリティ: ATR(14)
        df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
        df['ATR_MA'] = df['ATR'].rolling(window=20).mean()
        
        # トレンド確定: Supertrend (10, 3.0)
        st = ta.supertrend(df['High'], df['Low'], df['Close'], length=10, multiplier=3.0)
        df['ST_Dir'] = st['SUPERTd_10_3.0']
        
        # モメンタム: RSI(14)
        df['RSI'] = ta.rsi(df['Close'], length=14)
        
        # ブレイクアウト判定用: 直近30本(2.5時間)の高値・安値に拡大し、信頼性を向上
        df['Range_High'] = df['High'].rolling(window=30).max()
        df['Range_Low'] = df['Low'].rolling(window=30).min()

        # --- 上位足 (1h) 環境判定 ---
        # 【絶対準拠ルール7】ルックアヘッドバイアス排除 (.shift(1))
        df_1h = df[['Open', 'High', 'Low', 'Close']].resample('1h').last().shift(1)
        
        # 1h EMA(20)
        df_1h['EMA_20'] = ta.ema(df_1h['Close'], length=20)
        
        # 1h ADX(14): トレンド強度の閾値を20から25へ引き上げ、「真のトレンド」のみを抽出
        adx_df = ta.adx(df_1h['High'], df_1h['Low'], df_1h['Close'], length=14)
        df_1h['ADX'] = adx_df['ADX_14']
        
        # 上位足トレンド判定 (1: Bull, -1: Bear, 0: Range)
        df_1h['Trend_1h'] = 0
        # ADX > 25 とすることで、レンジ相場での往復ビンタ(だまし)を大幅に削減
        df_1h.loc[(df_1h['Close'] > df_1h['EMA_20']) & (df_1h['ADX'] > 25), 'Trend_1h'] = 1
        df_1h.loc[(df_1h['Close'] < df_1h['EMA_20']) & (df_1h['ADX'] > 25), 'Trend_1h'] = -1
        
        # 上位足の判定結果を下位足(5m)にマッピング
        df['Trend_1h'] = df.index.floor('1h').map(df_1h['Trend_1h'])
        
        return df

    def generate_signal(self, df):
        """
        「だまし」を排除し、利益効率を最大化させるための精密エントリーロジック。
        価格のブレイクに加え、キャンドルの「形状(ヒゲの少なさ)」を条件に追加。
        """
        if len(df) < 2:
            return None

        last = df.iloc[-1]
        
        # --- フィルター条件 ---
        # 1. 時間帯フィルター: 日本時間 21:00 〜 23:00
        current_hour = last.name.hour if hasattr(last.name, 'hour') else 0
        time_filter = (21 <= current_hour < 23)
        
        # 2. ボラティリティ・フィルター: ATR > ATR_MA * 1.1
        vol_filter = last['ATR'] > (last['ATR_MA'] * 1.1)
        
        # 3. 上位足トレンドフィルター
        trend_bull = (last['Trend_1h'] == 1)
        trend_bear = (last['Trend_1h'] == -1)

        # --- エントリートリガーの高度化 ---
        prev_range_high = df['Range_High'].iloc[-2] if len(df) > 1 else float('inf')
        prev_range_low = df['Range_Low'].iloc[-2] if len(df) > 1 else float('-inf')

        # 【キャンドル品質判定】
        # 実体がATRの0.5倍以上あり、かつ終値が足の全幅の80%以上の位置にあること
        # これにより、ブレイク後の急反転(長い上ヒゲ/下ヒゲ)によるだましを排除する。
        candle_range = last['High'] - last['Low']
        body_size = abs(last['Close'] - last['Open'])
        
        if candle_range == 0:
            return None
            
        is_strong_bull = (body_size > last['ATR'] * 0.5) and (last['Close'] >= last['Low'] + candle_range * 0.8)
        is_strong_bear = (body_size > last['ATR'] * 0.5) and (last['Close'] <= last['High'] - candle_range * 0.8)

        # Long: 上位足強気 + 時間内 + ボラ活性 + 30本高値ブレイク + Supertrend上昇 + RSI(50-70) + 強気キャンドル
        long_condition = (
            trend_bull and 
            time_filter and 
            vol_filter and 
            (last['Close'] > prev_range_high) and 
            (last['ST_Dir'] == 1) and
            (50 < last['RSI'] < 70) and 
            is_strong_bull
        )
        
        # Short: 上位足弱気 + 時間内 + ボラ活性 + 30本安値ブレイク + Supertrend下降 + RSI(30-50) + 弱気キャンドル
        short_condition = (
            trend_bear and 
            time_filter and 
            vol_filter and 
            (last['Close'] < prev_range_low) and 
            (last['ST_Dir'] == -1) and
            (30 < last['RSI'] < 50) and 
            is_strong_bear
        )

        if long_condition:
            return 'BUY'
        elif short_condition:
            return 'SELL'
        
        return None

MQL5実装コード

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

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input double   InpLotSize      = 0.1;      // ロットサイズ
input int      InpTP           = 750;      // 利確 (points)
input int      InpSL           = 220;      // 損切 (points)
input int      InpTrailStart   = 250;      // トレーリング開始 (points)
input int      InpADXThreshold = 25;       // ADX閾値
input int      InpRangePeriod  = 30;       // レンジ判定期間

//--- グローバル変数
CTrade         trade;
int            handleADX, handleEMA, handleRSI;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   handleADX = iADX(_Symbol, PERIOD_H1, 14);
   handleEMA = iMA(_Symbol, PERIOD_H1, 20, 0, MODE_EMA, PRICE_CLOSE);
   handleRSI = iRSI(_Symbol, PERIOD_M5, 14, PRICE_CLOSE);
   
   if(handleADX == INVALID_HANDLE || handleEMA == INVALID_HANDLE || handleRSI == INVALID_HANDLE)
      return(INIT_FAILED);
      
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // 時間帯フィルター (日本時間 21:00 - 23:00)
   // ※サーバー時間への変換が必要。ここでは例としてサーバー時間で判定
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   if(!(dt.hour >= 12 && dt.hour < 14)) return; // 例: GMT+2/3サーバーの場合

   // ポジション確認
   if(PositionsTotal() > 0) {
      ManageTrailingStop();
      return;
   }

   // 指標値の取得
   double adx[], ema[], rsi[];
   CopyBuffer(handleADX, 0, 1, 1, adx);
   CopyBuffer(handleEMA, 0, 1, 1, ema);
   CopyBuffer(handleRSI, 0, 0, 1, rsi);
   
   double closeH1 = iClose(_Symbol, PERIOD_H1, 1);
   double closeM5 = iClose(_Symbol, PERIOD_M5, 0);
   
   // 上位足トレンド判定
   int trendH1 = 0;
   if(adx[0] > InpADXThreshold) {
      if(closeH1 > ema[0]) trendH1 = 1;
      else if(closeH1 < ema[0]) trendH1 = -1;
   }

   // 30本高値安値の取得
   double rangeHigh = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, InpRangePeriod, 1));
   double rangeLow  = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, InpRangePeriod, 1));

   // キャンドル品質判定
   double atr = iATR(_Symbol, PERIOD_M5, 14, 0);
   double bodySize = MathAbs(iClose(_Symbol, PERIOD_M5, 0) - iOpen(_Symbol, PERIOD_M5, 0));
   double candleRange = iHigh(_Symbol, PERIOD_M5, 0) - iLow(_Symbol, PERIOD_M5, 0);
   
   bool isStrongBull = (bodySize > atr * 0.5) && (iClose(_Symbol, PERIOD_M5, 0) >= iLow(_Symbol, PERIOD_M5, 0) + candleRange * 0.8);
   bool isStrongBear = (bodySize > atr * 0.5) && (iClose(_Symbol, PERIOD_M5, 0) <= iHigh(_Symbol, PERIOD_M5, 0) - candleRange * 0.8);

   // エントリーロジック
   if(trendH1 == 1 && closeM5 > rangeHigh && rsi[0] > 50 && rsi[0] < 70 && isStrongBull) {
      double sl = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point;
      double tp = SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpTP * _Point;
      trade.Buy(InpLotSize, _Symbol, 0, sl, tp);
   }
   else if(trendH1 == -1 && closeM5 < rangeLow && rsi[0] > 30 && rsi[0] < 50 && isStrongBear) {
      double sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point;
      double tp = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpTP * _Point;
      trade.Sell(InpLotSize, _Symbol, 0, sl, tp);
   }
}

void ManageTrailingStop() {
   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)) {
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
            if(SymbolInfoDouble(_Symbol, SYMBOL_BID) - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point) {
               double newSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point;
               if(newSL > PositionGetDouble(POSITION_SL)) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         } else {
            if(PositionGetDouble(POSITION_PRICE_OPEN) - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > InpTrailStart * _Point) {
               double newSL = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point;
               if(newSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}

double iATR(string symbol, ENUM_TIMEFRAMES tf, int period, int shift) {
   int h = iATR(symbol, tf, period);
   double buf[];
   CopyBuffer(h, 0, shift, 1, buf);
   return buf[0];
}