💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
自動売買の開発は困難な道です。多くの人がカーブフィッティングの罠に落ちます。短期間の成績に惑わされ、実運用で破綻する例を数多く見てきました。今回は、10年という長期のバックテストを破綻せずに生き抜いたロジックをご紹介します。ただし、成績は決して派手ではありません。むしろ、効率面では課題が残る結果となりました。

私は以前、ブレイクアウト手法の「ダマシ」に深く悩まされました。トレンドが出ると信じてエントリーし、そのまま反転して損切りになる。この経験から、相場の「環境」を厳格に分ける手法を模索しました。レンジ相場での逆張りこそが、低リスクを実現する鍵になると確信したからです。試行錯誤を繰り返し、ようやく辿り着いたのがこの適応型平均回帰ロジックです。

🤖
完璧を求めすぎると、取引回数が極端に減る。それがこのロジックの宿命でした。

このコードをベースにすれば、MT5のEA開発に費やす数百時間を節約できます。ゼロから構築するのは時間がかかります。本ロジックを土台にして、あなただけの最適解を探ってください。

<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>

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

項目 実績値
プロフィットファクター (PF) 2.11
勝率 45.8%
総取引数 24 回
純利益 (0.1ロット/1万通貨時) ¥19235.42
最大ドローダウン 0.86%
リカバリーファクター 2.24
期待利得 (Expected Payoff) 1.15

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

この成績を見て、まず気づくのは取引数の少なさです。10年で24回という数字は、運用効率として極めて低いです。PFは2.11と高い水準にあります。しかし、機会損失が非常に大きいといえます。

原因は、フィルターを厳しくしすぎたことです。ADXによるレンジ判定と、RSIの極端な閾値設定が、エントリーチャンスを削りすぎました。結果として「負けない」仕組みにはなりましたが、「稼ぐ」仕組みとしては不十分です。低ドローダウンを実現した代償として、資金効率を犠牲にした形になります。

ロジックの技術的詳細

本ロジックは、上位足の方向性を確認した上で、執行足の過熱感を狙う戦略です。

要素 設定・条件 役割
上位足トレンド 1時間足 EMA200 環境認識(買い・売りの方向決定)
レンジ判定 ADX(14) < 20 トレンド相場での逆張りを回避
ボラティリティ ATR(14) > ATR_MA(100) * 0.6 死に相場でのエントリーを制限
過熱感判定 RSI(14) $\le$ 25 または $\ge$ 75 統計的な売られすぎ・買われすぎを検知
エントリートリガー ボリンジャーバンド $\sigma 2.5$ の回帰 バンド外から内側への回帰を確認
ポイントは「1時間足のトレンド方向にのみ、5分足の逆張りを行う」という点です。これにより、大きな流れに逆らうリスクを最小限に抑えています。

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

この無料コードは、いわば「未完成の原石」です。ここから利益を伸ばすには、いくつかの改善策が考えられます。

  • ADX閾値の動的変更: 固定値の20ではなく、相場のボラティリティに合わせて変動させると、取引回数を増やせます。
  • 時間帯フィルターの導入: 東京時間やロンドン時間など、通貨ペア特有のクセを考慮した時間制限を設けることです。
  • 利確目標の最適化: 現在は固定ピプスですが、ATRに基づいた動的な利確設定に変更すれば、期待利得が向上します。

プロのEAは、こうした環境認識をさらに詳細なロジックで補っています。この土台を改造して、あなただけの最強の武器に仕上げてください。

Pythonソースコード

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

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

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

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

