创建一个行情卷播面板:基本版·综合运用
📊

创建一个行情卷播面板:基本版·综合运用

(3/3)· 从资产文件读到面板绘制,把前两步的基础拼成能跑的基本版

含代码示例偏理论 第 3/3 篇
不少人想做实时报价墙,却随手用脚本堆在图表上,一切换周期就全没了。把面板塞进 EA 也别扭,它本该只管下单。用指标承载卷播逻辑,才是少踩坑的起点。

「用 Edit 对象把多品种报价钉在图表上」

想在 MT5 图表上同时盯好几个品种,又不肯切窗口,可以用 C_Object_Edit 在画布上动态建文本框。下面这段逻辑先按配置逐个建对象:品种名框宽占 def_MaxWidth-1、高 22,价格框默认 0.0、宽 32,品种名框宽 10。 新建时不走恢复模式就会给 m_Infos.Symbols 扩容并写入代码,ArrayResize 的预留步长是 10,意味着每加一个品种最多多申请 10 个槽位,避免频繁重分配。nSymbols 自增后才算登记完成。 UpdateSymbolInfo 负责刷价:从 M1 取最近 1 根 K 线,CopyRates 只拉 1 根,把 close 写进价格框并染色(m_Infos.CorPrice),两个框的 X 轴都对到传入的 x 坐标,所以多品种会纵向对齐成一列。 配置从 Widget\ 目录下的 CSV 读,FILE_ANSI 编码;FileOpen 返回 INVALID_HANDLE 就直接 PrintFormat 报错并返 false,因此文件缺失时面板不会半残着跑。外汇与贵金属波动剧烈,这类浮动报价面板只作辅助观察,信号误读可能导致较大回撤。

MQL5 / C++
edit.Size(A, def_MaxWidth - class="num">1, class="num">22);
}

