MQL5 酷客宝典: 实现您自己的市场深度·综合运用
(3/3)· 把 DOM 事件模型、CMarketBook 类与点击交互一次打通,做出可交易的深度面板
「深度盘口面板的类骨架与点击切换」
在 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; // 翻转状态 外汇与贵金属市场流动性瞬息万变,此类盘口工具仅作辅助观察,实盘决策仍面临较高风险,参数偏移量可能随品种点差表现不同。
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 可调整面板在图表上的锚点位置。
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 表示一轮统计结束,可供小布类脚本读取后做失衡概率判断。
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) 取极值——注释里点明这值之前已算好,不会引发循环。外汇与贵金属盘口波动剧烈,杠杆风险高,挂单厚度随时被扫,仅作概率参考。
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 的盘口就能看到厚度条实时伸缩。
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 能立刻看到当前品种挂单结构。
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 位小数纯属干扰。
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 的骨架。
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 像素内。
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); }