MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统·综合运用
◍ 初始订单与状态机骨架
这段逻辑把一个 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 分支而不是爆单。
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 击穿频率。
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()。回测完成后报告会展示成交与权益曲线,但任何历史表现都只是概率参考。
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 决定首轮最多铺几个子单。外汇与贵金属杠杆高、滑点跳空频繁,参数没压住前别挂实盘。 教学归教学,市场不会按回测跑。真要验证,拿历史数据跑完再看权益曲线断层出现在哪段行情,比看任何结论都实在。