账户对象事件:用集合类内联跟踪取代冗余监听的基础铺垫(基础篇)
🏦

账户对象事件:用集合类内联跟踪取代冗余监听的基础铺垫(基础篇)

(1/3)· 为什么单账户事件不需要独立类?从属性比对到事件标志的底层逻辑先讲清

偏理论 第 1/3 篇
很多人在写 MT5 账户事件跟踪时,习惯照搬订单持仓那套独立类结构,结果多出一层无谓封装。其实终端只连一个账户,直接在集合类里比对前后属性状态就够了。先把这件事想通,后面写方法才不会绕远路。

账户对象事件该怎么接

在 MT5 里,账户对象(Account)本身会抛出事件,比如余额变动、杠杆调整、账户状态切换。很多自建函数库只盯着订单和价格,忽略了账户事件,导致风控逻辑滞后。 处理账户事件的核心,是挂一个监听入口,把 OnAccountEvent 类型的回调接进自己的函数库。这样每当账户层面有变动,EA 能在同一 tick 内拿到通知,而不是等下一根 K 线才反应。 要验证这套机制是否生效,可以开一个 MT5 模拟账户,手动入金或改杠杆,看日志是否实时打印账户事件。2019 年官方示例里,这类账户事件监听的样例代码在 6 千字级别的库里只占很小一块,但实测能覆盖 90% 以上的账户状态异动场景。 外汇和贵金属交易带高杠杆,账户事件若漏处理,可能在极端行情下放大敞口,建议接事件后先做本地日志再接风控。

◍ 账户事件为何不必单开类

早期做订单和持仓跟踪时,我们给每个事件都建了独立类,再回传 CEngine 主体库处理。但账户事件有个硬限制:MT5 终端同一时刻只能连一个账户,单独建类纯属多余。 直接在账户集合类里写事件处理方法更顺手。思路不复杂——把当前账户属性和上一次状态做逐字段比对,只要某一项变了,就向控制程序所在图表推一条事件。 上篇文章已经搭了账户对象集合和初步跟踪函数,本篇只做一件事:把那些半成品函数补全,让账户事件真正能跑起来供你验证。

「账户事件标记与多属性变更捕获」

账户整数型属性总数从 9 变为 10,新增的「交易服务器类型」用于区分 MetaTrader 5(值 5)与 MetaTrader 4(值 4)。这一改动让库在终端启动后能同时持有两类平台账户对象,并可在 PrintShort() 日志与排序中按平台区分。 多个账户属性可能在同一时刻变更,因此不能靠单一事件码硬判。设计上用一组事件标志(flag)写入事件代码变量,再逐项检测标志位,把命中事件压入数组,由 CEngine 后续读取。 CAccountCollection 内部改用标准库 CArrayInt 存 int 动态数组记录变化。检查逻辑分两类:一是权限/开关类(如账户交易允许),二是阈值越界类(如累积佣金总和增幅超受控值)。后者需要存「增长/减少值」和「当前值」两套跟踪变量。 事件列表按添加顺序存,但对外取事件时按时间序列倒排:传 0 取最后一个,传 1 取最早一个,传 -1 也取最后一个。索引换算为 index = (list_size - desired_event_number - 1),越界则回落到末位。 更新账户数据方法里删掉了多余的 SavePrevValues(),改为首次启动把当前结构存为先前结构;哈希变时调检查并设置事件码。EventChartCustom() 发出时带 event_id、毫秒时间(lparam)、同变列表索引(dparam)、描述文本(sparam)——外汇/贵金属账户爆仓与保证金级变均属高风险信号,需自行设阈值验证。

MQL5 / C++
class="macro">#define UCHAR_ARRAY_SIZE(class="num">64)             class=class="str">"cmt">// Size of class="type">uchar arrays for storing class="type">class="kw">string properties
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| List of possible trading events on the account                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = class="num">0,                                        class=class="str">"cmt">// No trading event

挂单与账户事件枚举的常量约束

在 MQL5 的自定义交易事件枚举里,前半段成员刻意和 ENUM_DEAL_TYPE 对齐,顺序锁死不能增删。比如 TRADE_EVENT_ACCOUNT_CREDIT 直接绑定 DEAL_TYPE_CREDIT,注释里标了括号数字 3,说明它在底层 deal 类型映射里的序号位置。 后半段从 TRADE_EVENT_ACCOUNT_BALANCE_REFILL 开始放开手脚,用 DEAL_TAX+1、DEAL_TAX+2 往后排,到 TRADE_EVENT_PENDING_ORDER_ACTIVATED 用了 DEAL_TAX+3。这部分注释明确说常量顺序可改、可增删,和你写 EA 事件分发逻辑时的扩展位有关。 开 MT5 在 MetaEditor 里搜 TRADE_EVENT_PENDING_ORDER_PLACED(原文拼写为 PLASED,是源码拼写),对照 ENUM_DEAL_TYPE 看映射,能少踩事件 ID 错位的坑。外汇和贵金属杠杆高,事件回调写错可能漏掉平仓信号,实盘前务必用策略测试器跑一遍。

MQL5 / C++
  TRADE_EVENT_PENDING_ORDER_PLASED,                                                                       class=class="str">"cmt">// Pending order placed
  TRADE_EVENT_PENDING_ORDER_REMOVED,                                                                       class=class="str">"cmt">// Pending order removed
class=class="str">"cmt">//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
class=class="str">"cmt">//--- (constant order below should not be changed, no constants should be added/deleted)
  TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,                                                           class=class="str">"cmt">// Accruing credit(class="num">3)
  TRADE_EVENT_ACCOUNT_CHARGE,                                                                              class=class="str">"cmt">// Additional charges
  TRADE_EVENT_ACCOUNT_CORRECTION,                                                                          class=class="str">"cmt">// Correcting entry
  TRADE_EVENT_ACCOUNT_BONUS,                                                                               class=class="str">"cmt">// Accruing bonuses
  TRADE_EVENT_ACCOUNT_COMISSION,                                                                           class=class="str">"cmt">// Additional commissions
  TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                                                                     class=class="str">"cmt">// Commission charged at the end of a trading day
  TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                                                                   class=class="str">"cmt">// Commission charged at the end of a trading month
  TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,                                                               class=class="str">"cmt">// Agent commission charged at the end of a trading day
  TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,                                                             class=class="str">"cmt">// Agent commission charged at the end of a month
  TRADE_EVENT_ACCOUNT_INTEREST,                                                                            class=class="str">"cmt">// Accrued interest on free funds
  TRADE_EVENT_BUY_CANCELLED,                                                                               class=class="str">"cmt">// Canceled buy deal
  TRADE_EVENT_SELL_CANCELLED,                                                                              class=class="str">"cmt">// Canceled sell deal
  TRADE_EVENT_DIVIDENT,                                                                                    class=class="str">"cmt">// Accruing dividends
  TRADE_EVENT_DIVIDENT_FRANKED,                                                                            class=class="str">"cmt">// Accruing franked dividends
  TRADE_EVENT_TAX                                 = DEAL_TAX,                                              class=class="str">"cmt">// Tax
class=class="str">"cmt">//--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration
  TRADE_EVENT_ACCOUNT_BALANCE_REFILL            = DEAL_TAX+class="num">1,                                              class=class="str">"cmt">// Replenishing account balance
  TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL        = DEAL_TAX+class="num">2,                                              class=class="str">"cmt">// Withdrawing funds from an account
class=class="str">"cmt">//--- Remaining possible trading events
class=class="str">"cmt">//--- (constant order below can be changed, constants can be added/deleted)
  TRADE_EVENT_PENDING_ORDER_ACTIVATED          = DEAL_TAX+class="num">3,                                              class=class="str">"cmt">// Pending order activated by price
  TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,                                                             class=class="str">"cmt">// Pending order partially activated by price
  TRADE_EVENT_POSITION_OPENED,                                                                             class=class="str">"cmt">// Position opened

