构建自动运行的 EA(第 06 部分):账户类型(I)·综合运用
🛡️

构建自动运行的 EA(第 06 部分):账户类型(I)·综合运用

(3/3)· 当 EA 直接碰订单系统,一个错参就可能触发异常平仓,隔离层才是自动化最后一块拼图

实战向 第 3/3 篇
把下单逻辑直接写进 EA 主循环,看似省事,实则每次调用都要重复校验账户类型和成交模式。引入中间管理层后,参数只在初始化时定一次,后续调用自动继承,安全边界立刻收紧。

「挂单与持仓的过滤逻辑」

在 EA 初始化或刷新持仓快照时,先遍历当前账户全部订单,用 OrderGetTicket 按索引取ticket,取不到(返回0)就跳过该次循环。接着用 OrderGetString(ORDER_SYMBOL) 比对 _Symbol,不是当前图表品种的直接 continue,避免跨品种误处理。 再用 OrderGetInteger(ORDER_MAGIC) 比对自家 magic number,魔法码不对的订单不纳入管理范围,这是多 EA 同账户共存时的基础隔离手段。若开启对冲账户模式且已记录某持仓 ticket,则把对应挂单 RemoveOrderPendent 后 continue,防止同方向重复挂单。 普通(非对冲)模式下,若 m_TicketPending 已大于0说明前面已有挂单,调用 SetUserError(ERR_Unknown) 标记异常,否则把本次 value 记进 m_TicketPending。下面这段是订单遍历的核心片段。 LoadPositionValid 里用 PositionsTotal()-1 倒序取持仓 ticket,同样以返回值0为过滤条件,保证只处理真实存在的持仓。外汇与贵金属杠杆高,这类过滤若写错可能误撤实盘挂单,上 MT5 用策略测试器单步跟一遍最稳妥。

MQL5 / C++
if ((value = OrderGetTicket(c0)) == class="num">0) class="kw">continue;
if (OrderGetString(ORDER_SYMBOL) != _Symbol) class="kw">continue;
if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) class="kw">continue;
if ((m_bAccountHedging) && (m_Position.Ticket > class="num">0))
{
   RemoveOrderPendent(value);
   class="kw">continue;
}
if (m_TicketPending > class="num">0) SetUserError(ERR_Unknown); else m_TicketPending = value;

