MQL5自动化交易策略(第九部分):构建亚洲盘突破策略的智能交易系统(EA)·进阶篇
🌏

MQL5自动化交易策略(第九部分):构建亚洲盘突破策略的智能交易系统(EA)·进阶篇

(2/3)·手动盯亚洲盘区间常被假突破洗出去?这篇把时段框+MA过滤+动态止损写成可跑的EA

新手友好 第 2/3 篇
很多交易者靠眼睛盯亚洲时段高低点画框,结果一转身价格就假突破扫损。把区间捕获和挂单逻辑交给代码,你只需要在GMT 13:00看系统是否自动平掉残留单。

「用 OnTick 卡死交易时段边界」

EA 退出前必须释放指标句柄,否则 MT5 会残留无效资源。下面这段在 deinit 里先判断 maHandle 是否不等于 INVALID_HANDLE,成立才调用 IndicatorRelease 交还系统,图表上手动画的线则保留供复盘翻看。 真正控制交易节奏的是 OnTick。每笔报价进来先 TimeCurrent 抓服务器时间(默认当 GMT 用),再 TimeToStruct 拆成年月日时分秒存进 dt 结构,后面比大小全靠它。 判定收市很简单:dt.hour 大于 SessionEndHour,或者小时相等且 dt.min 大于等于 SessionEndMinute,就认为已过当日交易尾盘。此时新建 sesEnd 结构,把年月日抄成今天,时分秒填用户参数并把 sec 置 0,StructToTime 转回 datetime 即得精确收市点。 跨天会话要单独处理。若 SessionStartHour 晚于 SessionEndHour(或同小时但分钟更大),说明是隔夜品种,sessionStart 不能直接用今天,得拿 sessionEnd 减 86400 秒退回前一日——这一步漏了,黄金或外汇夜盘会直接错判 24 小时。

