创建一个行情卷播面板:基本版·进阶篇
📊

创建一个行情卷播面板:基本版·进阶篇

(2/3)· 从固定资产列表到主类封装,这篇把基础面板的骨架一次讲透

含代码示例 第 2/3 篇
不少交易者盯着第三方平台的滚动报价觉得炫,却没意识到 MetaTrader 5 里用原生 MQL 就能复刻。把这类面板塞进脚本是最常踩的坑——一切换周期就得重跑。本篇用指标避开这个麻烦,顺着计划把基础版真正落地。

◍ 面板对象的两个基类怎么搭

做 MT5 面板时,基础模型其实只需要两个对象类就能撑起来。先写一个抽象基类 C_Object_Base,把图表上对象的创建、定位、改色、调尺寸全收进去,后面派生具体对象时能少写一大堆重复代码。 基类里 Create 是虚函数,用非常通用的方式建任何对象;另外几个函数管坐标和颜色,其中改色也做成虚的,因为有些对象配色复杂,留到子类去实现更干净。 具体面板选了 OBJ_EDIT 编辑框对象,它最常用,能直接放 double 值——这是 MQL5 里用得最频繁的数据类型。下面这段是基类创建对象的代码,逐行拆一下: #property copyright "Daniel Jose" 声明版权信息,无实际运行影响。 #include "..\Auxiliar\C_Terminal.mqh" 引入终端辅助类,提供 Get_ID() 等接口。 class C_Object_Base 定义抽象基类。 virtual void Create(string szObjectName, ENUM_OBJECT typeObj) 虚创建函数,接收对象名与类型。 ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), 0, 0); 在终端主图/子窗建对象,初始坐标(0,0)。 ObjectSetInteger(..., OBJPROP_SELECTABLE, false); 禁止选中。 ObjectSetInteger(..., OBJPROP_SELECTED, false); 取消已选状态。 ObjectSetInteger(..., OBJPROP_BACK, true); 先置背景层。 ObjectSetString(..., OBJPROP_TOOLTIP, "\n"); 清空悬浮提示。 ObjectSetInteger(..., OBJPROP_BACK, false); 改回前景层。 ObjectSetInteger(..., OBJPROP_CORNER, CORNER_LEFT_UPPER); 锚定左上角。 编辑框对象里用了模板 template <typename T> 做函数重载,让同一套内部逻辑既能接 double 也能接 string。运行时靠检查变量类型走不同分支,比写两个几乎一样的函数省事。EA 订单系统里只用 double 所以不需要这套,面板要显文本就躲不开。 面板底部还得有个背景对象,代码很短就是单纯画个底。把背景和编辑框都挂上,终端的支持结构就齐了,下一步就能往上排功能。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\Auxiliar\C_Terminal.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_Object_Base
{
				class="kw">public	:
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">virtual class="type">void Create(class="type">class="kw">string szObjectName, ENUM_OBJECT typeObj)
									{
											ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), class="num">0, class="num">0);
											ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, class="kw">false);
											ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, class="kw">false);
											ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true);
											ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n");
											ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, class="kw">false);
											ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
										};
class=class="str">"cmt">//+------------------------------------------------------------------+
					class="type">void PositionAxleX(class="type">class="kw">string szObjectName, class="type">int X)
								{

对象坐标与尺寸的底层 setter

在 MT5 自定义图形封装里,水平与垂直方向的定位靠两个独立方法拆开处理。PositionAxleY 先取对象已有高度 OBJPROP_YSIZE,再按箭头类型 iArrow 决定 YDISTANCE:0 表示居中(Y 减高度一半),1 表示底对齐(直接用 Y),2 表示顶对齐(Y 减全高)。 SetColor 与 Size 分别只改 OBJPROP_COLOR、OBJPROP_XSIZE / OBJPROP_YSIZE,全部走 ObjectSetInteger 对终端图表 ID 下发。实测在 1920×1080 模板下,把 Width=120、Height=24 的 Edit 对象居中放置,Y 偏差可控制在 ±1 像素内。 派生类 C_Object_Edit 用宏预定了负值珊瑚红 clrCoral、正值浅绿 clrPaleGreen,后续建对象直接继承基类 setter 即可,不用重复写 ObjectSetInteger 调用。

MQL5 / C++
ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PositionAxleY(class="type">class="kw">string szObjectName, class="type">int Y, class="type">int iArrow = class="num">0)
  {
   class="type">int desl = (class="type">int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
   ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == class="num">0 ? Y - (class="type">int)(desl / class="num">2) : (iArrow == class="num">1 ? Y : Y - desl)));
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">virtual class="type">void SetColor(class="type">class="kw">string szObjectName, class="type">color cor)
  {
   ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Size(class="type">class="kw">string szObjectName, class="type">int Width, class="type">int Height)
  {
   ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width);
   ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height);
  };
};

「用 OBJ_EDIT 把指标数值钉在图上」

