対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
もともと、私はブレイクアウト手法に傾倒していました。しかし、レンジ相場での「だまし」に何度も資金を削られました。そこで、徹底的に「相場の端」だけを狙う逆張り手法を模索しました。インジケーターの組み合わせを数え切れないほど試し、ようやくたどり着いたのが今回のロジックです。
このコードを土台にしてください。MT5開発にかかる数百時間を節約できます。自分だけのオリジナルEAを作る最高の素材になるはずです。

<CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.35 |
| 勝率 | 50.0% |
| 総取引数 | 24 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥10004.46 |
| 最大ドローダウン | 1.81% |
| リカバリーファクター | 0.55 |
| 期待利得 (Expected Payoff) | 0.68 |
今回の検証結果の「限界」と「ダメ出し」
この成績の最大の弱点は、取引回数の少なさです。10年で24回という数字は、実戦では機会損失が大きすぎます。フィルターを厳しくしすぎた結果、利益を伸ばすチャンスまで捨ててしまいました。
PF1.35という数字も、爆発力に欠けます。最大ドローダウンが低いのは、単にトレードしていない期間が長いからです。効率的に資産を増やす仕組みとしては、まだ不十分と言えます。
ロジックの技術的詳細
本ロジックは、相場の「過熱感」と「停滞感」を同時に判定する仕組みです。
| 要素 | 条件・指標 | 設定値・判定基準 |
|---|---|---|
| 環境認識(MTF) | 上位足ADX (1h, 4h, 1d) | すべて25以下(トレンド不在を重視) |
| 環境認識(執行足) | Choppiness Index & ADX | CHOP > 50 かつ ADX < 20 |
| ボラティリティ | ATR / ATR_SMA | ATR < ATR_SMA * 1.5 (安定した変動幅) |
| エントリートリガー | BB & Keltner Channel | 価格が両方のバンドの外側に位置すること |
| 最終確認 | CCI (24) | +/- 200からの反転(極端な売られすぎ/買われすぎ) |
どう改善すべきか(次なる展望)
ここから改造して、性能を上げる余地は十分にあります。例えば、ADXの閾値を少し緩めることで、取引回数を増やせるかもしれません。また、時間帯フィルターを追加し、流動性の低い時間帯を避ける構成も有効です。
プロのEAは、ここに独自の環境認識を組み合わせています。例えば、通貨強弱の分析や、重要指標の回避ロジックなどです。この無料コードをベースに、あなただけのフィルターを付け足してください。
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class MeanReversionSynergy(BaseStrategy):
"""
Mean Reversion Synergy (MRS) Strategy - High-Efficiency Surgical Version
- Execution Timeframe: 15m
- Objective: Elevate PF >= 1.20 and RF >= 3.00 by eliminating "False Signs".
- Surgical Fixes:
1. Signal Quality Hardening: Reverted from "OR" to "AND" logic for BB and KC.
Price must now be outside BOTH bands to confirm a true extreme overshoot.
2. Over-trading Correction: Increased indicator periods from 20 to 24 to smooth noise
and reduced trade frequency from 1318 to a high-conviction subset.
3. Momentum Precision: Raised CCI exhaustion thresholds to +/- 200, capturing only
the most acute price exhaustion points.
4. Environment Shielding: Changed environment filter to "AND" logic (CHOP > 50 AND ADX < 20).
This strictly prohibits trading in anything other than a confirmed range.
5. MTF Tightening: Lowered HTF ADX threshold to 25 to avoid "trend-traps" on higher timeframes.
6. Expectancy Optimization: Adjusted TP to 70 and SL to 25 to improve the Payoff Ratio
and increase the Recovery Factor.
"""
def __init__(self):
# Quant Tuning: Optimized for high-quality swings.
# TP=70 / SL=25 creates a strong mathematical edge (Payoff Ratio 2.8)
# to ensure a high PF even with a lower trade frequency.
super().__init__(
name="MeanReversionSynergy",
default_tp_pips=70.0,
default_sl_pips=25.0,
enable_trailing_stop=True,
trail_start_pips=20.0
)
self.base_timeframe = "15m"
self.vision_timeframes = ["15m", "1h", "4h", "1d"]
def calculate_indicators(self, df):
# --- 1. Base Timeframe Indicators (15m) ---
# Choppiness Index
df['CHOP'] = ta.chop(df['High'], df['Low'], df['Close'])
# ADX: Trend strength
adx_df = ta.adx(df['High'], df['Low'], df['Close'])
df['ADX'] = adx_df.iloc[:, 0]
# Volatility: ATR for stability
df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'])
df['ATR_SMA'] = df['ATR'].rolling(window=24).mean()
# Entry Triggers: Increased period to 24 for superior noise filtering
# Bollinger Bands (24, 2.0)
bb = ta.bbands(df['Close'], length=24, std=2.0)
df['BBL'] = bb.iloc[:, 0]
df['BBM'] = bb.iloc[:, 1]
df['BBU'] = bb.iloc[:, 2]
# Keltner Channel (24, 2.0)
kc = ta.kc(df['High'], df['Low'], df['Close'], length=24, scalar=2.0)
df['KCL'] = kc.iloc[:, 0]
df['KCU'] = kc.iloc[:, 2]
# CCI (24)
df['CCI'] = ta.cci(df['High'], df['Low'], df['Close'], length=24)
# --- 2. Multi-Timeframe (MTF) Alignment ---
for tf in ["1h", "4h", "1d"]:
resampled = df[['High', 'Low', 'Close']].resample(tf).agg({
'High': 'max',
'Low': 'min',
'Close': 'last'
})
adx_tf_series = ta.adx(resampled['High'], resampled['Low'], resampled['Close'])
adx_tf_shifted = adx_tf_series.iloc[:, 0].shift(1)
df[f'ADX_{tf}'] = adx_tf_shifted.reindex(df.index, method='ffill')
return df
def generate_signal(self, df):
# Minimum data requirement
if len(df) < 30:
return None
curr = df.iloc[-1]
prev = df.iloc[-2]
# --- 1. MTF Environment Filter (The Alignment) ---
# Tightened: ADX > 25 on any HTF = Trend danger. No mean reversion.
mtf_trending = (curr['ADX_1h'] > 25) or (curr['ADX_4h'] > 25) or (curr['ADX_1d'] > 25)
if mtf_trending:
return None
# --- 2. Execution Timeframe Environment Filter (The Shield) ---
# Hardened: Must be both Choppy AND Low Trend.
# This removes the "false range" signals that dilute the PF.
is_choppy = (curr['CHOP'] > 50.0) and (curr['ADX'] < 20)
is_vol_stable = curr['ATR'] < (curr['ATR_SMA'] * 1.5)
if not (is_choppy and is_vol_stable):
return None
# --- 3. Entry Triggers (The Sniper) ---
# Long Signal:
# 1. Price MUST be below BOTH BBL and KCL (Maximum Overshoot)
# 2. CCI pivot from below -200 (Maximum Exhaustion)
if (curr['Close'] < curr['BBL']) and (curr['Close'] < curr['KCL']):
if (prev['CCI'] < -200) and (curr['CCI'] > prev['CCI']):
return 'BUY'
# Short Signal:
# 1. Price MUST be above BOTH BBU and KCU (Maximum Overshoot)
# 2. CCI pivot from above 200 (Maximum Exhaustion)
if (curr['Close'] > curr['BBU']) and (curr['Close'] > curr['KCU']):
if (prev['CCI'] > 200) and (curr['CCI'] < prev['CCI']):
return 'SELL'
return None
MQL5再現コード
//+------------------------------------------------------------------+
//| MeanReversionSynergy_EA.mq5 |
//| Copyright 2026, Quant Engineer |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input int InpPeriod = 24; // Indicator Period
input double InpBBStd = 2.0; // BB Standard Deviation
input double InpKCScale = 2.0; // KC Scalar
input double InpCCILevel = 200.0; // CCI Exhaustion Level
input double InpTP = 700; // Take Profit (points)
input double InpSL = 250; // Stop Loss (points)
input double InpLot = 0.1; // Lot Size
input int InpTrailing = 200; // Trailing Start (points)
//--- Global Handles
int hBB, hKC, hCCI, hADX, hATR;
int hADX_1h, hADX_4h, hADX_1d;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
hBB = iBands(_Symbol, _Period, InpPeriod, 0, InpBBStd, PRICE_CLOSE);
hCCI = iCCI(_Symbol, _Period, InpPeriod, PRICE_TYPICAL);
hADX = iADX(_Symbol, _Period, InpPeriod);
hATR = iATR(_Symbol, _Period, InpPeriod);
hADX_1h = iADX(_Symbol, PERIOD_H1, InpPeriod);
hADX_4h = iADX(_Symbol, PERIOD_H4, InpPeriod);
hADX_1d = iADX(_Symbol, PERIOD_D1, InpPeriod);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom Chop Index calculation |
//+------------------------------------------------------------------+
double CalculateChop(int period)
{
double high_max = iHigh(_Symbol, _Period, iHighest(_Symbol, _Period, MODE_HIGH, period, 1));
double low_min = iLow(_Symbol, _Period, iLowest(_Symbol, _Period, MODE_LOW, period, 1));
double sum_range = 0;
for(int i=1; i<=period; i++)
sum_range += MathAbs(iHigh(_Symbol, _Period, i) - iLow(_Symbol, _Period, i));
if(high_max - low_min == 0) return 0;
return 100.0 * MathLog10(sum_range / (high_max - low_min)) / MathLog10(period);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(!isNewBar()) return;
double bbL[], bbU[], cci[], adx[], atr[];
double adx1h[], adx4h[], adx1d[];
CopyBuffer(hBB, 2, 0, 2, bbL); // Lower band
CopyBuffer(hBB, 0, 0, 2, bbU); // Upper band
CopyBuffer(hCCI, 0, 0, 2, cci);
CopyBuffer(hADX, 0, 0, 2, adx);
CopyBuffer(hATR, 0, 0, 2, atr);
CopyBuffer(hADX_1h, 0, 0, 1, adx1h);
CopyBuffer(hADX_4h, 0, 0, 1, adx4h);
CopyBuffer(hADX_1d, 0, 0, 1, adx1d);
// Keltner Channel Calculation (Simplified)
double sma = 0;
for(int i=1; i<=InpPeriod; i++) sma += iClose(_Symbol, _Period, i);
sma /= InpPeriod;
double kc_range = atr[0] * InpKCScale;
double kcL = sma - kc_range;
double kcU = sma + kc_range;
double chop = CalculateChop(InpPeriod);
double close = iClose(_Symbol, _Period, 0);
//--- 1. MTF Filter
if(adx1h[0] > 25 || adx4h[0] > 25 || adx1d[0] > 25) return;
//--- 2. Environment Filter
if(!(chop > 50.0 && adx[0] < 20)) return;
double atr_sma = 0;
for(int i=0; i<24; i++) atr_sma += iATR(_Symbol, _Period, InpPeriod, i);
atr_sma /= 24;
if(atr[0] >= atr_sma * 1.5) return;
//--- 3. Entry Signals
// Buy
if(close < bbL[0] && close < kcL)
{
if(cci[1] < -InpCCILevel && cci[0] > cci[1])
{
TradeOpen(ORDER_TYPE_BUY);
}
}
// Sell
else if(close > bbU[0] && close > kcU)
{
if(cci[1] > InpCCILevel && cci[0] < cci[1])
{
TradeOpen(ORDER_TYPE_SELL);
}
}
}
void TradeOpen(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 : price + InpSL * _Point;
double tp = (type == ORDER_TYPE_BUY) ? price + InpTP * _Point : price - InpTP * _Point;
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = InpLot;
request.type = type;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = 123456;
OrderSend(request, result);
}
bool isNewBar()
{
static datetime last_time = 0;
datetime lastbar_time = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
if(last_time != lastbar_time)
{
last_time = lastbar_time;
return true;
}
return false;
}