class MTFRAMRStrategy(BaseStrategy):
    """
    MTF-Regime Adaptive Mean Reversion (MTF-RAMR) - Optimized Version
    """
    def __init__(self):
        # リスク管理パラメータの最適化
        super().__init__(
            name="MTF-RAMR_Optimized", 
            default_tp_pips=40.0, 
            default_sl_pips=30.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):
        # --- 1. 執行足 (5min) インジケーター計算 ---
        df.ta.bbands(length=20, std=2.5, append=True)
        df.ta.rsi(length=14, append=True)
        df.ta.adx(length=14, append=True)
        df.ta.atr(length=14, append=True)

        atr_col = [c for c in df.columns if c.startswith('ATRr')][0]
        df['atr_ma'] = df[atr_col].rolling(window=100).mean()

        # --- 2. 上位足 (1h) トレンド判定 ---
        h1_resampled = df['Close'].resample('1h').last()
        h1_ema = ta.ema(h1_resampled, length=200)
        
        h1_trend = np.where(h1_resampled > h1_ema, 1, -1)
        h1_trend_series = pd.Series(h1_trend, index=h1_resampled.index)
        
        df['h1_trend'] = h1_trend_series.shift(1).reindex(df.index, method='ffill')

        return df

    def generate_signal(self, df):
        if len(df) < 200:
            return None

        curr = df.iloc[-1]
        prev = df.iloc[-2]

        try:
            bbl_col = [c for c in df.columns if c.startswith('BBL')][0]
            bbu_col = [c for c in df.columns if c.startswith('BBU')][0]
            rsi_col = [c for c in df.columns if c.startswith('RSI')][0]
            adx_col = [c for c in df.columns if c.startswith('ADX')][0]
            atr_col = [c for c in df.columns if c.startswith('ATRr')][0]
        except IndexError:
            return None

        is_range = curr[adx_col] < 20
        is_volatile = curr[atr_col] > (curr['atr_ma'] * 0.6)
        h1_trend = curr['h1_trend']

        if h1_trend == 1 and is_range and is_volatile:
            if prev[rsi_col] <= 25 and prev['Close'] <= prev[bbl_col]:
                if curr['Close'] > curr[bbl_col]:
                    return 'BUY'

        if h1_trend == -1 and is_range and is_volatile:
            if prev[rsi_col] >= 75 and prev['Close'] >= prev[bbu_col]:
                if curr['Close'] < curr[bbu_col]:
                    return 'SELL'

        return None

MQL5コード (.mq5)

//+------------------------------------------------------------------+
//|                                            MTF_RAMR_Optimized.mq5|
//|                                  Copyright 2026, AI Researcher   |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AI Researcher"
#property link      "https://your-blog-url.com"
#property version   "1.00"
#property strict

// パラメータ設定
input int      InpBBPeriod   = 20;          // Bollinger Bands Period
input double   InpBBStdDev   = 2.5;         // Bollinger Bands StdDev
input int      InpRSIPeriod  = 14;          // RSI Period
input int      InpADXPeriod  = 14;          // ADX Period
input int      InpATRPeriod  = 14;          // ATR Period
input int      InpATRMAPeriod= 100;         // ATR MA Period
input int      InpEMAH1Period= 200;         // H1 EMA Period
input double   InpTP_Pips    = 40.0;        // Take Profit (Pips)
input double   InpSL_Pips    = 30.0;        // Stop Loss (Pips)
input double   InpTrailStart = 15.0;        // Trailing Stop Start (Pips)

