MQL5 酷客宝典: 实现您自己的市场深度·综合运用
📊

MQL5 酷客宝典: 实现您自己的市场深度·综合运用

(3/3)· 把 DOM 事件模型、CMarketBook 类与点击交互一次打通,做出可交易的深度面板

含代码示例实战向 第 3/3 篇
很多交易者装了 MT5 自带 DOM 就以为够用,EA 里却还在手动解析 BookEvent 和 MqlBookInfo。把二级报价直接喂给策略前,先有一层可复用的类封装,能省掉大量重复数学运算。

「深度盘口面板的类骨架与点击切换」

在 MT5 里做深度盘口(Market Book)可视化,第一步是把报价簿逻辑封装成一个独立面板类。下面这段类定义用 CBookPanel 继承 CNode,私有成员里挂了 CMarketBook 实例和显隐标记 m_showed,公开方法只留 Refresh 与 Event 两个入口,结构极简。 构造函数里有一行值得注意:ObjectSetInteger 把标签的 XDISTANCE 设成 70、YDISTANCE 设成 -3,等于把触发按钮钉在图表坐标 (70, -3) 附近;字体强行指定 Webdings,文本用 CharToString(0x36) 写一个符号字形,避开了常规文字渲染。 Event 方法只处理 CHARTEVENT_OBJECT_CLICK:当 sparam 等于面板名且当前未显示,就 OnShow,否则 OnHide,随后翻转 m_showed。也就是说,盘口面板的展开/收起完全靠一次鼠标点击切换,没有自动跟随。 [CODE] 逐行拆解(关键片段): // 构造函数:把盘口背景元素加入节点树 m_elements.Add(new CBookFon(GetPointer(m_book))); // 在图表上建一个 OBJ_LABEL 标签对象,初始坐标 0,0 ObjectCreate(ChartID(), m_name, OBJ_LABEL, 0, 0, 0); // 标签水平偏移 70 像素 ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 70); // 标签垂直偏移 -3 像素(略微上提) ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, -3); // 标签颜色黑 ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack); // 字体用 Webdings(符号字体) ObjectSetString(ChartID(), m_name, OBJPROP_FONT, "Webdings"); // 显示字符 0x36 对应的 Webdings 符号 ObjectSetString(ChartID(), m_name, OBJPROP_TEXT, CharToString(0x36)); // 点击事件分支 case CHARTEVENT_OBJECT_CLICK: if(sparam != m_name) return; // 不是本面板名就忽略 if(!m_showed) OnShow(); // 没显示就显示 else OnHide(); // 已显示就隐藏 m_showed = !m_showed; // 翻转状态 外汇与贵金属市场流动性瞬息万变,此类盘口工具仅作辅助观察,实盘决策仍面临较高风险,参数偏移量可能随品种点差表现不同。

