概要:なぜこのロジックは「激動の2年」を生き残れたのか
直近2年間の相場は、急激なインフレとそれに伴う各国の金利変動により、トレンドの発生と崩壊が極めて激しい局面が続きました。多くのEAがレンジ相場での往復ビンタや、トレンド転換時の深いドローダウンに耐えられず脱落する中、本ロジックがプロフィットファクター(PF) 1.28、勝率50%という堅実な数字を維持できた理由は、「極めて厳格な環境認識(フィルター)」と「高リスクリワード設計」の組み合わせにあります。
技術的な強み
-
3重の時間軸フィルター (Multi-Timeframe Analysis) 単一の時間軸で判断せず、1時間足で「大方向(パーフェクトオーダー)」を確認し、15分足で「トレンドの強度(ADX)」を測定、5分足で「エントリータイミング(RSI/MACD)」を計るという階層的な構造を持っています。これにより、トレンドがない相場でのエントリーを徹底的に排除しています。
-
ボラティリティの選別と時間帯制限 NY市場のオープン前後(JST 21:00 - 23:00)にのみ限定して動作させます。この時間帯は流動性が高く、方向感が出やすいため、トレンドフォロー戦略との相性が極めて良好です。
-
「押し目・戻り」の定量化 単なるトレンドフォローではなく、RSIを用いて「トレンド方向への一時的な調整(プルバック)」を待ち、MACDのヒストグラム反転でモメンタムの回帰を確認してからエントリーします。これにより、高値掴み・安値掴みのリスクを低減させています。
-
数学的な優位性(リスクリワード比) 勝率50%でありながら利益を残せるのは、SL 30pipsに対しTP 100pipsという、リスクリワード比 1:3.33 の設計になっているためです。さらにトレーリングストップを実装することで、トレンドが伸びた際の利益を最大化し、PFを底上げしています。
Pythonコードの公開
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class NyTrendPullbackStrategy(BaseStrategy):
"""
NY Trend Specialized Pullback Strategy
- 1h: EMA 50/200 Perfect Order for major trend identification
- 15min: ADX > 25 to filter out range markets
- 5min: RSI for short-term adjustment (Pullback) + MACD reversal for entry
- Goal: Maximize Risk-Reward ratio by entering at 'value' areas within a strong trend.
"""
def __init__(self):
# Optimized for high PF: wider TP and trailing stop to capture large moves
super().__init__(
name="NyTrendPullbackStrategy_V3",
default_tp_pips=100.0,
default_sl_pips=30.0,
enable_trailing_stop=True,
trail_start_pips=25.0
)
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15min", "1h"]
def calculate_indicators(self, df):
# --- 5min Timeframe Indicators ---
# MACD (12, 26, 9) -> returns DataFrame [MACD, Signal, Hist]
macd_df = df.ta.macd(close='Close', fast=12, slow=26, signal=9)
df['MACD_hist'] = macd_df.iloc[:, 2]
# RSI (14) - used to detect pullbacks
df['RSI'] = df.ta.rsi(close='Close', length=14)
# ATR - for volatility check
df['ATR'] = df.ta.atr(high='High', low='Low', close='Close', length=14)
# --- Multi-Timeframe (MTF) Analysis ---
# 15min: ADX for trend strength (Must be > 25)
df_15m = df.resample('15min').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'})
adx_15m = df_15m.ta.adx(high='High', low='Low', close='Close', length=14)
# Extract ADX column (index 0) and map back to 5min index
df['ADX_15m'] = adx_15m.iloc[:, 0].reindex(df.index, method='ffill')
# 1h: EMA 50 and EMA 200 for Perfect Order
df_1h = df.resample('1h').agg({'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last'})
ema_50_1h = df_1h.ta.ema(close='Close', length=50)
ema_200_1h = df_1h.ta.ema(close='Close', length=200)
df['EMA_50_1h'] = ema_50_1h.reindex(df.index, method='ffill')
df['EMA_200_1h'] = ema_200_1h.reindex(df.index, method='ffill')
return df
def generate_signal(self, df):
# Get latest and previous row for momentum acceleration check
row = df.iloc[-1]
prev_row = df.iloc[-2]
# --- 1. Time Filter (JST 21:00 - 23:00) ---
current_hour = df.index[-1].hour
if not (21 <= current_hour <= 23):
return None
# --- 2. Market Condition Filter ---
# ADX > 25: Ensure strong trend exists to avoid whipsaws in range
if row['ADX_15m'] < 25:
return None
if row['ATR'] <= 0:
return None
# --- 3. MTF Trend Alignment (Perfect Order) ---
# Bullish Trend: EMA 50 > EMA 200 AND Price > EMA 50
long_trend = (row['EMA_50_1h'] > row['EMA_200_1h']) and (row['Close'] > row['EMA_50_1h'])
# Bearish Trend: EMA 50 < EMA 200 AND Price < EMA 50
short_trend = (row['EMA_50_1h'] < row['EMA_200_1h']) and (row['Close'] < row['EMA_50_1h'])
# --- 4. Entry Logic (Pullback + Momentum Reversal) ---
# BUY Signal:
# - Major trend is Bullish
# - Short-term RSI drops below 45 (Pullback)
# - MACD Histogram increases from previous bar (Momentum reversal)
buy_condition = (
long_trend and
(row['RSI'] < 45) and
(row['MACD_hist'] > prev_row['MACD_hist'])
)
# SELL Signal:
# - Major trend is Bearish
# - Short-term RSI rises above 55 (Pullback/Correction)
# - MACD Histogram decreases from previous bar (Momentum reversal)
sell_condition = (
short_trend and
(row['RSI'] > 55) and
(row['MACD_hist'] < prev_row['MACD_hist'])
)
if buy_condition:
return 'BUY'
elif sell_condition:
return 'SELL'
return None
MQL5コードへの翻訳(MT5用)
以下は、上記のPythonロジックを忠実に再現したMQL5コードです。MT5のメタエディターに貼り付けてコンパイルして使用してください。
//+------------------------------------------------------------------+
//| NyTrendPullbackStrategy.mq5 |
//| Copyright 2026, System Trader |
//| https://example.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, System Trader"
#property link "https://example.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- Input Parameters
input int InpTP_Pips = 100; // Take Profit (Pips)
input int InpSL_Pips = 30; // Stop Loss (Pips)
input int InpTrailStart_Pips = 25; // Trailing Stop Start (Pips)
input int InpADX_Threshold = 25; // ADX Trend Strength Filter
input int InpRSI_Buy_Level = 45; // RSI Pullback Buy Level
input int InpRSI_Sell_Level = 55; // RSI Pullback Sell Level
//--- Indicator Handles
int hEMA50_H1, hEMA200_H1, hADX_M15, hRSI_M5, hMACD_M5;
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 1h EMA handles
hEMA50_H1 = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
hEMA200_H1 = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE);
// 15m ADX handle
hADX_M15 = iADX(_Symbol, PERIOD_M15, 14);
// 5m RSI and MACD handles
hRSI_M5 = iRSI(_Symbol, PERIOD_M5, 14, PRICE_CLOSE);
hMACD_M5 = iMACD(_Symbol, PERIOD_M5, 12, 26, 9, PRICE_CLOSE);
if(hEMA50_H1 == INVALID_HANDLE || hEMA200_H1 == INVALID_HANDLE ||
hADX_M15 == INVALID_HANDLE || hRSI_M5 == INVALID_HANDLE || hMACD_M5 == INVALID_HANDLE)
{
Print("Indicator Handle Error");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Time Filter (JST 21:00 - 23:00)
// Note: Broker time depends on GMT. Assuming GMT+2/3 (Common for MT5 brokers)
// JST 21:00-23:00 is roughly 13:00-15:00 Broker Time (GMT+2)
MqlDateTime dt;
TimeCurrent(dt);
if(!(dt.hour >= 13 && dt.hour <= 15)) return;
//--- Buffers
double ema50[], ema200[], adx[], rsi[], macd_main[], macd_sig[];
CopyBuffer(hEMA50_H1, 0, 0, 2, ema50);
CopyBuffer(hEMA200_H1, 0, 0, 2, ema200);
CopyBuffer(hADX_M15, 0, 0, 2, adx);
CopyBuffer(hRSI_M5, 0, 0, 2, rsi);
CopyBuffer(hMACD_M5, 0, 0, 2, macd_main); // MACD main line
CopyBuffer(hMACD_M5, 1, 0, 2, macd_sig); // MACD signal line
double close_price = iClose(_Symbol, PERIOD_M5, 0);
double macd_hist_curr = macd_main[0] - macd_sig[0];
double macd_hist_prev = macd_main[1] - macd_sig[1];
//--- Trend & Filter Logic
bool long_trend = (ema50[0] > ema200[0]) && (close_price > ema50[0]);
bool short_trend = (ema50[0] < ema200[0]) && (close_price < ema50[0]);
bool strong_trend = (adx[0] > InpADX_Threshold);
//--- Position Management
if(PositionsTotal() == 0)
{
// BUY: Trend Up + RSI Pullback + MACD Reversal
if(long_trend && strong_trend && rsi[0] < InpRSI_Buy_Level && macd_hist_curr > macd_hist_prev)
{
double sl = close_price - InpSL_Pips * _Point * 10;
double tp = close_price + InpTP_Pips * _Point * 10;
trade.Buy(0.1, _Symbol, close_price, sl, tp, "NyTrend_Buy");
}
// SELL: Trend Down + RSI Pullback + MACD Reversal
else if(short_trend && strong_trend && rsi[0] > InpRSI_Sell_Level && macd_hist_curr < macd_hist_prev)
{
double sl = close_price + InpSL_Pips * _Point * 10;
double tp = close_price - InpTP_Pips * _Point * 10;
trade.Sell(0.1, _Symbol, close_price, sl, tp, "NyTrend_Sell");
}
}
else
{
ManageTrailingStop();
}
}
//+------------------------------------------------------------------+
//| Trailing Stop Logic |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i=PositionsTotal()-1; i>=0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
double price_current = PositionGetDouble(POSITION_PRICE_CURRENT);
double price_open = PositionGetDouble(POSITION_PRICE_OPEN);
double stop_loss = PositionGetDouble(POSITION_SL);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(type == POSITION_TYPE_BUY)
{
if(price_current - price_open > InpTrailStart_Pips * _Point * 10)
{
double new_sl = price_current - InpSL_Pips * _Point * 10;
if(new_sl > stop_loss) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
}
else if(type == POSITION_TYPE_SELL)
{
if(price_open - price_current > InpTrailStart_Pips * _Point * 10)
{
double new_sl = price_current + InpSL_Pips * _Point * 10;
if(new_sl < stop_loss || stop_loss == 0) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
}
}
}
}