DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库·进阶篇
(2/3)· 单一品种时序已不够用,如何用集合对象统管全市场多周期历史数据
◍ 库对象的扩展基类怎么搭
在 MT5 自建指标或面板时,给所有库对象写一个扩展基类能省掉大量重复代码。下面这段从实际工程里抽出来的 CBaseObjExt 就是典型做法:在 CBaseObj 基础上挂事件列表、Tick 结构和哈希校验字段。 宏 CONTROLS_TOTAL 写死为 10,意味着任意对象的属性二维数组列宽被锁死,超出的控件属性在编译期就会越界。私有里 m_long_prop_total / m_double_prop_total 记录两类属性的计数,FillPropertySettings 用模板把当前值与上次值写进 array 和 array_prev,顺带回写 event_id,方便后面比对异动。 受保护成员才是重点:m_tick 接实时报价,m_hash_sum 与 m_hash_sum_prev 做两轮数据指纹,只有哈希变了才置 m_is_event,m_event_code 和 m_event_id 再标记具体事件。外汇和贵金属波动快,这种哈希比对能把无效刷新压下去,但高频行情下仍可能漏判,属概率性过滤而非绝对保险。 开 MT5 建个空 EA 把这段类头塞进去,先只留 CONTROLS_TOTAL(10) 和 m_tick,编译过一遍就能确认你的环境跟这套基类契约不冲突。
class="macro">#define CONTROLS_TOTAL(class="num">10) class CBaseObjExt : class="kw">public CBaseObj { 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">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) };
「事件追踪数组与基础取值接口」
控价逻辑里,先把被监控的属性变化存进二维数组,每一行对应一个属性索引,列宽由 CONTROLS_TOTAL 决定,共 10 列。第 0、1 列是预设的增/减阈值,第 2 列是控制位准,第 3、4 列存当前值与变动量,第 5~9 列则是五种越界/触位旗标,用来在后续判断里直接读位。 整数类与实数类分开存放:m_long_prop_event 和 m_double_prop_event 记当前帧,m_long_prop_event_prev 和 m_double_prop_event_prev 留上一帧,两帧相减才知道属性是往上还是往下跳。 取数接口已经写好,TickTime() 在 MQL5 下直接回传 time_msc 毫秒时间戳,MSCfromTime() 只剥出千毫秒内的尾数(MQL4 分支恒回 0)。IsPresentEventFlag() 用位与判断某个 change_code 是否已在事件码里置位,比挨个 bool 判断省事。 DigitsCurrency() 回传账户货币小数位,GetDigits(double) 按值动态算精度;数组尺寸由 SetControlDataArraySizeLong/Double 按监控属性数量扩,没扩够就写越界。开 MT5 把这段头文件贴进类定义,编译过说明你的 CONTROLS_TOTAL 至少是 10。
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 ; } 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
把属性变化塞进 long 的字节里
这套对象事件封装里,最容易被忽略的是 UshortToLong 和 UshortToByte:它们把 16 位的 ushort 值按字节位塞进 64 位 long 的指定偏移,避免为每个监控属性单独开数组。
CheckControlDataArraySize(bool check_long=true) 在每次写入前核对数组边界,默认只查 long 类属性数组;若你后续混用了 double 类属性,需手动传 false 补查,否则越界写入会让 MT5 终端在实时行情推送时静默丢事件。
| 三个模板方法 `SetControlledValueINC / DEC / LEVEL` 对应属性「增量、减量、控制阈值」三种监听态。内部用 `m_long_prop_event[property][0 | 1 | 2]` 存整数类,用 `m_double_prop_event[property-m_long_prop_total][0 | 1 | 2]` 存实数类——注意 double 类的索引要减去整数属性总数偏移。 |
|---|
取数接口全是 inline const:例如 GetControlledLongValueINC 直接返回 [property][0],GetControlledDoubleValueDEC 返回 [property-m_long_prop_total][1]。在 EA 的 OnTick 里轮询这些 getter,就能在不挂定时器的情况下捕获对象属性异动,外汇与贵金属波动快、杠杆高,此类监听仍可能因 tick 跳空漏掉瞬时变化,实盘前务必用历史回放验证命中率。
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(class="kw">const class="type">class="kw">ushort ushort_value,class="kw">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(class="kw">const class="type">class="kw">ushort value,class="kw">const class="type">uchar to_byte) class="kw">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(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">//--- Return the set value of the controlled(class="num">1) integer and(class="num">2) real object properties increase class="type">long GetControlledLongValueINC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">0]; } class="type">class="kw">double GetControlledDoubleValueINC(class="kw">const class="type">int class="kw">property) class="kw">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(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">1]; } class="type">class="kw">double GetControlledDoubleValueDEC(class="kw">const class="type">int class="kw">property) class="kw">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(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">2]; }
◍ 受控属性取值与变动标记的读取接口
在 MT5 的事件封装类里,整数与实数属性被分存于两个二维数组:m_long_prop_event 与 m_double_prop_event。实数属性因为排在整数属性之后,索引时要先减去 m_long_prop_total 做偏移,否则会越界读到错误内存。 下面这段是直接从类定义里抽出的读取方法,覆盖了当前值、变动值、以及增减越界标记三类。注意数组第二维的硬编码下标:3 是当前值、4 是变动值、5 是增量越界、6 是减量越界。 double GetControlledDoubleValueLEVEL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][2]; } // 返回受控实数属性的控制水位,下标 2 与其他读取接口错开,专用于 level 判定 long GetPropLongValue(const int property) const { return this.m_long_prop_event[property][3]; } double GetPropDoubleValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][3]; } long GetPropLongChangedValue(const int property) const { return this.m_long_prop_event[property][4]; } double GetPropDoubleChangedValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][4]; } long GetPropLongFlagINC(const int property) const { return this.m_long_prop_event[property][5]; } double GetPropDoubleFlagINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][5]; } long GetPropLongFlagDEC(const int property) const { return this.m_long_prop_event[property][6]; } double GetPropDoubleFlagDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][6]; } 实盘写 EA 时,若你监控 OBJ_RECTANGLE 的价位变动,调用 GetPropDoubleFlagINC 拿到非零值,意味着该实数属性向上击穿了预设增量阈值,可据此触发告警或挂单逻辑。外汇与贵金属波动剧烈,这类阈值需随品种点值动态换算,盲目套用固定点数大概率在高波动时段频繁误触发。 开 MT5 把上面方法塞进你自己的 CObject 派生类,打印 m_long_prop_total 与数组实际长度,能直接验证偏移逻辑是否和你的属性枚举对齐。
class="type">class="kw">double GetControlledDoubleValueLEVEL(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">2]; } class="type">long GetPropLongValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">3]; } class="type">class="kw">double GetPropDoubleValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">3]; } class="type">long GetPropLongChangedValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">4]; } class="type">class="kw">double GetPropDoubleChangedValue(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">4]; } class="type">long GetPropLongFlagINC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">5]; } class="type">class="kw">double GetPropDoubleFlagINC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">5]; } class="type">long GetPropLongFlagDEC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">6]; } class="type">class="kw">double GetPropDoubleFlagDEC(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">6]; }
「属性越界与事件标记的取数接口」
在盯盘类的内部存储里,整数型与双精度型属性各自维护了一张事件矩阵。整数属性走 m_long_prop_event[property][索引],双精度属性因为被压在整数属性之后,取数时要先减去 m_long_prop_total 偏移量再定位 m_double_prop_event。 越界判断分三种状态:索引 7 是「增幅超过控制线」的标记,8 是「降幅低于控制线」,9 是「等于控制线」。GetPropLongFlagMORE 返回 m_long_prop_event[property][7],GetPropDoubleFlagLESS 则返回 m_double_prop_event[property-m_long_prop_total][8],这套映射在回测里能直接区分价格行为是破位还是触线。 事件系统方面,EventAdd 负责把带 lparam/dparam/sparam 的底层事件塞进列表,EventBaseAdd 多带一个 ENUM_BASE_EVENT_REASON 用来标注触发原因。SetEvent 只是翻转 m_is_event 这个布尔旗标,告诉你这一帧对象数据里到底有没有事发生。 外汇与贵金属波动大、杠杆高,这类属性监视逻辑只解决「信号有没有」,不解决「该不该下单」,实盘前务必在 MT5 策略测试器里跑一遍确认索引不越界。
class="type">long GetPropLongFlagMORE(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">7]; } class="type">class="kw">double GetPropDoubleFlagMORE(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">7]; } class="type">long GetPropLongFlagLESS(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">8]; } class="type">class="kw">double GetPropDoubleFlagLESS(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">8]; } class="type">long GetPropLongFlagEQUAL(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_long_prop_event[class="kw">property][class="num">9]; } class="type">class="kw">double GetPropDoubleFlagEQUAL(class="kw">const class="type">int class="kw">property) class="kw">const { class="kw">return this.m_double_prop_event[class="kw">property-this.m_long_prop_total][class="num">9]; } class="type">void ResetChangesParams(class="type">void); class="kw">virtual class="type">void ResetControlsParams(class="type">void); class="type">bool EventAdd(class="kw">const class="type">class="kw">ushort event_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">bool EventBaseAdd(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); class="type">void SetEvent(class="kw">const class="type">bool flag) { this.m_is_event=flag; }
事件对象的接口与魔数位布局
在 MT5 自定义事件系统里,CBaseObj 派生类通过一组 getter 把内部状态暴露给上层逻辑。IsEvent() 用 m_is_event 标志区分当前实例是否承载了事件;GetListEvents() 直接返回 m_list_events 指针,外部可遍历对象事件链表而不必拷贝。 GetEventsTotal() 的实现只有一行:return this.m_list_events.Total(); 这意味着事件计数完全依赖底层 CArrayObj 的节点数,若你在 Refresh() 前调用,拿到的可能是上一次扫描的残留值。 EventDescription() 是个多参纯虚风格接口,入参包含 property、reason、source、value、property_descr 与 digits,用来拼出人类可读的事件说明,digits 控制浮点字段的小数位,写日志或推送到「小布盯盘」面板时别漏传。 魔数(magic number)的 int 值被切成 4 个字节:bit 32|31–24 为 byte3,23–16 为 byte2,15–8 为 byte1,7–0 为 byte0。把订单识别信息塞进 magic 时,按字节规划能避免和平台保留位撞车,外汇与贵金属杠杆高,误判 magic 可能导致平仓逻辑错配,实盘前务必在策略测试器跑通。 Refresh() 标了 virtual,子类应重写它来拉取最新盘面或对象变动,再调用基类的 CBaseObj::Refresh() 做统一登记,否则事件链可能断在中间层。
class="type">bool IsEvent(class="type">void) class="kw">const { class="kw">return this.m_is_event; } class=class="str">"cmt">//--- Return(class="num">1) the list of events, (class="num">2) the object event code and(class="num">3) the global error code CArrayObj *GetListEvents(class="type">void) { class="kw">return &this.m_list_events; } class="type">int GetEventCode(class="type">void) class="kw">const { class="kw">return this.m_event_code; } class=class="str">"cmt">//--- Return(class="num">1) an event object and(class="num">2) a base event by its number in the list CEventBaseObj *GetEvent(class="kw">const class="type">int shift=WRONG_VALUE,class="kw">const class="type">bool check_out=true); CBaseEvent *GetEventBase(class="kw">const class="type">int index); class=class="str">"cmt">//--- Return the number of(class="num">1) object events class="type">int GetEventsTotal(class="type">void) class="kw">const { class="kw">return this.m_list_events.Total(); } class=class="str">"cmt">//--- Update the object data to search for changes(Calling from the descendants: CBaseObj::Refresh()) class="kw">virtual class="type">void Refresh(class="type">void); class=class="str">"cmt">//--- Return an object event description class="type">class="kw">string EventDescription(class="kw">const class="type">int class="kw">property, class="kw">const ENUM_BASE_EVENT_REASON reason, class="kw">const class="type">int source, class="kw">const class="type">class="kw">string value, class="kw">const class="type">class="kw">string property_descr, class="kw">const class="type">int digits); class=class="str">"cmt">//--- Data location in the magic number class="type">int value class=class="str">"cmt">//----------------------------------------------------------- class=class="str">"cmt">// bit class="num">32|class="num">31 class="num">24|class="num">23 class="num">16|class="num">15 class="num">8|class="num">7 class="num">0| class=class="str">"cmt">//----------------------------------------------------------- class=class="str">"cmt">// byte | class="num">3 | class="num">2 | class="num">1 | class="num">0 | class=class="str">"cmt">//-----------------------------------------------------------