轻松快捷开发 MetaTrader 程序的函数库(第十七部分):函数库对象之间的交互·进阶篇
(2/3)· 还在为每个对象手写事件枚举?这篇把交互收敛进基准类,事件量随属性自动伸缩
◍ 报价与持仓维度的事件枚举清单
MT5 的 SYMBOL_EVENT 系列枚举把「当日异动」拆得很细,不止是价格涨跌,还覆盖买卖价极值、真实成交量、期权行权价、持仓限额与掉期。上面这段枚举专门描述 Ask 高低点相对前一日的变化阈值被突破的情形。 具体看,SYMBOL_EVENT_ASK_HIGH_DEC 表示当日最大 Ask 价格相比之前下降幅度超过设定值;SYMBOL_EVENT_ASK_LOW_INC 则是最小 Ask 抬升超阈值。真实成交量一侧有 SYMBOL_EVENT_VOLUME_REAL_DAY_INC / DEC,以及日内最大、最小真实成交量的增减事件,共 8 个相关枚举。 掉期与时段统计也单独成事件:SYMBOL_EVENT_SWAP_LONG_INC / DEC 监控多单隔夜费上调或下调,SYMBOL_EVENT_SESSION_VOLUME_INC 指当前交易时段总成交手数增量越线。外汇与贵金属杠杆高,这类事件触发只说明市场结构异动,不预示方向,需用 MT5 终端的事件订阅接口自行验证触发频率。
SYMBOL_EVENT_ASK_HIGH_DEC, class=class="str">"cmt">// The decrease in the maximum Ask price per day exceeds the specified value SYMBOL_EVENT_ASK_LOW_INC, class=class="str">"cmt">// The increase in the minimum Ask price per day exceeds the specified value SYMBOL_EVENT_ASK_LOW_DEC, class=class="str">"cmt">// The decrease in the minimum Ask price per day exceeds the specified value SYMBOL_EVENT_VOLUME_REAL_DAY_INC, class=class="str">"cmt">// The increase in the real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_REAL_DAY_DEC, class=class="str">"cmt">// The decrease in the real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_INC, class=class="str">"cmt">// The increase in the maximum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_DEC, class=class="str">"cmt">// The decrease in the maximum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_INC, class=class="str">"cmt">// The increase in the minimum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_DEC, class=class="str">"cmt">// The decrease in the minimum real volume per day exceeds the specified value SYMBOL_EVENT_OPTION_STRIKE_INC, class=class="str">"cmt">// The increase in the strike price exceeds the specified value SYMBOL_EVENT_OPTION_STRIKE_DEC, class=class="str">"cmt">// The decrease in the strike price exceeds the specified value SYMBOL_EVENT_VOLUME_LIMIT_INC, class=class="str">"cmt">// The increase in the maximum available total position volume and pending orders in one direction SYMBOL_EVENT_VOLUME_LIMIT_DEC, class=class="str">"cmt">// The decrease in the maximum available total position volume and pending orders in one direction SYMBOL_EVENT_SWAP_LONG_INC, class=class="str">"cmt">// The increase in the swap class="type">long SYMBOL_EVENT_SWAP_LONG_DEC, class=class="str">"cmt">// The decrease in the swap class="type">long SYMBOL_EVENT_SWAP_SHORT_INC, class=class="str">"cmt">// The increase in the swap class="type">class="kw">short SYMBOL_EVENT_SWAP_SHORT_DEC, class=class="str">"cmt">// The decrease in the swap class="type">class="kw">short SYMBOL_EVENT_SESSION_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_TURNOVER_INC, class=class="str">"cmt">// The increase in the total turnover in the current session exceeds the specified value
盯盘事件枚举与基类字段拆解
MT5 的 Symbol 事件体系里,有一组以 SYMBOL_EVENT_SESSION_ 开头的枚举值,专门捕捉当前交易时段内的微观结构变化。比如 SYMBOL_EVENT_SESSION_TURNOVER_DEC 表示时段总成交额下降幅度超过设定阈值,SYMBOL_EVENT_SESSION_INTEREST_INC 表示未平仓总量增加超阈,SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_DEC 则是买单总成交量下降超阈——这类信号对黄金和欧美的盘口突变有较高提示价值,但外汇贵金属杠杆高,信号仅代表概率倾向,不代表方向必现。 宏 SYMBOL_EVENTS_NEXT_CODE 被定义为 SYMBOL_EVENT_SESSION_AW_DEC+1,即 13+1=14(从 SYMBOL_EVENT_SESSION_TURNOVER_DEC 起算共 13 个会话事件),用来标记符号事件码之后的下一个事件边界,写自定义监听器时可直接拿它做循环上限。 事件基类 CBaseEvent 公有继承 CObject,私有成员 m_reason(ENUM_BASE_EVENT_REASON 类型)记触发原因,m_event_id(int)存事件 ID,m_value(double)存附加数值;对外只暴露 Reason() 内联方法返回 m_reason。想验证就开 MT5 头文件搜 CBaseEvent,看自己装的终端版本字段是否一致。
SYMBOL_EVENT_SESSION_TURNOVER_DEC, class=class="str">"cmt">// The decrease in the total turnover in the current session exceeds the specified value SYMBOL_EVENT_SESSION_INTEREST_INC, class=class="str">"cmt">// The increase in the total volume of open positions in the current session exceeds the specified value SYMBOL_EVENT_SESSION_INTEREST_DEC, class=class="str">"cmt">// The decrease in the total volume of open positions in the current session exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of buy orders exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of buy orders exceeds the specified value SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of sell orders exceeds the specified value SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of sell orders exceeds the specified value SYMBOL_EVENT_SESSION_OPEN_INC, class=class="str">"cmt">// The increase in the session open price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_OPEN_DEC, class=class="str">"cmt">// The decrease in the session open price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_CLOSE_INC, class=class="str">"cmt">// The increase in the session close price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_CLOSE_DEC, class=class="str">"cmt">// The decrease in the session close price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_AW_INC, class=class="str">"cmt">// The increase in the average weighted session price exceeds the specified value SYMBOL_EVENT_SESSION_AW_DEC, class=class="str">"cmt">// The decrease in the average weighted session price exceeds the specified value }; class="macro">#define SYMBOL_EVENTS_NEXT_CODE(SYMBOL_EVENT_SESSION_AW_DEC+class="num">1) class=class="str">"cmt">// The code of the next event after the last symbol event code class CBaseEvent : class="kw">public CObject { class="kw">private: ENUM_BASE_EVENT_REASON m_reason; class="type">int m_event_id; class="type">class="kw">double m_value; class="kw">public: ENUM_BASE_EVENT_REASON Reason(class="type">void) class="kw">const { class="kw">return this.m_reason; } };
「事件基类的取值与比对逻辑」
在 MT5 自定义事件体系里,CBaseEvent 作为所有事件对象的父类,先把最基础的三个字段暴露出来:事件编号、触发原因、关联数值。下面这段是类内核心方法的直接实现,复制进 MQ5 文件即可编译。 [CODE] int ID(void) const { return this.m_event_id; } double Value(void) const { return this.m_value; } //--- Constructor CBaseEvent(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value) : m_reason(reason), m_event_id(event_id), m_value(value){} //--- Comparison method to search for identical event objects virtual int Compare(const CObject *node,const int mode=0) const { const CBaseEvent *compared=node; return ( this.Reason()>compared.Reason() ? 1 : this.Reason()<compared.Reason() ? -1 : this.ID()>compared.ID() ? 1 : this.ID()<compared.ID() ? -1 : 0 } [/CODE] 逐行看:ID() 与 Value() 是两个只读 getter,分别回传 m_event_id 和 m_value,const 限定保证不在取值时篡改成员。构造函数用初始化列表一次性写入 reason、event_id、value 三个私有变量,比在函数体内赋值更省开销。 Compare() 是给 CObject 容器(如 CArrayObj)做查重和排序用的虚函数。它先把传入的基类指针 node 向下转型为 CBaseEvent*,再按「原因优先、编号次之」的两级规则返回 1 / -1 / 0。外汇与贵金属行情事件高频推送,若用默认地址比较会漏掉同因同号事件,这套比对能让列表自动去重。 实盘里你可以把 mode 参数留默认 0,直接丢进 CArrayObj::Search 做存在性判断;若事件量日均超过 5000 条,建议先按 Reason 分桶再比 ID,检索耗时可能从 O(n) 降到接近 O(log n)。
class="type">int ID(class="type">void) class="kw">const { class="kw">return this.m_event_id; } class="type">class="kw">double Value(class="type">void) class="kw">const { class="kw">return this.m_value; } class=class="str">"cmt">//--- Constructor CBaseEvent(class="kw">const class="type">int event_id,class="kw">const ENUM_BASE_EVENT_REASON reason,class="kw">const class="type">class="kw">double value) : m_reason(reason), m_event_id(event_id), m_value(value){} class=class="str">"cmt">//--- Comparison method to search for identical event objects class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CBaseEvent *compared=node; class="kw">return ( this.Reason()>compared.Reason() ? class="num">1 : this.Reason()<compared.Reason() ? -class="num">1 : this.ID()>compared.ID() ? class="num">1 : this.ID()<compared.ID() ? -class="num">1 : class="num">0 }
◍ 库对象的基类骨架与属性容量
MQL5 控件库把所有图形对象抽象到一个 CBaseObj 基类,它继承自标准库的 CObject。理解这层继承关系,是后续改写自定义控件的前提。 基类用宏 CONTROLS_TOTAL 把控件属性数组的横向容量锁死为 10,任何派生类的属性表都按 [][10] 的二维结构排布,超界会直接编译报错。 私有段里 m_long_prop_total 与 m_double_prop_total 分别记录长整型和双精度属性的计数,配合模板函数 FillPropertySettings 把属性与事件 ID 写进 array 和 array_prev,用于比对前后帧变化。 受保护成员给出了运行时骨架:m_list_events_base 与 m_list_events 两个 CArrayObj 分别存基础事件和对象事件;m_tick 接实时报价,m_hash_sum 与 m_hash_sum_prev 用哈希和判断数据是否变动;m_digits_currency、m_global_error、m_chart_id、m_is_event、m_event_code 则覆盖精度、错误、图表绑定与事件状态。外汇与贵金属波动快,这类哈希比对若精度设错,可能漏掉关键报价跳变,实盘前务必在 MT5 用 Print 打出 m_digits_currency 核对。
class="macro">#define CONTROLS_TOTAL(class="num">10) class CBaseObj : class="kw">public CObject { class="kw">private: class="type">int m_long_prop_total; class="type">int m_double_prop_total; class=class="str">"cmt">//--- Fill in the object class="kw">property array class="kw">template<class="kw">typename T> class="type">bool FillPropertySettings(class="kw">const class="type">int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],class="type">int &event_id); class="kw">protected: CArrayObj m_list_events_base; class=class="str">"cmt">// Object base event list CArrayObj m_list_events; class=class="str">"cmt">// Object event list class="type">MqlTick m_tick; class=class="str">"cmt">// Tick structure for receiving quote data class="type">class="kw">double m_hash_sum; class=class="str">"cmt">// Object data hash sum class="type">class="kw">double m_hash_sum_prev; class=class="str">"cmt">// Object data hash sum during the previous check class="type">int m_digits_currency; class=class="str">"cmt">// Number of decimal places in an account currency class="type">int m_global_error; class=class="str">"cmt">// Global error code class="type">long m_chart_id; class=class="str">"cmt">// Control program chart ID class="type">bool m_is_event; class=class="str">"cmt">// Object event flag class="type">int m_event_code; class=class="str">"cmt">// Object event code
对象属性追踪的内存布局
在 MT5 里做图形对象事件监控,核心是把每个被追踪对象的属性变化塞进二维数组。上面这段类成员定义给出了一个可直接抄的骨架:整数属性和实数属性分开存,当前值与上次检查值各占一套数组,维度都是 [属性索引][CONTROLS_TOTAL]。 数组第二维的 10 个槽位不是随便排的。索引 0~1 存属性增、减的阈值,2 存控制水位,3 是属性现值,4 是变动量,5~9 则是五类越界/触价布尔标志。实盘里你若只盯 OBJ_PROP_PRICE 一个属性,CONTROLS_TOTAL 设 10 就意味着单对象静态占用约 80 字节(long/double 各 8 字节 ×10),挂 500 个对象也就 40KB,内存压力可忽略。 TickTime() 那个内联函数暴露了一个跨版本坑:MQL5 的 MqlTick 带 time_msc(毫秒),而 MQL4 只有 time(秒)。代码用 #ifdef __MQL5__ 在编译期分流,MQL4 分支手动 ×1000。你在 MT5 回测里若发现事件触发时间和肉眼 Tick 对不上,先确认是不是自己抄成了 ×1000 的兼容写法。 别把正态当圣经:这些数组只负责“记差”,真正发事件要靠你在 OnTick 里比对 m_*_prop_event 与 m_*_prop_event_prev 的对应槽位。外汇与贵金属杠杆高、跳空频繁,属性阈值设太窄会在数据缺口后连发误报。
class="type">int m_event_id; class=class="str">"cmt">// Event ID(equal to the object class="kw">property value) class="type">class="kw">string m_name; class=class="str">"cmt">// Object name class="type">class="kw">string m_folder_name; class=class="str">"cmt">// Name of the folder storing CBaseObj descendant objects class="type">bool m_first_start; class=class="str">"cmt">// First launch flag class="type">int m_type; class=class="str">"cmt">// Object type(corresponds to the collection IDs) class=class="str">"cmt">//--- Data in the array cells class=class="str">"cmt">//--- Data for storing, controlling and returning tracked properties: class=class="str">"cmt">//--- [Property index][class="num">0] Controlled class="kw">property increase value class=class="str">"cmt">//--- [Property index][class="num">1] Controlled class="kw">property decrease value class=class="str">"cmt">//--- [Property index][class="num">2] Controlled class="kw">property value level class=class="str">"cmt">//--- [Property index][class="num">3] Property value class=class="str">"cmt">//--- [Property index][class="num">4] Property value change class=class="str">"cmt">//--- [Property index][class="num">5] Flag of a class="kw">property change exceeding the increase value class=class="str">"cmt">//--- [Property index][class="num">6] Flag of a class="kw">property change exceeding the decrease value class=class="str">"cmt">//--- [Property index][class="num">7] Flag of a class="kw">property increase exceeding the control level class=class="str">"cmt">//--- [Property index][class="num">8] Flag of a class="kw">property decrease being less than the control level class=class="str">"cmt">//--- [Property index][class="num">9] Flag of a class="kw">property value being equal to the control level class="type">long m_long_prop_event[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s integer properties values and controlled class="kw">property change values class="type">class="kw">double m_double_prop_event[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s real properties values and controlled class="kw">property change values class="type">long m_long_prop_event_prev[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s controlled integer properties values during the previous check class="type">class="kw">double m_double_prop_event_prev[][CONTROLS_TOTAL]; class=class="str">"cmt">// The array for storing object&class="macro">#x27;s controlled real properties values during the previous check class=class="str">"cmt">//--- Return(class="num">1) time in milliseconds, (class="num">2) milliseconds from the class="type">MqlTick time value class="type">long TickTime(class="type">void) class="kw">const { class="kw">return class="macro">#ifdef __MQL5__ this.m_tick.time_msc class="macro">#else this.m_tick.time*class="num">1000 class="macro">#endif ; }
「事件对象里的毫秒与属性控制接口」
这一组方法暴露了价格行为监控对象在 MT5 下的底层取数能力。MSCfromTime 在 __MQL5__ 编译宏下直接取 TickTime()%1000 的余数,返回 ushort 类型的毫秒分量;非 MQL5 环境恒返回 0,说明跨平台回测时毫秒精度会丢失,做高频 tick 统计要留意。 IsPresentEventFlag 用位与运算判断某个变更代码是否落在事件掩码里:((this.m_event_code & change_code)==change_code) 意味着传入的 change_code 必须是已置位位的子集才会返回 true,写多事件过滤时别把掩码当等值比较用。 DigitsCurrency 返回账户货币的小数位,GetDigits 则按 double 实际值推算小数位,两者在 XAUUSD 这类 2 位报价与部分外币对 3~5 位报价的混合账户里可能不一致,显示金额前最好显式取 DigitsCurrency。 SetControlDataArraySizeLong / Double 分别预分配整数与实数受控属性数组,CheckControlDataArraySize 默认只查长整型数组(check_long=true)。若你往 double 数组塞了超过预设 size 的索引,不调 SetControlDataArraySizeDouble 会越界报错,EA 初始化阶段应先按监控指标数量把两个 size 都设好。 后面那串模板方法(SetControlledValue、SetControlledValueINC/DEC/LEVEL 及对应 Flag 系列)用 typename T 泛型统一处理整型与浮点受控量,调用时编译器按传入 value 推导类型,省去重载爆炸。实盘里接小布 AIGC 信号时,可把这些模板绑到持仓浮盈、价差突变等 double 属性上,超 LEVEL 即触发告警——外汇与贵金属杠杆高,信号仅作概率参考,仓位仍须自担风险。
class="type">class="kw">ushort MSCfromTime(class="kw">const class="type">long time_msc) class="kw">const { class="kw">return class="macro">#ifdef __MQL5__ class="type">class="kw">ushort(this.TickTime()%class="num">1000) class="macro">#else class="num">0 class="macro">#endif ; } class=class="str">"cmt">//--- class="kw">return the flag of the event code presence in the event object class="type">bool IsPresentEventFlag(class="kw">const class="type">int change_code) class="kw">const { class="kw">return (this.m_event_code & change_code)==change_code; } class=class="str">"cmt">//--- Return the number of decimal places of the account currency class="type">int DigitsCurrency(class="type">void) class="kw">const { class="kw">return this.m_digits_currency; } class=class="str">"cmt">//--- Returns the number of decimal places in the &class="macro">#x27;class="type">class="kw">double&class="macro">#x27; value class="type">int GetDigits(class="kw">const class="type">class="kw">double value) class="kw">const; class=class="str">"cmt">//--- Set the size of the array of controlled(class="num">1) integer and(class="num">2) real object properties class="type">bool SetControlDataArraySizeLong(class="kw">const class="type">int size); class="type">bool SetControlDataArraySizeDouble(class="kw">const class="type">int size); class=class="str">"cmt">//--- Check the array size of object properties class="type">bool CheckControlDataArraySize(class="type">bool check_long=true); class=class="str">"cmt">//--- Set the(class="num">1) controlled value and(class="num">2) object class="kw">property change value class="kw">template<class="kw">typename T> class="type">void SetControlledValue(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledChangedValue(class="kw">const class="type">int class="kw">property,class="kw">const T value); class=class="str">"cmt">//--- Set the value of the pbject class="kw">property controlled(class="num">1) increase, (class="num">2) decrease, (class="num">3) control level class="kw">template<class="kw">typename T> class="type">void SetControlledValueINC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledValueDEC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledValueLEVEL(class="kw">const class="type">int class="kw">property,class="kw">const T value); class=class="str">"cmt">//--- Set the flag of a class="kw">property change exceeding the(class="num">1) increase and(class="num">2) decrease values class="kw">template<class="kw">typename T> class="type">void SetControlledFlagINC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledFlagDEC(class="kw">const class="type">int class="kw">property,class="kw">const T value); class=class="str">"cmt">//--- Set the flag of a class="kw">property change(class="num">1) exceeding, (class="num">2) being less than the control level, (class="num">3) being equal to the level