MQL5交易策略自动化(第十二部分):实现缓解型订单块(MOB)策略·综合运用
📘

MQL5交易策略自动化(第十二部分):实现缓解型订单块(MOB)策略·综合运用

第 3/3 篇

订单块失效判定与反向触发逻辑

订单块对象在图表上带有结束时间与颜色属性,先通过 ObjectGetInteger 取出这两个值,再用前一根 K 线时间 time(1) 与结束时间比对:若 time(1) < orderBlockEndTime,说明该块尚未过期,doesOrderBlockExist 置真,后续才参与撮合。 价格 mitigation 的判定很直接—— bullish 块看前收 close(1) 是否跌破块下沿 orderBlockLow,bearish 块看前收是否上破 orderBlockHigh,且对应 mitigated 状态必须为 false。触发时以市价买卖:bullish 破位用 Bid 做空,bearish 破位用 Ask 做多,止损止盈按 stopLossDistance / takeProfitDistance 乘 _Point 偏移。 成交后立刻把该块 mitigated 状态写真,并将 OB 颜色改为 mitigatedOrderBlockColor,同时在原文本对象上追加 "Mitigated" 字样,方便肉眼回溯哪根块被吃掉了。 过期块由 ArrayRemove 从名称、结束时间、状态三个平行数组同步摘除(索引 j 处删 1 项)。外汇与贵金属杠杆高,OB 破位仅代表概率倾向反转,实盘须严控仓位。

MQL5 / C++
class="type">class="kw">datetime orderBlockEndTime = (class="type">class="kw">datetime)ObjectGetInteger(class="num">0, currentOrderBlockName, OBJPROP_TIME, class="num">1);
class="type">class="kw">color orderBlockCurrentColor = (class="type">class="kw">color)ObjectGetInteger(class="num">0, currentOrderBlockName, OBJPROP_COLOR);
class=class="str">"cmt">//--- Check if the order block is still valid(not expired)
if (time(class="num">1) < orderBlockEndTime) {
   doesOrderBlockExist = true;
}
class=class="str">"cmt">//---
}
class=class="str">"cmt">//--- Get current market prices
class="type">class="kw">double currentAskPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
class="type">class="kw">double currentBidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);
class=class="str">"cmt">//--- Check for mitigation and execute trades if trading is enabled
if (enableTrading && orderBlockCurrentColor == bullishOrderBlockColor && close(class="num">1) < orderBlockLow && !orderBlockMitigatedStatus[j]) {
   class=class="str">"cmt">//--- Sell trade when price breaks below a bullish order block
   class="type">class="kw">double entryPrice = currentBidPrice;
   class="type">class="kw">double stopLossPrice = entryPrice + stopLossDistance * _Point;
   class="type">class="kw">double takeProfitPrice = entryPrice - takeProfitDistance * _Point;
   obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice);
   orderBlockMitigatedStatus[j] = true;
   ObjectSetInteger(class="num">0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor);
   class="type">class="kw">string blockDescription = "Bullish Order Block";
   class="type">class="kw">string textObjectName = currentOrderBlockName + blockDescription;
   if (ObjectFind(class="num">0, textObjectName) >= class="num">0) {
      ObjectSetString(class="num">0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription);
   }
   Print("Sell trade entered upon mitigation of bullish OB: ", currentOrderBlockName);
} else if (enableTrading && orderBlockCurrentColor == bearishOrderBlockColor && close(class="num">1) > orderBlockHigh && !orderBlockMitigatedStatus[j]) {
   class=class="str">"cmt">//--- Buy trade when price breaks above a bearish order block
   class="type">class="kw">double entryPrice = currentAskPrice;
   class="type">class="kw">double stopLossPrice = entryPrice - stopLossDistance * _Point;
   class="type">class="kw">double takeProfitPrice = entryPrice + takeProfitDistance * _Point;
   obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice);
   orderBlockMitigatedStatus[j] = true;
   ObjectSetInteger(class="num">0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor);
   class="type">class="kw">string blockDescription = "Bearish Order Block";
   class="type">class="kw">string textObjectName = currentOrderBlockName + blockDescription;
   if (ObjectFind(class="num">0, textObjectName) >= class="num">0) {
      ObjectSetString(class="num">0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription);
   }
   Print("Buy trade entered upon mitigation of bearish OB: ", currentOrderBlockName);
}
class=class="str">"cmt">//--- Remove expired order blocks from arrays
if (!doesOrderBlockExist) {
   class="type">bool removedName = ArrayRemove(orderBlockNames, j, class="num">1);
   class="type">bool removedTime = ArrayRemove(orderBlockEndTimes, j, class="num">1);
   class="type">bool removedStatus = ArrayRemove(orderBlockMitigatedStatus, j, class="num">1);

「移动止损的逐行实现与回测落点」

上面这段 MQL5 把订单块数据从数组里清掉之后,紧跟着就是 trailing stop 的函数体。它先按市价算买卖两边的保护位:买仓用 BID 减 trailingPoints 个点,卖仓用 ASK 加同样点数,再用 NormalizeDouble 按品种精度收口。 循环从 PositionsTotal()-1 倒序扫持仓,用 PositionGetTicket 拿ticket,过滤本符号和 magic 号(传0表示不区分)。买仓只在「新止损高于开仓价且优于原SL或原SL为0」时才改;卖仓对称处理,条件全反。改仓只动 SL,TP 原样传回。 函数外那句 if(enableTrailingStop) 才是真正触发点,把 trailingStopPoints、交易对象、uniqueMagicNumber 灌进去。你在 MT5 里把 enableTrailingStop 设为 true,就能直接观察保护位随 tick 上移。 回测跑完会出图形与报告两项产出,用来核对这套移动止损在样本期内对浮盈回吐的削减幅度。外汇与贵金属杠杆高,回测盈利不代表实盘概率同构,参数过拟合风险得自己排。

MQL5 / C++
  if (removedName && removedTime && removedStatus) {
        Print("Success removing OB DATA from arrays at index ", j);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trailing stop function                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void applyTrailingStop(class="type">class="kw">double trailingPoints, CTrade &trade_object, class="type">int magicNo = class="num">0) {
  class=class="str">"cmt">//--- Calculate trailing stop levels based on current market prices
  class="type">class="kw">double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits);
  class="type">class="kw">double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits);
  
  class=class="str">"cmt">//--- Loop through all open positions
  for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
      class="type">ulong ticket = PositionGetTicket(i);
      if (ticket > class="num">0) {
        if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
            (magicNo == class="num">0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) {
          class=class="str">"cmt">//--- Adjust stop loss for buy positions
          if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
              buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) &&
              (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == class="num">0)) {
            trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP));
          }
          class=class="str">"cmt">//--- Adjust stop loss for sell positions
          else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
                   sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) &&
                   (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == class="num">0)) {
            trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP));
          }
        }
      }
  }
}
class=class="str">"cmt">//--- Apply trailing stop to open positions if enabled
if (enableTrailingStop) {
  applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber);
}

