MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统·综合运用
📘

MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统·综合运用

第 3/3 篇

◍ 初始订单与状态机骨架

这段逻辑把一个 Zone 交易类的初始化和首单开口拆得很清楚:构造函数里先给恢复单打标签 EA_RECOVERY_+magic,同时把恢复标志和移动止损层级归零,析构时只做一件事——释放 activeTickets 数组,避免 EA 重载时内存残留。 openInitialOrder 是实盘里最该盯的函数。它按信号区分 BUY/SELL,用 NormalizeDouble(getMarketAsk(), Digits()) 取合规开价;手数支持固定与按风险百分比两种,若算出来 ≤0 直接返回 -1 并打印 Magic 编号,方便多实例排查。PositionOpen 成功后会把 ResultOrder 强转 int 作为 ticket 返回。 evaluateMarketTick 显示状态机只认 INACTIVE / TERMINATING / 其他运行态:非活跃直接跳过,终止态调 finalizePosition 收尾。BUY 方向下用 Bid 算浮盈点数 (currentPrice - openPrice)/_Point,且仅当 enableInitialTrailing && !hasRecoveryTrades && profitPoints >= minProfitPoints 才触发初始移动止损——这意味着恢复单挂上后,首单 trailing 会被刻意禁用。外汇与贵金属杠杆高,这套状态隔离能降低多单纠缠风险,但参数误设仍可能快速放大回撤。 开 MT5 把这段塞进你的 EA 骨架,先单测 openInitialOrder 在 FIXED_LOTSIZE 下能否稳定返回正 ticket;再故意把 m_initialLotSize 设 0,确认它走 -1 分支而不是爆单。

MQL5 / C++
m_tradeConfig.recoveryTradeLabel = "EA_RECOVERY_" + IntegerToString(magic); class=class="str">"cmt">//--- Label for recovery positions
m_tradeConfig.hasRecoveryTrades = false; class=class="str">"cmt">//--- Initialize recovery flag
m_tradeConfig.trailingStopLevel = class="num">0.0; class=class="str">"cmt">//--- Initialize trailing stop
m_tradeExecutor.SetExpertMagicNumber(magic);
 }
 ~MarketZoneTrader() {
   ArrayFree(m_tradeConfig.activeTickets);
 }

class="type">bool activateTrade(class="type">ulong ticket) {
  m_tradeConfig.hasRecoveryTrades = false;
  m_tradeConfig.trailingStopLevel = class="num">0.0;

  class=class="str">"cmt">//--- THE REST OF THE LOGIC REMAINS

  class="kw">return true;
}

class="type">int openInitialOrder(ENUM_ORDER_TYPE orderType) {
  class=class="str">"cmt">//--- Open INITIAL position based on signal
  class="type">int ticket;
  class="type">class="kw">double openPrice;
  if (orderType == ORDER_TYPE_BUY) {
    openPrice = NormalizeDouble(getMarketAsk(), Digits());
  } else if (orderType == ORDER_TYPE_SELL) {
    openPrice = NormalizeDouble(getMarketBid(), Digits());
  } else {
    Print("Invalid order type [Magic=", m_tradeConfig.tradeIdentifier, "]");
    class="kw">return -class="num">1;
  }
  class="type">class="kw">double lotSize = class="num">0;
  if (m_lotOption == FIXED_LOTSIZE) {
    lotSize = m_initialLotSize;
  } else if (m_lotOption == UNFIXED_LOTSIZE) {
    lotSize = calculateLotSize(m_riskPercentage, m_riskPoints);
  }
  if (lotSize <= class="num">0) {
    Print("Invalid lot size [Magic=", m_tradeConfig.tradeIdentifier, "]: ", lotSize);
    class="kw">return -class="num">1;
  }
  if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, class="num">0, class="num">0, m_tradeConfig.initialTradeLabel)) {
    ticket = (class="type">int)m_tradeExecutor.ResultOrder();
    Print("INITIAL trade opened [Magic=", m_tradeConfig.tradeIdentifier, "]: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize);
  } else {
    ticket = -class="num">1;
    Print("Failed to open INITIAL order [Magic=", m_tradeConfig.tradeIdentifier, "]: Type=", EnumToString(orderType), ", Volume=", lotSize);
  }
  class="kw">return ticket;
}

