创建一个基于日波动区间突破策略的 MQL5 EA·综合运用
📈

创建一个基于日波动区间突破策略的 MQL5 EA·综合运用

(3/3)· 把前兩篇的模块拼成可跑的 EA,并解决假突破与止损 placement 的真实麻烦

含代码示例偏理论 第 3/3 篇
很多交易者把突破信号当圣杯,忽略亚洲时段区间被伦敦盘假扫后反向的概率。没有基于上一根波动高低点挂止损的 EA,往往在噪音里连续止损。外汇与贵金属杠杆高,区间突破策略同样可能连续失效。

◍ 给区间突破打标记的代码落点

在 MT5 里把突破点画上图表,本质就是给箭头对象设属性再补一段文字标签。下面这段逻辑承接前文的区间判定:当收盘价跌破当日区间下沿且在允许突破时段内,就触发标记绘制。 调用 drawBreakPoint 时传入了具体参数:箭头代码 233(对应 Wingdings 字体里的特定符号)、颜色 clrBlue、方向 1 表示向下突破。外汇与贵金属品种波动剧烈,这类标记仅作视觉提示,实盘仍需结合止损,杠杆交易风险偏高。 代码先通过 ObjectSetInteger 给箭头设箭头码、颜色、字号 12,再按 direction 正负决定锚点在顶部还是底部。随后用 OBJ_TEXT 建一个“ Break”文字对象,同样设色与字号,并根据方向选 ANCHOR_LEFT_UPPER 或 ANCHOR_LEFT_LOWER 贴着箭头放。最后 ChartRedraw(0) 强制重绘。 下半段是下破的判定分支:barClose < minimum_price 且时段合法、尚未标记过,就 Print 日志、置 isHaveRangeBreak=true,并用 233/clrBlue/1 调 drawBreakPoint 画点。复制进 EA 的 OnCalculate 尾段即可在回测里看到蓝色破位箭头。

MQL5 / C++
class=class="str">"cmt">//--- Set the arrow&class="macro">#x27;s code(symbol)
ObjectSetInteger(class="num">0, objName, OBJPROP_ARROWCODE, arrCode);

class=class="str">"cmt">//--- Set the class="type">class="kw">color for the arrow
ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr);

class=class="str">"cmt">//--- Set the font size for the arrow
ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, class="num">12);

class=class="str">"cmt">//--- Set the anchor position for the arrow based on the direction
if (direction > class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
if (direction < class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);

class=class="str">"cmt">//--- Define a text label for the break point
class="type">class="kw">string txt = " Break";
class="type">class="kw">string objNameDescr = objName + txt;

class=class="str">"cmt">//--- Create a text object for the break point description
ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time, price);

class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text description
ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr);

class=class="str">"cmt">//--- Set the font size for the text
ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, class="num">12);

class=class="str">"cmt">//--- Adjust the text anchor based on the direction of the arrow
if (direction > class="num">0) {
   ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt);
}
if (direction < class="num">0) {
   ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
   ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt);
}
  }
  class=class="str">"cmt">//--- Redraw the chart to reflect the new objects
  ChartRedraw(class="num">0);
}

  class=class="str">"cmt">//--- Check for lower range breakout condition
  else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
    Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event
    isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
    drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
  }

日内区间突破的挂单与标记逻辑

在 MT5 EA 里处理突破信号,第一步是把当前报价规整到品种精度:用 SymbolInfoDouble 取 SYMBOL_ASK / SYMBOL_BID,再套 NormalizeDouble 按 _Digits 截断,避免浮点误差导致下单价非法。 上破条件要求收盘价 barClose 大于当日统计的 maximum_price,且日内区间已算好(isHaveDailyRange_Prices)、尚未触发过突破(!isHaveRangeBreak),并且 barTime 落在 validBreakTime_start 到 validBreakTime_end 的合法窗口内。命中后打印日志、置位 isHaveRangeBreak,并调用 drawBreakPoint 以编号 234、黑色在突破位画点。 下单用 obj_Trade.Buy(0.01, _Symbol, Ask, minimum_price, Bid+(maximum_price-minimum_price)*2):0.01 手试单,止损放在区间下沿 minimum_price,止盈距离为区间高度的两倍——这种倍数止盈在震荡转趋势的行情里可能比固定点数更跟得住。 下破对称处理:barClose 小于 minimum_price 且其他标志相同,画点编号 233、蓝色,obj_Trade.Sell 的止损设于 maximum_price,止盈为 Ask-(maximum_price-minimum_price)*2。外汇与贵金属杠杆高,这类突破追单在假突破频发的时段可能连续止损,建议先在策略测试器用真实点差回测再上仿真。

