账户对象事件:用集合类内联跟踪取代冗余监听的基础铺垫(基础篇)
(1/3)· 为什么单账户事件不需要独立类?从属性比对到事件标志的底层逻辑先讲清
账户对象事件该怎么接
在 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)——外汇/贵金属账户爆仓与保证金级变均属高风险信号,需自行设阈值验证。
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 错位的坑。外汇和贵金属杠杆高,事件回调写错可能漏掉平仓信号,实盘前务必用策略测试器跑一遍。
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 即可。
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,相邻标志位权严格翻倍。
| 这种位权设计意味着可以用按位或一次性订阅多个事件:例如 1 | 4 | 16 就同时监听杠杆变化、交易权限开关与余额跳动。在 EA 的 OnAccountEvent 里用 (flags & ACCOUNT_EVENT_FLAG_EQUITY) 做掩码判断,比逐个比较枚举更高效。 |
|---|
外汇与贵金属账户保证金波动频繁,盲目监听全部标志会让处理器空转;建议只挂与策略强相关的位(如 32 权益越界、1024 保证金水平越界),降低 MT5 终端负载。打开 MetaEditor 把下面枚举贴进头文件,改几行掩码就能验证事件触发逻辑。
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 +/- };