// ハンドル
int handleBB, handleRSI, handleADX, handleATR, handleEMAH1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    handleBB    = iBands(_Symbol, _Period, InpBBPeriod, 0, InpBBStdDev, PRICE_CLOSE);
    handleRSI   = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
    handleADX   = iADX(_Symbol, _Period, InpADXPeriod);
    handleATR   = iATR(_Symbol, _Period, InpATRPeriod);
    handleEMAH1 = iMA(_Symbol, PERIOD_H1, InpEMAH1Period, 0, MODE_EMA, PRICE_CLOSE);

    if(handleBB == INVALID_HANDLE || handleRSI == INVALID_HANDLE || 
       handleADX == INVALID_HANDLE || handleATR == INVALID_HANDLE || handleEMAH1 == INVALID_HANDLE)
    {
        Print("インジケーターハンドルの作成に失敗しました");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

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

    double bbLower[], bbUpper[], rsi[], adx[], atr[], emaH1[], close[];
    
    CopyBuffer(handleBB, 2, 0, 3, bbLower);
    CopyBuffer(handleBB, 1, 0, 3, bbUpper);
    CopyBuffer(handleRSI, 0, 0, 3, rsi);
    CopyBuffer(handleADX, 0, 0, 3, adx);
    CopyBuffer(handleATR, 0, 0, InpATRMAPeriod + 2, atr);
    CopyBuffer(handleEMAH1, 0, 0, 3, emaH1);
    CopyClose(_Symbol, _Period, 0, 3, close);

    ArraySetAsSeries(bbLower, true);
    ArraySetAsSeries(bbUpper, true);
    ArraySetAsSeries(rsi, true);
    ArraySetAsSeries(adx, true);
    ArraySetAsSeries(atr, true);
    ArraySetAsSeries(emaH1, true);
    ArraySetAsSeries(close, true);

    // ATRの移動平均を計算
    double atrSum = 0;
    for(int i=1; i <= InpATRMAPeriod; i++) atrSum += atr[i];
    double atrMA = atrSum / InpATRMAPeriod;

    // 環境フィルター
    bool isRange = adx[1] < 20;
    bool isVolatile = atr[1] > (atrMA * 0.6);
    int h1Trend = (close[0] > emaH1[1]) ? 1 : -1; // 簡略化したトレンド判定

    // エントリーロジック
    if(PositionsTotal() == 0)
    {
        // LONG
        if(h1Trend == 1 && isRange && isVolatile)
        {
            if(rsi[2] <= 25 && close[2] <= bbLower[2])
            {
                if(close[1] > bbLower[1])
                {
                    ExecuteTrade(ORDER_TYPE_BUY);
                }
            }
        }
        // SHORT
        if(h1Trend == -1 && isRange && isVolatile)
        {
            if(rsi[2] >= 75 && close[2] >= bbUpper[2])
            {
                if(close[1] < bbUpper[1])
                {
                    ExecuteTrade(ORDER_TYPE_SELL);
                }
            }
        }
    }
    
    ManageTrailingStop();
}

// 取引実行関数
void ExecuteTrade(ENUM_ORDER_TYPE type)
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double sl = (type == ORDER_TYPE_BUY) ? price - InpSL_Pips * _Point * 10 : price + InpSL_Pips * _Point * 10;
    double tp = (type == ORDER_TYPE_BUY) ? price + InpTP_Pips * _Point * 10 : price - InpTP_Pips * _Point * 10;

    request.action = TRADE_ACTION_DEAL;
    request.symbol = _Symbol;
    request.volume = 0.1;
    request.type = type;
    request.price = price;
    request.sl = sl;
    request.tp = tp;
    request.deviation = 10;
    request.magic = 123456;
    request.type_filling = ORDER_FILLING_IOC;

    OrderSend(request, result);
}

// トレーリングストップ
void ManageTrailingStop()
{
    for(int i=PositionsTotal()-1; i>=0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            if(PositionGetInteger(POSITION_MAGIC) != 123456) continue;
            
            double currentPrice = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentSL = PositionGetDouble(POSITION_SL);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if(currentPrice - openPrice > InpTrailStart * _Point * 10)
                {
                    double newSL = currentPrice - InpTrailStart * _Point * 10;
                    if(newSL > currentSL) ModifySL(ticket, newSL);
                }
            }
            else
            {
                if(openPrice - currentPrice > InpTrailStart * _Point * 10)
                {
                    double newSL = currentPrice + InpTrailStart * _Point * 10;
                    if(newSL < currentSL || currentSL == 0) ModifySL(ticket, newSL);
                }
            }
        }
    }
}

void ModifySL(ulong ticket, double sl)
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    request.action = TRADE_ACTION_SLTP;
    request.position = ticket;
    request.sl = sl;
    OrderSend(request, result);
}

bool IsNewBar()
{
    static datetime lastBar;
    datetime currBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
    if(lastBar != currBar)
    {
        lastBar = currBar;
        return true;
    }
    return false;
}