◍ 把策略跑起来之前先看清边界

这套缓解型订单块(MOB)逻辑在 MT5 里已经能落地:订单块识别、突破验证、趋势脉冲判定加化解逻辑,再挂上追踪止损与仓位约束,整体是一套可编译运行的 EA 框架。源码 Mitigation_Order_Blocks.mq5 约 19.27 KB,直接丢进 MetaEditor 就能回测。 有用户在策略测试器反馈:默认参数下多个货币对零成交,日志只给「没有扩展:超出范围」和「未检测到脉冲运动」。说明脉冲阈值与扩展区间若不匹配品种波动,EA 会静默空跑,不是报错而是根本不触发。 外汇与贵金属杠杆高、滑点跳空频繁,MOB 再严谨也只处理已发生的主力痕迹,对突发流动性断裂无能为力。先拿历史数据跑通脉冲参数,再谈实盘,别把教学代码当保本方案。

常见问题

失效看收盘价是否完全越过订单块另一侧并伴随放量;仅影线触碰不算,回抽不破只能算未触发,不能确认仍有效。
按持仓浮盈比例分层平移止损,每层设最小距离过滤;回测落点偏差多因点差未计入,需在回测里加载真实点差数据。
小布可加载品种页的 AIGC 诊断,自动标注订单块边界与止损触发点,你只需复核信号即可,不必手动刷图。
单笔风险别超本金2%,且需确认品种波动时段;贵金属与外汇高风险,边界不清容易连续回撤。
叠加更高周期方向确认,只在顺势侧允许反向触发;同时要求突破后两根K线不收回,可减少假信号。