轻松快捷开发 MetaTrader 程序的函数库(第十三部分):帐户对象事件·综合运用
(3/3)· 从枚举宏替换到多属性变更捕获,把单账户事件跟踪做成可扩展的收尾模块
- 账户结构体的字段拆解
- 账户结构体的保证金与字符串字段
- 账户属性读取的封装方法
- 把账户属性一次性抓进对象里
- 账户属性在对象里的落地写法
- 把账户对象压进结构体的落盘写法
- 账户结构体到对象的字段映射
- 账户对象属性落盘与日志简报
- 账户属性枚举的本地化文本拼装
- 从账户属性读出服务器类型
- 账户快照的结构化落地
- 账户结构体与变更跟踪的成员布局
- 账户事件结构体的字段拆解
- 账户状态变量的跟踪骨架
- 账户风控变量的内部声明拆解
- 账户维护保证金与资产负债的变动追踪变量
- 账户变更参数的初始化细节
- 账户状态快照的清零初始化
- 账户风控字段的初始化清零
- 账户状态追踪的阈值设定
- 账户变动的位标志累加逻辑
- 账户事件类型的判定与写入
- 账户杠杆与挂单额度的增量捕获
- 账户风控阈值与余额变动的事件捕获
- 账户盈亏与净值的阈值触发逻辑
- 账户保证金变动的事件触发逻辑
- 保证金三类变动的事件触发分支
- 维护保证金与资产负债的异动捕获
- 盯住账户冻结佣金跳变
- 账户事件与筛选接口的返回方法
- 账户集合类的接口与杠杆变动追踪
- 账户风控状态的取值接口
- 账户余额与信用变化的类内钩子
- 账户权益与利润的变动追踪接口
- 权益与保证金变动的阈值捕获
- 保证金与游离保证金的阈值跟踪接口
- 跟踪保证金水平变动的接口怎么写
- 读懂保证金追保与止损平仓的变动接口
- 盯住初始与维持保证金的变动阈值
- 账户资产与负债变动的跟踪接口
- 负债与冻结佣金的阈值跟踪接口
- 账户事件回溯与文本描述的取数链路
- 账户事件类型的文字映射
- 账户事件的中文提示串拼法
- 账户事件的多语种提示串拼接
- 账户保证金事件的中文提示串拼法
- 保证金与强平阈值的事件文案拼接
- 账户事件的多语言变动播报
- 账户集合对象的初始化与刷新逻辑
- 账户指纹变了才触发事件派发
- 账户事件与计数器类的私有成员布局
- 从持仓到账户事件的一站式取数接口
- 账户事件轮询怎么落进引擎里
- 测试帐户事件
- 账户索引与多账户遍历的取数细节
- EA 初始化与 tick 里的事件捕获
- 把库事件接进图表与持仓逻辑
- 净值跳涨后平掉最赚那仓
- 事件去重漏洞与后续品种对象规划
- 最后一句大实话
账户结构体的字段拆解
在 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 倍,写仓位计算前先把这个值打出来核对。
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()) 就能看到当前账户是实盘、模拟还是观望模式。
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。外汇与贵金属杠杆放大波动,账户属性读错可能让仓位计算直接偏离,属高风险操作。
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,你能在日志里直接看到杠杆和交易权限,比手动翻终端设置快得多。
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 段,就能直接打印出你当前账户的缓存结构。
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 字段变化——能验证你对枚举窄化的理解对不对。外汇与贵金属杠杆高,账户净值波动可能触发强平,这段代码只解决数据存储,不涉及任何方向判断。
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)],能直接验证你的账户对象有没有正确吃到结构体数据。外汇和贵金属保证金波动大,这类底层映射写错可能让风控计算偏一倍,实盘前务必用策略测试器跑一遍。
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' 加具体数值。做自己的面板时,直接复用这套描述函数能省掉手写的标签映射表。
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 字段核对。
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 后端。 外汇与贵金属杠杆品种波动剧烈,用代码读取服务器类型仅作环境识别,不等于任何交易信号,实盘前请在策略测试器核实返回值。
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 一个集合开始压测。
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 更利于做历史回看。
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 标志翻转。
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%),跑一周模拟盘看标志位翻转频率,再回来调阈值。
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 搜这些变量名,能直接定位风控模块的触发逻辑。
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 读到野指针大概率误触发。
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,能把漏报概率压到最低。外汇与贵金属品种受杠杆和权限突变影响大,这类复位缺失可能导致策略在禁交易期误发单,属高风险操作环节。
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 跑一帧即可验证字段是否干净。
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 前拿到干净的账户快照。
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 用策略测试器看日志里触发频率是否符合预期。
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_* 常量。 这种位或累加的好处是:一次账户刷新可能同时动了余额和保证金,变更码里两个标志位都会置起,外部用位与就能精确知道哪几项变了,不用多次轮询。外汇和贵金属账户杠杆与保证金变动频繁,用这套机制捕获状态切换,对自动暂停加仓或弹窗告警有直接用处。
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 把这段接进自己的账户监听类,改一下事件枚举名就能直接跑;外汇与贵金属杠杆高,权限被关时未平仓头寸可能瞬间放大风险敞口,需让小布这类工具第一时间弹窗。
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 事件只触发一次。
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 里第一时间感知并降仓,但券商改参数本身不预示行情,仅影响强平概率。
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 都进事件。
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 历史跑一周,看事件触发次数是否和手动标注的加仓时段吻合,再反推阈值。
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/dec 与 m_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;
}
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 里跑一轮观察。
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 把冻结佣金事件打印到日志,能直接看券商在什么时点锁了多少佣金,外汇和贵金属账户杠杆高,冻结变动往往预示保证金重算,属高风险信号,需结合净值一起看。
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 字符串有效,这类市场杠杆高、点差跳变快,错误品种可能导致后续计算全部偏移。
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 布尔,用来判定杠杆是上调还是下调方向。 外汇与贵金属杠杆调整往往预示风控取向变化,属高风险信号,只能作为概率性参考,不能单独用作下单依据。把这些接口接进你的面板,可能在经纪商偷偷调杠杆时第一时间弹提示。
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,封装类把「变化」而非「绝对值」吐出来,省掉一层比对代码。
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,此时去查日志大概率对应平仓盈余或入金,外汇与贵金属杠杆品种须警惕此类跳变伴随的滑点风险。
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 同理,常用于跟踪赠金或隔夜费导致的非交易性余额变动,避免和真实浮动盈亏混淆。
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 分钟周期上较早抓到异常敞口波动;外汇与贵金属杠杆高,这类监控只作预警,不替代止损。
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()) 跑一晚模拟盘,就能验证阈值触发是否如预期。
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 的前后差值。
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 每秒比对一次这几个接口。
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 里做二次确认。
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,就能在负债增幅超阈时触发告警或减仓逻辑,但任何触发都只是概率性风控,不保证避损。
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 返回值,就能验证当前品种在滑点行情下负债跟踪是否灵敏。
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 在 '%' 与币种间切换。外汇和贵金属杠杆波动大,这类账户数据读取错误可能让风控逻辑失效,属高风险环节。
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 的第一个参数是俄文、第二个是英文,若你的面板只面向中文用户,可改成中文串或自行封装一个中文函数,避免界面出现看不懂的语种。 外汇与贵金属账户事件受券商实时控制,交易允许状态可能在秒级翻转,这类映射只负责「显示」,真正的开关权限仍以券商下发的事件为准,高风险品种尤其要留意自动交易被静默关闭的可能。
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 里跑,能在日志里立刻看到经纪商改了哪项参数。
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() 是否返回了有效数值。外汇与贵金属账户波动频繁,这类事件提示能帮助快速定位保证金异动,但高杠杆下误读浮盈浮亏可能引发错误平仓,需结合上下文判断。
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 里接这套提示做实时盯盘预警。
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 变动可能瞬间影响持仓,验证时建议开模拟账户观察事件触发节奏。
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 就能直接看不同精度下的输出差异。
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 前的事件判断必然为假。
外汇与贵金属账户数据高频变动,这类集合若用于跨账户监控,需意识到多账户文件读写存在共享冲突可能,建议先在模拟环境验证目录权限与刷新频率。
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,保证下一帧比对基准是最新值;这一手漏了就会重复触发,日志刷屏。
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 跑一遍。
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 终端实测返回值。
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(); } }
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)) 兜底:最小手数翻倍作下限,外汇贵金属波动大,手数算错可能触发拒单。
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 是否随帧切换描述即可。
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) 取该品种小数位——这意味着不同品种的事件处理会自动适配报价精度。
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,说明这类自动平仓在账户禁交易时段会直接失效,外汇与贵金属自带高杠杆风险,实盘前务必在策略测试器里跑一遍事件触发路径。
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 美元)做回放,确认事件捕获顺序没错再上真金。
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);