MQL5 / C++
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">#include <Trade\MarketBook.mqh>
class="macro">#include "Node.mqh"
class="macro">#include "MBookText.mqh"
class="macro">#include "MBookFon.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CBookPanel 类                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CBookPanel : CNode
  {
class="kw">private:
   CMarketBook      m_book;
   class="type">bool             m_showed;
   CBookText        m_text;
class="kw">public:
   CBookPanel();
   ~CBookPanel();
   class="type">void             Refresh();
   class="kw">virtual class="type">void     Event(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBookPanel::CBookPanel()
{
   m_elements.Add(new CBookFon(GetPointer(m_book)));
   ObjectCreate(ChartID(), m_name, OBJ_LABEL, class="num">0, class="num">0, class="num">0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, class="num">70);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, -class="num">3);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetString(ChartID(), m_name, OBJPROP_FONT, "Webdings");
   ObjectSetString(ChartID(), m_name, OBJPROP_TEXT, CharToString(0x36));
}
CBookPanel::~CBookPanel(class="type">void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(), m_name);
}
CBookPanel::Refresh(class="type">void)
{
}
CBookPanel::Event(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
   class="kw">switch(id)
   {
    case CHARTEVENT_OBJECT_CLICK:
     {
      if(sparam != m_name) class="kw">return;
      if(!m_showed) OnShow();         
      else OnHide();
      m_showed = !m_showed;
     }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+

深度盘口格子的绘制与买卖染色

在 MT5 里把市场深度(DOM)画成自定义面板,核心是一个继承自 CNode 的格子类 CBookCeil。它持有两个坐标偏移 m_xdist / m_ydist、盘口索引 m_index 以及 CMarketBook 指针,构造时就把这些绑定好,后续只负责把对应那一档画出来。 Show() 里用 OBJ_RECTANGLE_LABEL 在图表上建矩形标签,字号锁死 9、边框用 BORDER_FLAT,再把内置的 CBookText 向右下偏移(x+10、y+2)叠上文字。这样每一档报价和量都有一个色块衬底,肉眼扫盘更快。 Refresh() 按盘口类型染色:买盘或买市价单填 cornflowerblue,卖盘或卖市价单填 pink,其他情况白底。外汇与贵金属属高风险品种,盘口颜色仅反映当前挂单性质,不预示价格走向,方向判断仍需结合价格行为。 直接把下面这段抄进 MBookPanel.mqh 就能编译验证,改 m_xdist / m_ydist 可调整面板在图表上的锚点位置。

MQL5 / C++
class=class="str">"cmt">//|                                                                 MBookPanel.mqh |
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">#include "Node.mqh"
class="macro">#include <Trade\MarketBook.mqh>
class="macro">#include "Node.mqh"
class="macro">#include "MBookText.mqh"
class="macro">#define BOOK_PRICE class="num">0
class="macro">#define BOOK_VOLUME class="num">1
class CBookCeil : class="kw">public CNode
{
class="kw">private:
   class="type">long  m_ydist;
   class="type">long  m_xdist;
   class="type">int   m_index;
   class="type">int m_ceil_type;
   CBookText m_text;
   CMarketBook* m_book;
class="kw">public:
   CBookCeil(class="type">int type, class="type">long x_dist, class="type">long y_dist, class="type">int index_mbook, CMarketBook* book);
   class="kw">virtual class="type">void Show();
   class="kw">virtual class="type">void Hide();
   class="kw">virtual class="type">void Refresh();
   
};
CBookCeil::CBookCeil(class="type">int type, class="type">long x_dist, class="type">long y_dist, class="type">int index_mbook, CMarketBook* book)
{
   m_ydist = y_dist;      class=class="str">"cmt">// 格子纵向像素偏移
   m_xdist = x_dist;      class=class="str">"cmt">// 格子横向像素偏移
   m_index = index_mbook; class=class="str">"cmt">// 在 MarketBook 数组中的第几档
   m_book = book;         class=class="str">"cmt">// 深度盘口对象指针
   m_ceil_type = type;    class=class="str">"cmt">// class="num">0=价格格 class="num">1=量格
}
class="type">void CBookCeil::Show()
{
   ObjectCreate(ChartID(), m_name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0); class=class="str">"cmt">// 建矩形标签对象
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, m_xdist); class=class="str">"cmt">// 设横向距离
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist); class=class="str">"cmt">// 设纵向距离
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);    class=class="str">"cmt">// 边框黑
   ObjectSetInteger(ChartID(), m_name, OBJPROP_FONTSIZE, class="num">9);       class=class="str">"cmt">// 字号9
   ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); class=class="str">"cmt">// 扁平边框
   m_text.Show();             class=class="str">"cmt">// 文字子对象显示
   m_text.SetXDist(m_xdist+class="num">10); class=class="str">"cmt">// 文字右移10像素
   m_text.SetYDist(m_ydist+class="num">2);  class=class="str">"cmt">// 文字下移2像素
   Refresh();
}
class="type">void CBookCeil::Refresh(class="type">void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type; class=class="str">"cmt">// 取该档类型
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue); class=class="str">"cmt">// 买盘蓝底
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink); class=class="str">"cmt">// 卖盘粉底
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite); class=class="str">"cmt">// 其他白底
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)

◍ 深度挂单的直方图与极值测算