class="kw">inline class="type">void LoadPositionValid(class="type">void)
{
   class="type">class="kw">ulong value;
   for (class="type">int c0 = PositionsTotal() - class="num">1; (c0 >= class="num">0) && (_LastError == ERR_SUCCESS); c0--)
   {
      if ((value = PositionGetTicket(c0)) == class="num">0) class="kw">continue;

◍ 持仓遍历里的锁仓与魔术码过滤

在 EA 的持仓管理循环里,先按品种和魔术码做一次硬过滤:不是当前图表品种的直接跳过,魔术码不匹配的也跳过。这样能避免把手动单或其他 EA 的挂单误当成自己的仓位来处理。 当账户开启锁仓模式(m_bAccountHedging 为真)且已存在挂单 ticket(m_TicketPending > 0)时,代码会直接平掉该持仓并 continue。这意味着在有未成交挂单的锁仓账户上,新持仓不会被重复统计,逻辑上倾向只维护一条活跃线。 构造函数 C_Manager 接收魔术码、止损金额、止盈金额、杠杆、是否日内以及触发阈值,并把 m_bAccountHedging 初始化为 false、m_TicketPending 置 0。在初始化体内调用 ResetLastError 与 ZeroMemory(m_Position),可排除上一轮错误码和残留仓位结构对本次扫描的干扰。 开 MT5 把这段贴进自己的持仓类,改 m_TicketPending 的赋值条件,能验证锁仓账户下平仓分支是否按预期触发。外汇与贵金属杠杆交易风险高,该逻辑仅降低误平概率,不保证资金安全。

MQL5 / C++
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) class="kw">continue;
if(PositionGetInteger(POSITION_MAGIC)!=GetMagicNumber()) class="kw">continue;
if((m_bAccountHedging)&&(m_TicketPending>class="num">0))
{
   ClosePosition(value);
   class="kw">continue;
}
if(m_Position.Ticket>class="num">0) SetUserError(ERR_Unknown); else
{
   m_Position.Ticket=value;
   SetInfoPositions();
}
C_Manager(class="kw">const class="type">class="kw">ulong magic,class="type">class="kw">double FinanceStop,class="type">class="kw">double FinanceTake,class="type">uint Leverage,class="type">bool IsDayTrade,class="type">class="kw">double Trigger)
   :C_Orders(magic),
   m_bAccountHedging(class="kw">false),
   m_TicketPending(class="num">0),
   m_Trigger(Trigger)
{
   class="type">class="kw">string szInfo;
   ResetLastError();
   ZeroMemory(m_Position);
   m_InfosManager.FinanceStop=FinanceStop;

账户对冲属性与订单校验的初始化分支

EA 初始化阶段要把风控参数先塞进信息管理对象:止盈金额、杠杆倍数、是否日内交易这三个字段直接赋值,后续下单逻辑都从 m_InfosManager 里读,避免散落全局变量。 紧接着用 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 拿账户保证金模式,转成枚举后走 switch:零售对冲返回 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING 时把 m_bAccountHedging 置 true 并打印 HEDGING;净仓模式对应 NETTING;交易所模式对应 EXCHANGE。三种模式决定持仓计算方式,外汇 MT5 真实账户多为 NETTING 或 HEDGING,开仓前必须确认。 识别完模式后 Print 打出 Detected Account 及具体类型,然后调用 LoadOrderValid() 与 LoadPositionValid() 做订单和持仓的合法性预载。若 _LastError 仍为 ERR_SUCCESS,说明账户环境干净、历史订单读取无阻塞,才继续后续流程;否则应中断初始化。外汇与贵金属杠杆交易高风险,账户模式误判可能导致保证金计算偏差。

MQL5 / C++
m_InfosManager.FinanceTake = FinanceTake;
 m_InfosManager.Leverage     = Leverage;
 m_InfosManager.IsDayTrade  = IsDayTrade;
 class="kw">switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
 {
   case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
     m_bAccountHedging = true;
     szInfo = "HEDGING";
     break;
   case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
     szInfo = "NETTING";
     break;
   case ACCOUNT_MARGIN_MODE_EXCHANGE:
     szInfo = "EXCHANGE";
     break;
 }
 Print("Detected Account ", szInfo);
 LoadOrderValid();
 LoadPositionValid();
 if (_LastError == ERR_SUCCESS)

「上传成功后的单号回执拼装」

在 EA 把挂单或持仓同步到服务端之后,需要把结果打印出来供人工核对。下面这段逻辑只做一件事:拼一个 szInfo 字符串,成功提示打头,再按需追加持仓 Ticket 与挂单 Ticket。 如果 m_Position.Ticket 大于 0,说明当前有持仓,就补一行「Ticket Position:」加票号;m_TicketPending 大于 0 则补「Ticket Order:」加挂单票号。两个判断各自独立,可能只打印其中一个,也可能都不打(都非正时只剩成功提示)。 Print(szInfo) 把最终字符串吐到 MT5 专家日志。你在策略测试器里跑一遍,日志出现「Successful upload...」且带 Ticket 行,就说明下单链路通了;外汇与贵金属杠杆高,票号核对错可能引发重复建仓,实盘前务必在模拟盘验证。

MQL5 / C++
            {
                  szInfo = "Successful upload...";
                  szInfo += StringFormat("%s", (m_Position.Ticket > class="num">0 ? "\nTicket Position: " + (class="type">class="kw">string)m_Position.Ticket + "\n" : ""));
                  szInfo += StringFormat("%s", (m_TicketPending > class="num">0 ? "\nTicket Order: " + (class="type">class="kw">string)m_TicketPending : ""));
                  Print(szInfo);
            }

◍ 把谨慎写进 EA 的第一行

自动化 EA 的门槛不在语法,而在容错。经验不足的人直接上手写自主交易逻辑,很容易在异常行情里把账户拖进无法挽回的坑,外汇和贵金属的高杠杆特性会把这个风险再放大数倍。 下一篇会接着拆「如何把 EA 安全地挂上图表」这件事。核心前提不变:任何自动逻辑上线前,都要先在策略测试器里用真实点差和滑点跑过,别拿实盘当调试场。 写 EA 的人最该养成的习惯,是假设每一行代码都可能在某些时刻失效。把这种谨慎直接落到初始化和错误处理里,比事后加过滤器更省钱。

交给小布盯盘看账户边界
这些账户类型与订单模式的自检,小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前环境是否匹配你的 EA 假设,把重复劳动交给小布,你专注决策。

常见问题

私有继承让 EA 无法直接触达底层订单函数,所有访问必须经过管理类转发,避免误传参数导致异常成交,可靠性显著提升。
可以,小布盯盘的品种页会标注当前账户是 NETTING 还是 HEDGE 模式,并提示 EA 兼容性,减少手动核对的遗漏。
过早加入这类机制会增加重构成本,作者选择先建立通用管理层,等结构稳定后再叠加风控逻辑,降低返工概率。
EXCHANGE 更接近交易所撮合逻辑,持仓与订单处理与 HEDGE 的对冲逻辑不同,EA 发送请求前必须确认模式,否则可能拒绝执行。