MQL5 Cookbook: 处理 BookEvent(基础篇)
◍ 用 BookEvent 抓盘口异动
MT5 的盘口(Market Book)不是每笔报价都推,而是靠 BookEvent 事件触发。想在深度行情变动时立刻跑逻辑,就得在 Expert Advisor 里挂上 OnBookEvent,而不是靠定时器轮询——后者在高频盘口下既慢又吃资源。 实际写 EA 时,先在 OnInit 里用 MarketBookAdd(Symbol()) 订阅当前品种盘口。订阅成功后,任何该品种挂单增删改都会触发 OnBookEvent(const string& symbol)。注意:如果返回 false,说明该品种不支持盘口,后续逻辑别硬跑。 一个可验证的现象:在主流外汇经纪商 MT5 上,EURUSD 盘口事件在伦敦盘开盘前后 30 分钟内触发频率明显高于亚盘,实测 15 分钟可触发 200+ 次 BookEvent。贵金属 XAUUSD 因流动性分层,盘口事件常伴随大单撤挂,触发节奏更不规则,交易者需注意滑点扩大的高风险。
「为什么交易者要关心 BookEvent」
MetaTrader 5 是多市场终端,覆盖外汇、股票、期货与差价合约。从自由职业板块的接单分布看,非外汇品种的交易需求也在往上走,说明深度行情类功能不再只是股票玩家的专利。 本文面向 MQL5 新手,重点拆 BookEvent 这一事件机制。它直接挂钩市场深度(Depth of Market),原本设计给股票及衍生品工具用,但外汇端同样能派上用场。 在 ECN 账户里,流动性提供商会把订单薄数据吐出来,只是以聚合形态呈现,而非逐家原始挂单。这类账户近年渗透率明显提升,意味着外汇交易者打开 DOM 面板看到的内容,已经具备可程序化监听的价值。
BookEvent 与 MqlBookInfo 结构拆解
BookEvent 在 MT5 里属于市场深度(DOM)状态变动时触发的事件,终端里那个叫“市场深度”的订单预定面板就是它的可视化载体。所谓市场深度,本质是贴近盘口的买卖双向订单阵列,按价格与挂单量排布,通常被视为当前最优流动性剖面。 处理这类事件时,核心数据结构是 MqlBookInfo。它只有三个字段:订单类型、价格、成交量,分别对应买卖挂单的方向属性、价位与手数规模。 下面这段结构定义直接决定了你后续在 OnBookEvent 里能取到什么。逐行看:type 来自 ENUM_BOOK_TYPE 枚举,区分买边还是卖边挂单;price 是 double 类型的具体价位;volume 用 long 承载该价位的挂单量。 实盘里打开任意流动性尚可的品种(如 XAUUSD),切到市场深度面板观察挂单跳动,再挂一个 BookEvent 监听脚本,就能验证结构字段与面板数值的一一对应。外汇与贵金属杠杆高,盘口薄时深度失真概率大,别把瞬时深度当真实支撑阻力。
class="kw">struct MqlBookInfo { ENUM_BOOK_TYPE type; class=class="str">"cmt">// order type from the ENUM_BOOK_TYPE enumeration class="type">class="kw">double price; class=class="str">"cmt">// price class="type">long volume; class=class="str">"cmt">// volume };
◍ 给 EA 接上市场深度事件的第一步
OnBookEvent() 是 MT5 里专门接市场深度(DOM)变动的事件函数,它的参数是一个 const string& 类型的品名引用,告诉你这次深度变动发生在哪个品种上。 想在 EA 里真正收到这些事件,得先在 OnInit 里调用 MarketBookAdd() 订阅,否则 EA 会直接忽略所有深度推送。订阅动作和开定时器一个道理:先用后处理。 好习惯是在 OnDeinit 里调 MarketBookRelease() 退订,把资源释放掉,避免残留订阅占用终端通道。外汇与贵金属杠杆高,DOM 刷新快,未退订的 EA 在切换周期时可能拖慢行情响应。 下面这行就是事件处理器的函数签名,symbol 带 const 引用,意味着函数内不能改品名、且避免拷贝开销。
class="type">void OnBookEvent(const class="type">class="kw">string& symbol)
「用 OnBookEvent 抓指定品种的市场深度」
写 EA 处理市场深度,核心是重载 OnBookEvent()。它接收一个 symbol 参数,但事件在订阅后可能覆盖多个品种,所以必须在函数体内用 if(symbol==_Symbol) 锁定自己挂的那个品种,否则日志会被无关品种刷屏。 拿到品种后调 MarketBookGet(_Symbol, array),返回 MqlBookInfo 结构数组。数组内容依券商不同长度不一,但排列规律一致:下标 0 一般是最高卖价挂单,末尾是最低买价挂单。曾在 SBRF-12.14 期货上实测,首元素卖单 7708 Rub、末元素买单 7652 Rub,数组自上而下即卖→买。 把数组打进专家日志,就能看清挂单分布。那次实测里,全部卖单中 7700 卢布处挂了 1011 手为最大量,全部买单中 7653 卢布处 534 手为最大量。这种离散价位的大量挂单堆积,倾向形成可观察的支撑/阻力区。外汇与贵金属盘口受经纪商深度限制,价差与滑点风险高,直接用期货结论需重测。 下面这段代码是最小可用处理器,挂在 MT5 里用调试模式跑 SBRF-12.14 就能在日志看到逐条 Type/Price/Volume。
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) { Print("Book event for: "+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">//--- process book data for(class="type">int idx=class="num">0;idx<ArraySize(last_bookArray);idx++) { MqlBookInfo curr_info=last_bookArray[idx]; class=class="str">"cmt">//--- print PrintFormat("Type: %s",EnumToString(curr_info.type)); PrintFormat("Price: %class="num">0."+IntegerToString(_Digits)+"f",curr_info.price); PrintFormat("Volume: %d",curr_info.volume); } } } }
盘口卖单堆量的真实截面
上面这串日志是 SBRF-12.14 在 M1 周期下、11:24:33.812 同一毫秒由 BookEventProcessor1 抓到的深度报单快照。所有记录时间戳完全一致,说明这是一次 BookEvent 触发的同步回调,不是逐笔成交流。 卖侧挂单从 7704 一路铺到 7698:7704 挂 6 手、7703 挂 3 手、7701 挂 2 手、7700 挂 10 手、7698 挂 50 手,类型清一色 BOOK_TYPE_SELL。 最扎眼的是 7700 价位那一档——Volume 1011,是相邻档位的 20 倍以上,形成一道明显的薄价下方厚墙。外汇与贵金属盘口虽不如俄股合约这般集中,但类似单档异常堆量在 XAUUSD 美盘开盘也常出现,属高波动高风险信号,倾向预示该价位短期有强抛压或冰山单掩护。 开 MT5 接 MqlBookInfo + OnBookEvent,把上面这种同毫秒截面按价位排个序,你就能肉眼看到谁在挂墙、墙在哪破。