创建一个行情卷播面板:基本版(基础篇)
📘

创建一个行情卷播面板:基本版(基础篇)

第 1/3 篇

◍ 用 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、宽高、子窗口号,用来捕获所用窗口的尺寸;公开部分留作访问接口。引入这层抽象不是强制的,但能把终端相关细节封在一个类里,避免指标删除对象时乱发事件。 外汇与贵金属行情跳变频繁,这类依赖图表事件的面板在实盘里可能因事件堆积而卡顿,先用模拟盘验证再上真仓。后面逻辑会变重,这一节先把容器和通知机制认熟就行。

MQL5 / C++
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,注销事件监听即可。外汇与贵金属波动剧烈、杠杆风险高,面板只是辅助观测,信号不代表方向确定性。

MQL5 / C++
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',因为它缺了函数头和实现体。 外汇与贵金属杠杆高,残缺脚本切勿挂实盘;把它当占位符,补完指标计算后再验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+                
};
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

先确定用指标承载还是自动交易程序承载,指标更适合纯展示;搭好基础骨架后逐步加滚动逻辑即可。
纯看行情用指标更轻量不干扰交易;若需自动下单交互才用EA,基础展示选指标省事。
小布可基于你的品种清单生成面板代码框架并标好生命周期同步点,你复制进终端调尺寸就能跑。
在创建时绑定父窗口坐标系并监听大小变化事件,每次重绘同步宽高即可避免错位。
正常,基础篇只搭生命周期与骨架;滚动内容与数据填充留到后续篇章逐步接上。