一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·进阶篇
🎛️

一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·进阶篇

(2/3)· 不懂 MQL5 也能拖出市价单与挂单界面,这篇只讲清楚对象坐标与背景层逻辑

含代码示例 第 2/3 篇
很多有交易想法的人被编程门槛挡在门外,其实 MT5 自带的对象系统就能拼出可交互面板。用 X/Y 坐标定位的标签、按钮、编辑框,才是搭建界面真正顺手的积木。先从干净的图表模板起步,能少踩一堆干扰对象的坑。

从模板文件批量还原 GUI 对象

这段逻辑干的事很直接:解析一个图表模板文件,把里面声明的控件按类型在 MT5 上重建出来。外汇与贵金属图表挂 EA 时高频用到这类自绘面板,属于高风险环境下的界面层操作,任何对象创建失败都应立刻中断并返回 false。 外层 for 先把 eRESULT 到 eEDIT_STOP 区间内的对象名清空,m_SubWindow 记录子窗口号,随后 while 循环逐行读文件,直到碰到的行是 </chart> 才停。 遇到 <object> 标记就进入对象解析分支,再读一行若等于 type,则根据 m_szValue 的数值分派:102 建 OBJ_LABEL、103 建 OBJ_BUTTON、106 建 OBJ_BITMAP_LABEL、107 建 OBJ_EDIT、110 建 OBJ_RECTANGLE_LABEL,任一个 LoopCreating 返回 false 就整体退出。 FileReadLine 自己管底层读取:用 SHORT_VALUE 按 UTF-16 读字,遇到 0x000D(回车)吞掉后续并结束本行;遇到 0x003D(等号)翻转 b0 标志,用来区分属性名与属性值。循环上限设为 500,意味着单行超 500 个双字节就可能截断,调模板时注意这一点。

MQL5 / C++
for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
m_SubWindow = nSub;
m_szLine = "";
class="kw">while (m_szLine != "</chart>")
{
  if (!FileReadLine()) class="kw">return false;
  if (m_szLine == "<object>")
  {
    if (!FileReadLine()) class="kw">return false;
    if (m_szLine == "type")
    {
      if (m_szValue == "class="num">102") if (!LoopCreating(OBJ_LABEL)) class="kw">return false;
      if (m_szValue == "class="num">103") if (!LoopCreating(OBJ_BUTTON)) class="kw">return false;
      if (m_szValue == "class="num">106") if (!LoopCreating(OBJ_BITMAP_LABEL)) class="kw">return false;
      if (m_szValue == "class="num">107") if (!LoopCreating(OBJ_EDIT)) class="kw">return false;
      if (m_szValue == "class="num">110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) class="kw">return false;
    }
  }
}
FileClose(m_fp);
class="kw">return true;
}
class="type">bool FileReadLine(class="type">void)
{
  class="type">int utf_16 = class="num">0;
  class="type">bool b0 = false;
  m_szLine = m_szValue = "";
  for (class="type">int c0 = class="num">0; c0 < class="num">500; c0++)
  {
    utf_16 = FileReadInteger(m_fp, SHORT_VALUE);
    if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); class="kw">return true; } else
    if (utf_16 == 0x003D) b0 = true; else

◍ 图表交互控件的对象枚举与内存布局

这段 MQL5 片段定义了一个名为 C_Chart_IDE 的图表交互类,用来在 MT5 自定义指标或 EA 面板上挂按钮、编辑框和勾选框。它把界面元素抽象成枚举 eObjectsIDE,包含结果标签、买卖按钮、日内交易勾选、取消键以及三个编辑框(杠杆、止盈、止损)。 宏 def_MaxObject 被设为 eEDIT_STOP + 32,意味着对象数组在最大枚举值之外还预留了 32 个槽位,实际可挂对象数依赖编译器对该枚举字面量的展开。m_ArrObject 是一个定长结构数组,每个元素记对象名与 X 坐标,这种静态数组比动态列表在回测和实时刷新时更不容易出现句柄漂移。 类外初始化的静态字符串数组 szMsgIDE 存放了 "MSG_RESULT"、"MSG_BUY_MARKET"、"MSG_SELL_MARKET" 等通信标识,EA 与面板之间靠这些常量做消息路由,避免硬编码字符串散落各处。 读文件那段逻辑里,utf_16 == 0x003E(即 '>')作为段落结束判定返回,说明解析器按 UTF-16 逐字符拼接到 m_szValue 或 m_szLine,遇到文件尾 FileIsEnding 就 break。外汇与贵金属品种上跑这类面板请记得:自定义控件不替你管理仓位风险,杠杆与止损参数填错可能瞬间放大回撤。

