DoEasy 函数库中的时间序列(第五十七部分):存储一次即时报价数据的对象·进阶篇
(2/3)· 很多人直接读 MqlTick 流,却从没想过把单次报价封装成可搜索、可排序的对象
◍ 即时报价数据对象类
在 “New tick” 对象类所在的 \MQL5\Include\DoEasy\Objects\ Ticks\ 文件夹中,创建一个即时报价数据对象类的新文件 DataTick.mqh 。 该对象对于函数库类而言并非什么新事物。 一切都是标准的。 我们来分析一下类主体: 在 最开始的第一篇文章中 曾详细讨论过函数库对象的组成及其创建。 现在,我们遍历主要的变量和方法,并进一步分析其在类中的实现。 在类的私密部分包含辅助变量,存储对象整数型、实数型和字符串型的属性值数组;并返回属性在数组所处的实际索引的方法。 在类的公开部分包含设置指定属性的值,并将其返回给相应对象属性数组的方法;返回对象支持某个属性的标志的方法;对象搜索、比较和排序的方法;以及类构造函数。 显示对象属性说明的方法 , 访问对象属性的简化方法 也放在此处。 现在,我们来看一下类方法的实现。 品种 和已填充当前即时报价数据的结构 也应传递给参数类构造函数: 在构造函数中,简单地在对象属性中填充来自即时报价结构的相应数值,以及从中即时报价数据里获取的品种即可。 依据指定属性比较对象两个参数的方法: 传递给该方法的参数包括,指向所需比较的两个对象的指针,两个对象所欲比较的属性类型。 取决于所传递的当前对象比较模式,比较两个对象的这些属性,如果当前对象的属性值分别大于/小于或等于比较对象的属性值,则返回 1/-1/0。 比较两个对象标识的方法: 此处:指向欲比较对象的指针,传递给该方法。 然后,每个属性组在三重循环中,逐一比较两个对象的连续属性。 如果至少一个属性与所比较对象的相同属性不匹配,则返回 false 。 直至所有三个循环完毕,则返回 true — 两个对象的所有属性都相等。 这意味着对象是相同的。 在日志中显示所有对象属性的方法: 此处:每个对象属性组在三重循环中,利用相应的方法 GetPropertyDescription() 在日志中逐一显示每个连续属性的说明。 如果在方法参数中将 false 值传递给 full_prop 变量,则仅显示对象支持的属性。 对于不支持的属性,在日志里显示不支持该属性,尽管此对象支持所有属性。 但这可以在类的衍生对象中更改。 返回指定对象的 整数型 、 实数型 和 字符串型 属性说明的方法: 此处:取决于传递给方法的属性,返回其字符串说明。 返回即时报价所有标志的说明文字的方法: 在其属性中,每个即时报价都有一个包含一组标志的变量。 这些标志定义了引发该即时报价的事件: 来自 MqlTick 用户指南 为了找出当前即时报价里哪些特定数据被更改,分析其标志: TICK_FLAG_BID — 即时报价出价(Bid)改变 TICK_FLAG_ASK — 即时报价要价(Ask)改变 TICK_FLAG_LAST — 即时报价最后成交价改变 TICK_FLAG_VOLUME — 即时报价交易量改变 TICK_FLAG_BUY — 即时报价结果来自买入交易 TICK_FLAG_SELL — 即时报价结果来自卖出交易 我们有六个方法(按标志的数量)返回每个标志变量的状态标志: 在所讲述的方法中,首先检查变量中存在标志。 取决于是否这种变化可行与否,结果文本应添加换行符+标志说明或空字符串。 在检查完所有标志之后,最终我们得到已编译文本,其中包含变量中存在标志的说明。 如果有若干个标志可用,则每个标志的说明在日志中会以新行开始。 在日志中显示报价数据对象简述的方法: 该方法简单地在日志中 显示其自身的简称 ,该名称由以下方法返回: 在此阶段,即时报价数据对象的
把报价属性塞进三类数组
在 MT5 自建 tick 容器时,整数、双精度、字符串三类属性不能混在一个数组里存。上面这段把 long 型放 m_long_prop,double 型经 IndexProp 偏移后落 m_double_prop,string 型再叠一层偏移进 m_string_prop,偏移基数分别是 TICK_PROP_INTEGER_TOTAL 和 TICK_PROP_DOUBLE_TOTAL。 IndexProp 的两个重载是关键:double 属性索引 = (int)property - TICK_PROP_INTEGER_TOTAL,string 属性索引再减去 TICK_PROP_DOUBLE_TOTAL。这意味着枚举值必须连续编排,否则数组下标会错位。 SetProperty / GetProperty 对称封装了写和读,SupportProperty 默认全返回 true,说明基类不挑食。真要跑起来,开 MT5 新建 EA 把这段贴进 CDataTick 类,故意传一个越界枚举看是否爆数组,比读文档直观。 外汇与贵金属 tick 流受点差跳变影响,这类底层结构在高波动时段可能丢属性,实盘前务必用历史 tick 回放验证。
class="type">class="kw">string m_string_prop[TICK_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties class=class="str">"cmt">//--- Return the index of the array the tick’s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are actually located at class="type">int IndexProp(ENUM_TICK_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-TICK_PROP_INTEGER_TOTAL; } class="type">int IndexProp(ENUM_TICK_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-TICK_PROP_INTEGER_TOTAL-TICK_PROP_DOUBLE_TOTAL;} class="kw">public: class=class="str">"cmt">//--- Set tick’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property class="type">void SetProperty(ENUM_TICK_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; } class="type">void SetProperty(ENUM_TICK_PROP_DOUBLE class="kw">property,class="type">class="kw">double value) { this.m_double_prop[this.IndexProp(class="kw">property)]=value; } class="type">void SetProperty(ENUM_TICK_PROP_STRING class="kw">property,class="type">class="kw">string value) { this.m_string_prop[this.IndexProp(class="kw">property)]=value; } class=class="str">"cmt">//--- Return tick’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property from the properties array class="type">long GetProperty(ENUM_TICK_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return this.m_long_prop[class="kw">property]; } class="type">class="kw">double GetProperty(ENUM_TICK_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; } class="type">class="kw">string GetProperty(ENUM_TICK_PROP_STRING class="kw">property) class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; } class=class="str">"cmt">//--- Return the flag of the tick supporting this class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_TICK_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_TICK_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_TICK_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return itself CDataTick *GetObject(class="type">void) { class="kw">return &this;} class=class="str">"cmt">//--- Compare CDataTick objects with each other by the specified class="kw">property (for sorting the lists by a specified object class="kw">property)
「Tick 数据对象的比较与属性读取接口」
在自定义 CDataTick 类里,Compare 方法被声明为虚函数,接收另一个 CObject 指针与 mode 参数(默认 0),用途是把两个 tick 对象按全部属性比对,方便在容器里检索完全相同的样本。配套的 IsEqual 则直接吃一个 CDataTick* 做相等判断,比走 Compare 更直白。 类里还给了三组 GetPropertyDescription 重载,分别吃 ENUM_TICK_PROP_INTEGER、ENUM_TICK_PROP_DOUBLE、ENUM_TICK_PROP_STRING 枚举,把整数、双精度、字符串类型的 tick 属性翻译成可读描述。Print 方法能按 full_prop 开关决定打印全部属性还是仅受支持项,PrintShort 负责输出精简日志,Header 与 FlagsDescription 则拿来拿对象短名和标志位说明。 简化访问这块最实用:Time() 直接把 TICK_PROP_TIME 强转 datetime 返回,TimeMSC() 取毫秒级时间戳,Volume() 和 Flags() 同理走 GetProperty。你在 EA 里想快速抽某 tick 的成交量与毫秒时间,调这几个内联方法比反复查 MqlTick 结构省事。外汇与贵金属 tick 流受流动性影响跳变频繁,用对象封装做历史回放时须留意高风险。
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=class="str">"cmt">//--- Compare CDataTick objects with each other by all properties(to search equal objects) class="type">bool IsEqual(CDataTick* compared_obj) class="kw">const; class=class="str">"cmt">//--- Constructors CDataTick(){;} CDataTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Descriptions of object tick data properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return description of tick&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property class="type">class="kw">string GetPropertyDescription(ENUM_TICK_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_TICK_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_TICK_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Display the description of tick properties in the journal(full_prop=true - all properties, class="kw">false - supported ones only) class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false); class=class="str">"cmt">//--- Display a class="type">class="kw">short description of the tick in the journal class="kw">virtual class="type">void PrintShort(class="type">void); class=class="str">"cmt">//--- Return the(class="num">1) class="type">class="kw">short name and(class="num">2) description of tick data object flags class="kw">virtual class="type">class="kw">string Header(class="type">void); class="type">class="kw">string FlagsDescription(class="type">void); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of simplified access to tick data object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return tick’s(class="num">1) Time, (class="num">2) time in milliseconds, (class="num">3) volume, (class="num">4) flags class="type">class="kw">datetime Time(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(TICK_PROP_TIME); } class="type">long TimeMSC(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_TIME_MSC); } class="type">long Volume(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_VOLUME); }
◍ 从 Tick 对象直接抠出买卖价与所属 K 线
MQL5 里封装好的 Tick 结构暴露了一组只读方法,不用自己解析原始报价流,直接调成员就能拿到当笔 tick 的 Bid、Ask、Last、VolumeReal 和 Spread。这些方法底层都走 GetProperty,返回 double 类型,精度比老版 Volume 整数量大得多,做逐笔成交分布时更靠谱。 Flags 方法返回 uint 的 TICK_PROP_FLAGS,用来判断这笔 tick 是报价更新还是成交更新,过滤只盯买卖盘跳动时很有用。Symbol 返回所属品种名,跨品种回测时别写死字符串。 TimeBar 是个容易被忽略的落点:传一个 ENUM_TIMEFRAMES 进去,它用 iTime 配合 Index 算出这笔 tick 落在哪根已有 K 线上。做 tick 级形态还原到分钟线时,这比自己算时间整除省事,且能直接对齐历史回放。 外汇和贵金属点差跳变频繁,Spread 返回值受经纪商浮动点差影响,实盘和策略测试器可能差好几个点,验证时务必开 MT5 用真实品种跑一遍。
class="type">uint Flags(class="type">void) class="kw">const { class="kw">return (class="type">uint)this.GetProperty(TICK_PROP_FLAGS); } class="type">class="kw">double Bid(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_BID); } class="type">class="kw">double Ask(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_ASK); } class="type">class="kw">double Last(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_LAST); } class="type">class="kw">double VolumeReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_VOLUME_REAL); } class="type">class="kw">double Spread(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_SPREAD); } class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.GetProperty(TICK_PROP_SYMBOL); } class="type">class="kw">datetime TimeBar(class="kw">const ENUM_TIMEFRAMES timeframe) class="kw">const { class="kw">return ::iTime(this.Symbol(),timeframe,this.Index(timeframe)); }
用位标志拆开每一笔 tick 的变动成分
在自定义行情类里,区分一次 tick 更新到底动了什么,比直接读价格更有用。上面这组方法把 MqlTick 的 flags 字段按位拆开,分别判断 Bid、Ask、Last、Volume、Buy、Sell 六类变动是否发生。 Index() 方法借 iBarShift 把当前 tick 时间映射到指定周期 K 线序号;传 PERIOD_CURRENT 时自动回落到当前图表周期。实盘里若切到 M15 看 EURUSD,调用 Index(PERIOD_M15) 就能直接拿到该 tick 落在第几根 15 分钟 bar,便于做跨周期 tick 统计。
| 六个 IsChange* 方法本质都是同一句位运算:Flags() & 某宏 == 该宏。比如 IsChangeBid 只看 TICK_FLAG_BID 位是否被置起,返回 true 才说明这次推送改了买价。写剥头皮逻辑时,只处理 IsChangeBid() | IsChangeAsk() 的 tick,能把无效刷新过滤掉大半,MT5 回测里 CPU 占用倾向明显下降。 |
|---|
构造器 CDataTick 接收 symbol 与 MqlTick 引用,先抓 SYMBOL_DIGITS 存 m_digits,再把 tick.time 写进 TICK_PROP_TIME。外汇与贵金属杠杆高、点差跳变频繁,这类细粒度 tick 解析只是风控与信号前置的一步,不代表任何方向确定性。
class="type">int Index(class="kw">const ENUM_TIMEFRAMES timeframe) class="kw">const { class="kw">return ::iBarShift(this.Symbol(),(timeframe==PERIOD_CURRENT ? ::Period() : timeframe),this.Time()); } class=class="str">"cmt">//--- Return the flag of(class="num">1) Bid, (class="num">2) Ask, (class="num">3) Last price, (class="num">4) volume change; (class="num">5) buy and(class="num">6) sell trades class="type">bool IsChangeBid() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_BID)==TICK_FLAG_BID); } class="type">bool IsChangeAsk() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_ASK)==TICK_FLAG_ASK); } class="type">bool IsChangeLast() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_LAST)==TICK_FLAG_LAST); } class="type">bool IsChangeVolume() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_VOLUME)==TICK_FLAG_VOLUME); } class="type">bool IsChangeBuy() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_BUY)==TICK_FLAG_BUY); } class="type">bool IsChangeSell() class="kw">const { class="kw">return((this.Flags() & TICK_FLAG_SELL)==TICK_FLAG_SELL); } class=class="str">"cmt">//--- }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CDataTick::CDataTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick) { class=class="str">"cmt">//--- Save symbol’s Digits this.m_digits=(class="type">int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Save integer tick properties this.SetProperty(TICK_PROP_TIME,tick.time);
「用 mode 参数给逐笔 tick 排序」
把真实 tick 落库时,除了基础字段还会顺手算出一个衍生量:spread 直接用 ask 减 bid 写入 TICK_PROP_SPREAD,省得后面每次比点差都现算。symbol 留空就回退到当前图表品种,这样同一个 CDataTick 结构既能存 EURUSD 也能存 XAUUSD 的流。 Compare() 靠 mode 值切三类比较:小于 TICK_PROP_INTEGER_TOTAL 走整型分支,中间段走 double 分支,再往后是 string 分支,每段都返回 1 / -1 / 0 三态,可直接喂给 CArray 的排序方法。 IsEqual() 则是穷举式比对,先跑完所有整型属性(0 到 TICK_PROP_INTEGER_TOTAL),再偏移起点跑双精度、最后跑字符串,任意一项不等就立刻 false。外汇与贵金属 tick 高频写入时,这种全等判断能帮你快速剔除重复包,但注意点差微小跳动也会判不等,回测重放前最好先想清楚要去重到哪个粒度。
this.SetProperty(TICK_PROP_TIME_MSC,tick.time_msc); this.SetProperty(TICK_PROP_VOLUME,tick.volume); this.SetProperty(TICK_PROP_FLAGS,tick.flags); class=class="str">"cmt">//--- Save real tick properties this.SetProperty(TICK_PROP_BID,tick.bid); this.SetProperty(TICK_PROP_ASK,tick.ask); this.SetProperty(TICK_PROP_LAST,tick.last); this.SetProperty(TICK_PROP_VOLUME_REAL,tick.volume_real); class=class="str">"cmt">//--- Save additional tick properties this.SetProperty(TICK_PROP_SPREAD,tick.ask-tick.bid); this.SetProperty(TICK_PROP_SYMBOL,(symbol==NULL || symbol=="" ? ::Symbol() : symbol)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//| Compare CDataTick objects with each other by the specified class="kw">property | class=class="str">"cmt">//+----------------------------------------------------------------------+ class="type">int CDataTick::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CDataTick *obj_compared=node; class=class="str">"cmt">//--- compare integer properties of two objects if(mode<TICK_PROP_INTEGER_TOTAL) { class="type">long value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_INTEGER)mode); class="type">long value_current=this.GetProperty((ENUM_TICK_PROP_INTEGER)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class=class="str">"cmt">//--- compare real properties of two objects else if(mode<TICK_PROP_DOUBLE_TOTAL+TICK_PROP_INTEGER_TOTAL) { class="type">class="kw">double value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_DOUBLE)mode); class="type">class="kw">double value_current=this.GetProperty((ENUM_TICK_PROP_DOUBLE)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class=class="str">"cmt">//--- compare class="type">class="kw">string properties of two objects else if(mode<TICK_PROP_DOUBLE_TOTAL+TICK_PROP_INTEGER_TOTAL+TICK_PROP_STRING_TOTAL) { class="type">class="kw">string value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_STRING)mode); class="type">class="kw">string value_current=this.GetProperty((ENUM_TICK_PROP_STRING)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class="kw">return class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Compare CDataTick objects with each other by all properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CDataTick::IsEqual(CDataTick *compared_obj) class="kw">const { class="type">int beg=class="num">0, end=TICK_PROP_INTEGER_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_TICK_PROP_INTEGER prop=(ENUM_TICK_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false; } beg=end; end+=TICK_PROP_DOUBLE_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_TICK_PROP_DOUBLE prop=(ENUM_TICK_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false; } beg=end; end+=TICK_PROP_STRING_TOTAL;