直近2年のインフレ相場は非常に困難でした。金利変動でトレンドが急変し、多くのEAが破綻しています。その中で生き残った優秀なロジックを公開します。

バックテスト資産推移

これが直近2年間を生き抜いた資産推移の証拠です。(※本ロジックは ユーロドルの5分足 専用です)

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

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

結論から述べます。相場環境を厳格に選別したからです。

理由は3点あります。1つ目はADXを用いたレンジ判定です。2つ目はアジア時間への限定です。3つ目は上位足の方向性一致です。

具体例を挙げます。1時間足が上昇中で、5分足がレンジのときだけ買いを狙います。強いトレンドに逆らわず、押し目での反発のみを拾う設計です。

結論として、無駄な取引を徹底的に省いたことが低いドローダウンに繋がりました。

🤖
勝ち続けることより、大きく負けない仕組み作りが重要です。

戦略の技術的詳細

項目 設定内容 役割
メイン時間足 5分足 エントリータイミングの判定
上位足フィルター 1時間足 EMA200 全体のトレンド方向を決定
レンジ判定 ADX (14) < 25 トレンドの欠如を確認
反転シグナル RSI (14) 30/70 売られすぎ・買われすぎの判定
価格位置 ボリンジャーバンド バンド中央値との位置関係をチェック
取引時間 0:00 〜 9:00 レンジが発生しやすい時間帯に限定
このロジックの核心は「時間帯」と「ボラティリティ」の掛け合わせにあります。