MQL5 / C++
  class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
  class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
  
  class=class="str">"cmt">//--- Check for upper range breakout condition
  if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
      && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event
      isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
      obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*class="num">2);
  }
  class=class="str">"cmt">//--- Check for lower range breakout condition
  else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
        && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event
      isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
      obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*class="num">2);
  }

「用策略测试器榨出参数组合」

EA 写完后别急着上实盘,先丢进 MT5 策略测试器跑回测。日波动区间突破这种依赖时间窗口和盈亏比的策略,参数稍微一动,信号触发频率就能差出几倍,外汇和贵金属市场的高波动特性会放大这种偏差,必须先在历史数据里压一遍。 优化时把风险回报比、突破有效时长(小时)、交易方向三个量做成 input 暴露出来。原文示例里 r2r 默认 2、hoursValidity 默认 5,direction_of_trade 用枚举控制默认或反转方向——下边界被破时默认做空,切到 Invert 就改做多,核心逻辑不用动。 避免过度拟合有个土办法:优化只框一个月。跑完看参数曲面,挑曲线平坦那段而不是峰值最高的那组,实盘存活概率更高。 下面这段是参数声明加日内时间窗重置的核心。枚举让方向切换可读,input 让测试器能逐格扫;hoursValidity 被黄标高亮,因为它直接决定 validBreakTime_end 往后推几小时。

MQL5 / C++
enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};
class="kw">input class="type">int r2r = class="num">2;
class="kw">input class="type">int hoursValidity = class="num">5;
class="kw">input trade_direction direction_of_trade = Default_Trade_Directions;
  class="kw">static class="type">class="kw">datetime midnight = iTime(_Symbol,PERIOD_D1,class="num">0);  class=class="str">"cmt">//--- Get the time of midnight(start of the day) for daily chart
  class="kw">static class="type">class="kw">datetime sixAM = midnight + class="num">6 * class="num">3600;            class=class="str">"cmt">//--- Calculate class="num">6 AM based on midnight time
  class="kw">static class="type">class="kw">datetime scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Set scan time for the next bar after class="num">6 AM
  class="kw">static class="type">class="kw">datetime validBreakTime_start = scanBarTime;     class=class="str">"cmt">//--- Set the start of valid breakout time
  class="kw">static class="type">class="kw">datetime validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Set the end of valid breakout time to class="num">11 AM

  if (isNewDay()){
    class=class="str">"cmt">//--- Reset values for the new day
    midnight = iTime(_Symbol,PERIOD_D1,class="num">0);      class=class="str">"cmt">//--- Get the new midnight time
    sixAM = midnight + class="num">6 * class="num">3600;               class=class="str">"cmt">//--- Recalculate class="num">6 AM
    scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Recalculate the scan bar time
    validBreakTime_start = scanBarTime;        class=class="str">"cmt">//--- Update valid breakout start time
    validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Update valid breakout end time to class="num">11 AM
    maximum_price = -DBL_MAX;                  class=class="str">"cmt">//--- Reset the maximum price for the new day
    minimum_price = DBL_MAX;                   class=class="str">"cmt">//--- Reset the minimum price for the new day

    isHaveDailyRange_Prices = false;           class=class="str">"cmt">//--- Reset the daily range flag for the new day
    isHaveRangeBreak = false;                  class=class="str">"cmt">//--- Reset the breakout flag for the new day
  }
  class=class="str">"cmt">//---
  class=class="str">"cmt">//--- Check for upper range breakout condition
  if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak

◍ 收盘价突破日区间后的下单分支

这段逻辑只在 bars 收盘触破当日统计出的 high/low 区间、且处于 validBreakTime 窗口内、且尚未标记过突破时才触发。突破上沿时打印日志、置 isHaveRangeBreak 为真,并用 drawBreakPoint 在 234 号箭头(黑)标出突破点;突破下沿则用 233 号箭头(蓝)标记。 交易动作取决于 direction_of_trade 枚举:默认方向下,上破做多 0.01 手,止损放在 minimum_price,止盈为 Bid + (maximum_price-minimum_price)*r2r;下破做空,止损放 maximum_price,止盈为 Ask - 区间幅度*r2r。若切到 Invert_Trade_Directions,上下破信号反手执行。 代码头部可见 r2r 输入默认 2、hoursValidity 默认 5,意味着风险回报比预设 1:2、突破有效窗口为 5 小时。外汇与贵金属杠杆品种波动剧烈,实盘前应在 MT5 策略测试器用 0.01 手验证该止损止盈距离是否被平台最小止损规则拒绝。