C_Object_Edit edit;
macro_Create(macro_ObjectName(def_CharSymbol, szArg), szArg, class="num">10);
macro_Create(macro_ObjectName(def_CharPrice, szArg), class="num">0.0, class="num">32);
if (!bRestore)
{
   ArrayResize(m_Infos.Symbols, m_Infos.nSymbols + class="num">1, class="num">10);
   m_Infos.Symbols[m_Infos.nSymbols].szCode = szArg;
   m_Infos.nSymbols++;
}
class="macro">#undef macro_Create
}
class="kw">inline class="type">void UpdateSymbolInfo(class="kw">const class="type">int x, class="kw">const class="type">class="kw">string szArg)
{
   C_Object_Edit edit;
   class="type">class="kw">string sz0 = macro_ObjectName(def_CharPrice, szArg);
   class="type">MqlRates Rate[class="num">1];
   CopyRates(szArg, PERIOD_M1, class="num">0, class="num">1, Rate);
   edit.PositionAxleX(macro_ObjectName(def_CharSymbol, szArg), x);
   edit.SetTextValue(sz0, Rate[class="num">0].close, m_Infos.CorPrice);
   edit.PositionAxleX(sz0, x);
}
class="type">bool LoadConfig(class="kw">const class="type">class="kw">string szFileConfig)
{
   class="type">int file;
   class="type">class="kw">string sz0;
   class="type">bool ret;
   if ((file = FileOpen("Widget\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
   {
      PrintFormat("Configuration file %s not found.", szFileConfig);
      class="kw">return class="kw">false;

配置加载与对象清理的底层写法

这段 C_Widget 析构与初始化代码,暴露了 MT5 面板类在生命周期上的两个关键动作:退出时回收图形资源、启动时读配置并建背景。 析构函数里 Terminal.Close() 先断图表终端对象,再用 ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName) 按前缀批量删对象,最后 ArrayFree(m_Infos.Symbols) 释放符号数组。若你做多品种看板,不写这一句,切换周期时可能残留几十个旧标签。 Initialize 接收配置文件路径与三种颜色参数,用 IndicatorSetString(INDICATOR_SHORTNAME, szNameShort) 设短名,Terminal.Init(ChartWindowFind()) 绑定子窗口,最后 return LoadConfig(szFileConfig) 把解析交给上文那段循环读取逻辑。 LoadConfig 中 ArrayResize(m_Infos.Symbols, 30, 30) 预留 30 个槽位,说明设计上限约 30 个品种;若配置文件行数超此数,需改这个常量,否则尾部品种会被静默丢弃。

MQL5 / C++
}
   m_Infos.nSymbols = class="num">0;
   ArrayResize(m_Infos.Symbols, class="num">30, class="num">30);
   for (class="type">int c0 = class="num">1; (!FileIsEnding(file)) && (!_StopFlag); c0++)
   {
      if ((sz0 = FileReadString(file)) == "") class="kw">continue;
      if (SymbolExist(sz0, ret)) AddSymbolInfo(sz0); else
      {
         FileClose(file);
         PrintFormat("Ativo na linha %d não foi reconhecido.", c0);
         class="kw">return class="kw">false;
      }
   }
   FileClose(file);
   m_Infos.MaxPositionX = macro_MaxPosition;

   class="kw">return !_StopFlag;
}
~C_Widget()
{
   Terminal.Close();
   ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
   ArrayFree(m_Infos.Symbols);
}
class="type">bool Initilize(class="kw">const class="type">class="kw">string szFileConfig, class="kw">const class="type">class="kw">string szNameShort, class="type">color corText, class="type">color corPrice, class="type">color corBack)
{
   IndicatorSetString(INDICATOR_SHORTNAME, szNameShort);
   Terminal.Init(ChartWindowFind());
   Terminal.Resize();
   m_Infos.CorBackGround = corBack;
   m_Infos.CorPrice = corPrice;
   m_Infos.CorSymbol = corText;
   CreateBackGround();
   class="kw">return LoadConfig(szFileConfig);
}
class="type">void DispatchMessage(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="kw">static class="type">int tx = class="num">0;
   class="type">class="kw">string szRet[];

   class="kw">switch (id)
   {
      case (CHARTEVENT_CUSTOM + Ev_RollingTo):

◍ 面板拖拽与图表事件的分发逻辑

这段处理的是自定义监控面板在 MT5 图表上的交互响应。核心思路是把鼠标拖动、窗口缩放、对象删除三类事件分开处理,避免面板错位或残留。 tx 先叠加 lparam 的位移量,再用三目运算夹在 -def_MaxWidth 与 m_Infos.MaxPositionX 之间,实现左右越界回卷。随后按 m_Infos.nSymbols 个品种逐个右移 def_MaxWidth 像素调用 UpdateSymbolInfo,只刷新落在 Terminal.GetWidth() 内的部分,超出 MaxPositionX 就从左侧 -def_MaxWidth 续接。 CHARTEVENT_CHART_CHANGE 里只做两件事:Terminal.Resize() 重算可用区,把 m_Infos.MaxPositionX 重置为 macro_MaxPosition,再 ChartRedraw。窗口大小变了不重算坐标,面板就会永久偏出可视区。 CHARTEVENT_OBJECT_DELETE 要分辨删的是背景还是单个品种标签。前缀匹配 def_PrefixName 且按 '#' 拆出两段时,走 AddSymbolInfo 补回该品种;若删的是 def_NameObjBackGround,则清掉全部前缀对象并重建背景,再循环把所有已记录品种重新挂上。外汇与贵金属行情高波动,这类面板仅作辅助盯盘,信号滞后可能导致滑点风险。

MQL5 / C++
tx = (class="type">int) (tx + lparam);
tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
for (class="type">int c0 = class="num">0, px = tx; (c0 < m_Infos.nSymbols); c0++)
{
   if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
   px += def_MaxWidth;
   px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
}
ChartRedraw();
break;
case CHARTEVENT_CHART_CHANGE:
   Terminal.Resize();
   m_Infos.MaxPositionX = macro_MaxPosition;
   ChartRedraw();
   break;
case CHARTEVENT_OBJECT_DELETE:
   if (StringSubstr(sparam, class="num">0, StringLen(def_PrefixName)) == def_PrefixName) if (StringSplit(sparam, &class="macro">#x27;#&class="macro">#x27;, szRet) == class="num">2)
   {
      AddSymbolInfo(szRet[class="num">1], true);
      ChartRedraw();
   }else if (sparam == def_NameObjBackGround)
   {
      ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
      CreateBackGround();
      for (class="type">int c0 = class="num">0; c0 < m_Infos.nSymbols; c0++) AddSymbolInfo(m_Infos.Symbols[c0].szCode, true);

「报价面板指标的定时器与绘制骨架」

这段代码片段展示了一个独立窗口指标的核心声明与事件驱动骨架:它不画常规曲线(indicator_plots 设为 0),而是用 45 像素高的子窗口承载报价面板。 关键参数里 user01 默认 -1 控制水平偏移,user02 默认 60 即定时器间隔毫秒数;OnInit 里 EventSetMillisecondTimer(user02) 决定了面板刷新节奏,调小该值可能让报价跟手但吃掉更多 CPU。 OnCalculate 直接返回 rates_total 不做计算,真正推图靠 OnTimer 发自定义事件 EventChartCustom,再由 Widget 对象接手重绘。外汇与贵金属报价跳动快,这种异步刷新结构在高波动时段可能比同步计算更稳。

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="macro">#class="kw">property indicator_height class="num">45
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Widget\Rolling Price\C_Widget.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">class="kw">string    user00 = "Config.cfg";   class=class="str">"cmt">//Configuration file
class="kw">input class="type">int       user01 = -class="num">1;             class=class="str">"cmt">//Shift
class="kw">input class="type">int       user02 = class="num">60;             class=class="str">"cmt">//Pause in milliseconds
class="kw">input class="type">color     user03 = clrWhiteSmoke; class=class="str">"cmt">//Asset class="type">color
class="kw">input class="type">color     user04 = clrYellow;     class=class="str">"cmt">//Price class="type">color
class="kw">input class="type">color     user05 = clrBlack;      class=class="str">"cmt">//Background class="type">color
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Widget Widget;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   if (!Widget.Initilize(user00, "Widget Price", user03, user04, user05))
      class="kw">return INIT_FAILED;
   EventSetMillisecondTimer(user02);
   
   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()
{
   EventChartCustom(Terminal.Get_ID(), C_Widget::Ev_RollingTo, user01, class="num">0.0, "");
}

图表事件如何驱动面板横移

在 MT5 自定义面板里,鼠标拖拽或按钮触发的横向滚动,本质是靠 CHARTEVENT_CUSTOM 自定义事件把位移量送进 DispatchMessage。下面这段处理函数就是滚动逻辑的核心,改一处边界常量就能调出不同的回弹手感。 事件入口先由 OnChartEvent 把 id、lparam 等参数原样转交给 Widget.DispatchMessage,后者在收到 Ev_RollingTo 事件时,用静态变量 tx 累加 lparam 作为新的起始 X 坐标。 tx 被限制在 -def_MaxWidth 到 m_Infos.MaxPositionX 之间:小于左边界就跳到右端,大于右端就绕回左端,形成循环滚动。循环宽度 def_MaxWidth 若设成 120,而 m_Infos.MaxPositionX 为 800,则可视滚动区约 920 像素。 随后按 m_Infos.nSymbols 个品种依次右移 px 并调用 UpdateSymbolInfo 重绘,只要 px 还没超出 Terminal.GetWidth() 就更新对应报价块;越界部分折返到左侧继续排,最后 ChartRedraw 一次性刷新。外汇与贵金属报价面板的高频重绘会带来 CPU 占用,实盘前建议在策略测试器里跑一遍观察帧率。

MQL5 / C++
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)
{
      Widget.DispatchMessage(id, lparam, dparam, sparam);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
      EventKillTimer();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DispatchMessage(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="kw">static class="type">int tx = class="num">0;
      class="type">class="kw">string szRet[];
      class="kw">switch (id)
      {
            case (CHARTEVENT_CUSTOM + Ev_RollingTo):
                  tx = (class="type">int) (tx + lparam);
                  tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
                  for (class="type">int c0 = class="num">0, px = tx; (c0 < m_Infos.nSymbols); c0++)
                  {
                        if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
                        px += def_MaxWidth;
                        px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
                  }
                  ChartRedraw();
                  break;

◍ 自定义品种文件就能限定交易标的

行情卷播面板的基础版靠一个外部文件决定展示哪些资产,文件名随意,放在指定目录即可。文件里每行写一个品种名,必须和 MT5 市场观察窗口里的命名完全一致,改文件就行,不用动代码。 有个硬限制得记牢:同一个 MT5 实例里不能既看外汇货币对又看股票,这两类市场是隔离的,混开必然冲突。外汇和贵金属本身杠杆高、波动猛,真拿这套去跑实盘前,先想清楚自己是不是在赌隔离规则之外的意外。 附件里的示例文件是以 IBOV 指数成分股为模板的,想只看 EURUSD 或 XAUUSD,照着格式自建一个纯货币对清单丢进去,面板就只滚这些。下一步作者打算加资产图标,识别速度会更快,但当前这版先吃透文件机制最实在。

让小布替你盯多品种跳动
这些多品种报价诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你只需决定哪些资产进观察池。

常见问题

脚本在图表周期切换时会被终止,交易者得反复手动运行;指标随图表常驻,能持续刷新报价,更适合做常显面板。
小布盯盘内置了多品种行情监测模块,虽不是同一套 MQL5 代码,但观察逻辑一致,可省去自行编译指标的步骤。
外部文件更灵活,能随时调整观察组合而不重编译;固定数组仅适合极简场景,扩展时改动成本高。
可在原结构上加下单按钮、价差提醒或成交量标色,前篇规划的资源隔离思路能让这些扩展不互相拖累。