対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
もともとは、逆張り手法における「落ちてくるナイフ」を掴む恐怖から開発を始めました。ボリンジャーバンドの端でエントリーしても、強いトレンドが出ると底なしに資金を減らします。このダマシを排除するため、時間軸を重ねた厳格なフィルターを構築することに心血を注ぎました。
このコードをベースにすれば、MT5のEA開発に費やす数百時間を節約できます。検証済みの堅牢な土台があることで、あなた独自の改良を加える作業に集中できるはずです。
<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.26 |
| 勝率 | 38.5% |
| 総取引数 | 26 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥4883.82 |
| 最大ドローダウン | 1.06% |
| リカバリーファクター | 0.46 |
| 期待利得 (Expected Payoff) | 0.77 |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、取引回数の少なさです。10年間で26回という数字は、フィルターが厳しすぎると言わざるを得ません。機会損失が非常に大きく、資産の増加スピードは極めて緩やかです。
また、勝率が4割を切っている点も課題です。損小利大を徹底していますが、連敗時の心理的ストレスは避けられません。純粋な統計上の優位性はありますが、運用効率の面では不十分な結果となりました。
ロジックの技術的詳細
本ロジックは、3つの時間軸を組み合わせた「適応型平均回帰エンジン」です。
| 項目 | 設定内容 | 役割 |
|---|---|---|
| 上位足 (1時間足) | ADX < 20 | マクロなトレンドの不在を確認 |
| 中位足 (15分足) | ADX < 15 / RSI < 25 or > 75 | マイクロなレンジ判定と売買圏の特定 |
| 執行足 (5分足) | BB (30, 2.5) への回帰 | 正確なエントリータイミングの計測 |
| 確定条件 | ローソク足の方向性 (陽線/陰線) | 逆行の勢いが止まったことを確認 |
| リスク管理 | TP 35pips / SL 18pips | 低ドローダウンを維持する固定比率 |
どう改善すべきか(次なる展望)
このロジックを「化けさせる」には、環境認識の緩和が必要です。例えば、ADXの閾値を少し上げるだけで、取引回数を劇的に増やせます。もちろん、それにより最大ドローダウンが増えるリスクは伴います。
また、プロのEAはここに「ボラティリティ・フィルター」を導入しています。ATRなどで相場の変動幅を測定し、利確幅を動的に変更すれば、収益性はさらに向上するはずです。
Pythonコードの公開
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
import numpy as np
class AdaptiveMeanReversionEngine(BaseStrategy):
"""
Quantitative Surgical Optimization: Version 4.0
Focus: PF improvement (0.82 -> 1.20+) and MaxDD reduction (14.6% -> <10%)
Key Adjustments:
1. Regime Filter: Tightened ADX thresholds to eliminate 'quasi-trends' (False Signals).
2. Signal Quality: Shifted BB period from 20 to 30 and StdDev to 2.5 to target extreme exhaustion.
3. Momentum Filter: RSI boundaries shifted to 25/75 for higher probability reversal points.
4. Execution Trigger: Added 'Candle Direction' confirmation (Price Action) to filter noise.
5. RR Optimization: Adjusted TP/SL ratio to increase Payoff Ratio while capping MaxDD.
"""
def __init__(self):
super().__init__(
name="Adaptive Mean-Reversion Engine V4",
default_tp_pips=35.0, # Slightly increased to improve Payoff Ratio
default_sl_pips=18.0, # Tightened to protect MaxDD and improve RF
enable_trailing_stop=True,
trail_start_pips=10.0 # Optimized for early profit locking
)
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15m", "1h"]
def calculate_indicators(self, df):
# Ensure float types for all price columns
for col in ['Open', 'High', 'Low', 'Close']:
df[col] = df[col].astype(float)
# --- 1. HTF (1h) Indicators: Macro Regime Filter ---
df_h1 = df[['Open', 'High', 'Low', 'Close']].resample('1h').last()
adx_h1 = ta.adx(df_h1['High'], df_h1['Low'], df_h1['Close'], length=14)
# Positional indexing to prevent KeyError
h1_adx_series = adx_h1.iloc[:, 0].shift(1)
df['HTF_ADX'] = h1_adx_series.reindex(df.index, method='ffill')
# --- 2. LTF (15min) Indicators: Setup Filter ---
df_15m = df[['Open', 'High', 'Low', 'Close']].resample('15min').last()
# ADX for LTF Range confirmation
adx_15m = ta.adx(df_15m['High'], df_15m['Low'], df_15m['Close'], length=14)
# BBands for LTF extremes (Period 30, Std 2.5 for higher quality filtered signals)
bb_15m = ta.bbands(df_15m['Close'], length=30, std=2.5)
# RSI for deeper exhaustion (14)
rsi_15m = ta.rsi(df_15m['Close'], length=14)
# Shift(1) and Reindex to avoid look-ahead bias
df['LTF_ADX'] = adx_15m.iloc[:, 0].shift(1).reindex(df.index, method='ffill')
df['LTF_BBL'] = bb_15m.iloc[:, 0].shift(1).reindex(df.index, method='ffill')
df['LTF_BBU'] = bb_15m.iloc[:, 2].shift(1).reindex(df.index, method='ffill')
df['LTF_RSI'] = rsi_15m.shift(1).reindex(df.index, method='ffill')
# --- 3. Base (5m) Indicators: Precision Trigger ---
# Adjusted to Period 30, Std 2.5 for consistency with LTF filter
bb_5m = ta.bbands(df['Close'], length=30, std=2.5)
df['BBL_5M'] = bb_5m.iloc[:, 0]
df['BBU_5M'] = bb_5m.iloc[:, 2]
return df
def generate_signal(self, df):
if len(df) < 60:
return None
# Analysis of current and previous candles
curr = df.iloc[-1]
prev = df.iloc[-2]
# --- Quantitative Regime Filter (Surgical Adjustment) ---
# Only trade in extremely flat markets.
# HTF_ADX < 20: Macro non-trending
# LTF_ADX < 15: Micro range-bound
is_range_regime = (curr['HTF_ADX'] < 20) and (curr['LTF_ADX'] < 15)
if not is_range_regime:
return None
# --- BUY Signal Logic ---
# 1. LTF RSI is deeply oversold (< 25)
# 2. Trigger: 5m Price closes back inside Lower BB (Re-entry)
# 3. Confirmation: Current candle must be bullish (Close > Open) to filter falling knives
if (curr['LTF_RSI'] < 25 and
prev['Close'] < prev['BBL_5M'] and
curr['Close'] > curr['BBL_5M'] and
curr['Close'] > curr['Open']):
return 'BUY'
# --- SELL Signal Logic ---
# 1. LTF RSI is deeply overbought (> 75)
# 2. Trigger: 5m Price closes back inside Upper BB (Re-entry)
# 3. Confirmation: Current candle must be bearish (Close < Open) to filter breakout spikes
elif (curr['LTF_RSI'] > 75 and
prev['Close'] > prev['BBU_5M'] and
curr['Close'] < curr['BBU_5M'] and
curr['Close'] < curr['Open']):
return 'SELL'
return None
MQL5(MT5用)コードへの翻訳
//+------------------------------------------------------------------+
//| AdaptiveMeanReversionEngine.mq5 |
//| Copyright 2026, System Trader |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
// 入力パラメータ
input int InpBBPeriod = 30; // BB Period
input double InpBBStdDev = 2.5; // BB StdDev
input int InpRSIPeriod = 14; // RSI Period
input int InpADXPeriod = 14; // ADX Period
input double InpTP_Pips = 35.0; // Take Profit (Pips)
input double InpSL_Pips = 18.0; // Stop Loss (Pips)
input double InpLotSize = 0.1; // Lot Size
// ハンドル
int hADX_H1, hADX_M15, hRSI_M15, hBB_M5;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
hADX_H1 = iADX(_Symbol, PERIOD_H1, InpADXPeriod);
hADX_M15 = iADX(_Symbol, PERIOD_M15, InpADXPeriod);
hRSI_M15 = iRSI(_Symbol, PERIOD_M15, InpRSIPeriod, PRICE_CLOSE);
hBB_M5 = iBands(_Symbol, PERIOD_M5, InpBBPeriod, 0, InpBBStdDev, PRICE_CLOSE);
if(hADX_H1 == INVALID_HANDLE || hADX_M15 == INVALID_HANDLE ||
hRSI_M15 == INVALID_HANDLE || hBB_M5 == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 新しい足の確定を待つ(5分足ベース)
static datetime last_bar = 0;
datetime current_bar = iTime(_Symbol, PERIOD_M5, 0);
if(last_bar == current_bar) return;
last_bar = current_bar;
double adx_h1[], adx_m15[], rsi_m15[], bb_upper[], bb_lower[], bb_mid[];
MqlRates rates_m5[];
ArraySetAsSeries(adx_h1, true);
ArraySetAsSeries(adx_m15, true);
ArraySetAsSeries(rsi_m15, true);
ArraySetAsSeries(bb_upper, true);
ArraySetAsSeries(bb_lower, true);
ArraySetAsSeries(rates_m5, true);
if(CopyBuffer(hADX_H1, 0, 1, 1, adx_h1) < 1) return;
if(CopyBuffer(hADX_M15, 0, 1, 1, adx_m15) < 1) return;
if(CopyBuffer(hRSI_M15, 0, 1, 1, rsi_m15) < 1) return;
if(CopyBuffer(hBB_M5, 1, 1, 2, bb_upper) < 2) return;
if(CopyBuffer(hBB_M5, 2, 1, 2, bb_lower) < 2) return;
if(CopyRates(_Symbol, PERIOD_M5, 1, 2, rates_m5) < 2) return;
// レジームフィルター
bool is_range = (adx_h1[0] < 20.0 && adx_m15[0] < 15.0);
if(!is_range) return;
double close_curr = rates_m5[0].close;
double open_curr = rates_m5[0].open;
double close_prev = rates_m5[1].close;
// BUY Signal
if(rsi_m15[0] < 25.0 && close_prev < bb_lower[1] &&
close_curr > bb_lower[0] && close_curr > open_curr)
{
ExecuteTrade(ORDER_TYPE_BUY);
}
// SELL Signal
else if(rsi_m15[0] > 75.0 && close_prev > bb_upper[1] &&
close_curr < bb_upper[0] && close_curr < open_curr)
{
ExecuteTrade(ORDER_TYPE_SELL);
}
}
//+------------------------------------------------------------------+
//| Trade Execution |
//+------------------------------------------------------------------+
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 = InpLotSize;
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);
}