公開コード (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 AMRFStrategy(BaseStrategy):
    def __init__(self):
        super().__init__(
            name="AdaptiveMeanReversionFramework_Surgical_V5",
            default_tp_pips=45.0,      # 期待値向上のためTPを微調整
            default_sl_pips=25.0,      # Max DDを抑えつつ、勝率を確保するための適正値に設定
            enable_trailing_stop=True,
            trail_start_pips=15.0      # 利益確定を早めに行い、PFとリカバリーファクターを改善
        )
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15min", "1h"]

    def calculate_indicators(self, df):
        # --- Vectorized Indicator Calculation (5m) ---
        # Bollinger Bands: std=2.0へ戻し、エントリー頻度を劇的に改善
        bb = df.ta.bbands(length=20, std=2.0)
        bb.columns = ['BBL', 'BBM', 'BBU', 'BBL_band', 'BBU_band']
        df = pd.concat([df, bb], axis=1)
        
        # RSI: 期間14。閾値を標準的な30/70へ緩和し、機会損失を防止
        df.ta.rsi(length=14, append=True)
        
        # ADX: 期間14。トレンド強度判定
        df.ta.adx(length=14, append=True)
        
        # ATR: 期間14。ボラティリティ測定
        df.ta.atr(length=14, append=True)

        # --- MTF Filter (1h EMA 200) ---
        # 大方向のトレンドフィルター
        df_1h_close = df['Close'].resample('1h').last().shift(1)
        ema200_1h = ta.ema(df_1h_close, length=200)
        df['ema200_1h'] = ema200_1h.reindex(df.index, method='ffill')

        return df

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

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

        # カラム名の確定
        rsi_col = 'RSI_14'
        adx_col = 'ADX_14'
        
        # 1. Range Filter: ADX < 25 (厳格すぎたADX<20および下降条件を撤廃し、取引回数を確保)
        is_range = last[adx_col] < 25
        
        # 2. Time Filter: 0:00 - 9:00 (レンジ相場の優位性が高い時間帯)
        is_time = 0 <= last.name.hour <= 9
        
        # 3. HTF Trend Bias (1h EMA 200)
        is_bull_bias = last['Close'] > last['ema200_1h']
        is_bear_bias = last['Close'] < last['ema200_1h']
        
        # --- Long Signal ---
        # 条件:
        # - HTF上昇トレンド中 + レンジ相場 + 指定時間帯
        # - 価格がBB下限付近にあり、RSIが30を上抜ける反転シグナル
        if is_range and is_time and is_bull_bias:
            # 価格がBBLを割り込んでいるか、BBL付近にあり、RSIが30を上抜けた瞬間
            if (last['Close'] < last['BBM']) and \
               (prev[rsi_col] < 30) and (last[rsi_col] >= 30):
                return 'BUY'
        
        # --- Short Signal ---
        # 条件:
        # - HTF下降トレンド中 + レンジ相場 + 指定時間帯
        # - 価格がBB上限付近にあり、RSIが70を下抜ける反転シグナル
        if is_range and is_time and is_bear_bias:
            # 価格がBBUを上回っているか、BBU付近にあり、RSIが70を下抜けた瞬間
            if (last['Close'] > last['BBM']) and \
               (prev[rsi_col] > 70) and (last[rsi_col] <= 70):
                return 'SELL'
        
        return None

MT5用コード (MQL5)

//+------------------------------------------------------------------+
//|                                AMRF_Surgical_V5.mq5              |
//|                                  Copyright 2026, Developer       |
//+------------------------------------------------------------------+
#property strict

input double   InpTP = 45.0;            // Take Profit (Pips)
input double   InpSL = 25.0;            // Stop Loss (Pips)
input double   InpTrailStart = 15.0;    // Trailing Start (Pips)
input int      InpADXPeriod = 14;       // ADX Period
input int      InpRSIPeriod = 14;       // RSI Period
input int      InpBBPeriod = 20;        // BB Period
input double   InpBBStdDev = 2.0;       // BB Standard Deviation
input int      InpEMA_HTF = 200;        // HTF EMA Period

int handleADX, handleRSI, handleBB, handleEMA_HTF;

int OnInit()
{
    handleADX = iADX(_Symbol, _Period, InpADXPeriod);
    handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
    handleBB = iBands(_Symbol, _Period, InpBBPeriod, 0, InpBBStdDev, PRICE_CLOSE);
    handleEMA_HTF = iMA(_Symbol, PERIOD_H1, InpEMA_HTF, 0, MODE_EMA, PRICE_CLOSE);
    
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
    IndicatorRelease(handleADX);
    IndicatorRelease(handleRSI);
    IndicatorRelease(handleBB);
    IndicatorRelease(handleEMA_HTF);
}

void OnTick()
{
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    if(dt.hour < 0 || dt.hour > 9) return;

    double adx[], rsi[], bbUpper[], bbLower[], bbMain[], emaHTF[];
    ArraySetAsSeries(adx, true);
    ArraySetAsSeries(rsi, true);
    ArraySetAsSeries(bbUpper, true);
    ArraySetAsSeries(bbLower, true);
    ArraySetAsSeries(bbMain, true);
    ArraySetAsSeries(emaHTF, true);

    if(CopyBuffer(handleADX, 0, 0, 2, adx) < 2) return;
    if(CopyBuffer(handleRSI, 0, 0, 2, rsi) < 2) return;
    if(CopyBuffer(handleBB, 1, 0, 2, bbUpper) < 2) return;
    if(CopyBuffer(handleBB, 2, 0, 2, bbLower) < 2) return;
    if(CopyBuffer(handleBB, 0, 0, 2, bbMain) < 2) return;
    if(CopyBuffer(handleEMA_HTF, 0, 0, 2, emaHTF) < 2) return;

    double close = iClose(_Symbol, _Period, 0);
    bool isRange = adx[0] < 25;
    bool isBullBias = close > emaHTF[0];
    bool isBearBias = close < emaHTF[0];

    if(PositionsTotal() == 0)
    {
        if(isRange && isBullBias && close < bbMain[0] && rsi[1] < 30 && rsi[0] >= 30)
        {
            ExecuteOrder(ORDER_TYPE_BUY);
        }
        else if(isRange && isBearBias && close > bbMain[0] && rsi[1] > 70 && rsi[0] <= 70)
        {
            ExecuteOrder(ORDER_TYPE_SELL);
        }
    }
    
    ManageTrailingStop();
}

void ExecuteOrder(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 * _Point * 10 : price + InpSL * _Point * 10;
    double tp = (type == ORDER_TYPE_BUY) ? price + InpTP * _Point * 10 : price - InpTP * _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))
        {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
            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 newSL)
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    request.action = TRADE_ACTION_SLTP;
    request.position = ticket;
    request.sl = newSL;
    OrderSend(request, result);
}