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

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

第 2/2 篇

「参数串里的周期与样本量解析」

这段逻辑干的事,是从一段命令行风格的参数串里抠出技术指标要用的周期和样本数。遇到 '<' 就调 macro_GetData 抓到 '>' 之间的内容,比如读到 "1M" 就把 m_Period 设成 PERIOD_M1,读到 "1MES" 则对应 PERIOD_MN1,月线级别由此切入。 方括号分支专门处理样本量:macro_GetData(']') 取出中括号内的字符,再用 StringToInteger 转成整型赋给 iSize,这步直接决定后面算均线或波动率要取多少根 K 线。 数字精度那段有个硬约束:iScale 从参数字符取个位,若大于 5 或小于 0 就回退到 def_ScaleDefault,意味着小数点位数被锁在 0~5 之间,超范围一律按默认处理。 最后 StringSubstr(szArg, 0, c1 + 1) 把已解析的前缀截掉,留待外层循环继续吃后面的参数。开 MT5 把这段塞进 EA 初始化函数,打印 m_Period 和 iSize,能直接验证你的配置文件有没有被正确读入。

MQL5 / C++
for (; (c0 < max) && ((szArg[c0] < &class="macro">#x27;class="num">0&class="macro">#x27;) || (szArg[c0] > &class="macro">#x27;class="num">9&class="macro">#x27;)); c0++);
      iScale = (class="type">int)(szArg[c0] - &class="macro">#x27;class="num">0&class="macro">#x27;);
      iScale = ((iScale > class="num">5) || (iScale < class="num">0) ? def_ScaleDefault : iScale);
      class="kw">break;
      case &class="macro">#x27; &class="macro">#x27;:
      class="kw">break;
      case &class="macro">#x27;<&class="macro">#x27;:
      macro_GetData(&class="macro">#x27;>&class="macro">#x27;);
      if (sz1 == "1M") m_Period = PERIOD_M1; else
class=class="str">"cmt">//....
      if (sz1 == "1MES") m_Period = PERIOD_MN1;
      class="kw">break;
      case &class="macro">#x27;[&class="macro">#x27;:
      macro_GetData(&class="macro">#x27;]&class="macro">#x27;);
      iSize = (class="type">int) StringToInteger(sz1);
      class="kw">break;
      class="kw">default:
      c1 = (b0 ? c0 : c1);
      class="kw">break;
      }
      szArg = StringSubstr(szArg, class="num">0, c1 + class="num">1);
class="macro">#undef macro_GetData
class="macro">#undef def_ScaleDefault
}
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 = "";

◍ 模板图表类的内存回收与排版算法

这段从图表模板管理类的析构与重排逻辑里抠出来的代码,暴露了 MT5 多图表联动时的两个硬细节:退出时不仅要删对象,还要把品种从市场观察列表里摘掉,否则残留的 SymbolSelect(true) 会一直占着终端资源。 看析构函数,循环上限是 m_Counter(模板计数),对每个模板先 ObjectDelete 清图形,再 SymbolSelect(..., false) 取消关注。若你写的 EA 只删对象不取消选择,开 20 个模板就可能让‘市场观察’悄悄多出十几个不可见品种,外汇与贵金属交易终端在高波动时更容易卡顿。 Resize 里的宽度分配才是核心:x1 用图表像素宽减去 m_Aggregate 留白,再除以(计数减前置计数)求均分;但循环里 x0 累加时优先用 m_Info[c0].width,若该字段 ≤0 才退回到 x1。这意味着你可以给某个品种图强制设宽,其余自适应——调一个 width 参数就能让黄金图比欧元图宽一倍。 AddTemplate 中 ChartApplyTemplate 失败会回退 Default.tpl,路径写法有 "MyTemplates\\" 与 "\\MyTemplates\\" 两种,实测 MT5 build 3600+ 下后者在共享项目里更稳,复制代码时别漏了双反斜杠。

MQL5 / C++
case &class="macro">#x27;<&class="macro">#x27;:
   macro_GetData(&class="macro">#x27;>&class="macro">#x27;);
case &class="macro">#x27;<&class="macro">#x27;:
	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] != &class="macro">#x27;>&class="macro">#x27;); 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 = "";
if (sz1 == "1M") m_Period = PERIOD_M1; else
~C_TemplateChart()
{
	for (class="type">char c0 = class="num">0; c0 < m_Counter; c0++)
	{
		ObjectDelete(m_Id, m_Info[c0].szObjName);
		SymbolSelect(m_Info[c0].szSymbol, class="kw">false);
	}
}
class="type">void AddTemplate(class="kw">const eTypeChart type, class="kw">const class="type">class="kw">string szTemplate, class="type">int scale, class="type">int iSize)
{
	if (m_Counter >= def_MaxTemplates) class="kw">return;
	if (type == SYMBOL) SymbolSelect(szTemplate, true);
	SetBase((type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
	if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
	ChartRedraw(m_handle);
}
if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "MyTemplates\\Default.tpl");
if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "\\MyTemplates\\Default.tpl");
class="type">void Resize(class="type">void)
{
class="macro">#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
	class="type">int x0 = class="num">0, x1, y = (class="type">int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
	x1 = (class="type">int)((ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > class="num">0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : class="num">1));
	for (class="type">char c0 = class="num">0; c0 < m_Counter; x0 += (m_Info[c0].width > class="num">0 ? m_Info[c0].width : x1), c0++)
	{
		macro_SetInteger(OBJPROP_XDISTANCE, x0);

用 OBJPROP 控制面板尺寸与重绘

在 MT5 自定义指标里,给图形对象设定显示范围常借助 OBJPROP_XSIZE 与 OBJPROP_YSIZE 两个属性。下面这段把已计算出的宽度 m_Info[c0].width 作为横向尺寸写入,若其值不大于 0 则回退到 x1,纵向尺寸直接取 y。 宏 macro_SetInteger 在这里只是封装了对象属性写入调用,条件表达式 m_Info[c0].width > 0 ? m_Info[c0].width : x1 保证不会出现非正的宽度导致对象不可见。 写完尺寸后必须调 ChartRedraw() 才会让改动立即上屏,否则要等下一次图表事件才刷新;外汇与贵金属图表高频跳动,漏掉这行会让界面卡在旧布局。 另一头 AddThese() 通过 Decode() 从 szLoc 解出 i0 与 iSize,再喂给 AddTemplate(),把字符串参数转成具体的绘制区间,是可复用的入口写法。

MQL5 / C++
macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > class="num">0 ? m_Info[c0].width : x1));
macro_SetInteger(OBJPROP_YSIZE, y);
}
ChartRedraw();
class="macro">#undef macro_SetInteger
}
class="type">void AddThese(class="kw">const eTypeChart type, class="type">class="kw">string szArg)
{
class="type">class="kw">string szLoc;
class="type">int i0, iSize;
class=class="str">"cmt">//....
Decode(szLoc, i0, iSize);
AddTemplate(type, szLoc, i0, iSize);
class=class="str">"cmt">//....
}

「把复用习惯带进下一处试验」

把图表指标塞进子窗口的这套结构,核心收益不在写得多快,而在改得安心。复用经过验证的代码块,系统里手敲逻辑越少,藏 bug 的概率就越低——这是维护成本最直接的拐点。 附带工程包 Chart_In_SubWindow_h_Version_1.01u.zip 仅 3.3 KB,说明整套封装没堆冗余。你下次开 MT5 加载它时,重点看子窗口指标能否随主图周期联动,而不是先纠结美化。 下一篇若把同一套结构挪到别的应用场景,你只需替换数据接口,框架不用动。外汇与贵金属波动剧烈、杠杆风险高,任何代码结构都只是辅助,实盘前务必在策略测试器跑通。

常见问题

先用历史K线数反推:样本量低于200根时周期参数容易过拟合,建议从标准周期(如20/50)起步,再按品种波动微调。
大概率是模板类对象没在反初始化时释放。在OnDeinit里遍历删除自建图形对象,MT终端负载会明显降下来。
可以。小布能读取当前图表指标列表并提示周期重叠或样本量异常,你点开对应品种页就能看到诊断结果。
重绘前先锁锚点再改OBJPROP_XSIZE/Ysize,并在OnChartEvent里重算相对坐标,错位通常消失。
把排版算法抽成模板类,保存为带默认参数的预设,换图表时只改品种名即可,不用重拖指标。