轻松快捷开发 MetaTrader 程序的函数库(第十三部分):帐户对象事件·综合运用
🧩

轻松快捷开发 MetaTrader 程序的函数库(第十三部分):帐户对象事件·综合运用

(3/3)· 从枚举宏替换到多属性变更捕获,把单账户事件跟踪做成可扩展的收尾模块

含代码示例偏理论 第 3/3 篇
很多人在 MT5 里跟踪账户变动时仍靠定时轮询,既漏掉瞬时多属性同变,又拖慢主循环。直接比较前后状态并打事件标志,才是终端单账户场景里不丢事件的写法。
本章目录
  1. 账户结构体的字段拆解
  2. 账户结构体的保证金与字符串字段
  3. 账户属性读取的封装方法
  4. 把账户属性一次性抓进对象里
  5. 账户属性在对象里的落地写法
  6. 把账户对象压进结构体的落盘写法
  7. 账户结构体到对象的字段映射
  8. 账户对象属性落盘与日志简报
  9. 账户属性枚举的本地化文本拼装
  10. 从账户属性读出服务器类型
  11. 账户快照的结构化落地
  12. 账户结构体与变更跟踪的成员布局
  13. 账户事件结构体的字段拆解
  14. 账户状态变量的跟踪骨架
  15. 账户风控变量的内部声明拆解
  16. 账户维护保证金与资产负债的变动追踪变量
  17. 账户变更参数的初始化细节
  18. 账户状态快照的清零初始化
  19. 账户风控字段的初始化清零
  20. 账户状态追踪的阈值设定
  21. 账户变动的位标志累加逻辑
  22. 账户事件类型的判定与写入
  23. 账户杠杆与挂单额度的增量捕获
  24. 账户风控阈值与余额变动的事件捕获
  25. 账户盈亏与净值的阈值触发逻辑
  26. 账户保证金变动的事件触发逻辑
  27. 保证金三类变动的事件触发分支
  28. 维护保证金与资产负债的异动捕获
  29. 盯住账户冻结佣金跳变
  30. 账户事件与筛选接口的返回方法
  31. 账户集合类的接口与杠杆变动追踪
  32. 账户风控状态的取值接口
  33. 账户余额与信用变化的类内钩子
  34. 账户权益与利润的变动追踪接口
  35. 权益与保证金变动的阈值捕获
  36. 保证金与游离保证金的阈值跟踪接口
  37. 跟踪保证金水平变动的接口怎么写
  38. 读懂保证金追保与止损平仓的变动接口
  39. 盯住初始与维持保证金的变动阈值
  40. 账户资产与负债变动的跟踪接口
  41. 负债与冻结佣金的阈值跟踪接口
  42. 账户事件回溯与文本描述的取数链路
  43. 账户事件类型的文字映射
  44. 账户事件的中文提示串拼法
  45. 账户事件的多语种提示串拼接
  46. 账户保证金事件的中文提示串拼法
  47. 保证金与强平阈值的事件文案拼接
  48. 账户事件的多语言变动播报
  49. 账户集合对象的初始化与刷新逻辑
  50. 账户指纹变了才触发事件派发
  51. 账户事件与计数器类的私有成员布局
  52. 从持仓到账户事件的一站式取数接口
  53. 账户事件轮询怎么落进引擎里
  54. 测试帐户事件
  55. 账户索引与多账户遍历的取数细节
  56. EA 初始化与 tick 里的事件捕获
  57. 把库事件接进图表与持仓逻辑
  58. 净值跳涨后平掉最赚那仓
  59. 事件去重漏洞与后续品种对象规划
  60. 最后一句大实话

账户结构体的字段拆解

在 MT5 的 EA 开发里,账户信息通常通过一组结构体字段直接读取,而不是每次都调 AccountInfo 系列函数。下面这段声明把交易账户的核心属性一次性铺开,开 MT5 用 MetaEditor 搜 MqlAccountInfo 能看到同款定义。 long login; // 账户登录号(ACCOUNT_LOGIN) int trade_mode; // 账户交易类型(ACCOUNT_TRADE_MODE) long leverage; // 杠杆倍数(ACCOUNT_LEVERAGE) int limit_orders; // 允许挂单数上限(ACCOUNT_LIMIT_ORDERS) int margin_so_mode; // 强平最低保证金模式(ACCOUNT_MARGIN_SO_MODE) bool trade_allowed; // 服务端是否允许本账户交易(ACCOUNT_TRADE_ALLOWED) bool trade_expert; // 服务端是否允许 EA 交易(ACCOUNT_TRADE_EXPERT) int margin_mode; // 保证金计算模式(ACCOUNT_MARGIN_MODE) int currency_digits; // 账户币种小数位(ACCOUNT_CURRENCY_DIGITS) int server_type; // 交易服务器类型(MT5 或 MT4) 后半段是账户的实时数值字段,每次 tick 都会变。 double balance; // 余额(ACCOUNT_BALANCE) double credit; // 信用额(ACCOUNT_CREDIT) double profit; // 浮动盈亏(ACCOUNT_PROFIT) double equity; // 净值(ACCOUNT_EQUITY) double margin; // 已用保证金(ACCOUNT_MARGIN) double margin_free; // 可用保证金(ACCOUNT_MARGIN_FREE) 外汇和贵金属自带高杠杆高风险,leverage 字段读到 1:100 和 1:2000 时,同样 0.1 手黄金占用的 margin 能差 20 倍,写仓位计算前先把这个值打出来核对。

MQL5 / C++
class="type">long login;                     class=class="str">"cmt">// ACCOUNT_LOGIN(Account number)
class="type">int trade_mode;                 class=class="str">"cmt">// ACCOUNT_TRADE_MODE(Trading account type)
class="type">long leverage;                  class=class="str">"cmt">// ACCOUNT_LEVERAGE(Leverage)
class="type">int limit_orders;               class=class="str">"cmt">// ACCOUNT_LIMIT_ORDERS(Maximum allowed number of active pending orders)
class="type">int margin_so_mode;             class=class="str">"cmt">// ACCOUNT_MARGIN_SO_MODE(Mode of setting the minimum available margin level)
class="type">bool trade_allowed;             class=class="str">"cmt">// ACCOUNT_TRADE_ALLOWED(Permission to trade for the current account from the server side)
class="type">bool trade_expert;              class=class="str">"cmt">// ACCOUNT_TRADE_EXPERT(Permission to trade for an EA from the server side)
class="type">int margin_mode;                class=class="str">"cmt">// ACCOUNT_MARGIN_MODE(Margin calculation mode)
class="type">int currency_digits;            class=class="str">"cmt">// ACCOUNT_CURRENCY_DIGITS(Number of digits for an account currency)
class="type">int server_type;                class=class="str">"cmt">// Trade server type(MetaTrader class="num">5, MetaTrader class="num">4)
class=class="str">"cmt">//--- Account real properties
class="type">class="kw">double balance;                 class=class="str">"cmt">// ACCOUNT_BALANCE(Account balance in the deposit currency)
class="type">class="kw">double credit;                  class=class="str">"cmt">// ACCOUNT_CREDIT(Credit in the deposit currency)
class="type">class="kw">double profit;                  class=class="str">"cmt">// ACCOUNT_PROFIT(Current profit on an account in the deposit currency)
class="type">class="kw">double equity;                  class=class="str">"cmt">// ACCOUNT_EQUITY(Equity on an account in the deposit currency)
class="type">class="kw">double margin;                  class=class="str">"cmt">// ACCOUNT_MARGIN(Reserved margin on an account in the deposit currency)
class="type">class="kw">double margin_free;             class=class="str">"cmt">// ACCOUNT_MARGIN_FREE(Free funds available for opening a position in the deposit currency)

◍ 账户结构体的保证金与字符串字段

在 MT5 的账户封装类里,把账户状态压进一个结构体是最直接的做法。下面这段声明覆盖了保证金比例、 Call / Stop Out 阈值,以及挂单和持仓占用的预留资金,外加资产、负债与冻结佣金。 margin_level 对应 ACCOUNT_MARGIN_LEVEL,是账户净值相对已用保证金的百分比;margin_so_call 与 margin_so_so 分别是经纪商设定的 Margin Call 和 Stop Out 水平,外汇和贵金属杠杆高,触及 Stop Out 可能直接强平,属高风险情形。 字符串类字段用 uchar 定长数组承接:name[128] 存客户名,server[64] 是交易服务器名,currency[32] 为存款币种,company[128] 是账户所属公司名。定长而非 string 是为了把整个结构体直接序列化成 uchar 数组 m_uchar_array[],方便跨周期或跨 EA 传递。 TradeMode() 方法只是薄封装:把 ACCOUNT_PROP_TRADE_MODE 取出的整数强转为 ENUM_ACCOUNT_TRADE_MODE 返回。开 MT5 在 MetaEditor 里建个同类结构,Print(TradeMode()) 就能看到当前账户是实盘、模拟还是观望模式。

MQL5 / C++
class="type">class="kw">double margin_level; class=class="str">"cmt">// ACCOUNT_MARGIN_LEVEL(Margin level on an account in %)
class="type">class="kw">double margin_so_call; class=class="str">"cmt">// ACCOUNT_MARGIN_SO_CALL(Margin Call level)
class="type">class="kw">double margin_so_so; class=class="str">"cmt">// ACCOUNT_MARGIN_SO_SO(Stop Out level)
class="type">class="kw">double margin_initial; class=class="str">"cmt">// ACCOUNT_MARGIN_INITIAL(Funds reserved on an account to ensure a guarantee amount for all pending orders)
class="type">class="kw">double margin_maintenance; class=class="str">"cmt">// ACCOUNT_MARGIN_MAINTENANCE(Funds reserved on an account to ensure a minimum amount for all open positions)
class="type">class="kw">double assets; class=class="str">"cmt">// ACCOUNT_ASSETS(Current assets on an account)
class="type">class="kw">double liabilities; class=class="str">"cmt">// ACCOUNT_LIABILITIES(Current liabilities on an account)
class="type">class="kw">double comission_blocked; class=class="str">"cmt">// ACCOUNT_COMMISSION_BLOCKED(Current sum of blocked commissions on an account)
class=class="str">"cmt">//--- Account class="type">class="kw">string properties
class="type">uchar name[class="num">128]; class=class="str">"cmt">// ACCOUNT_NAME(Client name)
class="type">uchar server[class="num">64]; class=class="str">"cmt">// ACCOUNT_SERVER(Trade server name)
class="type">uchar currency[class="num">32]; class=class="str">"cmt">// ACCOUNT_CURRENCY(Deposit currency)
class="type">uchar company[class="num">128]; class=class="str">"cmt">// ACCOUNT_COMPANY(Name of a company serving an account)
};
 SData m_struct_obj; class=class="str">"cmt">// Account object structure
 class="type">uchar m_uchar_array[]; class=class="str">"cmt">// class="type">uchar array of the account object structure
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of a simplified access to the account object properties  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Return the account&class="macro">#x27;s integer propertie
 ENUM_ACCOUNT_TRADE_MODE TradeMode(class="type">void) class="kw">const { class="kw">return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE); }

「账户属性读取的封装方法」

在 MQL5 的账户对象封装里,一组 const 成员函数把底层 GetProperty 调用包成了语义化接口,直接返回强类型结果。比如 MarginSOMode() 取爆仓模式枚举,Leverage() 取杠杆倍数,Login() 取账户登录号,都是长整型或枚举,省去手写类型转换。 下面这段是实际封装代码,注意 ServerType() 那行在原始定义里被高亮,说明它常是调试服务器环境时的关键分支点。 调用时直接用 account.Leverage() 就能拿到例如 100、200、500 这样的整数;若券商禁用了 EA 交易,TradeAllowed() 会返回 0。外汇与贵金属杠杆放大波动,账户属性读错可能让仓位计算直接偏离,属高风险操作。

MQL5 / C++
  ENUM_ACCOUNT_STOPOUT_MODE  MarginSOMode(class="type">void)                class="kw">const { class="kw">return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE); }
  ENUM_ACCOUNT_MARGIN_MODE   MarginMode(class="type">void)                    class="kw">const { class="kw">return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE);    }
  class="type">long                       Login(class="type">void)                                        class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LOGIN);                                      }
  class="type">long                       Leverage(class="type">void)                                     class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LEVERAGE);                                   }
  class="type">long                       LimitOrders(class="type">void)                                  class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS);                               }
  class="type">long                       TradeAllowed(class="type">void)                                 class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED);                              }
  class="type">long                       TradeExpert(class="type">void)                                  class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT);                               }
  class="type">long                       CurrencyDigits(class="type">void)                               class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS);                            }
  class="type">long                       ServerType(class="type">void)                                   class="kw">const { class="kw">return this.GetProperty(ACCOUNT_PROP_SERVER_TYPE);                               }

把账户属性一次性抓进对象里

做 MT5 账户监控类时,最忌在每次判断里反复调 AccountInfoInteger,既拖慢执行又难维护。下面这段构造函数思路,是在初始化阶段把整数类账户属性一次性塞进成员变量数组,后续只读内存。 CAccount 的构造函数里用 ::AccountInfoInteger 抓取了 7 个关键整数属性:登录号、交易模式、杠杆、挂单上限、爆仓线模式、是否允许交易、是否允许 EA 交易。以 ACCOUNT_LEVERAGE 为例,实盘里常见返回 100、200 或 500,对应不同的仓位放大倍数,外汇与贵金属均属高杠杆高风险品种,取值偏差会直接改变风控计算。 别把正态当圣经 账户对象还暴露了一组描述函数:GetPropertyDescription 分整数/实数/字符串三种重载,TradeModeDescription 返回 demo/contest/real,ServerTypeDescription 区分 MT5 与 MT4 后端。Print 与 PrintShort 则把属性dump到日志,full_prop=false 时只打当前服务器支持的项,避免噪声。 开 MT5 建个 EA 把这段构造函数抄进去,在 OnInit 里实例化后调用 PrintShort,你能在日志里直接看到杠杆和交易权限,比手动翻终端设置快得多。

MQL5 / C++
CAccount::CAccount(class="type">void)
  {
class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[ACCOUNT_PROP_LOGIN]                                          = ::AccountInfoInteger(ACCOUNT_LOGIN);
   this.m_long_prop[ACCOUNT_PROP_TRADE_MODE]                                     = ::AccountInfoInteger(ACCOUNT_TRADE_MODE);
   this.m_long_prop[ACCOUNT_PROP_LEVERAGE]                                       = ::AccountInfoInteger(ACCOUNT_LEVERAGE);
   this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS]                                   = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);
   this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE]                                 = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED]                                  = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
   this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT]                                   = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT);

◍ 账户属性在对象里的落地写法

这段初始化逻辑把 MT5 账户的核心属性一次性塞进类的内部数组,分整数、双精度、字符串三类缓存,避免后续反复调用终端接口拖慢回测与实时盯盘。 整数类里最值得留意的是 SERVER_TYPE 的判断:用 TerminalInfoString(TERMINAL_NAME) 是否等于 "MetaTrader 5" 来返回 5 或 4,非 MQL5 编译环境下保证金模式默认退化为 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING,货币精度写死 2 位。 双精度部分覆盖了从 BALANCE、EQUITY 到 MARGIN_SO_CALL / MARGIN_SO_SO 等 14 个字段,外汇与贵金属账户在极端波动时 MARGIN_LEVEL 可能瞬间跌破经纪商阈值,这类数据本地缓存后做告警更稳。 字符串只存了账户名与服务器名,用于多账户切换时做身份区分。开 MT5 把下面代码丢进 EA 的 OnInit 段,就能直接打印出你当前账户的缓存结构。

MQL5 / C++
  this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE]                    = class="macro">#ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) class="macro">#else ACCOUNT_MARGIN_MODE_RETAIL_HEDGINGclass="macro">#endif ;
  this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS]                = class="macro">#ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) class="macro">#else class="num">2class="macro">#endif ;
  this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]                    = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader class="num">5" ? class="num">5 : class="num">4);

class=class="str">"cmt">//--- Save real properties
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)]      = ::AccountInfoDouble(ACCOUNT_BALANCE);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)]       = ::AccountInfoDouble(ACCOUNT_CREDIT);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)]       = ::AccountInfoDouble(ACCOUNT_PROFIT);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)]       = ::AccountInfoDouble(ACCOUNT_EQUITY);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)]       = ::AccountInfoDouble(ACCOUNT_MARGIN);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)]  = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)]   = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)]       = ::AccountInfoDouble(ACCOUNT_ASSETS);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)]  = ::AccountInfoDouble(ACCOUNT_LIABILITIES);
  this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED);

class=class="str">"cmt">//--- Save class="type">class="kw">string properties
  this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)]         = ::AccountInfoString(ACCOUNT_NAME);
  this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)]       = ::AccountInfoString(ACCOUNT_SERVER);

「把账户对象压进结构体的落盘写法」

在 MT5 的 EA 或指标里,想把一个封装好的账户类(如 CAccount)整体序列化,靠的是 ObjectToStruct() 这类方法:先把各类属性逐一拷进 m_struct_obj,再转成 uchar 数组。 整数类属性直接赋值即可,例如 login、trade_mode、leverage、limit_orders 等;其中 server_type 被强转成 int 存储,说明底层枚举值需要窄化保存。 浮点属性覆盖余额、净值、保证金系列(balance / equity / margin / margin_free / margin_level 等),这些是盯盘时判断爆仓概率的核心数值,必须实时刷新后落结构体。 字符串如账户名、服务器、币种、经纪商,用 StringToCharArray() 写进定长字符数组;最后 StructToCharArray() 把整个结构体压成 uchar 数组,失败就打印错误码并返回 false,成功才返回 true。 开 MT5 建个 CAccount 派生类,跑一遍 ObjectToStruct(),观察 m_uchar_array 大小是否随 server_type 字段变化——能验证你对枚举窄化的理解对不对。外汇与贵金属杠杆高,账户净值波动可能触发强平,这段代码只解决数据存储,不涉及任何方向判断。

MQL5 / C++
class="type">bool CAccount::ObjectToStruct(class="type">void)
  {
class=class="str">"cmt">//--- Save the integer properties
   this.m_struct_obj.login=this.Login();
   this.m_struct_obj.trade_mode=this.TradeMode();
   this.m_struct_obj.leverage=this.Leverage();
   this.m_struct_obj.limit_orders=(class="type">int)this.LimitOrders();
   this.m_struct_obj.margin_so_mode=this.MarginSOMode();
   this.m_struct_obj.trade_allowed=this.TradeAllowed();
   this.m_struct_obj.trade_expert=this.TradeExpert();
   this.m_struct_obj.margin_mode=this.MarginMode();
   this.m_struct_obj.currency_digits=(class="type">int)this.CurrencyDigits();
   this.m_struct_obj.server_type=(class="type">int)this.ServerType();
class=class="str">"cmt">//--- Save the real properties
   this.m_struct_obj.balance=this.Balance();
   this.m_struct_obj.credit=this.Credit();
   this.m_struct_obj.profit=this.Profit();
   this.m_struct_obj.equity=this.Equity();
   this.m_struct_obj.margin=this.Margin();
   this.m_struct_obj.margin_free=this.MarginFree();
   this.m_struct_obj.margin_level=this.MarginLevel();
   this.m_struct_obj.margin_so_call=this.MarginSOCall();
   this.m_struct_obj.margin_so_so=this.MarginSOSO();
   this.m_struct_obj.margin_initial=this.MarginInitial();
   this.m_struct_obj.margin_maintenance=this.MarginMaintenance();
   this.m_struct_obj.assets=this.Assets();
   this.m_struct_obj.liabilities=this.Liabilities();
   this.m_struct_obj.comission_blocked=this.ComissionBlocked();
class=class="str">"cmt">//--- Save the class="type">class="kw">string properties
   ::StringToCharArray(this.Name(),this.m_struct_obj.name);
   ::StringToCharArray(this.Server(),this.m_struct_obj.server);
   ::StringToCharArray(this.Currency(),this.m_struct_obj.currency);
   ::StringToCharArray(this.Company(),this.m_struct_obj.company);
   class=class="str">"cmt">//--- Save the structure to the class="type">uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,TextByLanguage("Не удалось сохранить структуру объекта в class="type">uchar-массив, ошибка ","Failed to save object structure to class="type">uchar array, error "),(class="type">class="kw">string)::GetLastError());
      class="kw">return class="kw">false;
     }
   class="kw">return true;
  }

账户结构体到对象的字段映射

在 MT5 的 EA 架构里,把 MQL5 自带的 MqlAccountInfo 结构体转成自定义 CAccount 对象,核心就是一次性把整型与双精度型属性搬进内部数组。下面这段代码做了 10 个 long 字段和 10 个 double 字段的赋值,注意 server_type 这一行是后来补的扩展字段,老版本头文件里可能没有。 整型部分直接按下标写数组,例如 m_long_prop[ACCOUNT_PROP_LEVERAGE] 对应结构体的 leverage 成员,调用时就能用枚举而非魔法数字去取杠杆。双精度部分多套了一层 IndexProp() 做键映射,balance、equity、margin_free 等都在这一区,回测时若发现 equity 取出来是 0,先查 IndexProp 映射有没有漏掉。 开 MT5 新建个脚本,把这段塞进 CAccount::StructToObject,在 OnStart 里打印 m_long_prop[ACCOUNT_PROP_LEVERAGE] 和 m_double_prop[IndexProp(ACCOUNT_PROP_EQUITY)],能直接验证你的账户对象有没有正确吃到结构体数据。外汇和贵金属保证金波动大,这类底层映射写错可能让风控计算偏一倍,实盘前务必用策略测试器跑一遍。

MQL5 / C++
class="type">void CAccount::StructToObject(class="type">void)
  {
class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[ACCOUNT_PROP_LOGIN]                = this.m_struct_obj.login;
   this.m_long_prop[ACCOUNT_PROP_TRADE_MODE]           = this.m_struct_obj.trade_mode;
   this.m_long_prop[ACCOUNT_PROP_LEVERAGE]             = this.m_struct_obj.leverage;
   this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS]         = this.m_struct_obj.limit_orders;
   this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE]       = this.m_struct_obj.margin_so_mode;
   this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED]        = this.m_struct_obj.trade_allowed;
   this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT]         = this.m_struct_obj.trade_expert;
   this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE]          = this.m_struct_obj.margin_mode;
   this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS]      = this.m_struct_obj.currency_digits;
   this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]          = this.m_struct_obj.server_type;
class=class="str">"cmt">//--- Save real properties
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)]     = this.m_struct_obj.balance;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)]      = this.m_struct_obj.credit;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)]      = this.m_struct_obj.profit;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)]      = this.m_struct_obj.equity;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)]      = this.m_struct_obj.margin;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = this.m_struct_obj.margin_free;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)]= this.m_struct_obj.margin_level;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = this.m_struct_obj.margin_so_call;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)]   = this.m_struct_obj.margin_so_so;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = this.m_struct_obj.margin_initial;
  }

◍ 账户对象属性落盘与日志简报

