💡 本ロジックの対象環境
対象通貨ペア:GBPJPY / 使用時間足:5分足専用
FXの自動売買開発は困難な道です。多くのロジックが数ヶ月で破綻します。今回は10年のバックテストを生き抜いたロジックを公開します。成績は決して高くありません。しかし、過酷な相場を生き残る強靭さを備えています。

もともと私はブレイクアウト手法を好んでいました。しかし、レンジ相場での「だまし」に何度も資金を削られました。どうすればだましを減らせるか。その答えを探して、ボラティリティの収束と拡散に注目しました。試行錯誤を繰り返し、守備力を極限まで高めたのがこのロジックです。

このコードを手に入れれば、MT5のEA開発に費やす数百時間を節約できます。ゼロから組む苦労はありません。自分だけのオリジナルEAを作るための、最高の土台になるはずです。

バックテスト分析 - equity

<CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>

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

項目 実績値
プロフィットファクター (PF) 1.25
勝率 43.4%
総取引数 175 回
純利益 (0.1ロット/1万通貨時) ¥52654.84
最大ドローダウン 4.43%
リカバリーファクター 1.19
期待利得 (Expected Payoff) 0.71

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

この成績は、正直に言って平凡です。PF1.25という数値は、爆発的な利益を期待できるものではありません。勝率が4割強と低く、連敗が続く場面も見られます。

最大の要因は、フィルターの掛けすぎです。破綻を避けるために条件を厳格にしました。その結果、絶好のチャンスさえも切り捨てています。10年で175回という取引数は少なすぎます。機会損失が非常に大きく、効率的な運用とは言い切れません。

🤖
守りに寄りすぎて、攻撃力が死んでいる状態ですね。

ロジックの技術的詳細

本ロジックは、ボラティリティの「スクイーズ(収束)」からの「エクスパンション(拡散)」を狙います。

項目 設定・条件
対象通貨・時間足 GBPJPY / 5分足
トレンド判定 EMA 200 + MTF(1h, 4h, 1d)の方向一致
レジーム判定 CHOP Index < 60(トレンド相場の限定)
勢い判定 ADX > 20 + DMP/DMNの方向性
エントリートリガー BBスクイーズ後のBBブレイク + MACD方向一致
時間フィルター 13:00 〜 23:00(ロンドン・NY時間)
決済戦略 固定TP/SL + トレーリングストップ
単一の時間足ではなく、日足まで遡ったマルチタイムフレーム分析で方向性を固定している点が、低ドローダウンの秘訣です。

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

このコードをベースに、以下の改造を試してみてください。

  • フィルターの緩和: 日足のトレンド判定を外し、1時間足までにする。これで取引回数を増やせます。
  • 動的な利確設定: 固定のTPではなく、ATRに基づいた変動幅に変更する。相場のボラティリティに合わせた利益追求が可能です。
  • 時間帯の最適化: 通貨ペアの特性に合わせて、エントリー時間を微調整する。

プロのEAは、ここに独自の環境認識を組み込みます。この無料コードを「原石」として、あなただけの最強のロジックへ磨き上げてください。

公開Pythonコード

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

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

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

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