class="type">void evaluateMarketTick() {
  if (m_tradeConfig.currentState == INACTIVE) class="kw">return;
  if (m_tradeConfig.currentState == TERMINATING) {
    finalizePosition();
    class="kw">return;
  }
  class="type">class="kw">double currentPrice;
  class="type">class="kw">double profitPoints = class="num">0.0;
  class=class="str">"cmt">//--- Handle BUY initial position
  if (m_tradeConfig.direction == ORDER_TYPE_BUY) {
    currentPrice = getMarketBid();
    profitPoints = (currentPrice - m_tradeConfig.openPrice) / _Point;
    class=class="str">"cmt">//--- Trailing Stop Logic for Initial Position
    if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
      class=class="str">"cmt">//--- Calculate desired trailing stop level

「初始持仓的跟踪止损与区间回收触发」

这段代码处理两类初始持仓方向:BUY 与 SELL,核心是在盈利达到阈值后启动跟踪止损,并在价格触及区间边界时执行平仓或触发回收单。外汇与贵金属杠杆高,这类逻辑若参数设错,可能在震荡中频繁触发回收单放大风险。 以 BUY 为例,newTrailingStop 用 currentPrice 减去 trailingStopPoints 个 _Point 算出跟踪线。只有当 profitPoints 不小于 minProfitPoints + trailingStopPoints 时,才允许把 trailingStopLevel 上移;若价格回踩到该水平及以下,就调用 finalizePosition() 平仓并 return。 SELL 方向镜像处理:跟踪止损线为 currentPrice 加上 trailingStopPoints * _Point,且要求 newTrailingStop 小于已有 trailingStopLevel 才更新,避免止损线向亏损方向倒退。 区间回收部分不依赖跟踪止损。当 currentPrice 高于 zoneTargetHigh 直接平仓;低于 zoneLow 则 triggerRecoveryTrade() 发反向单。实盘前建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍,观察 zoneLow 击穿频率。

MQL5 / C++
class="type">class="kw">double newTrailingStop = currentPrice - trailingStopPoints * _Point;
class=class="str">"cmt">//--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
if (profitPoints >= minProfitPoints + trailingStopPoints) {
   if (m_tradeConfig.trailingStopLevel == class="num">0.0 || newTrailingStop > m_tradeConfig.trailingStopLevel) {
      m_tradeConfig.trailingStopLevel = newTrailingStop;
      Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
   }
}
class=class="str">"cmt">//--- Check if price has hit trailing stop
if (m_tradeConfig.trailingStopLevel > class="num">0.0 && currentPrice <= m_tradeConfig.trailingStopLevel) {
   Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " <= TrailingStop=", m_tradeConfig.trailingStopLevel);
   finalizePosition();
   class="kw">return;
}
}
class=class="str">"cmt">//--- Zone Recovery Logic
if (currentPrice > m_zoneBounds.zoneTargetHigh) {
   Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh);
   finalizePosition();
   class="kw">return;
} else if (currentPrice < m_zoneBounds.zoneLow) {
   Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow);
   triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);
}
   }
   class=class="str">"cmt">//--- Handle SELL initial position
   else if (m_tradeConfig.direction == ORDER_TYPE_SELL) {
   currentPrice = getMarketAsk();
   profitPoints = (m_tradeConfig.openPrice - currentPrice) / _Point;
   class=class="str">"cmt">//--- Trailing Stop Logic for Initial Position
   if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
      class=class="str">"cmt">//--- Calculate desired trailing stop level
      class="type">class="kw">double newTrailingStop = currentPrice + trailingStopPoints * _Point;
      class=class="str">"cmt">//--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
      if (profitPoints >= minProfitPoints + trailingStopPoints) {
         if (m_tradeConfig.trailingStopLevel == class="num">0.0 || newTrailingStop < m_tradeConfig.trailingStopLevel) {
            m_tradeConfig.trailingStopLevel = newTrailingStop;
            Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
         }
      }
      class=class="str">"cmt">//--- Check if price has hit trailing stop

追踪止损与区间恢复的执行分支

这段逻辑是篮子管理器在每根报价到来时的核心决策:先判追踪止损,再判区间恢复。 当 trailingStopLevel 大于 0 且当前 Ask 不低于该阈值时,直接打印触发信息并调用 finalizePosition() 平仓退出。这意味着追踪止损位是人工设定的硬边界,不是动态计算的。 若价格跌破 zoneTargetLow,同样平仓收尾;若涨过 zoneHigh,则不平仓而是 triggerRecoveryTrade() 反向开多,进入恢复交易。外汇与贵金属杠杆高,这种恢复单可能快速放大浮亏,实盘前需用策略测试器跑通逻辑。 全局实例部分只做三件事:OnInit 里 new 出 BasketManager 并初始化,失败就删实例返回 INIT_FAILED;OnDeinit 释放指针;OnTick 把报价交给 processTick()。回测完成后报告会展示成交与权益曲线,但任何历史表现都只是概率参考。

MQL5 / C++
    if (m_tradeConfig.trailingStopLevel > class="num">0.0 && currentPrice >= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " >= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            class="kw">return;
        }
      class=class="str">"cmt">//--- Zone Recovery Logic
      if (currentPrice < m_zoneBounds.zoneTargetLow) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow);
         finalizePosition();
         class="kw">return;
      } else if (currentPrice > m_zoneBounds.zoneHigh) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh);
         triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice);
      }
   }
}
class=class="str">"cmt">//--- Global Instance
BasketManager *manager = NULL;
class="type">int OnInit() {
   manager = new BasketManager(_Symbol, baseMagicNumber, maxInitialPositions);
   if (!manager.initialize()) {
      class="kw">delete manager;
      manager = NULL;
      class="kw">return INIT_FAILED;
   }
   class="kw">return INIT_SUCCEEDED;
}
class="type">void OnDeinit(const class="type">int reason) {
   if (manager != NULL) {
      class="kw">delete manager;
      manager = NULL;
      Print("EA deinitialized");
   }
}
class="type">void OnTick() {
   if (manager != NULL) {
      manager.processTick();
   }
}