MQL5 / C++
if (b0) m_szValue = StringFormat("%s%c", m_szValue, (class="type">char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (class="type">char)utf_16);
if (FileIsEnding(m_fp)) class="kw">break;
}
class="kw">return (utf_16 == 0x003E);
}
class C_Chart_IDE
{
   class="kw">protected:
      enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_HeaderMSG "IDE_"
class="macro">#define def_MaxObject eEDIT_STOP + class="num">32
class=class="str">"cmt">//+------------------------------------------------------------------+
   class="kw">private :
      class="type">int      m_fp,
               m_SubWindow,
               m_CountObject;
      class="type">class="kw">string   m_szLine,
               m_szValue;
      class="type">bool     m_IsDayTrade;
      class="kw">struct st0
            {
               class="type">class="kw">string  szName;
               class="type">int     iPosX;
            }m_ArrObject[def_MaxObject];
class=class="str">"cmt">// ... The rest of the class code....
   class="kw">public  :
      class="kw">static class="kw">const class="type">class="kw">string szMsgIDE[];
class=class="str">"cmt">// ... The rest of the class code....
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="kw">const class="type">class="kw">string C_Chart_IDE::szMsgIDE[] = {
               "MSG_RESULT",
               "MSG_BUY_MARKET",
               "MSG_SELL_MARKET",

「从 XML 标签循环生成图表对象」

这段逻辑干的事很直接:读外部 XML 式的文本流,遇到 <object></object> 之间的字段,就批量在 MT5 图表上建对象。它先定义了一组消息 ID 字符串数组 szMsgIDE,包含日交易、平仓、杠杆、止盈、止损五种提示类型,用于后面匹配 name 节点。 LoopCreating() 里用两个宏 macro_SetInteger / macro_SetString 封装了 ObjectSetIntegerObjectSetString,调用时只需传属性枚举和值,少写重复代码。主循环 while (m_szLine != "</object>") 持续读行,读不到就返回 false,避免死循环卡脚本。 当行内容是 name 时,先把值转大写,用 for 循环在 eRESULTeEDIT_STOP 区间比对,命中就取对应下标,没命中就落到 m_CountObject 新增槽位。随后用 StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue) 拼出唯一对象名,先删后建保证不重名。 pos_xpos_y 的解析很朴素:直接 StringToInteger 转成整数距离,塞进 OBJPROP_YDISTANCE 等属性。外汇和贵金属图表挂这类对象属高风险环境下的辅助显示,数值偏移量需你在实盘图验证,可能因分辨率不同产生像素偏差。

MQL5 / C++
"MSG_DAY_TRADE",
"MSG_CLOSE_POSITION",
"MSG_LEVERAGE_VALUE",
"MSG_TAKE_VALUE",
"MSG_STOP_VALUE"
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool LoopCreating(ENUM_OBJECT type)
{
class="macro">#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
class="macro">#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
  class="type">int c0;
  class="type">bool b0;
  class="type">class="kw">string sz0 = m_szValue;
  class="kw">while (m_szLine != "</object>") if (!FileReadLine()) class="kw">return false; else
  {
    if (m_szLine == "name")
    {
      b0 = false;
      StringToUpper(m_szValue);
      for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
      c0 = (b0 ? c0 : m_CountObject);
      m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);
      ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName);
      ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, class="num">0, class="num">0);
    }
    if (m_szLine == "pos_x"      ) m_ArrObject[c0].iPosX = (class="type">int) StringToInteger(m_szValue);
    if (m_szLine == "pos_y"      ) macro_SetInteger(OBJPROP_YDISTANCE, StringToInteger(m_szValue));

用文本指令批量改面板对象属性

