在一张图表上的多个指标(第 03 部分):为用户开发定义(基础篇)
📘

在一张图表上的多个指标(第 03 部分):为用户开发定义(基础篇)

第 1/2 篇

多指标同图的需求从哪来

在 MT5 实盘里,交易者常要把几个指标叠在同一张图表上对照,而不是来回切窗口。官方示例库里有一篇 2022 年 5 月 6 日发布的图文,浏览量到了 1765,说明这类需求不是小众癖好。 原文作者 Daniel Jose 把它归为「为用户开发定义」的一类:先想清楚使用者要在图上同时看什么,再决定指标怎么挂、怎么互不挡视线。 外汇和贵金属波动快、杠杆高,同图堆太多线反而容易误判,建议先只叠两个相关性强的指标做验证。

◍ 从子窗口多指标到可扩展系统

上一篇文章里给出的基础代码,只是在图表子窗口里堆叠多个指标的最简骨架,本质上只是个更大系统的入口。 在这个骨架上还能做很多分支扩展,比如把不同周期的信号汇总到同一个子窗口,或者让指标之间互相读取缓冲区。但别一口气吃成胖子——这一系列文章的目的之一是带你逐步学会自己写代码,等你理解了底层机制,就能按自己的交易逻辑改系统。 本文在这一篇里做的是功能外延:如果你已经用惯了子窗口多指标、又嫌它能干的事太少,接下来的内容值得你打开 MT5 把原工程调出来对照着改。外汇和贵金属波动剧烈,这类自定义系统仅用于辅助判断,信号失效的概率不低。

「给指标对象类留好扩展口子」

做新系统最忌讳的是上来就写死结构。正确做法是先起项目、留好未来改动的余地,尤其新手要持续规划扩展点。核心循环逻辑没动,但对象类私有变量明显变多——这不是冗余,而是为了把资产名、对象名、宽度等相关数据用结构打包,访问更快。 新结构体把 symbol、objName、width 绑在一起,数组维度是 def_MaxTemplates。这样后面要加固定周期、固定像素宽度的功能,直接往结构里塞字段即可,不用满代码改散变量。 语法上引入分隔符来控制行为:< > 内写周期如 < 15m > 会把指标锁在 15 分钟数据上,主图切周期它不跟;[ ] 内写像素如 [ 350 ] 固定指标子窗口宽度。指标名必须放最前,其余组合随意。外汇和贵金属波动剧烈、杠杆高风险大,这种固定周期显示便于同屏对比多周期,但实战要先在 MT5 demo 验证。 宏 macro_SetInteger 这类写法把重复代码压缩成一行声明,编译器展开时替换 m_Info[m_Counter].szObjName,体现能复用就别重写。模板搜索逻辑也值得记:ChartApplyTemplate 不写目录时,MT5 先找指标 exe 同级的 MYTEMPLATES 子目录,没有再去 MQL5\Profiles\Templates\MyTemplates;若代码细节调整,搜索顺序会反过来。知道文件落点,你就能自己摆模板不发懵。

MQL5 / C++
class="kw">struct st
{
    class="type">class="kw">string  szObjName,
            szSymbol;
    class="type">int     width;
}m_Info[def_MaxTemplates];
class="type">int         m_IdSubWin,
            m_Counter,
            m_CPre,
            m_Aggregate;
class="type">long        m_Id,
            m_handle;
ENUM_TIMEFRAMES m_Period;
class="type">void SetBase(class="kw">const class="type">class="kw">string szSymbol, class="type">int iScale, class="type">int iSize)
{
class="macro">#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
    if (m_IdSubWin < class="num">0)
    {
        m_Id = ChartID();
        m_IdSubWin = (class="type">int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - class="num">1;
        m_Aggregate = class="num">0;
    }

用随机后缀避开图表对象命名冲突

在 MT5 里批量挂 OBJ_CHART 对象时,最容易被忽略的坑是对象名撞车:同名对象第二次 Create 会直接失败。上面这段把 __FILE__ 拼上 MathRand() 和 ObjectsTotal(-1, OBJ_CHART),等于给每个子图对象一个极低重复概率的身份证。 ObjectCreate 之后立刻用 ObjectSetString 绑定交易品种,再用 ObjectGetInteger 取回 OBJPROP_CHART_ID 句柄——这套顺序不能反,先建对象再读属性是 MT5 图形对象的硬规矩。 Decode 函数里 def_ScaleDefault 写死成 4,意味着未指定比例时默认 4 倍缩放;macro_GetData 用状态机方式从字符串切参数,c0 跳过空格、i0 记长度,逻辑紧凑但容易在边界字符上漏判。 想验证就开 MT5 把这段塞进 EA 的 OnInit,连续调十次 Create,观察 ObjectsTotal 是否每次 +1,以及专家日志里对象名是否真带随机尾数。外汇与贵金属图表上跑这类脚本属高风险操作,对象堆积多了会拖慢终端响应。

MQL5 / C++
m_Info[m_Counter].szObjName = __FILE__ + (class="type">class="kw">string) MathRand() + (class="type">class="kw">string) ObjectsTotal(m_Id, -class="num">1, OBJ_CHART);
ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, class="num">0, class="num">0);
ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));
class=class="str">"cmt">// ....
macro_SetInteger(OBJPROP_PERIOD, m_Period);
m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
m_Aggregate += iSize;
m_Info[m_Counter].width = iSize;
m_CPre += (iSize > class="num">0 ? class="num">1 : class="num">0);
m_Counter++;
class="macro">#undef macro_SetInteger
};
class="type">void Decode(class="type">class="kw">string &szArg, class="type">int &iScale, class="type">int &iSize)
{
class="macro">#define def_ScaleDefault class="num">4
class="macro">#define macro_GetData(A)\
  b0 = class="kw">false;\
  for (c0++; (c0 < max) && (szArg[c0] == &class="macro">#x27; &class="macro">#x27;); c0++);\
  for (i0 = class="num">0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != &class="macro">#x27; &class="macro">#x27; ? c0 - i1 + class="num">1 : i0), c0++);\
  if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
  class="type">class="kw">string sz1;
  class="type">int i0, i1, c1 = StringLen(szArg);
  class="type">bool b0 = true;
  StringToUpper(szArg);
  iScale = def_ScaleDefault;
  m_Period = _Period;
  for (class="type">int c0 = class="num">0, max = StringLen(szArg); c0 < max; c0++) class="kw">switch (szArg[c0])
  {
    case &class="macro">#x27;:&class="macro">#x27;:
      b0 = class="kw">false;

常见问题

传统做法是每个指标占一个子窗口,挤占空间且难联动;把多个指标画在同一主图并错开绘制区域,能直接对照价格看信号。
先抽象出指标对象基类,把创建、绘制、销毁接口定好,后续新指标继承即可接入,不必改主框架。
小布可扫描当前品种页面上的自定义对象并列出命名后缀,提示重复或随机冲突项,省去你手动翻属性框。
给每个运行时对象拼一段随机后缀(如时间戳+随机数),基本能避开同图命名冲突,指标就不会莫名不显示。
先定义好指标对象的统一接口和生命周期管理,再谈渲染层合并;没基类直接堆代码,后面加一个指标改一片。