轻松快捷开发 MetaTrader 程序的函数库 (第 三十部分) :延后交易请求 - 管理请求对象·进阶篇
(2/3)· 用虚拟计时器与暂停对象替代 Sleep(),在周末也能跑通延后下单逻辑
「把重复逻辑塞回基类里」
做函数库重构时,第一桩事是把所有对象类里反复出现的日志级别、魔幻数字 ID 存取方法,统一挪到 CBaseObj 这个总基类。子类还没继承的,直接令其父类为 CBaseObj,再从子类私密/公开段删掉已在父类存在的变量与方法,避免两份实现互相打架。 具体落地:TradeObj.mqh、Order.mqh 里包含基类头文件并改继承;Order 类构造器保留魔幻数字里 ID 的位宽,只删掉取值方法本体。Trading.mqh 把集合指针和延后请求指针从 private 提到 protected,因为后续管理延后请求的类要复用;类计时器留空,实际逻辑交给父类计时器。 实测暴露过一个坑:ClosePosition() 部分平仓时偶尔收到无效交易量,甚至发出 0 交易量还跳过验证。修复办法是在创建延后请求时补一段规范化代码——传 -1 或超持仓量就写全量平仓,否则把常规化后的量送进验证方法。 延后请求对象侧也有变动。Defines.mqh 里整数型属性从 19 扩到 22,实数型从 6 扩到 11,并加了新的排序条件。抽象基类 PendRequest.mqh 引入暂停对象,构造时以生成时间和等待时长初始化;比较方法用临时对象跑 CObject::Compare(),相等返回 0,经逻辑取反后对外吐 true,方便判断属性是否被改过。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base object class for all library objects | class=class="str">"cmt">//+------------------------------------------------------------------+ 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(const class="type">int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],class="type">int &event_id); class="kw">protected:
◍ 基类的内部字段清单
在 MT5 自建事件追踪框架时,CBaseObj 派生类靠一组私有成员维持状态。下面这段声明直接决定了对象能否在每次 OnTick 里比对出属性变化。 m_list_events_base 与 m_list_events 都是 CArrayObj 容器,前者存基础事件对象,后者存实际触发的事件实例;m_log_level 用 ENUM_LOG_LEVEL 控制日志粒度,避免刷屏。 m_tick 承接实时报价,m_hash_sum 与 m_hash_sum_prev 分别记录本次与上次的数据哈希,差值非零即代表对象属性可能变动。m_digits_currency 取账户货币小数位,做金额类显示时用得上。 m_global_error、m_chart_id 管错误码与主图绑定;m_is_event、m_event_code、m_event_id 三者配合标记事件是否发生及具体编号。m_name 与 m_folder_name 负责对象寻址,m_first_start 区分首次初始化,m_type 对应集合 ID。 开 MT5 把这套字段原样贴进类声明,编译通过后就能在观察窗口看到每个对象的哈希随报价跳动。外汇与贵金属波动剧烈,这类追踪逻辑仅用于辅助复盘,实盘仍属高风险。
CArrayObj m_list_events_base; class=class="str">"cmt">// Object base event list CArrayObj m_list_events; class=class="str">"cmt">// Object event list ENUM_LOG_LEVEL m_log_level; class=class="str">"cmt">// Logging level 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 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 for storing, controlling and returning tracked properties:
事件对象的属性数组与辅助取值函数
在图形对象事件封装里,受控属性被压进二维数组,列数由 CONTROLS_TOTAL 决定。整数类与实数类分开存:m_long_prop_event 存整型属性现值与变化阈值,m_long_prop_event_prev 存上一次检查的整型快照;double 版本同理,这样跨帧比对才能判断属性是否越界。 数组列索引 0~9 有明确分工:0 是受控增量上限,1 是受控减量下限,2 是控制水位,3 是属性现值,4 是变动量,5~9 是五类越界/触线标志位。写 EA 时直接按索引读,不必每次重新枚举属性名。 几个内联函数值得直接抄进自己的类里。TickTime() 在 MQL5 下返回 time_msc 毫秒时间戳,MQL4 回退到 time*1000;IsPresentEventFlag() 用位与判断某事件码是否命中,比遍历快得多。 账户货币小数位由 DigitsCurrency() 返回 m_digits_currency,做仓位或点值显示时别硬编码 2 位,叉盘和贵金属常常不是这个数。外汇与贵金属杠杆高,属性越界只是信号,实盘前务必在 MT5 策略测试器用真实点差跑一遍。
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) 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 ; } class="type">class="kw">ushort MSCfromTime(const class="type">long time_msc) 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(const class="type">int change_code) 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) 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(const class="type">class="kw">double value) 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(const class="type">int size); class="type">bool SetControlDataArraySizeDouble(const class="type">int size); class=class="str">"cmt">//--- Check the array size of object properties
「对象属性变更的事件封装与取值接口」
在图形对象监控类里,属性变化的捕获被拆成了「增量、减量、临界值」三类,分别由 SetControlledValueINC / DEC / LEVEL 三个模板方法写入。这样分类后,后续 CheckEvents() 只需扫一遍变更列表就能抛出事件,不必每次都全量比对对象属性。 UshortToLong() 负责把一个 16 位无符号数压进 long 的指定字节(to_byte 指明目标偏移),底层由 protected 的 UshortToByte() 完成位运算。这类打包在同时监控几十个对象属性时,能把事件标识压缩进一个 long 数组索引,减少结构体开销。 日志级别用 SetLogLevel() 和 GetLogLevel() 直接读写 m_log_level,是内联实现,调用零成本。取值接口如 GetControlledLongValueINC(property) 直接返回 m_long_prop_event[property][0],即该属性「增量」方向的已设值;双精度版本则用 property 减去 m_long_prop_total 做下标换算,偏移错了会越界。 开 MT5 把下面接口声明的头文件贴进自己的对象监控 EA,先只接两个属性试跑,看 CheckEvents 触发频率。外汇与贵金属波动快,这类监控逻辑若用在实盘需自担高频刷新带来的滑点风险。
class="type">bool CheckControlDataArraySize(class="type">bool check_long=true); class=class="str">"cmt">//--- Check the list of object class="kw">property changes and create an event class="type">void CheckEvents(class="type">void); class=class="str">"cmt">//--- (class="num">1) Pack a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; number to a passed &class="macro">#x27;class="type">long&class="macro">#x27; number class="type">long UshortToLong(const class="type">class="kw">ushort ushort_value,const class="type">uchar to_byte,class="type">long &long_value); class="kw">protected: class=class="str">"cmt">//--- (class="num">1) convert a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; value to a specified &class="macro">#x27;class="type">long&class="macro">#x27; number byte class="type">long UshortToByte(const class="type">class="kw">ushort value,const class="type">uchar to_byte) const; class="kw">public: 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(const class="type">int class="kw">property,const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledValueDEC(const class="type">int class="kw">property,const T value); class="kw">template<class="kw">typename T> class="type">void SetControlledValueLEVEL(const class="type">int class="kw">property,const T value); class=class="str">"cmt">//--- (class="num">1) Set, (class="num">2) class="kw">return the error logging level class="type">void SetLogLevel(const ENUM_LOG_LEVEL level) { this.m_log_level=level; } ENUM_LOG_LEVEL GetLogLevel(class="type">void) const { class="kw">return this.m_log_level; } class=class="str">"cmt">//--- Return the set value of the controlled(class="num">1) integer and(class="num">2) real object properties increase class="type">long GetControlledLongValueINC(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">0]; } class="type">class="kw">double GetControlledDoubleValueINC(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">0]; } class=class="str">"cmt">//--- Return the set value of the controlled(class="num">1) integer and(class="num">2) real object properties decrease class="type">long GetControlledLongValueDEC(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">1]; }
◍ 事件对象里抓属性变化的取值接口
在自建的事件监听类里,整数属性和双精度属性的存储是分开的两个二维数组:m_long_prop_event 与 m_double_prop_event。双精度部分为了不和整数索引撞车,读取时统一用 property 减去 m_long_prop_total 做偏移,这个减操作在下面每个 double 接口里都会出现。 以 GetControlledDoubleValueDEC 为例,它取的是受控双精度属性在[1]位置上的下限阈值;对应的 LEVEL 版本取[2]位置的控制线。整数侧不用减偏移,直接 m_long_prop_event[property][2] 就拿到了 LEVEL。 当前值由 [3] 索引返回:GetPropLongValue 给整数、GetPropDoubleValue 给双精度。变化量放在 [4],超过增量和减量的判定标记则分别在 [5]。写 EA 时若只关心价格突破某条控制线,直接调 GetControlledDoubleValueLEVEL 拿线值,再和 GetPropDoubleValue 的实时值比大小即可,外汇和贵金属波动快,这类比较要意识到滑点和高风险可能让触发偏离。
class="type">class="kw">double GetControlledDoubleValueDEC(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">1]; } class=class="str">"cmt">//--- Return the specified control level of object&class="macro">#x27;s(class="num">1) integer and(class="num">2) real properties class="type">long GetControlledLongValueLEVEL(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">2]; } class="type">class="kw">double GetControlledDoubleValueLEVEL(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">2]; } class=class="str">"cmt">//--- Return the current value of the object(class="num">1) integer and(class="num">2) real class="kw">property class="type">long GetPropLongValue(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">3]; } class="type">class="kw">double GetPropDoubleValue(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">3]; } class=class="str">"cmt">//--- Return the change value of the controlled(class="num">1) integer and(class="num">2) real object class="kw">property class="type">long GetPropLongChangedValue(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">4]; } class="type">class="kw">double GetPropDoubleChangedValue(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">4]; } class=class="str">"cmt">//--- Return the flag of an(class="num">1) integer and(class="num">2) real class="kw">property value change exceeding the increase value class="type">long GetPropLongFlagINC(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">5]; } class="type">class="kw">double GetPropDoubleFlagINC(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">5]; }
属性越界与事件标记的取数接口
这组 getter 把属性变化事件的状态码从内部二维数组里抽出来,整数类走 m_long_prop_event[property][n],实数类走 m_double_prop_event[property - m_long_prop_total][n],偏移量靠 m_long_prop_total 把两类属性隔开。 索引 6 到 9 分别对应 DEC(降幅超控)、MORE(增幅超控)、LESS(降幅低于控线)、EQUAL(等于控线)四种判定。实盘里若想在小布盯盘里捕获「某货币对点差突破控线」这类事件,直接调 GetPropDoubleFlagMORE 比对返回值是否为 1 即可,外汇与贵金属杠杆高、跳空频繁,该标记仅作概率参考。 ResetChangesParams 与虚函数 ResetControlsParams 负责把跟踪和控线变量清零,可在子类重写以适配自定义品种。EventAdd 则把外部事件及原因塞进列表,ushort 事件号加 lparam/dparam/sparam 三元组,是后续回放与 AIGC 标注的入口。
class="type">long GetPropLongFlagDEC(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">6]; } class="type">class="kw">double GetPropDoubleFlagDEC(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">6]; } class=class="str">"cmt">//--- Return the flag of an(class="num">1) integer and(class="num">2) real class="kw">property value increase exceeding the control level class="type">long GetPropLongFlagMORE(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">7]; } class="type">class="kw">double GetPropDoubleFlagMORE(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">7]; } class=class="str">"cmt">//--- Return the flag of an(class="num">1) integer and(class="num">2) real class="kw">property value decrease being less than the control level class="type">long GetPropLongFlagLESS(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">8]; } class="type">class="kw">double GetPropDoubleFlagLESS(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">8]; } class=class="str">"cmt">//--- Return the flag of an(class="num">1) integer and(class="num">2) real class="kw">property being equal to the control level class="type">long GetPropLongFlagEQUAL(const class="type">int class="kw">property) const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">9]; } class="type">class="kw">double GetPropDoubleFlagEQUAL(const class="type">int class="kw">property) const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">9]; } class=class="str">"cmt">//--- Reset the variables of(class="num">1) tracked and(class="num">2) controlled object data(can be reset in the descendants) class="type">void ResetChangesParams(class="type">void); class="kw">virtual class="type">void ResetControlsParams(class="type">void); class=class="str">"cmt">//--- Add the(class="num">1) object event and(class="num">2) the object event reason to the list class="type">bool EventAdd(const class="type">class="kw">ushort event_id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam);