対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
もともとは、ブレイクアウト手法の「だまし」に絶望したことが開発のきっかけでした。多くのインジケーターを組み合わせても、レンジ相場では資産を削られます。そこで私は、時間帯とボラティリティを極限まで絞り込む手法を模索しました。試行錯誤を繰り返し、ようやく生き残れる条件に辿り着いた経緯があります。
このコードを手に入れることで、MT5のEA開発に費やす数百時間を節約できます。ゼロからロジックを組む手間は不要です。堅牢な土台があるため、ここから独自のフィルターを加えるだけで、あなただけのオリジナルEAを構築できるはずです。
<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.54 |
| 勝率 | 50.0% |
| 総取引数 | 140 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥75900.14 |
| 最大ドローダウン | 2.48% |
| リカバリーファクター | 3.06 |
| 期待利得 (Expected Payoff) | 0.77 |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、取引回数の少なさです。10年で140回という数字は、年平均に直すとわずか14回となります。チャンスを絞りすぎた結果、資金効率が極めて低い状態です。
また、PF1.54という数値は合格点ですが、爆発力に欠けます。ボラティリティの急拡大を待つため、長い停滞期が発生します。精神的な忍耐力が求められる設計であり、短期的に利益を求める方には不向きな仕様と言えます。
ロジックの技術的詳細
本ロジックは、ロンドン市場開始時のモメンタムを狙い撃つスナイパー形式です。
| 要素 | 設定内容 | 役割 |
|---|---|---|
| ベース時間足 | 5分足 | エントリータイミングの判定 |
| 環境認識足 | 1時間足 (EMA200) | 長期トレンドの方向性一致を確認 |
| 時間帯制限 | 16:00 〜 18:30 | ボラティリティ最大局面への凝縮 |
| ボラティリティ | ATR(14) > ATR_MA(100) * 1.5 | 異常な値動きが発生しているか判定 |
| トレンド強度 | ADX(14) > 25 且つ 上昇中 | 勢いが加速していることを確認 |
| モメンタム | RSI(9) > 60 または < 40 | 明確な方向への圧力があるか判定 |
| ブレイク判定 | ドンチアンチャネル(20) | 直近高値・安値の明確な突破を検知 |
どう改善すべきか(次なる展望)
このコードは「未完成の原石」です。さらなる進化のためには、以下の改善案が考えられます。
- 時間帯の拡張: ニューヨーク市場の主要時間帯にも適用できるか検証すること。
- ボラティリティ閾値の動的化: 固定の1.5倍ではなく、相場状況に合わせて変動させること。
- 出口戦略の精緻化: 現在の固定TP/SLではなく、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 LondonOpenMomentumBreakout(BaseStrategy):
def __init__(self):
# 【クオンツ外科処置:利益効率の極大化】
# PF 1.05 → 1.20+ および リカバリーファクターの劇的改善のため、
# リスクリワード比を 4:1 (80/20) へ拡大し、期待値を数学的に底上げする。
# トレーリングストップの開始点を引き上げ、利益を最大限に伸ばす設計に変更。
super().__init__(
name="LondonOpenMomentumBreakout",
default_tp_pips=80.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=30.0
)
# 【絶対準拠】時間足定義
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15m", "1h"]
def calculate_indicators(self, df):
"""
だまし(False Signal)を徹底的に排除するための高精度フィルタリング演算
"""
# --- 1. 上位足(1h)の環境認識(トレンドの整合性を厳格化) ---
h1_close_series = df['Close'].resample('1h').last()
# 長期トレンドの基準としてEMA200を維持しつつ、方向性を確定させる
h1_ema_series = ta.ema(h1_close_series, length=200)
df['ema200_1h'] = h1_ema_series.shift(1).reindex(df.index, method='ffill')
df['close_1h'] = h1_close_series.shift(1).reindex(df.index, method='ffill')
# --- 2. 5分足のテクニカル計算 ---
# ボラティリティ (ATR)
df.ta.atr(length=14, append=True) # ATRr_14
# トレンド強度 (ADX)
df.ta.adx(length=14, append=True) # ADX_14
# モメンタム (RSI) - 周期を14から9へ短縮し、直近の反応速度を向上
df.ta.rsi(length=9, append=True) # RSI_9
# ドンチアンチャネル - 期間を20に最適化し、ブレイクの鮮度を重視
df['DCH_20'] = df['High'].rolling(window=20).max()
df['DCL_20'] = df['Low'].rolling(window=20).min()
# ボラティリティ・ベースライン (ATRの長期平均)
# 期間を100に設定し、真に異常なボラティリティ拡大のみを抽出
df['atr_ma_long'] = df['ATRr_14'].rolling(window=100).mean()
return df
def generate_signal(self, df):
"""
リカバリーファクター 3.0 突破を目指すスナイパー形式のエントリー判定
"""
if len(df) < 200:
return None
last = df.iloc[-1]
prev = df.iloc[-2]
prev2 = df.iloc[-3]
# --- A. 時間帯フィルター (最盛期への凝縮) ---
# ロンドンオープン直後の最もボラティリティが高い 16:00 - 17:30 に限定
# 時間帯を絞ることで、低効率なレンジ相場での取引回数を削減しPFを向上させる
current_hour = df.index[-1].hour
current_minute = df.index[-1].minute
if not (16 <= current_hour < 18):
if current_hour == 18 and current_minute < 30:
pass # 18:30まで許容
else:
return None
# --- B. 高精度フィルター (だまし排除の外科処置) ---
# 1. ボラティリティの爆発判定: ATRが長期平均の 1.5倍 を超えていること
volatility_ok = (last['ATRr_14'] > last['atr_ma_long'] * 1.5)
# 2. トレンド強度の「上昇」判定: ADX > 25 かつ ADXが前本より上昇していること
# (単なる数値ではなく、勢いが加速している局面のみを抽出)
trend_strong = (last['ADX_14'] > 25) and (last['ADX_14'] > prev['ADX_14'])
# 3. RSIによるモメンタムの明確化:
# Long: RSI > 60 (強い上昇圧力) / Short: RSI < 40 (強い下降圧力)
rsi_long_ok = (last['RSI_9'] > 60)
rsi_short_ok = (last['RSI_9'] < 40)
if not (volatility_ok and trend_strong):
return None
# --- C. エントリーロジック (厳格なブレイクアウト判定) ---
# ロング条件:
# 1. 1hトレンドが強気 (Close > EMA200)
# 2. 5m終値が前本のドンチアン上限を明確に超え、かつ前本が陽線であること
if (last['close_1h'] > last['ema200_1h']) and \
(last['Close'] > prev['DCH_20']) and \
(prev['Close'] > prev['Open']) and \
rsi_long_ok:
return 'BUY'
# ショート条件:
# 1. 1hトレンドが弱気 (Close < EMA200)
# 2. 5m終値が前本のドンチアン下限を明確に下回り、かつ前本が陰線であること
elif (last['close_1h'] < last['ema200_1h']) and \
(last['Close'] < prev['DCL_20']) and \
(prev['Close'] < prev['Open']) and \
rsi_short_ok:
return 'SELL'
return None
MQL5実装コード
//+------------------------------------------------------------------+
//| LondonOpenMomentumBreakout.mq5 |
//| Copyright 2026, AI Researcher |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AI Researcher"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
// 入力パラメータ
input double InpLotSize = 0.1; // ロットサイズ
input int InpTP_Pips = 800; // 利確 (Points)
input int InpSL_Pips = 200; // 損切 (Points)
input int InpTrailStart = 300; // トレーリング開始 (Points)
input int InpDonchianPeriod = 20; // ドンチアン期間
input int InpATRPeriod = 14; // ATR期間
input int InpATRMAPeriod = 100; // ATR平均期間
input int InpRSIPeriod = 9; // RSI期間
input int InpADXPeriod = 14; // ADX期間
// ハンドル
int handle_h1_ema, handle_m5_atr, handle_m5_adx, handle_m5_rsi;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handle_h1_ema = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE);
handle_m5_atr = iATR(_Symbol, PERIOD_M5, InpATRPeriod);
handle_m5_adx = iADX(_Symbol, PERIOD_M5, InpADXPeriod);
handle_m5_rsi = iRSI(_Symbol, PERIOD_M5, InpRSIPeriod, PRICE_CLOSE);
if(handle_h1_ema == INVALID_HANDLE || handle_m5_atr == INVALID_HANDLE ||
handle_m5_adx == INVALID_HANDLE || handle_m5_rsi == INVALID_HANDLE)
{
Print("ハンドルの作成に失敗しました");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 新しい足の確定時のみ判定
static datetime last_bar;
datetime current_bar = iTime(_Symbol, PERIOD_M5, 0);
if(last_bar == current_bar) return;
last_bar = current_bar;
// 時間帯フィルター (16:00 - 18:30)
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
bool time_ok = (dt.hour >= 16 && dt.hour < 18) || (dt.hour == 18 && dt.min < 30);
if(!time_ok) return;
// インジケーター値の取得
double ema_h1[], atr_m5[], adx_m5[], rsi_m5[], close_h1[];
CopyBuffer(handle_h1_ema, 0, 1, 1, ema_h1);
CopyBuffer(handle_m5_atr, 0, 0, InpATRMAPeriod + 1, atr_m5);
CopyBuffer(handle_m5_adx, 0, 0, 2, adx_m5);
CopyBuffer(handle_m5_rsi, 0, 1, 1, rsi_m5);
CopyClose(_Symbol, PERIOD_H1, 1, 1, close_h1);
// ATRの長期平均計算
double atr_sum = 0;
for(int i=0; i<InpATRMAPeriod; i++) atr_sum += atr_m5[i];
double atr_ma = atr_sum / InpATRMAPeriod;
// ドンチアンチャネル計算
double high_20 = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, InpDonchianPeriod, 1));
double low_20 = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, InpDonchianPeriod, 1));
// 条件判定
bool vol_ok = (atr_m5[0] > atr_ma * 1.5);
bool trend_strong = (adx_m5[0] > 25 && adx_m5[0] > adx_m5[1]);
if(!vol_ok || !trend_strong) return;
double close_m5 = iClose(_Symbol, PERIOD_M5, 1);
double open_m5 = iOpen(_Symbol, PERIOD_M5, 1);
// ロングエントリー
if(close_h1[0] > ema_h1[0] && close_m5 > high_20 && close_m5 > open_m5 && rsi_m5[0] > 60)
{
TradeExecute(ORDER_TYPE_BUY);
}
// ショートエントリー
else if(close_h1[0] < ema_h1[0] && close_m5 < low_20 && close_m5 < open_m5 && rsi_m5[0] < 40)
{
TradeExecute(ORDER_TYPE_SELL);
}
ManageTrailingStop();
}
// 取引実行関数
void TradeExecute(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.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))
{
if(PositionGetInteger(POSITION_MAGIC) != 123456) continue;
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
double current_sl = PositionGetDouble(POSITION_SL);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
if(current_price - open_price > InpTrailStart * _Point)
{
double new_sl = current_price - InpSL_Pips * _Point;
if(new_sl > current_sl) ModifySL(ticket, new_sl);
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
if(open_price - current_price > InpTrailStart * _Point)
{
double new_sl = current_price + InpSL_Pips * _Point;
if(new_sl < current_sl || current_sl == 0) ModifySL(ticket, new_sl);
}
}
}
}
}
void ModifySL(ulong ticket, double new_sl)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = new_sl;
OrderSend(request, result);
}