◍ 别急着下结论

这套在包络线区间补仓基础上接进移动止损与多篮子管理的框架,核心新增了 BasketManager 类与改写后的 MarketZoneTrader 函数,把原本单线摊平的逻辑扩成了可按 zone 回收的仓位组。 想直接上手,把 EA 文件 Envelopes_Trend_Bounce_with_Zone_Recovery_Trailing_Stop_EA.mq5(28.24 KB)拖进 MT5 后,先动两个参数:trailingStopPoints 控制回撤跟随的距离,maxInitialPositions 决定首轮最多铺几个子单。外汇与贵金属杠杆高、滑点跳空频繁,参数没压住前别挂实盘。 教学归教学,市场不会按回测跑。真要验证,拿历史数据跑完再看权益曲线断层出现在哪段行情,比看任何结论都实在。

常见问题

用状态机骨架管初始订单,只在价格进入预设区间且前一笔未触发回收时才开,避免重叠建仓。
不会,执行分支里追踪止损只跟初始持仓走,区间恢复触发另走判断,两者用不同条件隔离。
小布可接入你的品种页,自动标出跟踪止损偏移和区间回收信号,省去你盯多图表。
是,外汇杠杆高、跳空多,篮子补仓可能快速放大亏损,务必用小仓和硬止损上限。
优先查滑点和区间边界在实盘是否被突破,以及状态机有没有漏掉恢复分支的边界情况。