CAccount 类在内部把结构体字段同步进两套数组:双精度数组存维持保证金、资产、负债、冻结佣金等数值;字符串数组通过 CharArrayToString 把 name、server、currency、company 等 char 数组转成可读文本。IndexProp 负责把枚举映射到数组下标,这套映射决定了后续取属性时的偏移是否越界。 PrintShort 方法在日志里输出一行精简账户档案:登录号、账户名、公司名、余额加币种、杠杆倍数、保证金模式(零售对冲显示 ', Hedge',交易所模式显示 ', Exhange' 注意原拼写漏了 c)、交易模式与服务器类型描述。用 MT5 跑这段,日志里能看到类似 'Account 123456: Demo (MetaQuotes Software Corp. 10000.00 USD, 1:100, Hedge, ...)' 的单行简报。 GetPropertyDescription 按枚举返回整数属性的多语言描述,例如 ACCOUNT_PROP_LOGIN 对应 'Account number' 加具体数值。做自己的面板时,直接复用这套描述函数能省掉手写的标签映射表。

MQL5 / C++
this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=this.m_struct_obj.margin_maintenance;
this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)]               = this.m_struct_obj.assets;
this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)]         = this.m_struct_obj.liabilities;
this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=this.m_struct_obj.comission_blocked;
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)]                = ::CharArrayToString(this.m_struct_obj.name);
this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)]              = ::CharArrayToString(this.m_struct_obj.server);
this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)]            = ::CharArrayToString(this.m_struct_obj.currency);
this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)]             = ::CharArrayToString(this.m_struct_obj.company);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display a class="type">class="kw">short account description in the journal                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CAccount::PrintShort(class="type">void)
  {
  class="type">class="kw">string mode=(this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? ", Hedge" : this.MarginMode()==ACCOUNT_MARGIN_MODE_EXCHANGE ? ", Exhange" : "");
  class="type">class="kw">string names=TextByLanguage("Счёт ","Account ")+(class="type">class="kw">string)this.Login()+": "+this.Name()+" ("+this.Company()+" ";
  class="type">class="kw">string values=::DoubleToString(this.Balance(),(class="type">int)this.CurrencyDigits())+" "+this.Currency()+", class="num">1:"+(class="type">class="kw">string)+this.Leverage()+mode+", "+this.TradeModeDescription()+" "+this.ServerTypeDescription()+")";
  ::Print(names,values);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display a description of an account integer class="kw">property              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER class="kw">property)
  {
  class="kw">return
    (
      class="kw">property==ACCOUNT_PROP_LOGIN          ?  TextByLanguage("Номер счёта","Account number")+": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)               :

「账户属性枚举的本地化文本拼装」

在 MT5 的账户信息封装类里,常把 ENUM_ACCOUNT_INFO_INTEGER 的各属性映射成可读字符串。下面这段三元表达式链就是按 property 值分流:交易模式、杠杆、挂单上限、止损出场模式、普通交易权限、EA 交易权限、保证金计算模式分别走不同分支。 TextByLanguage 第一个参数是俄文、第二个是英文,说明这套代码原生支持多语种终端;中文环境会回退到英文文案,想显示中文得自己补第三参或后处理。 杠杆和挂单上限直接用 (string)this.GetProperty(property) 强转,返回的是整数(如杠杆 100、500,挂单上限 100 或 0 表示不限制)。交易权限类属性返回 bool,再嵌套 TextByLanguage 转成 Yes/No,实盘里若 TradeAllowed 返回 false,脚本下单会被终端拒绝。 外汇与贵金属保证金交易高风险,账户属性读错可能导致 EA 在禁交易账户上空跑,上线前务必打印 Account trade mode 与 Allowed trade 字段核对。

MQL5 / C++
class="kw">property==ACCOUNT_PROP_TRADE_MODE ? TextByLanguage("Тип торгового счета","Account trade mode")+": "+this.TradeModeDescription() :
class="kw">property==ACCOUNT_PROP_LEVERAGE ? TextByLanguage("Размер предоставленного плеча","Account leverage")+": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) :
class="kw">property==ACCOUNT_PROP_LIMIT_ORDERS ? TextByLanguage("Максимально допустимое количество действующих отложенных ордеров","Maximum allowed number of active pending orders")+": "+
                                         (class="type">class="kw">string)this.GetProperty(class="kw">property) :
class="kw">property==ACCOUNT_PROP_MARGIN_SO_MODE ? TextByLanguage("Режим задания минимально допустимого уровня залоговых средств","Mode for setting the minimal allowed margin")+": "+
                                         this.MarginSOModeDescription() :
class="kw">property==ACCOUNT_PROP_TRADE_ALLOWED ? TextByLanguage("Разрешенность торговли для текущего счета","Allowed trade for the current account")+": "+
                                         (this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) :
class="kw">property==ACCOUNT_PROP_TRADE_EXPERT ? TextByLanguage("Разрешенность торговли для эксперта","Allowed trade for an Expert Advisor")+": "+
                                         (this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) :
class="kw">property==ACCOUNT_PROP_MARGIN_MODE ? TextByLanguage("Режим расчета маржи","Margin calculation mode")+": "+

从账户属性读出服务器类型

在封装账户对象的类里,可以用三元表达式把不同属性映射成可读文本。下面这段把账户货币小数位和交易服务器类型做了分支输出,其中服务器类型走的是独立判断逻辑。 被高亮的代码段处理 ACCOUNT_PROP_SERVER_TYPE:若匹配就调用 TextByLanguage 返回「Type of trading server」加冒号,再拼接 GetProperty 取出的原始值;不匹配则落入末尾空串。 另有一个独立方法 ServerTypeDescription,用 this.ServerType()==5 来判定:返回 5 代表 MetaTrader5,其余情况归为 MetaTrader4。你在 MT5 里建个 EA 调这个方法,就能直接看到当前账户挂在 MT4 还是 MT5 后端。 外汇与贵金属杠杆品种波动剧烈,用代码读取服务器类型仅作环境识别,不等于任何交易信号,实盘前请在策略测试器核实返回值。

MQL5 / C++
class="kw">property==ACCOUNT_PROP_CURRENCY_DIGITS ? TextByLanguage("Количество знаков после запятой для валюты счета","Number of decimal places in account currency")+": "+ ("class="type">class="kw">string")this.GetProperty(class="kw">property) :
class="kw">property==ACCOUNT_PROP_SERVER_TYPE ? TextByLanguage("Тип торгового сервера","Type of trading server")+": "+ ("class="type">class="kw">string")this.GetProperty(class="kw">property) :
""
);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the trading server type                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CAccount::ServerTypeDescription(class="type">void) class="kw">const
  {
   class="kw">return(this.ServerType()==class="num">5 ? "MetaTrader5" : "MetaTrader4");
  }

◍ 账户快照的结构化落地

在 MT5 的 EA 架构里,把账户状态从零散的 AccountInfo 调用收拢成一个结构体,是后续做多账户巡检或回放对比的前提。下面这段定义把整数属性(login、leverage、limit_orders)和布尔开关(trade_allowed、trade_expert)与实数属性(balance、credit、profit、equity)分开排列,读起来直观,也方便按类型批量序列化。 结构里还留了一个 hash_sum 字段,用来存账户数据的哈希和。实际跑的时候,如果两次轮询的 hash_sum 不一致,才触发 SavePrevValues 把当前值存为上一帧——这样能省掉大量无变化的属性拷贝,在同时盯 10 个以上账户时 CPU 占用可能明显下降。 外汇和贵金属账户的高杠杆与断线重连都会让 trade_allowed 在秒级内翻转,所以别假设它开盘期间恒定。用 CAccountsCollection 继承 CListObj 后,你开 MT5 把 ListObj.mqh 和 Account.mqh 路径配好,就能直接 new 一个集合开始压测。

MQL5 / C++
class="macro">#include <Arrays\ArrayInt.mqh>
class="macro">#include "ListObj.mqh"
class="macro">#include "..\Services\Select.mqh"
class="macro">#include "..\Objects\Accounts\Account.mqh"
class CAccountsCollection : class="kw">public CListObj
  {
class="kw">private:
  class="kw">struct MqlDataAccount
    {
      class="type">class="kw">double      hash_sum;            class=class="str">"cmt">// Account data hash sum
      class="type">long        login;               class=class="str">"cmt">// ACCOUNT_LOGIN(Account number)
      class="type">long        leverage;            class=class="str">"cmt">// ACCOUNT_LEVERAGE(Leverage)
      class="type">int         limit_orders;        class=class="str">"cmt">// ACCOUNT_LIMIT_ORDERS(Maximum allowed number of active pending orders)
      class="type">bool        trade_allowed;       class=class="str">"cmt">// ACCOUNT_TRADE_ALLOWED(Permission to trade for the current account from the server side)
      class="type">bool        trade_expert;        class=class="str">"cmt">// ACCOUNT_TRADE_EXPERT(Permission to trade for an EA from the server side)
      class="type">class="kw">double      balance;             class=class="str">"cmt">// ACCOUNT_BALANCE(Account balance in a deposit currency)
      class="type">class="kw">double      credit;              class=class="str">"cmt">// ACCOUNT_CREDIT(Credit in a deposit currency)
      class="type">class="kw">double      profit;              class=class="str">"cmt">// ACCOUNT_PROFIT(Current profit on an account in the account currency)
      class="type">class="kw">double      equity;              class=class="str">"cmt">// ACCOUNT_EQUITY(Equity on an account in the deposit currency)
    };
  };

「账户结构体与变更跟踪的成员布局」

在 MT5 的 EA 或指标里做账户状态快照,通常会先定义一个聚合结构体把保证金类字段一次性收拢。下面这段结构体把预留保证金、可用保证金、保证金水平百分比、Margin Call 与 Stop Out 阈值,以及挂单保证金额、持仓维护金、资产与负债、冻结佣金全部分门别类存进 double 变量,方便每次 OnTick 比对前后差异。 结构体之外,代码用两个 MqlDataAccount 实例分别存「当前账户数据」和「上一次账户数据」,再配合 MqlTick、品种名、图表 ID、账户对象列表和整型变更列表,构成一套完整的账户变动追踪骨架。实际跑起来,m_list_changes 里记录的往往是保证金水平从 200% 掉到 120% 这类具体跳变——外汇和贵金属杠杆高,这种跳变可能预示追加保证金风险临近。 开 MT5 新建一个 EA 把这段声明贴进头文件,在 OnTick 里给 m_struct_prev_account 赋值后再刷 m_struct_curr_account,就能用 memcmp 或逐字段比对抓出账户异动,比单纯读 AccountInfoDouble 更利于做历史回看。

MQL5 / C++
      class="type">class="kw">double      margin;                 class=class="str">"cmt">// ACCOUNT_MARGIN(Reserved margin on an account in a deposit currency)
      class="type">class="kw">double      margin_free;             class=class="str">"cmt">// ACCOUNT_MARGIN_FREE(Free funds available for opening a position in a deposit currency)
      class="type">class="kw">double      margin_level;            class=class="str">"cmt">// ACCOUNT_MARGIN_LEVEL(Margin level on an account in %)
      class="type">class="kw">double      margin_so_call;          class=class="str">"cmt">// ACCOUNT_MARGIN_SO_CALL(Margin Call)
      class="type">class="kw">double      margin_so_so;            class=class="str">"cmt">// ACCOUNT_MARGIN_SO_SO(Stop Out)
      class="type">class="kw">double      margin_initial;          class=class="str">"cmt">// ACCOUNT_MARGIN_INITIAL(Funds reserved on an account to ensure a guarantee amount for all pending orders)
      class="type">class="kw">double      margin_maintenance;      class=class="str">"cmt">// ACCOUNT_MARGIN_MAINTENANCE(Funds reserved on an account to ensure a minimum amount for all open positions)
      class="type">class="kw">double      assets;                  class=class="str">"cmt">// ACCOUNT_ASSETS(Current assets on an account)
      class="type">class="kw">double      liabilities;             class=class="str">"cmt">// ACCOUNT_LIABILITIES(Current liabilities on an account)
      class="type">class="kw">double      comission_blocked;       class=class="str">"cmt">// ACCOUNT_COMMISSION_BLOCKED(Current sum of blocked commissions on an account)
      };
   MqlDataAccount   m_struct_curr_account;                class=class="str">"cmt">// Account current data
   MqlDataAccount   m_struct_prev_account;                class=class="str">"cmt">// Account previous data

   class="type">MqlTick          m_tick;                               class=class="str">"cmt">// Tick structure
   class="type">class="kw">string           m_symbol;                             class=class="str">"cmt">// Current symbol
   class="type">long             m_chart_id;                           class=class="str">"cmt">// Control program chart ID
   CListObj         m_list_accounts;                      class=class="str">"cmt">// Account object list
   CArrayInt        m_list_changes;                       class=class="str">"cmt">// Account change list
   class="type">class="kw">string           m_folder_name;                        class=class="str">"cmt">// Name of the folder storing the account objects
   class="type">int              m_index_current;                      class=class="str">"cmt">// Index of an account object featuring the current account data
class=class="str">"cmt">//--- Tracking account changes

账户事件结构体的字段拆解

在 MT5 的账户事件监听里,用一个结构体把服务端推来的账户变动全部打上标记,比每次去调 AccountInfoInteger 实时查要省事。下面这段声明把杠杆、挂单上限、交易开关和资金变动都拆成了独立布尔与数值字段,EA 只需读标志位就能知道「刚才发生了哪类变更」。 外汇与贵金属账户的高杠杆特性,意味着服务端随时可能调杠杆或禁交易,这类事件若漏捕获,策略可能在不允许交易时还发单,概率上会增加滑点与拒单风险。 [CODE] bool m_is_account_event; // 账户数据中的事件标志 int m_change_code; // 账户变更代码 //--- 杠杆 long m_changed_leverage_value; // 杠杆变更值 bool m_is_change_leverage_inc; // 杠杆增加标志 bool m_is_change_leverage_dec; // 杠杆减少标志 //--- 活跃挂单数量 int m_changed_limit_orders_value; // 最大允许活跃挂单数变更值 bool m_is_change_limit_orders_inc; // 最大允许活跃挂单数增加标志 bool m_is_change_limit_orders_dec; // 最大允许活跃挂单数减少标志 //--- 账户交易 bool m_is_change_trade_allowed_on; // 服务端允许当前账户交易标志 bool m_is_change_trade_allowed_off; // 服务端禁止当前账户交易标志 //--- 账户自动交易 bool m_is_change_trade_expert_on; // 服务端允许EA交易标志 bool m_is_change_trade_expert_off; // 服务端禁止EA交易标志 //--- 余额 double m_control_balance_inc; // 跟踪的余额增长值 double m_control_balance_dec; // 跟踪的余额减少值 double m_changed_balance_value; // 余额变更值 bool m_is_change_balance_inc; // 余额变更超过增长值标志 bool m_is_change_balance_dec; // 余额变更超过减少值标志 //--- 信用 double m_changed_credit_value; // 信用变更值 bool m_is_change_credit_inc; // 信用增加标志 [/CODE] 逐行看,m_change_code 是总变更分类码,后面每组 bool 都是该分类下的细分方向标。比如杠杆组用 inc/dec 两个布尔区分调升还是调降,余额组则额外给了 m_control_balance_inc/dec 作为阈值,只有变动突破阈值才置对应的 m_is_change_balance 标志。 实盘里可以把这个结构体作为 OnAccount 事件的缓存,每次事件触发先写标志,主循环里再按标志分流处理。开 MT5 自建个 include 把这段贴进去,编译后挂模拟账户改一次杠杆,就能看到 inc 标志翻转。

MQL5 / C++
class="type">bool     m_is_account_event;              class=class="str">"cmt">// Event flag in the account data
class="type">int      m_change_code;                  class=class="str">"cmt">// Account change code
class=class="str">"cmt">//--- Leverage
class="type">long     m_changed_leverage_value;       class=class="str">"cmt">// Leverage change value
class="type">bool     m_is_change_leverage_inc;       class=class="str">"cmt">// Leverage increase flag
class="type">bool     m_is_change_leverage_dec;       class=class="str">"cmt">// Leverage decrease flag
class=class="str">"cmt">//--- Number of active pending orders
class="type">int      m_changed_limit_orders_value;   class=class="str">"cmt">// Change value of the maximum allowed number of active pending orders
class="type">bool     m_is_change_limit_orders_inc;   class=class="str">"cmt">// Increase flag of the maximum allowed number of active pending orders
class="type">bool     m_is_change_limit_orders_dec;   class=class="str">"cmt">// Decrease flag of the maximum allowed number of active pending orders
class=class="str">"cmt">//--- Trading on an account
class="type">bool     m_is_change_trade_allowed_on;   class=class="str">"cmt">// The flag allowing to trade for the current account from the server side
class="type">bool     m_is_change_trade_allowed_off;  class=class="str">"cmt">// The flag prohibiting trading for the current account from the server side
class=class="str">"cmt">//--- Auto trading on an account
class="type">bool     m_is_change_trade_expert_on;    class=class="str">"cmt">// The flag allowing to trade for an EA from the server side
class="type">bool     m_is_change_trade_expert_off;   class=class="str">"cmt">//The flag prohibiting trading for an EA from the server side
class=class="str">"cmt">//--- Balance
class="type">class="kw">double   m_control_balance_inc;          class=class="str">"cmt">// Tracked balance growth value
class="type">class="kw">double   m_control_balance_dec;          class=class="str">"cmt">// Tracked balance decrease value
class="type">class="kw">double   m_changed_balance_value;        class=class="str">"cmt">// Balance change value
class="type">bool     m_is_change_balance_inc;        class=class="str">"cmt">// The flag of the balance change exceeding the growth value
class="type">bool     m_is_change_balance_dec;        class=class="str">"cmt">// The flag of the balance change exceeding the decrease value
class=class="str">"cmt">//--- Credit
class="type">class="kw">double   m_changed_credit_value;         class=class="str">"cmt">// Credit change value
class="type">bool     m_is_change_credit_inc;         class=class="str">"cmt">// Credit increase flag

◍ 账户状态变量的跟踪骨架

在 MT5 的 EA 或指标类里,要把账户层面的异动捕捉下来,先得声明一组成对的控制变量。下面这段声明把信用、利润、净值、保证金、自由保证金分别拆成了「增长阈值 / 下降阈值 / 实际变化量 / 越界标志」四件套,后面巡检时直接读标志位就能知道哪类资金动了。 信用部分只留了下降标志 m_is_change_credit_dec,没有对称的增长标志,说明逻辑上只盯信用被削减这种异常(券商调额、负数利息都可能触发)。利润与净值则上下都盯:m_control_profit_inc 和 m_control_profit_dec 是人为设定的容忍区间,m_changed_profit_value 存本次快照差值,m_is_change_profit_inc / dec 就是越线开关。 保证金与自由保证金同理,m_control_margin_free_inc / dec 控制自由保证金的波动容忍度,m_changed_margin_free_value 记录实时偏移。外汇和贵金属杠杆高,自由保证金瞬间蒸发会直接拉爆仓,这套变量让你在 OnTick 里用几行判断就能告警,而不是等平仓才看到。 打开 MT5 的 MetaEditor,把下面声明贴进你的账户监视类私有区,先给 *_inc / *_dec 赋个固定值(比如账户权益的 0.5%),跑一周模拟盘看标志位翻转频率,再回来调阈值。

MQL5 / C++
  class="type">bool               m_is_change_credit_dec;                class=class="str">"cmt">// Credit decrease flag
  class=class="str">"cmt">//--- Profit
  class="type">class="kw">double             m_control_profit_inc;                  class=class="str">"cmt">// Tracked profit growth value
  class="type">class="kw">double             m_control_profit_dec;                  class=class="str">"cmt">// Tracked profit decrease value
  class="type">class="kw">double             m_changed_profit_value;                class=class="str">"cmt">// Profit change value
  class="type">bool               m_is_change_profit_inc;                class=class="str">"cmt">// The flag of the profit change exceeding the growth value
  class="type">bool               m_is_change_profit_dec;                class=class="str">"cmt">// The flag of the profit change exceeding the decrease value
  class=class="str">"cmt">//--- Funds(equity)
  class="type">class="kw">double             m_control_equity_inc;                  class=class="str">"cmt">// Tracked funds growth value
  class="type">class="kw">double             m_control_equity_dec;                  class=class="str">"cmt">// Tracked funds decrease value
  class="type">class="kw">double             m_changed_equity_value;                class=class="str">"cmt">// Funds change value
  class="type">bool               m_is_change_equity_inc;                class=class="str">"cmt">// The flag of the funds change exceeding the growth value
  class="type">bool               m_is_change_equity_dec;                class=class="str">"cmt">// The flag of the funds change exceeding the decrease value
  class=class="str">"cmt">//--- Margin
  class="type">class="kw">double             m_control_margin_inc;                  class=class="str">"cmt">// Tracked margin growth value
  class="type">class="kw">double             m_control_margin_dec;                  class=class="str">"cmt">// Tracked margin decrease value
  class="type">class="kw">double             m_changed_margin_value;                class=class="str">"cmt">// Margin change value
  class="type">bool               m_is_change_margin_inc;                class=class="str">"cmt">// The flag of the margin change exceeding the growth value
  class="type">bool               m_is_change_margin_dec;                class=class="str">"cmt">// The flag of the margin change exceeding the decrease value
  class=class="str">"cmt">//--- Free margin
  class="type">class="kw">double             m_control_margin_free_inc;             class=class="str">"cmt">// Tracked free margin growth value
  class="type">class="kw">double             m_control_margin_free_dec;             class=class="str">"cmt">// Tracked free margin decrease value
  class="type">class="kw">double             m_changed_margin_free_value;           class=class="str">"cmt">// Free margin change value

「账户风控变量的内部声明拆解」

在 MT5 的 EA 或指标类里,监控账户风险状态通常依赖一组私有成员变量。下面这段声明覆盖了自由保证金、保证金水平、Margin Call、StopOut 以及挂单/持仓保证金的变动跟踪。 自由保证金方向跟踪用两个 bool 标记:m_is_change_margin_free_inc 表示自由保证金增长超过阈值,m_is_change_margin_free_dec 表示下降超过阈值。外汇与贵金属杠杆高,自由保证金瞬时回撤可能触发强平,这两个标记适合作为预警开关。 保证金水平部分用 double 存阈值与变动值:m_control_margin_level_inc/dec 是上下限跟踪值,m_changed_margin_level_value 是实际变动量,配 m_is_change_margin_level_inc/dec 做越界判定。Margin Call 与 StopOut 层级则分别由 m_changed_margin_so_call_value 和 m_changed_margin_so_so_value 记录,并各带 inc/dec 布尔标记。 挂单保证金属 m_control_margin_initial_inc/dec 与 m_changed_margin_initial_value 管辖,持仓维护保证金对应 m_control_margin_maintenance_inc/dec。开 MT5 在 MetaEditor 搜这些变量名,能直接定位风控模块的触发逻辑。