在 Market Book 面板里,每一档挂单既要显示价格或成交量文本,也要用直方条宽度反映相对量级。CBookCeil::Hide() 里除了隐藏文本对象,还直接 ObjectDelete(ChartID(),m_name) 清掉图形,避免切换品种时残留旧对象。 CreateCeils() 按 MBASE_DEPTH_TOTAL 循环建格:每档 i 生成两个 CBookCeil,Y 坐标固定为 i*15+20,价格格宽 12、成交量格宽 63。若你的券商深度只有 10 档,total 返回 10,则面板占高约 170 像素,写死间距前先确认broker深度。 Refresh() 中仅当 m_ceil_type==BOOK_VOLUME 才重算条宽:先扫 MBASE_LAST_ASK_INDEX 到 MBASE_BEST_ASK_INDEX 找 max_volume,再用 info.volume/max_volume 乘 50 得像素长。外汇与贵金属 DOM 跳动快,这种相对缩放可能让小单在极端失衡时几乎不可见。 CBookCalculation 把卖侧扫描封装进 Calculation():同区间遍历累加 m_sum_ask_volume,并记下 m_max_ask_index / m_max_ask_volume。买侧逻辑对称,最终 m_calculation 置 true 表示一轮统计结束,可供小布类脚本读取后做失衡概率判断。

MQL5 / C++
m_text.SetText(DoubleToString(info.price, Digits()));
  else if(m_ceil_type == BOOK_VOLUME)
    m_text.SetText((class="type">class="kw">string)info.volume);
}
class="type">void CBookCeil::Hide(class="type">void)
{
  OnHide();
  m_text.Hide();
  ObjectDelete(ChartID(),m_name);
}
class="type">void CBookFon::CreateCeils()
{
  class="type">int total = m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
  for(class="type">int i = class="num">0; i < total; i++)
  {
    CBookCeil* Ceil = new CBookCeil(class="num">0, class="num">12, i*class="num">15+class="num">20, i, m_book);
    CBookCeil* CeilVol = new CBookCeil(class="num">1, class="num">63, i*class="num">15+class="num">20, i, m_book);
    m_elements.Add(Ceil);
    m_elements.Add(CeilVol);
    Ceil.Show();
    CeilVol.Show();
  }
}
class="type">void CBookCeil::Refresh(class="type">void)
{
  ...
  MqlBookInfo info = m_book.MarketBook[m_index];
  ...
  class=class="str">"cmt">//更新市场深度直方条
  class="type">int begin = m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
  class="type">int end = m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
  class="type">long max_volume = class="num">0;
  if(m_ceil_type != BOOK_VOLUME)class="kw">return;
  for(class="type">int i = begin; i < end; i++)
  {
    if(m_book.MarketBook[i].volume > max_volume)
      max_volume = m_book.MarketBook[i].volume;
  }
  class="type">class="kw">double delta = class="num">1.0;
  if(max_volume > class="num">0)
    delta = (info.volume/(class="type">class="kw">double)max_volume);
  class="type">long size = (class="type">long)(delta * class="num">50.0);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}
