MQL5 酷客宝典: 实现您自己的市场深度(基础篇)
📘

MQL5 酷客宝典: 实现您自己的市场深度(基础篇)

第 1/3 篇

「在 MT5 里自己画出市场深度」

MT5 自带的 DOM 面板够用,但想叠加自己的成交量阈值线、或者把买卖队列按自定义逻辑重排,就得自己写一管。核心不是界面,而是先把 MarketBookGet 拿到的挂单数组吃透。 MarketBookGet 每次返回的是当前品种的全部挂单快照,结构里含价格、成交量、买卖方向。注意它返回的是 MqlBookInfo 数组,若市场无挂单则返回空数组,调用前务必判断 ArraySize。 实操上,先在 OnTick 里触发 MarketBookGet,再遍历数组把买侧(book_type=BOOK_TYPE_BUY)和卖侧(BOOK_TYPE_SELL)分别压进两个容器。这样你就能在自定义 Indicator 或 EA 面板里画一条实时深度曲线。外汇与贵金属杠杆高,深度瞬时抽离属常态,任何基于 DOM 的判定都只能当作概率参考。

◍ 这套 DOM 教程到底讲什么

这篇系列不是泛泛而谈界面操作,而是把 MT5 市场深度(DOM)从底层事件到自研面板全拆开。前 1/3 篇幅落在 MetaTrader 5 标准 DOM 与 MarketBookGet 函数、MqlBookInfo 结构上,属于第二级报价(Level II)的接入基础。 中间用一整章写了 CMarketBook 类:从设计、常用指数计算,到用前值预测最优买卖价、用 GetDeviationByVol 控最大滑点,附带可跑的例程。外汇与贵金属杠杆高、盘口薄时滑点可能突变,这类方法仅作概率参考。 后段转向实战——自己写面板指标,处理点击事件、画交易量直方条,并迭代优化最大交易量计算;最后补了 CMarketBook 的文档与枚举。开 MT5 按目录第 1.3 节试 MarketBookGet,能直接看到十档以外的挂单分布。

用类封装把 DOM 访问门槛降下来

MetaTrader 5 内置的市场深度(DOM)只负责把限价单按价格档位陈列出来,但要让 EA 直接吃进这些数据做逻辑,原生的 MQL5 接口偏底层,得自己补一堆数学换算。对做价格行为交易的人来说,这种中间计算纯属噪音,不如封装掉。 思路是写一个 CMarketBook 类,继承标准库、把档位解析和级别操作都收进类内部。对外只暴露几个直观方法,EA 调一下就能拿到某档量价,不用管内部索引怎么排。类还能直接挂成指标面板,实时映射 DOM 状态,盯盘时一眼看穿排队厚度。 第一章跑完,你应该清楚 MT5 的规范 DOM 能力远不止面板显示。我们不去复刻官方那套,而是用面向对象的办法做个轻量交易面板,证明从 EA 直连 DOM 并不难,且视觉示意能实打实省下翻书时间。外汇与贵金属杠杆高,DOM 只反映限价挂单意图,不代表成交概率,需结合实时盘口验证。

「在 MT5 里抓取二级报价的前置动作」

MT5 的标准市场深度(DOM)不只是限价单列表,它还叠加了分时图、点差级别和买卖订单总级别。打开路径是「视图 → 市场深度 → 金融工具名」,弹窗里卖单买盘和最后成交量的分布能直接看出领先模式。但真正要做程序化读取,得先理解它的事件模型。 快速行情下 DOM 每秒可能变几十次,若对所有品种裸监听,OnBookEvent 的调用量会拖垮 CPU。正确做法是只订阅你关心的符号:在 OnInit 里调 MarketBookAdd("Si-9.15"),终端就只把该合约的簿记变更事件推给你的 EA;退出时用 MarketBookRelease 在 OnDeinit 退订,避免资源泄漏。 订阅之后,MarketBookGet 配合 MqlBookInfo 结构才能拿到具体数据。DOM 在 MQL5 里是一维数组,索引 0 是最差卖价(BOOK_TYPE_SELL),末尾才是最优买价,最佳买卖价往往落在数组中部。下面这段是最小订阅骨架,黄底那行就是核心订阅指令,Si-9.15 是 2015 年 9 月到期的美元/卢布期货,换你自己的品种名就能直接跑。 外汇和贵金属属于高杠杆高风险品种,用 DOM 做微观结构判断前,先在模拟盘验证事件频率是否可接受。