MQL5 / C++
&& barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event
      isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(class="num">0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   class=class="str">"cmt">//--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event
      isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(class="num">0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }

用静态时间锚点框定日内区间扫描窗

做日内突破策略时,先得把“当天该扫哪段”钉死。下面这段初始化把午夜、早6点、扫描 bar 时间以及有效突破窗口都用 static 变量算好,避免每个 tick 重复计算,也防止跨日错乱。 double minimum_price = DBL_MAX 是把最低价初值推到浮点上限,保证第一根符合条件的 K 线必能刷新它;maximum_time / minimum_time 留着存高低点发生时刻;isHaveDailyRange_Prices 与 isHaveRangeBreak 是两个状态锁,防止一天内重复画矩形或重复触发突破信号。 宏定义给图形对象加前缀:RECTANGLE_PREFIX 为 "RANGE RECTANGLE ",上下边界线分别是 "UPPER LINE " 和 "LOWER LINE "。这样在 MT5 对象列表里能一眼区分不同用途的画线,不会和手动画的趋势线混在一起。 OnInit / OnDeinit 这里留空,仅返回 INIT_SUCCEEDED,说明本段只负责时间锚点与状态变量的声明,具体抓取逻辑在 OnTick 内按日重置。注意 validBreakTime_end 用 midnight + (6+hoursValidity)*3600 计算,若 hoursValidity=5 则窗口收在 11:00(6+5=11),外汇与贵金属此时流动性变化明显,假突破概率可能上升,需结合更高周期过滤。 打开 MT5 新建 EA,把这段直接贴进文件,改 hoursValidity 参数到 4~6 之间,编译后挂 EURUSD 的 M5,观察对象列表是否按前缀生成矩形,即可验证锚点逻辑是否成立。

MQL5 / C++
class="type">class="kw">double minimum_price = DBL_MAX;   class=class="str">"cmt">//--- Initialize the minimum price with the largest possible value
class="type">class="kw">datetime maximum_time, minimum_time; class=class="str">"cmt">//--- Declare variables to store the time of the highest and lowest prices
class="type">bool isHaveDailyRange_Prices = false; class=class="str">"cmt">//--- Boolean flag to check if daily range prices are extracted
class="type">bool isHaveRangeBreak = false;        class=class="str">"cmt">//--- Boolean flag to check if a range breakout has occurred
class="macro">#define RECTANGLE_PREFIX "RANGE RECTANGLE " class=class="str">"cmt">//--- Prefix for naming range rectangles
class="macro">#define UPPER_LINE_PREFIX "UPPER LINE "     class=class="str">"cmt">//--- Prefix for naming upper range line
class="macro">#define LOWER_LINE_PREFIX "LOWER LINE "     class=class="str">"cmt">//--- Prefix for naming lower range line
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(){
   class=class="str">"cmt">//--- Initialization code can be placed here if needed

   class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Return successful initialization
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason){
   class=class="str">"cmt">//--- Deinitialization code can be placed here if needed
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(){
   class=class="str">"cmt">//--- 
   class="kw">static class="type">class="kw">datetime midnight = iTime(_Symbol,PERIOD_D1,class="num">0);  class=class="str">"cmt">//--- Get the time of midnight(start of the day) for daily chart
   class="kw">static class="type">class="kw">datetime sixAM = midnight + class="num">6 * class="num">3600;            class=class="str">"cmt">//--- Calculate class="num">6 AM based on midnight time
   class="kw">static class="type">class="kw">datetime scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Set scan time for the next bar after class="num">6 AM
   class="kw">static class="type">class="kw">datetime validBreakTime_start = scanBarTime;     class=class="str">"cmt">//--- Set the start of valid breakout time
   class="kw">static class="type">class="kw">datetime validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Set the end of valid breakout time to class="num">11 AM

   if (isNewDay()){
      class=class="str">"cmt">//--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,class="num">0);     class=class="str">"cmt">//--- Get the new midnight time
      sixAM = midnight + class="num">6 * class="num">3600;               class=class="str">"cmt">//--- Recalculate class="num">6 AM
      scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Recalculate the scan bar time

「日内区间扫描与突破窗口的初始化逻辑」

这段逻辑处理的是每日凌晨重置与 6 点区间提取的衔接。新的一天到来时,代码先把有效突破的起止时间、当日最高/最低价、以及两个状态标志(是否已提取区间、是否已发生突破)全部清零,保证上一日数据不会污染当日计算。 其中有效突破结束时间被硬性设为 midnight + (6 + hoursValidity) * 3600,也就是在凌晨 6 点基础上再加 hoursValidity 小时,若 hoursValidity 取 5,则窗口关闭于当天 11:00(夏令时服务器时间)。这是后续判断「亚洲段区间是否被有效破位」的时间边界。 当 isNewBar() 触发且当前 Bar 时间等于 scanBarTime、且当日区间尚未提取时,脚本会先算出 midnight 到 6:00 之间的 Bar 数量:total_bars = (sixAM - midnight) / PeriodSeconds(_Period) + 1。以 M5 周期为例,6 小时共 72 根 Bar,total_bars 即为 73(含起点那根)。 随后用 for 循环从 i=1 遍历到 total_bars,对每根 Bar 取 open 与 close,以两者中较大值为该 Bar 的「最高参考价」、较小值为「最低参考价」,再据此刷新全局 maximum_price / minimum_price 及对应索引和时间。注意这里没有用影线高低,只用实体边界,对外汇和贵金属这类高风险品种,实体突破比影线刺穿更不容易出现假信号。 打开 MT5 把这段接进 EA,把 hoursValidity 改成你常用的持有时长,跑一周模拟盘就能验证 11:00 前未破区间是否被准确标注。

MQL5 / C++
validBreakTime_start = scanBarTime;                 class=class="str">"cmt">//--- Update valid breakout start time
validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Update valid breakout end time to class="num">11 AM
maximum_price = -DBL_MAX;                               class=class="str">"cmt">//--- Reset the maximum price for the new day
minimum_price = DBL_MAX;                                class=class="str">"cmt">//--- Reset the minimum price for the new day

isHaveDailyRange_Prices = false;                        class=class="str">"cmt">//--- Reset the daily range flag for the new day
isHaveRangeBreak = false;                               class=class="str">"cmt">//--- Reset the breakout flag for the new day
 }

 if (isNewBar()){
   class=class="str">"cmt">//--- If a new bar has been formed, process the data
   class="type">class="kw">datetime currentBarTime = iTime(_Symbol,_Period,class="num">0); class=class="str">"cmt">//--- Get the time of the current bar
   
   if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
     class=class="str">"cmt">//--- If it&class="macro">#x27;s time to scan and the daily range is not yet extracted
     Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); class=class="str">"cmt">//--- Log the extraction process
     class="type">int total_bars = class="type">int((sixAM - midnight)/PeriodSeconds(_Period)) + class="num">1; class=class="str">"cmt">//--- Calculate total bars between midnight and class="num">6 AM
     Print("Total Bars for scan = ",total_bars); class=class="str">"cmt">//--- Log the total number of bars for scanning
     class="type">int highest_price_bar_index = -class="num">1;  class=class="str">"cmt">//--- Variable to store the bar index of the highest price
     class="type">int lowest_price_bar_index = -class="num">1;   class=class="str">"cmt">//--- Variable to store the bar index of the lowest price

     for (class="type">int i=class="num">1; i<=total_bars ; i++){ class=class="str">"cmt">//--- Loop through all bars within the defined time range
       class="type">class="kw">double open_i = open(i);          class=class="str">"cmt">//--- Get the opening price of the i-th bar
       class="type">class="kw">double close_i = close(i);        class=class="str">"cmt">//--- Get the closing price of the i-th bar
       
       class="type">class="kw">double highest_price_i = (open_i > close_i) ? open_i : close_i; class=class="str">"cmt">//--- Determine the highest price between open and close
       class="type">class="kw">double lowest_price_i = (open_i < close_i) ? open_i : close_i;  class=class="str">"cmt">//--- Determine the lowest price between open and close
       
       if (highest_price_i > maximum_price){
         class=class="str">"cmt">//--- If the current highest price is greater than the recorded maximum price
         maximum_price = highest_price_i; class=class="str">"cmt">//--- Update the maximum price
         highest_price_bar_index = i;     class=class="str">"cmt">//--- Update the index of the highest price bar
         maximum_time = time(i);          class=class="str">"cmt">//--- Update the time of the highest price

◍ 日内区间突破的触发与下单逻辑

这段代码片段负责在已提取日内最高/最低价后,监听上一根_bar的收盘价是否向上击穿区间上沿。只有当 isHaveDailyRange_Prices 为真、且未发生过突破、并且_bar时间落在 validBreakTime_startvalidBreakTime_end 之间时,才会判定为有效突破。外汇与贵金属杠杆高,假突破概率不低,实盘前务必在MT5策略测试器里跑一遍。 核心判定写得很直白:barClose > maximum_price 即收盘价高于日内最高价,同时用 !isHaveRangeBreak 防止重复触发。一旦成立,先 Print 日志、置 isHaveRangeBreak=true,再调用 drawBreakPoint 在图表上标出突破点(箭头代码234、黑色)。 下单分支取决于 direction_of_trade:若为默认方向,则以0.01手买入,止损设在 minimum_price(区间下沿),止盈为 Bid+(maximum_price-minimum_price)*r2r——即跌破区间幅度乘以r2r系数。若设为反向交易,则走 Invert_Trade_Directions 分支(原文未展开)。 把 r2r 从默认改到2.0或0.5,回测里能明显看到胜率与平均盈利的分布偏移,建议先调这一个参数验证区间突破策略的脆弱点。

MQL5 / C++
if (lowest_price_i < minimum_price){
   class=class="str">"cmt">//--- If the current lowest price is lower than the recorded minimum price
   minimum_price = lowest_price_i;   class=class="str">"cmt">//--- Update the minimum price
   lowest_price_bar_index = i;       class=class="str">"cmt">//--- Update the index of the lowest price bar
   minimum_time = time(i);           class=class="str">"cmt">//--- Update the time of the lowest price
   }
   }
class=class="str">"cmt">//--- Log the maximum and minimum prices, along with their respective bar indices and times
Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);

class=class="str">"cmt">//--- Create visual elements to represent the daily range
create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); class=class="str">"cmt">//--- Create a rectangle for the daily range
create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,class="num">3,clrBlack,DoubleToString(maximum_price,_Digits)); class=class="str">"cmt">//--- Draw upper range line
create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,class="num">3,clrRed,DoubleToString(minimum_price,_Digits));   class=class="str">"cmt">//--- Draw lower range line

