构建自动运行的 EA(第 09 部分):自动化(I)·综合运用
(3/3)· 从 OCO 订单到盈亏平衡与尾随停止,拆解一套适配净持结算账户的低负载自动化机制
很多交易者把自动化想成无限挂单和密集轮询,结果 EA 越写越卡,平台资源被吃满。其实在 MetaTrader 5 里,用轻量事件配合持仓级触发,就能绕开这种超载陷阱。本篇接上两篇,把前面铺垫的类与函数真正跑成一套可用的自动化。
挂单清理与仓位信息的实时同步
这段逻辑处理的是 EA 在每轮 tick 里对挂单和当前持仓的兜底同步。当分支走到 else 时,说明前置条件未满足,先 ZeroMemory(m_Position) 把结构体清零,避免上一轮残留数据污染本次判断;若宏 def_ORDER_FINISH 为真,则调用 RemoveOrderPendent 撤掉未成交挂单并把 m_TicketPending 置 0,随后 ResetLastError 清掉可能累积的错误码。 SetInfoPositions 负责把 MT5 持仓池里的关键字段搬进自定义结构。杠杆倍数用持仓量除以终端最小交易量算出来:m_Position.Leverage = (int)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal),例如 VolMinimal 为 0.01 手时,0.1 手持仓会得出 Leverage = 10。方向、TP、SL、开仓价都从 PositionGetInteger / PositionGetDouble 取,若挂单未完成且选中成功,则用挂单开仓价 v1 覆盖 SL 参考值。 EnableBreakEven 的判定值得细看:若 def_ORDER_FINISH 且挂单已清(m_TicketPending==0),或按买卖方向判断 v1 与 v2(开仓价)的位置关系,买权时 v1<v2、卖权时 v1>v2 才允许保本触发。最后 Gap 由 FinanceToPoints 把触发金额转成点数,函数返回本次杠杆与旧值的差,供上层感知仓位变动。 TriggerBreakeven 函数体在这里只声明了 double price,具体移动 SL 到 break-even 的算法在后续片段。外汇与贵金属杠杆品种波动剧烈,这类自动保本逻辑可能在某些滑点行情下失效,实盘前建议在 MT5 策略测试器用历史数据跑一遍。
else { ZeroMemory(m_Position); if (def_ORDER_FINISH) { RemoveOrderPendent(m_TicketPending); m_TicketPending = class="num">0; } } ResetLastError(); } class="kw">inline class="type">int SetInfoPositions(class="type">void) { class="type">class="kw">double v1, v2; class="type">int tmp = m_Position.Leverage; m_Position.Leverage = (class="type">int)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal); m_Position.IsBuy = ((class="type">ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY; m_Position.TP = PositionGetDouble(POSITION_TP); v1 = m_Position.SL = PositionGetDouble(POSITION_SL); v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN); if (def_ORDER_FINISH) if (m_TicketPending > class="num">0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN); m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == class="num">0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2)); m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage); class="kw">return m_Position.Leverage - tmp; } class="kw">inline class="type">void TriggerBreakeven(class="type">void) { class="type">class="kw">double price;
◍ 保本与跟踪止损的触发逻辑
这段片段展示了在持仓盈利达到触发阈值后,把止损挪到开仓价上方(多单)或下方(空单)一个 tick 距离的实现路径。先通过 PositionSelectByTicket 锁定持仓,再用 PositionGetDouble(POSITION_PROFIT) 对比 m_Trigger,满足条件才计算保本价。 保本价计算用到了开仓价加/减 PointPerTick:多单加、空单减,对应 m_Position.IsBuy 的三元判断。若挂单已成交(def_ORDER_FINISH 为真)就改挂单的 ModifyPricePoints,否则直接改原持仓的 SL 到开仓价、新 SL 到保本价。 TriggerTrailingStop 负责总调度:Ticket 为 0 或对应单子 SL 为 0 时直接 return;若已启用保本(EnableBreakEven)就调 TriggerBreakeven,否则读 SymbolInfoDouble 取当前市场价(LAST / ASK / BID 视品种和图表模式而定)准备跟踪。外汇与贵金属波动剧烈,这类逻辑在实盘里可能频繁改单,滑点风险需自行在 MT5 回测验证。
if (PositionSelectByTicket(m_Position.Ticket)) if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger) { price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? class="num">1 : -class="num">1)); if (def_ORDER_FINISH) { if (m_TicketPending > class="num">0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, class="num">0, class="num">0); }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP); } } class="kw">inline class="type">void TriggerTrailingStop(class="type">void) { class="type">class="kw">double price, v1; if ((m_Position.Ticket == class="num">0) || (def_ORDER_FINISH ? m_TicketPending == class="num">0 : m_Position.SL == class="num">0)) class="kw">return; if (m_Position.EnableBreakEven) TriggerBreakeven(); else { price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID))); v1 = m_Position.SL;
「挂单成交后如何重定位加仓价」
当挂单已成交(def_ORDER_FINISH 为真),脚本先通过 OrderSelect 锁定原挂单票号,再用 OrderGetDouble 读出它的实际开盘价 v1。这一步很关键:你之后加仓的参考点不能是预设价,而是真实成交价。 若 v1 大于 0,且当前 price 与 v1 的绝对差不小于 m_Position.Gap 的两倍,说明价格已脱离原挂单位置足够远,具备重定价条件。此时按方向重写 price:买入侧加一个 Gap,卖出侧减一个 Gap。 重定价后分两条路走:挂单类调用 ModifyPricePoints 改挂单价;持仓类则把原持仓的开盘价、新 price、原 TP 一并传入修改。外汇与贵金属杠杆高,这种自动重定位若 Gap 设太小,可能在震荡里频繁改单、放大滑点风险。 把下面这段直接贴进 MT5 的 EA 调试,把 m_Position.Gap 从 10 点调到 20 点,观察 ModifyPricePoints 的触发次数变化,比看文字直观得多。
if (def_ORDER_FINISH) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN); if (v1 > class="num">0) if (MathAbs(price - v1) >= (m_Position.Gap * class="num">2)) { price = v1 + (m_Position.Gap * (m_Position.IsBuy ? class="num">1 : -class="num">1)); if (def_ORDER_FINISH) ModifyPricePoints(m_TicketPending, price, class="num">0, class="num">0); else ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP); }
从模拟账户跑通双指令防跳空
前面几节把基于止损线的最简触发逻辑讲完了,附件里的 EA 初始只带止损线,要用挂单当止损得照着改。若想把它塞进投资组合里自动跑,这套机制倾向不合适,更建议留作手动系统的补充。外汇和贵金属波动大、跳空频繁,这类品种用 OCO 单时止损可能被跳过。 有读者在讨论里问过:OCO 平仓后挂单是否一并撤销。实际在服务器侧,若开仓时就把止损止盈绑在单子上,只发一次请求,触发后整仓平掉;但价格跳空时 OCO 可能不成交。部分程序员改用两张指令控仓,账簿里留一个不被覆盖的“跛脚”单,即使市价跳过 OCO 也大概率能接住平仓。不过对冲账户允许同标的多空同开,这招在 HEDGING 账户上不生效,净额账户才会在第二张单触发时把仓平掉。 想真弄懂,别光看文字。开 MT5 建模拟账户,外汇用对冲、股票用净额,把附带的 EA_Automatico_-_09.zip(9.27 KB)拖进去跑一遍,观察跳空时段两张指令的成交差异,比读十遍描述都实在。