MQL5 / C++
  class=class="str">"cmt">//--- Release the MA handle if valid
  if(maHandle != INVALID_HANDLE)                                 class=class="str">"cmt">//--- Check if MA handle exists
     IndicatorRelease(maHandle);                                 class=class="str">"cmt">//--- Release the MA handle
  class=class="str">"cmt">//--- Drawn objects remain on the chart for historical reference
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(){
  class=class="str">"cmt">//--- Get the current server time(assumed GMT)
  class="type">class="kw">datetime currentTime = TimeCurrent();                          class=class="str">"cmt">//--- Retrieve current time
  class="type">MqlDateTime dt;                                                 class=class="str">"cmt">//--- Declare a structure for time components
  TimeToStruct(currentTime, dt);                                  class=class="str">"cmt">//--- Convert current time to structure
  
  class=class="str">"cmt">//--- Check if the current time is at or past the session end(using hour and minute)
  if(dt.hour > SessionEndHour || (dt.hour == SessionEndHour && dt.min >= SessionEndMinute)){
     class=class="str">"cmt">//--- Build the session end time using today&class="macro">#x27;s date and user-defined session end time
     class="type">MqlDateTime sesEnd;                                          class=class="str">"cmt">//--- Declare a structure for session end time
     sesEnd.year = dt.year;                                       class=class="str">"cmt">//--- Set year
     sesEnd.mon  = dt.mon;                                        class=class="str">"cmt">//--- Set month
     sesEnd.day  = dt.day;                                        class=class="str">"cmt">//--- Set day
     sesEnd.hour = SessionEndHour;                                class=class="str">"cmt">//--- Set session end hour
     sesEnd.min  = SessionEndMinute;                              class=class="str">"cmt">//--- Set session end minute
     sesEnd.sec  = class="num">0;                                             class=class="str">"cmt">//--- Set seconds to class="num">0
     class="type">class="kw">datetime sessionEnd = StructToTime(sesEnd);                  class=class="str">"cmt">//--- Convert structure to class="type">class="kw">datetime
     
     class=class="str">"cmt">//--- Determine the session start time
     class="type">class="kw">datetime sessionStart;                                       class=class="str">"cmt">//--- Declare variable for session start time
     class=class="str">"cmt">//--- If session start is later than or equal to session end, assume overnight session
     if(SessionStartHour > SessionEndHour || (SessionStartHour == SessionEndHour && SessionStartMinute >= SessionEndMinute)){
        class="type">class="kw">datetime prevDay = sessionEnd - class="num">86400;                    class=class="str">"cmt">//--- Subtract class="num">24 hours to get previous day

◍ 跨日会话起点与箱体重算的判定

处理会话起点时,若 prevDay 与 sesEnd 不在同一天,就给前一天的 MqlDateTime 强行塞入 SessionStartHour / SessionStartMinute 并把秒置 0,再 StructToTime 回去;否则直接用 sesEnd 的年月日拼出当天的起点。这样能避免把「今天 08:00」误挂到昨天的 K 线上。 只有 sessionEnd 不等于 lastBoxSessionEnd 时才进 ComputeBox。该判断把重算频率压到每个会话一次,而不是每根 tick 都跑;同时把 boxCalculated 置 true、ordersPlaced 复位 false,相当于给新会话清了挂单状态。外汇与贵金属波动剧烈,这套时间拼装若写错 hour/min,箱体高低点会整体偏移,实盘前务必在 MT5 用 Print(sessionStart) 核对。

MQL5 / C++
   class="type">MqlDateTime dtPrev;                                        class=class="str">"cmt">//--- Declare structure for previous day time
   TimeToStruct(prevDay, dtPrev);                               class=class="str">"cmt">//--- Convert previous day time to structure
   dtPrev.hour = SessionStartHour;                              class=class="str">"cmt">//--- Set session start hour for previous day
   dtPrev.min  = SessionStartMinute;                            class=class="str">"cmt">//--- Set session start minute for previous day
   dtPrev.sec  = class="num">0;                                             class=class="str">"cmt">//--- Set seconds to class="num">0
   sessionStart = StructToTime(dtPrev);                         class=class="str">"cmt">//--- Convert structure back to class="type">class="kw">datetime
   }
   else{
   class=class="str">"cmt">//--- Otherwise, use today&class="macro">#x27;s date for session start
   class="type">MqlDateTime temp;                                            class=class="str">"cmt">//--- Declare temporary structure
   temp.year = sesEnd.year;                                     class=class="str">"cmt">//--- Set year from session end structure
   temp.mon  = sesEnd.mon;                                      class=class="str">"cmt">//--- Set month from session end structure
   temp.day  = sesEnd.day;                                      class=class="str">"cmt">//--- Set day from session end structure
   temp.hour = SessionStartHour;                                class=class="str">"cmt">//--- Set session start hour
   temp.min  = SessionStartMinute;                              class=class="str">"cmt">//--- Set session start minute
   temp.sec  = class="num">0;                                               class=class="str">"cmt">//--- Set seconds to class="num">0
   sessionStart = StructToTime(temp);                           class=class="str">"cmt">//--- Convert structure to class="type">class="kw">datetime
   }

   class=class="str">"cmt">//--- Recalculate the session box only if this session hasn&class="macro">#x27;t been processed before
   if(sessionEnd != lastBoxSessionEnd){
      ComputeBox(sessionStart, sessionEnd);                     class=class="str">"cmt">//--- Compute session box using start and end times
      lastBoxSessionEnd = sessionEnd;                           class=class="str">"cmt">//--- Update last processed session end time
      boxCalculated   = true;                                   class=class="str">"cmt">//--- Set flag indicating the box has been calculated
      ordersPlaced    = false;                                  class=class="str">"cmt">//--- Reset flag for order placement for the new session
   }

用会话起止时间圈出高低点时刻

在 MT5 里做价格区间统计,第一步是把指定会话窗口内的极值和它们发生的时间抓出来。下面这段函数以 sessionStart、sessionEnd 为边界,遍历对应周期的所有 K 线,只处理落在该时间区间内的 bar。 初始化时 highVal 设为 -DBL_MAX、lowVal 设为 DBL_MAX,保证任何真实价格都能刷新极值;同时把 BoxHighTime、BoxLowTime 清零,避免上一次会话的残留时间干扰本次记录。 循环里每遇到一根时间在窗口内的 bar,就比较其 high/low 与当前极值,一旦突破就同步更新价格与 rates[i].time。这样跑完一遍,你就拿到了该会话的最高价、最低价以及它们各自触发的精确时间点,可直接用于后续绘制区间或触发突破逻辑。外汇与贵金属波动受时段流动性影响大,这类统计仅反映历史分布,实盘突破概率仍受新闻与流动性冲击,属高风险操作。 让小布替你跑这套 把 BoxTimeframe 切到 M5、sessionStart/End 填亚盘 00:00–08:00,回测一周就能看到高低点时刻是否集中在某两小时,据此调你的突破挂单时间窗。

MQL5 / C++
class="type">void ComputeBox(class="type">class="kw">datetime sessionStart, class="type">class="kw">datetime sessionEnd){
   class="type">int totalBars = Bars(_Symbol, BoxTimeframe);      class=class="str">"cmt">//--- Get total number of bars on the specified timeframe
   if(totalBars <= class="num">0){
      Print("No bars available on timeframe ", EnumToString(BoxTimeframe)); class=class="str">"cmt">//--- Print error if no bars available
      class="kw">return;                                                        class=class="str">"cmt">//--- Exit if no bars are found
   }
   
   class="type">MqlRates rates[];                                                class=class="str">"cmt">//--- Declare an array to hold bar data
   ArraySetAsSeries(rates, false);                                  class=class="str">"cmt">//--- Set array to non-series order(oldest first)
   class="type">int copied = CopyRates(_Symbol, BoxTimeframe, class="num">0, totalBars, rates); class=class="str">"cmt">//--- Copy bar data into array
   if(copied <= class="num">0){
      Print("Failed to copy rates for box calculation.");            class=class="str">"cmt">//--- Print error if copying fails
      class="kw">return;                                                        class=class="str">"cmt">//--- Exit if error occurs
   }
   
   class="type">class="kw">double highVal = -DBL_MAX;                                       class=class="str">"cmt">//--- Initialize high value to the lowest possible
   class="type">class="kw">double lowVal  = DBL_MAX;                                        class=class="str">"cmt">//--- Initialize low value to the highest possible
   class=class="str">"cmt">//--- Reset the times for the session extremes
   BoxHighTime = class="num">0;                                                 class=class="str">"cmt">//--- Reset stored high time
   BoxLowTime  = class="num">0;                                                 class=class="str">"cmt">//--- Reset stored low time

   class=class="str">"cmt">//--- Loop through each bar within the session period to find the extremes
   for(class="type">int i = class="num">0; i < copied; i++){
      if(rates[i].time >= sessionStart && rates[i].time <= sessionEnd){
         if(rates[i].high > highVal){
            highVal = rates[i].high;                                class=class="str">"cmt">//--- Update highest price
            BoxHighTime = rates[i].time;                            class=class="str">"cmt">//--- Record time of highest price
         }
         if(rates[i].low < lowVal){

「把时段高低点落进图形对象」

扫描完指定时段内的 K 线后,代码把实时刷新出的最低价 lowVal 与对应时间 BoxLowTime 存下来,最高价同理。若 highVal 仍等于 -DBL_MAX 或 lowVal 等于 DBL_MAX,说明该时段内没有任何有效 BAR,直接 Print 报错并 return 退出,避免画出空框。 确认有数据后,BoxHigh 与 BoxLow 被赋为最终高低值,并用 TimeToString 把高低点发生时间打印到日志,方便你回看某根 session 极值究竟出现在几点。随后调用 DrawSessionObjects(sessionStart, sessionEnd),把矩形、水平线、价签一次性画到图上。 DrawSessionObjects 先取 ChartGetInteger(0, CHART_SCALE, 0) 拿到 0~5 的图表缩放级,再算动态字号:dynamicFontSize = 7 + chartScale * 1,即缩放每升一级字号加 1;线宽按 dynamicLineWidth = MathRound(1 + chartScale*2.0/5) 线性插值,缩放 5 级时线宽约为 3。sessionID 用 lastBoxSessionEnd 拼成 "Sess_" 前缀,保证每个时段框对象名不冲突。外汇与贵金属波动剧烈,这类时段框仅作结构参考,实盘须自担高风险。

MQL5 / C++
lowVal = rates[i].low;                     class=class="str">"cmt">//--- Update lowest price
    BoxLowTime = rates[i].time;                class=class="str">"cmt">//--- Record time of lowest price
     }
   }
  }
  if(highVal == -DBL_MAX || lowVal == DBL_MAX){
    Print("No valid bars found within the session time range."); class=class="str">"cmt">//--- Print error if no valid bars found
    class="kw">return;                                                 class=class="str">"cmt">//--- Exit if invalid data
  }
  BoxHigh = highVal;                                        class=class="str">"cmt">//--- Store final highest price
  BoxLow  = lowVal;                                         class=class="str">"cmt">//--- Store final lowest price
  Print("Session box computed: High = ", BoxHigh, " at ", TimeToString(BoxHighTime),
        ", Low = ", BoxLow, " at ", TimeToString(BoxLowTime)); class=class="str">"cmt">//--- Output computed session box data

  class=class="str">"cmt">//--- Draw all session objects(rectangle, horizontal lines, and price labels)
  DrawSessionObjects(sessionStart, sessionEnd);             class=class="str">"cmt">//--- Call function to draw objects using computed values
}
class="type">void DrawSessionObjects(class="type">class="kw">datetime sessionStart, class="type">class="kw">datetime sessionEnd){
   class="type">int chartScale = (class="type">int)ChartGetInteger(class="num">0, CHART_SCALE, class="num">0); class=class="str">"cmt">//--- Retrieve the chart scale(class="num">0 to class="num">5)
   class="type">int dynamicFontSize = class="num">7 + chartScale * class="num">1;                 class=class="str">"cmt">//--- Base class="num">7, increase by class="num">2 per scale level
   class="type">int dynamicLineWidth = (class="type">int)MathRound(class="num">1 + (chartScale * class="num">2.0 / class="num">5)); class=class="str">"cmt">//--- Linear interpolation

   class=class="str">"cmt">//--- Create a unique session identifier using the session end time
   class="type">class="kw">string sessionID = "Sess_" + IntegerToString(lastBoxSessionEnd);

   class=class="str">"cmt">//--- Draw the filled rectangle(box) using the recorded high/low times and prices

◍ 把交易时段框进MT5图层的画法

在 MT5 上把某个交易时段的高低价区可视化,核心是用 ObjectCreate 按对象类型分别建矩形、趋势线和文本。下面这段逻辑给每个时段加了唯一后缀 sessionID,避免多周期重绘时对象互相覆盖。 矩形用 OBJ_RECTANGLE,以 BoxHighTime/BoxLowTime 为时间边界、BoxHigh/BoxLow 为价格边界;填充色 clrThistle、置底显示,能直观看出时段波动包裹区。上下边界再用 OBJ_TREND 画水平线,顶线蓝、底线红,线宽走 dynamicLineWidth 变量,RAY_RIGHT 关掉才不会向右无限延伸。 价格标签用 OBJ_TEXT 挂在 sessionEnd 的 BoxHigh 处,DoubleToString(BoxHigh, _Digits) 保证小数位和品种一致。外汇与贵金属波动剧烈,这类图形仅辅助辨识结构,实际突破方向仍可能反向,请自行在策略测试器验证。

MQL5 / C++
  class="type">class="kw">string rectName = "SessionRect_" + sessionID;                    class=class="str">"cmt">//--- Unique name for the rectangle
  if(!ObjectCreate(class="num">0, rectName, OBJ_RECTANGLE, class="num">0, BoxHighTime, BoxHigh, BoxLowTime, BoxLow))
      Print("Failed to create rectangle: ", rectName); class=class="str">"cmt">//--- Print error if creation fails
  ObjectSetInteger(class="num">0, rectName, OBJPROP_COLOR, clrThistle); class=class="str">"cmt">//--- Set rectangle class="type">color to blue
  ObjectSetInteger(class="num">0, rectName, OBJPROP_FILL, true);        class=class="str">"cmt">//--- Enable filling of the rectangle
  ObjectSetInteger(class="num">0, rectName, OBJPROP_BACK, true);        class=class="str">"cmt">//--- Draw rectangle in background

  class=class="str">"cmt">//--- Draw the top horizontal line spanning from sessionStart to sessionEnd at the session high
  class="type">class="kw">string topLineName = "SessionTopLine_" + sessionID; class=class="str">"cmt">//--- Unique name for the top line
  if(!ObjectCreate(class="num">0, topLineName, OBJ_TREND, class="num">0, sessionStart, BoxHigh, sessionEnd, BoxHigh))
      Print("Failed to create top line: ", topLineName); class=class="str">"cmt">//--- Print error if creation fails
  ObjectSetInteger(class="num">0, topLineName, OBJPROP_COLOR, clrBlue); class=class="str">"cmt">//--- Set line class="type">color to blue
  ObjectSetInteger(class="num">0, topLineName, OBJPROP_WIDTH, dynamicLineWidth); class=class="str">"cmt">//--- Set line width dynamically
  ObjectSetInteger(class="num">0, topLineName, OBJPROP_RAY_RIGHT, false); class=class="str">"cmt">//--- Do not extend line infinitely

  class=class="str">"cmt">//--- Draw the bottom horizontal line spanning from sessionStart to sessionEnd at the session low
  class="type">class="kw">string bottomLineName = "SessionBottomLine_" + sessionID; class=class="str">"cmt">//--- Unique name for the bottom line
  if(!ObjectCreate(class="num">0, bottomLineName, OBJ_TREND, class="num">0, sessionStart, BoxLow, sessionEnd, BoxLow))
      Print("Failed to create bottom line: ", bottomLineName); class=class="str">"cmt">//--- Print error if creation fails
  ObjectSetInteger(class="num">0, bottomLineName, OBJPROP_COLOR, clrRed); class=class="str">"cmt">//--- Set line class="type">color to blue
  ObjectSetInteger(class="num">0, bottomLineName, OBJPROP_WIDTH, dynamicLineWidth); class=class="str">"cmt">//--- Set line width dynamically
  ObjectSetInteger(class="num">0, bottomLineName, OBJPROP_RAY_RIGHT, false); class=class="str">"cmt">//--- Do not extend line infinitely

  class=class="str">"cmt">//--- Create the top price label at the right edge of the top horizontal line
  class="type">class="kw">string topLabelName = "SessionTopLabel_" + sessionID; class=class="str">"cmt">//--- Unique name for the top label
  if(!ObjectCreate(class="num">0, topLabelName, OBJ_TEXT, class="num">0, sessionEnd, BoxHigh))
      Print("Failed to create top label: ", topLabelName); class=class="str">"cmt">//--- Print error if creation fails
  ObjectSetString(class="num">0, topLabelName, OBJPROP_TEXT," "+DoubleToString(BoxHigh, _Digits)); class=class="str">"cmt">//--- Set label text to session high price
  ObjectSetInteger(class="num">0, topLabelName, OBJPROP_COLOR, clrBlack); class=class="str">"cmt">//--- Set label class="type">color to blue

用退出时间与均线过滤挂单时机

会话框算完、订单还没挂、且当前时间早于设定的交易退出时间,这三个条件同时成立时才允许进场挂单。退出时间由用户设置的小时和分钟拼到当天日期上,秒固定为 0,用 MqlDateTime 结构转换得到 datetime。 进场前还会拉一次均线数值做过滤。代码里用 CopyBuffer(maHandle,0,0,1,maBuffer) 只拷贝最新 1 根 MA 值,若返回值 <=0 直接 Print 报错并 return,避免无均线数据就盲目挂单。 外汇与贵金属杠杆高、滑点跳空频繁,这套时间+均线双重过滤只降低乱挂单概率,不保证胜率,实盘前请在 MT5 策略测试器用真实点差回测。

MQL5 / C++
ObjectSetInteger(class="num">0, topLabelName, OBJPROP_FONTSIZE, dynamicFontSize); class=class="str">"cmt">//--- Set dynamic font size for label
ObjectSetInteger(class="num">0, topLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT); class=class="str">"cmt">//--- Anchor label to the left so text appears to right

class=class="str">"cmt">//--- Create the bottom price label at the right edge of the bottom horizontal line
class="type">class="kw">string bottomLabelName = "SessionBottomLabel_" + sessionID; class=class="str">"cmt">//--- Unique name for the bottom label
if(!ObjectCreate(class="num">0, bottomLabelName, OBJ_TEXT, class="num">0, sessionEnd, BoxLow))
    Print("Failed to create bottom label: ", bottomLabelName); class=class="str">"cmt">//--- Print error if creation fails
ObjectSetString(class="num">0, bottomLabelName, OBJPROP_TEXT," "+DoubleToString(BoxLow, _Digits)); class=class="str">"cmt">//--- Set label text to session low price
ObjectSetInteger(class="num">0, bottomLabelName, OBJPROP_COLOR, clrBlack); class=class="str">"cmt">//--- Set label class="type">color to blue
ObjectSetInteger(class="num">0, bottomLabelName, OBJPROP_FONTSIZE, dynamicFontSize); class=class="str">"cmt">//--- Set dynamic font size for label
ObjectSetInteger(class="num">0, bottomLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT); class=class="str">"cmt">//--- Anchor label to the left so text appears to right
}
class=class="str">"cmt">//--- Build the trade exit time using user-defined hour and minute for today
class="type">MqlDateTime exitTimeStruct;                      class=class="str">"cmt">//--- Declare a structure for exit time
TimeToStruct(currentTime, exitTimeStruct);       class=class="str">"cmt">//--- Use current time&class="macro">#x27;s date components
exitTimeStruct.hour = TradeExitHour;             class=class="str">"cmt">//--- Set trade exit hour
exitTimeStruct.min   = TradeExitMinute;          class=class="str">"cmt">//--- Set trade exit minute
exitTimeStruct.sec   = class="num">0;                        class=class="str">"cmt">//--- Set seconds to class="num">0
class="type">class="kw">datetime tradeExitTime = StructToTime(exitTimeStruct); class=class="str">"cmt">//--- Convert exit time structure to class="type">class="kw">datetime
class=class="str">"cmt">//--- If the session box is calculated, orders are not placed yet, and current time is before trade exit time, place orders
if(boxCalculated && !ordersPlaced && currentTime < tradeExitTime){
   class="type">class="kw">double maBuffer[];                             class=class="str">"cmt">//--- Declare array to hold MA values
   ArraySetAsSeries(maBuffer, true);             class=class="str">"cmt">//--- Set the array as series(newest first)
   if(CopyBuffer(maHandle, class="num">0, class="num">0, class="num">1, maBuffer) <= class="num">0){ class=class="str">"cmt">//--- Copy class="num">1 value from the MA buffer
      Print("Failed to copy MA buffer.");        class=class="str">"cmt">//--- Print error if buffer copy fails
      class="kw">return;                                    class=class="str">"cmt">//--- Exit the function if error occurs
   }
   class="type">class="kw">double maValue = maBuffer[class="num">0];                 class=class="str">"cmt">//--- Retrieve the current MA value
让小布替你跑这套盘前诊断
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到亚洲盘区间和MA趋势过滤状态,把重复劳动交给小布,你专注决策。

常见问题

该窗口覆盖东京早盘前后流动性最薄的盘整段,高低点区间相对稳定,但可根据经纪商时区与品种特性自定义偏移。
可能减少逆势信号,但盘整转突破时MA斜率滞后,EA仍可能双向预挂,需配合时间退出避免隔夜暴露。
小布盯盘已内置时段区间与MA状态识别,可展示当前框体和突破挂单位置,但EA编译仍需在MetaEditor完成。
伦敦午后流动性转向,亚盘突破行情多数已走完,未平仓单大概率陷入震荡,自动关闭可控制隔夜高风险。