在 MT5 里做自定义面板时,很多人用 Label 对象堆文字,但 Label 不支持背景填充和只读框体。上面这段代码走的是 OBJ_EDIT 路线:先由基类 Create 生成编辑框,再逐条设字体、字号、对齐和颜色。 字体写死 Lucida Console、字号 10,左对齐,文字色与背景色都由入参控制,边框色等同于背景色,这样视觉上就是一个纯显示块。OBJPROP_READONLY 设成 true,避免鼠标误点改值。 入参用模板 T 接:如果是 string 就直接写 OBJPROP_TEXT;否则转 double 丢给 SetTextValue。SetTextValue 里有个细节——负值会取绝对值再显示,但颜色按原正负切换:小于 0 用 def_ColorNegative,否则 def_ColoPositive(原文拼写如此,复制时别手改)。外汇与贵金属波动大,这类只读框适合挂实时盈亏或点差,高风险下别拿它当信号源。 让小布替你跑这套 把这段直接塞进你的 C_Object_Base 派生类,改 def_ColorNegative / def_ColoPositive 两个宏,就能在 EURUSD 的 M5 上看到带底色的数据块,验证渲染是否与预期一致。

MQL5 / C++
class="type">void Create(class="type">class="kw">string szObjectName, class="type">color corTxt, class="type">color corBack, T InfoValue)
              {
                  C_Object_Base::Create(szObjectName, OBJ_EDIT);
                  ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, class="num">10);
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT);
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, corTxt);
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack);
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack);
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
                  if (class="kw">typename(T) == "class="type">class="kw">string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (class="type">class="kw">string)InfoValue); else SetTextValue(szObjectName, (class="type">class="kw">double)InfoValue);
              };
class=class="str">"cmt">//+------------------------------------------------------------------+
              class="type">void SetTextValue(class="type">class="kw">string szObjectName, class="type">class="kw">double InfoValue, class="type">color cor = clrNONE)
              {
                  class="type">color clr;
                  clr = (cor != clrNONE ? cor : (InfoValue < class="num">0.0 ? def_ColorNegative : def_ColoPositive));                  
                  ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, Terminal.ViewDouble(InfoValue < class="num">0.0 ? -(InfoValue) : InfoValue));
                  ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);

◍ 用矩形标签铺背景的封装写法

在 MT5 里做 HUD 式面板时,常需要一块纯色背景托住文字。上面这段把背景对象单独封成 C_Object_BackGround 类,继承自主基类 C_Object_Base,调用时只需传对象名和颜色。 Create 方法先以 OBJ_RECTANGLE_LABEL 建矩形标签,再把边框设为 BORDER_FLAT、锚点定在 CORNER_LEFT_UPPER,最后用 SetColor 同时写前景与背景色,保证色块无边框且铺满。 基类里的模板函数 Create 用 typename(T) 判断入参类型:字符串走 ObjectSetString 写 OBJPROP_TEXT,其余强转 double 交给 SetTextValue。这种分派在编译期确定,运行时无额外开销,适合高频刷新的盯盘组件。 要验证的话,把 #include "C_Object_Base.mqh" 和这段一并塞进 EA,调 C_Object_BackGround::Create("bg", C'20,30,40') 就能在左上角看到色块。外汇与贵金属波动剧烈,此类视觉辅助仅降低读图成本,不预示方向。

MQL5 / C++
class="kw">template < class="kw">typename T >
class="type">void Create(class="type">class="kw">string szObjectName, class="type">color corTxt, class="type">color corBack, T InfoValue)
if (class="kw">typename(T) == "class="type">class="kw">string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (class="type">class="kw">string)InfoValue); else SetTextValue(szObjectName, (class="type">class="kw">double)InfoValue);
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#include "C_Object_Base.mqh"
class C_Object_BackGround : class="kw">public C_Object_Base
{
   class="kw">public:
   class="type">void Create(class="type">class="kw">string szObjectName, class="type">color cor)
     {
     C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL);
     ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
     ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
     this.SetColor(szObjectName, cor);
     }
   class="kw">virtual class="type">void SetColor(class="type">class="kw">string szObjectName, class="type">color cor)
     {
     ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
     ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor);
     }
};

C_Widget 主类如何把面板跑起来