MQL5 / C++
class="type">void OnInit()
{
  MarketBookAdd("Si-class="num">9.15");
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
  MarketBookRelease("Si-class="num">9.15");
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Expert.mq5 |
class=class="str">"cmt">//|          Copyright class="num">2015, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2015, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 初始化函数                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  MarketBookAdd("Si-class="num">9.15");
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 逆初函数                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用 OnBookEvent 把盘口变动抓出来

想在 MT5 里盯 Live 盘口,得先订阅再监听。EA 退出时记得用 MarketBookRelease("Si-9.15") 释放合约 Si-9.15 的深度订阅,否则底层资源会一直挂着。 监听入口是 OnBookEvent(const string &symbol)。最简单的写法只是一行 printf 提示某合约深度变了;但要拿真实数据,得在回调里声明 MqlBookInfo 数组,再调 MarketBookGet(symbol, book) 把当前挂单拉进来。 MqlBookInfo 结构只有三个字段:type(买卖挂单类型)、price(挂单价格)、volume(挂单量)。若 ArraySize(book)==0,说明订阅没生效或读取失败,用 GetLastError() 打出原因直接 return。 实测日志里,2015.06.05 15:54:17 前后连续三帧都打出「价格: 56464; 交易量: 56; 类型: BOOK_TYPE_SELL」,说明那一瞬间卖单在 56464 堆叠了 56 手且未变。外汇与贵金属盘口跳动更快,这类深度数据仅反映瞬时概率,杠杆品种高风险,别凭一两帧挂单方向下重注。 把下面代码丢进 EA,开 Si-9.15(或你做的 XAUUSD)的图表挂上,就能在终端看到每笔盘口首档变化。

MQL5 / C++
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
   MarketBookRelease("Si-class="num">9.15");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| BookEvent 函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(class="kw">const class="type">class="kw">string &symbol)
  {
class=class="str">"cmt">//---
   printf("市场深度 " + symbol +  " 有变化");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct MqlBookInfo
  {
   ENUM_BOOK_TYPE   type;         class=class="str">"cmt">// 来自 ENUM_BOOK_TYPE 枚举的订单类型
   class="type">class="kw">double           price;        class=class="str">"cmt">// 订单价格
   class="type">long             volume;       class=class="str">"cmt">// 订单交易量
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| BookEvent 函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(class="kw">const class="type">class="kw">string &symbol)
  {
class=class="str">"cmt">//---
   class=class="str">"cmt">//printf("市场深度 " + symbol +  " 有变化"); 
   MqlBookInfo book[];
   MarketBookGet(symbol, book);
   if(ArraySize(book) == class="num">0)
   {
      printf("加载市场预订价格失败。原因: " + (class="type">class="kw">string)GetLastError());
      class="kw">return;
   }
   class="type">class="kw">string line = "价格: " + DoubleToString(book[class="num">0].price, Digits()) + "; ";
   line += "交易量: " + (class="type">class="kw">string)book[class="num">0].volume + "; ";
   line += "类型: " + EnumToString(book[class="num">0].type);
   printf(line);
  }

用 CMarketBook 直接算 DOM 索引与滑点

CMarketBook 本质是对 MqlBookInfo 数组的一层薄封装,只为把最高频的 DOM 访问变成一行调用。它强制一对一绑定单一品种——想同时盯 Si-9.15 和 ED-9.15 就得各自 new 一个实例,避免把每秒几十次的深度更新摊到全市场而拖垮 EA。 最坏卖价索引恒为 0,最坏买价索引 = 深度总数 - 1;卖出深度直接等于最佳买价索引,买入深度 = 深度总数 - 最佳买价索引。这套排减公式把本该循环计算的量压成了直接取值,在 64 级深度、每秒多次刷新的盘面里省下的 CPU 很实在。 最佳买卖索引不能靠『一分为二』。OFZ2-9.15 曾出现买侧 4 级、卖侧 8 级,二分法直接错乱;类里改用记忆前次索引 + 按需迭代:先查旧最佳卖价是否仍有效,再向后找首个 BOOK_TYPE_BUY,匹配就跳过整轮遍历。实测流动性好的大 DOM 里,这种检查比 for 循环快一个量级。 滑点评估交给 GetDeviationByVol(volume, side)。它从最佳价朝坏价累加成交量,碰够指定手数就退出,算加权平均入场价与最佳价的差绝对值。外汇、贵金属杠杆高、闪崩滑点可能吃掉止损,用前务必在 MT5 用真实品种跑一遍验证量级。 下面这段是类构造与绑定的最小样例,挂在 EA 里才能跑(脚本无权访问 MarketBook): CMarketBook("Si-9.15"); // 市场深度 Si-9.15 CMarketBook("ED-9.15"); // 市场深度 ED-9.15 CMarketBook("SBRF-9.15"); // 市场深度 SBRF-9.15 CMarketBook("GAZP-9.15"); // 市场深度 GAZP-9.15 CMarketBook BookOnSi("Si-9.15"); // 绑定 Si-9.15 的实例 //+------------------------------------------------------------------+

MQL5 / C++
CMarketBook("Si-class="num">9.15");                class=class="str">"cmt">// 市场深度 Si-class="num">9.15
CMarketBook("ED-class="num">9.15");                class=class="str">"cmt">// 市场深度 ED-class="num">9.15
CMarketBook("SBRF-class="num">9.15");              class=class="str">"cmt">// 市场深度 SBRF-class="num">9.15
CMarketBook("GAZP-class="num">9.15");              class=class="str">"cmt">// 市场深度 GAZP-class="num">9.15
CMarketBook BookOnSi("Si-class="num">9.15");        class=class="str">"cmt">// 绑定 Si-class="num">9.15 的实例
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

先确认你的平台支持二级报价回调,用内置的盘口事件钩子抓取买卖挂单,再用图形对象把价位和量画到图表上即可。
用盘口类把每个价位的累计挂单量算出来,厚度薄的价位更容易被扫,下单前看一眼能避开易滑点层。
可以,小布盯盘的 AIGC 已内置盘口诊断,打开对应品种页就能看到挂单厚度与异动提示,不用自己写代码。
封装后索引计算和滑点估算都藏在类里,你只传品种和价位就能拿结果,比每次重写循环更不易出错。
二级报价只在挂单增减时推事件,纯成交吃单不改挂单结构就不会触发,需同时订阅报价流补盲区。