构建自动运行的 EA(第 07 部分):账户类型(II)(基础篇)
先看清账户类型再写 EA
MT5 里跑 EA 前,账户类型是绕不开的前提。同一套下单逻辑,在净头寸账户(hedging 关闭)和锁仓账户(hedging 开启)上行为完全不同,写错判断条件会直接让 PositionSelect 返回错误结果。
- 年 4 月 3 日发布的这篇系列第 7 部分原文阅读量 1754,说明很多实盘者卡在账户差异而不是策略本身。开 MT5 按 F2 看账户窗口的『对冲』字段,能立刻确认当前是哪种模式。
净头寸账户里同一品种只能有一条 position,平仓用 POSITION_TYPE 反手;锁仓账户允许同品种多空各一条。EA 初始化时用 ENUM_ACCOUNT_MODE 读 ACCOUNT_MARGIN_MODE 之外的属性,比运行时反复猜更稳。
◍ 净持结算账户带来的隐藏麻烦
上一节里我们搭好了 C_Manager 类,用来在 EA 行为异常时把它从图表强制移除,也顺手做了挂单和市价单的触发拦截。但这套机制还没覆盖完 EA 与 C_Orders 类之间的所有交互死角,净持结算账户就是其中一个典型坑。 在净持平掉(netting)账户下,同一品种只允许存在一个同向持仓,开仓逻辑和锁仓账户完全不同。如果 EA 仍按逐仓思路调用 C_Orders 的下单接口,就会出现平仓失败或误平不该平的头寸,这类问题不会立刻报错,却会让策略悄悄偏离原意。 外汇和贵金属杠杆高、滑点跳空频繁,自动 EA 绝不能在无人盯盘时裸跑。哪怕代码里写了再聪明的保护,也只是降低失控概率,不表示能替你兜底。发现订单动作超出预设边界,趁还没爆仓前手动拖出图表最稳妥。
「给 EA 调用下单类套上枷锁」
EA 若仍能随意穿透 C_Orders 类直接发单,前面做的所有管控逻辑都等于空谈。即便初始化阶段由管理类强制过规则,运行期若不下死命令限制,市价单和挂单仍可能被悄悄送进订单簿——这是实盘里最容易被忽略的越权漏洞。 我们在 CreateOrder 里先卡一道:示意挂单单号变量 m_TicketPending 大于 0,或对冲账户已存在持仓(m_Position.Ticket > 0),新挂单请求直接 return。条件放行后才把 C_Orders::CreateOrder 的返回值写进 m_TicketPending,此后同周期再调便因单号有效而被拒。 ToMarket 的坑更深。市价单只允许在对冲账户无持仓时发送;净持结算账户下,首次调用返回值可能与持仓单号不一致,若直接覆盖持仓单号会丢真实单号。所以对冲账户可放心存返回值,净持账户仅当 m_Position.Ticket 为 0 时才保存,否则忽略。外汇与贵金属杠杆高,这类账户类型差异处理不当可能直接引发重复开仓风险。 下面这段是加了拦截后的核心片段,绿底行即挂单拦截,黄底行即市价单拦截:
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateOrder(const ENUM_ORDER_TYPE type, const class="type">class="kw">double Price) { C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ToMarket(const ENUM_ORDER_TYPE type) { C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateOrder(const ENUM_ORDER_TYPE type, const class="type">class="kw">double Price) { if ((m_TicketPending > class="num">0) || (m_bAccountHedging && (m_Position.Ticket > class="num">0))) class="kw">return; m_TicketPending = C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ToMarket(const ENUM_ORDER_TYPE type) { class="type">ulong tmp; if (m_bAccountHedging && (m_Position.Ticket > class="num">0)) class="kw">return;
对冲账户下的持仓票号接管逻辑
这段片段处理的是下单后持仓票号(Ticket)的回写,核心分歧在账户是否开启对冲模式。当 m_bAccountHedging 为真,每次市价单都直接采用 ToMarket 返回的新票号 tmp,不关心旧仓位。 非对冲账户走的是另一套:若原 m_Position.Ticket 已大于 0 就沿用旧票号,否则才用 tmp。这意味着净持仓账户不会因加仓而丢失原仓位引用,避免重复开仓统计错位。 随后用 PositionSelectByTicket 校验票号是否真实存在,成功则刷新持仓信息,失败就把 Ticket 置 0。外汇与贵金属保证金交易高风险,对冲开关直接影响仓位管理逻辑,建议在 MT5 策略测试器里切换账户类型验证该分支。
tmp = C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade); m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > class="num">0 ? m_Position.Ticket : tmp)); if (PositionSelectByTicket(m_Position.Ticket)) SetInfoPositions(); else m_Position.Ticket = class="num">0; }