直近2年間のインフレおよび金利変動相場を勝ち抜いた自動売買ロジックを公開します。 このEAは徹底したリスク管理と環境認識を組み合わせた設計です。

バックテスト資産推移

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

指標 実績値
プロフィットファクター (PF) 1.21
勝率 45.9%
総取引数 724回
純利益 $1568.35
最大ドローダウン 4.60%

なぜこのロジックは勝ち続けられたのか

結論から述べます。 単なる逆張りではなく「トレンドの強弱」を定量的に判定したからです。

多くの逆張りEAは強いトレンドに巻き込まれ破綻します。 しかし本ロジックはADXを用いて強いトレンド相場を完全に排除しました。 さらに上位足(1時間足)の過熱感をフィルターに採用しています。

具体的にはADXが35以上の局面では一切のエントリーを禁止しました。 これにより、インフレ局面の強烈な一方向相場での踏み上げを回避しています。 1時間足のRSIが極端な値を示す際は、エントリー条件をさらに厳格化しました。

結果として、無駄なトレードを削ぎ落とし最大DDを4.60%に抑え込んでいます。 厳選したポイントのみを狙う姿勢が、低リスクでの利益積み上げを実現しました。

🤖
「逆張りは危険」と言われますが、フィルター次第で最強の武器になりますね。

技術的な強みの解説

本ロジックの核心は、以下の3段階フィルターにあります。

  • トレンド強度フィルター: ADXを用いて「逆張り禁止区域」を特定。
  • マルチタイムフレーム分析: 1時間足の方向性と過熱感を判定し、閾値を変動。
  • 反転の根拠の積み上げ: BBタッチに加え、RSIの反転とストキャスティクスのクロスを必須条件に設定。
特に「RSIが底を打って上昇し始めていること」を条件に加えた点が重要です。 単なる数値の低さではなく、方向性の変化を確認してからエントリーします。

公開コード(Python)

AIが生成したベースとなるPythonコードです。

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

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

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

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

