MеtaTrader5 图表上的水平示意图·综合运用
📊

MеtaTrader5 图表上的水平示意图·综合运用

(3/3)·从绑定坐标到数组化删除,前两周铺垫的示意图终于能跑通完整生命周期

案例拆解 第 3/3 篇
很多人写指标时直接堆几百个图形对象,名字随手编,结果刷新一次图表就重影、卡顿、删不干净。水平示意图若不做数组化命名和绑定坐标,后期移动缩放基本失控。外汇贵金属图表波动快,这种混乱在高波动时段会直接拖垮看盘。

◍ 把每根 K 线的成交量摊到价格档位上

这段代码干的事很直接:把一段 MQL5 的 K 线数据,按价格区间拆开,把成交量均匀铺到每个 tick 价位上,方便后面画直方图。 先清零两个数组 pcur 和 vcur,pcur 存的是每个档位对应的实际价格,vcur 存的是该档位累计的成交量。prmax 用来记录最大档位成交量,初始化为 0。 第一层循环按 prmin 起算,把每个整数档位 i 映射成真实价格 (prmin+i)*hdPoint,写进 pcur。这一步只是把“第几档”翻译成“多少钱”。 第二层循环遍历 source 数组里的 K 线(从 1 开始)。tick_volume 为 0 或四舍五入校验不过的 K 线直接跳过。delta 是高低价差的整数档数,若为 0 则强行置 1,避免除零。avTick = tick_volume / delta,即每档应摊的成交量。 内层再按 low 到 low+delta 的每个档位 j,把 avTick 累加进 vcur[j],并实时刷新 prmax。若 j 超出 prsize 数组边界,直接 Print 报错并返回 false——这通常意味着当前周期数据跨度比预设档位范围大,需要换周期或等下一根。 循环结束后用 rs 记录实际用到的最大档位下标,prsize 重设为 rs+1,返回 true。你在 MT5 里跑这段时,若终端日志冒出 “Internal ERROR” 那句,八成是 hdPoint 和 prmin 设得太窄,外汇和贵金属波动剧烈时这种越界概率更高,属正常高风险调试现象。

MQL5 / C++
  ArrayInitialize(hdta.pcur, class="num">0);
  ArrayInitialize(hdta.vcur, class="num">0);

  class="type">class="kw">double avTick;
  class="type">int i, delta;
  hdta.prmax = class="num">0;

  for (i = class="num">0; i < hdta.prsize; i++) hdta.pcur[i] = (hdta.prmin + i) * hdPoint;
  class="type">int rs = class="num">0;
  for (i = class="num">1; i < iSCount; i++) {
      if (source[i].tick_volume == class="num">0.0) class="kw">continue;
      if (!MqlRatesRound(source[i], (class="type">int)hdta.prmin) ) class="kw">continue;
      delta = (class="type">int)(source[i].high - source[i].low);
      if (delta == class="num">0) delta = class="num">1;
      avTick = (class="type">class="kw">double)(source[i].tick_volume / delta);
      class="type">int j;
      for (j = (class="type">int)source[i].low; j <= (class="type">int)(source[i].low) + delta; j++) {
           if (j >= hdta.prsize) {
                Print("Internal ERROR. Wait for next source period or class="kw">switch timeframe");
                class="kw">return class="kw">false;
           }
           hdta.vcur[j] += avTick;
           if (hdta.vcur[j] > hdta.prmax) hdta.prmax = (class="type">int)hdta.vcur[j];
      }class=class="str">"cmt">//for (class="type">int j = (class="type">int)source[i].low; j <= (class="type">int)(source[i].low) + delta; j++)
      if (j > rs) rs = j; class=class="str">"cmt">//real size
  }class=class="str">"cmt">//for (class="type">int i = class="num">1; i < iSCount; i++)
  hdta.prsize = rs + class="num">1;
  class="kw">return (true);
}

「对象命名的层级链条」

在水平示意图体系里,几乎所有关联对象都靠名称做索引,而名称不是随便起的,是按层级拼出来的。管理器内部有一个私有字段 m_BaseName 作为“base”基底,后面所有名字都从它派生。 创建管理器实例时要传一个唯一 ID,这个唯一性由调用方保证,不是系统自动兜底。m_BaseName 加上这个 ID 就拼成了管理器自己的名字。 示意图对象在被创建时,会基于所属管理器的名称再挂上自己的唯一名;而示意图里的图形基元,又顺着示意图对象名继续往下拼。这样一层套一层,你在 MT5 对象列表里就能用前缀快速筛出某套示意图的全部元素,批量隐藏或删除都不会误伤别的图层。

把指标改成右侧逐笔量分布图