在 MT5 的 GUI 宏解析里,可以把外部配置文件里的键值对直接映射成图形对象的属性写入,不必在代码里硬编码每个按钮或标签的参数。下面这段解析逻辑覆盖了尺寸、偏移、颜色、位图与文字等常见字段,实盘里用来动态换肤或切换品种显示很顺手。 [CODE] if (m_szLine == "size_x") macro_SetInteger(OBJPROP_XSIZE, StringToInteger(m_szValue)); if (m_szLine == "size_y") macro_SetInteger(OBJPROP_YSIZE, StringToInteger(m_szValue)); if (m_szLine == "offset_x") macro_SetInteger(OBJPROP_XOFFSET, StringToInteger(m_szValue)); if (m_szLine == "offset_y") macro_SetInteger(OBJPROP_YOFFSET, StringToInteger(m_szValue)); if (m_szLine == "bgcolor") macro_SetInteger(OBJPROP_BGCOLOR, StringToInteger(m_szValue)); if (m_szLine == "color") macro_SetInteger(OBJPROP_COLOR, StringToInteger(m_szValue)); if (m_szLine == "bmpfile_on") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue); if (m_szLine == "bmpfile_off") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue); if (m_szLine == "fontsz") macro_SetInteger(OBJPROP_FONTSIZE, StringToInteger(m_szValue)); if (m_szLine == "fontnm") macro_SetString(OBJPROP_FONT, m_szValue); if (m_szLine == "descr") macro_SetString(OBJPROP_TEXT, m_szValue); if (m_szLine == "readonly") macro_SetInteger(OBJPROP_READONLY, StringToInteger(m_szValue) == 1); if (m_szLine == "state") macro_SetInteger(OBJPROP_STATE, StringToInteger(m_szValue) == 1); [/CODE] 逐行看:前四行把 size_x / size_y / offset_x / offset_y 转成整数后写入对象的宽高与坐标偏移;bgcolor 和 color 同理处理背景与前景色。bmpfile_on 与 bmpfile_off 调用 ObjectSetString 分别设状态 0 和 1 下的位图路径,适合做按钮亮灭图切换。 fontsz、fontnm、descr 管字号、字体和显示文本;readonly 与 state 把配置里的 1 转成布尔真,控制对象是否可编辑及选中态。外汇与贵金属图表上挂这类自定义面板,参数误写可能导致对象重叠或不可点,属高风险操作,建议先在模拟盘验证。 想省事就直接把这段粘进你的 EA 面板解析函数,外部改 txt 配置就能换布局,不用重编译。

MQL5 / C++
if (m_szLine == "size_x") macro_SetInteger(OBJPROP_XSIZE, StringToInteger(m_szValue));
if (m_szLine == "size_y") macro_SetInteger(OBJPROP_YSIZE, StringToInteger(m_szValue));
if (m_szLine == "offset_x") macro_SetInteger(OBJPROP_XOFFSET, StringToInteger(m_szValue));
if (m_szLine == "offset_y") macro_SetInteger(OBJPROP_YOFFSET, StringToInteger(m_szValue));
if (m_szLine == "bgcolor") macro_SetInteger(OBJPROP_BGCOLOR, StringToInteger(m_szValue));
if (m_szLine == "class="type">color") macro_SetInteger(OBJPROP_COLOR, StringToInteger(m_szValue));
if (m_szLine == "bmpfile_on") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, class="num">0, m_szValue);
if (m_szLine == "bmpfile_off") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, class="num">1, m_szValue);
if (m_szLine == "fontsz") macro_SetInteger(OBJPROP_FONTSIZE, StringToInteger(m_szValue));
if (m_szLine == "fontnm") macro_SetString(OBJPROP_FONT, m_szValue);
if (m_szLine == "descr") macro_SetString(OBJPROP_TEXT, m_szValue);
if (m_szLine == "readonly") macro_SetInteger(OBJPROP_READONLY, StringToInteger(m_szValue) == class="num">1);
if (m_szLine == "state") macro_SetInteger(OBJPROP_STATE, StringToInteger(m_szValue) == class="num">1);
让小布替你跑这套
这些对象坐标与模板配置思路,小布盯盘已做成可视化的品种页辅助视图,打开对应外汇或贵金属品种即可直接套用,不必自己从头排坐标。

常见问题

文本和位图按日期和价格坐标定位,在屏幕缩放或品种切换时会漂移,而标签、按钮等用 X/Y 像素坐标能稳定贴在固定位置,更适合做交互界面。
小布盯盘内置了基于同类对象逻辑的辅助视图,可快速在品种页调出面板原型,你只需按策略改参数,不必手动逐个放对象。
标签、按钮、图形、位图标签、编辑、矩形标签这六类已能覆盖背景、输入和触发区域,外汇贵金属高风险,界面越简洁越不易误触。
用 X/Y 坐标的对象不依赖周期数据,切换周期通常保留;但建议单独存 IDE 模板,避免和常规看图模板混用导致对象重叠。