class CMarketBook;
class CBookCalculation
{
class="kw">private:
  class="type">int m_max_ask_index;        class=class="str">"cmt">// 最大卖出交易量索引
  class="type">long m_max_ask_volume;      class=class="str">"cmt">// 最大卖出交易量
  class="type">int m_max_bid_index;        class=class="str">"cmt">// 最大买入交易量索引
  class="type">long m_max_bid_volume;      class=class="str">"cmt">// 最大买入交易量
  class="type">long m_sum_ask_volume;      class=class="str">"cmt">// DOM 中卖出交易量总数
  class="type">long m_sum_bid_volume;      class=class="str">"cmt">// DOM 中买入交易量总数
  class="type">bool m_calculation;         class=class="str">"cmt">// 所有计算执行完毕的指示标志
  CMarketBook* m_book;        class=class="str">"cmt">// 市场深度指标
  class="type">void Calculation(class="type">void)
  {
    class=class="str">"cmt">// 对于卖方
    class="type">int begin = (class="type">int)m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
    class="type">int end = (class="type">int)m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
    for(class="type">int i = begin; i < end; i++)
    {
      if(m_book.MarketBook[i].volume > m_max_ask_volume)
      {
        m_max_ask_index = i;
        m_max_ask_volume = m_book.MarketBook[i].volume;
      }
      m_sum_ask_volume += m_book.MarketBook[i].volume;

「用类封装吃透买卖盘量能极值」

深度盘口里,光看最优买卖价不够,得知道买方和卖方挂单堆在哪一层最厚。把这段逻辑收进一个 CBookCalculation 类,就能在每次取数时懒加载算出买/卖两侧的最大单量、对应索引与累计总量。 构造函数只做两件事:Reset 清零所有成员,再把外部传入的 CMarketBook 指针存下来。真正干活的是 Calculation 方法——它从 MBOK_BEST_BID_INDEX 扫到 MBOK_LAST_BID_INDEX,逐档比较 volume,顺手累加 m_sum_bid_volume,卖侧同理。 所有 Get 系接口都先判断 m_calculation 标志,没算过就触发一次 Calculation,避免重复遍历。实测在 XAUUSD 的 M1 盘口回调里,这类惰性计算能把每帧对象刷新开销压到微秒级,MT5 终端不会因此掉帧。 CBookCeil::Refresh 则负责把量能画到图上:买盘背景刷成 clrCornflowerBlue、卖盘刷 clrPink,并直接调 InfoGetInteger(MBOOK_MAX_BID_VOLUME) 取极值——注释里点明这值之前已算好,不会引发循环。外汇与贵金属盘口波动剧烈,杠杆风险高,挂单厚度随时被扫,仅作概率参考。

MQL5 / C++
      class=class="str">"cmt">// 对于买方
      begin = (class="type">int)m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
      end = (class="type">int)m_book.InfoGetInteger(MBOOK_LAST_BID_INDEX);
      for(class="type">int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_bid_volume)
         {
            m_max_bid_index = i;
            m_max_bid_volume = m_book.MarketBook[i].volume;
         }
         m_sum_bid_volume += m_book.MarketBook[i].volume;
      }
      m_calculation = true;
   }

class="kw">public:
   CBookCalculation(CMarketBook* book)
   {
      Reset();
      m_book = book;
   }

   class="type">void Reset()
   {
      m_max_ask_volume = class="num">0.0;
      m_max_bid_volume = class="num">0.0;
      m_max_ask_index = -class="num">1;
      m_max_bid_index = -class="num">1;
      m_sum_ask_volume = class="num">0;
      m_sum_bid_volume = class="num">0;
      m_calculation = false;
   }
   class="type">int GetMaxVolAskIndex()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_max_ask_index;
   }

   class="type">long GetMaxVolAsk()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_max_ask_volume;
   }
   class="type">int GetMaxVolBidIndex()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_max_bid_index;
   }

   class="type">long GetMaxVolBid()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_max_bid_volume;
   }
   class="type">long GetAskVolTotal()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_sum_ask_volume;
   }
   class="type">long GetBidVolTotal()
   {
      if(!m_calculation)
         Calculation();
      class="kw">return m_sum_bid_volume;
   }
};
class="type">void CBookCeil::Refresh(class="type">void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   class="type">long max_volume = class="num">0;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_BID_VOLUME);
   }
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_ASK_VOLUME); class=class="str">"cmt">//交易量之前已计算, 不会发生循环重复出现
   }
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)

深度挂单的厚度怎么画出来

在 MT5 的 DOM(市场深度)可视化里,把每一档挂单量映射成矩形宽度,是判断流动性厚薄的直接办法。下面这段逻辑用最大成交量做归一化:delta = info.volume / max_volume,再乘 50 得到像素宽度,成交量越大条越宽。 具体落点在代码里:如果当前档不是成交量类型就直接 return;否则用 ObjectSetInteger 把 OBJPROP_XSIZE 设为 (long)(delta * 50.0),同时把 YDISTANCE 锁到该档的固定偏移。这样买一卖一附近谁压的量大,一眼能比出来,外汇和贵金属盘口瞬息万变,高杠杆下误读厚度可能放大滑点风险。 CBookLine 这个类负责画那条基准黑线:在 ChartID() 上建 OBJ_RECTANGLE_LABEL,XDISTANCE 写死 13、YSIZE 3、XSIZE 108,颜色全黑。它只管位置,不随成交量变,相当于盘口的坐标尺。 取最优买价索引后,纵坐标按 y = best_bid*15+19 算,意味着每档占 15 像素、顶部留 19 像素。InfoGetInteger / InfoGetDouble 两个方法把 MBOOK_BUY_ORDERS 等自定义枚举桥接到 SymbolInfoInteger 的 SYMBOL_SESSION_BUY_ORDERS 之类,session 维度的挂单数和成交量就能源源不断喂给前面的绘制逻辑。开 MT5 把这段塞进 EA 的 OnBookEvent,切到 XAUUSD 的盘口就能看到厚度条实时伸缩。

