対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用

これが過酷な全期間8年半の市場変動を破綻せず生き抜いた資産推移グラフ(長期検証証拠)です。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.20 |
| 勝率 | 47.1% |
| 総取引数 | 87 回 |
| 純利益 (0.1ロット/1万通貨時) | $20992.71 |
| 最大ドローダウン | 2.61% |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の弱点は、取引回数の少なさです。 8年半で87回という数字は、機会損失が非常に多いことを意味します。 PF1.20という数値も、控えめな成績と言わざるを得ません。
原因は、フィルター条件を厳しくしすぎたことです。 特に時間帯制限とADXの閾値設定が、多くのチャンスを切り捨てています。 結果として、ドローダウンは極めて低いものの、資産増加のスピードは緩やかになりました。 いわば「守りすぎて攻めきれていない」状態です。
ロジックの技術的詳細
本ロジックは、NY時間の初動を狙うモメンタム戦略です。 上位足の環境認識と、下位足のブレイクアウトを組み合わせています。
| 項目 | 設定内容 |
|---|---|
| ベース時間足 | 5分足 |
| 環境認識足 | 1時間足(EMA20 / ADX14) |
| トレンド判定 | ADX > 25 かつ EMA20との位置関係 |
| エントリー条件 | 30本高値/安値更新 + Supertrend一致 + RSI範囲内 |
| フィルター | 日本時間 21:00 〜 23:00 / ATRボラティリティ上昇 |
| キャンドル判定 | 実体サイズがATRの0.5倍以上かつヒゲが短いこと |
| 決済ルール | 固定TP/SL + トレーリングストップ |
どう改善すべきか(次なる展望)
このロジックを「化けさせる」には、フィルターの最適化が必要です。 例えば、時間帯制限をロンドン市場の時間帯まで広げる方法が考えられます。 また、固定のTP/SLではなく、ATRに基づいた変動幅を採用すべきです。
まずは提供するコードをベースに、ご自身で条件を緩めて検証してみてください。 研究素材としての価値は十分にあるはずです。
公開コード(Python)
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class NYInstitutionalMomentum(BaseStrategy):
def __init__(self):
# 【クオンツ外科手術】PF 1.14 -> 1.20+ および リカバリーファクター 1.49 -> 3.00+ への引き上げ
# 最大DDが非常に低く抑えられているため、リスク許容度を微増させつつ、
# 期待値を最大化させる「損小利大」の比率をさらに最適化。
# TPを伸ばし、トレーリングストップの開始点を遅らせることで、トレンドの最大幅を取り切る設計に変更。
super().__init__(
name="NY_Institutional_Momentum_Burst_V4",
default_tp_pips=75.0,
default_sl_pips=22.0,
enable_trailing_stop=True,
trail_start_pips=25.0
)
# 【絶対準拠ルール5】ベース時間足の定義
self.base_timeframe = "5m"
# 【絶対準拠ルール6】画像認識AI用時間足の定義
self.vision_timeframes = ["5m", "15m", "1h"]
def calculate_indicators(self, df):
"""
ベクトル演算による高速計算。
「だまし」を排除するため、トレンド強度(ADX)の閾値引き上げと、
価格の「位置関係」を厳格に判定するフィルタを導入。
"""
# --- 下位足 (5m) 指標計算 ---
# ボラティリティ: ATR(14)
df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
df['ATR_MA'] = df['ATR'].rolling(window=20).mean()
# トレンド確定: Supertrend (10, 3.0)
st = ta.supertrend(df['High'], df['Low'], df['Close'], length=10, multiplier=3.0)
df['ST_Dir'] = st['SUPERTd_10_3.0']
# モメンタム: RSI(14)
df['RSI'] = ta.rsi(df['Close'], length=14)
# ブレイクアウト判定用: 直近30本(2.5時間)の高値・安値に拡大し、信頼性を向上
df['Range_High'] = df['High'].rolling(window=30).max()
df['Range_Low'] = df['Low'].rolling(window=30).min()
# --- 上位足 (1h) 環境判定 ---
# 【絶対準拠ルール7】ルックアヘッドバイアス排除 (.shift(1))
df_1h = df[['Open', 'High', 'Low', 'Close']].resample('1h').last().shift(1)
# 1h EMA(20)
df_1h['EMA_20'] = ta.ema(df_1h['Close'], length=20)
# 1h ADX(14): トレンド強度の閾値を20から25へ引き上げ、「真のトレンド」のみを抽出
adx_df = ta.adx(df_1h['High'], df_1h['Low'], df_1h['Close'], length=14)
df_1h['ADX'] = adx_df['ADX_14']
# 上位足トレンド判定 (1: Bull, -1: Bear, 0: Range)
df_1h['Trend_1h'] = 0
# ADX > 25 とすることで、レンジ相場での往復ビンタ(だまし)を大幅に削減
df_1h.loc[(df_1h['Close'] > df_1h['EMA_20']) & (df_1h['ADX'] > 25), 'Trend_1h'] = 1
df_1h.loc[(df_1h['Close'] < df_1h['EMA_20']) & (df_1h['ADX'] > 25), 'Trend_1h'] = -1
# 上位足の判定結果を下位足(5m)にマッピング
df['Trend_1h'] = df.index.floor('1h').map(df_1h['Trend_1h'])
return df
def generate_signal(self, df):
"""
「だまし」を排除し、利益効率を最大化させるための精密エントリーロジック。
価格のブレイクに加え、キャンドルの「形状(ヒゲの少なさ)」を条件に追加。
"""
if len(df) < 2:
return None
last = df.iloc[-1]
# --- フィルター条件 ---
# 1. 時間帯フィルター: 日本時間 21:00 〜 23:00
current_hour = last.name.hour if hasattr(last.name, 'hour') else 0
time_filter = (21 <= current_hour < 23)
# 2. ボラティリティ・フィルター: ATR > ATR_MA * 1.1
vol_filter = last['ATR'] > (last['ATR_MA'] * 1.1)
# 3. 上位足トレンドフィルター
trend_bull = (last['Trend_1h'] == 1)
trend_bear = (last['Trend_1h'] == -1)
# --- エントリートリガーの高度化 ---
prev_range_high = df['Range_High'].iloc[-2] if len(df) > 1 else float('inf')
prev_range_low = df['Range_Low'].iloc[-2] if len(df) > 1 else float('-inf')
# 【キャンドル品質判定】
# 実体がATRの0.5倍以上あり、かつ終値が足の全幅の80%以上の位置にあること
# これにより、ブレイク後の急反転(長い上ヒゲ/下ヒゲ)によるだましを排除する。
candle_range = last['High'] - last['Low']
body_size = abs(last['Close'] - last['Open'])
if candle_range == 0:
return None
is_strong_bull = (body_size > last['ATR'] * 0.5) and (last['Close'] >= last['Low'] + candle_range * 0.8)
is_strong_bear = (body_size > last['ATR'] * 0.5) and (last['Close'] <= last['High'] - candle_range * 0.8)
# Long: 上位足強気 + 時間内 + ボラ活性 + 30本高値ブレイク + Supertrend上昇 + RSI(50-70) + 強気キャンドル
long_condition = (
trend_bull and
time_filter and
vol_filter and
(last['Close'] > prev_range_high) and
(last['ST_Dir'] == 1) and
(50 < last['RSI'] < 70) and
is_strong_bull
)
# Short: 上位足弱気 + 時間内 + ボラ活性 + 30本安値ブレイク + Supertrend下降 + RSI(30-50) + 弱気キャンドル
short_condition = (
trend_bear and
time_filter and
vol_filter and
(last['Close'] < prev_range_low) and
(last['ST_Dir'] == -1) and
(30 < last['RSI'] < 50) and
is_strong_bear
)
if long_condition:
return 'BUY'
elif short_condition:
return 'SELL'
return None
MQL5実装コード
//+------------------------------------------------------------------+
//| NY_Institutional_Momentum_V4.mq5 |
//| Copyright 2026, Quant Engineer |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link "https://your-blog-url.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- 入力パラメータ
input double InpLotSize = 0.1; // ロットサイズ
input int InpTP = 750; // 利確 (points)
input int InpSL = 220; // 損切 (points)
input int InpTrailStart = 250; // トレーリング開始 (points)
input int InpADXThreshold = 25; // ADX閾値
input int InpRangePeriod = 30; // レンジ判定期間
//--- グローバル変数
CTrade trade;
int handleADX, handleEMA, handleRSI;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleADX = iADX(_Symbol, PERIOD_H1, 14);
handleEMA = iMA(_Symbol, PERIOD_H1, 20, 0, MODE_EMA, PRICE_CLOSE);
handleRSI = iRSI(_Symbol, PERIOD_M5, 14, PRICE_CLOSE);
if(handleADX == INVALID_HANDLE || handleEMA == INVALID_HANDLE || handleRSI == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 時間帯フィルター (日本時間 21:00 - 23:00)
// ※サーバー時間への変換が必要。ここでは例としてサーバー時間で判定
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(!(dt.hour >= 12 && dt.hour < 14)) return; // 例: GMT+2/3サーバーの場合
// ポジション確認
if(PositionsTotal() > 0) {
ManageTrailingStop();
return;
}
// 指標値の取得
double adx[], ema[], rsi[];
CopyBuffer(handleADX, 0, 1, 1, adx);
CopyBuffer(handleEMA, 0, 1, 1, ema);
CopyBuffer(handleRSI, 0, 0, 1, rsi);
double closeH1 = iClose(_Symbol, PERIOD_H1, 1);
double closeM5 = iClose(_Symbol, PERIOD_M5, 0);
// 上位足トレンド判定
int trendH1 = 0;
if(adx[0] > InpADXThreshold) {
if(closeH1 > ema[0]) trendH1 = 1;
else if(closeH1 < ema[0]) trendH1 = -1;
}
// 30本高値安値の取得
double rangeHigh = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, InpRangePeriod, 1));
double rangeLow = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, InpRangePeriod, 1));
// キャンドル品質判定
double atr = iATR(_Symbol, PERIOD_M5, 14, 0);
double bodySize = MathAbs(iClose(_Symbol, PERIOD_M5, 0) - iOpen(_Symbol, PERIOD_M5, 0));
double candleRange = iHigh(_Symbol, PERIOD_M5, 0) - iLow(_Symbol, PERIOD_M5, 0);
bool isStrongBull = (bodySize > atr * 0.5) && (iClose(_Symbol, PERIOD_M5, 0) >= iLow(_Symbol, PERIOD_M5, 0) + candleRange * 0.8);
bool isStrongBear = (bodySize > atr * 0.5) && (iClose(_Symbol, PERIOD_M5, 0) <= iHigh(_Symbol, PERIOD_M5, 0) - candleRange * 0.8);
// エントリーロジック
if(trendH1 == 1 && closeM5 > rangeHigh && rsi[0] > 50 && rsi[0] < 70 && isStrongBull) {
double sl = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point;
double tp = SymbolInfoDouble(_Symbol, SYMBOL_BID) + InpTP * _Point;
trade.Buy(InpLotSize, _Symbol, 0, sl, tp);
}
else if(trendH1 == -1 && closeM5 < rangeLow && rsi[0] > 30 && rsi[0] < 50 && isStrongBear) {
double sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point;
double tp = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - InpTP * _Point;
trade.Sell(InpLotSize, _Symbol, 0, sl, tp);
}
}
void ManageTrailingStop() {
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket)) {
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
if(SymbolInfoDouble(_Symbol, SYMBOL_BID) - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point) {
double newSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - InpSL * _Point;
if(newSL > PositionGetDouble(POSITION_SL)) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
} else {
if(PositionGetDouble(POSITION_PRICE_OPEN) - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > InpTrailStart * _Point) {
double newSL = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + InpSL * _Point;
if(newSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
}
}
}
double iATR(string symbol, ENUM_TIMEFRAMES tf, int period, int shift) {
int h = iATR(symbol, tf, period);
double buf[];
CopyBuffer(h, 0, shift, 1, buf);
return buf[0];
}