対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
もともと私は、ブレイクアウト手法の「だまし」に悩み続けていました。多くのインジケーターを組み合わせても、結局は相場のノイズに飲み込まれます。そこで、時間軸を分けた環境認識と、ボラティリティの厳選に注力しました。試行錯誤の末に辿り着いたのが、この保守的な設計です。
このコードを入手すれば、MT5のEA開発に費やす数百時間を節約できます。ゼロからロジックを組む必要はありません。本コードを土台にして、あなただけの改良を加えるのが最短ルートです。研究素材として、自由にご活用ください。
<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.21 |
| 勝率 | 50.6% |
| 総取引数 | 170 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥43217.48 |
| 最大ドローダウン | 3.32% |
| リカバリーファクター | 1.30 |
| 期待利得 (Expected Payoff) | 0.60 |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、収益性の低さです。10年で170回という取引数は、あまりに少なすぎます。機会損失が非常に大きく、資金効率は最悪と言えます。
PF 1.21という数字も、プロの視点では物足りません。これは、カーブフィッティングを徹底的に排除した結果です。安全性を重視しすぎたため、利益を伸ばす局面で早すぎる決済が行われています。また、レンジ相場での待機時間が長く、退屈な運用になることは間違いありません。
ロジックの技術的詳細
本ロジックは、上位足のトレンド方向へ、短期的なボラティリティ拡大を狙ってエントリーします。
| 項目 | 設定・条件 |
|---|---|
| 執行時間足 | 5分足 |
| 環境認識足 | 1時間足(EMA50, EMA200, ADX) |
| 取引時間制限 | UTC 15:00 〜 23:00 |
| ボラティリティ判定 | ATR > (ATR_SMA200 * 1.1) |
| トレンド条件 | 価格 > EMA50 > EMA200 且つ ADX > 35 |
| エントリートリガー | ドンチャンチャネル(40)突破 且つ MACDヒストグラム加速 |
| 決済ルール | TP: 80pips / SL: 30pips / トレーリングストップあり |
どう改善すべきか(次なる展望)
このロジックを「化けさせる」には、エントリーフィルターの緩和が必要です。現在はADXの閾値を35と高く設定しています。ここを25程度に下げ、代わりに価格帯のサポート・レジスタンスを判定に加えるべきです。
また、固定の利確幅ではなく、ボラティリティに連動した可変TPの導入を推奨します。プロのEAは、相場の変動幅に合わせて決済指値を動かしています。この土台に「市場の周期性」という視点を加えれば、収益性は飛躍的に向上するはずです。
Pythonソースコード
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas as pd
import pandas_ta as ta
class MTF_VBO_Strategy(BaseStrategy):
"""
Multi-Timeframe Volatility Breakout (MTF-VBO) Strategy - High-Efficiency Quant Edition.
Focus: Drastic improvement of Recovery Factor (RF) and Profit Factor (PF) by
eliminating low-probability "noise" trades and maximizing trend capture.
"""
def __init__(self):
# Optimization for RF and PF:
# 1. Increased TP to capture the "fat tail" of breakout moves.
# 2. Adjusted SL to avoid premature stops in high-volatility environments.
# 3. Delayed trailing stop start to allow the trade to develop, reducing 'death by a thousand cuts'.
super().__init__(
name="MTF-VBO_Quant_Ultra",
default_tp_pips=80.0,
default_sl_pips=30.0,
enable_trailing_stop=True,
trail_start_pips=25.0
)
self.base_timeframe = "15m"
self.vision_timeframes = ["5min", "15min", "1h"]
def calculate_indicators(self, df):
"""
Calculates all necessary indicators using vectorized operations.
Rule 8: No loops. Rule 10: No Volume.
"""
# --- 1. LTF (Execution Timeframe: 15m) Indicators ---
# Volatility Filter: ATR and its SMA
# Increased SMA length to 200 to create a more robust baseline for "abnormal" volatility.
df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
df['ATR_SMA'] = ta.sma(df['ATR'], length=200)
# Donchian Channel: Increased window to 40 to filter out short-term fakeouts (False Signs).
# This ensures we only enter on significant breakouts.
df['DC_Upper'] = df['High'].shift(1).rolling(window=40).max()
df['DC_Lower'] = df['Low'].shift(1).rolling(window=40).min()
# MACD (12, 26, 9) - Standard momentum confirmation
macd = ta.macd(df['Close'], fast=12, slow=26, signal=9)
df = pd.concat([df, macd], axis=1)
self.macd_hist_col = 'MACDh_12_26_9'
# --- 2. HTF (Higher Timeframe: 1h) Indicators ---
# Rule 7: Strict look-ahead bias elimination
df_h1 = df[['Open', 'High', 'Low', 'Close']].resample('1h').last()
# HTF Trend Filter: Triple Confirmation (Price > EMA50 > EMA200)
df_h1['HTF_EMA50'] = ta.ema(df_h1['Close'], length=50)
df_h1['HTF_EMA200'] = ta.ema(df_h1['Close'], length=200)
# HTF ADX (14): Raised threshold to 35 to ensure the market is in a strong trending phase.
adx_h1 = ta.adx(df_h1['High'], df_h1['Low'], df_h1['Close'], length=14)
df_h1 = pd.concat([df_h1, adx_h1], axis=1)
# Trend Logic for HTF (Stricter filter to boost PF)
df_h1['HTF_Trend'] = 0
up_cond = (df_h1['Close'] > df_h1['HTF_EMA50']) & \
(df_h1['HTF_EMA50'] > df_h1['HTF_EMA200']) & \
(df_h1['ADX_14'] > 35) & \
(df_h1['DMP_14'] > df_h1['DMN_14'])
dn_cond = (df_h1['Close'] < df_h1['HTF_EMA50']) & \
(df_h1['HTF_EMA50'] < df_h1['HTF_EMA200']) & \
(df_h1['ADX_14'] > 35) & \
(df_h1['DMN_14'] > df_h1['DMP_14'])
df_h1.loc[up_cond, 'HTF_Trend'] = 1
df_h1.loc[dn_cond, 'HTF_Trend'] = -1
# Shift(1) to avoid look-ahead bias
df_h1['HTF_Trend'] = df_h1['HTF_Trend'].shift(1)
# Map HTF trend back to LTF
df['HTF_Trend'] = df_h1['HTF_Trend'].reindex(df.index, method='ffill')
return df
def generate_signal(self, df):
"""
Generates trading signals based on the most recent completed candle.
"""
if len(df) < 200:
return None
# Use the last completed row to avoid repainting
last = df.iloc[-1]
prev = df.iloc[-2]
# --- High-Precision Filters ---
# 1. Time Window Filter (UTC 15:00 - 23:00) - Core volatility window
is_active_time = 15 <= last.name.hour <= 23
if not is_active_time:
return None
# 2. Volatility Expansion Filter:
# Only enter when current ATR is significantly above the long-term average (1.1x).
# This prevents entries during "drifting" markets which cause low RF.
volatility_ok = last['ATR'] > (last['ATR_SMA'] * 1.1)
if not volatility_ok:
return None
# 3. HTF Trend Filter: Must be in a strong confirmed trend
htf_trend = last['HTF_Trend']
if htf_trend == 0:
return None
# --- Entry Triggers (Surgical Precision) ---
# BUY: Strong HTF Trend + 40-period Breakout + Positive and Accelerating MACD Histogram
if htf_trend == 1:
if (last['Close'] > last['DC_Upper']) and \
(last[self.macd_hist_col] > 0) and \
(last[self.macd_hist_col] > prev[self.macd_hist_col]):
return 'BUY'
# SELL: Strong HTF Trend + 40-period Breakout + Negative and Accelerating MACD Histogram
if htf_trend == -1:
if (last['Close'] < last['DC_Lower']) and \
(last[self.macd_hist_col] < 0) and \
(last[self.macd_hist_col] < prev[self.macd_hist_col]):
return 'SELL'
return None
MQL5コード(MT5用)
//+------------------------------------------------------------------+
//| MTF_VBO_Quant_Ultra.mq5|
//| Copyright 2026, Trading Dev |
//| https://www.example.com|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Trading Dev"
#property link "https://www.example.com"
#property version "1.00"
#property strict
//--- Input Parameters
input double InpLotSize = 0.1; // Lot Size
input int InpTP_Pips = 800; // Take Profit (points)
input int InpSL_Pips = 300; // Stop Loss (points)
input int InpTrailStart = 250; // Trailing Stop Start (points)
input int InpDCPeriod = 40; // Donchian Channel Period
input int InpATRPeriod = 14; // ATR Period
input int InpATR_SMA = 200; // ATR SMA Period
//--- Indicator Handles
int handleATR, handleATR_SMA, handleMACD, handleEMA50_H1, handleEMA200_H1, handleADX_H1;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleATR = iATR(_Symbol, _Period, InpATRPeriod);
handleMACD = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE);
handleEMA50_H1 = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
handleEMA200_H1 = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE);
handleADX_H1 = iADX(_Symbol, PERIOD_H1, 14);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Time Filter (UTC 15:00 - 23:00)
MqlDateTime dt;
TimeCurrent(dt);
if(dt.hour < 15 || dt.hour > 23) return;
// Get Indicator Values
double atr[], macd_main[], macd_sig[];
CopyBuffer(handleATR, 0, 0, 2, atr);
CopyBuffer(handleMACD, 0, 0, 2, macd_main);
CopyBuffer(handleMACD, 1, 0, 2, macd_sig);
// Calculate ATR SMA manually (simplification for sample)
double atr_sum = 0;
double atr_buf[];
CopyBuffer(handleATR, 0, 0, InpATR_SMA, atr_buf);
for(int i=0; i<InpATR_SMA; i++) atr_sum += atr_buf[i];
double atr_sma = atr_sum / InpATR_SMA;
if(atr[0] < atr_sma * 1.1) return;
// HTF Trend Filter (H1)
double ema50[], ema200[], adx[], plusDI[], minusDI[], closeH1[];
CopyBuffer(handleEMA50_H1, 0, 0, 1, ema50);
CopyBuffer(handleEMA200_H1, 0, 0, 1, ema200);
CopyBuffer(handleADX_H1, 0, 0, 1, adx);
CopyBuffer(handleADX_H1, 1, 0, 1, plusDI);
CopyBuffer(handleADX_H1, 2, 0, 1, minusDI);
CopyClose(_Symbol, PERIOD_H1, 0, 1, closeH1);
int htf_trend = 0;
if(closeH1[0] > ema50[0] && ema50[0] > ema200[0] && adx[0] > 35 && plusDI[0] > minusDI[0])
htf_trend = 1;
else if(closeH1[0] < ema50[0] && ema50[0] < ema200[0] && adx[0] > 35 && minusDI[0] > plusDI[0])
htf_trend = -1;
if(htf_trend == 0) return;
// Donchian Channel
double high_max = iHigh(_Symbol, _Period, iHighest(_Symbol, _Period, MODE_HIGH, InpDCPeriod, 1));
double low_min = iLow(_Symbol, _Period, iLowest(_Symbol, _Period, MODE_LOW, InpDCPeriod, 1));
double macd_hist_curr = macd_main[0] - macd_sig[0];
double macd_hist_prev = macd_main[1] - macd_sig[1];
// Entry Logic
if(htf_trend == 1 && iClose(_Symbol, _Period, 0) > high_max && macd_hist_curr > 0 && macd_hist_curr > macd_hist_prev)
{
if(PositionsTotal() == 0)
TradeOpen(ORDER_TYPE_BUY);
}
else if(htf_trend == -1 && iClose(_Symbol, _Period, 0) < low_min && macd_hist_curr < 0 && macd_hist_curr < macd_hist_prev)
{
if(PositionsTotal() == 0)
TradeOpen(ORDER_TYPE_SELL);
}
ManageTrailingStop();
}
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_Pips * _Point : price + InpSL_Pips * _Point;
double tp = (type == ORDER_TYPE_BUY) ? price + InpTP_Pips * _Point : price - InpTP_Pips * _Point;
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = InpLotSize;
request.type = type;
request.price = price;
request.sl = sl;
request.tp = tp;
request.magic = 123456;
OrderSend(request, result);
}
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 price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(price - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point)
{
double new_sl = price - InpSL_Pips * _Point;
if(new_sl > PositionGetDouble(POSITION_SL))
{
MqlTradeRequest req = {}; MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.sl = new_sl;
req.tp = PositionGetDouble(POSITION_TP);
OrderSend(req, res);
}
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(PositionGetDouble(POSITION_PRICE_OPEN) - price > InpTrailStart * _Point)
{
double new_sl = price + InpSL_Pips * _Point;
if(new_sl < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)
{
MqlTradeRequest req = {}; MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.sl = new_sl;
req.tp = PositionGetDouble(POSITION_TP);
OrderSend(req, res);
}
}
}
}
}
}