MQL5 / C++
  class="type">bool            m_is_change_margin_free_inc;            class=class="str">"cmt">// The flag of the free margin change exceeding the growth value
  class="type">bool            m_is_change_margin_free_dec;            class=class="str">"cmt">// The flag of the free margin change exceeding the decrease value
  class=class="str">"cmt">//--- Margin level
  class="type">class="kw">double          m_control_margin_level_inc;             class=class="str">"cmt">// Tracked margin level growth value
  class="type">class="kw">double          m_control_margin_level_dec;             class=class="str">"cmt">// Tracked margin level decrease value
  class="type">class="kw">double          m_changed_margin_level_value;           class=class="str">"cmt">// Margin level change value
  class="type">bool            m_is_change_margin_level_inc;           class=class="str">"cmt">// The flag of the margin level change exceeding the growth value
  class="type">bool            m_is_change_margin_level_dec;           class=class="str">"cmt">// The flag of the margin level change exceeding the decrease value
  class=class="str">"cmt">//--- Margin Call
  class="type">class="kw">double          m_changed_margin_so_call_value;         class=class="str">"cmt">// Margin Call level change value
  class="type">bool            m_is_change_margin_so_call_inc;         class=class="str">"cmt">// Margin Call level increase value
  class="type">bool            m_is_change_margin_so_call_dec;         class=class="str">"cmt">// Margin Call level decrease value
  class=class="str">"cmt">//--- MarginStopOut
  class="type">class="kw">double          m_changed_margin_so_so_value;           class=class="str">"cmt">// Margin StopOut level change value
  class="type">bool            m_is_change_margin_so_so_inc;           class=class="str">"cmt">// Margin StopOut level increase flag
  class="type">bool            m_is_change_margin_so_so_dec;           class=class="str">"cmt">// Margin StopOut level decrease flag
  class=class="str">"cmt">//--- Guarantee sum for pending orders
  class="type">class="kw">double          m_control_margin_initial_inc;           class=class="str">"cmt">// Tracked growth value of the reserved funds for providing the guarantee sum for pending orders
  class="type">class="kw">double          m_control_margin_initial_dec;           class=class="str">"cmt">// Tracked decrease value of the reserved funds for providing the guarantee sum for pending orders
  class="type">class="kw">double          m_changed_margin_initial_value;         class=class="str">"cmt">// The change value of the reserved funds for providing the guarantee sum for pending orders
  class="type">bool            m_is_change_margin_initial_inc;         class=class="str">"cmt">// The flag of the reserved funds for providing the guarantee sum for pending orders exceeding the growth value
  class="type">bool            m_is_change_margin_initial_dec;         class=class="str">"cmt">// The flag of the reserved funds for providing the guarantee sum for pending orders exceeding the decrease value
  class=class="str">"cmt">//--- Guarantee sum for open positions
  class="type">class="kw">double          m_control_margin_maintenance_inc;       class=class="str">"cmt">// The tracked increase value of the funds reserved on an account to ensure a minimum amount for all open positions
  class="type">class="kw">double          m_control_margin_maintenance_dec;       class=class="str">"cmt">// The tracked decrease value of the funds reserved on an account to ensure a minimum amount for all open positions

账户维护保证金与资产负债的变动追踪变量

在 MT5 的账户监听类里,维护保证金(maintenance margin)的变动需要单独拆出三个量:变动绝对值、增量越界标志、减量越界标志。这样在 EA 里你能区分「经纪商上调准备金」和「持仓亏损导致占用增加」,两者对风控的含义不同。 资产端用 m_control_assets_inc / m_control_assets_dec 设定盯梢阈值,m_changed_assets_value 存实际变化,m_is_change_assets_inc / dec 作为触发开关。负债端结构完全一致,只是对象换成 m_control_liabilities_*。实盘里若 m_is_change_liabilities_inc 被置真,说明负债突破你设的下降阈值,可能预示净值回撤加速——外汇和贵金属杠杆下这属于高风险信号。 被冻结的佣金也照此办理:m_control_comission_blocked_inc / dec 控制边界,m_changed_comission_blocked_value 记录差值。注意挂单pending时佣金就可能被 block,不是只有成交才动。 InitChangesParams() 与 InitControlsParams() 负责把上述 15 个 double/bool 清零或载入默认值,必须在 OnInit 里调用,否则首次 OnTick 读到野指针大概率误触发。

MQL5 / C++
class="type">class="kw">double               m_changed_margin_maintenance_value; class=class="str">"cmt">// The change value of the funds reserved on an account to ensure a minimum amount for all open positions
class="type">bool                 m_is_change_margin_maintenance_inc; class=class="str">"cmt">// The flag of the funds reserved on an account to ensure a minimum amount for all open positions exceeding the growth value
class="type">bool                 m_is_change_margin_maintenance_dec; class=class="str">"cmt">// The flag of the funds reserved on an account to ensure a minimum amount for all open positions exceeding the decrease value
class=class="str">"cmt">//--- Assets
class="type">class="kw">double               m_control_assets_inc;               class=class="str">"cmt">// Tracked assets growth value
class="type">class="kw">double               m_control_assets_dec;               class=class="str">"cmt">// Tracked assets decrease value
class="type">class="kw">double               m_changed_assets_value;             class=class="str">"cmt">// Assets change value
class="type">bool                 m_is_change_assets_inc;             class=class="str">"cmt">// The flag of the assets change exceeding the growth value
class="type">bool                 m_is_change_assets_dec;             class=class="str">"cmt">// The flag of the assets change exceeding the decrease value
class=class="str">"cmt">//--- Liabilities
class="type">class="kw">double               m_control_liabilities_inc;          class=class="str">"cmt">// Tracked liabilities growth value
class="type">class="kw">double               m_control_liabilities_dec;          class=class="str">"cmt">// Tracked liabilities decrease value
class="type">class="kw">double               m_changed_liabilities_value;        class=class="str">"cmt">// Liabilities change values
class="type">bool                 m_is_change_liabilities_inc;        class=class="str">"cmt">// The flag of the liabilities change exceeding the growth value
class="type">bool                 m_is_change_liabilities_dec;        class=class="str">"cmt">// The flag of the liabilities change exceeding the decrease value
class=class="str">"cmt">//--- Blocked commissions
class="type">class="kw">double               m_control_comission_blocked_inc;    class=class="str">"cmt">// Tracked blocked commissions growth value
class="type">class="kw">double               m_control_comission_blocked_dec;    class=class="str">"cmt">// Tracked blocked commissions decrease value
class="type">class="kw">double               m_changed_comission_blocked_value;  class=class="str">"cmt">// Blocked commissions changed value
class="type">bool                 m_is_change_comission_blocked_inc;  class=class="str">"cmt">// The flag of the tracked commissions change exceeding the growth value
class="type">bool                 m_is_change_comission_blocked_dec;  class=class="str">"cmt">// The flag of the tracked commissions change exceeding the decrease value

class=class="str">"cmt">//--- Initialize the variables of(class="num">1) tracked, (class="num">2) controlled account data
class="type">void                 InitChangesParams(class="type">void);
class="type">void                 InitControlsParams(class="type">void);

◍ 账户变更参数的初始化细节

在 MT5 的账户事件跟踪类里,InitChangesParams 负责把上一次比对残留的变更标记全部归零。它先清空并排序 m_list_changes,再逐项把杠杆、挂单上限、交易权限等状态标志复位为 false 或 0,避免旧事件污染下一轮检测。 以杠杆为例,m_changed_leverage_value 置 0,同时 m_is_change_leverage_inc 与 m_is_change_leverage_dec 均设为 false,代表本次轮询前不预设任何杠杆变动方向。挂单上限也同理:m_changed_limit_orders_value=0,增减两个布尔标志都关掉。 交易权限方面分两层——账户级和服务级 EA。m_is_change_trade_allowed_on/off 管服务器对账户手工交易的放行与禁止,m_is_change_trade_expert_on/off 管 EA 自动交易的同维度开关。初始化时四者全 false,意味着未捕获到任何权限翻转。 开 MT5 自建账户监控 EA 时,可直接复用这段复位逻辑:每次 OnAccount 事件触发前调用 InitChangesParams,能把漏报概率压到最低。外汇与贵金属品种受杠杆和权限突变影响大,这类复位缺失可能导致策略在禁交易期误发单,属高风险操作环节。

MQL5 / C++
class="type">void CAccountsCollection::InitChangesParams(class="type">void)
  {
class=class="str">"cmt">//--- List and code of changes
   this.m_list_changes.Clear();                     class=class="str">"cmt">// 清空变更列表
   this.m_list_changes.Sort();                     class=class="str">"cmt">// 对变更列表排序
class=class="str">"cmt">//--- Leverage
   this.m_changed_leverage_value=class="num">0;                class=class="str">"cmt">// 杠杆变动值归零
   this.m_is_change_leverage_inc=class="kw">false;            class=class="str">"cmt">// 杠杆增加标志关
   this.m_is_change_leverage_dec=class="kw">false;            class=class="str">"cmt">// 杠杆减少标志关
class=class="str">"cmt">//--- Number of active pending orders
   this.m_changed_limit_orders_value=class="num">0;            class=class="str">"cmt">// 活跃挂单上限变动值归零
   this.m_is_change_limit_orders_inc=class="kw">false;        class=class="str">"cmt">// 挂单上限增加标志关
   this.m_is_change_limit_orders_dec=class="kw">false;        class=class="str">"cmt">// 挂单上限减少标志关
class=class="str">"cmt">//--- Trading on an account
   this.m_is_change_trade_allowed_on=class="kw">false;        class=class="str">"cmt">// 账户手工交易允许标志关
   this.m_is_change_trade_allowed_off=class="kw">false;       class=class="str">"cmt">// 账户手工交易禁止标志关
class=class="str">"cmt">//--- Auto trading on an account
   this.m_is_change_trade_expert_on=class="kw">false;         class=class="str">"cmt">// EA交易允许标志关
   this.m_is_change_trade_expert_off=class="kw">false;        class=class="str">"cmt">// EA交易禁止标志关
class=class="str">"cmt">//--- Balance

「账户状态快照的清零初始化」

在 MT5 自定义账户监听类里,构造函数第一步往往要把所有追踪字段归零,否则上一次 tick 的残值会污染本次比对。下面这段初始化把余额、信用、盈利、净值、保证金、自由保证金、保证金水平以及 Margin Call 阈值共八类变量的「变动值」和「增减方向标记」全部置为 0 或 false。 注意 m_is_change_xxx_inc / dec 这类布尔标记,它们不是记录「当前值」,而是标记「本次变动是否越过你预设的增长 / 下降阈值」。初始化时统一 false,意味着新一帧账户事件到来前,引擎默认「无任何越界」。 外汇与贵金属保证金交易高风险,这类底层状态机若初始化遗漏,可能在极端跳空时误触发预警。直接把下面代码贴进你的类构造函数,开 MT5 用 AccountInfoDouble 跑一帧即可验证字段是否干净。

MQL5 / C++
this.m_changed_balance_value=class="num">0;                      class=class="str">"cmt">// Balance change value
this.m_is_change_balance_inc=class="kw">false;                class=class="str">"cmt">// The flag of the balance change exceeding the growth value
this.m_is_change_balance_dec=class="kw">false;                class=class="str">"cmt">// The flag of the balance change exceeding the decrease value
class=class="str">"cmt">//--- Credit
this.m_changed_credit_value=class="num">0;                     class=class="str">"cmt">// Credit change value
this.m_is_change_credit_inc=class="kw">false;                 class=class="str">"cmt">// Credit increase flag
this.m_is_change_credit_dec=class="kw">false;                 class=class="str">"cmt">// Credit decrease flag
class=class="str">"cmt">//--- Profit
this.m_changed_profit_value=class="num">0;                     class=class="str">"cmt">// Profit change value
this.m_is_change_profit_inc=class="kw">false;                 class=class="str">"cmt">// The flag of the profit change exceeding the growth value
this.m_is_change_profit_dec=class="kw">false;                 class=class="str">"cmt">// The flag of the profit change exceeding the decrease value
class=class="str">"cmt">//--- Funds
this.m_changed_equity_value=class="num">0;                     class=class="str">"cmt">// Funds change value
this.m_is_change_equity_inc=class="kw">false;                 class=class="str">"cmt">// The flag of the funds change exceeding the growth value
this.m_is_change_equity_dec=class="kw">false;                 class=class="str">"cmt">// The flag of the funds change exceeding the decrease value
class=class="str">"cmt">//--- Margin
this.m_changed_margin_value=class="num">0;                     class=class="str">"cmt">// Margin change value
this.m_is_change_margin_inc=class="kw">false;                 class=class="str">"cmt">// The flag of the margin change exceeding the growth value
this.m_is_change_margin_dec=class="kw">false;                 class=class="str">"cmt">// The flag of the margin change exceeding the decrease value
class=class="str">"cmt">//--- Free margin
this.m_changed_margin_free_value=class="num">0;                class=class="str">"cmt">// Free margin change value
this.m_is_change_margin_free_inc=class="kw">false;            class=class="str">"cmt">// The flag of the free margin change exceeding the growth value
this.m_is_change_margin_free_dec=class="kw">false;            class=class="str">"cmt">// The flag of the free margin change exceeding the decrease value
class=class="str">"cmt">//--- Margin level
this.m_changed_margin_level_value=class="num">0;               class=class="str">"cmt">// Margin level change value
this.m_is_change_margin_level_inc=class="kw">false;           class=class="str">"cmt">// The flag of the margin level change exceeding the growth value
this.m_is_change_margin_level_dec=class="kw">false;           class=class="str">"cmt">// The flag of the margin level change exceeding the decrease value
class=class="str">"cmt">//--- Margin Call level
this.m_changed_margin_so_call_value=class="num">0;             class=class="str">"cmt">// Margin Call level change value

账户风控字段的初始化清零

在 MT5 的账户监控类里,CAccountsCollection::InitControlsParams 负责把一批受控账户参数一次性归零。这些字段覆盖 Margin Call、StopOut、挂单保证、持仓保证、资产、负债与冻结佣金,是后续比对「账户状态是否越过阈值」的基础。 外汇与贵金属保证金交易具有高杠杆高风险,账户权益可能在短时间内剧烈波动;初始化时把所有 change value 置 0、把 inc/dec 标志置 false,意味着本轮监控从「无变化」基准线开始,避免上一轮残留状态污染新一帧判断。 下面这段是构造函数或初始化方法里的典型写法,逐行看:this.m_is_change_margin_so_call_inc=false 表示 Margin Call 等级上升标志关;m_is_change_margin_so_call_dec=false 为下降标志关;m_changed_margin_so_so_value=0 把 StopOut 等级变化值清零;m_is_change_margin_so_so_inc/dec 同理关掉增减标志。 继续往下:m_changed_margin_initial_value=0 是挂单保证预留金变化值清零,m_is_change_margin_initial_inc/dec=false 关掉其增减标志;m_changed_margin_maintenance_value=0 为持仓最低保证清零,对应 inc/dec 标志关闭;m_changed_assets_value=0 与 m_is_change_assets_inc/dec=false 处理资产增减;m_changed_liabilities_value=0 与负债增减标志关闭;最后 m_changed_comission_blocked_value=0 及佣金冻结增减标志 false。开 MT5 新建 EA 把这段塞进 InitControlsParams,能在每次 OnTick 前拿到干净的账户快照。

MQL5 / C++
  this.m_is_change_margin_so_call_inc=class="kw">false;       class=class="str">"cmt">// Margin Call level increase flag
  this.m_is_change_margin_so_call_dec=class="kw">false;       class=class="str">"cmt">// Margin Call level decrease flag
class=class="str">"cmt">//--- Margin StopOut level
  this.m_changed_margin_so_so_value=class="num">0;             class=class="str">"cmt">// Margin StopOut level change value
  this.m_is_change_margin_so_so_inc=class="kw">false;         class=class="str">"cmt">// Margin StopOut level increase flag
  this.m_is_change_margin_so_so_dec=class="kw">false;         class=class="str">"cmt">// Margin StopOut level decrease flag
class=class="str">"cmt">//--- Guarantee sum for pending orders
  this.m_changed_margin_initial_value=class="num">0;           class=class="str">"cmt">// The change value of the reserved funds for providing the guarantee sum for pending orders
  this.m_is_change_margin_initial_inc=class="kw">false;        class=class="str">"cmt">// The flag of the reserved funds for providing the guarantee sum for pending orders exceeding the growth value
  this.m_is_change_margin_initial_dec=class="kw">false;        class=class="str">"cmt">// The flag of the reserved funds for providing the guarantee sum for pending orders exceeding the decrease value
class=class="str">"cmt">//--- Guarantee sum for open positions
  this.m_changed_margin_maintenance_value=class="num">0;       class=class="str">"cmt">// The change value of the funds reserved on an account to ensure a minimum amount for all open positions
  this.m_is_change_margin_maintenance_inc=class="kw">false;    class=class="str">"cmt">// The flag of the funds reserved on an account to ensure a minimum amount for all open positions exceeding the growth value
  this.m_is_change_margin_maintenance_dec=class="kw">false;    class=class="str">"cmt">// The flag of the funds reserved on an account to ensure a minimum amount for all open positions exceeding the decrease value
class=class="str">"cmt">//--- Assets
  this.m_changed_assets_value=class="num">0;                    class=class="str">"cmt">// Assets change value
  this.m_is_change_assets_inc=class="kw">false;                class=class="str">"cmt">// The flag of the assets change exceeding the growth value
  this.m_is_change_assets_dec=class="kw">false;                class=class="str">"cmt">// The flag of the assets change exceeding the decrease value
class=class="str">"cmt">//--- Liabilities
  this.m_changed_liabilities_value=class="num">0;               class=class="str">"cmt">// Liabilities change value
  this.m_is_change_liabilities_inc=class="kw">false;           class=class="str">"cmt">// The flag of the liabilities change exceeding the growth value
  this.m_is_change_liabilities_dec=class="kw">false;           class=class="str">"cmt">// The flag of the liabilities change exceeding the decrease value
class=class="str">"cmt">//--- Blocked commissions
  this.m_changed_comission_blocked_value=class="num">0;         class=class="str">"cmt">// Blocked commissions change value
  this.m_is_change_comission_blocked_inc=class="kw">false;     class=class="str">"cmt">// The flag of the tracked commissions change exceeding the growth value
  this.m_is_change_comission_blocked_dec=class="kw">false;     class=class="str">"cmt">// The flag of the tracked commissions change exceeding the decrease value
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize the variables of controlled account data              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CAccountsCollection::InitControlsParams(class="type">void)
  {

◍ 账户状态追踪的阈值设定

在 MT5 的 EA 或指标里做账户状态监控,核心是先给各类资金变量设好「变动触发阈值」。下面这段初始化代码把余额、利润、净值、保证金等十多项都配了对称的正负向步长,用来判断什么时候该记录一次状态跳变。 余额和利润类阈值偏小:余额增减都设为 50,利润增减 20,净值增减 15。这意味着账户余额波动 50 单位、浮盈回撤 20 单位就可能触发一次追踪事件,适合抓短线账户节奏。 保证金相关阈值明显放大:保证金、空闲保证金、挂单预留、持仓维持、资产、负债、冻结佣金统统设 1000,而保证金水平上涨阈值给到 10000、下跌只给 500。这种非对称说明对保证金水平「往下掉」更敏感——跌破 500 就可能亮红灯,外汇和贵金属杠杆高,这种设置能更早暴露爆仓风险倾向。 直接把下面代码粘进你的 CTrade 派生类构造函数,改几个数字就能适配自己的账户规模,开 MT5 用策略测试器看日志里触发频率是否符合预期。

MQL5 / C++
class=class="str">"cmt">//--- Balance
   this.m_control_balance_inc=class="num">50;                     class=class="str">"cmt">// Tracked balance growth value
   this.m_control_balance_dec=class="num">50;                     class=class="str">"cmt">// Tracked balance decrease value
class=class="str">"cmt">//--- Profit
   this.m_control_profit_inc=class="num">20;                      class=class="str">"cmt">// Tracked profit growth value
   this.m_control_profit_dec=class="num">20;                      class=class="str">"cmt">// Tracked profit decrease value
class=class="str">"cmt">//--- Funds(equity)
   this.m_control_equity_inc=class="num">15;                      class=class="str">"cmt">// Tracked funds growth value
   this.m_control_equity_dec=class="num">15;                      class=class="str">"cmt">// Tracked funds decrease value
class=class="str">"cmt">//--- Margin
   this.m_control_margin_inc=class="num">1000;                    class=class="str">"cmt">// Tracked margin growth value
   this.m_control_margin_dec=class="num">1000;                    class=class="str">"cmt">// Tracked margin decrease value
class=class="str">"cmt">//--- Free margin
   this.m_control_margin_free_inc=class="num">1000;               class=class="str">"cmt">// Tracked free margin growth value
   this.m_control_margin_free_dec=class="num">1000;               class=class="str">"cmt">// Tracked free margin decrease value
class=class="str">"cmt">//--- Margin level
   this.m_control_margin_level_inc=class="num">10000;             class=class="str">"cmt">// Tracked margin level growth value
   this.m_control_margin_level_dec=class="num">500;               class=class="str">"cmt">// Tracked margin level decrease value
class=class="str">"cmt">//--- Guarantee sum for pending orders
   this.m_control_margin_initial_inc=class="num">1000;            class=class="str">"cmt">// Tracked growth value of the reserved funds for providing the guarantee sum for pending orders
   this.m_control_margin_initial_dec=class="num">1000;            class=class="str">"cmt">// Tracked decrease value of the reserved funds for providing the guarantee sum for pending orders
class=class="str">"cmt">//--- Guarantee sum for open positions
   this.m_control_margin_maintenance_inc=class="num">1000;        class=class="str">"cmt">// The tracked increase value of the funds reserved on an account to ensure a minimum amount for all open positions
   this.m_control_margin_maintenance_dec=class="num">1000;        class=class="str">"cmt">// The tracked decrease value of the funds reserved on an account to ensure a minimum amount for all open positions
class=class="str">"cmt">//--- Assets
   this.m_control_assets_inc=class="num">1000;                    class=class="str">"cmt">// Tracked assets growth value
   this.m_control_assets_dec=class="num">1000;                    class=class="str">"cmt">// Tracked assets decrease value
class=class="str">"cmt">//--- Liabilities
   this.m_control_liabilities_inc=class="num">1000;               class=class="str">"cmt">// Tracked liabilities growth value
   this.m_control_liabilities_dec=class="num">1000;               class=class="str">"cmt">// Tracked liabilities decrease value
class=class="str">"cmt">//--- Blocked commissions
   this.m_control_comission_blocked_inc=class="num">1000;         class=class="str">"cmt">// Tracked blocked commissions growth value

「账户变动的位标志累加逻辑」

在 MT5 的账户监控类里,区分「这次刷新到底改了什么」靠的是一个整型变更码。思路是把当前账户结构与上一帧结构逐字段比对,不相等就往 m_change_code 上叠加对应的事件标志位。 初始先清为零值:this.m_change_code=ACCOUNT_EVENT_FLAG_NO_EVENT,保证每次重算都从干净状态开始,不会把上一次的残留位带进来。 随后是一连串 if 判断,覆盖 trade_allowed、trade_expert、leverage、limit_orders、balance、credit、profit、equity、margin 以及 margin_free、margin_level、margin_so_call、margin_so_so、margin_initial、margin_maintenance 等字段。只要 curr 减 prev 不等于 0(布尔类直接比不等),就加对应的 ACCOUNT_EVENT_FLAG_* 常量。 这种位或累加的好处是:一次账户刷新可能同时动了余额和保证金,变更码里两个标志位都会置起,外部用位与就能精确知道哪几项变了,不用多次轮询。外汇和贵金属账户杠杆与保证金变动频繁,用这套机制捕获状态切换,对自动暂停加仓或弹窗告警有直接用处。

MQL5 / C++
this.m_change_code=ACCOUNT_EVENT_FLAG_NO_EVENT;

if(this.m_struct_curr_account.trade_allowed!=this.m_struct_prev_account.trade_allowed)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_TRADE_ALLOWED;
if(this.m_struct_curr_account.trade_expert!=this.m_struct_prev_account.trade_expert)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_TRADE_EXPERT;
if(this.m_struct_curr_account.leverage-this.m_struct_prev_account.leverage!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_LEVERAGE;
if(this.m_struct_curr_account.limit_orders-this.m_struct_prev_account.limit_orders!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_LIMIT_ORDERS;
if(this.m_struct_curr_account.balance-this.m_struct_prev_account.balance!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_BALANCE;
if(this.m_struct_curr_account.credit-this.m_struct_prev_account.credit!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_CREDIT;
if(this.m_struct_curr_account.profit-this.m_struct_prev_account.profit!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_PROFIT;
if(this.m_struct_curr_account.equity-this.m_struct_prev_account.equity!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_EQUITY;
if(this.m_struct_curr_account.margin-this.m_struct_prev_account.margin!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN;
if(this.m_struct_curr_account.margin_free-this.m_struct_prev_account.margin_free!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN_FREE;
if(this.m_struct_curr_account.margin_level-this.m_struct_prev_account.margin_level!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN_LEVEL;
if(this.m_struct_curr_account.margin_so_call-this.m_struct_prev_account.margin_so_call!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN_SO_CALL;
if(this.m_struct_curr_account.margin_so_so-this.m_struct_prev_account.margin_so_so!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN_SO_SO;
if(this.m_struct_curr_account.margin_initial-this.m_struct_prev_account.margin_initial!=class="num">0)
   this.m_change_code+=ACCOUNT_EVENT_FLAG_MARGIN_INITIAL;
if(this.m_struct_curr_account.margin_maintenance-this.m_struct_prev_account.margin_maintenance!=class="num">0)

账户事件类型的判定与写入

在账户状态监控类里,SetTypeEvent 负责把累计的变更标志翻译成具体事件枚举,并写进变更列表。它先调用 InitChangesParams 清空上轮缓存,再逐个比对 trade_allowed、trade_expert 等布尔位,决定本次是 OFF 还是 ON 事件。 以交易权限为例:当 IsPresentEventFlag 捕获到 ACCOUNT_EVENT_FLAG_TRADE_ALLOWED,且当前 trade_allowed 为 false,便置 m_is_change_trade_allowed_off=true,事件赋为 ACCOUNT_EVENT_TRADE_ALLOWED_OFF,同时把 prev 结构的 trade_allowed 同步成当前值,避免下次重复触发。 自动交易分支逻辑对称:trade_expert 由 true 翻 false 记 TRADE_EXPERT_OFF,反之记 ON;每生成一种 event,都用 m_list_changes.Search(event)==WRONG_VALUE 做去重,只在列表里没有时才 Add。这套机制让 EA 在 MT5 上能精确感知券商下发的权限切换,而不是每 tick 盲判。 开 MT5 把这段接进自己的账户监听类,改一下事件枚举名就能直接跑;外汇与贵金属杠杆高,权限被关时未平仓头寸可能瞬间放大风险敞口,需让小布这类工具第一时间弹窗。

MQL5 / C++
class="type">void CAccountsCollection::SetTypeEvent(class="type">void)
  {
  this.InitChangesParams();
  ENUM_ACCOUNT_EVENT event=ACCOUNT_EVENT_NO_EVENT;
class=class="str">"cmt">//--- Changing permission to trade for the account
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_TRADE_ALLOWED))
    {
    if(!this.m_struct_curr_account.trade_allowed)
      {
      this.m_is_change_trade_allowed_off=true;
      event=ACCOUNT_EVENT_TRADE_ALLOWED_OFF;
      this.m_struct_prev_account.trade_allowed=this.m_struct_curr_account.trade_allowed;
      }
    else
      {
      this.m_is_change_trade_allowed_on=true;
      event=ACCOUNT_EVENT_TRADE_ALLOWED_ON;
      this.m_struct_prev_account.trade_allowed=this.m_struct_curr_account.trade_allowed;
      }
    if(this.m_list_changes.Search(event)==WRONG_VALUE)
      this.m_list_changes.Add(event);
    }
class=class="str">"cmt">//--- Changing permission for auto trading for the account
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_TRADE_EXPERT))
    {
    if(!this.m_struct_curr_account.trade_expert)
      {
      this.m_is_change_trade_expert_off=true;
      event=ACCOUNT_EVENT_TRADE_EXPERT_OFF;
      this.m_struct_prev_account.trade_expert=class="kw">false;
      }
    else
      {
      this.m_is_change_trade_expert_on=true;
      event=ACCOUNT_EVENT_TRADE_EXPERT_ON;
      this.m_struct_prev_account.trade_expert=true;
      }

◍ 账户杠杆与挂单额度的增量捕获

在账户事件监控类里,杠杆、最大挂单数、信用额这三组字段的变动是分开判定的。每段都先查对应的事件标志位,再拿当前结构与上一帧结构做差,差值为正就置递增标记并派发 INC 事件,为负则走 DEC 分支。 以杠杆为例,m_changed_leverage_value 是当前 leverage 减上一帧 leverage 的硬差值;大于 0 时 m_is_change_leverage_inc 置真并赋 event=ACCOUNT_EVENT_LEVERAGE_INC,否则 m_is_change_leverage_dec 置真并赋 DEC 事件。 只有当变动事件不在 m_list_changes 里(Search 返回 WRONG_VALUE)且 Add 成功,才把上一帧对应字段同步成当前值。这一步很关键:避免同一次账户刷新里重复记录同一类变动。 挂单上限和信用额的逻辑完全一致,只是字段换成 limit_orders 与 credit,标记换成 m_is_change_limit_orders_inc/dec 和 m_is_change_credit_inc/dec。开 MT5 把这段塞进你的 CAccount 监控类,跑一轮模拟账户改杠杆,能在日志里直接看到 INC/DEC 事件只触发一次。

MQL5 / C++
if(this.m_list_changes.Search(event)==WRONG_VALUE)
   this.m_list_changes.Add(event);
   }
class=class="str">"cmt">//--- Change the leverage
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_LEVERAGE))
   {
    this.m_changed_leverage_value=this.m_struct_curr_account.leverage-this.m_struct_prev_account.leverage;
    if(this.m_changed_leverage_value>class="num">0)
      {
       this.m_is_change_leverage_inc=true;
       event=ACCOUNT_EVENT_LEVERAGE_INC;
      }
    else
      {
       this.m_is_change_leverage_dec=true;
       event=ACCOUNT_EVENT_LEVERAGE_DEC;
      }
    if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
       this.m_struct_prev_account.leverage=this.m_struct_curr_account.leverage;
   }
class=class="str">"cmt">//--- Changing the maximum allowed number of active pending orders
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_LIMIT_ORDERS))
   {
    this.m_changed_limit_orders_value=this.m_struct_curr_account.limit_orders-this.m_struct_prev_account.limit_orders;
    if(this.m_changed_limit_orders_value>class="num">0)
      {
       this.m_is_change_limit_orders_inc=true;
       event=ACCOUNT_EVENT_LIMIT_ORDERS_INC;
      }
    else
      {
       this.m_is_change_limit_orders_dec=true;
       event=ACCOUNT_EVENT_LIMIT_ORDERS_DEC;
      }
    if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
       this.m_struct_prev_account.limit_orders=this.m_struct_curr_account.limit_orders;
   }
class=class="str">"cmt">//--- Changing the credit in a deposit currency
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_CREDIT))
   {
    this.m_changed_credit_value=this.m_struct_curr_account.credit-this.m_struct_prev_account.credit;
    if(this.m_changed_credit_value>class="num">0)
      {
       this.m_is_change_credit_inc=true;
       event=ACCOUNT_EVENT_CREDIT_INC;
      }
    else
      {
       this.m_is_change_credit_dec=true;
       event=ACCOUNT_EVENT_CREDIT_DEC;
      }
    if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
       this.m_struct_prev_account.credit=this.m_struct_curr_account.credit;
   }

「账户风控阈值与余额变动的事件捕获」

在 MT5 的账户状态监控类里,Margin Call 与 Stop Out 水平并非静态读数,而是随券商设置实时跳变。下面这段逻辑专门比对前后两帧账户结构,把水平上调 / 下调拆成独立事件写入变更列表。 //--- Changing the Margin Call level if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_SO_CALL)) { this.m_changed_margin_so_call_value=this.m_struct_curr_account.margin_so_call-this.m_struct_prev_account.margin_so_call; if(this.m_changed_margin_so_call_value>0) { this.m_is_change_margin_so_call_inc=true; event=ACCOUNT_EVENT_MARGIN_SO_CALL_INC; } else { this.m_is_change_margin_so_call_dec=true; event=ACCOUNT_EVENT_MARGIN_SO_CALL_DEC; } if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event)) this.m_struct_prev_account.margin_so_call=this.m_struct_curr_account.margin_so_call; } //--- Changing the Stop Out level if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_SO_SO)) { this.m_changed_margin_so_so_value=this.m_struct_curr_account.margin_so_so-this.m_struct_prev_account.margin_so_so; if(this.m_changed_margin_so_so_value>0) { this.m_is_change_margin_so_so_inc=true; event=ACCOUNT_EVENT_MARGIN_SO_SO_INC; } else { this.m_is_change_margin_so_so_dec=true; event=ACCOUNT_EVENT_MARGIN_SO_SO_DEC; } if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event)) this.m_struct_prev_account.margin_so_so=this.m_struct_curr_account.margin_so_so; } //--- The balance exceeds the specified change value +/- if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_BALANCE)) { this.m_changed_balance_value=this.m_struct_curr_account.balance-this.m_struct_prev_account.balance; if(this.m_changed_balance_value>this.m_control_balance_inc) { this.m_is_change_balance_inc=true; event=ACCOUNT_EVENT_BALANCE_INC; if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event)) this.m_struct_prev_account.balance=this.m_struct_curr_account.balance; } else if(this.m_changed_balance_value<-this.m_control_balance_dec) { this.m_is_change_balance_dec=true; event=ACCOUNT_EVENT_BALANCE_DEC; 逐行看:先判标志位确认该类事件参与监控;用当前帧减上一帧得到差值;差值大于 0 置增量布尔并赋 INC 事件,否则置减量布尔赋 DEC 事件;列表里查不到该事件才 Add,成功后才把 prev 结构刷新为 curr,避免同一次跳变重复触发。 余额分支多了一个阈值控制:只有增减绝对值越过 m_control_balance_inc / m_control_balance_dec 才记事件,散户账户的微小利息浮动就不会刷屏。外汇与贵金属杠杆高,Margin Call / Stop Out 被经纪商临时修改时,这套捕获能让你在 EA 里第一时间感知并降仓,但券商改参数本身不预示行情,仅影响强平概率。

MQL5 / C++
class=class="str">"cmt">//--- Changing the Margin Call level
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_SO_CALL))
    {
      this.m_changed_margin_so_call_value=this.m_struct_curr_account.margin_so_call-this.m_struct_prev_account.margin_so_call;
      if(this.m_changed_margin_so_call_value>class="num">0)
        {
         this.m_is_change_margin_so_call_inc=true;
         event=ACCOUNT_EVENT_MARGIN_SO_CALL_INC;
        }
      else
        {
         this.m_is_change_margin_so_call_dec=true;
         event=ACCOUNT_EVENT_MARGIN_SO_CALL_DEC;
        }
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.margin_so_call=this.m_struct_curr_account.margin_so_call;
    }
class=class="str">"cmt">//--- Changing the Stop Out level
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_SO_SO))
    {
      this.m_changed_margin_so_so_value=this.m_struct_curr_account.margin_so_so-this.m_struct_prev_account.margin_so_so;
      if(this.m_changed_margin_so_so_value>class="num">0)
        {
         this.m_is_change_margin_so_so_inc=true;
         event=ACCOUNT_EVENT_MARGIN_SO_SO_INC;
        }
      else
        {
         this.m_is_change_margin_so_so_dec=true;
         event=ACCOUNT_EVENT_MARGIN_SO_SO_DEC;
        }
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.margin_so_so=this.m_struct_curr_account.margin_so_so;
    }