把之前写好的指标回炉,目标只有一个:在图表右侧画出逐笔报价的交易量分布,并且始终贴着终端窗口右边对齐。外汇与贵金属市场跳价频繁,这种图能帮你看清某一价位的成交密度,但杠杆品种波动剧烈,任何分布都只是概率参考。 改造动作很轻:初始化里把图表对象改成靠右停靠,OnCalculate 里的计算逻辑全部清空,只留 OnChartEvent 接管重绘。窗口尺寸一变就调 Align(),分布图不会跑偏。 下面这段就是核心改动。pHd 在初始化时以 HD_RIGHT 模式新建,MaxHDcount 控制柱子数量上限;OnChartEvent 只在图表变更事件里触发对齐,其他事件直接放过,CPU 占用压到最低。

MQL5 / C++
  pHd         = new CHDiags(HD_RIGHT, hdStyle, HD_RIGHTLEFT, hdHorSize, hdColor, hdWidth, class="num">0, MaxHDcount);
  if(pHd       == NULL) class="kw">return (INIT_FAILED);
  iCurr        = pHd.AddHDiag(TimeCurrent() );
  if(iCurr     == -class="num">1) class="kw">return (INIT_FAILED); 
class="type">void OnChartEvent(class="kw">const class="type">int id,
                  class="kw">const class="type">long &lparam,
                  class="kw">const class="type">class="kw">double &dparam,
                  class="kw">const class="type">class="kw">string &sparam)
  {
      class="kw">switch (id) {
       case CHARTEVENT_CHART_CHANGE:
            pHd.Align();
            class="kw">break;
       class="kw">default:
            class="kw">break;   
      }class=class="str">"cmt">//class="kw">switch (id)
  } 

◍ 演示指标的边界与扩展空间

这套水平示意图的搭建逻辑已经跑通:启用单个文件即可生成图形,构造数据的准备属于开发侧工作,不在本文讨论范围内。库文件里保留的方法仍有优化余地,后续可以补进更多图形基元来丰富绘制能力。 需要注意,随文附带的指标仅作演示与培训用途,实盘直接套用存在高风险,外汇与贵金属品种波动剧烈,信号失效可能造成本金亏损。 特别一点:用作数据源的时间帧内不能挂任何控件,否则读数会被干扰,示意图的参照意义就废了。

「配套文件与延迟处理的实情」

这套成交量分布思路落地时附带三个文件:VolChart.mq5(12.65 KB)是指标本体,负责把逐笔报价按价格区画出交易量分布;HDiagsE.mqh(36.66 KB)是库文件,管水平示意图的创建和清理;VolChart1.mq5(12.21 KB)和前者逻辑一致,但强制对齐到终端窗口右侧。想验证的人直接把这三个文件拖进 MT5 的 MQL5 目录编译即可,不用自己重写框架。 作者在原讨论里提过两个绕不开的延迟点。初始化阶段若所选时间框架历史不全,指标会在 OnCalculate 里先检查源数据就绪,然后主动跳过 5 次调用,日志打 pass 1、pass 2 直到确认数据齐了才画历史图——这避免了半截图乱跑,但代价是启动肉眼可见地慢几秒。第二种延迟在图形由指标托管时出现:用户拖图表会触发一连串 CHARTEVENT_CHART_CHANGE,指标选择不处理、直接返回,等停止操作 3 秒后再重绘一次,宁可晚一点也不抢资源。外汇和贵金属盘口事件密集,这种取舍能明显降低卡顿概率,但是非实时刷新,介意就改那行等待常数。 指标本身只是演示原理,不是拿来直接跟单的成品;俄文原帖作者也明确说 Market 里有同原理的免费 Demo 指标。贵金属和外汇杠杆高、滑点突发行情多,拿去实盘前先在策略测试器用 Tick 数据跑一遍再谈参数。

把对象清理交给小布
这些示意图的创建、缩放与批量删除逻辑,小布盯盘的 AIGC 诊断已内置常见模板,打开对应品种页即可对照自己的指标结构查漏。

常见问题

可绑定终端窗口左侧或右侧,前者适合示意图起点在可视区外,后者适合绑定当前周期避免工作区对象过多,构成示意图的基元至少有一个时间坐标保持不变。
如卖/买/总量逐笔报价分布,拆层后各自管理名称与颜色,缩放移动互不覆盖,也方便后期单独删除某一层而不影响整体。
常见是把一段价格按百分比等分,例如百分之百区间取百分之十步幅,得到恒定间隔数,这样柱状条高度和位置计算更简单,但步幅需结合品种点值调整。
目前小布内置的是结构校验与对象清理建议,不自动写完整指标,但可对照它给出的对象数组范例反查你代码里的命名冲突。
指标退出或重算时应按名称数组遍历删除,否则残留对象会累积;用唯一前缀加索引的数组管理是最省事的做法。