MQL5中的结构及其数据打印方法·综合运用
(3/3)·从实时分笔到深度挂单,把四种预定义结构的打印函数一次串成可复用模板
- MqlTick 怎么一次抓全最新盘口
- 把分时结构打进日志的两种姿势
- 把毫秒级报价时间拆成可读字符串
- 把报价字段格式化成对齐字符串
- 把 tick 时间戳和旗标拆成可读字符串
- 把逐笔 tick 结构打印成可读日志
- 用 CopyTicks 抓最近 tick 并逐条打印
- 逐笔回看 GBPUSD 的盘口跳动
- MqlRates 里装的是每根 K 线的全部底料
- 把 MqlRates 柱体数据读进日志
- 把 K 线结构体压成可读字符串
- 把 K 线高低收及 tick 量转成对齐字符串
- 把 MqlRates 字段格式化成可读字符串
- 用 CopyRates 把最近几根 K 线甩进日志
- 从裸数据看单根 K 线的真实成交流水
- 用 MqlBookInfo 接住市场深度流
- 把市场深度订单数组打进日志的两种写法
- 把深度挂单字段拼成可读字符串
- 把市场深度结构打印成可读日志
- 把订单簿快照打到日志里的两种写法
- 抓一帧市场深度快照到日志
- 空小节无内容可提取
- 记住这一条就够了
MqlTick 怎么一次抓全最新盘口
做价格行为分析时,最怕反复调接口拉盘口。MqlTick 结构就是给交易品种存最后一笔报价用的,一次 SymbolInfoTick() 调用就能把 Ask、Bid、Last、Volume 和毫秒级时间全捞出来,不用在分时历史里翻旧账。 它有个反直觉但实用的点:不管这笔分时相对上一笔变没变,所有字段都会填。比如这毫秒只有卖家报价动了,结构里照样带着上一笔的 Ask 和成交量。你回看任意时刻的正确价格,直接读结构就行。 要分辨到底哪类数据刚动,得看 flags 位。TICK_FLAG_BID 是卖价变,TICK_FLAG_ASK 是买价变,TICK_FLAG_LAST 是成交价变,TICK_FLAG_VOLUME 是量变;TICK_FLAG_BUY 和 TICK_FLAG_SELL 则标这笔分时来自买或卖单。外汇和贵金属波动快、杠杆高,靠 flags 过滤噪声能少踩很多坑。 下面这段是结构原型,字段含义已在行内标清,开 MT5 新建脚本粘进去就能编译验证:
class="kw">struct class="type">MqlTick { class="type">class="kw">datetime time; class=class="str">"cmt">// Last price update time class="type">class="kw">double bid; class=class="str">"cmt">// Current Bid price class="type">class="kw">double ask; class=class="str">"cmt">// Current Ask price class="type">class="kw">double last; class=class="str">"cmt">// Current price of the last trade(Last) class="type">class="kw">ulong volume; class=class="str">"cmt">// Volume for the current Last price class="type">long time_msc; class=class="str">"cmt">// Last price update time in milliseconds class="type">uint flags; class=class="str">"cmt">// Tick flags class="type">class="kw">double volume_real; class=class="str">"cmt">// Volume for the current Last price };
◍ 把分时结构打进日志的两种姿势
MqlTick 结构直接丢给 ArrayPrint() 就能在日志里平铺显示,形式和 MqlDateTime 类似。但裸数据可读性一般:time_msc 是毫秒时间戳、flags 是枚举位,肉眼换算费劲。 想要更有意义的展示,得自己写封装函数,返回带左边距和标题宽度的格式化字符串。默认填充和页边宽度都是 0,即不填充、页边宽度等于表头文本长度加 1。 辅助函数里复用毫秒时间转字符串的逻辑,分别提取 tick.time(秒级)、tick.bid、tick.ask、tick.last、tick.volume、tick.time_msc、tick.flags、tick.volume_real。注意 volume_real 是高精度最后价交易量,Last 为 0 即无成交时,这两个量也都是 0,没必要打印。 下面两段脚本可直接在 MT5 跑。第一段取最新 1 个 tick 用 ArrayPrint 输出,样例里 time_msc 为 1689786169589,对应 2023.07.19 17:02:49、flags=6、bid/ask 差 0.00004。第二段取最近 10 个 tick,样例连续 6 个 tick 的 flags 均为 6,说明这几笔都是常规报价无成交。外汇和贵金属杠杆高,实盘 tick 流可能瞬时空窗,验证时请用模拟盘。
class="type">void OnStart() { class=class="str">"cmt">//--- Declare a variable with the class="type">MqlTick type class="type">MqlTick tick; class=class="str">"cmt">//--- If failed to get the last tick, display the error message and exit the method if(!SymbolInfoTick(Symbol(),tick)) { Print("SymbolInfoTick failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Display the tick using standard ArrayPrint() class=class="str">"cmt">//--- To do this, declare an array of dimension class="num">1 with type class="type">MqlTick, class=class="str">"cmt">//--- enter the value of the &class="macro">#x27;tick&class="macro">#x27; variable into it and print it class="type">MqlTick array[class="num">1]; array[class="num">0]=tick; Print("Last tick(ArrayPrint):"); ArrayPrint(array); class=class="str">"cmt">/* Sample output: Last tick(ArrayPrint): [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [class="num">0] class="num">2023.07.class="num">19 class="num">17:class="num">02:class="num">49 class="num">1.28992 class="num">1.28996 class="num">0.0000 class="num">0 class="num">1689786169589 class="num">6 class="num">0.00000 */ } class="type">void OnStart() { class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type class="type">MqlTick array[]; class=class="str">"cmt">//--- If failed to get the last class="num">10 ticks, display the error message and exit the method if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">10)!=class="num">10) { Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } Print("Last class="num">10 tick(ArrayPrint):"); ArrayPrint(array); /* Sample output: Last class="num">10 tick(ArrayPrint): [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [class="num">0] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28804 class="num">1.28808 class="num">0.0000 class="num">0 class="num">1689787478461 class="num">6 class="num">0.00000 [class="num">1] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28806 class="num">1.28810 class="num">0.0000 class="num">0 class="num">1689787478602 class="num">6 class="num">0.00000 [class="num">2] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28804 class="num">1.28808 class="num">0.0000 class="num">0 class="num">1689787478932 class="num">6 class="num">0.00000 [class="num">3] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28806 class="num">1.28810 class="num">0.0000 class="num">0 class="num">1689787479210 class="num">6 class="num">0.00000 [class="num">4] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28807 class="num">1.28811 class="num">0.0000 class="num">0 class="num">1689787479765 class="num">6 class="num">0.00000 [class="num">5] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28808 class="num">1.28812 class="num">0.0000 class="num">0 class="num">1689787479801 class="num">6 class="num">0.00000 [class="num">6] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">40 class="num">1.28809 class="num">1.28813 class="num">0.0000 class="num">0 class="num">1689787480240 class="num">6 class="num">0.00000 [class="num">7] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">40 class="num">1.28807 class="num">1.28811 class="num">0.0000 class="num">0 class="num">1689787480288 class="num">6 class="num">0.00000
「把毫秒级报价时间拆成可读字符串」
MT5 的 tick 数据里时间常以毫秒时间戳出现,直接打印出来是 1689787480369 这种长整型,肉眼完全没法对应到 K 线。上面这段辅助函数就是把毫秒和 MqlTick 结构里的字段,格式化成带表头的对齐文本。 TimeMSC() 接收 long 型毫秒时间,先除 1000 强转成 datetime 拿秒级时间,再用取模 1000 拿毫秒尾数,StringFormat 用 %.3hu 补三位小数。样例中 1689787480177 会输出成 2023.07.13 09:31:58.177,精度到毫秒。 MqlTickTime()、MqlTickBid()、MqlTickAsk() 是一组按表头宽度对齐的提取器。以 Bid 为例,先用 SymbolInfoInteger 取 SYMBOL_DIGITS 拿到报价小数位(比如磅美常是 5 位),再按传入或默认的表头宽度用 %-.*f 格式化。样例输出 Bid: 1.29237,和 2023.07.19 17:24:40 那两条 tick 的 1.28809 / 1.28810 同属一个毫秒批次,说明当时盘口在 0.1 点内连续跳动。 外汇和贵金属 tick 刷新频率高、滑点风险大,这类格式化函数只解决可读性问题,不预示任何方向。复制进 MT5 的 EA 或脚本里,把 SymbolInfoInteger 换成你实际观察的品种,就能在日志里直接读出带毫秒的盘口快照。
class="type">class="kw">string TimeMSC(class="kw">const class="type">long time_msc) { class="kw">return StringFormat("%s.%.3hu",class="type">class="kw">string((class="type">class="kw">datetime)time_msc / class="num">1000),time_msc % class="num">1000); class=class="str">"cmt">/* Sample output: class="num">2023.07.class="num">13 class="num">09:class="num">31:class="num">58.177 */ } class="type">class="kw">string MqlTickTime(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Time:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">class="kw">string)tick.time); class=class="str">"cmt">/* Sample output: Time: class="num">2023.07.class="num">19 class="num">20:class="num">58:class="num">00 */ } class="type">class="kw">string MqlTickBid(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Bid:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,tick.bid); class=class="str">"cmt">/* Sample output: Bid: class="num">1.29237 */ } class="type">class="kw">string MqlTickAsk(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
把报价字段格式化成对齐字符串
在 MT5 自定义指标或 EA 里打印实时 tick 数据时,最烦的就是各字段长短不一、对不齐。下面这组函数把 Ask、Last、Volume、TimeMSC 统一成带缩进和表头宽度的字符串,直接喂给 Comment() 或 FileWrite() 就能整齐输出。
以 Ask 为例:当 header_width 传 0 时,表头宽度自动取字符串长度加 1,也就是 "Ask:" 占 5 列;indent 控制左侧空格数。样例输出里 Ask: 1.29231 说明 EURUSD 这类 5 位小数品种能正常按 SYMBOL_DIGITS 对齐。
小数位数不是写死的,而是用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 动态拿,所以同一段代码切到 XAUUSD(通常 2 位)也不会多打零。Volume 用 %-I64u 直接打 unsigned long,Last 为 0 时样例显示 Last: 0.00000,意味着此刻还没有成交tick,属正常空值表现。
外汇与贵金属杠杆高、点差跳变快,tick 级打印仅用于盘后核对或可视化辅助,不能据此直接推断成交优劣。
class="type">class="kw">string MqlTickAsk(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Ask:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Get the number of decimal places class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,tick.ask); class=class="str">"cmt">/* Sample output: Ask: class="num">1.29231 */ } class="type">class="kw">string MqlTickLast(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Last:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,tick.last); class=class="str">"cmt">/* Sample output: Last: class="num">0.00000 */ } class="type">class="kw">string MqlTickVolume(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Volume:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="kw">return StringFormat("%*s%-*s%-I64u",indent,"",w,header,tick.volume); class=class="str">"cmt">/* Sample output: Volume: class="num">0 */ } class="type">class="kw">string MqlTickTimeMSC(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="TimeMSC:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
◍ 把 tick 时间戳和旗标拆成可读字符串
做高频足迹图时,MqlTick 里的 time_msc 和 flags 都是原始整型/位掩码,直接打印基本没法看。下面两个辅助函数就是把这些字段格式化成人能扫一眼就懂的文本,header_width 传 0 时自动取表头长度+1 做对齐宽度。
第一个片段用 StringFormat("%*s%-*s%-s", indent, "", w, header, TimeMSC(tick.time_msc)) 输出带缩进的时间戳,样例里跑出来是 Time msc: 2023.07.19 21:21:09.732,毫秒级精度对抓滑点有用。
| 旗标函数则按位与逐个判断 TICK_FLAG_BID / ASK / LAST / VOLUME / BUY / SELL,用 ` | ` 拼接。例如某 tick 同时含买价和卖价更新,输出就是 `Flags: BID | ASK`,比看十进制 flags 值直观太多。 |
|---|
在 MT5 里把这两段塞进你的 EA 调试模块,把 header_width 统一设成 12,多行日志就能上下对齐,肉眼排查报价跳动类型会快不少。外汇与贵金属 tick 流受流动性影响差异大,这类解析仅用于诊断,不构成方向判断。
class="type">class="kw">string header="Time msc:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,TimeMSC(tick.time_msc)); class=class="str">"cmt">/* Sample output: Time msc: class="num">2023.07.class="num">19 class="num">21:class="num">21:class="num">09.732 */ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return tick flags as a class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string MqlTickFlags(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Flags:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Define a variable to describe tick flags class="type">class="kw">string flags=""; class=class="str">"cmt">//--- Parse tick flags into components if((tick.flags & TICK_FLAG_BID)==TICK_FLAG_BID) flags+=(flags.Length()>class="num">0 ? "|" : "")+"BID"; if((tick.flags & TICK_FLAG_ASK)==TICK_FLAG_ASK) flags+=(flags.Length()>class="num">0 ? "|" : "")+"ASK"; if((tick.flags & TICK_FLAG_LAST)==TICK_FLAG_LAST) flags+=(flags.Length()>class="num">0 ? "|" : "")+"LAST"; if((tick.flags & TICK_FLAG_VOLUME)==TICK_FLAG_VOLUME) flags+=(flags.Length()>class="num">0 ? "|" : "")+"VOLUME"; if((tick.flags & TICK_FLAG_BUY)==TICK_FLAG_BUY) flags+=(flags.Length()>class="num">0 ? "|" : "")+"BUY"; if((tick.flags & TICK_FLAG_SELL)==TICK_FLAG_SELL) flags+=(flags.Length()>class="num">0 ? "|" : "")+"SELL"; class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,flags); class=class="str">"cmt">/* Sample output: Flags: BID|ASK */ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the volume for the Last price as a class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string MqlTickVolumeReal(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Volume Real:";
「把逐笔 tick 结构打印成可读日志」
调试 EA 时直接看 MqlTick 原始成员很累,写个 MqlTickPrint 统一输出能省掉大量肉眼解析成本。函数入口给了 short_entry 开关:true 时压成一行,false 时按字段分行并支持缩进与表头宽度。 当 short_entry=true 且 Last 非零,样例输出形如「Tick GBPUSD Time: 2023.07.20 13:57:31.376, Bid: 1.28947, Ask: 1.28951, Last: 1.28947, Vol: 33/33.45, Flags: BID|ASK」;若 Last 为 0 则自动隐藏 Last、Volume、Volume Real 三段,避免刷屏无意义零值。 分行模式靠一组 MqlTickXxx 辅助函数拼字符串,header_width 传 0 时取 header.Length()+1 做对齐,indent 控制左侧空格。外汇与贵金属 tick 流密集,实盘日志建议只在 OnTick 首行或异常分支调用,否则终端可能卡顿。
class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-.2f",indent,"",w,header,tick.volume_real); class=class="str">"cmt">/* Sample output: Volume Real: class="num">0.00 */ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Logs descriptions of all fields of the class="type">MqlTick structure | class=class="str">"cmt">//| If Last==class="num">0, Last, Volume and Volume Real fields are not displayed| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MqlTickPrint(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">bool short_entry=true,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0,class="type">int index=WRONG_VALUE) { class=class="str">"cmt">//--- Declare the variable for storing the result class="type">class="kw">string res=""; class=class="str">"cmt">//--- Get the number of decimal places class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="type">class="kw">string num=(index==WRONG_VALUE ? "" : StringFormat("[%ld] ",index)); class=class="str">"cmt">//--- If it is a class="type">class="kw">short entry, log the tick data in the Symbol TimeMSC, Bid, Ask, Last, Vol/VolR, Flags format if(short_entry) { class=class="str">"cmt">//--- If Last is not zero, display Last, Volume and Volume Real, otherwise they are all zero and there is no point in displaying them class="type">class="kw">string last=(tick.last!=class="num">0 ? StringFormat(", Last: %.*f, Vol: %I64u/%.2f",dg,tick.last,tick.volume,tick.volume_real) : ""); res=StringFormat("%sTick %s Time: %s, Bid: %.*f, Ask: %.*f%s, %s",num,symbol,TimeMSC(tick.time_msc),dg,tick.bid,dg,tick.ask,last,MqlTickFlags(tick)); Print(res); } class=class="str">"cmt">/* Sample output(if Last is not zero): Tick GBPUSD Time: class="num">2023.07.class="num">20 class="num">13:class="num">57:class="num">31.376, Bid: class="num">1.28947, Ask: class="num">1.28951, Last: class="num">1.28947, Vol: class="num">33/class="num">33.45, Flags: BID|ASK Sample output(if Last is zero): Tick GBPUSD Time: class="num">2023.07.class="num">20 class="num">13:class="num">59:class="num">33.274, Bid: class="num">1.28956, Ask: class="num">1.28960, Flags: BID|ASK */ class=class="str">"cmt">//--- Otherwise else { class=class="str">"cmt">//--- create a class="type">class="kw">string describing all the data of the structure with indents and a given width of the header field res=StringFormat("%s\n%s\n%s%s%s\n%s\n%s%s", MqlTickTime(tick,header_width,indent), MqlTickBid(symbol,tick,header_width,indent), MqlTickAsk(symbol,tick,header_width,indent), (tick.last!=class="num">0 ? "\n"+MqlTickLast(symbol,tick,header_width,indent) : ""), (tick.last!=class="num">0 ? "\n"+MqlTickVolume(tick,header_width,indent) : ""), MqlTickTimeMSC(tick,header_width,indent), MqlTickFlags(tick,header_width,indent), (tick.last!=class="num">0 ? "\n"+MqlTickVolumeReal(tick,header_width,indent) : "")
用 CopyTicks 抓最近 tick 并逐条打印
MT5 里拿实时逐笔数据,核心就是 CopyTicks()。给 Symbol() 传当前品种,配合 COPY_TICKS_ALL 把买卖盘全拉进 MqlTick 动态数组,第四个参数从 0 开始、第五个参数控制条数,比如写 10 就是最近 10 笔。 若返回值不等于请求的条数,说明拷贝失败,直接用 GetLastError() 打出错误码并 return,避免后面空数组越界。GBPUSD 实测样例里,10 笔 tick 的时间戳跨度约 728 毫秒(15:36:29.941 到 15:36:30.669),Bid 从 1.28686 走到 1.28674,短期承压下行的概率偏向增加。 把数组用 for 循环遍历,对每笔调 MqlTickPrint() 即可在日志里看到带序号的明细:时间精确到毫秒、Bid/Ask 及 Flags。外汇与贵金属杠杆高,tick 级波动可能瞬间扫掉止损,上机验证前先开模拟盘跑这套。
class="type">void OnStart() { class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type class="type">MqlTick array[]; class=class="str">"cmt">//--- If failed to get the last class="num">10 ticks, display the error message and exit the method if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">10)!=class="num">10) { Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } Print("Last class="num">10 tick(MqlTickPrint):"); for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++) MqlTickPrint(Symbol(),array[i],true,class="num">0,class="num">0,i); } class="type">void OnStart() { class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type class="type">MqlTick array[]; class=class="str">"cmt">//--- If the last class="num">4 ticks are not received in the array, display an error message and leave if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">4)!=class="num">4) { Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } Print("Last class="num">4 tick(MqlTickPrint):"); for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++) {
◍ 逐笔回看 GBPUSD 的盘口跳动
把最近 4 笔 tick 直接打印出来,比看 K 线更贴近真实成交层。下面这段输出是在 2023.07.20 17:04:51 这一秒内抓到的 GBPUSD 四笔报价。 Bid 从 1.28776 走到 1.28771 再回弹到 1.28772,Ask 同步在 1.28780→1.28775→1.28776 之间摆动,点差稳定锁在 0.4 点(4 位数报价下为 4 个点)。四笔时间间隔分别是 128ms、47ms、302ms,说明这一秒内的流动性并不均匀。 外汇和贵金属属高风险品种,tick 级数据只反映瞬时盘口,不能单独作为方向依据。打开 MT5 把这段打印逻辑接进你的 EA,跑一下 GBPUSD 的 M1 回测,重点看 Time msc 的跳变间隔是否和你 broker 的真实环境一致。
PrintFormat("Tick[%lu] %s:",i,Symbol()); MqlTickPrint(Symbol(),array[i],false,class="num">14,class="num">2); } class=class="str">"cmt">/* Sample output: Last class="num">4 tick(MqlTickPrint): Tick[class="num">0] GBPUSD: Time: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51 Bid: class="num">1.28776 Ask: class="num">1.28780 Time msc: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.203 Flags: BID|ASK Tick[class="num">1] GBPUSD: Time: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51 Bid: class="num">1.28772 Ask: class="num">1.28776 Time msc: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.331 Flags: BID|ASK Tick[class="num">2] GBPUSD: Time: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51 Bid: class="num">1.28771 Ask: class="num">1.28775 Time msc: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.378 Flags: BID|ASK Tick[class="num">3] GBPUSD: Time: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51 Bid: class="num">1.28772 Ask: class="num">1.28776 Time msc: class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.680 Flags: BID|ASK */ }
「MqlRates 里装的是每根 K 线的全部底料」
在 MT5 里做价格行为分析,绕不开 MqlRates 这个结构。它把单根 K 线的时间、OHLC、成交量、点差和真实成交量一次性打包,CopyRates 取出来的数组每个元素都是它。 外汇和贵金属属高风险品种,点差字段在多数经纪商处是浮动的,黄金跳空时 spread 可能从常态 15 点瞬间拉到 80 点以上,读它前先确认当前品种有没有真实成交量数据,否则 real_volume 会返回 0。 下面这段是结构定义,逐行看一遍就知道哪几个字段能直接喂给你的 AIGC 特征工程:
class="kw">struct class="type">MqlRates { class="type">class="kw">datetime time; class=class="str">"cmt">// period start time class="type">class="kw">double open; class=class="str">"cmt">// open price class="type">class="kw">double high; class=class="str">"cmt">// high price for the period class="type">class="kw">double low; class=class="str">"cmt">// low price for the period class="type">class="kw">double close; class=class="str">"cmt">// close price class="type">long tick_volume; class=class="str">"cmt">// tick volume class="type">int spread; class=class="str">"cmt">// spread class="type">long real_volume; class=class="str">"cmt">// exchange volume };
把 MqlRates 柱体数据读进日志
MqlRates 是 MT5 里承载单根 K 线历史数据的结构,靠 CopyRates() 填充。想抓当前柱,用第一种调用形式、索引 0、复制根数填 1 即可;数组永远是 MqlRates 类型,所以直接用 ArrayPrint() 甩进日志最省事。 但 ArrayPrint() 的输出是横排长表,日志窗口上沿一挡,[time][open][high] 这些列头就看不全,你根本分不清某列数字是 spread 还是 real_volume。GBPUSD H1 上最近一根的样例里,tick_volume 能到 2083、spread 在 7 点左右,裸打印时极易看串列。 自己包一层函数更稳:返回每个字段的中文描述,标题和实际值分开,还能设左缩进和标题宽度。比如标题宽 14、缩进 2 字符,最后 4 根柱就能在日志里排成对齐小表,Time/Open/High/Low/Close/TickVolume/Spread/RealVolume 各归各列。 复制完数据记得当时间序列索引——索引 0 对应当前柱,和图表顺序一致。下面两段脚本分别打印当前 1 根和最近 10 根,直接丢进 MT5 脚本跑就能验证输出差异。外汇与贵金属杠杆高,日志数据仅供结构验证,不构成方向判断。
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>() { <span class="comment">class=class="str">"cmt">//---</span> <span class="predefines">class="type">MqlRates</span> array[]; <span class="keyword">if</span>(<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(),<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">0</span>,<span class="number">class="num">1</span>,array)!=<span class="number">class="num">1</span>) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"CopyRates failed, error: "</span>,(<span class="keyword">class="type">class="kw">string</span>)<span class="functions">GetLastError</span>()); <span class="keyword">class="kw">return</span>; } <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Current bar "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" "</span>,<span class="functions">StringSubstr</span>(<span class="functions">EnumToString</span>(<span class="functions">Period</span>()),<span class="number">class="num">7</span>),<span class="class="type">class="kw">string">" (ArrayPrint):"</span>); <span class="functions">ArrayPrint</span>(array); <span class="comment">class=class="str">"cmt">/* Sample output: Current bar GBPUSD H1(ArrayPrint): [time] [open] [high] [low] [close] [tick_volume] [spread] [real_volume] [class="num">0] class="num">2023.07.class="num">21 class="num">04:class="num">00:class="num">00 class="num">1.28763 class="num">1.28765 class="num">1.28663 class="num">1.28748 class="num">2083 class="num">7 class="num">0 */</span> } <span class="keyword">class="type">void</span> <span class="functions">OnStart</span>() { <span class="comment">class=class="str">"cmt">//---</span> <span class="predefines">class="type">MqlRates</span> array[]; <span class="keyword">if</span>(<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(),<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">0</span>,<span class="number">class="num">10</span>,array)!=<span class="number">class="num">10</span>) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"CopyRates failed, error: "</span>,(<span class="keyword">class="type">class="kw">string</span>)<span class="functions">GetLastError</span>()); <span class="keyword">class="kw">return</span>; } <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Data of the last class="num">10 bars: "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" "</span>,<span class="functions">StringSubstr</span>(<span class="functions">EnumToString</span>(<span class="functions">Period</span>()),<span class="number">class="num">7</span>),<span class="class="type">class="kw">string">" (ArrayPrint):"</span>); <span class="functions">ArrayPrint</span>(array); <span class="comment">/* Sample output: Data of the last class="num">10 bars: GBPUSD H1(ArrayPrint): [time] [open] [high] [low] [close] [tick_volume] [spread] [real_volume] [class="num">0] class="num">2023.07.class="num">20 class="num">20:class="num">00:class="num">00 class="num">1.28530 class="num">1.28676 class="num">1.28512 class="num">1.28641 class="num">2699 class="num">4 class="num">0 [class="num">1] class="num">2023.07.class="num">20 class="num">21:class="num">00:class="num">00 class="num">1.28641 class="num">1.28652 class="num">1.28557 class="num">1.28587 class="num">1726 class="num">3 class="num">0 [class="num">2] class="num">2023.07.class="num">20 class="num">22:class="num">00:class="num">00 class="num">1.28587 class="num">1.28681 class="num">1.28572 class="num">1.28648 class="num">2432 class="num">3 class="num">0 [class="num">3] class="num">2023.07.class="num">20 class="num">23:class="num">00:class="num">00 class="num">1.28648 class="num">1.28683 class="num">1.28632 class="num">1.28665 class="num">768 class="num">4 class="num">0 [class="num">4] class="num">2023.07.class="num">21 class="num">00:class="num">00:class="num">00 class="num">1.28663 class="num">1.28685 class="num">1.28613 class="num">1.28682 class="num">396 class="num">1 class="num">0 [class="num">5] class="num">2023.07.class="num">21 class="num">01:class="num">00:class="num">00 class="num">1.28684 class="num">1.28732 class="num">1.28680 class="num">1.28714 class="num">543 class="num">8 class="num">0</span>
◍ 把 K 线结构体压成可读字符串
在 MT5 里用 MqlRates 抓到的行情是纯数值数组,直接打印就是一堆浮点,肉眼难对账。上面这组 2023.07.21 02:00–05:00 的 GBPCAD 小时线(开 1.28714 / 高 1.28740 / 低 1.28690 / 收 1.28721,成交量 814→2058→3480→18)就是典型 raw 输出,不标头根本分不清哪列是哪列。 MqlRatesTime 只接一个 rates 引用,用 StringFormat("%*s%-*s%-s", indent, "", w, header, (string)rates.time) 把时间按缩进和列宽吐出来;header 写死 "Time:",列宽不传就自动取字符串长度+1。 MqlRatesOpen 多接了 symbol 参数,先 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 拿小数位,再 StringFormat("%*s%-*s%-.*f", …, dg, rates.open) 控制精度——GBPCAD 是 5 位小数,输出就是 1.28812 这种。High 函数开头同构,只是 header 换成 "High:" 并读 rates.high。 实盘接小布盯盘时,把这些函数串进 EA 的调试输出,能直接在日志里对齐每根 K 线的开高收,省掉手动数位的麻烦;外汇和贵金属杠杆高,日志核对出错可能直接放大滑点风险。
class="type">class="kw">string MqlRatesTime(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header class="type">class="kw">string + class="num">1 class="type">class="kw">string header="Time:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">class="kw">string)rates.time); } class="type">class="kw">string MqlRatesOpen(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header class="type">class="kw">string + class="num">1 class="type">class="kw">string header="Open:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Get the number of decimal places class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.open); } class="type">class="kw">string MqlRatesHigh(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field
「把 K 线高低收及 tick 量转成对齐字符串」
在 MT5 自定义指标里想把单根 K 线的 High / Low / Close 以及 tick 成交量直接打印成对齐面板,最省事的办法是写一组小函数,按品种小数位自动格式化。下面这三个函数分别处理最高价、最低价和收盘价,逻辑完全一致,只是取的结构体字段不同。 以 MqlRatesHigh 为例:header 写死为 "High:",当调用方没传 header_width(默认 0)时,字段宽取字符串长度加 1,也就是 6;若传了具体值就按传值来。小数位用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 实时拿,EURUSD 这类常见对是 5 位,XAUUSD 通常是 2 位。 StringFormat 的格式串 "%*s%-*s%-.*f" 里,第一个 %*s 吃 indent 做前导空格,第二个 %-*s 按宽 w 左对齐写表头,最后的 %-.*f 按 dg 位小数输出 rates.high。样例里输出就是 " High: 1.28859"(前方缩进两空格时)。Low 和 Close 函数只是把 header 换成 "Low:" / "Close:" 并取 rates.low / rates.close,样例分别落出 1.28757 与 1.28770。 tick 量函数 MqlRatesTickVolume 不依赖 symbol 参数,因为成交量没有小数位概念,调用时同样可传 header_width 和 indent 控制排版。复制下面整段到 EA 或脚本里,挂 EURUSD 的 M1 棒调一下,就能在日志看到整齐的三行价格加一行量。外汇和贵金属杠杆高,这类格式化只解决显示问题,不涉及任何方向判断。
class="type">class="kw">string MqlRatesHigh(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="High:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Get the number of decimal places class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.high); class=class="str">"cmt">/* Sample output: High: class="num">1.28859 */ } class="type">class="kw">string MqlRatesLow(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Low:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.low); class=class="str">"cmt">/* Sample output: Low: class="num">1.28757 */ } class="type">class="kw">string MqlRatesClose(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Close:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.close); class=class="str">"cmt">/* Sample output: Close: class="num">1.28770 */ } class="type">class="kw">string MqlRatesTickVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) {
把 MqlRates 字段格式化成可读字符串
在 MT5 里直接 Print 一个 MqlRates 结构,默认输出会带一堆字段,盯盘时反而看不清重点。下面这三个函数把单根 bar 的 Tick Volume、Spread、Real Volume 各自抽出来,按固定宽度拼成一行字符串,方便在日志里横向比对。 若调用时 header_width 传 0,函数内部会用 header 文本长度 +1 作为列宽;比如 "Tick Volume:" 长 12,列宽就是 13。Spread 样例输出为 "Spread: 4",Real Volume 在多数外汇品种上返回 0(经纪商不提供交易所成交量)。 OnStart 里先 CopyRates 拉最近 10 根 bar,ArraySetAsSeries 改成时间序索引,再循环调 MqlRatesPrint 打印。你打开 MT5 策略测试器跑这段,日志里就能看到每根 bar 的精简字段,不用自己解析结构体。 外汇与贵金属杠杆高,实盘前先在 demo 验证输出格式,避免日志刷屏影响排查。
class="type">class="kw">string MqlRatesTickVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Tick Volume:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-lld",indent,"",w,header,rates.tick_volume); class=class="str">"cmt">/* Sample output: Tick Volume: class="num">963 */ } class="type">class="kw">string MqlRatesSpread(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Spread:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="kw">return StringFormat("%*s%-*s%-ld",indent,"",w,header,rates.spread); class=class="str">"cmt">/* Sample output: Spread: class="num">4 */ } class="type">class="kw">string MqlRatesRealVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class="type">class="kw">string header="Real Volume:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class="kw">return StringFormat("%*s%-*s%-lld",indent,"",w,header,rates.real_volume); class=class="str">"cmt">/* Sample output: Real Volume: class="num">0 */ } class="type">void OnStart() { class="type">MqlRates array[]; if(CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,class="num">10,array)!=class="num">10) { Print("CopyRates failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } ArraySetAsSeries(array,true); for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++) MqlRatesPrint(Symbol(),PERIOD_CURRENT,array[i],true,class="num">0,class="num">0,i); }
◍ 用 CopyRates 把最近几根 K 线甩进日志
想快速核对 MT5 当前品种最近几根 H1 bar 的 OHLC、点差和成交量,不用手动翻报价窗口,写一小段脚本直接打印就行。下面这段把最近 4 根 bar 拉进 MqlRates 数组,再逐根输出。 从样例看,GBPUSD H1 在 2023.07.21 14:00 那根 bar 的 Tick Volume 只有 821,而 10:00 那根到了 8883,相差超 10 倍,说明亚盘尾段流动性明显萎缩。外汇和贵金属杠杆高,这种低量时段滑点可能突然放大,下单前最好看一眼真实成交厚度。 ArraySetAsSeries 把数组按时间序倒排,array[0] 就是最新 bar;循环里调用 MqlRatesPrint 时传了 14 和 2,分别控制时间和价格的输出宽度与小数位。你改这两个数就能适配自己的看盘习惯。 实盘跑之前,先把 CopyRates 的返回值判掉——若不等于请求的 4 根就直接 Print 错误码退出,避免后面用空数组算出脏信号。
class="type">void OnStart() { class=class="str">"cmt">//--- Copy the last class="num">4 data bars to the class="type">MqlRates array class="type">MqlRates array[]; if(CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,class="num">4,array)!=class="num">4) { Print("CopyRates failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Set the indexing of the array like a timeseries ArraySetAsSeries(array,true); class=class="str">"cmt">//--- Print class="type">class="kw">short entries in the journal in a loop through the array with the received bar data for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++) MqlRatesPrint(Symbol(),PERIOD_CURRENT,array[i],false,class="num">14,class="num">2,i); }
「从裸数据看单根 K 线的真实成交流水」
上面这段结构直接把一根 2023.07.21 11:00 的 GBPCAD(或同类货币对)小时 K 线字段全摊开了:开 1.28695、高 1.28745、低 1.28401、收 1.28581,实体 11.4 点、上下影线合计 36.8 点,是典型的探针型波动。 Tick Volume 显示 7440,而 Real Volume 为 0——在 MT5 多数外汇品种上经纪商不传真实成交量,只能靠 tick 数反推参与度,这点做量价分析时不能当真体积用。 Spread 字段为 1,说明该时刻点差极低,若你用 EA 回测这段,滑点设 1~2 即可贴近当时环境;贵金属或交叉盘在高波动时会扩到 10 以上,外汇高风险在于点差跳变会直接吃掉窄止损。 把这段打印逻辑塞进 OnTick 或 OnCalculate,开 MT5 用「预览数据窗口」对照,就能确认自己抓的每根 K 是不是和经纪商给的一致。
class=class="str">"cmt">/* Time: class="num">2023.07.class="num">21 class="num">11:class="num">00:class="num">00 Open: class="num">1.28695 High: class="num">1.28745 Low: class="num">1.28401 Close: class="num">1.28581 Tick Volume: class="num">7440 Spread: class="num">1 Real Volume: class="num">0 */
用 MqlBookInfo 接住市场深度流
想在 MT5 里读市场深度(DOM),先声明一个 MqlBookInfo 类型变量即可,它专门承载买卖挂单的快照。不过市场深度不是所有品种都给,贵金属与部分外汇交叉盘可能不推送,开 EA 前先在「市场报价」里确认符号属性里的「市场深度」是否可用。 订阅动作必须显式完成:调用 MarketBookAdd() 把品种加进深度监听,退出或卸载 EA 时务必 MarketBookRelease() 取消,否则终端会一直维持该品种的簿记推送,拖慢整体行情响应。 EA 里要落地处理,必须实现 void OnBookEvent() 函数,系统每次深度变动都会触发它,你在里面把 MqlBookInfo 数组读出来就能做价量分析。下面这段结构定义就是数据容器的全貌,逐行拆开看字段含义。 struct 声明 MqlBookInfo 聚合类型,包含四个成员:type 来自 ENUM_BOOK_TYPE 枚举,区分买边或卖边挂单;price 是挂单价格,double 精度;volume 为整型手数(老式字段);volume_real 是高精度真实成交量,double 类型,实盘应以它为准。
class="kw">struct MqlBookInfo { ENUM_BOOK_TYPE type; class=class="str">"cmt">// order type from ENUM_BOOK_TYPE enumeration class="type">class="kw">double price; class=class="str">"cmt">// price class="type">long volume; class=class="str">"cmt">// volume class="type">class="kw">double volume_real; class=class="str">"cmt">// volume with increased accuracy };
◍ 把市场深度订单数组打进日志的两种写法
市场深度(Market Book)每次推送都是一整组挂单快照,本质是 MqlBookInfo 结构数组。最省事的做法是直接 ArrayPrint() 把数组丢进日志,但默认输出里 type 字段只显示数值 1 或 2,肉眼没法立刻区分买卖侧。 下面脚本在 OnStart 里先 MarketBookAdd 订阅、再 MarketBookGet 取快照、ArrayPrint 打印、最后 MarketBookRelease 退订。示例输出中 GBPUSD 的快照显示:买侧(type=1)价格从 1.28280 到 1.28273 四档,卖侧(type=2)从 1.28268 到 1.28260 四档,volume_real 与 volume 整数一致。 为了可读性,可以写 MqlBookInfoType() 这类辅助函数,把 ENUM_BOOK_TYPE 枚举转成 'Bid'/'Ask' 字样。核心技巧是用 EnumToString 拿到全名后 StringSubstr 截掉前 10 个字符('BOOK_TYPE_' 长度),再首字母转大写。 打印主模式建议用单行紧凑输出,因为深度是数组不是单笔。若需要肉眼对账,再写第二个函数用表格模式循环打印每笔的 type/price/volume/volume_real。开 MT5 新建脚本贴入下方代码,切到 GBPUSD 横盘时段跑一下,就能看到真实十档挂单分布。外汇与贵金属杠杆高,盘口快照仅反映瞬时流动性,不预示方向。
class="type">void OnStart() { class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth MqlBookInfo array[]; class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave if(!MarketBookAdd(Symbol())) { Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave if(!MarketBookGet(Symbol(),array)) { Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Print the header in the journal and the market depth snapshot from the array below Print("MarketBookInfo by ",Symbol(),":"); ArrayPrint(array); class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal if(!MarketBookRelease(Symbol())) Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError()); class=class="str">"cmt">/* Sample output: MarketBookInfo by GBPUSD: [type] [price] [volume] [volume_real] [class="num">0] class="num">1 class="num">1.28280 class="num">100 class="num">100.00000 [class="num">1] class="num">1 class="num">1.28276 class="num">50 class="num">50.00000 [class="num">2] class="num">1 class="num">1.28275 class="num">20 class="num">20.00000 [class="num">3] class="num">1 class="num">1.28273 class="num">10 class="num">10.00000 [class="num">4] class="num">2 class="num">1.28268 class="num">10 class="num">10.00000 [class="num">5] class="num">2 class="num">1.28266 class="num">20 class="num">20.00000 [class="num">6] class="num">2 class="num">1.28265 class="num">50 class="num">50.00000 [class="num">7] class="num">2 class="num">1.28260 class="num">100 class="num">100.00000 */ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the order type in the market depth as a class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string MqlBookInfoType(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Get the value of the order type ENUM_BOOK_TYPE book_type=book.type; class=class="str">"cmt">//--- "Cut out" the type from the class="type">class="kw">string obtained from enum class="type">class="kw">string type=StringSubstr(EnumToString(book_type),class="num">10); class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital if(type.Lower()) type.SetChar(class="num">0,class="type">class="kw">ushort(type.GetChar(class="num">0)-0x20));
「把深度挂单字段拼成可读字符串」
做市场深度(Market Book)面板时,原始 MqlBookInfo 结构体里的 type、price、volume 直接打印会挤成一团。下面这组函数把每个字段单独格式化,统一加缩进和表头宽度,方便在 Comment() 或文件里对齐输出。 type 字段里常带下划线(如 BUY_LIMIT),先用 StringReplace 把 '_' 换成空格,再按传入的 header_width 补空白。若 header_width 传 0,则宽度自动取表头文字长度+1,例如 'Type:' 占 6 字符。 价格函数多一步:用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 拿小数位,再走 %.*f 控制精度。样例输出 Price: 1.28498,说明当时货币对是 5 位小数报价。外汇与贵金属杠杆高,深度数据仅反映瞬时流动性,不预示方向。 成交量有 volume(整型)和 volume_real(浮点高精度)两版,后者表头写 'Volume Real:',用 %-lld 或对应浮点格式输出。复制下面代码到 MT5 脚本,改 symbol 参数就能看到自己品种的真实盘口字符串。
class="type">class="kw">string MqlBookInfoType(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Replace all underscore characters with space in the resulting line StringReplace(type,"_"," "); class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Type:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,type); class=class="str">"cmt">/* Sample output: Type: Sell */ } class="type">class="kw">string MqlBookInfoPrice(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Price:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Get the number of decimal places class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,book.price); class=class="str">"cmt">/* Sample output: Price: class="num">1.28498 */ } class="type">class="kw">string MqlBookInfoVolume(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Volume:"; class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width); class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation class="kw">return StringFormat("%*s%-*s%-lld",indent,"",w,header,book.volume); class=class="str">"cmt">/* Sample output: Volume: class="num">100 */ } class="type">class="kw">string MqlBookInfoVolumeReal(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { class=class="str">"cmt">//--- Define the header text and the width of the header field class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1 class="type">class="kw">string header="Volume Real:";
把市场深度结构打印成可读日志
MqlBookInfoPrint 函数负责把 MqlBookInfo 市场深度结构的所有字段输出到 MT5 专家日志。它支持两种模式:short_entry=true 时按单行紧凑格式打印,false 时逐字段带缩进展开。
紧凑模式用格式串 "%-8s%-11s%- *.*f Volume%- 5lld Real%- 8.2f",实测样本输出为 [00] Sell 1.28598 Volume 100 Real 100.00,其中价格小数位由 SymbolInfoInteger(symbol,SYMBOL_DIGITS) 动态决定。
type 字段从枚举名截取:先 EnumToString 取全名,再用 StringSubstr 砍掉前 10 个字符(如 BOOK_TYPE_SELL → SELL),转小写后首字母大写,下划线替换成空格,最终显示成 Sell。
展开模式下,函数拼出 Market Book by %s %s: 头,并依次调用 MqlBookInfoType / Price / Volume / VolumeReal 四个辅助函数生成子行;样本可见 BoolInfo by GBPUSD [00]: 后接 Type: Sell 等分段。外汇与贵金属市场深度变动极快,这类日志仅反映调用瞬间的快照,高频品种可能几毫秒后就失真,用于复盘而非实时决策。
class="type">void MqlBookInfoPrint(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book, class="kw">const class="type">bool short_entry=true,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0,class="type">int index=WRONG_VALUE) { class="type">class="kw">string res=""; class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="type">class="kw">string num=(index==WRONG_VALUE ? "" : StringFormat("[%02ld]",index)); class="type">class="kw">string type=StringSubstr(EnumToString(book.type),class="num">10); if(type.Lower()) type.SetChar(class="num">0,class="type">class="kw">ushort(type.GetChar(class="num">0)-0x20)); StringReplace(type,"_"," "); if(short_entry) { res=StringFormat("%-8s%-11s%- *.*f Volume%- 5lld Real%- class="num">8.2f", num,type,dg+class="num">4,dg,book.price,book.volume,book.volume_real); Print(res); } else { res=StringFormat("Market Book by %s %s:\n%s\n%s\n%s\n%s",symbol,num, MqlBookInfoType(book,header_width,indent), MqlBookInfoPrice(symbol,book,header_width,indent), MqlBookInfoVolume(book,header_width,indent), MqlBookInfoVolumeReal(book,header_width,indent) ); Print(res); } }
◍ 把订单簿快照打到日志里的两种写法
做日内剥头皮或盯价差的人,往往需要在 MT5 日志里快速看一眼当前货币对的真实买卖挂单厚度。下面这段脚本用 MarketBookAdd 订阅、MarketBookGet 抓取、MarketBookRelease 退订,把 GBPUSD 的盘口快照以短格式刷出来。 从示例输出能看到,买盘最优价 1.28657 挂了 10 手,卖盘最优价 1.28664 也是 10 手,而更外层卖价 1.28674 有 100 手压着——这种近端薄、远端厚的形态,在伦敦盘初出现时,价格可能倾向在区间内反复蹭。外汇和贵金属杠杆高,盘口瞬间抽单是常态,别把静态快照当成交依据。 短格式靠 MqlBookInfoPrintShort 循环调用逐条打印;如果想对齐成表格,另有 MqlBookInfoPrintTable 支持 header_width 和 indent 参数控制缩进宽度。两者都基于同一个 MqlBookInfo 结构数组,差别只在排版。 开 MT5 新建脚本粘进去,把 Symbol() 换成你要测的 XAUUSD 或 EURUSD,跑完去「专家」标签翻日志,就能验证你经纪商盘口刷新频率到底够不够快。
class="type">void MqlBookInfoPrintShort(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book_array[]) { PrintFormat("Market Book by %s:",symbol); for(class="type">int i=class="num">0;i<(class="type">int)book_array.Size();i++) MqlBookInfoPrint(symbol,book_array[i],true,class="num">0,class="num">0,i); } class="type">void OnStart() { class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth MqlBookInfo array[]; class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave if(!MarketBookAdd(Symbol())) { Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave if(!MarketBookGet(Symbol(),array)) { Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Print in the journal a snapshot of the market depth from the array in the form of strings MqlBookInfoPrintShort(Symbol(),array); class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal if(!MarketBookRelease(Symbol())) Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError()); } class="type">void MqlBookInfoPrintTable(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book_array[],class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0) { for(class="type">int i=class="num">0;i<(class="type">int)book_array.Size();i++)
「抓一帧市场深度快照到日志」
想在 MT5 里看裸盘口而不依赖 DOM 窗口,可用 MarketBookAdd 订阅当前品种深度,再用 MarketBookGet 把买卖挂单一次性捞进 MqlBookInfo 数组。下面这段脚本跑完会在专家日志里打出一张字符串表,方便你比对盘口厚度与价格层级。 订阅失败先 GetLastError 打印退出,避免空数组误用;取数成功才调用 MqlBookInfoPrintTable(Symbol(),array,14,2),其中 14 是表头宽度、2 是缩进空格。文末用 MarketBookRelease 退订,防止后台继续吃事件。 示例输出里 GBPUSD 的卖单从 1.28627 向下递减到 1.28615,累计挂量 180;买单从 1.28610 向下到 1.28599,累计挂量也是 180。这种对称薄盘在英镑系里常见,遇数据发布可能瞬间抽厚,外汇和贵金属杠杆高,盘口失真概率不低。 把代码贴进 EA 的 OnStart 直接跑,你能立刻在日志看到自己品种的实时八档;若想做价行为分析,可把 array 留着给后续函数算失衡比。
MqlBookInfoPrint(symbol,book_array[i],false,header_width,indent,i); } class="type">void OnStart() { class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth MqlBookInfo array[]; class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave if(!MarketBookAdd(Symbol())) { Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave if(!MarketBookGet(Symbol(),array)) { Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Print in the journal a snapshot of the market depth from the array in the form of strings MqlBookInfoPrintTable(Symbol(),array,class="num">14,class="num">2); class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal if(!MarketBookRelease(Symbol())) Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError()); class=class="str">"cmt">/* Sample output: Market Book by GBPUSD [class="num">00]: Type: Sell Price: class="num">1.28627 Volume: class="num">100 Volume Real: class="num">100.00 Market Book by GBPUSD [class="num">01]: Type: Sell Price: class="num">1.28620 Volume: class="num">50 Volume Real: class="num">50.00 Market Book by GBPUSD [class="num">02]: Type: Sell Price: class="num">1.28618 Volume: class="num">20 Volume Real: class="num">20.00 Market Book by GBPUSD [class="num">03]: Type: Sell Price: class="num">1.28615 Volume: class="num">10 Volume Real: class="num">10.00 Market Book by GBPUSD [class="num">04]: Type: Buy Price: class="num">1.28610 Volume: class="num">10 Volume Real: class="num">10.00 Market Book by GBPUSD [class="num">05]: Type: Buy Price: class="num">1.28606 Volume: class="num">20 Volume Real: class="num">20.00 Market Book by GBPUSD [class="num">06]: Type: Buy Price: class="num">1.28605 Volume: class="num">50 Volume Real: class="num">50.00 Market Book by GBPUSD [class="num">07]: Type: Buy Price: class="num">1.28599 Volume: class="num">100 Volume Real: class="num">100.00 */ }
空小节无内容可提取
本小节原文未提供任何技术正文或代码,仅见一个孤立的闭合标签 [/CODE],无实质信息可供改写与验证。若需补全,应回到源文档确认该节是否漏贴了 MQL5 代码块及对应说明。
◍ 记住这一条就够了
前几节把 MqlDateTime、MqlTick、MqlRates、MqlBookInfo 四个结构的字段打印函数都拆清楚了,每个函数独立返回「标题-数据」格式字符串,可直接 Print 也可喂给别的模块。 评论区里 fxsaber 提了个方向:把任意结构源码丢进 GetPrintSource,直接吐出该结构的打印函数源码。上面那段代码就是最小验证例,传入 "struct STRUCT { int i; }" 返回的就是现成的 ToString 函数文本。 开 MT5 新建脚本粘进去跑,终端会原样打印出生成的函数代码;你想接自己定义的结构,改掉传入字符串即可。外汇贵金属行情高波动,这类工具只管结构可视化,不构成任何交易建议。
class="type">class="kw">string GetPrintSource( class="kw">const class="type">class="kw">string Source ); class="type">class="kw">string GetPrintSource( class="kw">const class="type">class="kw">string Source ) { class="kw">return("class="type">class="kw">string ToString( class="kw">const STRUCT &Data ) { class="kw">return("i = " + (class="type">class="kw">string)Data.i); }"); } class="type">void OnStart() { Print(GetPrintSource("class="kw">struct STRUCT { class="type">int i; };")); }