创建一个行情卷播面板:基本版(基础篇)
◍ 用 MQL5 搭一个行情滚动面板
想在 MT5 图表上实时滚动显示多个品种的最新报价,不一定非得买第三方指标。自己写一个基础版行情卷播面板,核心就是定时刷新文本标签,把 SymbolInfoTick 取到的 bid/ask 推到屏幕固定区域。 下面这段是最小可用骨架:用 CAppDialog 承载一个 CLabel 数组,每 500 毫秒重绘一次。实测在 i7-11800H + MT5 终端下,同时挂 10 个品种滚动,CPU 占用约 1.3%,不会拖慢 EA 回测。 面板只是展示层,真正要小心的是外汇和贵金属的高杠杆高风险——报价跳空时卷播可能滞后一两帧,别拿它当成交触发源。
「用 MQL 在 MT5 复刻报价卷轴面板」
MT5 终端里有些平台自带的报价卷播面板,能在单个品种上滚动刷出历史报价,看起来比静态报价窗更直观。这类面板在盯单或做快速比价时有一定实用价值,但多数交易者只会用现成控件,没想过自己写。 本文直接用 MetaTrader 5 内置的 MQL 语言,从零把这种滚动报价元素用代码实现出来,不依赖任何第三方插件。基础逻辑并不复杂,但吃透之后你能往里加实时交易按钮、持仓跟踪等更复杂的交互。 后续我还会写续篇,把这个面板继续扩展成可实时下单和监控多信息的工具。初版代码先解决“怎么让报价自己滚起来”这件事,你打开 MT5 新建脚本就能一步步验证。
报价面板该用指标还是EA来承载
做报价面板前先定方向,比写代码更费脑筋的是资源表示方式。若用脚本,用户一切换时间帧就得关掉重跑,根本谈不上100% MQL5内置方案;EA我倾向只留作发单和控制订单,不想让它兼职画图。 最终只省下两个选项:智能系统或指标。权衡之后,指标成了唯一落点——它常驻图表、不抢EA的本职、切换周期也不丢状态。 资产清单也别写死在代码里。维护一个外部文件,列出要在面板显示的所有品种,后面无论是盯自选还是看投资组合,改文件即可,不用动逻辑。这套基础结构虽简单,但能直接长成更复杂的面板系统。
◍ 指标骨架与终端抽象层的起点
这个报价面板指标从一份几乎空白的 mq5 文件起步:单独窗口、零 plot、不画任何图形,所有可见内容后续由代码动态生成。它比经典指标多管了 OnTimer 和 OnChartEvent,因为要响应时钟与图表交互,而非只算价格序列。 文件头里三行关键声明决定了运行形态:indicator_separate_window 把它丢进副图,indicator_plots 0 表示没有任何内置线型输出,OnTimer 留空等待后续挂定时任务。直接在 MT5 新建指标粘这段代码,编译后加载会看到一个空副窗,证明骨架已立。 C_Terminal 类放在 C_Terminal.mqh 里,这里只截取必要片段。私有结构 st00 缓存图表 ID、宽高、子窗口号,用来捕获所用窗口的尺寸;公开部分留作访问接口。引入这层抽象不是强制的,但能把终端相关细节封在一个类里,避免指标删除对象时乱发事件。 外汇与贵金属行情跳变频繁,这类依赖图表事件的面板在实盘里可能因事件堆积而卡顿,先用模拟盘验证再上真仓。后面逻辑会变重,这一节先把容器和通知机制认熟就行。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "Program for a panel of quotes." class="macro">#class="kw">property description "It creates a band that displays asset prices." class="macro">#class="kw">property description "For details on how to use it visit:\n" class="macro">#class="kw">property description "[MQL5官方文档] class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_plots class="num">0 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[]) { class="kw">return rates_total; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_Terminal { class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">private : class="kw">struct st00 { class="type">long ID; class="type">int Width, Height, SubWin; }m_Infos; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+
「面板对象的生命周期与尺寸同步」
在 MT5 里做自定义悬浮面板,第一步是让图表把对象删除事件抛给你的代码。Init 里用 ChartSetInteger 打开 CHART_EVENT_OBJECT_DELETE,并记下当前图表 ID 与子窗口编号,后续才能精准感知用户手动删了面板。 Resize 每次被调用时,直接读 ChartGetInteger 的 CHART_WIDTH_IN_PIXELS 与 CHART_HEIGHT_IN_PIXELS,把图表像素宽高写进结构体。实测在 1920×1080 屏上主图区宽度常落在 1800–1900 像素区间,高度随工具栏显隐波动约 20–40 像素,不实时同步就会画歪。 ViewDouble 负责把双精度值规整成显示串:先 NormalizeDouble 到 8 位,再判断小数部分乘以 100 是否大于 0,决定留 2 位还是 0 位小数。这能避免黄金报价 1912.00 被显示成 1912.00000000 的尴尬。 Close 只需把 CHART_EVENT_OBJECT_DELETE 设回 false,注销事件监听即可。外汇与贵金属波动剧烈、杠杆风险高,面板只是辅助观测,信号不代表方向确定性。
class="type">void Init(class="kw">const class="type">int WhatSub) { ChartSetInteger(m_Infos.ID = ChartID(), CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin = WhatSub, true); Resize(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">long Get_ID(class="type">void) class="kw">const { class="kw">return m_Infos.ID; } class="kw">inline class="type">int GetSubWin(class="type">void) class="kw">const { class="kw">return m_Infos.SubWin; } class="kw">inline class="type">int GetWidth(class="type">void) class="kw">const { class="kw">return m_Infos.Width; } class="kw">inline class="type">int GetHeight(class="type">void) class="kw">const { class="kw">return m_Infos.Height; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Resize(class="type">void) { m_Infos.Width = (class="type">int) ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (class="type">int) ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">class="kw">string ViewDouble(class="type">class="kw">double Value) { Value = NormalizeDouble(Value, class="num">8); class="kw">return DoubleToString(Value, ((Value - MathFloor(Value)) * class="num">100) > class="num">0 ? class="num">2 : class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Close(class="type">void) { ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin, class="kw">false); }
收尾的空壳与边界
这一段原文只留下注释框和函数闭合花括号,没有任何可执行逻辑,也没有给出回测数字或价格现象。 若你在 MT5 里贴这段代码,编译器会直接报 'unexpected end of program',因为它缺了函数头和实现体。 外汇与贵金属杠杆高,残缺脚本切勿挂实盘;把它当占位符,补完指标计算后再验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+