DoEasy 函数库中的价格(第六十四部分):市场深度,DOM 快照类和快照序列对象(基础篇)
📘

DoEasy 函数库中的价格(第六十四部分):市场深度,DOM 快照类和快照序列对象(基础篇)

第 1/3 篇

给 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 条目。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- CMarketBookOrd</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_TEXT_MBOOK_ORD,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Order in DOM</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_VOLUME,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Volume</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_VOLUME_REAL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Extended accuracy volume</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_STATUS_BUY,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Buy side</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_STATUS_SELL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Sell side</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_TYPE_SELL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Sell order</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_TYPE_BUY,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Buy order </span>
&nbsp;&nbsp; MSG_MBOOK_ORD_TYPE_SELL_MARKET,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Sell order by Market</span>
&nbsp;&nbsp; MSG_MBOOK_ORD_TYPE_BUY_MARKET,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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>
&nbsp;&nbsp; MSG_MBOOK_SNAP_TEXT_SNAPSHOT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// DOM snapshot</span>
&nbsp;&nbsp;
<span class="comment">class=class="str">"cmt">//--- CMBookSeries</span>
&nbsp;&nbsp; MSG_MBOOK_SERIES_TEXT_MBOOKSERIES,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// DOM snapshot series</span></span>
&nbsp;&nbsp;
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//--- CMarketBookOrd</span>
&nbsp;&nbsp; {"Заявка в стакане цен","Order in Depth of Market"},
&nbsp;&nbsp; {"Объем","Volume"},
&nbsp;&nbsp; {"Объем c повышенной точностью","Volume Real"},
&nbsp;&nbsp; {"Сторона Buy","Buy side"},
&nbsp;&nbsp; {"Сторона Sell","Sell side"},
&nbsp;&nbsp; {"Заявка на продажу","Sell order"},
&nbsp;&nbsp; {"Заявка на покупку","Buy order"},
&nbsp;&nbsp; {"Заявка на продажу по рыночной цене","Sell order at market price"},
&nbsp;&nbsp; {"Заявка на покупку по рыночной цене","Buy order at market price"},
&nbsp;&nbsp;
<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>
&nbsp;&nbsp; {"Снимок стакана цен","Depth of Market Snapshot"},
&nbsp;&nbsp;
<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 模式,外汇与贵金属杠杆高、盘口瞬变,这类排序仅作概率参考。

MQL5 / C++
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 确认逻辑。

MQL5 / C++
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 瞬时翻转快,抽样结果仅反映当时盘口,不代表后续方向。

MQL5 / C++
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;
  }

常见问题

在 DOM 快照序列对象里补一个毫秒级时间字段,每次新增快照时写入当前系统微秒换算的毫秒值,回看时按时间字段排序即可。
用深度盘口对象的排序与筛选入口,传入价格区间或订单属性常量做条件,只保留符合的挂单列表再输出。
可以,小布能按你设定的筛选条件持续读取深度盘口,发现某价位挂单量突增时直接弹提醒,不用自己盯序列。
DOM 快照存的是某一刻买卖盘各档挂单量与价,不是成交价序列;单独建类便于管理快照序列和做盘口级分析。
把订单属性常量作为筛选入参传给深度盘口对象,可限定只看买盘、卖盘或某类挂单状态,缩小列表范围。