构建自动运行的 EA(第 12 部分):自动化(IV)·综合运用
🤖

构建自动运行的 EA(第 12 部分):自动化(IV)·综合运用

(3/3)· 每天交易量上限里藏着的致命缺陷,正是挡在100%自动化前的最后拦路虎

案例拆解新手友好 第 3/3 篇

不少交易者以为把EA切到自动模式就能撒手,却在某个平静的交易日被一条看似无害的成交量限制悄悄拖垮。半自动时从未暴露的边界条件,一旦失去人工兜底,就会变成实时运行里的暗雷。这篇收尾把前两份铺垫过的隐患摊开,看清楚它如何被触发。

◍ 持仓校验与加仓可行性的底层判断

EA 在动手下新单前,必须先确认旧仓位还在不在。用 PositionSelectByTicket 按 ticket 回查,若查不到就把 m_Position 清零,避免拿着失效结构体继续算杠杆。 IsPossible 这个函数管的是「这口加仓到底让不让开」。它先卡时间窗口 CtrlTimeIsPassed,再卡静态杠杆上限:m_StaticLeverage 一旦大于等于账户 MaxLeverage,或对冲账户里已经有 ticket>0 的仓位,直接 return false。 把当前仓位杠杆折算成带符号整数 i0(买单为正、卖单为负),新单杠杆按方向加减得到 i1。若 i1 与 i0 异号且跨过零轴——比如多单 i0=5、i1=-2 满足 (i1<i0 && i1>=0 && i0>0)——说明这笔单在平掉部分原有暴露,函数返回 true。

若不是平仓式反向,则检查 m_StaticLeverage +i1是否突破 MaxLeverage,超了就 Print 拒绝原因并 return false。外汇与贵金属杠杆变异大,这种硬卡上限的逻辑能避免 EA 在极端波动下超仓,实盘前建议在 MT5 策略测试器里把 MaxLeverage 调低 20% 跑一遍边界。
MQL5 / C++
if (m_Position.Ticket > class="num">0) bRet = PositionSelectByTicket(m_Position.Ticket);
if (!bRet) ZeroMemory(m_Position);

class="kw">return bRet;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="type">bool IsPossible(class="kw">const ENUM_ORDER_TYPE type, class="kw">const class="type">uint Leverage)
  {
   class="type">int i0, i1;

   if (!CtrlTimeIsPassed()) class="kw">return class="kw">false;
   if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > class="num">0))) class="kw">return class="kw">false;
   if ((m_Pending.Ticket > class="num">0) || (Leverage > INT_MAX)) class="kw">return class="kw">false;
   i0 = (class="type">int)(m_Position.Ticket == class="num">0 ? class="num">0 : m_Position.Leverage) * (m_Position.IsBuy ? class="num">1 : -class="num">1);
   i1 = i0 + ((class="type">int)(Leverage * (type == ORDER_TYPE_BUY ? class="num">1 : -class="num">1)));
   if (((i1 < i0) && (i1 >= class="num">0) && (i0 > class="num">0)) || ((i1 > i0) && (i1 <= class="num">0) && (i0 < class="num">0))) class="kw">return true;
   if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
     {
      Print("Request denied, as it would violate the maximum volume allowed for the EA.");
      class="kw">return class="kw">false;

从持仓对象里抠出杠杆与方向

这段 inline 函数负责把当前 MT5 持仓的关键字段刷进自定义结构体 m_Position,并返回杠杆变动量。它先暂存旧杠杆 tmp 与旧方向 tBuy,再用 PositionGetDouble / PositionGetInteger 把实时成交量、买卖类型、TP、SL、开仓价读进来。 杠杆不是券商给的倍数,而是用 POSITION_VOLUME 除以终端最小成交量 VolMinimal 算出的手数等效值;比如 VolMinimal=0.01、持仓 0.5 手,算出来就是 50。 EnableBreakEven 的判定值得盯:订单已了结且挂单票号为 0 时直接 true,否则沿用原值;再叠加一层——买权要求 SL 低于开仓价、卖权要求 SL 高于开仓价才允许保本。 末行返回值是重点:若方向没变,返回新杠杆减旧杠杆的差值;若方向翻转,直接返回新杠杆。把这个 int 拿去比对,就能知道 EA 本次是加仓、减仓还是反手。外汇与贵金属杠杆波动意味着风险敞口突变,实盘前务必在策略测试器跑一遍验证。

MQL5 / C++
class="kw">inline class="type">int SetInfoPositions(class="type">void)
  {
    class="type">class="kw">double v1, v2;
    class="type">uint tmp = m_Position.Leverage;
    class="type">bool tBuy = m_Position.IsBuy;

    m_Position.Leverage = (class="type">uint)(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 (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > class="num">0) v1 = m_Pending.PriceOpen;
    m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 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 (class="type">int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
  }

「画得少,看得清」

自动化 EA 写到最后,最容易被忽略的不是逻辑复杂度,而是终端里几乎不打印消息——交易者本该靠这些反馈盯住系统状态。这一节没有新策略,只补一个实打实能改的参数坑。 若 EA 报错“交易量违反用法规则”而拒发订单,根因常在 C_Manager 构造末尾那个交易量上限值。原文默认填 10,意味着阻断前最多发 10 次该手数订单;外汇市场波动下常不够用,多数人直接填 100,要扩到百倍就填 1000。 下面两段是同一处初始化的对照,仅末参不同: int OnInit(){ string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for(ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) int OnInit(){ string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for(ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) 把 10 改成 1000,发送额度从 10 次提到 1000 次,外汇和贵金属的高风险环境下订单被误拦的概率会明显下降。别拿现成代码直连实盘,先在策略测试器跑多轮再动参数。

MQL5 / C++
class="type">int OnInit()
{
    class="type">class="kw">string szInfo;
    
    manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, class="kw">false, class="num">10);
    mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
    for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
class="type">int OnInit()
{
    class="type">class="kw">string szInfo;
    
    manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, class="kw">false, class="num">1000);
    mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
    for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
把边界监控交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到EA在自动跑时的成交量越界与报错倾向,你只管判断要不要人工介入。

常见问题

半自动时人工会在旁兜底,很多边界条件被手动操作掩盖;全托管后运行时错误和代码漏洞直接作用于实时交易,缺少缓冲,故障概率倾向上升。
当限制逻辑未在自动循环里正确重置或校验时,可能在跨周期或断线重连后误判已用量,导致漏单或超额,具体要看代码里的计数作用域。
可以,小布盯盘的AIGC会把品种页的EA运行状态、异常报错和成交量偏离做持续诊断,省去你盯日志的重复劳动,但外汇贵金属高风险,信号仅作参考。
本篇把减少故障的设计原则和自动化III里的监督机制拼到同一案例里,展示为何看似完美的系统仍要在自动模式下补上最后一道裂缝,完整讨论见《构建自动运行的EA·基础篇》。