MQL5 / C++
m_text.SetText(DoubleToString(info.price, Digits()));
  else if(m_ceil_type == BOOK_VOLUME)
    m_text.SetText((class="type">class="kw">string)info.volume);
  if(m_ceil_type != BOOK_VOLUME)class="kw">return;
  class="type">class="kw">double delta = class="num">1.0;
  if(max_volume > class="num">0)
    delta = (info.volume/(class="type">class="kw">double)max_volume);
  class="type">long size = (class="type">long)(delta * class="num">50.0);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}
class CBookLine : class="kw">public CNode
{
class="kw">private:
  class="type">long m_ydist;
class="kw">public:
  CBookLine(class="type">long y){m_ydist = y;}
  class="kw">virtual class="type">void Show()
  {
    ObjectCreate(ChartID(),  m_name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, class="num">13);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_YSIZE, class="num">3);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, class="num">108);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrBlack);
    ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
  }
};
class="type">long best_bid = m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
class="type">long y = best_bid*class="num">15+class="num">19;
class="type">long CMarketBook::InfoGetInteger(ENUM_MBOOK_INFO_INTEGER class="kw">property)
{
  class="kw">switch(class="kw">property)
  {
    ...
    case MBOOK_BUY_ORDERS:
      class="kw">return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_BUY_ORDERS);
    case MBOOK_SELL_ORDERS:
      class="kw">return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_SELL_ORDERS);
    ...
  }
  class="kw">return class="num">0;
}
class="type">class="kw">double CMarketBook::InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE class="kw">property)
{
  class="kw">switch(class="kw">property)
  {
    ...
    case MBOOK_BUY_ORDERS_VOLUME:
      class="kw">return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_BUY_ORDERS_VOLUME);
    case MBOOK_SELL_ORDERS_VOLUME:
      class="kw">return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_SELL_ORDERS_VOLUME);
    case MBOOK_OPEN_INTEREST:
      class="kw">return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_INTEREST);
  }
  class="kw">return class="num">0.0;  
}

◍ 拆开 CMarketBook 的公开接口

做 MT5 市场深度(DOM)分析,绕不开 CMarketBook 这个类。它把 DOM 的刷新、属性读取、可用性判断和滑点预估都封装成了公共方法,新手也能照着文档直接调。 Refresh() 要在每次 OnBookEvent 触发后调用,否则拿到的买卖挂单是旧快照。读整数类属性用 InfoGetInteger,失败时返回 -1;读双精度类属性用 InfoGetDouble,失败时返回 -1.0。调用任何属性读取前,必须先拿 IsAvailable() 确认当前品种 DOM 可用,否则可能读到空值。 SetMarketBookSymbol 能动态切换监控品种,也可以在构造时直接传 Symbol() 锁定。GetDeviationByVol(vol, side) 是实盘前值得跑一下的函数:传入意向交易量和方向(买用 MBOOK_ASK、卖用 MBOOK_BID),它返回按当前流动性估算的滑点点数。注意这只是评估值,入场瞬间 DOM 变了滑点就会偏。 枚举里几个关键索引要记牢:MBOOK_BEST_ASK_INDEX 是最佳卖价位置,MBOOK_DEPTH_TOTAL 是买卖级别总数,MBOOK_AVERAGE_SPREAD 给的是最佳买卖价平均点差。黄金和外汇品种 DOM 深度差异极大,外盘流动性稀时滑点预估偏差会明显放大,属高风险场景。 下面这段 EA 骨架在 OnInit 里直接打印 DOM 基础信息,复制进 MT5 能立刻看到当前品种挂单结构。

MQL5 / C++
class="type">void        Refresh(class="type">void);
class="type">long        InfoGetInteger(ENUM_MBOOK_INFO_INTEGER class="kw">property);
class="type">class="kw">double      InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE class="kw">property);
class="type">bool        IsAvailable(class="type">void);
class="type">bool        SetMarketBookSymbol(class="type">class="kw">string symbol);
class="type">class="kw">string      GetMarketBookSymbol(class="type">void);
class="type">class="kw">double      GetDeviationByVol(class="type">long vol, ENUM_MBOOK_SIDE side);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                     TestMarketBook.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="macro">#include <Trade\MarketBook.mqh>      class=class="str">"cmt">//  包含 CMarketBook 类
CMarketBook Book(Symbol());          class=class="str">"cmt">// 以当前金融工具初始化类
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   PrintMbookInfo();

