轻松快捷开发 MetaTrader 程序的函数库(第十七部分):函数库对象之间的交互·进阶篇
🔗

轻松快捷开发 MetaTrader 程序的函数库(第十七部分):函数库对象之间的交互·进阶篇

(2/3)· 还在为每个对象手写事件枚举?这篇把交互收敛进基准类,事件量随属性自动伸缩

含代码示例 第 2/3 篇
不少开发者给每个衍生类单独写事件标志,结果枚举越堆越长,改一处牵全身。更隐蔽的坑是:用 LONG_MAX 初始化的属性若没排除,会被误判成有效事件源。对象一多,计时器里的刷新链就变成性能黑洞。

◍ 报价与持仓维度的事件枚举清单

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 终端的事件订阅接口自行验证触发频率。

MQL5 / C++
  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,看自己装的终端版本字段是否一致。

MQL5 / C++
  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)。

MQL5 / C++
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 核对。

MQL5 / C++
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 的对应槽位。外汇与贵金属杠杆高、跳空频繁,属性阈值设太窄会在数据缺口后连发误报。

MQL5 / C++
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 即触发告警——外汇与贵金属杠杆高,信号仅作概率参考,仓位仍须自担风险。

MQL5 / C++
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
把对象事件流交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到对象属性越阈与事件触发状态,你只管调阈值,不用自己写监听循环。

常见问题

在基准类实现通用事件控制,以整数型和实数型属性状态变化为统一事件源,衍生类仅需维护自身属性列表,事件 ID 由属性唯一确定,无需逐类定义标志。
可以。小布盯盘内置了对象属性越阈与事件流的呈现,接入函数库品种页后能省去自行编写监听与日志输出的重复劳动。
LONG_MAX 作为哨兵值使刷新与事件搜索逻辑跳过该属性,既不参与对象事件判定,也免去了在枚举中显式剔除的维护成本。
集合的 Refresh() 遍历保存的对象并调用其自身 Refresh(),基准类 Refresh() 内跟踪衍生对象变化,将事件列表上发至 CEngine 主对象,形成全集合事件掌控。
方向用于区分属性变化语义,例如点差收窄与扩张可触发不同处理逻辑;原因与变化值、事件 ID 一并写入基准事件类,支持按阈值和方向编程响应。