isHaveDailyRange_Prices = true; class=class="str">"cmt">//--- Set the flag indicating daily range prices have been extracted
   }
 }

 class=class="str">"cmt">//--- Get the close price and time of the previous bar
class="type">class="kw">double barClose = close(class="num">1);
class="type">class="kw">datetime barTime = time(class="num">1);

class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

 class=class="str">"cmt">//--- Check for upper range breakout condition
 if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
     && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
   Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event
   isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
   drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
   
   if (direction_of_trade == Default_Trade_Directions){
     obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
   }
   else if (direction_of_trade == Invert_Trade_Directions){

下破区间时的反向挂单与辅助函数

当收盘价低于 minimum_price 且处于有效突破时间窗内,系统判定为下轨突破,置位 isHaveRangeBreak 并调用 drawBreakPoint 在蓝点标记。若交易方向设为默认,则直接 Sell 0.01 手,止损放在 maximum_price,止盈为 Ask 减去区间高度乘以 r2r 系数;若设为反向,则转为 Buy 0.01 手,止损与止盈对称翻转。 下方五个小工具函数把 MT5 内置序列访问封装成 open/high/low/close/time,入参为柱索引,返回对应品种与周期的数据,写策略时直接 open(i) 比反复调 iOpen(_Symbol,_Period,i) 更干净。 create_Rectangle 负责在图表画区间框:先用 ObjectFind 判重,不存在才 ObjectCreate 生成 OBJ_RECTANGLE,随后用 ObjectSetInteger 写第一点时间坐标。外汇与贵金属波动剧烈,这类突破单触发后滑点可能吞掉预设止损,实盘前务必在 MT5 策略测试器用历史数据核对 r2r 与 validBreakTime 的联合表现。

MQL5 / C++
obj_Trade.Sell(class="num">0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
}
  }
  class=class="str">"cmt">//--- Check for lower range breakout condition
  else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event
      isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
        obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
        obj_Trade.Buy(class="num">0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
  }
}
class=class="str">"cmt">//--- Utility functions to retrieve price and time data for a given bar index
class="type">class="kw">double open(class="type">int index){class="kw">return (iOpen(_Symbol,_Period,index));}  class=class="str">"cmt">//--- Get the opening price
class="type">class="kw">double high(class="type">int index){class="kw">return (iHigh(_Symbol,_Period,index));}  class=class="str">"cmt">//--- Get the highest price
class="type">class="kw">double low(class="type">int index){class="kw">return (iLow(_Symbol,_Period,index));}    class=class="str">"cmt">//--- Get the lowest price
class="type">class="kw">double close(class="type">int index){class="kw">return (iClose(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the closing price
class="type">class="kw">datetime time(class="type">int index){class="kw">return (iTime(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the time of the bar
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|         FUNCTION TO CREATE A RECTANGLE                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void create_Rectangle(class="type">class="kw">string objName, class="type">class="kw">datetime time1, class="type">class="kw">double price1, class="type">class="kw">datetime time2, class="type">class="kw">double price2, class="type">class="kw">color clr) {
  class=class="str">"cmt">//--- Check if the object already exists by finding it on the chart
  if (ObjectFind(class="num">0, objName) < class="num">0) {
    class=class="str">"cmt">//--- Create a rectangle object using the defined parameters: name, type, and coordinates
    ObjectCreate(class="num">0, objName, OBJ_RECTANGLE, class="num">0, time1, price1, time2, price2);
    
    class=class="str">"cmt">//--- Set the time for the first point of the rectangle(start point)
    ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">0, time1);
    
    class=class="str">"cmt">//--- Set the price for the first point of the rectangle(start point)

「矩形与趋势线的属性落地写法」

在 MT5 里用代码画矩形,第二点的时间与价格必须分别用 ObjectSetInteger 和 ObjectSetDouble 写到位,索引参数填 1 而不是 0。矩形是否填充、颜色、是否置底,全靠 OBJPROP_FILL / OBJPROP_COLOR / OBJPROP_BACK 三个整型属性控制,最后不调 ChartRedraw(0) 图表不会即时刷新。 趋势线函数 create_Line 先以 ObjectFind 判重,返回负值才 ObjectCreate,类型用 OBJ_TREND,同样把两个点的时间、价格按 0 和 1 索引设好。线宽走 OBJPROP_WIDTH、颜色走 OBJPROP_COLOR,OBJPROP_BACK 设 false 可保证线压在其他对象之上。 实盘加载这类脚本时注意:外汇与贵金属杠杆高、滑点大,对象坐标若用历史精确价位,换周期后可能偏移,建议先在 EURUSD 的 M5 上跑一遍验证锚点。

MQL5 / C++
ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">0, price1);
class=class="str">"cmt">//--- Set the time for the second point of the rectangle(end point)
ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">1, time2);
class=class="str">"cmt">//--- Set the price for the second point of the rectangle(end point)
ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">1, price2);
class=class="str">"cmt">//--- Enable the fill class="kw">property for the rectangle, making it filled
ObjectSetInteger(class="num">0, objName, OBJPROP_FILL, true);
class=class="str">"cmt">//--- Set the class="type">class="kw">color for the rectangle
ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr);
class=class="str">"cmt">//--- Set the rectangle to not appear behind other objects
ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false);
class=class="str">"cmt">//--- Redraw the chart to reflect the new changes
ChartRedraw(class="num">0);
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|    FUNCTION TO CREATE A TREND LINE                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void create_Line(class="type">class="kw">string objName, class="type">class="kw">datetime time1, class="type">class="kw">double price1, class="type">class="kw">datetime time2, class="type">class="kw">double price2, class="type">int width, class="type">class="kw">color clr, class="type">class="kw">string text) {
  class=class="str">"cmt">//--- Check if the line object already exists by its name
  if (ObjectFind(class="num">0, objName) < class="num">0) {
    class=class="str">"cmt">//--- Create a trendline object with the specified parameters
    ObjectCreate(class="num">0, objName, OBJ_TREND, class="num">0, time1, price1, time2, price2);
    class=class="str">"cmt">//--- Set the time for the first point of the trendline
    ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">0, time1);
    class=class="str">"cmt">//--- Set the price for the first point of the trendline
    ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">0, price1);
    class=class="str">"cmt">//--- Set the time for the second point of the trendline
    ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">1, time2);
    class=class="str">"cmt">//--- Set the price for the second point of the trendline
    ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">1, price2);
    class=class="str">"cmt">//--- Set the width for the line
    ObjectSetInteger(class="num">0, objName, OBJPROP_WIDTH, width);
    class=class="str">"cmt">//--- Set the class="type">class="kw">color of the trendline
    ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr);
    class=class="str">"cmt">//--- Set the trendline to not be behind other objects

◍ 依图表缩放等级自适应标注字号

在 MT5 里画水平线或对象时,若固定字号,缩放拉到最大(scale=5)会挤成一团,缩到最小(scale=0)又小到看不清。上面这段逻辑先通过 ChartGetInteger 取当前 CHART_SCALE,失败则在日志提示并沿用默认 0。 随后用 if-else 把缩放等级映射到字号:scale 0→5、1→6、2→7、3→9、4→11、5→13。这样无论你怎么滚轮缩放,右侧价位的文字标注都保持可读。外汇与贵金属波动剧烈,图表缩放是高频操作,自适应能少掉很多视觉干扰。 取回 scale 后,代码在 price2 处用 OBJ_TEXT 建一个文本对象,锚点设 ANCHOR_LEFT 贴着线左,颜色继承 clr,字体写死 Calibri,内容由外部 text 传入,最后 ChartRedraw 刷新。 isNewBar 函数则靠静态变量 prevBars 存上一帧 BAR 数,iBars 取当前数,相等即非新 BAR 返回 false,这是控制标注只在新 K 线重画的常用闸门。

MQL5 / C++
ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false);

class=class="str">"cmt">//--- Retrieve the current chart scale
class="type">long scale = class="num">0;
if(!ChartGetInteger(class="num">0, CHART_SCALE, class="num">0, scale)) {
     class=class="str">"cmt">//--- Print an error message if unable to retrieve the chart scale
     Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
}
class=class="str">"cmt">//--- Set a class="kw">default font size based on the chart scale
class="type">int fontsize = class="num">11;
if (scale == class="num">0) { fontsize = class="num">5; }
else if (scale == class="num">1) { fontsize = class="num">6; }
else if (scale == class="num">2) { fontsize = class="num">7; }
else if (scale == class="num">3) { fontsize = class="num">9; }
else if (scale == class="num">4) { fontsize = class="num">11; }
else if (scale == class="num">5) { fontsize = class="num">13; }

class=class="str">"cmt">//--- Define the description text to appear near the right price
class="type">class="kw">string txt = " Right Price";
class="type">class="kw">string objNameDescr = objName + txt;

class=class="str">"cmt">//--- Create a text object next to the line to display the description
ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time2, price2);

class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text
ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr);

class=class="str">"cmt">//--- Set the font size for the text
ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, fontsize);

class=class="str">"cmt">//--- Anchor the text to the left of the line
ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);

class=class="str">"cmt">//--- Set the text content to display the specified class="type">class="kw">string
ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + text);

class=class="str">"cmt">//--- Set the font of the text to "Calibri"
ObjectSetString(class="num">0, objNameDescr, OBJPROP_FONT, "Calibri");

class=class="str">"cmt">//--- Redraw the chart to reflect the changes
ChartRedraw(class="num">0);
}
}
class="type">bool isNewBar() {
  class=class="str">"cmt">//--- Static variable to hold the previous number of bars
  class="kw">static class="type">int prevBars = class="num">0;

