在一张图表上的多个指标(第 03 部分):为用户开发定义(基础篇)
多指标同图的需求从哪来
在 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;若代码细节调整,搜索顺序会反过来。知道文件落点,你就能自己摆模板不发懵。
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,以及专家日志里对象名是否真带随机尾数。外汇与贵金属图表上跑这类脚本属高风险操作,对象堆积多了会拖慢终端响应。
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;