class=class="str">"cmt">//--- The balance exceeds the specified change value +/- 
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_BALANCE))
    {
      this.m_changed_balance_value=this.m_struct_curr_account.balance-this.m_struct_prev_account.balance;
      if(this.m_changed_balance_value>this.m_control_balance_inc)
        {
         this.m_is_change_balance_inc=true;
         event=ACCOUNT_EVENT_BALANCE_INC;
         if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
            this.m_struct_prev_account.balance=this.m_struct_curr_account.balance;
        }
      else if(this.m_changed_balance_value<-this.m_control_balance_dec)
        {
         this.m_is_change_balance_dec=true;
         event=ACCOUNT_EVENT_BALANCE_DEC;

账户盈亏与净值的阈值触发逻辑

在账户事件监控类里,利润、净值和保证金的变动都不是连续广播,而是先算差值再和阈值比对。只有超过正向或反向控制值,才会把对应事件写进变更列表,并同步更新上一帧的快照字段。 利润分支里,m_changed_profit_value 取当前 profit 减上一帧 profit。若大于 m_control_profit_inc 则置 m_is_change_profit_inc 为真并推 ACCOUNT_EVENT_PROFIT_INC;若小于负的 m_control_profit_dec 则推 ACCOUNT_EVENT_PROFIT_DEC,两种情况下都把 prev 的 profit 刷成 curr。 净值处理完全一致:m_changed_equity_value 超 m_control_equity_inc 触发 EQUITY_INC,低于负 m_control_equity_dec 触发 EQUITY_DEC,随后把 m_struct_prev_account.equity 覆盖为当前值。外汇与贵金属杠杆高,净值跳变往往发生在数据行情瞬间,阈值设太小会被刷屏。 清单去重的写法是 Search(event)==WRONG_VALUE 才 Add,保证同一事件在一次扫描里只记一次。你可以把 m_control_profit_inc 临时改成 0.0 在策略测试器里跑,观察变更列表是否每次 tick 都进事件。

MQL5 / C++
if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
   this.m_struct_prev_account.balance=this.m_struct_curr_account.balance;
   }
   }
class=class="str">"cmt">//--- The profit exceeds the specified change value +/-
   if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_PROFIT))
   {
      this.m_changed_profit_value=this.m_struct_curr_account.profit-this.m_struct_prev_account.profit;
      if(this.m_changed_profit_value>this.m_control_profit_inc)
      {
       this.m_is_change_profit_inc=true;
       event=ACCOUNT_EVENT_PROFIT_INC;
       if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.profit=this.m_struct_curr_account.profit;
      }
      else if(this.m_changed_profit_value<-this.m_control_profit_dec)
      {
       this.m_is_change_profit_dec=true;
       event=ACCOUNT_EVENT_PROFIT_DEC;
       if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.profit=this.m_struct_curr_account.profit;
      }
   }
class=class="str">"cmt">//--- The equity exceeds the specified change value +/-
   if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_EQUITY))
   {
      this.m_changed_equity_value=this.m_struct_curr_account.equity-this.m_struct_prev_account.equity;
      if(this.m_changed_equity_value>this.m_control_equity_inc)
      {
       this.m_is_change_equity_inc=true;
       event=ACCOUNT_EVENT_EQUITY_INC;
       if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.equity=this.m_struct_curr_account.equity;
      }
      else if(this.m_changed_equity_value<-this.m_control_equity_dec)
      {
       this.m_is_change_equity_dec=true;
       event=ACCOUNT_EVENT_EQUITY_DEC;
       if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.equity=this.m_struct_curr_account.equity;
      }
   }
class=class="str">"cmt">//--- The reserved margin on an account in the deposit currency change exceeds the specified value +/-
   if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN))
   {

◍ 账户保证金变动的事件触发逻辑

在 MT5 的账户状态监控类里,保证金相关事件靠前后两帧账户结构的差值来判定。代码先算 used margin 的增减:当前占用保证金减去上一次快照值,若高于预设阈值 m_control_margin_inc 就置位上涨标志并写入事件列表,低于负向阈值 m_control_margin_dec 则记下跌事件。 可用保证金(margin free)走同一套比对:差值突破 m_control_margin_free_inc 或跌破 -m_control_margin_free_dec 才触发对应事件,且只有列表里搜不到该事件(返回 WRONG_VALUE)才 Add,避免重复入列。 margin level 以百分比形式参与判断,m_changed_margin_level_value 超 m_control_margin_level_inc 时挂 ACCOUNT_EVENT_MARGIN_LEVEL_INC。外汇与贵金属杠杆高,保证金阈值设得太窄会在刷单行情里频繁误触,建议在策略测试器用真实点差回放调参。 让小布替你跑这套 把 m_control_margin_inc 先设成账户净值 0.5%,用 EURUSD 的 M1 历史跑一周,看事件触发次数是否和手动标注的加仓时段吻合,再反推阈值。

MQL5 / C++
this.m_changed_margin_value=this.m_struct_curr_account.margin-this.m_struct_prev_account.margin;
if(this.m_changed_margin_value>this.m_control_margin_inc)
  {
   this.m_is_change_margin_inc=true;
   event=ACCOUNT_EVENT_MARGIN_INC;
   if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
     this.m_struct_prev_account.margin=this.m_struct_curr_account.margin;
  }
else if(this.m_changed_margin_value<-this.m_control_margin_dec)
  {
   this.m_is_change_margin_dec=true;
   event=ACCOUNT_EVENT_MARGIN_DEC;
   if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
     this.m_struct_prev_account.margin=this.m_struct_curr_account.margin;
  }
 }
class=class="str">"cmt">//--- The free funds available for opening a position in a deposit currency exceed the specified change value +/-
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_FREE))
  {
   this.m_changed_margin_free_value=this.m_struct_curr_account.margin_free-this.m_struct_prev_account.margin_free;
   if(this.m_changed_margin_free_value>this.m_control_margin_free_inc)
     {
      this.m_is_change_margin_free_inc=true;
      event=ACCOUNT_EVENT_MARGIN_FREE_INC;
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
        this.m_struct_prev_account.margin_free=this.m_struct_curr_account.margin_free;
     }
   else if(this.m_changed_margin_free_value<-this.m_control_margin_free_dec)
     {
      this.m_is_change_margin_free_dec=true;
      event=ACCOUNT_EVENT_MARGIN_FREE_DEC;
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
        this.m_struct_prev_account.margin_free=this.m_struct_curr_account.margin_free;
     }
  }
class=class="str">"cmt">//--- The margin level on an account in % exceeds the specified change value +/-
  if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_MARGIN_LEVEL))
  {
   this.m_changed_margin_level_value=this.m_struct_curr_account.margin_level-this.m_struct_prev_account.margin_level;
   if(this.m_changed_margin_level_value>this.m_control_margin_level_inc)
     {
      this.m_is_change_margin_level_inc=true;
      event=ACCOUNT_EVENT_MARGIN_LEVEL_INC;

「保证金三类变动的事件触发分支」

账户监控类里,保证金相关事件分三块:保证金水平(margin_level)、挂单初始保证金(margin_initial)、持仓维持保证金(margin_maintenance)。每一块都先判断对应事件 flag 是否开启,再比较当前值与上一帧的差值是否越过增减阈值。 以 margin_level 为例,若 m_changed_margin_level_value 小于负的 m_control_margin_level_dec,说明保证金水平向下跳变超阈,置 m_is_change_margin_level_dec=true 并派发 ACCOUNT_EVENT_MARGIN_LEVEL_DEC 事件;事件入列前用 Search()==WRONG_VALUE 去重,入列成功才把 prev 快照刷新为 curr,避免重复触发。外汇与贵金属杠杆高,margin_level 骤降往往预示净值的快速回撤,需结合品种波动评估强平概率。 margin_initial 与 margin_maintenance 逻辑同构:前者监控挂单占用的保证金额度增减,后者监控持仓最低维持保证金增减。阈值分别由 m_control_margin_initial_inc/decm_control_margin_maintenance_inc/dec 控制,越过即写对应 bool 并压入变更列表。 下面这段是 margin_initial 分支的原文片段,可直贴 MT5 头文件类里核对: if(this.m_changed_margin_initial_value>this.m_control_margin_initial_inc) { this.m_is_change_margin_initial_inc=true; event=ACCOUNT_EVENT_MARGIN_INITIAL_INC; if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event)) this.m_struct_prev_account.margin_initial=this.m_struct_curr_account.margin_initial; } else if(this.m_changed_margin_initial_value<-this.m_control_margin_initial_dec) { this.m_is_change_margin_initial_dec=true; event=ACCOUNT_EVENT_MARGIN_INITIAL_DEC; if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event)) this.m_struct_prev_account.margin_initial=this.m_struct_curr_account.margin_initial; }

MQL5 / C++
if(this.m_changed_margin_initial_value>this.m_control_margin_initial_inc)
  {
   this.m_is_change_margin_initial_inc=true;
   event=ACCOUNT_EVENT_MARGIN_INITIAL_INC;
   if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
     this.m_struct_prev_account.margin_initial=this.m_struct_curr_account.margin_initial;
  }
else if(this.m_changed_margin_initial_value<-this.m_control_margin_initial_dec)
  {
   this.m_is_change_margin_initial_dec=true;
   event=ACCOUNT_EVENT_MARGIN_INITIAL_DEC;
   if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
     this.m_struct_prev_account.margin_initial=this.m_struct_curr_account.margin_initial;
  }

维护保证金与资产负债的异动捕获

账户状态监控里,维护保证金的下降触发靠 m_changed_margin_maintenance_value 与阈值 m_control_margin_maintenance_dec 比较,小于负阈值才置 m_is_change_margin_maintenance_dec 并写入 ACCOUNT_EVENT_MARGIN_MAINTENANCE_DEC 事件。 资产端的判定逻辑类似:用当前 assets 减上一帧 assets 得到差值,超过 m_control_assets_inc 记增加事件,低于负 m_control_assets_dec 记减少事件;列表里没有该事件才添加,并同步上一帧快照。 负债端同理,差值越界时置 m_is_change_liabilities_inc_dec,事件入列前先 Search 避免重复。外汇与贵金属杠杆高,这类负债突增可能预示保证金占用恶化,建议把阈值设成可配参数在 MT5 里跑一轮观察。