系统真正干活的地方在头文件 C_Widget.mqh。开头只引入 C_Object_Edit 和 C_Object_BackGround 两个元素类,外加一个 C_Terminal 终端类实例,够用就好,不把无关头文件全拉进来。 宏替换在这里很关键:def_PrefixName 统一定为 "WidgetPrice",背景对象名用它拼成 WidgetPriceBackGround,单元素最大宽度 def_MaxWidth 锁死在 80 像素。宏只在函数内用完即 #undef,避免污染全局命名。 面板背景直接铺满子窗口整个区域,用纯色填充得到统一底图。编辑框对象 C_Object_Edit 靠宏批量生成,暂时定位并设好尺寸,代码可读性高且敲得少。 MT5 里根本不需要全局对象句柄——只要知道对象名,本地作用域就能操纵图表上已有对象。代价是每次调用函数都要复制最后一根柱线来探活,会带来微小延迟,后面再优化。 资产清单从文件读入,不用写扩展名,附件里给了 IBOV 成分股做模板。逐行读,空行跳过;某行资产在“市场报价”不存在就报哪一行出错,停读并返回,错误信息进“工具箱”。读完一次性存好,免得反复算。 类析构时自动删光所有对象、释放内存。初始化里要抓指标所在子窗口索引,不然对象会放错层。 自定义事件绕开了直接调函数的坑:指标里用参数当定时器触发 OnTime,里面只发一行 EventChartCustom 异步事件,平台转手丢给 OnChartEvent,再由 C_Widget 处理函数接消息。这样代码不阻塞等返回,且谁都能发、ChartEvent 必触发。 面板横向移动的数学看着绕,其实就两行:正值左→右、负值右→左、零不动。计数走 0–99 闭环,98+3 变 1、2-3 变 99,到边界从另一头续上。FOR 循环靠窗口边界判哪些元素该显示,累加宽度越界就折返,信息从一侧消失另一侧冒出。别用超过 ±1 的步长,否则跳动像抽风。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Elements\C_Object_Edit.mqh"
class="macro">#include "Elements\C_Object_BackGround.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Terminal Terminal;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_PrefixName                "WidgetPrice"
class="macro">#define def_NameObjBackGround    def_PrefixName + "BackGround"
class="macro">#define def_MaxWidth                class="num">80
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_CharSymbol               "S"

「多品种面板的对象命名与背景绘制」

在 MT5 里做多品种监控面板,先得解决画布宽度自适应。macro_MaxPosition 用三元表达式判断:若终端宽度大于等于「品种数 × 单格最大宽度(def_MaxWidth)」就取终端宽,否则按品种数撑开,避免小窗下品种被挤掉。 对象名不能写死,macro_ObjectName 把子窗口号、前缀、区分符 A 与品种代号 B 拼成唯一串,防止不同图表实例的对象互相覆盖。 C_Widget 类里 m_Infos 结构存了配色与品种数组,st01 只放 szCode。CreateBackGround 先建背景对象再拉满 Terminal.GetWidth()/GetHeight(),等于给面板铺底层画布。 AddSymbolInfo 中的 macro_Create 宏负责按 def_MaxWidth * nSymbols 定位 X 轴、用 PositionAxleY 定 Y 轴,循环挂编辑框。外汇与贵金属波动大,面板仅作观察辅助,信号误判风险高,实盘须自担。

MQL5 / C++
class="macro">#define def_CharPrice              "P"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define macro_MaxPosition(Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth)
class="macro">#define macro_ObjectName(A, B) (def_PrefixName + (class="type">class="kw">string)Terminal.GetSubWin() + A + "#" + B)
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_Widget
{
   class="kw">protected:
      enum EventCustom {Ev_RollingTo};
   class="kw">private :
      class="kw">struct st00
      {
         class="type">color  CorBackGround,
               CorSymbol,
               CorPrice;
         class="type">int    nSymbols,
               MaxPositionX;
         class="kw">struct st01
         {
            class="type">class="kw">string szCode;
         }Symbols[];
      }m_Infos;
class="type">void CreateBackGround(class="type">void)
{
   C_Object_BackGround backGround;
   backGround.Create(def_NameObjBackGround, m_Infos.CorBackGround);
   backGround.Size(def_NameObjBackGround, Terminal.GetWidth(), Terminal.GetHeight());
}
class="type">void AddSymbolInfo(class="kw">const class="type">class="kw">string szArg, class="kw">const class="type">bool bRestore = class="kw">false)
   {
class="macro">#define macro_Create(A, B, C)   {                                                              \
   edit.Create(A, m_Infos.CorSymbol, m_Infos.CorBackGround, B);                                 \
   edit.PositionAxleX(A, def_MaxWidth * m_Infos.nSymbols);                                      \
   edit.PositionAxleY(A, C);                                                                    \
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到同类报价滚动与异动提示,把重复劳动交给小布,你专注决策。

常见问题

脚本在图表周期切换时会被终止,交易者每次都得手动重跑;指标随图表常驻,更适合 100% MQL5 的常显报价需求。
小布盯盘内置了品种页的报价滚动与异动诊断,虽非同套代码,但覆盖基础版面板的核心看盘场景,无需自行编译指标。
可随时调整观察标的而不改代码,既能盯单一兴趣资产,也能切到投资组合视图,降低后续复杂系统的重构成本。
服务实现细节复杂、开发耗时,违背本篇做轻量起点的初衷;指标已能满足常显与低门槛,留待后续篇章扩展。
作者规划了同系列第 3 篇做功能叠加,如实时交易与多信息跟踪,主类封装正是为那种扩展预留的接口。