class SessionAdaptiveVolatilityStrategy(BaseStrategy):
    def __init__(self):
        # 【クオンツ調整】リカバリーファクター(RF) 3.0突破のため、利益効率を最大化
        # ペイオフレシオを維持しつつ、トレーリングストップの開始値を最適化して利益を伸ばす
        super().__init__(
            name="SessionAdaptiveVolatility", 
            default_tp_pips=60.0, 
            default_sl_pips=30.0, 
            enable_trailing_stop=True, 
            trail_start_pips=15.0
        )
        # 【絶対準拠ルール5】執行足の固定
        self.base_timeframe = "15min"
        # 【絶対準拠ルール6】執行足を含む合計4つの時間足定義
        self.vision_timeframes = ["15min", "1h", "4h", "1d"]

    def calculate_indicators(self, df):
        """
        高速ベクトル演算を用いたテクニカル指標の計算
        カラム名依存によるKeyErrorを完全に排除するため、ilocによる位置指定抽出を徹底
        """
        # --- 1. 執行足 (15min) の指標計算 ---
        # CHOP Index - 期間を20に延長し、短期的なノイズ(だまし)を軽減
        df['CHOP'] = ta.chop(df['High'], df['Low'], df['Close'], length=20)
        
        # ADX (Trend Strength) - 期間を14に維持
        adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14)
        df['ADX'] = adx_df.iloc[:, 0]
        df['DMP'] = adx_df.iloc[:, 1]
        df['DMN'] = adx_df.iloc[:, 2]
        
        # 執行足トレンドフィルター (EMA 200) - だまし排除の主軸
        df['EMA_200'] = ta.ema(df['Close'], length=200)
        
        # Bollinger Bands (20, 2)
        bb = ta.bbands(df['Close'], length=20, std=2)
        df['BB_lower'] = bb.iloc[:, 0]
        df['BB_upper'] = bb.iloc[:, 2]
        df['BB_width'] = bb.iloc[:, 3]
        
        # Keltner Channels (20, 1.5)
        kc = ta.kc(df['High'], df['Low'], df['Close'], length=20, scalar=1.5)
        df['KC_lower'] = kc.iloc[:, 0]
        df['KC_upper'] = kc.iloc[:, 2]
        df['KC_width'] = kc.iloc[:, 2] - kc.iloc[:, 0]
        
        # MACD (12, 26, 9)
        macd = ta.macd(df['Close'])
        df['MACD_hist'] = macd.iloc[:, 1]
        
        # スクイーズ判定 (BB幅 < KC幅)
        df['is_squeeze'] = df['BB_width'] < df['KC_width']
        
        # --- 2. マルチタイムフレーム(MTF)トレンド判定 ---
        # 【絶対準拠ルール7】ルックアヘッドバイアスの完全排除
        for tf in ["1h", "4h", "1d"]:
            resampled_close = df['Close'].resample(tf).last().shift(1)
            ema_mtf = ta.ema(resampled_close, length=200)
            trend_series = (resampled_close > ema_mtf).astype(int)
            df[f'trend_{tf}'] = trend_series.reindex(df.index, method='ffill')
            
        return df

    def generate_signal(self, df):
        """
        最新の行に基づいた売買シグナル生成
        """
        if len(df) < 200: # EMA200計算のため十分なデータを確保
            return None
            
        last_row = df.iloc[-1]
        # 直近3本以内にスクイーズが発生していたかを確認
        recent_squeeze = df['is_squeeze'].iloc[-3:].any()
        
        # --- 環境フィルター (精度重視の外科調整) ---
        # 1. 時間フィルター: ロンドン~NY (13:00 - 23:00) - 欧州序盤の不安定な動きを排除
        current_hour = last_row.name.hour if hasattr(last_row.name, 'hour') else 0
        is_trade_session = 13 <= current_hour <= 23
        
        # 2. レジームフィルター: CHOP閾値を 60.0 に引き下げ (より明確なトレンド局面を限定)
        is_trending_regime = last_row['CHOP'] < 60.0
        
        # 3. 勢いフィルター: ADX閾値を 20 に引き上げ (弱いトレンドでのエントリーを排除)
        is_strong_momentum = last_row['ADX'] > 20
        
        # 4. MTF同期判定: 取引回数に余裕があるため、日足(1d)のフィルターを復活させ、整合性を強化
        mtf_bullish = (last_row['trend_1h'] == 1 and last_row['trend_4h'] == 1 and last_row['trend_1d'] == 1)
        mtf_bearish = (last_row['trend_1h'] == 0 and last_row['trend_4h'] == 0 and last_row['trend_1d'] == 0)
        
        # 5. 執行足トレンドフィルター: 価格がEMA200より上/下にあることを必須条件とする
        price_above_ema = last_row['Close'] > last_row['EMA_200']
        price_below_ema = last_row['Close'] < last_row['EMA_200']
        
        # --- エントリートリガー ---
        # ボリンジャーバンド・ブレイクアウト
        break_upper = last_row['Close'] > last_row['BB_upper']
        break_lower = last_row['Close'] < last_row['BB_lower']
        
        # モメンタム確認 (MACDヒストグラムの方向性)
        macd_bull = last_row['MACD_hist'] > 0
        macd_bear = last_row['MACD_hist'] < 0

        # --- 最終判定 (フィルタの厳格化によりPFとRFを向上) ---
        # ロング条件
        if (is_trade_session and 
            is_trending_regime and 
            is_strong_momentum and 
            price_above_ema and
            last_row['DMP'] > last_row['DMN'] and 
            mtf_bullish and 
            recent_squeeze and 
            break_upper and 
            macd_bull):
            return 'BUY'
            
        # ショート条件
        if (is_trade_session and 
            is_trending_regime and 
            is_strong_momentum and 
            price_below_ema and
            last_row['DMN'] > last_row['DMP'] and 
            mtf_bearish and 
            recent_squeeze and 
            break_lower and 
            macd_bear):
            return 'SELL'
            
        return None

MQL5コード(MT5用)

//+------------------------------------------------------------------+
//|                                  SessionAdaptiveVolatility.mq5   |
//|                                  Copyright 2026, EA Developer    |
//|                                             https://your-site.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, EA Developer"
#property link      "https://your-site.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input double   InpLotSize        = 0.1;      // ロットサイズ
input int      InpTP_Pips        = 600;      // 利確 (Points)
input int      InpSL_Pips        = 300;      // 損切 (Points)
input int      InpTrailStart     = 150;      // トレーリング開始 (Points)
input int      InpEMA_Period     = 200;      // EMA期間
input int      InpADX_Period     = 14;       // ADX期間
input int      InpBB_Period      = 20;       // BB期間
input double   InpBB_Dev         = 2.0;      // BB偏差
input int      InpKC_Period      = 20;       // KC期間
input double   InpKC_Scalar      = 1.5;      // KCスカラー
input double   InpCHOP_Limit     = 60.0;     // CHOP上限
input double   InpADX_Min        = 20.0;     // ADX下限

//--- ハンドル
int hEMA_curr, hADX, hBB, hMACD;
int hEMA_1h, hEMA_4h, hEMA_1d;

CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    hEMA_curr = iMA(_Symbol, _Period, InpEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
    hADX      = iADX(_Symbol, _Period, InpADX_Period);
    hBB       = iBands(_Symbol, _Period, InpBB_Period, 0, InpBB_Dev, PRICE_CLOSE);
    hMACD     = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE);
    
    hEMA_1h   = iMA(_Symbol, PERIOD_H1, InpEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
    hEMA_4h   = iMA(_Symbol, PERIOD_H4, InpEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
    hEMA_1d   = iMA(_Symbol, PERIOD_D1, InpEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
    
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    MqlDateTime dt;
    TimeCurrent(dt);
    if(dt.hour < 13 || dt.hour > 23) return;

    double close = iClose(_Symbol, _Period, 0);
    double ema_curr = GetMA(hEMA_curr, 0);
    
    // MTF Trend
    bool mtf_bull = (iClose(_Symbol, PERIOD_H1, 0) > GetMA(hEMA_1h, 0)) &&
                    (iClose(_Symbol, PERIOD_H4, 0) > GetMA(hEMA_4h, 0)) &&
                    (iClose(_Symbol, PERIOD_D1, 0) > GetMA(hEMA_1d, 0));
                    
    bool mtf_bear = (iClose(_Symbol, PERIOD_H1, 0) < GetMA(hEMA_1h, 0)) &&
                    (iClose(_Symbol, PERIOD_H4, 0) < GetMA(hEMA_4h, 0)) &&
                    (iClose(_Symbol, PERIOD_D1, 0) < GetMA(hEMA_1d, 0));

    // ADX & Momentum
    double adx_val = GetADX(hADX, 0);
    double dmp = GetDMP(hADX, 0);
    double dmn = GetDMN(hADX, 0);
    
    // CHOP (Simplified implementation as MQL5 doesn't have native CHOP)
    double chop = CalculateCHOP(InpKC_Period);

    // BB & Squeeze (BB Width < KC Width)
    double bb_upper = GetBBUpper(hBB, 0);
    double bb_lower = GetBBLower(hBB, 0);
    double bb_width = bb_upper - bb_lower;
    double kc_width = CalculateKCWidth(InpKC_Period, InpKC_Scalar);
    bool is_squeeze = (bb_width < kc_width);

    // MACD
    double macd_hist = GetMACDHist(hMACD, 0);

    // Entry Long
    if(mtf_bull && close > ema_curr && chop < InpCHOP_Limit && adx_val > InpADX_Min && 
       dmp > dmn && is_squeeze && close > bb_upper && macd_hist > 0)
    {
        double sl = close - InpSL_Pips * _Point;
        double tp = close + InpTP_Pips * _Point;
        trade.Buy(InpLotSize, _Symbol, close, sl, tp);
    }

    // Entry Short
    if(mtf_bear && close < ema_curr && chop < InpCHOP_Limit && adx_val > InpADX_Min && 
       dmn > dmp && is_squeeze && close < bb_lower && macd_hist < 0)
    {
        double sl = close + InpSL_Pips * _Point;
        double tp = close - InpTP_Pips * _Point;
        trade.Sell(InpLotSize, _Symbol, close, sl, tp);
    }
    
    ManageTrailingStop();
}

// --- Helper Functions ---
double GetMA(int handle, int shift) { double b[1]; CopyBuffer(handle, 0, shift, 1, b); return b[0]; }
double GetADX(int handle, int shift) { double b[1]; CopyBuffer(handle, 0, shift, 1, b); return b[0]; }
double GetDMP(int handle, int shift) { double b[1]; CopyBuffer(handle, 1, shift, 1, b); return b[0]; }
double GetDMN(int handle, int shift) { double b[1]; CopyBuffer(handle, 2, shift, 1, b); return b[0]; }
double GetBBUpper(int handle, int shift) { double b[1]; CopyBuffer(handle, 1, shift, 1, b); return b[0]; }
double GetBBLower(int handle, int shift) { double b[1]; CopyBuffer(handle, 2, shift, 1, b); return b[0]; }
double GetMACDHist(int handle, int shift) { double b[1]; CopyBuffer(handle, 1, shift, 1, b); return b[0]; }

double CalculateCHOP(int period) {
    double high_max = iHigh(_Symbol, _Period, iHighest(_Symbol, _Period, MODE_HIGH, period, 0));
    double low_min = iLow(_Symbol, _Period, iLowest(_Symbol, _Period, MODE_LOW, period, 0));
    double sum_diff = 0;
    for(int i=0; i<period; i++) sum_diff += MathAbs(iHigh(_Symbol, _Period, i) - iLow(_Symbol, _Period, i));
    return 100.0 * (1.0 - (high_max - low_min) / sum_diff);
}

double CalculateKCWidth(int period, double scalar) {
    double atr = iATR(_Symbol, _Period, period, 0);
    return 2.0 * scalar * atr;
}

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

bool IsNewBar() {
    static datetime last_bar;
    datetime curr_bar = iTime(_Symbol, _Period, 0);
    if(last_bar != curr_bar) { last_bar = curr_bar; return true; }
    return false;
}