対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
私自身、かつては単純なブレイクアウト手法に依存していました。しかし、相場の「だまし」に何度も資金を削られました。そこで、大口投資家が仕掛ける「ストップ狩り」の動きに着目しました。リクイディティ(流動性)の回収を待ってからエントリーする設計に苦心した記憶があります。
このコードをベースにすれば、MT5のEA開発にかかる数百時間を節約できます。ゼロから構築するよりも効率的です。自分だけのオリジナルEAを作るための最高の土台として活用してください。
<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.47 |
| 勝率 | 58.7% |
| 総取引数 | 75 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥29589.80 |
| 最大ドローダウン | 2.47% |
| リカバリーファクター | 1.20 |
| 期待利得 (Expected Payoff) | 0.61 |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、取引数の少なさです。10年で75回という回数は、機会損失が非常に多いことを意味します。フィルターを厳しくしすぎた結果です。
また、PF1.47という数字は安定していますが、資産を急激に増やす力はありません。トレンドが発生しないレンジ相場では、全くエントリーしません。効率的に稼ぐには、ボラティリティの低い局面での対応策が不足しています。
ロジックの技術的詳細
本ロジックは、上位足のトレンド方向へ、短期的な流動性の回収を確認してエントリーします。
| 項目 | 設定内容 | 役割 |
|---|---|---|
| 時間足 | 5分足(USDJPY専用) | 短期的な構造変化の検知 |
| 上位足フィルター | H1 EMA(20) > EMA(100) 且つ 傾き正 | 長期的な方向性の合致 |
| ボラティリティ | ATR(10) > ATR_SMA(20) | 相場の活性化を確認 |
| トレンド強度 | ADX(14) > 30 且つ 3本連続上昇 | 強いトレンドの発生を判定 |
| リクイディティ | 15時台の高値・安値の更新後の回帰 | ストップ狩り後の反転を検知 |
| トリガー | 直近3本の高値/安値を終値でブレイク | エントリータイミングの確定 |
| 決済 | TP 70pips / SL 20pips | リスクリワード 1:3.5 の設計 |
どう改善すべきか(次なる展望)
このロジックをさらに進化させるには、時間帯の最適化が不可欠です。現在はロンドンオープンのみに絞っています。ニューヨーク市場の特性に合わせたフィルターを追加すれば、取引数は向上します。
また、固定のTP/SLではなく、ATRに基づいた可変決済の導入を推奨します。相場のボラティリティに応じて利確幅を変えることで、PFの向上が見込めます。プロの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 SessionLiquidityShiftStrategy(BaseStrategy):
def __init__(self):
# 【クオンツ外科手術】PF 1.09 -> 1.20+ および RF 1.24 -> 3.00+ を目指す
# 期待値(Payoff)を劇的に向上させるため、RR比を 1:3.5 に再設計
# SLを20pipsにタイト化し、TPを70pipsへ拡大。
# トレーリングストップの開始を20pips(SL分)に設定し、建値への移行を早めてDDを抑制
super().__init__(
name="SessionLiquidityShift_Quants_v3",
default_tp_pips=70.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=20.0
)
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15min", "1h"]
def calculate_indicators(self, df):
"""
だまし(False Sign)を排除するための高精度フィルタリング実装
"""
# --- 1. 上位足(1h)トレンドフィルターの厳格化 ---
h1_close_series = df['Close'].resample('1h').last().shift(1)
h1_df = pd.DataFrame({'Close': h1_close_series})
# トレンドの方向性だけでなく、「乖離の拡大(モメンタム)」を確認
h1_ema_fast = ta.ema(h1_df['Close'], length=20)
h1_ema_slow = ta.ema(h1_df['Close'], length=100)
h1_slope = h1_ema_fast.diff(1)
df['h1_close'] = h1_close_series.reindex(df.index, method='ffill')
df['h1_ema_fast'] = h1_ema_fast.reindex(df.index, method='ffill')
df['h1_ema_slow'] = h1_ema_slow.reindex(df.index, method='ffill')
df['h1_slope'] = h1_slope.reindex(df.index, method='ffill')
# --- 2. ボラティリティ・トレンド強度フィルター ---
# ATRの期間を短縮して直近のボラティリティ変化に敏感に反応させる
df.ta.atr(length=10, append=True)
atr_col = 'ATRr_10'
df['atr_sma'] = df[atr_col].rolling(window=20).mean()
# ADX閾値を30に維持しつつ、ADXが「上昇トレンドにあること」を厳格に判定
df.ta.adx(length=14, append=True)
adx_col = 'ADX_14'
# 直近3本のADXがすべて上昇していることを条件に加える(だまし排除)
df['adx_strong_rising'] = (df[adx_col] > df[adx_col].shift(1)) & \
(df[adx_col].shift(1) > df[adx_col].shift(2))
# --- 3. セッションリクイディティ・ロジックの再構築 ---
# プレレンジ定義 (15:00 - 16:00)
is_pre_open = (df.index.hour == 15)
df['pre_high'] = df['High'].where(is_pre_open).groupby(df.index.date).transform('max')
df['pre_low'] = df['Low'].where(is_pre_open).groupby(df.index.date).transform('min')
# 【重要】単なる下抜け(dipped)ではなく、「下抜けてから戻した(Reclaim)」ことを検知
# これにより、単なるトレンド崩壊ではなく「ストップ狩り後の反転」を特定する
df['grab_long'] = (df['Low'] < df['pre_low']) & (df['Close'] > df['pre_low'])
df['grab_short'] = (df['High'] > df['pre_high']) & (df['Close'] < df['pre_high'])
# 過去12本(1時間)以内にリクイディティ・グラブが発生したかを判定
df['has_grabbed_long'] = df['grab_long'].rolling(window=12).max() > 0
df['has_grabbed_short'] = df['grab_short'].rolling(window=12).max() > 0
# --- 4. エントリー条件のベクトル判定 ---
# 時間帯: ロンドンオープン (16:00 - 19:00)
df['session_ok'] = (df.index.hour >= 16) & (df.index.hour <= 19)
# 環境: ボラティリティ拡大 且つ ADXが強く上昇中
df['regime_ok'] = (df[atr_col] > df['atr_sma']) & (df[adx_col] > 30) & (df['adx_strong_rising'])
# MTF同期: H1でEMA20 > EMA100 且つ 傾きが正
df['trend_long'] = (df['h1_ema_fast'] > df['h1_ema_slow']) & (df['h1_slope'] > 0)
df['trend_short'] = (df['h1_ema_fast'] < df['h1_ema_slow']) & (df['h1_slope'] < 0)
# トリガー: 直近3本の高値/安値を終値で明確にブレイク (ノイズ除去)
df['trigger_long'] = (df['Close'] > df['High'].shift(1).rolling(window=3).max())
df['trigger_short'] = (df['Close'] < df['Low'].shift(1).rolling(window=3).min())
return df
def generate_signal(self, df):
"""
外科的に調整されたフィルタリング条件に基づくシグナル生成
"""
if len(df) < 20:
return None
last = df.iloc[-1]
# ロング: セッション内 & 強トレンド & H1整合性 & 下値リクイディティ回収済 & 構造変化
if (last['session_ok'] and
last['regime_ok'] and
last['trend_long'] and
last['has_grabbed_long'] and
last['trigger_long']):
return 'BUY'
# ショート: セッション内 & 強トレンド & H1整合性 & 上値リクイディティ回収済 & 構造変化
if (last['session_ok'] and
last['regime_ok'] and
last['trend_short'] and
last['has_grabbed_short'] and
last['trigger_short']):
return 'SELL'
return None
MT5用 MQL5実装コード
//+------------------------------------------------------------------+
//| SessionLiquidityShift_Quants.mq5|
//| Copyright 2026, System Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link "https://your-blog-link.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
// 入力パラメータ
input int InpEMA_Fast = 20; // H1 EMA Fast
input int InpEMA_Slow = 100; // H1 EMA Slow
input int InpADX_Period = 14; // ADX Period
input int InpATR_Period = 10; // ATR Period
input double InpTP_Pips = 70.0; // Take Profit (Pips)
input double InpSL_Pips = 20.0; // Stop Loss (Pips)
input double InpTrailStart = 20.0; // Trailing Start (Pips)
input double InpLotSize = 0.1; // Lot Size
// グローバル変数
int handle_ema_fast, handle_ema_slow, handle_adx, handle_atr;
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handle_ema_fast = iMA(_Symbol, PERIOD_H1, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
handle_ema_slow = iMA(_Symbol, PERIOD_H1, InpEMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
handle_adx = iADX(_Symbol, PERIOD_CURRENT, InpADX_Period);
handle_atr = iATR(_Symbol, PERIOD_CURRENT, InpATR_Period);
if(handle_ema_fast == INVALID_HANDLE || handle_ema_slow == INVALID_HANDLE ||
handle_adx == INVALID_HANDLE || handle_atr == INVALID_HANDLE) return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 新しい足の確定時に判定
if(!isNewBar()) return;
MqlDateTime dt;
TimeCurrent(dt);
// 1. セッション判定 (16:00 - 19:00)
if(dt.hour < 16 || dt.hour > 19) return;
// 2. H1トレンド判定
double ema_f[2], ema_s[2];
CopyBuffer(handle_ema_fast, 0, 1, 2, ema_f);
CopyBuffer(handle_ema_slow, 0, 1, 2, ema_s);
bool trend_long = (ema_f[0] > ema_s[0]) && (ema_f[0] > ema_f[1]);
bool trend_short = (ema_f[0] < ema_s[0]) && (ema_f[0] < ema_f[1]);
// 3. ボラティリティ・ADX判定
double adx[3], atr[21];
CopyBuffer(handle_adx, 0, 1, 3, adx);
CopyBuffer(handle_atr, 0, 1, 21, atr);
double atr_sma = 0;
for(int i=0; i<20; i++) atr_sma += atr[i];
atr_sma /= 20.0;
bool regime_ok = (atr[0] > atr_sma) && (adx[0] > 30) && (adx[0] > adx[1]) && (adx[1] > adx[2]);
if(!regime_ok) return;
// 4. リクイディティ判定 (15時台のレンジ)
double pre_high = -1, pre_low = 999999;
MqlRates rates[];
int copied = CopyRates(_Symbol, PERIOD_M5, 0, 288, rates); // 過去12時間分
for(int i=0; i<copied; i++) {
MqlDateTime r_dt;
TimeToStruct(rates[i].time, r_dt);
if(r_dt.day == dt.day && r_dt.hour == 15) {
if(rates[i].high > pre_high) pre_high = rates[i].high;
if(rates[i].low < pre_low) pre_low = rates[i].low;
}
}
// リクイディティ・グラブ確認 (過去12本分)
bool has_grabbed_long = false;
bool has_grabbed_short = false;
for(int i=1; i<=12; i++) {
if(rates[copied-1-i].low < pre_low && rates[copied-1-i].close > pre_low) has_grabbed_long = true;
if(rates[copied-1-i].high > pre_high && rates[copied-1-i].close < pre_high) has_grabbed_short = true;
}
// 5. トリガー判定 (直近3本の高値安値ブレイク)
double highest_3 = MathMax(rates[copied-2].high, MathMax(rates[copied-3].high, rates[copied-4].high));
double lowest_3 = MathMin(rates[copied-2].low, MathMin(rates[copied-3].low, rates[copied-4].low));
double close_now = rates[copied-1].close;
// エントリー実行
if(trend_long && has_grabbed_long && close_now > highest_3) {
double sl = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL_Pips * _Point * 10;
double tp = SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpTP_Pips * _Point * 10;
trade.Buy(InpLotSize, _Symbol, 0, sl, tp);
}
else if(trend_short && has_grabbed_short && close_now < lowest_3) {
double sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL_Pips * _Point * 10;
double tp = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpTP_Pips * _Point * 10;
trade.Sell(InpLotSize, _Symbol, 0, sl, tp);
}
ApplyTrailingStop();
}
// トレーリングストップ関数
void ApplyTrailingStop() {
for(int i=PositionsTotal()-1; i>=0; i--) {
if(PositionGetSymbol(i) == _Symbol) {
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_sl = PositionGetDouble(POSITION_SL);
double current_bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double current_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
if(current_bid - open_price > InpTrailStart * _Point * 10) {
double new_sl = current_bid - InpSL_Pips * _Point * 10;
if(new_sl > current_sl) trade.PositionModify(PositionGetTicket(i), new_sl, PositionGetDouble(POSITION_TP));
}
} else {
if(open_price - current_ask > InpTrailStart * _Point * 10) {
double new_sl = current_ask + InpSL_Pips * _Point * 10;
if(new_sl < current_sl || current_sl == 0) trade.PositionModify(PositionGetTicket(i), new_sl, PositionGetDouble(POSITION_TP));
}
}
}
}
}
bool isNewBar() {
static datetime last_time = 0;
datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
if(last_time != current_time) {
last_time = current_time;
return true;
}
return false;
}