  class=class="str">"cmt">//--- Get the current number of bars on the chart
  class="type">int currBars = iBars(_Symbol, _Period);

  class=class="str">"cmt">//--- If the number of bars hasn&class="macro">#x27;t changed, class="kw">return false
  if (prevBars == currBars) class="kw">return (false);

日界判定与箭头落点怎么写

日内策略最怕把跨日波动误判成同根 K 线延续。下面这段逻辑用静态变量 prevDay 存前一天序号,每次调 isNewDay() 时把 TimeCurrent() 拆成 MqlDateTime 结构取 day 字段,不相等就置 newDay=true 并 Print 出日期,相等则保持 false。 静态变量只初始化一次,所以 prevDay 在 EA 重启前一直保留,避免每 tick 重算。外汇和贵金属隔夜跳空概率不低,用这个函数切日线上下文,比直接比日期字符串省事。 drawBreakPoint() 负责把突破点画成箭头:先 ObjectFind 看对象在不在,不在就 ObjectCreate 挂 OBJ_ARROW,再设箭头代码、颜色、12 号字体。direction>0 锚定顶部、<0 锚定底部,这样上破和下破的箭头不会和蜡烛重叠。 开 MT5 把这几个函数塞进脚本,日线切换时终端会打印『WE HAVE A NEW DAY WITH DATE 数字』,箭头也会随方向自动贴边,肉眼能直接验证锚点逻辑对不对。

MQL5 / C++
class="type">bool isNewDay() {
  class="type">bool newDay = false;
  class="type">MqlDateTime Str_DateTime;
  TimeToStruct(TimeCurrent(), Str_DateTime);
  class="kw">static class="type">int prevDay = class="num">0;
  class="type">int currDay = Str_DateTime.day;
  if (prevDay == currDay) {
    newDay = false;
  }
  else if (prevDay != currDay) {
    Print("WE HAVE A NEW DAY WITH DATE ", currDay);
    prevDay = currDay;
    newDay = true;
  }
  class="kw">return (newDay);
}
class="type">void drawBreakPoint(class="type">class="kw">string objName, class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">int arrCode, class="type">class="kw">color clr, class="type">int direction) {
  if (ObjectFind(class="num">0, objName) < class="num">0) {
    ObjectCreate(class="num">0, objName, OBJ_ARROW, class="num">0, time, price);
    ObjectSetInteger(class="num">0, objName, OBJPROP_ARROWCODE, arrCode);
    ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr);
    ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, class="num">12);
    if (direction > class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
    if (direction < class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
  }
}

「给突破点加带方向的文字标注」

在 MT5 的 EA 或指标里给图形对象补文字,最常见的是给箭头旁边写一句「Break」说明。下面这段逻辑就是按多空方向把文字锚点甩到不同角落,避免压住 K 线。 核心做法是先拼出对象名,再用 ObjectCreate 以 OBJ_TEXT 类型在指定 time、price 上落字;颜色与字号分别用 OBJPROP_COLOR、OBJPROP_FONTSIZE 写死,示例里字号取 12,肉眼在 1920×1080 屏上约占用 3~4 根 1 分钟 K 线的高度。 方向判断决定锚点:direction>0 时挂 ANCHOR_LEFT_UPPER,文字在箭头左上;direction<0 时换 ANCHOR_LEFT_LOWER,掉到左下。最后 ChartRedraw(0) 强制重绘,不然新对象可能要等下一次 tick 才显出来。外汇与贵金属波动快,这类标注仅作视觉辅助,信号失效概率不低,实盘须自担高风险。

MQL5 / C++
  class="type">class="kw">string txt = " Break";
  class="type">class="kw">string objNameDescr = objName + txt;
  
