DoEasy 函数库中的价格(第六十四部分):市场深度,DOM 快照类和快照序列对象(基础篇)
给 DOM 快照建个类
在 DoEasy 函数库第六十四部分里,作者把市场深度(DOM)的瞬时状态封装成了快照类,再把一串快照按时间顺序管起来就成了快照序列对象类。这样在 MT5 里抓盘口变化,不用每次都手写结构体,直接调库就能拿到某一刻的买卖挂单分布。 原文给出的示例发布于 2021 年 5 月 4 日,截至记录时该文在站内有 1788 次浏览、6 条评论,说明这类底层封装对实盘工具开发者确有参考价值。外汇与贵金属品种用 DOM 做微观结构判断时波动极快,属于高风险场景,任何挂单失衡都只是概率倾向而非确定信号。 快照序列对象类最关键的是能回放:把每个 tick 的 DOM 存进序列,后面想复现某段行情的盘口演化,直接按索引取快照即可。打开 MT5 用本地历史数据跑一遍序列写入,比只看成交价多一层深度视角。
「DOM 快照是怎么被攒出来的」
在 OnBookEvent() 被触发的瞬间,MarketBookGet() 会一次性返回当前市场深度数据,写入 MqlBookInfo 结构数组。这个数组里的每一条记录,对应一个独立的 DOM 订单对象;把它们整体打包,就构成了一张 DOM 快照。 每次事件回调都生成一张新快照,随后塞进该品种的 DOM 快照序列里。存储用的是标准库的 CObject 派生类动态指针数组,列表容量默认上限 20 万个快照对象,粗略能覆盖一两个交易日的深度变化。 每个交易品种都会单独维护一条这样的快照序列,最终统一收口到 DOM 数据集合。外汇与贵金属的 DOM 跳动极快,实盘跑这套逻辑前建议在 MT5 用 EURUSD 或 XAUUSD 的模拟环境先验证内存占用。 这一节先落地两个类:单品种 DOM 快照类、单品种 DOM 快照序列类;序列的集合类留到下一步再写和测。
◍ 给 DOM 快照序列补上毫秒级时间戳
要在 DOM 序列里按时间检索快照,得先给订单对象塞一个原文没有的属性——快照接收时间(毫秒)。MqlBookInfo 结构本身不带时间字段,所以我们把这个整数属性挂到 DOM 抽象订单对象上,并把对象整数型属性总数从 3 扩到 4。 在 Defines.mqh 中新增了 DOM 快照序列参数:数据天数与最大快照数量。作者明确提到第一个参数(天数)暂不使用,当前只靠第二个参数限制 DOM 快照最大可能数量,后续才可能把它绑到即时报价天数上。 对应地,排序逻辑也要改。新增一个按该毫秒整数属性排序的常量,在 CSelect 类(Select.mqh)里补一个搜索并排序 DOM 订单对象的方法,操作逻辑与此前其他库对象类的方法同构,不再复述。 MarketBookOrd 及其四个衍生类(买、买市、卖、卖市)的描述方法都加了一个「是否显示品种名」的标志位,默认不显示——因为快照对象标题已带品种,逐笔订单再标就显得冗余。下面这段是消息索引与文本映射的落地片段,高亮部分即本次新增的 CMarketBookSnapshot 与 CMBookSeries 条目。
<span class="comment">class=class="str">"cmt">//--- CMarketBookOrd</span> MSG_MBOOK_ORD_TEXT_MBOOK_ORD, <span class="comment">class=class="str">"cmt">// Order in DOM</span> MSG_MBOOK_ORD_VOLUME, <span class="comment">class=class="str">"cmt">// Volume</span> MSG_MBOOK_ORD_VOLUME_REAL, <span class="comment">class=class="str">"cmt">// Extended accuracy volume</span> MSG_MBOOK_ORD_STATUS_BUY, <span class="comment">class=class="str">"cmt">// Buy side</span> MSG_MBOOK_ORD_STATUS_SELL, <span class="comment">class=class="str">"cmt">// Sell side</span> MSG_MBOOK_ORD_TYPE_SELL, <span class="comment">class=class="str">"cmt">// Sell order</span> MSG_MBOOK_ORD_TYPE_BUY, <span class="comment">class=class="str">"cmt">// Buy order </span> MSG_MBOOK_ORD_TYPE_SELL_MARKET, <span class="comment">class=class="str">"cmt">// Sell order by Market</span> MSG_MBOOK_ORD_TYPE_BUY_MARKET, <span class="comment">class=class="str">"cmt">// Buy order by Market</span> <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="comment">class=class="str">"cmt">//--- CMarketBookSnapshot</span> MSG_MBOOK_SNAP_TEXT_SNAPSHOT, <span class="comment">class=class="str">"cmt">// DOM snapshot</span> <span class="comment">class=class="str">"cmt">//--- CMBookSeries</span> MSG_MBOOK_SERIES_TEXT_MBOOKSERIES, <span class="comment">class=class="str">"cmt">// DOM snapshot series</span></span> }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//--- CMarketBookOrd</span> {"Заявка в стакане цен","Order in Depth of Market"}, {"Объем","Volume"}, {"Объем c повышенной точностью","Volume Real"}, {"Сторона Buy","Buy side"}, {"Сторона Sell","Sell side"}, {"Заявка на продажу","Sell order"}, {"Заявка на покупку","Buy order"}, {"Заявка на продажу по рыночной цене","Sell order at market price"}, {"Заявка на покупку по рыночной цене","Buy order at market price"}, <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);"><span class="comment">class=class="str">"cmt">//--- CMarketBookSnapshot</span> {"Снимок стакана цен","Depth of Market Snapshot"}, <span class="comment">class=class="str">"cmt">//--- CMBookSeries</span>
DOM 快照序列与订单属性的底层常量
在 MT5 里做盘口深度(DOM)分析,先得弄清快照序列和订单属性的存储边界。系统对单个品种的 DOM 快照默认保留 1 天,最多存 200000 帧;tick 数据同样是默认 1 天、上限 200000 条,这两个上限直接决定了你回看盘口博弈的历史深度。 订单整数属性被收进 ENUM_MBOOK_ORD_PROP_INTEGER 枚举,包含状态、类型、成交量以及快照毫秒时间共 4 项,宏 MBOOK_ORD_PROP_INTEGER_TOTAL 写死为 4,而 MBOOK_ORD_PROP_INTEGER_SKIP 为 0 表示没有跳过不参与排序的整型属性。 排序基准由 FIRST_MB_DBL_PROP 与 FIRST_MB_STR_PROP 算出偏移,前者等于 4 减 0 即 4,意味着双精度属性从枚举值 4 起排。实战中若想按成交量给盘口挂单分层,可直接套 SORT_BY_MBOOK_ORD_VOLUME 模式,外汇与贵金属杠杆高、盘口瞬变,这类排序仅作概率参考。
class="macro">#define MBOOKSERIES_DEFAULT_DAYS_COUNT(class="num">1) class=class="str">"cmt">// The class="kw">default required number of days for DOM snapshots in the series class="macro">#define MBOOKSERIES_MAX_DATA_TOTAL(class="num">200000) class=class="str">"cmt">// Maximum number of stored DOM snapshots of a single symbol 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 MBOOK_ORD_PROP_TIME_MSC, class=class="str">"cmt">// Time of making a DOM snapshot in milliseconds }; class="macro">#define MBOOK_ORD_PROP_INTEGER_TOTAL(class="num">4) 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="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 { 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
「深度盘口对象的排序与筛选入口」
在 MT5 的盘口(DOM)对象体系里,排序枚举被拆成了三类属性:时间毫秒、实数精度量、字符串标识。其中 SORT_BY_MBOOK_ORD_TIME_MSC 按快照毫秒时间排,SORT_BY_MBOOK_ORD_PRICE 与 SORT_BY_MBOOK_ORD_VOLUME_REAL 挂在 FIRST_MB_DBL_PROP 之后按价格与扩展精度成交量排,SORT_BY_MBOOK_ORD_SYMBOL 则从 FIRST_MB_STR_PROP 起按品种名排。 实际调用落在 Select.mqh 的静态方法 ByMBookProperty 上,它针对整数、双精度、字符串三种属性各重载了一版,入参带 ENUM_COMPARER_TYPE 比较模式,返回的是 CArrayObj 指针。想验证的话,把 MarketBookOrd.mqh 一并包含,就能在 EA 里直接拿盘口快照做条件过滤。 外汇与贵金属盘口跳动极快,这类筛选只解决“取数”,不预示方向;高杠杆下误用快照可能造成滑点放大,请先在策略测试器跑历史 tick 确认逻辑。
SORT_BY_MBOOK_ORD_TIME_MSC, class=class="str">"cmt">// Sort by time of making a DOM snapshot in milliseconds 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 }; class="macro">#include "..\Objects\Book\MarketBookOrd.mqh" class=class="str">"cmt">//--- Return the list of DOM data with one out of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting a specified criterion class="kw">static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode); class="kw">static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode); class="kw">static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode);
◍ 用属性筛选深度挂单列表
在 DOM(市场深度)数据类里,先声明了三组静态极值查找函数:FindMBookMax 与 FindMBookMin 各自针对整数、双精度、字符串三种属性类型重载,用来定位某属性最大或最小对应的链表下标。 真正落地筛选的是 CSelect::ByMBookProperty 的重载实现。以整数版本为例,入参含源链表、属性枚举、目标值 long value 与比较模式 ENUM_COMPARER_TYPE mode;函数先判空并返回 NULL,新建 CArrayObj 后设 FreeMode(false) 避免自动释放元素,再把新链表挂进 ListStorage 统一管理。 遍历时用 list_source.Total() 拿到总数,逐条取 CMarketBookOrd 对象,若不支持该属性就 continue 跳过,否则读 GetProperty 值并交给 CompareValues 按 mode 比对,命中则加入结果链表。双精度版本逻辑完全一致,仅把 value 与 obj_prop 类型换成 double。 实盘接 MT5 时,可把 mode 设成等于或大于,快速抽出水单量大于某阈值的档位;外汇与贵金属杠杆高,DOM 瞬时翻转快,抽样结果仅反映当时盘口,不代表后续方向。
class="kw">static class="type">int FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property); class="kw">static class="type">int FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property); class="kw">static class="type">int FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Return the DOM data index in the list with the minimum value of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property of data class="kw">static class="type">int FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property); class="kw">static class="type">int FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property); class="kw">static class="type">int FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING class="kw">property); CArrayObj *CSelect::ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode) { if(list_ class="kw">return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) class="kw">return NULL; list.FreeMode(false); ListStorage.Add(list); class="type">int total=list_source.Total(); for(class="type">int i=class="num">0; i<total; i++) { CMarketBookOrd *obj=list_source.At(i); if(!obj.SupportProperty(class="kw">property)) class="kw">continue; class="type">long obj_prop=obj.GetProperty(class="kw">property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } class="kw">return list; } CArrayObj *CSelect::ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode) { if(list_ class="kw">return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) class="kw">return NULL; list.FreeMode(false); ListStorage.Add(list); for(class="type">int i=class="num">0; i<list_source.Total(); i++) { CMarketBookOrd *obj=list_source.At(i); if(!obj.SupportProperty(class="kw">property)) class="kw">continue; class="type">class="kw">double obj_prop=obj.GetProperty(class="kw">property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } class="kw">return list; }