class MeanReversionQuantEdge(BaseStrategy):
    """
    PF 1.00 の壁を突破するためのエッジ強化版ミーン・リバージョン戦略。
    単なるバンドタッチではなく、「過熱感」と「反転の兆し」を定量的に組み合わせ、
    強いトレンド相場での踏み上げを徹底的に排除することで勝率とPFを向上させる。
    """
    def __init__(self):
        # リスクリワード比を改善し、トレーリングストップで利益を最大化
        super().__init__(
            name="MeanReversion_QuantEdge",
            default_tp_pips=45.0, 
            default_sl_pips=25.0,
            enable_trailing_stop=True,
            trail_start_pips=15.0
        )
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5min", "15min", "1h"]

    def calculate_indicators(self, df):
        # --- 1. ベース指標の計算 ---
        # ボリンジャーバンド (20, 2.0)
        bb = df.ta.bbands(length=20, std=2.0)
        if bb is not None:
            df['BBL'] = bb.iloc[:, 0]
            df['BBM'] = bb.iloc[:, 1]
            df['BBU'] = bb.iloc[:, 2]
        
        # RSI (14) - 過熱感の判定
        df['RSI'] = df.ta.rsi(length=14)
        
        # ADX (14) - トレンド強度の判定(逆張り禁止区域の特定)
        adx_df = df.ta.adx(length=14)
        if adx_df is not None:
            df['ADX'] = adx_df.iloc[:, 0] # ADX_14
        
        # ストキャスティクス (14, 3, 3) - 反転のタイミング判定
        stoch = df.ta.stoch()
        if stoch is not None:
            df['STOCHk'] = stoch.iloc[:, 0]
            df['STOCHd'] = stoch.iloc[:, 1]
        
        # --- 2. マルチタイムフレーム(MTF)分析 ---
        # 未来のカンニングを完全に防止
        # 1時間足のトレンド方向と過熱感を確認
        df_1h_close = df['Close'].resample('1h').last().shift(1)
        df_1h_sma = df_1h_close.rolling(window=20).mean()
        df_1h_rsi = ta.rsi(df_1h_close, length=14)
        
        df['SMA_1h'] = df_1h_sma.reindex(df.index).ffill().bfill()
        df['Close_1h'] = df_1h_close.reindex(df.index).ffill().bfill()
        df['RSI_1h'] = df_1h_rsi.reindex(df.index).ffill().bfill()

        return df

    def generate_signal(self, df):
        """
        エッジ(優位性)を追求したシグナル生成
        """
        last = df.iloc[-1]
        prev = df.iloc[-2] # 1本前のデータを使用して「反転」を確認
        
        # 指標の抽出
        close = last['Close']
        bbl = last.get('BBL')
        bbu = last.get('BBU')
        rsi = last.get('RSI')
        prev_rsi = prev.get('RSI')
        adx = last.get('ADX')
        stoch_k = last.get('STOCHk')
        stoch_d = last.get('STOCHd')
        
        # MTF指標
        sma_1h = last.get('SMA_1h')
        close_1h = last.get('Close_1h')
        rsi_1h = last.get('RSI_1h')

        # 必須データのチェック
        if any(pd.isna(v) for v in [close, bbl, bbu, rsi, adx, stoch_k, stoch_d]):
            return None

        # --- A. 強いトレンドフィルター (ADX) ---
        # ADXが35を超えると強いトレンド相場となり、逆張りは極めて危険
        if adx >= 35:
            return None

        # --- B. 上位足の環境判定 ---
        # 1時間足で極端なトレンドがある場合、逆張りエントリーの閾値を厳格化する
        is_strong_bull_1h = (rsi_1h >= 65) if not pd.isna(rsi_1h) else False
        is_strong_bear_1h = (rsi_1h <= 35) if not pd.isna(rsi_1h) else False

        # --- C. BUYシグナル (反発の根拠を積み上げる) ---
        # 1. 価格がBB下限を突破している、あるいは極めて近い
        # 2. RSIが十分に低い (35以下) かつ、RSIが底を打って上昇し始めている (prev_rsi < rsi)
        # 3. ストキャスティクスが売られすぎ圏(20以下)でゴールデンクロス
        # 4. 上位足が強気トレンドならOK。強気下降トレンドならRSI 30以下という厳格条件
        
        buy_trigger = (close <= bbl * 1.0005)
        rsi_bottom = (rsi <= 35 and rsi > prev_rsi)
        stoch_cross_up = (stoch_k > stoch_d and prev.get('STOCHk', 100) <= prev.get('STOCHd', 0))
        
        if buy_trigger and rsi_bottom:
            if is_strong_bear_1h:
                # 強烈な下降トレンド時は、さらに深い押し目とStochクロスを必須とする
                if rsi <= 30 and stoch_cross_up:
                    return 'BUY'
            else:
                # 通常のレンジ〜緩やかなトレンド時は、Stochクロスがあればエントリー
                if stoch_cross_up or stoch_k < 15:
                    return 'BUY'

        # --- D. SELLシグナル (反落の根拠を積み上げる) ---
        # 1. 価格がBB上限を突破している、あるいは極めて近い
        # 2. RSIが十分に高い (65以上) かつ、RSIが天井を打って下降し始めている (prev_rsi > rsi)
        # 3. ストキャスティクスが買われすぎ圏(80以上)でデッドクロス
        # 4. 上位足が強弱トレンドならOK。強気上昇トレンドならRSI 70以上という厳格条件
        
        sell_trigger = (close >= bbu * 0.9995)
        rsi_top = (rsi >= 65 and rsi < prev_rsi)
        stoch_cross_down = (stoch_k < stoch_d and prev.get('STOCHk', 0) >= prev.get('STOCHd', 100))
        
        if sell_trigger and rsi_top:
            if is_strong_bull_1h:
                # 強烈な上昇トレンド時は、さらに高い過熱感とStochクロスを必須とする
                if rsi >= 70 and stoch_cross_down:
                    return 'SELL'
            else:
                # 通常のレンジ〜緩やかなトレンド時は、Stochクロスがあればエントリー
                if stoch_cross_down or stoch_k > 85:
                    return 'SELL'
        
        return None

MT5用実装コード(MQL5)

Pythonのロジックを忠実に再現したMQL5コードです。 そのままコンパイルして利用できます。

//+------------------------------------------------------------------+
//|                                    MeanReversion_QuantEdge.mq5   |
//|                                  Copyright 2026, System Trader   |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link      "https://your-blog-url.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

// 入力パラメータ
input int      InpBBPeriod   = 20;          // BB Period
input double   InpBBDev      = 2.0;         // BB Deviation
input int      InpRSIPeriod  = 14;          // RSI Period
input int      InpADXPeriod  = 14;          // ADX Period
input int      InpStochK     = 14;          // Stochastic K
input int      InpStochD     = 3;           // Stochastic D
input int      InpStochSlowing = 3;         // Stochastic Slowing
input double   InpTP_Pips    = 45.0;        // Take Profit (Pips)
input double   InpSL_Pips    = 25.0;        // Stop Loss (Pips)
input double   InpTrailStart = 15.0;        // Trailing Start (Pips)
input double   InpLotSize    = 0.1;         // Lot Size