  class=class="str">"cmt">//--- Create a text object for the break point description
  ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time, price);
  
  class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text description
  ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr);
  
  class=class="str">"cmt">//--- Set the font size for the text
  ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, class="num">12);
  
  class=class="str">"cmt">//--- Adjust the text anchor based on the direction of the arrow
  if (direction > class="num">0) {
     ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
     ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt);
  }
  if (direction < class="num">0) {
     ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
     ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt);
  }
   }
   class=class="str">"cmt">//--- Redraw the chart to reflect the new objects
   ChartRedraw(class="num">0);
}

◍ 把这条线请下神坛

这套每日区间突破 EA 的核心其实就三件事:用 MQL5 算出自午夜到早 6 点的当日区间、在图表上画矩形和趋势线标出突破位、突破瞬间由监控函数抓价差发单。作者在策略测试器里跑过,但没给任何收益率数字,只说要按自己的交易条件调参。 评论区有人指出原文标题有误导,实际更像“晨间平盘突破”,且代码没处理服务器 GMT 偏移,若你的券商午夜不是 00:00 开盘,就得自己改 TimeCurrent 的时区对齐逻辑。 外汇和贵金属杠杆高,自动化策略随时可能因滑点或流动性断层失效,拿去真金白银跑之前务必在 MT5 测试器用至少三年 tick 数据回测。 把 EA 文件里的 Input 参数公开出来,先只调 RangeStartHour 和 RangeEndHour 两个值,看不同币种对突破频率的影响,比直接信“突破必动”实在得多。

把回测报表交给小布读
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EA 在多个货币对上的区间突破胜率与最大回撤分布,你只需比对参数敏感度。

常见问题

4 小时过滤了更多短噪音,突破幅度倾向更显著但信号少;1 小时机会多却更容易受亚洲时段假突破干扰,需配合波动率过滤。
可用 iHigh/iLow 配合 PERIOD_D1 或 CopyRates 缓存日线,在零点切换时重算阻力支撑,避免实时 tick 重复计算。
可能未区分突破确认与追价,原策略要求在阻力上方一根收盘确认再入场,而非突破瞬间市价追,否则滑点扩大概率上升。
可以,小布盯盘的 AIGC 品种页内置了日波动区间带与突破提示,不需要自己写指标也能先看盘口结构再决定是否加载 EA。
会,尤其样本仅覆盖半年数据时。建议用 walk-forward 分段验证,止损放在上一波动高低点外侧的固定点数比纯优化更稳健。