「一日3回(東京9:30/欧州16:45/NY24:00)」に合わせて、時間窓で仕掛けるEAを設計する
コアは「時間帯→直前レンジ→ブレイク or 半値回帰」の2系統。まずは安全・拡張性重視の骨格を作り、後からロジックを差し替えできるようにする。
Strategy design overview
対象時間帯: 東京9:30、欧州16:45、NY24:00(JST)
エントリー系統:
ブレイクアウト: 時間窓直前の「ボックス高値・安値」突破で順張り
半値回帰(半値トレード): 直前スイングの高値・安値から50%リトレースで反発方向へ
ボラ・スプレッドフィルタ: ADR/ATRでボラ確認、スプレッド上限で夜間の悪化を回避
ニュース回避(任意): 指標直前x分は新規停止(外部ファイル/手動設定)
リスク管理: 固定%リスク、SLはボックス幅またはATR、TPはRR固定 or トレーリング
Session logic and parameters
時間変換: MT5は「サーバー時刻」。JSTとの差をパラメータで吸収(例: GMT+2サーバーならオフセット+7でJST化)
窓の定義例:
東京9:30: 9:00–9:25のレンジを測定→9:30にブレイク監視(仲値9:55は注意)
欧州16:45: 16:00–16:40のレンジ→16:45ブレイク
NY24:00: 23:00–23:55のレンジ→24:00ブレイク
測定レンジ: 高値H、安値L、中央値M = (H+L)/2 → 半値の基準に利用
フィルタ:
最小ボックス幅: pipsで下限(薄すぎるレンジを除外)
最大スプレッド: pips上限(夜間通貨でブローカー差対策)
最小ADR/ATR: 当日の可動域不足を除外
Entry and exit rules
ブレイクアウト(順張り)
買い: 価格がボックス高値Hをクリーンブレイク(x秒/ティック維持)でBuy
売り: 価格がボックス安値LをブレイクでSell
SL: 反対側ボックス端+バッファ
TP: RR固定(例: 1.2–2.0)またはトレール(ATRベース)
フィルタ: スプレッド <= MaxSpread、ボックス幅 >= MinBox
半値回帰(反発狙い)
基準: 直前スイングのH/LからM=半値
買い: 上昇スイング後、半値Mまで押したら反発でBuy(ローソク確定 or 反転パターン)
売り: 下降スイング後、半値Mまで戻したら反落でSell
SL: 直近スイング端超え+バッファ
TP: 直近高安の70–100%到達 or RR固定
フィルタ: トレンド判定(MA傾き/HHHL構造)で順方向のみ許容
MQL5 skeleton (modular, mt5)
//+------------------------------------------------------------------+
//| SessionTrendEA.mq5 (skeleton) |
//+------------------------------------------------------------------+
#property strict
// ---- Inputs ----
input string InpSymbol = "USDJPY";
input double InpRiskPercent = 1.0; // % per trade
input int InpMaxSpreadPoints = 30; // max spread (points)
input int InpMinBoxPoints = 80; // min box width (points)
input int InpATRPeriod = 14;
input double InpATRMultiplierSL = 1.0;
input double InpRR = 1.5; // TP = RR * SL
input bool InpUseTrailing = true;
// Time handling (server -> JST)
input int InpServerToJSTHours = 7; // e.g., server GMT+2 -> JST +7
input bool InpUseDST = true;
// Session windows (JST; convert in code)
input int TYO_Pivot_HH = 9;
input int TYO_Pivot_MM = 30;
input int TYO_BoxStart_HH = 9;
input int TYO_BoxStart_MM = 0;
input int TYO_BoxEnd_HH = 9;
input int TYO_BoxEnd_MM = 25;
input int EU_Pivot_HH = 16;
input int EU_Pivot_MM = 45;
input int EU_BoxStart_HH = 16;
input int EU_BoxStart_MM = 0;
input int EU_BoxEnd_HH = 16;
input int EU_BoxEnd_MM = 40;
input int NY_Pivot_HH = 24; // 24:00 JST allowed by conversion logic
input int NY_Pivot_MM = 0;
input int NY_BoxStart_HH = 23;
input int NY_BoxStart_MM = 0;
input int NY_BoxEnd_HH = 23;
input int NY_BoxEnd_MM = 55;
input bool InpEnableBreakout = true;
input bool InpEnableHalfReturn = false; // start simple; enable later
// ---- Globals ----
int atr_handle;
MqlTick last_tick;
//+------------------------------------------------------------------+
int OnInit()
{
atr_handle = iATR(InpSymbol, PERIOD_M5, InpATRPeriod);
if(atr_handle == INVALID_HANDLE) return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason){}
//+------------------------------------------------------------------+
void OnTick()
{
if(Symbol() != InpSymbol) return;
if(!SymbolInfoTick(InpSymbol, last_tick)) return;
if(!FiltersPass()) return;
datetime now_srv = TimeCurrent();
datetime now_jst = ShiftToJST(now_srv);
// Check sessions and trade once per pivot minute
CheckSessionAndTrade(now_jst, TYO_BoxStart_HH, TYO_BoxStart_MM, TYO_BoxEnd_HH, TYO_BoxEnd_MM, TYO_Pivot_HH, TYO_Pivot_MM);
CheckSessionAndTrade(now_jst, EU_BoxStart_HH, EU_BoxStart_MM, EU_BoxEnd_HH, EU_BoxEnd_MM, EU_Pivot_HH, EU_Pivot_MM);
CheckSessionAndTrade(now_jst, NY_BoxStart_HH, NY_BoxStart_MM, NY_BoxEnd_HH, NY_BoxEnd_MM, NY_Pivot_HH, NY_Pivot_MM);
ManageTrades(); // trailing, partials if enabled
}
//+------------------------------------------------------------------+
datetime ShiftToJST(datetime t_srv)
{
// Simple shift (DST handling can be expanded by date ranges)
return(t_srv + InpServerToJSTHours * 3600);
}
//+------------------------------------------------------------------+
bool FiltersPass()
{
// Spread filter
int spread_pts = (int)SymbolInfoInteger(InpSymbol, SYMBOL_SPREAD);
if(spread_pts > InpMaxSpreadPoints) return(false);
return(true);
}
//+------------------------------------------------------------------+
void CheckSessionAndTrade(datetime now_jst,
int box_start_hh, int box_start_mm,
int box_end_hh, int box_end_mm,
int pivot_hh, int pivot_mm)
{
// Build box over M1 bars between box_start and box_end (JST)
static bool traded_this_window = false;
datetime day_start = DateStartJST(now_jst);
datetime box_start = day_start + (box_start_hh*3600 + box_start_mm*60);
datetime box_end = day_start + (box_end_hh*3600 + box_end_mm*60);
datetime pivot = day_start + (pivot_hh*3600 + pivot_mm*60);
if(now_jst < box_end || now_jst > pivot + 3600) { traded_this_window = false; return; } // reset window outside pivot+1h
double H, L;
if(!GetRangeJST(box_start, box_end, PERIOD_M1, H, L)) return;
int box_points = (int)MathRound((H - L)/_Point);
if(box_points < InpMinBoxPoints) return;
// Breakout at/after pivot
if(InpEnableBreakout && TimeInMinute(now_jst) == pivot_mm && TimeHour(now_jst) == pivot_hh && !traded_this_window)
{
TradeBreakout(H, L);
traded_this_window = true;
}
// Half-return logic (optional)
if(InpEnableHalfReturn && !traded_this_window)
{
double M = (H + L) / 2.0;
TradeHalfReturn(H, L, M);
// set traded_this_window appropriately inside
}
}
//+------------------------------------------------------------------+
datetime DateStartJST(datetime t_jst)
{
// Start of local day (00:00 JST)
MqlDateTime dt;
TimeToStruct(t_jst, dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
return(StructToTime(dt));
}
//+------------------------------------------------------------------+
bool GetRangeJST(datetime from_jst, datetime to_jst, ENUM_TIMEFRAMES tf, double &H, double &L)
{
// Convert JST to server time by subtracting offset
datetime from_srv = from_jst - InpServerToJSTHours * 3600;
datetime to_srv = to_jst - InpServerToJSTHours * 3600;
int bars = iBarShift(InpSymbol, tf, from_srv, true) - iBarShift(InpSymbol, tf, to_srv, true) + 1;
if(bars <= 0) return(false);
H = -DBL_MAX; L = DBL_MAX;
for(int i=0; i<bars; i++)
{
int idx = iBarShift(InpSymbol, tf, to_srv, true) + i;
double hi = iHigh(InpSymbol, tf, idx);
double lo = iLow(InpSymbol, tf, idx);
if(hi > H) H = hi;
if(lo < L) L = lo;
}
return(true);
}
//+------------------------------------------------------------------+
void TradeBreakout(double H, double L)
{
double atr = GetATR();
double sl_buy = L - atr * InpATRMultiplierSL * _Point; // buffer via ATR
double sl_sell = H + atr * InpATRMultiplierSL * _Point;
// Buy stop at H (or market on break with confirmation)
if(PriceAbove(H)) PlaceOrder(ORDER_TYPE_BUY, H, sl_buy, InpRR);
// Sell stop at L
if(PriceBelow(L)) PlaceOrder(ORDER_TYPE_SELL, L, sl_sell, InpRR);
}
//+------------------------------------------------------------------+
void TradeHalfReturn(double H, double L, double M)
{
// Simple version: wait for touch of M and reversal (MA slope filter can be added)
if(PriceCrossDown(M))
{
// In uptrend (optional filter), buy on reversal
double sl = L - (H-L)*0.2*_Point; // buffer example
PlaceOrder(ORDER_TYPE_BUY, last_tick.ask, sl, InpRR);
}
if(PriceCrossUp(M))
{
double sl = H + (H-L)*0.2*_Point;
PlaceOrder(ORDER_TYPE_SELL, last_tick.bid, sl, InpRR);
}
}
//+------------------------------------------------------------------+
double GetATR()
{
double atr_val[];
if(CopyBuffer(atr_handle, 0, 0, 1, atr_val) < 1) return(0.0);
return(atr_val[0] / _Point); // points
}
//+------------------------------------------------------------------+
bool PriceAbove(double level){ return(last_tick.ask >= level); }
bool PriceBelow(double level){ return(last_tick.bid <= level); }
bool PriceCrossUp(double level){ static double prev=0; double now=last_tick.bid; bool c = (prev<level && now>=level); prev=now; return(c); }
bool PriceCrossDown(double level){ static double prevb=0; double now=last_tick.ask; bool c = (prevb>level && now<=level); prevb=now; return(c); }
//+------------------------------------------------------------------+
void PlaceOrder(ENUM_ORDER_TYPE type, double trigger_price, double sl_price, double rr)
{
double sl_points = MathAbs((trigger_price - sl_price) / _Point);
double tp_points = sl_points * rr;
double lot = CalcLotsByRisk(sl_points);
if(lot <= 0) return;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = InpSymbol;
req.type = type;
req.volume = lot;
req.price = (type == ORDER_TYPE_BUY ? last_tick.ask : last_tick.bid);
req.sl = sl_price;
req.tp = (type == ORDER_TYPE_BUY ? req.price + tp_points*_Point : req.price - tp_points*_Point);
req.deviation= 20;
OrderSend(req, res);
}
//+------------------------------------------------------------------+
double CalcLotsByRisk(int sl_points)
{
double accBal;
AccountInfoDouble(ACCOUNT_BALANCE, accBal);
double risk = accBal * InpRiskPercent / 100.0;
double tickVal = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize= SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_SIZE);
if(tickVal <= 0 || tickSize <= 0 || sl_points <= 0) return(0);
double money_per_point = tickVal / tickSize;
double lot = risk / (sl_points * money_per_point);
// Clamp lot to broker limits
double minLot = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_STEP);
lot = MathMax(minLot, MathMin(maxLot, NormalizeDouble(lot/step, 0)*step));
return(lot);
}
//+------------------------------------------------------------------+
void ManageTrades()
{
if(!InpUseTrailing) return;
// Implement ATR trailing or PSAR trailing as needed
}
Backtest and tuning checklist
時間オフセット検証: ブローカーのサーバー時刻→JSTへのオフセットが正しいか、チャート上で印字して確認。
窓の長さ検討: ボックスの測定時間を5–30分で比較。薄い日を除外するMinBoxPointsを最適化。
通貨ペア別最適: USDJPYは仲値影響、EURUSDはロンドンブレイク強め。ペアごとにMaxSpreadと窓調整。
RRとSL: RR1.2–2.0で曲線比較。SLはボックス幅 or ATRで感度を変える。
指標前停止: 重要指標(CPI、雇用統計)はPivot直近を避ける設定でダマシ減少。
Next steps
選択: ブレイクアウトと半値回帰、どちらを先に動かす?(おすすめはブレイクアウトから)
指定: 通貨ペア(USDJPY/EURUSD/GBPJPY…)、ボックス窓の分数、最小幅、RR、トレール方式を教えて。
環境: ブローカーのサーバー時刻(GMT±)と夏時間有無を共有して。正確なJST変換に必要。