// グローバル変数
int handleBB, handleRSI, handleADX, handleStoch, handleRSI_H1;
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    handleBB    = iBands(_Symbol, _Period, InpBBPeriod, 0, InpBBDev, PRICE_CLOSE);
    handleRSI   = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
    handleADX   = iADX(_Symbol, _Period, InpADXPeriod);
    handleStoch = iStochastic(_Symbol, _Period, InpStochK, InpStochD, InpStochSlowing, MODE_SMA, STO_LOWHIGH);
    handleRSI_H1 = iRSI(_Symbol, PERIOD_H1, InpRSIPeriod, PRICE_CLOSE);
    
    if(handleBB == INVALID_HANDLE || handleRSI == INVALID_HANDLE || 
       handleADX == INVALID_HANDLE || handleStoch == INVALID_HANDLE || handleRSI_H1 == INVALID_HANDLE)
        return(INIT_FAILED);
        
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 指標値の取得
    double bbl[], bbu[], rsi[], adx[], stochK[], stochD[], rsiH1[];
    ArraySetAsSeries(bbl, true); ArraySetAsSeries(bbu, true);
    ArraySetAsSeries(rsi, true); ArraySetAsSeries(adx, true);
    ArraySetAsSeries(stochK, true); ArraySetAsSeries(stochD, true);
    ArraySetAsSeries(rsiH1, true);
    
    if(CopyBuffer(handleBB, 2, 0, 2, bbl) < 2) return; // Lower Band
    if(CopyBuffer(handleBB, 1, 0, 2, bbu) < 2) return; // Upper Band (Actually index 1 is middle, 2 is upper in some MT5 versions)
    // Correcting BB Indices: 0:Base, 1:Upper, 2:Lower
    CopyBuffer(handleBB, 1, 0, 2, bbu);
    CopyBuffer(handleBB, 2, 0, 2, bbl);
    
    if(CopyBuffer(handleRSI, 0, 0, 2, rsi) < 2) return;
    if(CopyBuffer(handleADX, 0, 0, 2, adx) < 2) return;
    if(CopyBuffer(handleStoch, 0, 0, 2, stochK) < 2) return;
    if(CopyBuffer(handleStoch, 1, 0, 2, stochD) < 2) return;
    if(CopyBuffer(handleRSI_H1, 0, 0, 2, rsiH1) < 2) return;

    double close = iClose(_Symbol, _Period, 0);
    
    // 1. ADXトレンドフィルター
    if(adx[0] >= 35) return;
    
    // 2. 上位足判定
    bool isStrongBullH1 = (rsiH1[0] >= 65);
    bool isStrongBearH1 = (rsiH1[0] <= 35);
    
    // ポジション確認
    bool hasPosition = (PositionsTotal() > 0);
    if(!hasPosition)
    {
        // BUYシグナル
        bool buyTrigger = (close <= bbl[0] * 1.0005);
        bool rsiBottom = (rsi[0] <= 35 && rsi[0] > rsi[1]);
        bool stochCrossUp = (stochK[0] > stochD[0] && stochK[1] <= stochD[1]);
        
        if(buyTrigger && rsiBottom)
        {
            if(isStrongBearH1)
            {
                if(rsi[0] <= 30 && stochCrossUp) 
                    ExecuteTrade(ORDER_TYPE_BUY);
            }
            else
            {
                if(stochCrossUp || stochK[0] < 15) 
                    ExecuteTrade(ORDER_TYPE_BUY);
            }
        }
        
        // SELLシグナル
        bool sellTrigger = (close >= bbu[0] * 0.9995);
        bool rsiTop = (rsi[0] >= 65 && rsi[0] < rsi[1]);
        bool stochCrossDown = (stochK[0] < stochD[0] && stochK[1] >= stochD[1]);
        
        if(sellTrigger && rsiTop)
        {
            if(isStrongBullH1)
            {
                if(rsi[0] >= 70 && stochCrossDown) 
                    ExecuteTrade(ORDER_TYPE_SELL);
            }
            else
            {
                if(stochCrossDown || stochK[0] > 85) 
                    ExecuteTrade(ORDER_TYPE_SELL);
            }
        }
    }
    else
    {
        ApplyTrailingStop();
    }
}

void ExecuteTrade(ENUM_ORDER_TYPE type)
{
    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;
    
    trade.PositionOpen(_Symbol, type, InpLotSize, price, sl, tp);
}

void ApplyTrailingStop()
{
    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 * 10)
                {
                    double newSL = currentPrice - InpSL_Pips * _Point * 10;
                    if(newSL > stopLoss) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
                }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if(openPrice - currentPrice > InpTrailStart * _Point * 10)
                {
                    double newSL = currentPrice + InpSL_Pips * _Point * 10;
                    if(newSL < stopLoss || stopLoss == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}
🤖
MQL5版ではトレーリングストップを実装しました。 利益が乗ったところでしっかりと逃げる設定にしています。