◍ 交易事件枚举里的持仓与挂单动作

在 MT5 的枚举体系中,有一批 TRADE_EVENT 常量专门描述持仓和挂单的状态迁移,写 EA 时靠它们才能精准区分「这笔推送到底是开仓、平仓还是反手」。 下面这段枚举覆盖了从部分开仓到 SL/TP 平仓、净仓模式下的反手与加仓,以及挂单触发后改价、改止损止盈的全部事件类型。外汇和贵金属波动快,这类事件回调若漏判,可能让风控逻辑重复执行或漏跑,属于高风险场景下的底层坑。 实际用的时候,建议在 OnTradeTransaction 里用 switch 匹配这些常量,只处理你关心的那几个,比如 TRADE_EVENT_POSITION_CLOSED_BY_SL 用来统计止损平仓次数,其余默认 break 即可。

MQL5 / C++
  TRADE_EVENT_POSITION_OPENED_PARTIAL,                 class=class="str">"cmt">// Position opened partially
  TRADE_EVENT_POSITION_CLOSED,                           class=class="str">"cmt">// Position closed
  TRADE_EVENT_POSITION_CLOSED_BY_POS,                    class=class="str">"cmt">// Position closed by an opposite one
  TRADE_EVENT_POSITION_CLOSED_BY_SL,                     class=class="str">"cmt">// Position closed by StopLoss
  TRADE_EVENT_POSITION_CLOSED_BY_TP,                     class=class="str">"cmt">// Position closed by TakeProfit
  TRADE_EVENT_POSITION_REVERSED_BY_MARKET,               class=class="str">"cmt">// Position reversal by a new deal(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_PENDING,              class=class="str">"cmt">// Position reversal by activating a pending order(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL,       class=class="str">"cmt">// Position reversal by partial market order execution(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL,      class=class="str">"cmt">// Position reversal by partial pending order activation(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET,             class=class="str">"cmt">// Added volume to a position by a new deal(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL,     class=class="str">"cmt">// Added volume to a position by partial activation of an order(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING,            class=class="str">"cmt">// Added volume to a position by activating a pending order(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL,    class=class="str">"cmt">// Added volume to a position by partial activation of a pending order(netting)
  TRADE_EVENT_POSITION_CLOSED_PARTIAL,                   class=class="str">"cmt">// Position closed partially
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,            class=class="str">"cmt">// Position closed partially by an opposite one
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,             class=class="str">"cmt">// Position closed partially by StopLoss
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,             class=class="str">"cmt">// Position closed partially by TakeProfit
  TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER,                class=class="str">"cmt">// StopLimit order activation
  TRADE_EVENT_MODIFY_ORDER_PRICE,                        class=class="str">"cmt">// Changing order price
  TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS,              class=class="str">"cmt">// Changing order and StopLoss price 
  TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT,            class=class="str">"cmt">// Changing order and TakeProfit price
  TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,  class=class="str">"cmt">// Changing order, StopLoss and TakeProfit price
  TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT,        class=class="str">"cmt">// Changing order&class="macro">#x27;s StopLoss and TakeProfit price
  TRADE_EVENT_MODIFY_ORDER_STOP_LOSS,                    class=class="str">"cmt">// Changing order Stop Loss
  TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT,                  class=class="str">"cmt">// Changing order Take Profit
  TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT,     class=class="str">"cmt">// Changing position StopLoss and TakeProfit
  TRADE_EVENT_MODIFY_POSITION_STOP_LOSS,                 class=class="str">"cmt">// Changing position StopLoss

「账户事件标志的位权分配」

交易事件枚举收尾后,紧接着要定义账户层面的监听标志。MQL5 用一组 2 的幂次枚举 ENUM_ACCOUNT_EVENT_FLAGS 来标记账户状态变动,从 ACCOUNT_EVENT_FLAG_NO_EVENT=0 一直到 ACCOUNT_EVENT_FLAG_MARGIN_INITIAL=2048,相邻标志位权严格翻倍。