MQL5 / C++
      this.m_struct_prev_account.margin_maintenance=this.m_struct_curr_account.margin_maintenance;
      }
      else if(this.m_changed_margin_maintenance_value<-this.m_control_margin_maintenance_dec)
        {
         this.m_is_change_margin_maintenance_dec=true;
         event=ACCOUNT_EVENT_MARGIN_MAINTENANCE_DEC;
         if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
            this.m_struct_prev_account.margin_maintenance=this.m_struct_curr_account.margin_maintenance;
        }
     }
class=class="str">"cmt">//--- The current assets on an account exceed the specified change value +/-
   if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_ASSETS))
     {
      this.m_changed_assets_value=this.m_struct_curr_account.assets-this.m_struct_prev_account.assets;
      if(this.m_changed_assets_value>this.m_control_assets_inc)
        {
         this.m_is_change_assets_inc=true;
         event=ACCOUNT_EVENT_ASSETS_INC;
         if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
            this.m_struct_prev_account.assets=this.m_struct_curr_account.assets;
        }
      else if(this.m_changed_assets_value<-this.m_control_assets_dec)
        {
         this.m_is_change_assets_dec=true;
         event=ACCOUNT_EVENT_ASSETS_DEC;
         if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
            this.m_struct_prev_account.assets=this.m_struct_curr_account.assets;
        }
     }
class=class="str">"cmt">//--- The current liabilities on an account exceed the specified change value +/-
   if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_LIABILITIES))
     {
      this.m_changed_liabilities_value=this.m_struct_curr_account.liabilities-this.m_struct_prev_account.liabilities;
      if(this.m_changed_liabilities_value>this.m_control_liabilities_inc)
        {
         this.m_is_change_liabilities_inc=true;
         event=ACCOUNT_EVENT_LIABILITIES_INC;
         if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
            this.m_struct_prev_account.liabilities=this.m_struct_curr_account.liabilities;
        }
      else if(this.m_changed_liabilities_value<-this.m_control_liabilities_dec)
        {
         this.m_is_change_liabilities_dec=true;

◍ 盯住账户冻结佣金跳变

账户监控类里对冻结佣金(comission_blocked)的增减做了阈值判定:当前值减上一帧值超过正向控制量 m_control_comission_blocked_inc,就置 m_is_change_comission_blocked_inc 为真并写入 ACCOUNT_EVENT_COMISSION_BLOCKED_INC 事件;反向跌破负阈值则记 DEC 事件。 事件入列前用 m_list_changes.Search(event)==WRONG_VALUE 查重,避免同一帧重复添加;一旦添加成功,就把 prev 结构里的冻结佣金刷新为当前值,下一帧差值重新从零累计。 对外取数走三个 GetList 重载:无参返回全部账户对象裸列表,带 ENUM_ACCOUNT_PROP_INTEGER 或 DOUBLE 的版本交给 CSelect::ByAccountProperty 按相等或比较模式过滤。写个 EA 把冻结佣金事件打印到日志,能直接看券商在什么时点锁了多少佣金,外汇和贵金属账户杠杆高,冻结变动往往预示保证金重算,属高风险信号,需结合净值一起看。

MQL5 / C++
event=ACCOUNT_EVENT_LIABILITIES_DEC;
if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
   this.m_struct_prev_account.liabilities=this.m_struct_curr_account.liabilities;
}
 }
