DoEasy 函数库中的价格(第六十三部分):市场深度及其抽象请求类·进阶篇
(2/3)·从 OnBookEvent 到订单对象类,把四种盘口挂单收敛成供给与需求两条主线
「把 DOM 订单抽象成可排序的对象类」
在 MT5 里做深度盘口(DOM)分析,第一步是把每一笔挂单封装成对象,而不是散着读 MqlBookInfo 结构。先在函数库头文件里补三组枚举:整数型、实数型、字符串型属性,再加一个 ENUM_MBOOK_ORD_STATUS 区分买方挂单与卖方挂单。 按状态归类后,能一键筛出需求侧或供应侧的全部挂单,后续做失衡计算不用每次遍历全簿。整数属性枚举里目前定义了 STATUS、TYPE、VOLUME 三项,MBOOK_ORD_PROP_INTEGER_TOTAL 写死为 3,若你以后加字段记得同步改这个宏,否则排序会漏属性。 CMarketBookOrd 类继承基准对象 CBaseObj,构造函数直接吃 DOM 传来的订单结构,同时由派生类告知这笔单是买还是卖。类内 Compare() 返回 -1 / 0 / +1 三态,用于按指定属性排大小;CompareAll() 则逐属性比对,全相等才返 true,用来判重单。 日志方法分三个循环打印整数、实数、字符串属性说明,调试时开 MT5 专家日志就能看到每笔挂单的对象化结果。下一篇要建四个派生类,在初始化列表里写死各自状态,届时 new 一个对象就能自动归类。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Data for working with DOM | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of possible DOM events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define MBOOK_ORD_EVENTS_NEXT_CODE(SERIES_EVENTS_NEXT_CODE+class="num">1) class=class="str">"cmt">// The code of the next event after the last DOM event code class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Abstract DOM type(status) | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_MBOOK_ORD_STATUS { MBOOK_ORD_STATUS_BUY, class=class="str">"cmt">// Buy side MBOOK_ORD_STATUS_SELL, class=class="str">"cmt">// Sell side }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Integer properties of DOM order | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_MBOOK_ORD_PROP_INTEGER { MBOOK_ORD_PROP_STATUS = class="num">0, class=class="str">"cmt">// Order status MBOOK_ORD_PROP_TYPE, class=class="str">"cmt">// Order type MBOOK_ORD_PROP_VOLUME, class=class="str">"cmt">// Order volume }; class="macro">#define MBOOK_ORD_PROP_INTEGER_TOTAL(class="num">3) class=class="str">"cmt">// Total number of integer properties class="macro">#define MBOOK_ORD_PROP_INTEGER_SKIP(class="num">0) class=class="str">"cmt">// Number of integer DOM properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Real properties of DOM order | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ DOM 订单属性的枚举与排序基准
在 MT5 的 MarketBook 订单结构里,双精度属性被单独抽成一个枚举:价格(MBOOK_ORD_PROP_PRICE)和扩展精度成交量(MBOOK_ORD_PROP_VOLUME_REAL),宏定义标明这类实数属性共 2 个、不参与排序的有 0 个。 字符串属性目前只有品种名一项(MBOOK_ORD_PROP_SYMBOL),其枚举值要叠加前面整数与双精度属性的总数偏移,实数总数 1 个。 排序枚举 ENUM_SORT_MBOOK_ORD_MODE 把三类属性映射成连续排序键:整数类从 0 起(状态、类型、成交量),双精度类由 FIRST_MB_DBL_PROP 承接,字符串类由 FIRST_MB_STR_PROP 承接。 开 MT5 把这段头文件塞进 Include 目录,直接打印 SORT_BY_MBOOK_ORD_PRICE 的枚举值,能验证它等于整数属性可用数,从而确认深度图订单按价排序的底层偏移。
enum ENUM_MBOOK_ORD_PROP_DOUBLE { MBOOK_ORD_PROP_PRICE = MBOOK_ORD_PROP_INTEGER_TOTAL, class=class="str">"cmt">// Order price MBOOK_ORD_PROP_VOLUME_REAL, class=class="str">"cmt">// Extended accuracy order volume }; class="macro">#define MBOOK_ORD_PROP_DOUBLE_TOTAL(class="num">2) class=class="str">"cmt">// Total number of real properties class="macro">#define MBOOK_ORD_PROP_DOUBLE_SKIP(class="num">0) class=class="str">"cmt">// Number of real properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| String properties of DOM order | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_MBOOK_ORD_PROP_STRING { MBOOK_ORD_PROP_SYMBOL = (MBOOK_ORD_PROP_INTEGER_TOTAL+MBOOK_ORD_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Order symbol name }; class="macro">#define MBOOK_ORD_PROP_STRING_TOTAL(class="num">1) class=class="str">"cmt">// Total number of class="type">class="kw">string properties class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible sorting criteria of DOM orders | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_MB_DBL_PROP(MBOOK_ORD_PROP_INTEGER_TOTAL-MBOOK_ORD_PROP_INTEGER_SKIP) class="macro">#define FIRST_MB_STR_PROP(MBOOK_ORD_PROP_INTEGER_TOTAL-MBOOK_ORD_PROP_INTEGER_SKIP+MBOOK_ORD_PROP_DOUBLE_TOTAL-MBOOK_ORD_PROP_DOUBLE_SKIP) enum ENUM_SORT_MBOOK_ORD_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_MBOOK_ORD_STATUS = class="num">0, class=class="str">"cmt">// Sort by order status SORT_BY_MBOOK_ORD_TYPE, class=class="str">"cmt">// Sort by order type SORT_BY_MBOOK_ORD_VOLUME, class=class="str">"cmt">// Sort by order volume class=class="str">"cmt">//--- Sort by real properties SORT_BY_MBOOK_ORD_PRICE = FIRST_MB_DBL_PROP, class=class="str">"cmt">// Sort by order price SORT_BY_MBOOK_ORD_VOLUME_REAL, class=class="str">"cmt">// Sort by extended accuracy order volume class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_MBOOK_ORD_SYMBOL = FIRST_MB_STR_PROP, class=class="str">"cmt">// Sort by symbol name };
深度簿订单对象的属性存取设计
做 DOM(深度簿)分析时,把每一笔挂单抽象成一个对象比直接裸读 MqlBookInfo 数组更利于扩展。下面这个类从 CBaseObj 继承,把订单的整数、实数、字符串三类属性分别塞进三个定长数组,用枚举偏移直接寻址。 私有成员里 m_long_prop 长度为 MBOOK_ORD_PROP_INTEGER_TOTAL,m_double_prop 和 m_string_prop 则分别对应 DOUBLE 与 STRING 的总枚举数。IndexProp 两个重载就是关键:实数属性在数组里的真实下标 = (int)property - 整数属性总数,字符串再减去实数总数,避免每次都写魔法数字。 对外只暴露 SetProperty / GetProperty 三套重载。写实数属性时先调 IndexProp 算出偏移再落数组,读的时候同理。这样后续要加新字段,只需改枚举和总数常量,类内部不用动逻辑。 外汇与贵金属市场流动性瞬变,深度簿数据仅反映某一毫秒的挂单分布,据此交易仍属高风险,价格可能随时反向扫单。
class="macro">#class="kw">property strict class=class="str">"cmt">// Necessary for mql4 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "..\..\Services\DELib.mqh" class="macro">#include "..\..\Objects\BaseObj.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DOM abstract order class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMarketBookOrd : class="kw">public CBaseObj { class="kw">private: class="type">int m_digits; class=class="str">"cmt">// Number of decimal places class="type">long m_long_prop[MBOOK_ORD_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[MBOOK_ORD_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties class="type">class="kw">string m_string_prop[MBOOK_ORD_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties class=class="str">"cmt">//--- Return the index of the array the(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_MBOOK_ORD_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-MBOOK_ORD_PROP_INTEGER_TOTAL; } class="type">int IndexProp(ENUM_MBOOK_ORD_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-MBOOK_ORD_PROP_INTEGER_TOTAL-MBOOK_ORD_PROP_DOUBLE_TOTAL; } class="kw">public: class=class="str">"cmt">//--- Set object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">void SetProperty(ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; } class="type">void SetProperty(ENUM_MBOOK_ORD_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_MBOOK_ORD_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 object’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_MBOOK_ORD_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_MBOOK_ORD_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; } };
「深度盘口订单对象的属性读取与比较接口」
在 MT5 的 DOM(深度市场)分析里,CMarketBookOrd 类把每一笔盘口挂单封装成对象,对外暴露了三类属性读取:整型、双精度、字符串。上面这段头文件显示了 GetProperty 的重载实现,字符串属性直接通过 m_string_prop 数组按索引返回,调用方无需关心底层存储。 SupportProperty 的三个虚函数全部返回 true,意味着该订单对象默认宣称支持所有枚举属性;如果你在派生类里只实现部分属性,应当重写对应虚函数返回 false,否则 Print 全量输出时可能打印空值。 对象间排序依赖 Compare 虚函数(mode 参数默认 0,按全部属性比对),而 IsEqual 用于在同质订单中做等值检索。实盘上,黄金 XAUUSD 的盘口在流动性切换时一秒可能刷新十余次,用 IsEqual 快速去重比逐字段手写判断更稳。外汇与贵金属杠杆高、滑点突发,回测 DOM 行为不能替代实盘验证。 构造函数分两层:公开默认构造仅作占位,真正初始化走 protected 参数化构造,传入 ENUM_MBOOK_ORD_STATUS、MqlBookInfo 引用与 symbol 字符串。想自己扩展盘口逻辑,继承后务必调用该 protected 构造,否则 Status() 读出的枚举会是脏值。
class="type">class="kw">string GetProperty(ENUM_MBOOK_ORD_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 itself CMarketBookOrd *GetObject(class="type">void) { class="kw">return &this;} class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_MBOOK_ORD_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Get description of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">class="kw">string GetPropertyDescription(ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_MBOOK_ORD_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Display the description of object properties in the journal(full_prop=true - all properties, false - supported ones only) class="type">void Print(class="kw">const class="type">bool full_prop=false); class=class="str">"cmt">//--- Display a class="type">class="kw">short description of the object in the journal class="kw">virtual class="type">void PrintShort(class="type">void); class=class="str">"cmt">//--- Return the object class="type">class="kw">short name class="kw">virtual class="type">class="kw">string Header(class="type">void); class=class="str">"cmt">//--- Compare CMarketBookOrd objects by all possible properties(to sort the lists by a specified order object class="kw">property) 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 CMarketBookOrd objects by all properties(to search for equal request objects) class="type">bool IsEqual(CMarketBookOrd* compared_req) class="kw">const; class=class="str">"cmt">//--- Default constructor CMarketBookOrd(){;} class="kw">protected: class=class="str">"cmt">//--- Protected parametric constructor CMarketBookOrd(class="kw">const ENUM_MBOOK_ORD_STATUS status,class="kw">const MqlBookInfo &book_info,class="kw">const class="type">class="kw">string symbol); class="kw">public: class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//|Methods of a simplified access to the DOM request object properties| class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//--- Return order(class="num">1) status, (class="num">2) type and(class="num">3) order volume ENUM_MBOOK_ORD_STATUS Status(class="type">void) class="kw">const { class="kw">return (ENUM_MBOOK_ORD_STATUS)this.GetProperty(MBOOK_ORD_PROP_STATUS); }
◍ 从挂单对象里抠出价格和成交量
做深度盘口分析时,单条挂单的属性必须能随时被 EA 调取。下面这段把 CMarketBookOrd 类里的取值接口直接铺开,每一行都对应一个盘口字段,开 MT5 新建类就能照抄。 TypeOrd() 把内部存储的整型强转回 ENUM_BOOK_TYPE,用来区分买序 / 卖序;Volume() 取整型手数,Price() 与 VolumeReal() 分别返回双精度挂单价与真实小数手数——后者在贵金属 0.01 手以下微单里才有意义。 Symbol() 回传该挂单所属品种,Digits() 不走 GetProperty 而是直接读 m_digits 成员,避免在高频 OnBookEvent 里反复调 SymbolInfoInteger 拖慢回测。 构造器里先存一次 Digits,再把 status、type、volume 三个整型属性写进对象。外汇与贵金属盘口跳动极快,这类封装能降低重复系统调用,但杠杆品种滑点风险高,回测和实盘差异可能很大。
ENUM_BOOK_TYPE TypeOrd(class="type">void) class="kw">const { class="kw">return (ENUM_BOOK_TYPE)this.GetProperty(MBOOK_ORD_PROP_TYPE); } class="type">long Volume(class="type">void) class="kw">const { class="kw">return this.GetProperty(MBOOK_ORD_PROP_VOLUME); } class=class="str">"cmt">//--- Return(class="num">1) the price and(class="num">2) extended accuracy order volume class="type">class="kw">double Price(class="type">void) class="kw">const { class="kw">return this.GetProperty(MBOOK_ORD_PROP_PRICE); } class="type">class="kw">double VolumeReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(MBOOK_ORD_PROP_VOLUME_REAL); } class=class="str">"cmt">//--- Return(class="num">1) order symbol and(class="num">2) symbol&class="macro">#x27;s Digits class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.GetProperty(MBOOK_ORD_PROP_SYMBOL); } class="type">int Digits() class="kw">const { class="kw">return this.m_digits; } class=class="str">"cmt">//--- Return the description of order(class="num">1) type(ENUM_BOOK_TYPE) and(class="num">2) status(ENUM_MBOOK_ORD_STATUS) class="kw">virtual class="type">class="kw">string TypeDescription(class="type">void) class="kw">const { class="kw">return this.StatusDescription(); } class="type">class="kw">string StatusDescription(class="type">void) class="kw">const; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CMarketBookOrd::CMarketBookOrd(class="kw">const ENUM_MBOOK_ORD_STATUS status,class="kw">const MqlBookInfo &book_info,class="kw">const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- Save symbol’s Digits this.m_digits=(class="type">int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Save integer object properties this.SetProperty(MBOOK_ORD_PROP_STATUS,status); this.SetProperty(MBOOK_ORD_PROP_TYPE,book_info.type); this.SetProperty(MBOOK_ORD_PROP_VOLUME,book_info.volume); class=class="str">"cmt">//--- Save real object properties
订单簿对象的比较与全等判定
深度重构订单簿类时,比较逻辑决定了挂单在集合里如何排序。CMarketBookOrd::Compare 按 mode 值分流:小于整数属性总数走整型比较,介于整型与双精度总数之间走浮点比较,再往后走字符串比较,每层都返回 1 / -1 / 0 三态。 IsEqual 则是另一种用法——它不排序,只回答「两条挂单是否完全一致」。函数把整数、双精度、字符串三类属性区间拼成连续循环,任一属性不等立即返回 false,全过才算 true。 实盘里这套机制能直接支撑「同价同量挂单去重」:若某经纪商 XAUUSD 在 2035.20 暴露 3 笔各 0.50 手限价单,IsEqual 可在毫秒级识别出重复快照,避免你在小布盯盘里把同一流动性算成 1.5 手。外汇与贵金属盘口碎片化严重,这类判定失误会放大滑点风险,建议开 MT5 用下面代码自测一遍。
class="type">int CMarketBookOrd::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CMarketBookOrd *obj_compared=node; class=class="str">"cmt">//--- compare integer properties of two objects if(mode<MBOOK_ORD_PROP_INTEGER_TOTAL) { class="type">long value_compared=obj_compared.GetProperty((ENUM_MBOOK_ORD_PROP_INTEGER)mode); class="type">long value_current=this.GetProperty((ENUM_MBOOK_ORD_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<MBOOK_ORD_PROP_DOUBLE_TOTAL+MBOOK_ORD_PROP_INTEGER_TOTAL) { class="type">class="kw">double value_compared=obj_compared.GetProperty((ENUM_MBOOK_ORD_PROP_DOUBLE)mode); class="type">class="kw">double value_current=this.GetProperty((ENUM_MBOOK_ORD_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<MBOOK_ORD_PROP_DOUBLE_TOTAL+MBOOK_ORD_PROP_INTEGER_TOTAL+MBOOK_ORD_PROP_STRING_TOTAL) { class="type">class="kw">string value_compared=obj_compared.GetProperty((ENUM_MBOOK_ORD_PROP_STRING)mode); class="type">class="kw">string value_current=this.GetProperty((ENUM_MBOOK_ORD_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="type">bool CMarketBookOrd::IsEqual(CMarketBookOrd *compared_obj) class="kw">const { class="type">int beg=class="num">0, end=MBOOK_ORD_PROP_INTEGER_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_MBOOK_ORD_PROP_INTEGER prop=(ENUM_MBOOK_ORD_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return false; } beg=end; end+=MBOOK_ORD_PROP_DOUBLE_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_MBOOK_ORD_PROP_DOUBLE prop=(ENUM_MBOOK_ORD_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return false; } beg=end; end+=MBOOK_ORD_PROP_STRING_TOTAL; for(class="type">int i=beg; i<end; i++) {