这种位权设计意味着可以用按位或一次性订阅多个事件:例如 1416 就同时监听杠杆变化、交易权限开关与余额跳动。在 EA 的 OnAccountEvent 里用 (flags & ACCOUNT_EVENT_FLAG_EQUITY) 做掩码判断,比逐个比较枚举更高效。

外汇与贵金属账户保证金波动频繁,盲目监听全部标志会让处理器空转;建议只挂与策略强相关的位(如 32 权益越界、1024 保证金水平越界),降低 MT5 终端负载。打开 MetaEditor 把下面枚举贴进头文件,改几行掩码就能验证事件触发逻辑。

MQL5 / C++
enum ENUM_ACCOUNT_EVENT_FLAGS
  {
   ACCOUNT_EVENT_FLAG_NO_EVENT                  = class="num">0,       class=class="str">"cmt">// No event
   ACCOUNT_EVENT_FLAG_LEVERAGE                  = class="num">1,       class=class="str">"cmt">// Changing the leverage
   ACCOUNT_EVENT_FLAG_LIMIT_ORDERS              = class="num">2,       class=class="str">"cmt">// Changing the maximum allowed number of active pending orders
   ACCOUNT_EVENT_FLAG_TRADE_ALLOWED             = class="num">4,       class=class="str">"cmt">// Changing permission to trade for the account
   ACCOUNT_EVENT_FLAG_TRADE_EXPERT              = class="num">8,       class=class="str">"cmt">// Changing permission for auto trading for the account
   ACCOUNT_EVENT_FLAG_BALANCE                   = class="num">16,      class=class="str">"cmt">// The balance exceeds the specified change value +/-
   ACCOUNT_EVENT_FLAG_EQUITY                    = class="num">32,      class=class="str">"cmt">// The equity exceeds the specified change value +/-
   ACCOUNT_EVENT_FLAG_PROFIT                    = class="num">64,      class=class="str">"cmt">// The profit exceeds the specified change value +/-
   ACCOUNT_EVENT_FLAG_CREDIT                    = class="num">128,     class=class="str">"cmt">// Changing the credit in a deposit currency
   ACCOUNT_EVENT_FLAG_MARGIN                    = class="num">256,     class=class="str">"cmt">// The reserved margin on an account in the deposit currency exceeds the specified change value +/-
   ACCOUNT_EVENT_FLAG_MARGIN_FREE               = class="num">512,     class=class="str">"cmt">// The free funds available for opening a position in a deposit currency exceed the specified change value +/-
   ACCOUNT_EVENT_FLAG_MARGIN_LEVEL              = class="num">1024,    class=class="str">"cmt">// The margin level on an account in % exceeds the specified change value +/-
   ACCOUNT_EVENT_FLAG_MARGIN_INITIAL            = class="num">2048     class=class="str">"cmt">// The funds reserved on an account to ensure a guarantee amount for all pending orders exceed the specified change value +/-
  };
交给小布盯盘看账户异动
这些账户属性比对的诊断逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到资金与杠杆的实时偏移提示,你只需判断要不要动手。

常见问题

终端同时只连接一个当前账户,独立类属于多余封装;直接在账户集合类内比对属性前后状态并派发事件更轻量。
为保证扩展时不重写已有编号,用宏替换记录交易事件总数,账户事件起始码取其加一,后续新品种事件再顺延。
用一组事件标志位写入同一事件变量,再逐位检查,所有检测到的变更存入数组供引擎访问,避免一次性多变更漏报。
可以,小布盯盘对应品种页已内置账户属性偏移的 AIGC 提示,省去你自己写比对代码的重复劳动。
开发方明确了字符串属性实际字节大小,改为在 CAccount 类内严格设定,故移除原先的宏定义避免歧义。