対象通貨ペア:GBPJPY / 使用時間足:5分足専用
もともとは、ブレイクアウト手法の「だまし」に悩み抜いたことが始まりでした。多くの指標を組み合わせても、結局は相場に飲み込まれます。そこで、複数の時間足を完全に同期させる手法を模索しました。試行錯誤の末、極めて保守的なフィルタリングを実装したのが本ロジックです。
このコードを入手すれば、MT5のEA開発に費やす数百時間を節約できます。ゼロから構築する手間を省き、検証済みの土台を手に入れられるのは大きな利点です。自分だけのオリジナルEAを作るための、最高の研究素材として活用してください。

<CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>
これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 2.60 |
| 勝率 | 63.6% |
| 総取引数 | 22 回 |
| 純利益 (0.1ロット/1万通貨時) | ¥30789.55 |
| 最大ドローダウン | 1.74% |
| リカバリーファクター | 1.76 |
| 期待利得 (Expected Payoff) | 0.94 |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、取引回数の少なさです。10年間で22回という数字は、実運用では効率が悪すぎます。PFや最大ドローダウンの数値は優秀に見えます。しかし、これはフィルターを厳しくしすぎた結果です。
統計的な母数が不足しているため、将来的に同じ成績が出る保証はありません。また、トレンドが発生したときだけ的にエントリーする設計です。そのため、レンジ相場では完全に沈黙します。機会損失を許容しすぎた、極めて保守的な設計であるといえます。
ロジックの技術的詳細
本ロジックは、上位足のトレンド方向と執行足のモメンタムを同期させます。以下の表に、使用している指標と判定条件をまとめました。
| 分類 | 指標・条件 | 判定内容 |
|---|---|---|
| 環境認識 | CHOP / ADX | トレンドの質を判定(CHOP<45 かつ ADX>30) |
| MTF同期 | 1h / 4h / 1d | 全時間足のトレンド方向が完全に一致すること |
| トリガー | Keltner Channel | 執行足の価格がチャネル上限・下限を突破 |
| フィルター | RSI | 過熱感を排除(買い:55〜70 / 売り:30〜45) |
| 方向確認 | EMA 20 | 価格がEMAの上方(買い)または下方(売り)にあること |
shift(1) を適用しています。これにより、確定足のみで判定し、先読みバイアスを完全に排除した厳格な検証を行っています。
どう改善すべきか(次なる展望)
このコードをベースに改造するなら、フィルターの緩和を推奨します。例えば、ADXの閾値を少し下げるだけで、取引回数は大幅に増えるはずです。
また、固定のTP/SLではなく、ボラティリティ(ATR)に基づいた可変幅の導入も有効です。プロのEAは、相場の変動率に合わせて決済ポイントを最適化しています。ここに独自の環境認識フィルターを一つ加えれば、化ける可能性を秘めた原石といえます。
公開Pythonコード
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class QTrendAlignmentEngine(BaseStrategy):
def __init__(self):
# 【クオンツ調整】PF向上とDD抑制のため、リスクリワード比を最適化し、トレーリング幅を拡大
super().__init__(
name="Q-Trend Alignment Engine V2",
default_tp_pips=60.0, # 期待値向上のためTPを微増
default_sl_pips=30.0, # リスク限定
enable_trailing_stop=True,
trail_start_pips=15.0 # 利益確保のタイミングを最適化
)
# 【絶対遵守】執行足および上位足構成
self.base_timeframe = "15min"
self.vision_timeframes = ["15min", "1h", "4h", "1d"]
def calculate_indicators(self, df):
"""
高精度クオンツフィルタリングを実装したテクニカル指標計算
ルックアヘッドバイアスを排除した厳格なMTF処理
"""
# --- 1. 執行足 (15min) の指標計算 ---
# レンジフィルターの閾値を厳格化 (CHOP値を下げ、ADX値を上げ、だましを排除)
df['CHOP'] = ta.chop(df['High'], df['Low'], df['Close'], length=14)
adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14)
df['ADX'] = adx_df['ADX_14'] if adx_df is not None else 0
# エントリートリガー (Keltner Channel)
# スカラー値を2.0 -> 2.5へ引き上げ、真のブレイクアウトのみを抽出
kc = ta.kc(df['High'], df['Low'], df['Close'], length=20, scalar=2.5)
df['KC_Upper'] = kc['KCUe_20_2.5'] if kc is not None else 0
df['KC_Lower'] = kc['KCLe_20_2.5'] if kc is not None else 0
# モメンタム・トレンド確認
df['RSI'] = ta.rsi(df['Close'], length=14)
df['EMA20'] = ta.ema(df['Close'], length=20)
# --- 2. マルチタイムフレーム (MTF) 環境認識 ---
# 全てのHTFにおいて shift(1) を適用し、確定足のみで判定することで先読みを完全排除
# HTF 1 (1h): EMA 20 > EMA 100 (中期トレンドの安定性を重視し期間を延長)
df_1h_close = df['Close'].resample('1h').last()
ema20_1h = ta.ema(df_1h_close, length=20)
ema100_1h = ta.ema(df_1h_close, length=100)
df['MTF_1h_Trend'] = ((ema20_1h > ema100_1h).astype(int).shift(1)).reindex(df.index, method='ffill')
# HTF 2 (4h): Supertrend 方向判定 (期間を10->12へ調整しノイズを軽減)
df_4h_high = df['High'].resample('4h').max()
df_4h_low = df['Low'].resample('4h').min()
df_4h_close = df['Close'].resample('4h').last()
st_4h = ta.supertrend(df_4h_high, df_4h_low, df_4h_close, length=12, multiplier=3.0)
if st_4h is not None:
st_trend = st_4h.filter(like='SUPERTd').iloc[:, 0]
df['MTF_4h_Trend'] = st_trend.shift(1).reindex(df.index, method='ffill')
else:
df['MTF_4h_Trend'] = 0
# HTF 3 (1d): Price > EMA 200 (大局トレンドの絶対基準)
df_1d_close = df['Close'].resample('1d').last()
ema200_1d = ta.ema(df_1d_close, length=200)
df['MTF_1d_Trend'] = ((df_1d_close > ema200_1d).astype(int).shift(1)).reindex(df.index, method='ffill')
# 0/1 形式を -1/1 形式に正規化
df['MTF_1d_Trend'] = df['MTF_1d_Trend'].replace({0: -1, 1: 1})
df['MTF_1h_Trend'] = df['MTF_1h_Trend'].replace({0: -1, 1: 1})
return df
def generate_signal(self, df):
"""
クオンツ評価に基づいた厳格なエントリーフィルタリング
"""
if len(df) < 200:
return None
last = df.iloc[-1]
# 1. 相場環境フィルター (トレンドの質を極めて厳格に判定)
# CHOP < 45 (強いトレンド) AND ADX > 30 (十分な強度) に引き上げ、取引数を絞りPFを向上
is_trending = (last['CHOP'] < 45) and (last['ADX'] > 30)
if not is_trending:
return None
# 2. マルチタイムフレーム同期 (全時間足の完全一致を要求)
mtf_long = (last['MTF_1d_Trend'] == 1) and (last['MTF_4h_Trend'] == 1) and (last['MTF_1h_Trend'] == 1)
mtf_short = (last['MTF_1d_Trend'] == -1) and (last['MTF_4h_Trend'] == -1) and (last['MTF_1h_Trend'] == -1)
# 3. エントリートリガー (執行足でのモメンタム爆発 + 過熱感の排除)
# Long: KC上限突破 + RSIが強気(55-70) + EMA20上方
# RSI 70以上は過熱しすぎと判断し、逆張りリスクを排除
if mtf_long:
if (last['Close'] > last['KC_Upper']) and \
(55 < last['RSI'] < 70) and \
(last['Close'] > last['EMA20']):
return 'BUY'
# Short: KC下限突破 + RSIが弱気(30-45) + EMA20下方
# RSI 30以下は売られすぎと判断し、リバウンドリスクを排除
if mtf_short:
if (last['Close'] < last['KC_Lower']) and \
(30 < last['RSI'] < 45) and \
(last['Close'] < last['EMA20']):
return 'SELL'
return None
MQL5コード(MT5用 EA)
//+------------------------------------------------------------------+
//| QTrendAlignmentEngine_V2.mq5 |
//| Copyright 2026, AI Researcher |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AI Researcher"
#property link "https://example.com"
#property version "1.00"
#property strict
// 入力パラメータ
input double InpLotSize = 0.1; // ロットサイズ
input int InpTP_Pips = 600; // 利確 (Points)
input int InpSL_Pips = 300; // 損切 (Points)
input int InpTrailStart = 150; // トレーリング開始 (Points)
input int InpKC_Period = 20; // Keltner Channel 期間
input double InpKC_Scalar = 2.5; // Keltner Channel スカラー
input int InpRSI_Period = 14; // RSI 期間
input int InpEMA_Fast = 20; // EMA 短期
input int InpEMA_Slow = 100; // EMA 中期 (1h)
input int InpEMA_Daily = 200; // EMA 日足
// ハンドル
int handle_rsi, handle_ema20, handle_adx;
int handle_ema20_1h, handle_ema100_1h;
int handle_ema200_1d;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handle_rsi = iRSI(_Symbol, _Period, InpRSI_Period, PRICE_CLOSE);
handle_ema20 = iMA(_Symbol, _Period, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
handle_adx = iADX(_Symbol, _Period, 14);
handle_ema20_1h = iMA(_Symbol, PERIOD_H1, InpEMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
handle_ema100_1h = iMA(_Symbol, PERIOD_H1, InpEMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
handle_ema200_1d = iMA(_Symbol, PERIOD_D1, InpEMA_Daily, 0, MODE_EMA, PRICE_CLOSE);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Keltner Channel 計算関数 |
//+------------------------------------------------------------------+
double GetKC(bool upper)
{
double ema[], atr[];
int handle_atr = iATR(_Symbol, _Period, InpKC_Period);
CopyBuffer(handle_ema20, 0, 1, 1, ema);
CopyBuffer(handle_atr, 0, 1, 1, atr);
if(upper) return ema[0] + (atr[0] * InpKC_Scalar);
return ema[0] - (atr[0] * InpKC_Scalar);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 確定足判定のため、新しいバーの開始時のみチェックすることを推奨
if(!IsNewBar()) return;
double rsi[], ema20[], adx[], ema20_1h[], ema100_1h[], ema200_1d[], close_1d[];
CopyBuffer(handle_rsi, 0, 1, 1, rsi);
CopyBuffer(handle_ema20, 0, 1, 1, ema20);
CopyBuffer(handle_adx, 0, 1, 1, adx);
CopyBuffer(handle_ema20_1h, 0, 1, 1, ema20_1h);
CopyBuffer(handle_ema100_1h, 0, 1, 1, ema100_1h);
CopyBuffer(handle_ema200_1d, 0, 1, 1, ema200_1d);
CopyClose(_Symbol, PERIOD_D1, 1, 1, close_1d);
double close_curr = iClose(_Symbol, _Period, 1);
// 1. MTFトレンド判定
bool mtf_long = (ema20_1h[0] > ema100_1h[0]) && (close_1d[0] > ema200_1d[0]);
bool mtf_short = (ema20_1h[0] < ema100_1h[0]) && (close_1d[0] < ema200_1d[0]);
// 2. 環境フィルター (ADX)
// ※CHOPは標準関数にないため、ADXによるトレンド強度判定を優先
bool is_trending = (adx[0] > 30);
if(!is_trending) return;
// 3. エントリーロジック
if(mtf_long && close_curr > GetKC(true) && rsi[0] > 55 && rsi[0] < 70 && close_curr > ema20[0])
{
ExecuteTrade(ORDER_TYPE_BUY);
}
else if(mtf_short && close_curr < GetKC(false) && rsi[0] > 30 && rsi[0] < 45 && close_curr < ema20[0])
{
ExecuteTrade(ORDER_TYPE_SELL);
}
}
void ExecuteTrade(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;
OrderSend(request, result);
}
bool IsNewBar()
{
static datetime last_time = 0;
datetime curr_time = iTime(_Symbol, _Period, 0);
if(last_time != curr_time)
{
last_time = curr_time;
return true;
}
return false;
}