构建自动运行的 EA(第 06 部分):账户类型(I)·综合运用
(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 用策略测试器单步跟一遍最稳妥。
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 的赋值条件,能验证锁仓账户下平仓分支是否按预期触发。外汇与贵金属杠杆交易风险高,该逻辑仅降低误平概率,不保证资金安全。
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,说明账户环境干净、历史订单读取无阻塞,才继续后续流程;否则应中断初始化。外汇与贵金属杠杆交易高风险,账户模式误判可能导致保证金计算偏差。
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 行,就说明下单链路通了;外汇与贵金属杠杆高,票号核对错可能引发重复建仓,实盘前务必在模拟盘验证。
{
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 的人最该养成的习惯,是假设每一行代码都可能在某些时刻失效。把这种谨慎直接落到初始化和错误处理里,比事后加过滤器更省钱。