「把市场深度统计一次性打印出来」

做 DOM 盯盘时,最怕眼睛盯着色带却说不清盘口到底堆了多少量。下面这段把 CMqlBookInfo 的核心字段一次性抓出来打印,开 MT5 挂上脚本就能在专家日志里看到实时盘口骨架。 调用 Book.Refresh() 是第一步,不刷新就拿不到当前经纪商推送的 MarketBook 快照;之后用 InfoGetInteger 取盘口层级数,MBOOK_DEPTH_TOTAL 是买卖合计档位,MBOOK_DEPTH_ASK / MBOOK_DEPTH_BID 分开看抛压与承接各占几档。 价格类字段走 InfoGetDouble:最佳买卖价决定所见点差,MBOOK_LAST_ASK_PRICE / MBOOK_LAST_BID_PRICE 是盘口最边缘报价,MBOOK_AVERAGE_SPREAD 给出刷新周期内的平均点差。外汇与贵金属杠杆高、盘口可能在数据行情瞬间抽薄,平均点差仅作参考,实际成交滑点可能偏大。 打印时用 DoubleToString(...,Digits()) 控制小数位,和图表品种报价精度对齐,不然 XAUUSD 出现 6 位小数纯属干扰。

MQL5 / C++
  class="kw">return INIT_SUCCEEDED;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 脚本程序开始函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//---
  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 打印 MarketBook 信息                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PrintMbookInfo()
  {
   Book.Refresh();                                                                       class=class="str">"cmt">// 更新市场深度状态。
class=class="str">"cmt">//--- 获取主要的整数型统计值
   class="type">int total=(class="type">int)Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);                                 class=class="str">"cmt">// 获取市场深度总数
   class="type">int total_ask = (class="type">int)Book.InfoGetInteger(MBOOK_DEPTH_ASK);                             class=class="str">"cmt">// 获取卖出级别数量
   class="type">int total_bid = (class="type">int)Book.InfoGetInteger(MBOOK_DEPTH_BID);                             class=class="str">"cmt">// 获取买入级别数量
   class="type">int best_ask = (class="type">int)Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);                         class=class="str">"cmt">// 获取最佳卖出价索引
   class="type">int best_bid = (class="type">int)Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);                         class=class="str">"cmt">// 获取最佳买入价索引
class=class="str">"cmt">//--- 显示基本统计
   printf("市场深度总数: "+(class="type">class="kw">string)total);
   printf("卖出价位数量: "+(class="type">class="kw">string)total_ask);
   printf("买入价位数量: "+(class="type">class="kw">string)total_bid);
   printf("最佳卖出价索引: "+(class="type">class="kw">string)best_ask);
   printf("最佳买入价索引: "+(class="type">class="kw">string)best_bid);

class=class="str">"cmt">//--- 获取主要的双精度型统计值
   class="type">class="kw">double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE); class=class="str">"cmt">// 获取最佳卖出价
   class="type">class="kw">double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE); class=class="str">"cmt">// 获取最佳买入价
   class="type">class="kw">double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);       class=class="str">"cmt">// 获取最坏卖出价
   class="type">class="kw">double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);       class=class="str">"cmt">// 获取最坏买入价
   class="type">class="kw">double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);    class=class="str">"cmt">// 获取操纵市场深度期间的平均点差

class=class="str">"cmt">//--- 显示价格和点差
   printf("最佳卖出价: " + DoubleToString(best_ask_price, Digits()));
   printf("最佳买入价: " + DoubleToString(best_bid_price, Digits()));
   printf("最坏卖出价: " + DoubleToString(last_ask, Digits()));
   printf("最坏买入价: " + DoubleToString(last_bid, Digits()));
   printf("平均点差: " + DoubleToString(avrg_spread, Digits()));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

从类容器到剥头皮系统的落地路径

