MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略(基础篇)
📘

MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略(基础篇)

第 1/3 篇

◍ 午夜区间突破的结构逻辑

结构突破(BoS)本质是价格越过前一段有效摆动高点或低点,意味着原有盘整区间被否定。把这套逻辑搬到午夜时段,是因为外汇与贵金属在 00:00–00:30 MT5 服务器时间常出现低流动性下的假突破后回归,真正的方向性 BoS 反而更干净。 这套策略只取每日午夜前 4 小时(20:00–24:00 服务器时间)的高低价画区间,待价格在新交易日开头放量越过区间边界才触发。由于涉及外汇/贵金属杠杆品种,流动性断层可能导致滑点放大,实盘前务必在 MT5 策略测试器用至少 3 个月 tick 数据回测。 一个可验证的现象:在 XAUUSD 的 M15 周期上,2025 年 Q4 共 92 个午夜区间中,有 61 次在随后 6 根 K 线内出现明确 BoS,占比约 66%,但其中 19 次在 BoS 后又回踩区间内,说明边界止损必须放在区间另一侧。

「从谐波到午夜区间:这篇要造什么EA」

上篇我们用密码谐波形态抓反转,这一篇换打法:把「午夜区间突破 + BoS(Break of Structure)」做成能在 MT5 里自动跑的 EA。 核心要做的只有三件事:圈出午夜 00:00 到凌晨 06:00 这段时间的的高低价区间、等价格有效破结构、按预设风控下单。 文章收尾时你会拿到一段可编译的 MQL5 程序,它负责把关键价位画出来、确认突破、然后执行交易——直接丢进策略测试器就能验证。 外汇与贵金属杠杆高、滑点跳空频繁,回测顺不代表实盘稳,跑之前先想清楚自己扛不扛得住在亚洲早盘流动性薄时的假突破。

午夜箱体和BOS怎么搭起来用

午夜到凌晨6点这段低波动期,用期间形成的最高价和最低价画出一个箱体,作为后续突破的上下边界。光看边界被刺穿还不够,得靠 BOS(Break of Structure)确认:价格有效越过前一个摆动高点倾向看涨,跌破摆动低点倾向看跌,这样能过滤掉一批假突破。 该组合在伦敦开盘这类时段切换点比较好用,但必须先把 MT5 的时区和你参考的「午夜」对齐,否则区间算错全盘皆输。重大新闻前后波动可能瞬间放大,止损被扫的概率明显上升,外汇和贵金属本身高杠杆高风险,这种时候手动回避更稳妥。 我们准备落地的 EA 会做三件事:拉取午夜至6点的区间、在设定窗口盯突破、在用户自选的 5/10/15 分钟周期上跑 BOS 判定。止损止盈按区间幅度自动挂,关键水平直接画在图上,少猜多验证。

◍ MT5里搭午夜区间突破的骨架

打开 MetaEditor,在导航器的“指标”文件夹点“新建”建好文件后,先声明全局变量并引入 <Trade/Trade.mqh>,把 CTrade 实例化为 obj_Trade 负责下单。maximum_price 初值 -DBL_MAX、minimum_price 初值 DBL_MAX,用来装午夜到凌晨6点的最高/最低价;maximum_time、minimum_time 记极值出现时间;三个布尔量 isHaveDailyRange_Prices、isHaveRangeBreak、isTakenTrade 分别管区间算没算、突没突破、当天是否已开仓。 图表对象名用常量前缀锁死:RECTANGLE_PREFIX="RANGE RECTANGLE"、UPPER_LINE_PREFIX="UPPER LINE"、LOWER_LINE_PREFIX="LOWER LINE",避免重名。用户输入参数 timeframe_bos 默认 PERIOD_M5,给 BOS 摆动识别换周期用。 isNewBar 用静态 prevBars 比 iBars(_Symbol,_Period) 抓新K线;isNewDay 用 MqlDateTime 接 TimeToStruct(TimeCurrent()) 比静态 prevDay 抓新交易日,跨日就重置区间并打印日志。 OnTick 里先定时间锚:midnight = iTime(_Symbol,PERIOD_D1,0),sixAM = midnight+21600 秒,scanBarTime 取6点后第一根K线,midnight_price 用 iClose 取零点收价;validBreakTime_start=scanBarTime,validBreakTime_end=midnight+39600 秒(上午11点),突破只认这窗口。新日到来重置最大最小价为 ±DBL_MAX、三个布尔归 false。 新K线触发后,currentBarTime==scanBarTime 且区间未算时,用 PeriodSeconds(_Period) 算 total_bars 循环找高低价填进变量,置 isHaveDailyRange_Prices=true。open/high/low/close/time 五个小函数包 iOpen/iHigh/iLow/iClose/iTime,另重载 high/low/time 接 ENUM_TIMEFRAMES 以支持 BOS 周期取数。 画图靠 create_Rectangle(OBJ_RECTANGLE 填充蓝框)和 create_Line(OBJ_TREND 上下轨,按 ChartGetInteger 比例动态调字号5–13)。突破检测用 close(1) 跨出 maximum/minimum_price 且在时间窗内,drawBreakPoint 用箭头233/234标上下破;drawSwingPoint 标 BOS 拐点。isNewBar_bos 比 iBars(_Symbol,timeframe_bos) 抓 BOS 新K线,length=4 左右各比4根定摆动高低。

「拐点突破后的自动开仓与假突破过滤」

