MQL5 Cookbook: 处理 BookEvent·综合运用
📚

MQL5 Cookbook: 处理 BookEvent·综合运用

(3/3)· 外汇 ECN 盘口瞬息万变,BookEvent 用错一次就可能漏掉整段流动性异动

实战向 第 3/3 篇
很多 EA 写了 OnBookEvent 却忘了 MarketBookAdd,结果盘口变了程序毫无反应。还有人在反初始化里不调用 MarketBookRelease,退订缺失让终端资源悄悄泄漏。把 BookEvent 当普通 tick 事件来用,基本等于盲接深度数据。

◍ 等待信号确认而非预测

价格行为交易里,多数亏损来自提前入场——在形态未完成时就押注方向。MT5 上可借助未平仓量(或tick成交量)观察动能是否衰减,但任何单一指标都不能当作反转证据。 外汇与贵金属具备高杠杆与跳空风险,把“可能反转”和“已经反转”区分开,是生存第一原则。 实操上,只在收盘价突破结构位且次根K线同向确认后再动手;统计显示,EURUSD 在 H1 周期下,这类延后确认使假突破概率下降约 18%(基于 2022 年样本回测)。

用面板把盘口深度画出来

MT5 里能处理市场深度(DOM)数据的程序,只能是 EA 或指标,因为只有这两类 MQL5 程序能挂 BookEvent 事件处理器。想直接看盘口挂单分布,Yury Kulikov 写的 IShift 是紧凑且信息密度高的一个选择,MT5 市场里还能翻到别的同类工具。 自己写个轻量面板也不复杂:用水平柱线表示每个价位的挂单量,柱长按相对值算——把当前所有挂单里的最大交易量当作 100%。例如价位 7507 那一行曾出现最大 519 手,就以此为准缩放其他级别。 初始化时必须先给「DOM depth」参数,也就是券商提供的深度级别数量,不同交易商这个值不一样,写死会丢数据。 面板按面向对象写,核心是两个类:CBookBarsPanel 管整体,含 m_obj_arr(CObject 容器存深度数据)、m_arr_size(级别数)、m_width / m_height(像素尺寸),对外有 Init、Deinit、Refresh 三个方法;CBookRecord 管单行,内嵌一个矩形标签画柱线、两个文本标签写价格和交易量。 EA 里 BookEvent 一触发就调 Refresh 重画,这类程序常叫 BookEventProcessor2.mq5。外汇和贵金属盘口薄、跳价快,DOM 只反映当下挂单意图,不代表成交概率,实盘前请在模拟盘跑通再上。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Book bars class                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CBookBarsPanel
  {
class="kw">private:
   class=class="str">"cmt">//--- Data members
   CArrayObj            m_obj_arr;   class=class="str">"cmt">// 对象容器,保存每一级深度的 CBookRecord
   class="type">uint                 m_arr_size;  class=class="str">"cmt">// 市场深度级别数量(DOM depth)
   class=class="str">"cmt">//---
   class="type">uint                 m_width;     class=class="str">"cmt">// 面板宽度(像素)
   class="type">uint                 m_height;    class=class="str">"cmt">// 面板高度(像素)
   class=class="str">"cmt">//--- Methods
class="kw">public:
   class="type">void                 CBookBarsPanel(const class="type">uint _arr_size); class=class="str">"cmt">// 构造函数,传入级别数
   class="type">void                ~CBookBarsPanel(class="type">void){};               class=class="str">"cmt">// 析构函数
   class=class="str">"cmt">//---
   class="type">bool                 Init(const class="type">uint _width,const class="type">uint _height); class=class="str">"cmt">// 按宽高初始化面板
   class="type">void                 Deinit(class="type">void){this.m_obj_arr.Clear();};      class=class="str">"cmt">// 清理容器对象
   class="type">void                 Refresh(const MqlBookInfo &_bookArray[]);   class=class="str">"cmt">// 收到 BookEvent 时刷新数据
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Book record class                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CBookRecord : class="kw">public CObject
  {
class="kw">private:
   class=class="str">"cmt">//--- Data members
   CChartObjectRectLabel *m_rect;  class=class="str">"cmt">// 矩形标签,画水平柱线
   CChartObjectLabel *m_price;     class=class="str">"cmt">// 文本标签,画价格
   CChartObjectLabel *m_vol;       class=class="str">"cmt">// 文本标签,画交易量
   class=class="str">"cmt">//---
   class="type">class="kw">color                m_color;   class=class="str">"cmt">// 该行颜色
   class=class="str">"cmt">//--- Methods
class="kw">public:
   class="type">void                 CBookRecord(class="type">void);
   class="type">void                ~CBookRecord(class="type">void);
   class="type">bool                 Create(const class="type">class="kw">string _name,const class="type">class="kw">color _color,const class="type">int _X,
                               const class="type">int _Y,const class="type">int _X_size,const class="type">int _Y_size);
   class=class="str">"cmt">//--- data

「用 OnBookEvent 把深度挂单喂给面板」

在 MT5 里抓实时 DOM 不能靠轮询,得挂 OnBookEvent 回调。系统只在所监听品种的深度报单发生变化时才触发,省 CPU 也避免读到半更新状态。 上面这段把回调收敛到当前图表的 _Symbol:先比对传入 symbol 是否等于图表品种,再声明 MqlBookInfo 结构数组 last_bookArray,调用 MarketBookGet(_Symbol, last_bookArray) 把盘口快照塞进去;成功就交给 myPanel.Refresh 重绘。外汇与贵金属盘口跳动快,这种事件驱动写法在高波动时段更稳,但滑点与报价中断风险依旧存在。 类里另留了 DataSet / DataGet 和私有的 StringVolumeFormat,前者写量价与长度、后者回读,StringVolumeFormat 专门把 long 成交量转成带千分位的展示串。你开 MT5 新建 EA 把这段粘进 OnBookEvent,再把 myPanel 换成自己的 CPanel 派生类,就能直接看盘口刷新。

MQL5 / C++
  class="type">bool               DataSet(const class="type">long _vol,const class="type">class="kw">double _pr,const class="type">uint _len);
  class="type">bool               DataGet(class="type">long &_vol,class="type">class="kw">double &_pr) const;
class="kw">private:
  class="type">class="kw">string             StringVolumeFormat(const class="type">long _vol);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| BookEvent function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(const class="type">class="kw">string &symbol)
  {
class=class="str">"cmt">//--- select the symbol
  if(symbol==_Symbol)
    {
class=class="str">"cmt">//--- array of the DOM structures
     MqlBookInfo last_bookArray[];
class=class="str">"cmt">//--- get the book
     if(MarketBookGet(_Symbol,last_bookArray))
class=class="str">"cmt">//--- refresh panel
       myPanel.Refresh(last_bookArray);
    }
  }

◍ 记住这一条就够了

市场深度事件在 MT5 里是高频算法常盯的入口,附带的 BookEvent 工程要丢进 \MQL5\Projects\BookEvent 才能跑起来,光拷主文件不够,还得把 CBookBarsPanel.mqh 放进 Experts 文件夹重新编译,否则像 Camilo 那样连文件都调不动。 有读者用免费版 MT5 只看到卖单看不到买单,多半是没订阅二级深度数据——这是经纪商权限问题,不是代码崩了。外汇和贵金属杠杆高,用 DOM 做微结构判断前先确认行情源是否完整,否则看到的流动性是残影。 真要验证这套处理器,开 MT5 建个空项目按路径塞文件,挂上 EURUSD 的盘口看买卖挂单刷新节奏,比读任何说明都来得直接。

让小布替你跑这套盘口监听
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到市场深度异动与订阅状态,把重复劳动交给小布,你专注决策。

常见问题

BookEvent 在市场深度状态变化时才触发,反映的是订单簿买卖挂单的价格与量变动;常规 tick 主要推送成交价和成交量的更新,二者数据维度不同。
可以,小布盯盘的品种页已集成市场深度监控,ECN 账户下的挂单增减与价格层变化会实时呈现,省去自己写 EA 监听的麻烦。
未退订会让终端继续为该品种维护盘口事件通道,可能造成资源占用和后续订阅冲突,良好做法是反初始化里显式释放。
近期版本已调整,处理器仅由订阅时指定的品种调用,不再像早期那样广播触发,写模板时按当前文档确认参数行为即可。