対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
もともと私は、アジアレンジのブレイクアウト手法に傾倒していました。しかし、多くの「だまし」に資金を削られました。なぜ価格は節目を抜けた直後に反転するのか。その答えを求めて、リクイディティ・グラブ(流動性の確保)という概念に辿り着きました。大口投資家が仕掛ける罠を逆手に取るロジックを、試行錯誤して構築したのが本システムです。
このコードをベースにすれば、MT5のEA開発にかかる数百時間を節約できます。堅牢なリスク管理と時間フィルターの組み込み方は、最高の土台となるはずです。自分だけのオリジナルEAを作るための研究素材として活用してください。
<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.21 |
| 勝率 | 42.0% |
| 総取引数 | 88 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥14475.20 |
| 最大ドローダウン | 3.50% |
| リカバリーファクター | 0.41 |
| 期待利得 (Expected Payoff) | 0.70 |
今回の検証結果の「限界」と「ダメ出し」
この成績を見て、物足りなさを感じる方は多いはずです。PF1.21という数字は、決して高いとは言えません。最大の要因は、エントリー条件を厳格にしすぎたことです。
10年で88回という取引数は、あまりに少なすぎます。ボラティリティとトレンド強度のフィルターを重ねた結果、多くのチャンスを逃しました。また、勝率42%という低さは、リクイディティ・グラブの判定が単純であるためです。相場のノイズを拾い、損切りにかかる局面が散見されました。
ロジックの技術的詳細
本ロジックは、マルチタイムフレーム(MTF)分析と時間帯制限を組み合わせた戦略です。
| 要素 | 設定・条件 | 目的 |
|---|---|---|
| メイン時間足 | 5分足 | 精緻なエントリータイミングの計測 |
| トレンド判定 | 1時間足 EMA200 / EMA50 | 長期的な方向性の合致を確認 |
| ボラティリティ | 15分足 ATR > ATR(10)平均 | 市場の活性化を確認し、停滞相場を排除 |
| トレンド強度 | 15分足 ADX > 25 | 明確な方向感がある時のみに限定 |
| 時間フィルター | ロンドン時間 (16:00 - 19:00) | 流動性が高まる時間帯に集中 |
| エントリー条件 | アジアレンジ突破後の反転 + 強力な確定足 | 大口の罠(Fakeout)を検知して逆張り |
どう改善すべきか(次なる展望)
このロジックを「化けさせる」には、固定的なフィルターから動的な判断への移行が必要です。
例えば、ADXの閾値を固定せず、直近の相場環境に合わせて最適化させる手法があります。また、ドルインデックス(DXY)などの相関通貨ペアをフィルターに加えれば、精度はさらに向上するでしょう。
プロのEAは、ここからさらに「ニュースフィルター」や「時間帯別の動的利確」を組み込んでいます。この無料コードを土台にして、あなただけの独自の環境認識を付け加えてみてください。
Pythonソースコード
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
import numpy as np
class LondonLiquidityGrabStrategy(BaseStrategy):
def __init__(self):
# 【クオンツ調整】PF 0.68 -> 1.20+ への改善
# ペイオフレシオが1.00に低下しているため、期待値を上げるためTPを現実的な範囲に下げ、勝率を底上げ
# トレーリングストップの開始点を早め、利益確定の確実性を高める
super().__init__(
name="LondonLiquidityGrab_V3_Optimized",
default_tp_pips=40.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=12.0
)
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15min", "1h"]
def calculate_indicators(self, df):
"""
【絶対準拠ルール 8】完全ベクトル演算による高速計算実装
"""
# 1. MTFトレンド判定 (1時間足 EMA 200 + EMA 50)
# ルックアヘッドバイアス排除のため shift(1) を徹底
df_1h = df[['Close']].resample('1h').last().shift(1)
df_1h['ema_200'] = ta.ema(df_1h['Close'], length=200)
df_1h['ema_50'] = ta.ema(df_1h['Close'], length=50)
df = df.join(df_1h[['ema_200', 'ema_50']].reindex(df.index, method='ffill'))
# 2. 短期モメンタムフィルター (5分足 EMA 20)
# 価格が短期EMAより上/下にあることを条件に加え、反転の勢いを確認し「だまし」を排除
df['ema_20'] = ta.ema(df['Close'], length=20)
# 3. ボラティリティ・強度判定 (15分足 ATR, ADX)
resampled_15m = df[['Open', 'High', 'Low', 'Close']].resample('15min').agg({
'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'
}).shift(1)
resampled_15m['atr'] = ta.atr(resampled_15m['High'], resampled_15m['Low'], resampled_15m['Close'], length=14)
# ADX期間を14に回帰させつつ、閾値を25に引き上げ、明確なトレンド時のみに限定
adx_df = ta.adx(resampled_15m['High'], resampled_15m['Low'], resampled_15m['Close'], length=14)
resampled_15m['adx'] = adx_df['ADX_14']
df = df.join(resampled_15m[['atr', 'adx']].reindex(df.index, method='ffill'))
# 4. アジアレンジの算出 (09:00 - 15:00)
df['date'] = df.index.date
asian_mask = (df.index.hour >= 9) & (df.index.hour < 15)
asian_highs = df.loc[asian_mask, 'High'].groupby(df['date']).max()
asian_lows = df.loc[asian_mask, 'Low'].groupby(df['date']).min()
df['asian_high'] = df['date'].map(asian_highs)
df['asian_low'] = df['date'].map(asian_lows)
# 未来参照排除
df.loc[df.index.hour < 15, ['asian_high', 'asian_low']] = np.nan
df['asian_high'] = df['asian_high'].ffill()
df['asian_low'] = df['asian_low'].ffill()
# 5. フィルター条件の外科的調整
# ATRの移動平均期間を短縮(10)し、直近のボラティリティ変化に敏感に反応させる
df['atr_sma'] = df['atr'].rolling(10).mean()
df['volatility_ok'] = df['atr'] > df['atr_sma']
# ADX閾値を25に設定し、レンジ相場でのエントリーを厳格に排除
df['strength_ok'] = df['adx'] > 25
# 1時間足のパーフェクトオーダー条件
df['trend_up'] = (df['Close'] > df['ema_50']) & (df['ema_50'] > df['ema_200'])
df['trend_down'] = (df['Close'] < df['ema_50']) & (df['ema_50'] < df['ema_200'])
# ロンドン時間フィルター (16:00 - 19:00に集中させ、効率を最大化)
df['london_time'] = (df.index.hour >= 16) & (df.index.hour < 19)
# 6. リクイディティ・グラブ(Fakeout)の検知ロジック強化
# 単なる価格の戻りではなく、「強い反転キャンドル(実体が下ヒゲ/上ヒゲより大きい)」を条件に追加
body_size = (df['Close'] - df['Open']).abs()
candle_range = df['High'] - df['Low']
strong_reversal = body_size > (candle_range * 0.4) # 実体がレンジの40%以上を占める強い確定足
# Long: 前足Low < AsianLow AND 今足Close > AsianLow AND 陽線 AND 強力な反転 AND 短期EMA超え
df['grab_long'] = (df['Low'].shift(1) < df['asian_low']) & \
(df['Close'] > df['asian_low']) & \
(df['Close'] > df['Open']) & \
strong_reversal & \
(df['Close'] > df['ema_20'])
# Short: 前足High > AsianHigh AND 今足Close < AsianHigh AND 陰線 AND 強力な反転 AND 短期EMA割れ
df['grab_short'] = (df['High'].shift(1) > df['asian_high']) & \
(df['Close'] < df['asian_high']) & \
(df['Close'] < df['Open']) & \
strong_reversal & \
(df['Close'] < df['ema_20'])
return df
def generate_signal(self, df):
"""
【絶対準拠ルール 3】最新行に基づいたシグナル判定
"""
if len(df) < 2:
return None
last = df.iloc[-1]
# 共通フィルター: ロンドン時間 && ボラティリティ拡大 && トレンド強度あり
if not (last['london_time'] and last['volatility_ok'] and last['strength_ok']):
return None
# Longシグナル: 1Hトレンド合致 && 強化版リクイディティ・グラブ発生
if last['trend_up'] and last['grab_long']:
return 'BUY'
# Shortシグナル: 1Hトレンド合致 && 強化版リクイディティ・グラブ発生
if last['trend_down'] and last['grab_short']:
return 'SELL'
return None
MQL5コード(MT5用)
//+------------------------------------------------------------------+
//| LondonLiquidityGrab_V3_Opt.mq5 |
//| Copyright 2026, System Trader |
//| https://your-site.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link "https://your-site.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
// 入力パラメータ
input double InpLotSize = 0.1; // ロット数
input int InpTP = 400; // 利確 (points)
input int InpSL = 200; // 損切り (points)
input int InpTrailStart = 120; // トレーリング開始 (points)
input int InpEMA_Fast = 50; // 1H EMA Fast
input int InpEMA_Slow = 200; // 1H EMA Slow
input int InpEMA_Short = 20; // 5M EMA Short
input int InpADX_Period = 14; // ADX Period
input int InpATR_Period = 14; // ATR Period
// グローバル変数
CTrade trade;
int handle_ema_fast_1h, handle_ema_slow_1h, handle_ema_short_5m, handle_adx_15m, handle_atr_15m;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handle_ema_fast_1h = iMA(_Symbol, PERIOD_H1, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
handle_ema_slow_1h = iMA(_Symbol, PERIOD_H1, InpEMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
handle_ema_short_5m = iMA(_Symbol, PERIOD_M5, InpEMA_Short, 0, MODE_EMA, PRICE_CLOSE);
handle_adx_15m = iADX(_Symbol, PERIOD_M15, InpADX_Period);
handle_atr_15m = iATR(_Symbol, PERIOD_M15, InpATR_Period);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 新しい足の確定時に判定
if(!IsNewBar()) return;
MqlDateTime dt;
TimeCurrent(dt);
// ロンドン時間フィルター (16:00 - 19:00)
if(dt.hour < 16 || dt.hour >= 19) return;
// 指標値の取得
double ema_fast_1h = GetMA(handle_ema_fast_1h, 1);
double ema_slow_1h = GetMA(handle_ema_slow_1h, 1);
double ema_short_5m = GetMA(handle_ema_short_5m, 1);
double adx_15m = GetADX(handle_adx_15m, 1);
double atr_15m = GetATR(handle_atr_15m, 1);
// ATRの10期間平均を算出
double atr_sum = 0;
for(int i=1; i<=10; i++) atr_sum += GetATR(handle_atr_15m, i);
double atr_sma = atr_sum / 10.0;
// ボラティリティ・強度フィルター
if(atr_15m <= atr_sma || adx_15m <= 25) return;
// アジアレンジ算出 (09:00 - 15:00)
double asian_high = 0, asian_low = 0;
GetAsianRange(asian_high, asian_low);
// キャンドルデータの取得
MqlRates rates[];
ArraySetAsSeries(rates, true);
CopyRates(_Symbol, PERIOD_M5, 0, 2, rates);
double close0 = rates[0].close;
double open0 = rates[0].open;
double high0 = rates[0].high;
double low0 = rates[0].low;
double close1 = rates[1].close;
double high1 = rates[1].high;
double low1 = rates[1].low;
// 強い反転キャンドルの判定 (実体がレンジの40%以上)
double body_size = MathAbs(close0 - open0);
double candle_range = high0 - low0;
bool strong_reversal = (candle_range > 0) ? (body_size > candle_range * 0.4) : false;
// トレンド判定
bool trend_up = (close0 > ema_fast_1h && ema_fast_1h > ema_slow_1h);
bool trend_down = (close0 < ema_fast_1h && ema_fast_1h < ema_slow_1h);
// エントリーロジック
// Long: 前足Low < AsianLow AND 今足Close > AsianLow AND 陽線 AND 反転 AND EMA20超え
if(trend_up && low1 < asian_low && close0 > asian_low && close0 > open0 && strong_reversal && close0 > ema_short_5m)
{
if(PositionsTotal() == 0)
trade.Buy(InpLotSize, _Symbol, SymbolInfoDouble(_Symbol, SYMBOL_ASK),
SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point,
SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpTP * _Point);
}
// Short: 前足High > AsianHigh AND 今足Close < AsianHigh AND 陰線 AND 反転 AND EMA20割れ
if(trend_down && high1 > asian_high && close0 < asian_high && close0 < open0 && strong_reversal && close0 < ema_short_5m)
{
if(PositionsTotal() == 0)
trade.Sell(InpLotSize, _Symbol, SymbolInfoDouble(_Symbol, SYMBOL_BID),
SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point,
SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpTP * _Point);
}
ManageTrailingStop();
}
// --- 補助関数 ---
double GetMA(int handle, int shift) {
double buf[];
CopyBuffer(handle, 0, shift, 1, buf);
return buf[0];
}
double GetADX(int handle, int shift) {
double buf[];
CopyBuffer(handle, 0, shift, 1, buf);
return buf[0];
}
double GetATR(int handle, int shift) {
double buf[];
CopyBuffer(handle, 0, shift, 1, buf);
return buf[0];
}
void GetAsianRange(double &high, double &low) {
MqlRates rates[];
ArraySetAsSeries(rates, true);
CopyRates(_Symbol, PERIOD_M5, 0, 500, rates);
high = 0; low = 999999;
for(int i=0; i<500; i++) {
MqlDateTime dt;
TimeToStruct(rates[i].time, dt);
if(dt.hour >= 9 && dt.hour < 15) {
if(rates[i].high > high) high = rates[i].high;
if(rates[i].low < low) low = rates[i].low;
}
if(dt.hour < 9) break;
}
}
bool IsNewBar() {
static datetime last_bar = 0;
datetime curr_bar = iTime(_Symbol, PERIOD_M5, 0);
if(last_bar != curr_bar) {
last_bar = curr_bar;
return true;
}
return false;
}
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 - InpTrailStart * _Point;
if(new_sl > PositionGetDouble(POSITION_SL)) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
} 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 + InpTrailStart * _Point;
if(new_sl < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
}
}
}
}