class=class="str">"cmt">//--- The current sum of blocked commissions on an account exceeds the specified change value +/-
 if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_COMISSION_BLOCKED))
 {
   this.m_changed_comission_blocked_value=this.m_struct_curr_account.comission_blocked-this.m_struct_prev_account.comission_blocked;
   if(this.m_changed_comission_blocked_value>this.m_control_comission_blocked_inc)
     {
      this.m_is_change_comission_blocked_inc=true;
      event=ACCOUNT_EVENT_COMISSION_BLOCKED_INC;
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.comission_blocked=this.m_struct_curr_account.comission_blocked;
     }
   else if(this.m_changed_comission_blocked_value<-this.m_control_comission_blocked_dec)
     {
      this.m_is_change_comission_blocked_dec=true;
      event=ACCOUNT_EVENT_COMISSION_BLOCKED_DEC;
      if(this.m_list_changes.Search(event)==WRONG_VALUE && this.m_list_changes.Add(event))
         this.m_struct_prev_account.comission_blocked=this.m_struct_curr_account.comission_blocked;
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">public:
class=class="str">"cmt">//--- Return the full account collection list "as is"
   CArrayObj       *GetList(class="type">void)                                                                                { class="kw">return &this.m_list_accounts;                                                                               }
class=class="str">"cmt">//--- Return the list by selected(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting the compared criterion
   CArrayObj       *GetList(ENUM_ACCOUNT_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode=EQUAL)   { class="kw">return CSelect::ByAccountProperty(this.GetList(),class="kw">property,value,mode);}
   CArrayObj       *GetList(ENUM_ACCOUNT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode=EQUAL)  { class="kw">return CSelect::ByAccountProperty(this.GetList(),class="kw">property,value,mode);}

「账户事件与筛选接口的返回方法」

在账户对象封装里,GetList 方法通过 CSelect::ByAccountProperty 按字符串属性做筛选,mode 默认 EQUAL,也可传其他比较类型。它直接返回 CArrayObj 指针,调用方拿去遍历即可,不用自己写匹配逻辑。 IndexCurrentAccount 返回当前账户在列表里的下标,IsAccountEvent 用布尔标记账户数据是否发生过事件。这两个都是 const 方法,说明只读、不改动内部状态,适合在高频刷新里反复调用。 GetEventCode 回传 m_change_code 表示的事件代码,GetListChanges 返回 CArrayInt 指针记录变更序号列表,GetEvent 按 shift 取具体账户事件枚举。注意 GetEvent 的 shift 默认 WRONG_VALUE,不传参时可能拿到非法下标对应的结果。 SetSymbol 只有一行赋值,把外部传来的品种名写进 m_symbol。外汇和贵金属品种切换频繁,调它之前先确认 symbol 字符串有效,这类市场杠杆高、点差跳变快,错误品种可能导致后续计算全部偏移。

MQL5 / C++
CArrayObj *GetList(ENUM_ACCOUNT_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::ByAccountProperty(this.GetList(),class="kw">property,value,mode);}
class=class="str">"cmt">//--- Return the(class="num">1) current account object index, (class="num">2) the flag of the event occurred in the account data, (class="num">3) event type
class="type">int IndexCurrentAccount(class="type">void) class="kw">const { class="kw">return this.m_index_current; }
class="type">bool IsAccountEvent(class="type">void) class="kw">const { class="kw">return this.m_is_account_event; }
class=class="str">"cmt">//--- Return the(class="num">1) object account event code, (class="num">2) event list, (class="num">3) account event by its number in the list
class="type">int GetEventCode(class="type">void) class="kw">const { class="kw">return this.m_change_code; }
CArrayInt *GetListChanges(class="type">void) { class="kw">return &this.m_list_changes; }
ENUM_ACCOUNT_EVENT GetEvent(class="kw">const class="type">int shift=WRONG_VALUE);
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the current symbol
class="type">void SetSymbol(class="kw">const class="type">class="kw">string symbol) { this.m_symbol=symbol; }

账户集合类的接口与杠杆变动追踪

在 MT5 的 EA 架构里,把多账户状态收拢到一个集合类来管理,比散落全局变量更利于扩展。下面这段声明展示了 CAccountsCollection 暴露的核心方法群:取符号、绑图表 ID、增删对象、落盘与加载,以及刷新账户事件。 其中杠杆变动的追踪接口最值得盯盘者留心。GetValueChangedLeverage 返回 m_changed_leverage_value(long 类型),记录本次杠杆相对上值的绝对变化量;IsIncreaseLeverage 则返回 m_is_change_leverage_inc 布尔,用来判定杠杆是上调还是下调方向。 外汇与贵金属杠杆调整往往预示风控取向变化,属高风险信号,只能作为概率性参考,不能单独用作下单依据。把这些接口接进你的面板,可能在经纪商偷偷调杠杆时第一时间弹提示。

MQL5 / C++
class="type">class="kw">string GetSymbol(class="type">void) class="kw">const { class="kw">return this.m_symbol; }
class=class="str">"cmt">//--- Set the control program chart ID
class="type">void SetChartID(class="kw">const class="type">long id) { this.m_chart_id=id; }
class=class="str">"cmt">//--- Constructor, destructor
CAccountsCollection();
~CAccountsCollection();
class=class="str">"cmt">//--- Add the account object to the list
class="type">bool AddToList(CAccount* account);
class=class="str">"cmt">//--- (class="num">1) Save account objects from the list to the files
class=class="str">"cmt">//--- (class="num">2) Save account objects from the files to the list
class="type">bool SaveObjects(class="type">void);
class="type">bool LoadObjects(class="type">void);
class=class="str">"cmt">//--- Return the account event description
class="type">class="kw">string EventDescription(class="kw">const ENUM_ACCOUNT_EVENT event);
class=class="str">"cmt">//--- Update the current account data
class="type">void Refresh(class="type">void);

class=class="str">"cmt">//--- Get and set the parameters of tracked changes
class=class="str">"cmt">//--- Leverage:
class=class="str">"cmt">//--- (class="num">1) Leverage change value, (class="num">2) Leverage increase flag, (class="num">3) Leverage decrease flag
class="type">long GetValueChangedLeverage(class="type">void) class="kw">const { class="kw">return this.m_changed_leverage_value; }
class="type">bool IsIncreaseLeverage(class="type">void) class="kw">const { class="kw">return this.m_is_change_leverage_inc; }

◍ 账户风控状态的取值接口

在 MQL5 的账户状态封装类里,这一类内联方法专门暴露「本次事件相对上一次快照发生了什么变化」。它们全部标记为 const,意味着只读取成员变量、不修改对象内部状态,适合在 OnTick 或定时器里高频轮询。 下面这段截取了杠杆、挂单数与交易权限三组接口。注意挂单数用的是 int 返回变更值,而杠杆与交易开关都用 bool 返回增减标志——说明前者能拿到具体变动数量,后者只给方向信号。 实盘里若发现 IsOffTradeAllowed() 突然返回 true,代表服务器侧禁交易生效,EA 必须立刻停止发单。外汇与贵金属杠杆波动大,这类状态翻转往往伴随流动性骤紧,属高风险情景,策略应预设熔断而非硬扛。 把小布盯盘接进去时,可直接复用这些 getter 做差异告警:比起自己解析 AccountInfoInteger,封装类把「变化」而非「绝对值」吐出来,省掉一层比对代码。

MQL5 / C++
class="type">bool IsDecreaseLeverage(class="type">void) class="kw">const { class="kw">return this.m_is_change_leverage_dec; }
class=class="str">"cmt">//--- Number of active pending orders:
class=class="str">"cmt">//--- (class="num">1) Change value, (class="num">2) Increase flag, (class="num">3) Decrease flag
class="type">int GetValueChangedLimitOrders(class="type">void) class="kw">const { class="kw">return this.m_changed_limit_orders_value; }
class="type">bool IsIncreaseLimitOrders(class="type">void) class="kw">const { class="kw">return this.m_is_change_limit_orders_inc; }
class="type">bool IsDecreaseLimitOrders(class="type">void) class="kw">const { class="kw">return this.m_is_change_limit_orders_dec; }
class=class="str">"cmt">//--- Trading on an account:
class=class="str">"cmt">//--- (class="num">1) The flag allowing to trade for the current account, (class="num">2) The flag prohibiting trading for the current account from the server side
class="type">bool IsOnTradeAllowed(class="type">void) class="kw">const { class="kw">return this.m_is_change_trade_allowed_on; }
class="type">bool IsOffTradeAllowed(class="type">void) class="kw">const { class="kw">return this.m_is_change_trade_allowed_off; }
class=class="str">"cmt">//--- Auto trading on an account:
class=class="str">"cmt">//--- (class="num">1) The flag allowing to trade for an EA, (class="num">2) The flag prohibiting trading for an EA from the server side
class="type">bool IsOnTradeExpert(class="type">void) class="kw">const { class="kw">return this.m_is_change_trade_expert_on; }

「账户余额与信用变化的类内钩子」

在 EA 监控账户事件时,通常会把「是否暂停跟单」和「余额/信用变动」封装成一组类方法,方便主循环里直接读标志位而不是重复算差值。下面这段接口就暴露了两类状态:一个是专家信号是否被临时关闭,另一个是余额相对阈值的增减判定。 IsOffTradeExpert() 返回 m_is_change_trade_expert_off,当这个值被置 true,意味着外部可能切了信号源或手动禁用了自动交易,策略应当倾向跳过新单。余额部分用 SetControlBalanceInc/Dec 写入上下限,内部统一走 ::fabs(value) 取绝对值,避免传负阈值导致逻辑反转。 GetValueChangedBalance() 给出本次实际变动量,IsIncreaseBalance() / IsDecreaseBalance() 则分别返回是否突破你设的增、减阈值。信用部分只暴露了 GetValueChangedCredit() 及增减标志,没有写设置接口,说明信用阈值可能在构造或别处固化。 实盘里把 m_control_balance_inc 设成账户净值 0.5% 对应的金额,回测可见当余额单跳超该值,IsIncreaseBalance() 会在下一 tick 返回 true,此时去查日志大概率对应平仓盈余或入金,外汇与贵金属杠杆品种须警惕此类跳变伴随的滑点风险。

MQL5 / C++
class="type">bool IsOffTradeExpert(class="type">void) class="kw">const { class="kw">return this.m_is_change_trade_expert_off; }
class=class="str">"cmt">//--- Balance:
class=class="str">"cmt">//--- setting the tracked value of the balance(class="num">1) growth, (class="num">2) decrease
class=class="str">"cmt">//--- getting(class="num">3) the balance change value,
class=class="str">"cmt">//--- getting the flag of the balance change exceeding the(class="num">4) growth value, (class="num">5) decrease value
class="type">void SetControlBalanceInc(class="kw">const class="type">class="kw">double value) { this.m_control_balance_inc=::fabs(value); }
class="type">void SetControlBalanceDec(class="kw">const class="type">class="kw">double value) { this.m_control_balance_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedBalance(class="type">void) class="kw">const { class="kw">return this.m_changed_balance_value; }
class="type">bool IsIncreaseBalance(class="type">void) class="kw">const { class="kw">return this.m_is_change_balance_inc; }
class="type">bool IsDecreaseBalance(class="type">void) class="kw">const { class="kw">return this.m_is_change_balance_dec; }
class=class="str">"cmt">//--- Credit:
class=class="str">"cmt">//--- getting(class="num">1) the credit change value, (class="num">2) credit increase flag, (class="num">3) decrease flag
class="type">class="kw">double GetValueChangedCredit(class="type">void) class="kw">const { class="kw">return this.m_changed_credit_value; }

账户权益与利润的变动追踪接口

在 MT5 的 EA 或指标类里,想盯住账户净值和信用值的增减,得先有一组 getter/setter 把阈值和状态位管起来。下面这段类方法就干了这件事:用 bool 型标志位记录「本次 tick 是否发生信用增、信用减、利润增、利润减」,再用 double 存实际变动量。 设置利润监控阈值时,代码里调了 ::fabs(value) 取绝对值。这意味着你传负数进去也不会让阈值变成负的——比如 SetControlProfitDec(-50.0) 实际生效的是 50.0 美元(或报价货币单位)的回撤监控线。 GetValueChangedProfit() 直接返回 m_changed_profit_value,这个数就是最近一次权益快照里利润的真实跳变幅度。配合 IsIncreaseProfit() / IsDecreaseProfit() 的两个布尔标志,你可以在 OnTick 里写判断:当 IsIncreaseProfit() 为真且 GetValueChangedProfit() 大于你设的步进,就倾向认为短线动量对持仓有利——当然外汇和贵金属杠杆高,这种信号只作辅助,不代表方向确定。 信用值(credit)那两个 IsIncreaseCredit / IsDecreaseCredit 同理,常用于跟踪赠金或隔夜费导致的非交易性余额变动,避免和真实浮动盈亏混淆。

MQL5 / C++
class="type">bool IsIncreaseCredit(class="type">void) class="kw">const { class="kw">return this.m_is_change_credit_inc; }
class="type">bool IsDecreaseCredit(class="type">void) class="kw">const { class="kw">return this.m_is_change_credit_dec; }
class=class="str">"cmt">//--- Profit:
class=class="str">"cmt">//--- setting the tracked profit(class="num">1) growth, (class="num">2) decrease value
class=class="str">"cmt">//--- getting the(class="num">3) profit change value,
class=class="str">"cmt">//--- getting the flag of the profit change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlProfitInc(class="kw">const class="type">class="kw">double value) { this.m_control_profit_inc=::fabs(value); }
class="type">void SetControlProfitDec(class="kw">const class="type">class="kw">double value) { this.m_control_profit_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedProfit(class="type">void) class="kw">const { class="kw">return this.m_changed_profit_value; }
class="type">bool IsIncreaseProfit(class="type">void) class="kw">const { class="kw">return this.m_is_change_profit_inc; }
class="type">bool IsDecreaseProfit(class="type">void) class="kw">const { class="kw">return this.m_is_change_profit_dec; }
class=class="str">"cmt">//--- Equity:
class=class="str">"cmt">//--- setting the tracked equity(class="num">1) growth, (class="num">2) decrease value

◍ 权益与保证金变动的阈值捕获

在自研风控类里,权益(equity)和保证金(margin)的变动需要分开设阈值。给上涨和下跌各留一个控制值,用 fabs 取绝对值避免负数干扰,是 MT5 里常见的写法。 下面这段只截取了 equity 部分的函数定义,能看到设置增长/回撤阈值的 setter 直接写入成员变量 m_control_equity_inc / m_control_equity_dec,而 getter 则返回最近一次变动值 m_changed_equity_value 与方向标志 m_is_change_equity_inc / m_is_change_equity_dec。 保证金一侧的接口形态完全对称:SetControlMarginInc 同样用 ::fabs(value) 固化阈值。实盘里把 equity 阈值设成账户净值的 0.5%、margin 阈值设成已用保证金的 10%,可能在 5 分钟周期上较早抓到异常敞口波动;外汇与贵金属杠杆高,这类监控只作预警,不替代止损。

MQL5 / C++
class="type">void SetControlEquityInc(class="kw">const class="type">class="kw">double value) { this.m_control_equity_inc=::fabs(value); }
class="type">void SetControlEquityDec(class="kw">const class="type">class="kw">double value) { this.m_control_equity_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedEquity(class="type">void) class="kw">const { class="kw">return this.m_changed_equity_value; }
class="type">bool IsIncreaseEquity(class="type">void) class="kw">const { class="kw">return this.m_is_change_equity_inc; }
class="type">bool IsDecreaseEquity(class="type">void) class="kw">const { class="kw">return this.m_is_change_equity_dec; }
class="type">void SetControlMarginInc(class="kw">const class="type">class="kw">double value) { this.m_control_margin_inc=::fabs(value); }

「保证金与游离保证金的阈值跟踪接口」

在 MT5 的账户状态监控类里,保证金(margin)和游离保证金(free margin)的变化往往比权益曲线更早暴露风险。下面这组方法把「增减阈值」和「实际变动值」拆成了独立读写接口,方便策略在 OnTick 里做条件触发。 SetControlMarginDec 接收 double 参数后用 ::fabs 取绝对值再存入 m_control_margin_dec,意味着你传 -50 和 50 效果一样,都是把「保证金下降警戒线」设为 50。GetValueChangedMargin 则返回 m_changed_margin_value,即最近一次实际变动的绝对量。 IsIncreaseMargin 与 IsDecreaseMargin 是两个布尔开关,分别回传 m_is_change_margin_inc 和 m_is_change_margin_dec,用于判断本次变动是否越过了预设的增长或下降阈值。 游离保证金部分逻辑对称:SetControlMarginFreeInc / SetControlMarginFreeDec 设定自由保证金的增减跟踪值,同样经 fabs 处理;GetValueChangedMarginFree 拿回 m_changed_margin_free_value。外汇与贵金属杠杆高,这类接口若配合 0.1~0.5 手的小仓位回测,可能更快捕捉到爆仓前兆。 开 MT5 新建 EA,把上述方法塞进一个 CAccountMonitor 类,用 Print(GetValueChangedMarginFree()) 跑一晚模拟盘,就能验证阈值触发是否如预期。

MQL5 / C++
class="type">void SetControlMarginDec(class="kw">const class="type">class="kw">double value) { this.m_control_margin_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedMargin(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_value; }
class="type">bool IsIncreaseMargin(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_inc; }
class="type">bool IsDecreaseMargin(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_dec; }
class=class="str">"cmt">//--- Free margin:
class=class="str">"cmt">//--- setting the tracked free margin(class="num">1) growth, (class="num">2) decrease value
class=class="str">"cmt">//--- getting the(class="num">3) free margin change value,
class=class="str">"cmt">//--- getting the flag of the free margin change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlMarginFreeInc(class="kw">const class="type">class="kw">double value) { this.m_control_margin_free_inc=::fabs(value); }
class="type">void SetControlMarginFreeDec(class="kw">const class="type">class="kw">double value) { this.m_control_margin_free_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedMarginFree(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_free_value; }

跟踪保证金水平变动的接口怎么写

在 EA 或指标里监控账户风险,光看余额不够,得盯住自由保证金和保证金水平的变化方向。下面这组类方法把「增 / 减」拆成独立布尔量,回调时一眼就能判断当前是哪类变动触发了事件。 设置跟踪阈值时用了 ::fabs(value),也就是说无论你传正还是负,都按绝对值存。实际写风控逻辑时,SetControlMarginLevelInc(2.0) 和 SetControlMarginLevelDec(2.0) 要分别调,引擎不会自动对称。 GetValueChangedMarginLevel() 返回的是本次变动的具体数值,不是百分比也不是布尔。结合 IsIncreaseMarginLevel() / IsDecreaseMarginLevel() 两个标志,你可以在 OnTradeTransaction 里做条件分支:比如自由保证金水平跌破阈值且 IsDecreaseMarginLevel 为真,倾向减仓或告警。外汇和贵金属杠杆高,这类监控只能降低穿仓概率,不保证不爆仓。 别把布尔标志当事件本身 IsIncreaseMarginFree 只是返回内部标记位,它不代表「这一 tick 刚发生」,而是「自上次重置后曾发生」。若要捕捉瞬时跳变,得自己在调用后清标志或比对 GetValueChangedMarginLevel 的前后差值。

MQL5 / C++
class="type">bool IsIncreaseMarginFree(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_free_inc; }
class="type">bool IsDecreaseMarginFree(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_free_dec; }
class=class="str">"cmt">//--- Margin level:
class=class="str">"cmt">//--- setting the tracked margin level(class="num">1) growth, (class="num">2) decrease value
class=class="str">"cmt">//--- getting the(class="num">3) margin level change value,
class=class="str">"cmt">//--- getting the flag of the margin level change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlMarginLevelInc(class="kw">const class="type">class="kw">double value) { this.m_control_margin_level_inc=::fabs(value); }
class="type">void SetControlMarginLevelDec(class="kw">const class="type">class="kw">double value) { this.m_control_margin_level_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedMarginLevel(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_level_value; }
class="type">bool IsIncreaseMarginLevel(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_level_inc; }
class="type">bool IsDecreaseMarginLevel(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_level_dec; }
class=class="str">"cmt">//--- Margin Call:
class=class="str">"cmt">//--- getting(class="num">1) Margin Call change value, (class="num">2) increase flag, (class="num">3) decrease flag

◍ 读懂保证金追保与止损平仓的变动接口

在 MT5 的账户状态监听类里,追保(Margin Call)和止损平仓(Stop Out)各自暴露了三组只读接口:变动数值、是否增加、是否减少。实盘里经纪商调整保证金阈值时,这些标志位会翻转,EA 可借此判断风险线是被放宽还是收紧。 double GetValueChangedMarginCall(void) const { return this.m_changed_margin_so_call_value; } // 返回追保保证金的变动值(绝对值差),无变动时为 0 bool IsIncreaseMarginCall(void) const { return this.m_is_change_margin_so_call_inc; } // 若追保线较上次观测上调,返回 true bool IsDecreaseMarginCall(void) const { return this.m_is_change_margin_so_call_dec; } // 若追保线下调,返回 true Stop Out 部分结构完全一致:GetValueChangedMarginStopOut 取变动值,IsIncreaseMarginStopOut / IsDecreasMarginStopOute 取增减标志。注意原文末个函数名拼成了 IsDecreasMarginStopOute,编译前需改回 IsDecreaseMarginStopOut,否则链接报错。 挂单保证金占用(Guarantee sum for pending orders)走的是写入控制:SetControlMarginInitialInc 用 fabs 把传入值转绝对值存进 m_control_margin_initial_inc。外汇与贵金属杠杆高,经纪商临时改挂单预留保证金可能引发批量拒单,建议用 OnTimer 每秒比对一次这几个接口。

MQL5 / C++
class="type">class="kw">double GetValueChangedMarginCall(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_so_call_value; }
class="type">bool IsIncreaseMarginCall(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_so_call_inc; }
class="type">bool IsDecreaseMarginCall(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_so_call_dec; }
class=class="str">"cmt">//--- Margin StopOut:
class=class="str">"cmt">//--- getting(class="num">1) Margin StopOut change value, (class="num">2) increase flag, (class="num">3) decrease flag
class="type">class="kw">double GetValueChangedMarginStopOut(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_so_so_value; }
class="type">bool IsIncreaseMarginStopOut(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_so_so_inc; }
class="type">bool IsDecreasMarginStopOute(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_so_so_dec; }
class=class="str">"cmt">//--- Guarantee sum for pending orders:
class=class="str">"cmt">//--- setting the tracked value of the reserved funds for providing the guarantee sum for pending orders(class="num">1) growth, (class="num">2) decrease
class=class="str">"cmt">//--- getting the change value of the(class="num">3) reserved funds for providing the guarantee sum,
class=class="str">"cmt">//--- getting the flag of the reserved funds for providing the guarantee sum for pending orders exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlMarginInitialInc(class="kw">const class="type">class="kw">double value) { this.m_control_margin_initial_inc=::fabs(value); }

「盯住初始与维持保证金的变动阈值」

在 MT5 的账户事件封装类里,保证金维度的跟踪被拆成了「初始保证金」和「维持保证金」两条线。前者对应开仓时冻结的预付款,后者对应持仓期间账户必须保留的最低担保资金,二者分别设置增长、下降的控制阈值。 下方代码展示了核心存取接口:设置函数统一用 ::fabs(value) 取绝对值写入成员变量,避免负值污染阈值;读取函数则返回最近一次变动数值或布尔标志。 [CODE] void SetControlMarginInitialDec(const double value) { this.m_control_margin_initial_dec=::fabs(value); } double GetValueChangedMarginInitial(void) const { return this.m_changed_margin_initial_value; } bool IsIncreaseMarginInitial(void) const { return this.m_is_change_margin_initial_inc; } bool IsDecreaseMarginInitial(void) const { return this.m_is_change_margin_initial_dec; } void SetControlMarginMaintenanceInc(const double value) { this.m_control_margin_maintenance_inc=::fabs(value); } void SetControlMarginMaintenanceDec(const double value) { this.m_control_margin_maintenance_dec=::fabs(value); } double GetValueChangedMarginMaintenance(void) const { return this.m_changed_margin_maintenance_value; } bool IsIncreaseMarginMaintenance(void) const { return this.m_is_change_margin_maintenance_inc; } [/CODE] 逐行拆解:第1行 SetControlMarginInitialDec 设定初始保证金下降阈值,fabs 保证传入负数也按正数处理。第2~4行分别取回初始保证金变动值、判断其是否增长或下降。第5~8行对维持保证金做同样封装,Inc 管增长、Dec 管下降。 实盘里若把 Dec 阈值设为账户净值 0.5%(例如 5000 美元账户填 25),当维持保证金较前一刻减少超过该值,IsDecreaseMarginMaintenance 会翻真,可能暗示部分仓位已被平掉或保证金释放。外汇与贵金属杠杆高,这类变动常发生在剧烈波动后,建议直接挂到 EA 的 OnTradeTransaction 里做二次确认。

MQL5 / C++
class="type">void SetControlMarginInitialDec(class="kw">const class="type">class="kw">double value) { this.m_control_margin_initial_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedMarginInitial(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_initial_value; }
class="type">bool IsIncreaseMarginInitial(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_initial_inc; }
class="type">bool IsDecreaseMarginInitial(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_initial_dec; }
class="type">void SetControlMarginMaintenanceInc(class="kw">const class="type">class="kw">double value) { this.m_control_margin_maintenance_inc=::fabs(value); }
class="type">void SetControlMarginMaintenanceDec(class="kw">const class="type">class="kw">double value) { this.m_control_margin_maintenance_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedMarginMaintenance(class="type">void) class="kw">const { class="kw">return this.m_changed_margin_maintenance_value; }
class="type">bool IsIncreaseMarginMaintenance(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_maintenance_inc; }

账户资产与负债变动的跟踪接口

在 MT5 的 EA 或指标类里,如果要监控经纪商那边的保证金维持水平、资产与负债波动,通常会封装一组 getter / setter。下面这段就是针对「维持保证金下降」「资产增减」「负债增减」的最小可用接口。 IsDecreaseMarginMaintenance() 直接返回内部布尔量 m_is_change_margin_maintenance_dec,用来判断维持保证金是否较前一刻出现下降。外汇和贵金属杠杆高,维持保证金被动下调往往意味着净值回撤或持仓结构变化,需结合净值曲线一起看。 资产侧用 SetControlAssetsInc / SetControlAssetsDec 写入阈值,内部统一走 ::fabs(value) 取绝对值,避免传负参数导致监控失效。GetValueChangedAssets() 给出实际变动量,IsIncreaseAssets() / IsDecreaseAssets() 给出是否越阈的布尔标志。 负债侧逻辑对称:SetControlLiabilitiesInc() 同样以 ::fabs 存阈值到 m_control_liabilities_inc。实际跑的时候,把这组接口接进 OnTick,就能在负债增幅超阈时触发告警或减仓逻辑,但任何触发都只是概率性风控,不保证避损。

MQL5 / C++
class="type">bool IsDecreaseMarginMaintenance(class="type">void) class="kw">const { class="kw">return this.m_is_change_margin_maintenance_dec; }
class=class="str">"cmt">//--- Assets:
class=class="str">"cmt">//--- setting the tracked value of the assets(class="num">1) growth, (class="num">2) decrease
class=class="str">"cmt">//--- getting(class="num">3) the assets change value,
class=class="str">"cmt">//--- getting the flag of the change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlAssetsInc(class="kw">const class="type">class="kw">double value) { this.m_control_assets_inc=::fabs(value); }
class="type">void SetControlAssetsDec(class="kw">const class="type">class="kw">double value) { this.m_control_assets_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedAssets(class="type">void) class="kw">const { class="kw">return this.m_changed_assets_value; }
class="type">bool IsIncreaseAssets(class="type">void) class="kw">const { class="kw">return this.m_is_change_assets_inc; }
class="type">bool IsDecreaseAssets(class="type">void) class="kw">const { class="kw">return this.m_is_change_assets_dec; }
class=class="str">"cmt">//--- Liabilities:
class=class="str">"cmt">//--- setting the tracked value of the liabilities(class="num">1) growth, (class="num">2) decrease
class=class="str">"cmt">//--- getting(class="num">3) the liabilities change value,
class=class="str">"cmt">//--- getting the flag of the liabilities change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlLiabilitiesInc(class="kw">const class="type">class="kw">double value) { this.m_control_liabilities_inc=::fabs(value); }

◍ 负债与冻结佣金的阈值跟踪接口

在 MT5 账户状态监控类里,负债变动和冻结佣金变动各有一组 setter / getter,用来设定触发阈值并回读实际变化。负债项用 fabs 包裹入参,意味着无论传正传负,控制阈值都按绝对值存,避免误传负号导致监控失效。 SetControlLiabilitiesDec 把递减阈值写进 m_control_liabilities_dec;GetValueChangedLiabilities 返回 m_changed_liabilities_value,即本次快照相对上次的负债差值;IsIncreaseLiabilities / IsDecreaseLiabilities 分别回读 m_is_change_liabilities_inc 与 m_is_change_liabilities_dec 两个布尔标志,告诉你变动方向是否越过了设定线。 冻结佣金部分逻辑同构:SetControlComissionBlockedInc / Dec 设定增减阈值,GetValueChangedComissionBlocked 回读 m_changed_comission_blocked_value,IsIncreaseComissionBlocked 看 m_is_change_comission_blocked_inc。外汇与贵金属账户在极端波动时负债和冻结佣金可能瞬时跳变,用这组接口做 EA 风控,能在保证金告警前先捕获异常。 开 MT5 把下面代码塞进你的 CAccount 类,编译后打印 GetValueChangedLiabilities 返回值,就能验证当前品种在滑点行情下负债跟踪是否灵敏。

MQL5 / C++
class="type">void SetControlLiabilitiesDec(class="kw">const class="type">class="kw">double value) { this.m_control_liabilities_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedLiabilities(class="type">void) class="kw">const { class="kw">return this.m_changed_liabilities_value; }
class="type">bool IsIncreaseLiabilities(class="type">void) class="kw">const { class="kw">return this.m_is_change_liabilities_inc; }
class="type">bool IsDecreaseLiabilities(class="type">void) class="kw">const { class="kw">return this.m_is_change_liabilities_dec; }
class=class="str">"cmt">//--- Blocked commissions:
class=class="str">"cmt">//--- setting the tracked blocked commissions(class="num">1) growth, (class="num">2) decrease value
class=class="str">"cmt">//--- getting(class="num">3) the blocked commissions change value,
class=class="str">"cmt">//--- getting the flag of the tracked commissions change exceeding the(class="num">4) growth, (class="num">5) decrease value
class="type">void SetControlComissionBlockedInc(class="kw">const class="type">class="kw">double value) { this.m_control_comission_blocked_inc=::fabs(value); }
class="type">void SetControlComissionBlockedDec(class="kw">const class="type">class="kw">double value) { this.m_control_comission_blocked_dec=::fabs(value); }
class="type">class="kw">double GetValueChangedComissionBlocked(class="type">void) class="kw">const { class="kw">return this.m_changed_comission_blocked_value; }
class="type">bool IsIncreaseComissionBlocked(class="type">void) class="kw">const { class="kw">return this.m_is_change_comission_blocked_inc; }

「账户事件回溯与文本描述的取数链路」

在 CAccountsCollection 里,GetEvent 按 shift 倒序取账户事件:列表为空直接返回 ACCOUNT_EVENT_NO_EVENT,否则用 total-shift-1 定位,越界则取最后一条。 实际验证时把 shift 传 WRONG_VALUE(-1)就能拿到最新事件,这在盯盘面板刷新时很常用。 EventDescription 先查 m_list_accounts 总数,空则报双语错误;再取当前账户指针,拿不到也报错。 描述里的精度 dg 由 MarginSOMode 决定:金钱模式用账户币种小数位,否则固定 2 位;stop-out 级别单位按 IsPercentsForSOLevels 在 '%' 与币种间切换。外汇和贵金属杠杆波动大,这类账户数据读取错误可能让风控逻辑失效,属高风险环节。

MQL5 / C++
class="type">bool IsDecreaseComissionBlocked(class="type">void) class="kw">const { class="kw">return this.m_is_change_comission_blocked_dec; }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the account event by its number in the list               |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_ACCOUNT_EVENT CAccountsCollection::GetEvent(class="kw">const class="type">int shift=WRONG_VALUE)
  {
   class="type">int total=this.m_list_changes.Total();
   if(total==class="num">0)
      class="kw">return ACCOUNT_EVENT_NO_EVENT;
   class="type">int index=(shift<class="num">0 || shift>total-class="num">1 ? total-class="num">1 : total-shift-class="num">1);
   class="type">int event=this.m_list_changes.At(index);
   class="kw">return ENUM_ACCOUNT_EVENT(event!=NULL ? event : ACCOUNT_EVENT_NO_EVENT);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return an account event description                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CAccountsCollection::EventDescription(class="kw">const ENUM_ACCOUNT_EVENT event)
  {
   class="type">int total=this.m_list_accounts.Total();
   if(total==class="num">0)
      class="kw">return(DFUN+TextByLanguage("Ошибка. Список изменений пуст","Error. List of changes is empty"));
   CAccount* account=this.m_list_accounts.At(this.m_index_current);
   if(account==NULL)
      class="kw">return(DFUN+TextByLanguage("Ошибка. Не удалось получить данные аккаунта","Error. Failed to get account data"));
   class="kw">const class="type">int dg=(account.MarginSOMode()==ACCOUNT_STOPOUT_MODE_MONEY ? (class="type">int)account.CurrencyDigits() : class="num">2);
   class="kw">const class="type">class="kw">string curency=" "+account.Currency();
   class="kw">const class="type">class="kw">string mode_lev=(account.IsPercentsForSOLevels() ? "%" : " "+curency);
   class="kw">return

账户事件类型的文字映射

在 MT5 的账户事件回调里,event 参数会返回不同的枚举值,直接用数字判断可读性很差,通常用一个嵌套三元表达式把它转成双语提示文本。 下面这段代码片段就是典型的映射写法:先判断 event 是否等于 ACCOUNT_EVENT_NO_EVENT,若是则返回「No event」;否则继续往下比对其余五种事件常量。 注意 TextByLanguage 的第一个参数是俄文、第二个是英文,若你的面板只面向中文用户,可改成中文串或自行封装一个中文函数,避免界面出现看不懂的语种。 外汇与贵金属账户事件受券商实时控制,交易允许状态可能在秒级翻转,这类映射只负责「显示」,真正的开关权限仍以券商下发的事件为准,高风险品种尤其要留意自动交易被静默关闭的可能。

MQL5 / C++
event==ACCOUNT_EVENT_NO_EVENT ? TextByLanguage("Нет события","No event") :
event==ACCOUNT_EVENT_TRADE_ALLOWED_ON ? TextByLanguage("Торговля на счёте разрешена","Trading on account allowed now") :
event==ACCOUNT_EVENT_TRADE_ALLOWED_OFF ? TextByLanguage("Торговля на счёте запрещена","Trading on account prohibited now") :
event==ACCOUNT_EVENT_TRADE_EXPERT_ON ? TextByLanguage("Автоторговля на счёте разрешена","Autotrading on account allowed now") :
event==ACCOUNT_EVENT_TRADE_EXPERT_OFF ? TextByLanguage("Автоторговля на счёте запрещена","Autotrade on account prohibited now") :
event==ACCOUNT_EVENT_LEVERAGE_INC ?

◍ 账户事件的中文提示串拼法

在 MT5 的账户事件监听里,杠杆、挂单上限、余额、净值的变化都要实时转成可读提示。下面这段三元表达式链就是按事件类型拼多语言文本,俄文与英文通过 TextByLanguage 自动切换。 事件常量如 ACCOUNT_EVENT_LEVERAGE_INC 代表杠杆上调,GetValueChangedLeverage() 取变动量,account.Leverage() 取当前杠杆,拼出来类似「Leverage increased by 5 (1:20)」。余额类则用 DoubleToString 控制小数位 dg 并附货币后缀 curency。 外汇与贵金属账户杠杆变动会直接放大敞口风险,这类提示串只做记录不预测方向;复制去 EA 里跑,能在日志里立刻看到经纪商改了哪项参数。

MQL5 / C++
TextByLanguage("Плечо увеличено на ","Leverage increased by ")+(class="type">class="kw">string)this.GetValueChangedLeverage()+" (class="num">1:"+(class="type">class="kw">string)account.Leverage()+")"
 :
event==ACCOUNT_EVENT_LEVERAGE_DEC
 ?
 TextByLanguage("Плечо уменьшено на ","Leverage decreased by ")+(class="type">class="kw">string)this.GetValueChangedLeverage()+" (class="num">1:"+(class="type">class="kw">string)account.Leverage()+")"
 :
event==ACCOUNT_EVENT_LIMIT_ORDERS_INC
 ?
 TextByLanguage("Максимально допустимое количество действующих отложенных ордеров увеличено на","Maximum allowable number of active pending orders increased by ")+(class="type">class="kw">string)this.GetValueChangedLimitOrders()+" ("+(class="type">class="kw">string)account.LimitOrders()+")"
 :
event==ACCOUNT_EVENT_LIMIT_ORDERS_DEC
 ?
 TextByLanguage("Максимально допустимое количество действующих отложенных ордеров уменьшено на","Maximum allowable number of active pending orders decreased by ")+(class="type">class="kw">string)this.GetValueChangedLimitOrders()+" ("+(class="type">class="kw">string)account.LimitOrders()+")"
 :
event==ACCOUNT_EVENT_BALANCE_INC
 ?
 TextByLanguage("Баланс счёта увеличен на ","Account balance increased by ")+::DoubleToString(this.GetValueChangedBalance(),dg)+curency+" ("+::DoubleToString(account.Balance(),dg)+curency+")"
 :
event==ACCOUNT_EVENT_BALANCE_DEC
 ?
 TextByLanguage("Баланс счёта уменьшен на ","Account balance decreased by ")+::DoubleToString(this.GetValueChangedBalance(),dg)+curency+" ("+::DoubleToString(account.Balance(),dg)+curency+")"
 :
event==ACCOUNT_EVENT_EQUITY_INC
 ?

「账户事件的多语种提示串拼接」

在 MT5 的账户事件回调里,常用三元表达式链把不同事件映射成一句人类可读的提示。上面这段就是针对权益、浮盈、信用、保证金等变动,分别用俄语与英语双语种输出变动值和账户当前值。 TextByLanguage() 负责按终端语言返回对应文案,DoubleToString(value, dg) 控制小数位,curency 是账户币种后缀。比如权益增加事件会拼出「Equity increased by 12.50 USD (1024.30 USD)」这样的串,便于直接推到日志或面板。 这类写法把 6 类事件(EQUITY_INC/DEC、PROFIT_INC/DEC、CREDIT_INC/DEC)压缩在一个表达式里,回测或实盘时若发现某类事件没提示,优先检查对应 GetValueChangedXxx() 是否返回了有效数值。外汇与贵金属账户波动频繁,这类事件提示能帮助快速定位保证金异动,但高杠杆下误读浮盈浮亏可能引发错误平仓,需结合上下文判断。

MQL5 / C++
TextByLanguage("Средства увеличены на ","Equity increased by ")+::DoubleToString(this.GetValueChangedEquity(),dg)+curency+" ("+::DoubleToString(account.Equity(),dg)+curency+")" :
event==ACCOUNT_EVENT_EQUITY_DEC ?
 TextByLanguage("Средства уменьшены на ","Equity decreased by ")+::DoubleToString(this.GetValueChangedEquity(),dg)+curency+" ("+::DoubleToString(account.Equity(),dg)+curency+")" :
event==ACCOUNT_EVENT_PROFIT_INC ?
 TextByLanguage("Текущая прибыль счёта увеличена на ","Account current profit increased by ")+::DoubleToString(this.GetValueChangedProfit(),dg)+curency+" ("+::DoubleToString(account.Profit(),dg)+curency+")" :
event==ACCOUNT_EVENT_PROFIT_DEC ?
 TextByLanguage("Текущая прибыль счёта уменьшена на ","Account current profit decreased by ")+::DoubleToString(this.GetValueChangedProfit(),dg)+curency+" ("+::DoubleToString(account.Profit(),dg)+curency+")" :
event==ACCOUNT_EVENT_CREDIT_INC ?
 TextByLanguage("Предоставленный кредит увеличен на ","Credit increased by ")+::DoubleToString(this.GetValueChangedCredit(),dg)+curency+" ("+::DoubleToString(account.Credit(),dg)+curency+")" :
event==ACCOUNT_EVENT_CREDIT_DEC ?
 TextByLanguage("Предоставленный кредит уменьшен на ","Credit decreased by ")+::DoubleToString(this.GetValueChangedCredit(),dg)+curency+" ("+::DoubleToString(account.Credit(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_INC ?

账户保证金事件的中文提示串拼法

在 MT5 的账户事件监听里,保证金相关的变动要用三目运算符按事件类型拼出双语提示。下面这段把占用保证金、自由保证金、保证金水平、挂单保证金的增减都覆盖了,俄语与英文通过 TextByLanguage 自动切换。 TextByLanguage("Залоговые средства увеличены на ","Margin increased by ")+::DoubleToString(this.GetValueChangedMargin(),dg)+curency+" ("+::DoubleToString(account.Margin(),dg)+curency+")" 上面这行对应 ACCOUNT_EVENT_MARGIN_INC:占用保证金增加,输出变化额与当前 account.Margin() 值,dg 控制小数位。 TextByLanguage("Свободные средства увеличены на ","Free margin increased by ")+::DoubleToString(this.GetValueChangedMarginFree(),dg)+curency+" ("+::DoubleToString(account.MarginFree(),dg)+curency+")" 这行处理 ACCOUNT_EVENT_MARGIN_FREE_INC,取 GetValueChangedMarginFree() 与 account.MarginFree(),自由保证金越高爆仓概率越低。 TextByLanguage("Уровень залоговых средств увеличен на ","Margin level increased by ")+::DoubleToString(this.GetValueChangedMarginLevel(),dg)+"%"+" ("+::DoubleToString(account.MarginLevel(),dg)+"%)" 保证金水平用百分比,对应 ACCOUNT_EVENT_MARGIN_LEVEL_INC,MT5 中 MarginLevel 低于 50% 时多数券商会强平。 TextByLanguage("Гарантийная сумма по отложенным ордерам увеличена на ","Guarantee sum for pending orders increased by ")+::DoubleToString(this.GetValueChangedMarginInitial(),dg)+curency+" ("+::DoubleToString(account.MarginInitial(),dg)+curency+")" 最后这行是挂单保证金额度增加(ACCOUNT_EVENT_MARGIN_INITIAL_INC),用 account.MarginInitial() 取当前挂单占用。外汇与贵金属杠杆高,保证金事件频繁,建议在 EA 里接这套提示做实时盯盘预警。

MQL5 / C++
TextByLanguage("Залоговые средства увеличены на ","Margin increased by ")+::DoubleToString(this.GetValueChangedMargin(),dg)+curency+" ("+::DoubleToString(account.Margin(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_DEC ?
 TextByLanguage("Залоговые средства уменьшены на ","Margin decreased by ")+::DoubleToString(this.GetValueChangedMargin(),dg)+curency+" ("+::DoubleToString(account.Margin(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_FREE_INC ?
 TextByLanguage("Свободные средства увеличены на ","Free margin increased by ")+::DoubleToString(this.GetValueChangedMarginFree(),dg)+curency+" ("+::DoubleToString(account.MarginFree(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_FREE_DEC ?
 TextByLanguage("Свободные средства уменьшены на ","Free margin decreased by ")+::DoubleToString(this.GetValueChangedMarginFree(),dg)+curency+" ("+::DoubleToString(account.MarginFree(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_LEVEL_INC ?
 TextByLanguage("Уровень залоговых средств увеличен на ","Margin level increased by ")+::DoubleToString(this.GetValueChangedMarginLevel(),dg)+"%"+" ("+::DoubleToString(account.MarginLevel(),dg)+"%)" :
event==ACCOUNT_EVENT_MARGIN_LEVEL_DEC ?
 TextByLanguage("Уровень залоговых средств уменьшен на ","Margin level decreased by ")+::DoubleToString(this.GetValueChangedMarginLevel(),dg)+"%"+" ("+::DoubleToString(account.MarginLevel(),dg)+"%)" :
event==ACCOUNT_EVENT_MARGIN_INITIAL_INC ?
 TextByLanguage("Гарантийная сумма по отложенным ордерам увеличена на ","Guarantee sum for pending orders increased by ")+::DoubleToString(this.GetValueChangedMarginInitial(),dg)+curency+" ("+::DoubleToString(account.MarginInitial(),dg)+curency+")" :

◍ 保证金与强平阈值的事件文案拼接

这段三元表达式链专门处理账户事件里和保证金、强平线相关的提示文本。每一支都按「变化量 + 当前值」的格式拼字符串,双语用 TextByLanguage 自动切换,俄语给本地用户、英语给国际终端。 以初始保证金减少为例:ACCOUNT_EVENT_MARGIN_INITIAL_DEC 触发时,先写「挂单保证金减少」,再用 DoubleToString(this.GetValueChangedMarginInitial(),dg) 把变动额转成字符串,dg 控制小数位;随后接货币符号 curency,括号里放 account.MarginInitial() 的实时值。 Margin Call 与 Stop Out 两支略有不同,单位走 mode_lev(通常是百分比),而不是货币。比如 ACCOUNT_EVENT_MARGIN_SO_CALL_INC 输出「Margin Call 水平增加 X%(当前 Y%)」,数字直接来自 GetValueChangedMarginCall 与 account.MarginSOCall()。 在 MT5 里把这段塞进账户监听类的消息函数,改 dg 或 mode_lev 就能调精度;外汇和贵金属杠杆高,保证金线与 Stop Out 变动可能瞬间影响持仓,验证时建议开模拟账户观察事件触发节奏。

MQL5 / C++
event==ACCOUNT_EVENT_MARGIN_INITIAL_DEC ?
   TextByLanguage("Гарантийная сумма по отложенным ордерам уменьшена на ","Guarantee sum for pending orders decreased by ")+::DoubleToString(this.GetValueChangedMarginInitial(),dg)+curency+" ("+::DoubleToString(account.MarginInitial(),dg)+curency+")" :
event==ACCOUNT_EVENT_MARGIN_MAINTENANCE_INC ?
   TextByLanguage("Гарантийная сумма по позициям увеличена на ","Guarantee sum for positions increased by ")+::DoubleToString(this.GetValueChangedMarginMaintenance(),dg)+curency+" ("+::DoubleToString(account.MarginMaintenance(),dg)+curency+")"     :
event==ACCOUNT_EVENT_MARGIN_MAINTENANCE_DEC ?
   TextByLanguage("Гарантийная сумма по позициям уменьшена на ","Guarantee sum for positions decreased by ")+::DoubleToString(this.GetValueChangedMarginMaintenance(),dg)+curency+" ("+::DoubleToString(account.MarginMaintenance(),dg)+curency+")"     :
event==ACCOUNT_EVENT_MARGIN_SO_CALL_INC       ?
   TextByLanguage("Увеличен уровень Margin Call на ","Increased Margin Call level by ")+::DoubleToString(this.GetValueChangedMarginCall(),dg)+mode_lev+" ("+::DoubleToString(account.MarginSOCall(),dg)+mode_lev+")"                                      :
event==ACCOUNT_EVENT_MARGIN_SO_CALL_DEC       ?
   TextByLanguage("Уменьшен уровень Margin Call на ","Decreased Margin Call level by ")+::DoubleToString(this.GetValueChangedMarginCall(),dg)+mode_lev+" ("+::DoubleToString(account.MarginSOCall(),dg)+mode_lev+")"                                     :
event==ACCOUNT_EVENT_MARGIN_SO_SO_INC         ?
   TextByLanguage("Увеличен уровень Stop Out на ","Increased Margin Stop Out level by ")+::DoubleToString(this.GetValueChangedMarginStopOut(),dg)+mode_lev+" ("+::DoubleToString(account.MarginSOSO(),dg)+mode_lev+")"                             :
event==ACCOUNT_EVENT_MARGIN_SO_SO_DEC         ?
   TextByLanguage("Уменьшен уровень Stop Out на ","Decreased Margin Stop Out level by ")+::DoubleToString(this.GetValueChangedMarginStopOut(),dg)+mode_lev+" ("+::DoubleToString(account.MarginSOSO(),dg)+mode_lev+")"                            :
event==ACCOUNT_EVENT_ASSETS_INC               ?

「账户事件的多语言变动播报」

在 MT5 的账户事件监控里,资产、负债与冻结佣金的增减都能被实时捕获并转成可读文本。下面这段三元表达式链就是按事件类型拼出双语(俄/英)提示,并把变动值与账户当前总值一并带出。 它依赖一组 GetValueChanged* 方法取本次变动量,再用 account.Assets()、account.Liabilities()、account.ComissionBlocked() 取账户存量。DoubleToString 的第二个参数 dg 控制小数位,通常设 2 即够用。 外汇与贵金属账户权益波动频繁,这类事件播报只能反映账面变动,不代表任何方向判断;杠杆品种高风险,实际净值可能瞬间反向。把下面代码贴进 EA 的 OnAccountEvent 分支,改 dg 就能直接看不同精度下的输出差异。

MQL5 / C++
TextByLanguage("Размер активов увеличен на ","Assets increased by ")+::DoubleToString(this.GetValueChangedAssets(),dg)+" ("+::DoubleToString(account.Assets(),dg)+")"
 :
event==ACCOUNT_EVENT_ASSETS_DEC
 ?
 TextByLanguage("Размер активов уменьшен на ","Assets decreased by ")+::DoubleToString(this.GetValueChangedAssets(),dg)+" ("+::DoubleToString(account.Assets(),dg)+")"
 :
event==ACCOUNT_EVENT_LIABILITIES_INC
 ?
 TextByLanguage("Размер обязательств увеличен на ","Liabilities increased by ")+::DoubleToString(this.GetValueChangedLiabilities(),dg)+" ("+::DoubleToString(account.Liabilities(),dg)+")"
 :
event==ACCOUNT_EVENT_LIABILITIES_DEC
 ?
 TextByLanguage("Размер обязательств уменьшен на ","Liabilities decreased by ")+::DoubleToString(this.GetValueChangedLiabilities(),dg)+" ("+::DoubleToString(account.Liabilities(),dg)+")"
 :
event==ACCOUNT_EVENT_COMISSION_BLOCKED_INC
 ?
 TextByLanguage("Размер заблокированных комиссий увеличен на ","Blocked commissions increased by ")+::DoubleToString(this.GetValueChangedComissionBlocked(),dg)+" ("+::DoubleToString(account.ComissionBlocked(),dg)+")"
 :
event==ACCOUNT_EVENT_COMISSION_BLOCKED_DEC
 ?
 TextByLanguage("Размер заблокированных комиссий уменьшен на ","Blocked commissions decreased by ")+::DoubleToString(this.GetValueChangedComissionBlocked(),dg)+" ("+::DoubleToString(account.ComissionBlocked(),dg)+")"
 :
::EnumToString(event)
 );

账户集合对象的初始化与刷新逻辑

CAccountsCollection 的构造函数把账户管理的基础环境一次性搭好:默认存储目录拼成 DIRECTORY+"Accounts",同时把当前图表 ID 与品种通过 ::Symbol()::ChartID() 抓进成员变量。列表清空后按登录号排序、类型标为 COLLECTION_ACCOUNT_ID,前一次账户结构体与行情 tick 结构体都用 ZeroMemory 置零,避免脏内存导致后续比对误触发。 构造收尾阶段会用 FolderCreate 在公共文件区建 Accounts 文件夹,失败仅打印错误不中断;随后 new 出当前账户对象并尝试加入列表,成功则 PrintShort 输出简况。最后调用 LoadObjects 从文件恢复历史账户,并把 m_index_current 设为当前索引。 Refresh 方法开头先把 m_is_account_event 复位为 false,若 m_index_current 等于 WRONG_VALUE 直接 return。这意味着账户事件标志由刷新函数统一管辖,外部不能假设构造后就处于事件待处理状态——在 MT5 里挂这类集合类时,第一次 Refresh 前的事件判断必然为假。 外汇与贵金属账户数据高频变动,这类集合若用于跨账户监控,需意识到多账户文件读写存在共享冲突可能,建议先在模拟环境验证目录权限与刷新频率。

MQL5 / C++
CAccountsCollection::CAccountsCollection(class="type">void) : m_folder_name(DIRECTORY+"Accounts"),
                      m_is_account_event(class="kw">false),
                      m_symbol(::Symbol()),
                      m_chart_id(::ChartID())
  {
   this.m_list_accounts.Clear();
   this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN);
   this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID);
   ::ZeroMemory(this.m_struct_prev_account);
   ::ZeroMemory(this.m_tick);
   this.InitChangesParams();
   this.InitControlsParams();
class=class="str">"cmt">//--- Create the folder for storing account files
   ::ResetLastError();
   if(!::FolderCreate(this.m_folder_name,FILE_COMMON))
      Print(DFUN,TextByLanguage("Не удалось создать папку хранения файлов. Ошибка ","Could not create file storage folder. Error "),::GetLastError());
class=class="str">"cmt">//--- Create the current account object and add it to the list
   CAccount* account=new CAccount();
   if(account!=NULL)
     {
      if(!this.AddToList(account))
        {
         Print(DFUN_ERR_LINE,TextByLanguage("Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию.","Error. Failed to add current account object to collection list."));
         class="kw">delete account;
        }
      else
         account.PrintShort();
     }
   else
      Print(DFUN,TextByLanguage("Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта.","Error. Failed to create an account object with current account data."));
class=class="str">"cmt">//--- Download account objects from the files to the collection
   this.LoadObjects();
class=class="str">"cmt">//--- Save the current account index
   this.m_index_current=this.Index();
  }

class="type">void CAccountsCollection::Refresh(class="type">void)
  {
   this.m_is_account_event=class="kw">false;
   if(this.m_index_current==WRONG_VALUE)
      class="kw">return;
  }

◍ 账户指纹变了才触发事件派发

在盯盘引擎里,账户状态不是每跳都重算,而是先按索引取出当前账户对象,空指针直接 return,避免野引用把 EA 拖崩。 首次运行靠 m_struct_prev_account.login 判空:只要上一帧登录号为 0,就把当前结构体整包存为“上一账户”并退出,相当于冷启动只做基线、不误报事件。 真正的核心在 hash_sum 比对——当前账户哈希和上一帧不一致,才进变更分支。里面先算 change_code、定事件类型,再遍历 m_list_changes;若总数 >0 就把 m_is_account_event 置 true,逐条取枚举事件、借 SymbolInfoTick 拿实时 tick,拼出“毫秒时间: 事件描述”字符串。 别把正态当圣经:这段用 EventChartCustom 把 (ushort)event 和 tick.time_msc 推到图表,说明你开 MT5 后能在自定义事件里捕获账户切换/权益异动,外汇与贵金属杠杆高,这类事件常伴随保证金重算风险,验证时建议先开模拟盘跑一遍。 最后把 prev 的 hash_sum 同步成 curr,保证下一帧比对基准是最新值;这一手漏了就会重复触发,日志刷屏。

MQL5 / C++
  CAccount* account=this.m_list_accounts.At(this.m_index_current);
  if(account==NULL)
     class="kw">return;
  ::ZeroMemory(this.m_struct_curr_account);
  this.SetAccountsParams(account);

class=class="str">"cmt">//--- First launch
  if(!this.m_struct_prev_account.login)
     {
     this.m_struct_prev_account=this.m_struct_curr_account;
     class="kw">return;
     }
class=class="str">"cmt">//--- f the account hash sum changed
  if(this.m_struct_curr_account.hash_sum!=this.m_struct_prev_account.hash_sum)
     {
     this.m_change_code=this.SetChangeCode();
     this.SetTypeEvent();
     class="type">int total=this.m_list_changes.Total();
     if(total>class="num">0)
       {
       this.m_is_account_event=true;
       for(class="type">int i=class="num">0;i<total;i++)
         {
         ENUM_ACCOUNT_EVENT event=this.GetEvent(i);
         if(event==NULL || !::SymbolInfoTick(this.m_symbol,this.m_tick))
           class="kw">continue;
         class="type">class="kw">string sparam=TimeMSCtoString(this.m_tick.time_msc)+": "+this.EventDescription(event);
         Print(sparam);
         ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event,this.m_tick.time_msc,(class="type">class="kw">double)i,sparam);
         }
       }
     this.m_struct_prev_account.hash_sum=this.m_struct_curr_account.hash_sum;
     }

「账户事件与计数器类的私有成员布局」

在 MT5 的 EA 或指标工程里,若要同时监听多账户与定时器,通常会封装一个集合类。下面这段声明展示了该类内部如何存放账户集合、计数器列表以及一系列状态标志。 CAccountsCollection m_accounts; // 账户集合,管理多账号对象 CArrayObj m_list_counters; // 定时器计数器列表,用于按 id 追踪 bool m_first_start; // 首次启动标志,避免重复初始化 bool m_is_hedge; // 对冲账户标志,区分净仓/锁仓模式 bool m_is_tester; // 测试器内运行标志,区分实盘与回测 bool m_is_market_trade_event; // 市场成交事件标志 bool m_is_history_trade_event;// 历史成交事件标志 bool m_is_account_event; // 账户属性变更事件标志 ENUM_TRADE_EVENT m_last_trade_event; // 最近一次交易事件枚举 ENUM_ACCOUNT_EVENT m_last_account_event; // 最近一次账户属性事件枚举 紧接着是一组方法原型:CounterIndex(const int id) 按 id 取计数器下标;IsFirstStart() 返回是否首次启动;TradeEventsControl() 与 AccountEventsControl() 分别处理成交与账户事件。 取数接口覆盖较全:GetLastMarketPending() 取最新市价挂单,GetLastMarketOrder() 取最新市价单,GetLastPosition() 取最新持仓,GetPosition(ulong ticket) 按单号定位持仓;历史侧有 GetLastHistoryPending()、GetLastHistoryOrder()、GetHistoryOrder(ulong ticket)。 若你在 MT5 里写多账户监控 EA,直接复制上述成员声明做基类骨架,能省掉约 11 个状态变量的重复定义。外汇与贵金属波动剧烈,多账户并发监听存在滑点与事件丢失风险,上线前务必在策略测试器以 m_is_tester=true 跑一遍。

MQL5 / C++
  CAccountsCollection      m_accounts;                  class=class="str">"cmt">// Account collection
  CArrayObj                m_list_counters;              class=class="str">"cmt">// List of timer counters
  class="type">bool                     m_first_start;               class=class="str">"cmt">// First launch flag
  class="type">bool                     m_is_hedge;                  class=class="str">"cmt">// Hedge account flag
  class="type">bool                     m_is_tester;                 class=class="str">"cmt">// Flag of working in the tester
  class="type">bool                     m_is_market_trade_event;      class=class="str">"cmt">// Account trading event flag
  class="type">bool                     m_is_history_trade_event;     class=class="str">"cmt">// Account history trading event flag
  class="type">bool                     m_is_account_event;           class=class="str">"cmt">// Account change event flag
  ENUM_TRADE_EVENT         m_last_trade_event;           class=class="str">"cmt">// Account last trading event
  ENUM_ACCOUNT_EVENT       m_last_account_event;         class=class="str">"cmt">// Last event in account properties
class=class="str">"cmt">//--- Return the counter index by id
  class="type">int                      CounterIndex(class="kw">const class="type">int id) class="kw">const;
class=class="str">"cmt">//--- Return(class="num">1) the first launch flag, (class="num">2) the flag presence in a trading event
  class="type">bool                     IsFirstStart(class="type">void);
class=class="str">"cmt">//--- Working with(class="num">1) order, deal and position, as well as(class="num">2) account events
  class="type">void                     TradeEventsControl(class="type">void);
  class="type">void                     AccountEventsControl(class="type">void);
class=class="str">"cmt">//--- Return the last(class="num">1) market pending order, (class="num">2) market order, (class="num">3) last position, (class="num">4) position by ticket
  COrder*                  GetLastMarketPending(class="type">void);
  COrder*                  GetLastMarketOrder(class="type">void);
  COrder*                  GetLastPosition(class="type">void);
  COrder*                  GetPosition(class="kw">const class="type">class="kw">ulong ticket);
class=class="str">"cmt">//--- Return the last(class="num">1) removed pending order, (class="num">2) historical market order, (class="num">3) historical order(market or pending) by its ticket
  COrder*                  GetLastHistoryPending(class="type">void);
  COrder*                  GetLastHistoryOrder(class="type">void);
  COrder*                  GetHistoryOrder(class="kw">const class="type">class="kw">ulong ticket);
class=class="str">"cmt">//--- Return the(class="num">1) first and the(class="num">2) last historical market orders from the list of all position orders, (class="num">3) the last deal
  COrder*                  GetFirstOrderPosition(class="kw">const class="type">class="kw">ulong position_id);

从持仓到账户事件的一站式取数接口

在自建交易管理类里,把散落各处的订单、成交、持仓和账户变更收敛成统一 getter,是后续做风控和复盘的前提。下面这组公开方法直接返回对应的对象数组指针,调用方不用关心底层是实时池还是历史池。 GetListMarketPosition / GetListMarketPendings / GetListMarketOrders 分别给出当前活跃持仓、挂单和已成交市价单;GetListHistoryOrders / GetListHistoryPendings / GetListDeals 则覆盖历史订单、已删除挂单与成交记录。若想按某笔持仓 ID 拉出它名下的全部市价单,用 GetListAllOrdersByPosID(position_id) 即可,回测时定位某仓全部加减仓痕迹很实用。 账户层面,GetListAllAccounts 返回账户列表,GetListAccountEvents 拿到属性变动事件数组;GetListAllOrdersEvents 汇总订单/成交/持仓三类事件。LastTradeEvent 与 LastAccountEvent 只回吐最近一次枚举值,配合 ResetLastTradeEvent 可在每根 K 线开头清状态,避免重复触发。 IsHedge 与 IsTester 是两个布尔开关:前者区分净仓/锁仓账户(决定下单逻辑分支),后者标识是否在策略测试器内运行(影响计时器与真实下单)。外汇与贵金属杠杆高,用 IsHedge 误判账户类型可能导致同向加仓被拒,上线前务必在 MT5 终端实测返回值。

MQL5 / C++
COrder* GetLastOrderPosition(class="kw">const class="type">class="kw">ulong position_id);
COrder* GetLastDeal(class="type">void);
class="kw">public:
 class=class="str">"cmt">//--- Return the list of market(class="num">1) positions, (class="num">2) pending orders and(class="num">3) market orders
 CArrayObj* GetListMarketPosition(class="type">void);
 CArrayObj* GetListMarketPendings(class="type">void);
 CArrayObj* GetListMarketOrders(class="type">void);
 class=class="str">"cmt">//--- Return the list of historical(class="num">1) orders, (class="num">2) removed pending orders, (class="num">3) deals, (class="num">4) all position market orders by its id
 CArrayObj* GetListHistoryOrders(class="type">void);
 CArrayObj* GetListHistoryPendings(class="type">void);
 CArrayObj* GetListDeals(class="type">void);
 CArrayObj* GetListAllOrdersByPosID(class="kw">const class="type">class="kw">ulong position_id);
class=class="str">"cmt">//--- Return the list of(class="num">1) accounts, (class="num">2) account events
 CArrayObj* GetListAllAccounts(class="type">void) { class="kw">return this.m_accounts.GetList(); }
 CArrayInt* GetListAccountEvents(class="type">void) { class="kw">return this.m_accounts.GetListChanges(); }
class=class="str">"cmt">//--- Return the list of order, deal and position events
 CArrayObj* GetListAllOrdersEvents(class="type">void) { class="kw">return this.m_events.GetList(); }
class=class="str">"cmt">//--- Reset the last trading event
 class="type">void ResetLastTradeEvent(class="type">void) { this.m_events.ResetLastTradeEvent(); }
class=class="str">"cmt">//--- Return the(class="num">1) last trading event, (class="num">2) the last event in the account properties, (class="num">3) hedging account flag, (class="num">4) flag of working in the tester
 ENUM_TRADE_EVENT LastTradeEvent(class="type">void) class="kw">const { class="kw">return this.m_last_trade_event; }
 ENUM_ACCOUNT_EVENT LastAccountEvent(class="type">void) class="kw">const { class="kw">return this.m_last_account_event; }
 class="type">bool IsHedge(class="type">void) class="kw">const { class="kw">return this.m_is_hedge; }
 class="type">bool IsTester(class="type">void) class="kw">const { class="kw">return this.m_is_tester; }
class=class="str">"cmt">//--- Create the timer counter

◍ 账户事件轮询怎么落进引擎里

MT5 的 CEngine 类把账户事件管控拆成了一个独立方法,构造时先把 m_last_account_event 置为 ACCOUNT_EVENT_NO_EVENT,避免首帧误触发。 AccountEventsControl() 里第一句是 m_accounts.Refresh(),不刷新的话后续 IsAccountEvent() 读到的是上一 tick 的缓存,容易漏掉余额或杠杆的瞬时变动。 判定逻辑很直白:if(this.m_is_account_event) 成立才去 GetEvent() 写 m_last_account_event。实盘里账户事件频率远低于报价事件,建议把这个方法挂到 OnTimer 以较低频率(如 500ms~1000ms)调用,而不是每笔 tick 都跑,能省下可观的 CPU。外汇和贵金属杠杆高,账户净值瞬间跳变可能触发强平,这类事件监控不能省。 下面这段是原文里的核心片段,逐行看: // 构造函数初始化,账户事件标记先清零 CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(ACCOUNT_EVENT_NO_EVENT) // 账户事件管控方法 void CEngine::AccountEventsControl(void) { // 先刷新账户对象,确保读到最新属性 this.m_accounts.Refresh(); // 取是否有账户变动的布尔标记 this.m_is_account_event=this.m_accounts.IsAccountEvent(); // 若有变动 if(this.m_is_account_event) { // 拉取具体事件枚举并存入成员 this.m_last_account_event=this.m_accounts.GetEvent(); } }

MQL5 / C++
CEngine::CEngine() : m_first_start(true),m_last_trade_event(TRADE_EVENT_NO_EVENT),m_last_account_event(ACCOUNT_EVENT_NO_EVENT)
  {
  }
class="type">void CEngine::AccountEventsControl(class="type">void)
  {
  this.m_accounts.Refresh();
  this.m_is_account_event=this.m_accounts.IsAccountEvent();
  if(this.m_is_account_event)
    {
    this.m_last_account_event=this.m_accounts.GetEvent();
    }
  }

「测试帐户事件」

若要测试帐户事件,我们可以利用 上一篇文章中的 EA ,因为函数库会自行查找所有帐户属性,发送相应的消息作为图表事件,并在日志中显示已发生的帐户活动描述。 但是,我们来尝试超越函数库的“沙箱”,并在程序中处理一些帐户事件,例如增加净值。 目前,从外部访问程序功能非常有限。 但是,只有在我们收集和处理必要的数据之前才会出现这种情况 — 为了测试所创建的类,以及收集的数据和跟踪事件的正确性,函数库会在日志中显示各种事件。 进而,我们将引入更便利访问任意函数库数据的功能,来极大简化程序中的检索。 若要立即获取有关帐户属性变化的数据,需在 CEngine 类中稍作改进。 我们需要访问当前帐户对象和事件。 为此, 在 CEngine 类的公有部分(Engine.mqh 文件)中添加必要的方法: 此处我们添加了 按照变化列表中的索引接收帐户事件的方法 , 接收当前帐户对象的方法 和 接收帐户事件描述的方法 。 按照索引接收帐户事件的方法 仅返回帐户集合类中先前描述的 GetEvent() 方法的操作结果。 在清单的最后, 实现接收当前帐户对象的方法: 首先, 从帐户集合类 里获取当前帐户索引。 如果未收到索引,则返回 NULL 。 接下来, 获取完整的帐户列表 , 在帐户列表 中 按索引返回必要的当前帐户 。 若有列表或帐户错误的情况, 返回 NULL 。 另外, 我们还要实现返回帐户事件描述的方法: 该方法返回帐户集合类方法的操作结果,返回帐户事件描述。 若要测试帐户事件,请使用来自上一篇文章的 EA TestDoEasyPart12_2.mq5 ,位于 \MQL5\Experts\TestDoEasy\Part12\,并在 \MQL5\Experts\TestDoEasy\Part13 下将其另存为 TestDoEasyPart13.mq5 。 立即删除 以下输入 同时还有 OnInit() 处理程序中 的快速检查账户集合: 由于我们更改了帐户对象结构(更改了 uchar 数组的大小以便存储帐户字符串型属性,并添加了另一个整数型属性),因此无法再正确加载所有以前保存的帐户对象文件。 如果它们出现在终端公用文件夹 \Files\DoEasy\Accounts\ 之中,请在启动测试 EA 之前将其删除。 当从一个帐户切换到另一个帐户时,系统会依据新对象结构大小重新创建它们。 OnInit() 处理程序目前看起来如下: 为了处理到达程序的来自函数库的所有事件,并避免内建的 OnChartEvent() 混乱,请实现单独的 OnDoEasyEvent() 响应程序来处理其中获取的所有事件。 这会令代码更具可读性,更重要的是,它允许我们处理测试器中的事件,这正是我们现在所需要的,因为我们将处理“超过指定增长值的净值增加”帐户事件,并能更快速、更容易地检查测试器中的所有内容以实现这一目标。 我们在 OnTick() 处理程序中添加处理帐户事件所需的功能: 在此我们引入了 存储最后一个帐户事件类型的新变量 。 检查其当前状态与 CAccountsCollection 类返回的最后一个事件类型的关系 。 如果状态已变化,则会发生帐户事件。 接下来, 仅针对测试器, 在循环中根据同时发生事件从帐户列表获取新事件, 将其发送到函数库事件处理程序 。 清单注释包含用于接收事件并将其发送到处理程序的所有操作。 当测试器运行时,我们无法以毫秒为单位访问事件时间的数据。 因此,只需发送 当前时间 * 1000 。 现在,我们 改进 OnChartEvent() 事件处理程序: 在此

账户索引与多账户遍历的取数细节

在 MT5 的 EA 引擎封装里,取当前账户对象不能硬写下标,得先拿 IndexCurrentAccount() 的返回值做越界判断:若为 WRONG_VALUE 直接 return NULL,否则从 m_accounts.GetList() 拿对象数组再 At(index) 定位。这三行黄红蓝底色代码就是这套防御式取数的核心,漏掉 WRONG_VALUE 判断在切换账户时可能返回野指针。 GetAccountEventDescription() 只是薄封装,把 ENUM_ACCOUNT_EVENT 转成 m_accounts.EventDescription() 的多语描述串,方便面板直接显示「入金 / 出金 / 杠杆变更」等事件文案,本身不涉风险,但调用前需确认事件枚举已注册。 实盘跑多账户扫描时,用 engine.GetListAllAccounts() 拿到全量账户集合后,先判 NULL 再取 Total();样例里对 total>0 才打印分隔标题,循环内每账户 Sleep(100) 避免日志刷屏被经纪商限流。InpFullProperties 输入参数默认 false,决定走 account.Print() 全量属性还是 PrintShort() 简表——调 true 能在日志看到每账户保证金模式与币种,外汇与贵金属账户的高杠杆风险也更易暴露。 OnInit 里 lot 用 NormalizeLot(Symbol(), fmax(InpLots, MinimumLots(Symbol())*2.0)) 兜底:最小手数翻倍作下限,外汇贵金属波动大,手数算错可能触发拒单。

MQL5 / C++
class="type">int index=this.m_accounts.IndexCurrentAccount();
if(index==WRONG_VALUE)
   class="kw">return NULL;
CArrayObj* list=this.m_accounts.GetList();
class="kw">return(list!=NULL ? (list.At(index)!=NULL ? list.At(index) : NULL) : NULL);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the account event description                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CEngine::GetAccountEventDescription(ENUM_ACCOUNT_EVENT event)
  {
   class="kw">return this.m_accounts.EventDescription(event);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">bool    InpFullProperties   =   class="kw">false;class=class="str">"cmt">// Show full accounts properties
class=class="str">"cmt">//--- Fast check of the account object collection
   CArrayObj* list=engine.GetListAllAccounts();
   if(list!=NULL)
     {
      class="type">int total=list.Total();
      if(total>class="num">0)
         Print("\n",TextByLanguage("=========== Список сохранённых аккаунтов ===========","=========== List of saved accounts ==========="));
      for(class="type">int i=class="num">0;i<total;i++)
        {
         CAccount* account=list.At(i);
         if(account==NULL)
            class="kw">continue;
         Sleep(class="num">100);
         if(InpFullProperties)
            account.Print();
         else
            account.PrintShort();
        }
     }
class=class="str">"cmt">//---
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Calling the function displays the list of enumeration constants in the journal
class=class="str">"cmt">//--- (the list is set in the strings class="num">22 and class="num">25 of the DELib.mqh file) for checking the constants validity
   class=class="str">"cmt">//EnumNumbersTest();
class=class="str">"cmt">//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(class="type">int i=class="num">0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*class="num">2.0));
   magic_number=InpMagic;

◍ EA 初始化与 tick 里的事件捕获

这段逻辑出现在 MT5 智能交易的初始化尾段与 OnTick 主循环里,负责把外部输入参数落地为运行时变量,并清理旧图形对象。stoploss、takeprofit 等直接取自 Inp 系列输入,而 trailing_stop 和 trailing_step 乘了 Point(),说明追踪止损距离按当前品种最小报价单位换算,黄金和欧美在这行代码上算出的实际点数差可能差出十几倍。 初始化收尾会先判断前缀对象是否存在,存在就 ObjectsDeleteAll(0,prefix) 清掉上一次运行的按钮和画线,避免图形残留干扰。随后 CreateButtons 建按钮面板,失败直接 return INIT_FAILED,ButtonState 则根据 trailing_on 变量点亮追踪开关。 MQL5 分支里给 CTrade 设了偏差点数、魔术码、按品种填充模式和日志等级 NO,回测时这些设定会直接影响成交模拟。OnTick 中用 static 变量缓存上一次交易与账户事件枚举,只在事件切换时才 Comment 输出,能减少不必要的主图刷新开销。 测试环境下 OnTick 会主动调 engine.OnTimer() 和 PressButtonsControl(),并用 GetListAccountEvents() 循环遍历同帧触发的账户事件。想验证这套事件驱动是否灵敏,开 MT5 策略测试器跑一段含出入金或保证金变更的回测,看右下角 Comment 是否随帧切换描述即可。

MQL5 / C++
  stoploss=InpStopLoss;
  takeprofit=InpTakeProfit;
  distance_pending=InpDistance;
  distance_stoplimit=InpDistanceSL;
  slippage=InpSlippage;
  trailing_stop=InpTrailingStop*Point();
  trailing_step=InpTrailingStep*Point();
  trailing_start=InpTrailingStart;
  stoploss_to_modify=InpStopLossModify;
  takeprofit_to_modify=InpTakeProfitModify;
class=class="str">"cmt">//--- Check and remove remaining EA graphical objects
  if(IsPresentObects(prefix))
     ObjectsDeleteAll(class="num">0,prefix);
class=class="str">"cmt">//--- Create the button panel
  if(!CreateButtons(InpButtShiftX,InpButtShiftY))
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set trailing activation button status
  ButtonState(butt_data[TOTAL_BUTT-class="num">1].name,trailing_on);
class=class="str">"cmt">//--- Set CTrade trading class parameters
class="macro">#ifdef __MQL5__
  trade.SetDeviationInPoints(slippage);
  trade.SetExpertMagicNumber(magic_number);
  trade.SetTypeFillingBySymbol(Symbol());
  trade.SetMarginMode();
  trade.LogLevel(LOG_LEVEL_NO);
class="macro">#endif
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Initialize the last events
  class="kw">static ENUM_TRADE_EVENT last_trade_event=WRONG_VALUE;
  class="kw">static ENUM_ACCOUNT_EVENT last_account_event=WRONG_VALUE;
class=class="str">"cmt">//--- If working in the tester
  if(MQLInfoInteger(MQL_TESTER))
     {
     engine.OnTimer();
     PressButtonsControl();
     }
class=class="str">"cmt">//--- If the last trading event changed
  if(engine.LastTradeEvent()!=last_trade_event)
     {
     last_trade_event=engine.LastTradeEvent();
     Comment("\nLast trade event: ",EnumToString(last_trade_event));
     }
class=class="str">"cmt">//--- If the last account event changed
  if(engine.LastAccountEvent()!=last_account_event)
     {
     last_account_event=engine.LastAccountEvent();
     class=class="str">"cmt">//--- If this is a tester
     if(MQLInfoInteger(MQL_TESTER))
       {
       class=class="str">"cmt">//--- Get the list of all account events occurred simultaneously
       CArrayInt* list=engine.GetListAccountEvents();
       if(list!=NULL)
         {
         class=class="str">"cmt">//--- Get the next event in a loop
         class="type">int total=list.Total();
         for(class="type">int i=class="num">0;i<total;i++)
           {
           ENUM_ACCOUNT_EVENT event=(ENUM_ACCOUNT_EVENT)list.At(i);
           if(event==NULL)
              class="kw">continue;
           class="type">class="kw">string sparam=engine.GetAccountEventDescription(event);

「把库事件接进图表与持仓逻辑」

EA 主循环里先组装自定义事件参数:用 TimeCurrent()*1000 生成毫秒级 long 型时间戳塞进 lparam,把循环下标 i 强转成 double 写进 dparam,再调用 OnDoEasyEvent(CHARTEVENT_CUSTOM+event, lparam, dparam, sparam) 把事件推给处理器。 若 trailing_on 为真,紧接着跑 TrailingPositions() 与 TrailingOrders() 做动态止损;这两行和事件派发是并列关系,不会因事件类型而改变。 图表端 OnChartEvent 里先拦测试器环境:MQLInfoInteger(MQL_TESTER) 为真直接 return,避免回测中误触按钮逻辑。只有 id>=CHARTEVENT_CUSTOM 的自定义事件才转交 OnDoEasyEvent,普通 OBJECT_CLICK 且前缀为 BUTT_ 的走 PressButtonEvents。 在 OnDoEasyEvent 内部,idx=id-CHARTEVENT_CUSTOM 还原事件序号;当 idx<TRADE_EVENTS_NEXT_CODE 时判定为交易类事件,用 (ENUM_TRADE_EVENT)ushort(idx) 反枚举出名称,并以 SymbolInfoInteger(sparam, SYMBOL_DIGITS) 取该品种小数位——这意味着不同品种的事件处理会自动适配报价精度。

MQL5 / C++
class="type">long lparam=TimeCurrent()*class="num">1000;
class="type">class="kw">double dparam=(class="type">class="kw">double)i;
class=class="str">"cmt">//--- Send an event to the event handler
OnDoEasyEvent(CHARTEVENT_CUSTOM+event,lparam,dparam,sparam);
}
    }
  }
 Comment("\nLast account event: ",EnumToString(last_account_event));
  }
class=class="str">"cmt">//--- If the trailing flag is set
 if(trailing_on)
  {
   TrailingPositions();
   TrailingOrders();
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id,
                class="kw">const class="type">long &lparam,
                class="kw">const class="type">class="kw">double &dparam,
                class="kw">const class="type">class="kw">string &sparam)
  {
  if(MQLInfoInteger(MQL_TESTER))
    class="kw">return;
  if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>class="num">0)
    {
    PressButtonEvents(sparam);
    }
class=class="str">"cmt">//--- DoEasy library event
  if(id>=CHARTEVENT_CUSTOM)
    {
    OnDoEasyEvent(id,lparam,dparam,sparam);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handling DoEasy library events                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDoEasyEvent(class="kw">const class="type">int id,
                class="kw">const class="type">long &lparam,
                class="kw">const class="type">class="kw">double &dparam,
                class="kw">const class="type">class="kw">string &sparam)
  {
  class="type">int idx=id-CHARTEVENT_CUSTOM;
  class="type">class="kw">string event="::"+class="type">class="kw">string(idx);
  class="type">int digits=Digits();
class=class="str">"cmt">//--- Handling trading events
  if(idx<TRADE_EVENTS_NEXT_CODE)
    {
    event=EnumToString((ENUM_TRADE_EVENT)class="type">class="kw">ushort(idx));
    digits=(class="type">int)SymbolInfoInteger(sparam,SYMBOL_DIGITS);
    }

净值跳涨后平掉最赚那仓

账户事件分支里,当 idx 落在账户事件区间且具体事件为 ACCOUNT_EVENT_EQUITY_INC 时,逻辑会触发一次主动平仓。默认阈值由 m_control_equity_inc 控制,跟踪净值增长 15 个单位(该值在 CAccountsCollection::InitControlsParams 内设定,源码约 1199 行)。 先通过 engine.GetListMarketPosition() 取出全部未平仓位,再用 Sort(SORT_BY_ORDER_PROFIT_FULL) 按含手续费与库存费的完整利润排序。CSelect::FindOrderMax 拿到利润最高那条的索引,若大于 WRONG_VALUE 就取该仓位对象准备平仓。 MQL5 环境走 trade.PositionClose(position.Ticket()),老版本 MQL4 则带手数调 PositionClose(position.Ticket(),position.Volume())。日志里 2019.06.10 10:56 出现过 'Trading on the account is prohibited now',11:08 又恢复 allowed,说明这类自动平仓在账户禁交易时段会直接失效,外汇与贵金属自带高杠杆风险,实盘前务必在策略测试器里跑一遍事件触发路径。

MQL5 / C++
class=class="str">"cmt">//--- Handling account events
  else if(idx<ACCOUNT_EVENTS_NEXT_CODE)
    {
    event=EnumToString((ENUM_ACCOUNT_EVENT)class="type">class="kw">ushort(idx));
    digits=class="num">0;
    
    class=class="str">"cmt">//--- if this is an equity increase
    if((ENUM_ACCOUNT_EVENT)idx==ACCOUNT_EVENT_EQUITY_INC)
      {
       Print(DFUN,sparam);
       class=class="str">"cmt">//--- Close a position with the highest profit when the equity exceeds the value
       class=class="str">"cmt">//--- specified in the CAccountsCollection::InitControlsParams() method for
       class=class="str">"cmt">//--- the m_control_equity_inc variable tracking the equity growth by class="num">15 units(by class="kw">default)
       class=class="str">"cmt">//--- AccountCollection file, InitControlsParams() method, class="type">class="kw">string class="num">1199
       
       class=class="str">"cmt">//--- Get the list of all open positions
       CArrayObj* list_positions=engine.GetListMarketPosition();
       class=class="str">"cmt">//--- Sort the list by profit considering commission and swap
       list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL);
       class=class="str">"cmt">//--- Get the position index with the highest profit
       class="type">int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL);
       if(index>WRONG_VALUE)
         {
          COrder* position=list_positions.At(index);
          if(position!=NULL)
            {
             class=class="str">"cmt">//--- Get a ticket of a position with the highest profit and close the position by a ticket
             class="macro">#ifdef __MQL5__
               trade.PositionClose(position.Ticket());
             class="macro">#else
               PositionClose(position.Ticket(),position.Volume());
             class="macro">#endif
            }
         }
      }
    }

◍ 事件去重漏洞与后续品种对象规划

作者在这一阶段已把在场订单、持仓集合和交易事件框架搭完,下一步打算落地品种对象、品种对象集合以及品种事件,让事件系统从账户维度扩到具体交易标的维度。 有读者在 EURUSD M1 上跑测试 EA 时贴出日志:同一秒内的多笔平仓里,#542263753 被记录了两次(15:22:42 出现两行 Position closed),而 #542264331 等三笔开仓虽都在 15:27:11~13 内,事件集合却在 767 行报 'This event already in the list'。这说明按秒级时间戳做事件主键,在 MT4 兼容层会漏检或重检,概率随同秒成交笔数上升而增加。 当前函数库分 MQL5.zip(129.92 KB)与 MQL4.zip(127.28 KB)两个包,均含测试 EA 可直接挂 MT5 验证。外汇与贵金属杠杆高,同秒批量平仓在流动性跳空时更易触发此类重复,实盘前务必在策略测试器用 0.01 手多单并发回测一遍。 要修这个坑,得把事件唯一标识从时间改为 ticket+行为类型,或在 CEventsCollection::CreateNewEvent 里先按订单号查重而非仅按列表存在性判断。改完用读者那串日志重放,应看不到 767 行报错和重复平仓行。

「最后一句大实话」

上面那段 OnTick/OnTimer 里的 DoChecks() 已经把交易事件和账户事件的轮询骨架搭好了:每次心跳先调 engine.OnTimer(),再分别判 LastTradeEvent 和 LastAccountEvent,有事件就遍历列表逐个处理,随后 Reset 清零。 值得留意的细节是社区里有人指出,用 TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" 判终端类型可能踩坑——经纪商名也可能塞进 TERMINAL_NAME,更稳妥的写法是 StringFind 看是否包含 "MetaTrader 5" 再返回 5 或 4。 外汇和贵金属保证金交易杠杆高、滑点大,这类事件跟踪代码只解决「看得见」,不解决「赚得到」;把它接进你的 MT5 实盘前,先在策略测试器用 2019.10.14 那笔 EURUSD M1 买单调平(平仓价 1.10304、亏损 0.14 美元)做回放,确认事件捕获顺序没错再上真金。

MQL5 / C++
class="type">void OnTick() { DoChecks(); }
class="type">void OnTimer() { DoChecks(); }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 我的检查功能|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DoChecks() {
  engine.OnTimer();
  class=class="str">"cmt">//\//\ 贸易活动
  if(engine.LastTradeEvent()!=TRADE_EVENT_NO_EVENT) { class=class="str">"cmt">//engine.LastTradeEvent()!=last_trade_event
    CArrayObj* list=engine.GetListAllOrdersEvents();
    if(list!=NULL) {
      class="type">int total=list.Total();
      for(class="type">int i=class="num">0;i<total ; i++){
        CheckTradeEvent(list.At(i));
      }
    }
    class=class="str">"cmt">//--
    engine.ResetLastTradeEvent();
  }
  class=class="str">"cmt">//\//\ 账户事件
  if(engine.LastAccountEvent()!=ACCOUNT_EVENT_NO_EVENT){
    CArrayInt* list=engine.GetListAccountEvents();
    if(list!=NULL){
      class="type">int total=list.Total();
      for(class="type">int i=class="num">0;i<total;i++){
        ENUM_ACCOUNT_EVENT event=(ENUM_ACCOUNT_EVENT)list.At(i);
        if(event==NULL){ class="kw">continue; }
        CheckAccountEvent(event);
      }
    }
    class=class="str">"cmt">//--
    engine.ResetLastAccountEvent();
  }
}
this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]                    = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader class="num">5" ? class="num">5 : class="num">4);
把账户事件巡检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到账户属性异动标记,你只需在策略层消费事件,不用自己写比较逻辑。

常见问题

终端只连一个当前账户,单独类属冗余;把方法直接放进账户集合类,比较前后属性状态更顺手,也少一层对象转发。
可以,小布盯盘的品种页已把账户属性异动做了可视化,省去你从零搭事件捕获与标志解析的功夫。
这样交易事件扩容时只需改总数宏,账户事件编号自动顺延,后续加品种事件也能照此叠层,不重写旧代码。
用一组事件标志按位或进同一个代码变量,再逐位判存,所有命中项进数组供引擎读取,避免单次回调只报一个属性。
官方回应了实际大小,改为在 CAccount 内严格设数组长度,宏去掉后编译更清晰,也不会因宏误配导致截断。