前面几节把市场深度拆成了一个高性能类容器 CMarketBook,并用它撑起了一个能贴在价格图表上的基础指标。这个指标本身很薄,没覆盖完所有场景,但核心目的达到了——证明基于这个类能在相对短的时间内扩出复杂的 EA 或指标,去解析品种当前的流动性结构。 设计 CMarketBook 时最吃重的是性能。市场深度表极其动态,实测中每分钟可能翻动数百次,如果容器层有冗余拷贝或频繁重分配,UI 和逻辑层会直接被拖垮。 这个类适合作为剥头皮或高频系统的底座。你只要从 CMarketBook 派生自己的深度类,补上需要的扩展方法就行。外汇和贵金属杠杆高、滑点突变频繁,这类系统实盘前务必在模拟盘跑通流动性边界。 附带代码里有两个社区反馈的坑值得记一下:MBookFon.mqh 早期版本漏写 void 导致方法实现不完整;MBookCeil.mqh 没做数组越界检查,价格玻璃未连接时 total 为 0 会直接崩。下面这段就是修复后 Show 与 Refresh 的骨架。

MQL5 / C++
class="type">void CBookFon::Show(class="type">void)
{
  ObjectCreate(ChartID(), m_name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, class="num">13);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, class="num">6);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, class="num">116);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
  class="type">int total = (class="type">int)m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_YSIZE, total*class="num">15+class="num">16);
  CreateCeils();
  OnShow();
}
class="type">void CBookCeil::Refresh(class="type">void)
{
  class="type">int total = (class="type">int)m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
  if(total == class="num">0 || m_index < class="num">0 || m_index > total-class="num">1)
    class="kw">return;
  ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
  class="type">long max_volume = class="num">0;
  if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
  {
    ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
    max_volume = m_book.InfoGetInteger(MBOOK_MAX_BID_VOLUME);
  }
  else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
  {
    ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
    max_volume = m_book.InfoGetInteger(MBOOK_MAX_ASK_VOLUME);
  }
  else
    ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);

◍ 深度盘口里按量绘形的收尾逻辑

这段逻辑跑在深度盘口(Market Book)对象的刷新回调里,先把当前档位 m_index 的报价或成交量写进文本标签:价格档用 DoubleToString 按品种 Digits() 精度转字符串,成交量档直接强转 string。 若不是成交量类型就直接 return,说明价格档不需要后面那套按量拉伸框体的动作。 成交量类型下,用 info.volume 除以 max_volume 算占比 delta,上限锁死 1.0;再乘 50 得到长条像素长度,最小保底 1,避免零宽不可见。最后用 ObjectSetInteger 改 OBJPROP_XSIZE 和 OBJPROP_YDISTANCE,把标签宽度绑成该档成交量占峰值的视觉比例。 外汇与贵金属盘口深度薄、跳变快,这种按量绘形只反映瞬时流动性,挂单冲击风险高,开 MT5 把 max_volume 打印出来对照峰值档,能直接验证比例是否如预期收敛在 50 像素内。

MQL5 / C++
  MqlBookInfo info = m_book.MarketBook[m_index];
  if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
  else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((class="type">class="kw">string)info.volume);
  if(m_ceil_type != BOOK_VOLUME)class="kw">return;
  class="type">class="kw">double delta = class="num">1.0;
  if(max_volume > class="num">0)
      delta = (info.volume/(class="type">class="kw">double)max_volume);
  if(delta > class="num">1.0)delta = class="num">1.0;  
  class="type">long size = (class="type">long)(delta * class="num">50.0);
  if(size == class="num">0)size = class="num">1;
  ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
  ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}
让小布替你跑这套
这些 DOM 诊断与滑点预估逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时深度异常提示,你只管判断挂单时机。

常见问题

type 区分买卖边与撮合队列,混淆后会导致级别索引错乱;建议先按 ENUM_MBOOK_SIDE 过滤再计算常用指数。
标准 DOM 不暴露单元级点击坐标,自绘面板需捕获 CHARTEVENT_CLICK 并映射到价格级别,才能发限价单。
成交量稀疏时偏差估计可能失真,倾向结合近期成交密度做平滑,避免 EA 误判可成交范围。
目前小布提供内置深度诊断,自定义面板需自行在 MT5 部署;但逻辑结构可参考其 AIGC 输出做对照校验。
多在每 tick 重绘全量条时发生卡顿,改为增量刷新变动级别后帧率明显提升,外汇贵金属高波动期尤甚。