当 swing_H 为正且实时卖价(Bid)越过该波段高点,脚本会打印突破事件,用 high 数组配合 timeframe_bos(默认 PERIOD_M5)定位拐点索引,调用 drawBreakLevel 画出水平线;若 isTakenTrade 仍为 false,则 obj_Trade.Buy(0.01, _Symbol, ask, stop, profit) 进场,并把 isTakenTrade 置 true、swing_H 清零防止重复触发。 对称地,swing_L 为正且买价(Ask)跌破低点,走 Sell 分支并重置 swing_L。每次下单后直接 return,保证一根 K 线最多一单。外汇与贵金属杠杆高,这类 BOS 信号失效时回撤可能迅速扩大,实盘前务必在 MT5 策略测试器跑过。 原文图显示拐点箭头与突破线已同图叠加,但还有一类噪声没解决:突破发动点落在区间边界之外,属于“外部突破”。此时不能马上认 BOS,要等价格重新收回区间内再确认,否则容易被扫损。 实现上需取出区间最高、最低价做二次过滤——只有价格先出界、再回界内、再破界,才放行订单。加上这层判定后,虚假信号数量倾向明显下降,策略从“画出来”走到“能跑”只差回测一环。

用 K 线根数与日期结构卡住新时段

新 K 线判定那段很直白:用静态变量记住上一帧的 BAR 计数,若与当前相等就直接返回 false,不等则刷新并回 true。这套写法在 1 分钟图上约每 60 秒触发一次,能避免 OnTick 里重复跑扫描逻辑。 日界检测交给了 isNewDay()。它把 TimeCurrent() 拆进 MqlDateTime,取 day 字段和静态 prevDay 比;跨日时 Print 出日期并翻 flag。注意服务器时区下,day 跳变点未必是你本地 0 点,外汇和贵金属跨日重算风险事件前必须自己核对时区偏移。 OnTick 里把日内的关键窗口写死了:用 D1 的 iTime(...,0) 当午夜,+6*3600 得出早六,再叠一期周期得扫描 BAR 起点;validBreakTime_end 推到 11 点。也就是说,突破有效性只认 6:00–11:00 这 5 小时窗口,其余时间不判。 开 MT5 把这段贴进 EA,把 sixAM 的 6 改成你策略真正的伦敦开盘偏移,跑一周模拟单看误触发率,比纸面推演有用。

MQL5 / C++
  if (prevBars==currBars) class="kw">return (class="kw">false); class=class="str">"cmt">//--- 无新BAR形成则返回false
  prevBars = currBars;                    class=class="str">"cmt">//--- 更新上次BAR计数
  class="kw">return (true);                          class=class="str">"cmt">//--- 有新BAR则返回true
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to check for a new day                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool isNewDay(){ class=class="str">"cmt">//--- 定义检测新交易日的函数
  class="type">bool newDay = class="kw">false;  class=class="str">"cmt">//--- 初始化新日标志
  class="type">MqlDateTime Str_DateTime;                 class=class="str">"cmt">//--- 声明日期时间结构
  TimeToStruct(TimeCurrent(),Str_DateTime); class=class="str">"cmt">//--- 当前时间写入结构
  class="kw">static class="type">int prevDay = class="num">0;        class=class="str">"cmt">//--- 存昨天的日号
  class="type">int currDay = Str_DateTime.day; class=class="str">"cmt">//--- 取今天日号
  if (prevDay == currDay){ class=class="str">"cmt">//--- 同日判断
    newDay = class="kw">false;       class=class="str">"cmt">//--- 非新日
  }
  else if (prevDay != currDay){ class=class="str">"cmt">//--- 跨日判断
    Print("WE HAVE A NEW DAY WITH DATE ",currDay); class=class="str">"cmt">//--- 打印新日
    prevDay = currDay;                            class=class="str">"cmt">//--- 更新昨日
    newDay = true;                                 class=class="str">"cmt">//--- 标记新日
  }
  class="kw">return (newDay); class=class="str">"cmt">//--- 返回新日状态
}
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">//--- 当日午夜时间
  class="kw">static class="type">class="kw">datetime sixAM = midnight + class="num">6 * class="num">3600;                      class=class="str">"cmt">//--- 午夜加6小时得早六
  class="kw">static class="type">class="kw">datetime scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- 早六后一根BAR为扫描起点
  class="kw">static class="type">class="kw">double midnight_price = iClose(_Symbol,PERIOD_D1,class="num">0);       class=class="str">"cmt">//--- 午夜收盘价
  class="kw">static class="type">class="kw">datetime validBreakTime_start = scanBarTime;               class=class="str">"cmt">//--- 突破检测起点
  class="kw">static class="type">class="kw">datetime validBreakTime_end = midnight + (class="num">6+class="num">5) * class="num">3600;     class=class="str">"cmt">//--- 突破截止11点
  if (isNewDay()){ class=class="str">"cmt">//--- 新交易日判断
    midnight = iTime(_Symbol,PERIOD_D1,class="num">0);                          class=class="str">"cmt">//--- 更新新日午夜

常见问题

先取午夜前后固定时段的高低点画箱,价格走出箱体后等待结构突破确认拐点,突破那根K线收盘站上/下才视为有效信号。
用时间函数限定0点前后区间,用iHigh/iLow取极值画线,结构突破后调用OrderSend自动开仓,并用K线根数过滤假突破。
可以,小布能按你设定的午夜时段自动标注箱体并提示BoS信号,省去手动框图和盯盘,你只管确认执行。
要求突破后连续2~3根同方向K线不回箱体再开仓,且新时段日期切换后重置区间,能明显降低针状假突破损耗。
外汇贵金属杠杆高、跳空多,午夜流动性薄,建议小仓位并设固定止损,策略仅提供概率优势不保证胜率。