follow me

 


2025年11月25日火曜日

「一日3回(東京9:30/欧州16:45/NY24:00)」に合わせて、時間窓で仕掛けるEAを設計する MQL5 skeleton (modular, mt5)

 


「一日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変換に必要。

📉円高が急進!ドル円一時152円台へ|米景気減速懸念でドル売り加速【2週間ぶり水準】💱

  。 🇺🇸米経済統計がきっかけに…ドル売り強まる💵⬇️ 10日に発表された米国の経済指標が市場心理に影響しました。 昨年12月の米小売売上高(季節調整値) 前月比 0%(横ばい) 市場予想を下回る結果に📉 この結果を受けて「米国景気が減速するのでは?」との懸念が高まり、...