直方图形式的统计分布, 无需指标缓冲区和数组·进阶篇
📊

直方图形式的统计分布, 无需指标缓冲区和数组·进阶篇

(2/3)·想画买卖价分布却不想碰指标缓冲区和数组?这篇拆解图形对象隐藏属性的用法

案例拆解新手友好 第 2/3 篇

不少人在 MT5 里画统计分布第一反应是开数组、建缓冲区,结果行情范围一变就堆满内存和排序逻辑。其实图形对象自带的隐藏属性就能当存储用,只是多数人没往这边想。

主图里直接画指标直方图

把标准指标值搬进主窗口画直方图,逻辑和之前用图形内存画价量分布一致,只是把供给价/采购价换成了指标在当前柱的输出。iMA 可叠两条不同均化周期做对照:比如周期 20 与 14 的双 SMA 直方图,能直观看出快慢线价差结构。 布林带也能拆成三条缓冲区的直方图:BASE_LINE 放零号柱左侧,UPPER_BAND 和 LOWER_BAND 推到当前柱右侧,视觉上上下边带像合成一张图。这样带宽扩张/收缩在柱线排列上一眼可辨,外汇与贵金属品种上这种形态往往伴随波动率突变,属高风险信号,仅作概率参考。 之前实验里变体范围固定为 _Point 最小点数,每列就是独立价格值。其实 DrawHistogram() 的 span 入参控制数位容量,改它就能调频率统计的间隔;价格写进图形对象名后,创建和调整时自动归类,不用手写排序。下面这段双 iMA 直方图代码可直接贴进 MT5 自定义指标验证。

MQL5 / C++
class="kw">input class="type">int               period_MA1=class="num">20;      class=class="str">"cmt">// iMA1 的均化周期
class="kw">input class="type">int               period_MA2=class="num">14;      class=class="str">"cmt">// iMA2 的均化周期
class=class="str">"cmt">//---- 指标缓存区
class="type">class="kw">double   MA1[];
class="type">class="kw">double   MA2[];
class=class="str">"cmt">//---- 指标句柄
class="type">int      iMA1_handle;
class="type">int      iMA2_handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ObjectsDeleteAll(class="num">0,-class="num">1,-class="num">1);
   ChartRedraw();
   iMA1_handle=iMA(_Symbol,_Period,period_MA1,class="num">0,MODE_SMA,PRICE_CLOSE);
   iMA2_handle=iMA(_Symbol,_Period,period_MA2,class="num">0,MODE_SMA,PRICE_CLOSE);
   ArraySetAsSeries(MA1,true);
   ArraySetAsSeries(MA2,true);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
   ArraySetAsSeries(time,true);

◍ 把直方图推到新K线其实就这几行

双均线数值抓取靠 CopyBuffer 直取最新一根:iMA1_handle 与 iMA2_handle 各自读 0 号缓冲区、从 0 偏移取 1 个值进 MA1/MA2 数组,随后 DrawHistogram 按 true/false 区分上下直方图并把 MA1[0]、MA2[0] 标到 time[0] 这根柱。 新柱抵达由 time[0]>prevTimeBar 判定,一旦成立就遍历当前图表全部图形对象(ObjectsTotal 带 -1,-1 取所有类型),只挑首字符为 R 或 L 的直方图元素处理。 对 R 类元素,代码先读 OBJPROP_TEXT,用 StringSubstr(str,1) 剥掉前缀再 StringToInteger 转成偏移序号 n,把锚点 1 的时间重算为 time[0]+hsize*n,同时把颜色改成 color_R_passive。这样旧柱上的直方图会被整体平移到新柱,视觉上不丢历史分布。 开 MT5 把这段塞进 OnCalculate,hsize 设成 PeriodSeconds()/2 附近,能直接看到双均线直方图随每根新柱自动右移;外汇与贵金属波动剧烈,这类绘制仅作辅助参考,实际信号误判概率不低。

MQL5 / C++
  CopyBuffer(iMA1_handle,class="num">0,class="num">0,class="num">1,MA1);
  CopyBuffer(iMA2_handle,class="num">0,class="num">0,class="num">1,MA2);
  DrawHistogram(true,"iMA("+(class="type">class="kw">string)period_MA1+")=",MA1[class="num">0],time[class="num">0],_Digits);
  DrawHistogram(class="kw">false,"iMA("+(class="type">class="kw">string)period_MA2+")=",MA2[class="num">0],time[class="num">0],_Digits);
class=class="str">"cmt">//--- 将直方图平移到新的柱线
  if(time[class="num">0]>prevTimeBar) class=class="str">"cmt">// 定义新柱线抵达
    {
      prevTimeBar=time[class="num">0];
      class=class="str">"cmt">// 通过所有图形对象传递
      for(class="type">int obj=ObjectsTotal(class="num">0,-class="num">1,-class="num">1)-class="num">1;obj>=class="num">0;obj--)
        {
          class="type">class="kw">string obj_name=ObjectName(class="num">0,obj,-class="num">1,-class="num">1);                class=class="str">"cmt">// 获取已发现对象的名称
          if(obj_name[class="num">0]==R)                                      class=class="str">"cmt">// 搜索直方图元素前缀
            {                                                     class=class="str">"cmt">// 如果已发现直方图元素
              ObjectSetInteger(class="num">0,obj_name,OBJPROP_TIME,           class=class="str">"cmt">// 设置新坐标值
                              class="num">0,time[class="num">0]);                         class=class="str">"cmt">// 对于锚点 "class="num">0"
              class="type">class="kw">string str=ObjectGetString(class="num">0,obj_name,OBJPROP_TEXT);class=class="str">"cmt">// 从对象属性中读取变量
              class="type">class="kw">string strint=StringSubstr(str,class="num">1);                  class=class="str">"cmt">// 从接收的变量中分离出一个子字符串
              class="type">long n=StringToInteger(strint);                     class=class="str">"cmt">// 将字符串转换为长整型变量
              ObjectSetInteger(class="num">0,obj_name,OBJPROP_TIME,           class=class="str">"cmt">// 计算新坐标值
                              class="num">1,time[class="num">0]+hsize*n);                 class=class="str">"cmt">// 对于锚点 "class="num">1"
              ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,
                               color_R_passive);                  class=class="str">"cmt">// 改变已移动直方图元素的颜色
            }
          if(obj_name[class="num">0]==L)
            {

「布林带句柄与历史通道对象的回填」

这段逻辑解决的是「先把旧对象摆正、再让布林带接管」的问题。在循环里用 ObjectSetInteger 把图形对象的时间锚点钉在当前 K 线 time[0],再靠 ObjectGetString 读出对象文本,用 StringSubstr 砍掉首字符后转成整数 n,代表该对象要向左回退的 K 线根数。 ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n) 这一行是核心:以当前时间为基准,减去单根宽度 hsize 乘以 n,把对象的第二锚点推到历史位置。随后把颜色刷成 color_L_passive,视觉上和活跃通道区分开。 初始化阶段先 ObjectsDeleteAll(0,-1,-1) 清场,再拿 iBands(_Symbol,_Period,14,0,5.00,PRICE_CLOSE) 建句柄——14 周期、5 倍标准差、收盘价计算,是贵金属 1H 图常见的宽通道参数。UPPER 和 LOWER 两个缓存区都设成 ArraySetAsSeries(true),保证数组下标 0 对应最新柱。 开 MT5 把这段塞进指标模板,把 period_Bands 从 14 改成 20 对比一下,5 倍标准差下通道外缘在黄金活价里触价概率会明显下降,属于可立刻验证的现象。外汇与贵金属杠杆高,参数回测结论仅代表历史样本,实盘仍可能随时反向扫损。

MQL5 / C++
ObjectSetInteger(class="num">0,obj_name,OBJPROP_TIME,class="num">0,time[class="num">0]);
class="type">class="kw">string str=ObjectGetString(class="num">0,obj_name,OBJPROP_TEXT);
class="type">class="kw">string strint=StringSubstr(str,class="num">1);
class="type">long n=StringToInteger(strint);
ObjectSetInteger(class="num">0,obj_name,OBJPROP_TIME,class="num">1,time[class="num">0]-hsize*n);
ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,color_L_passive);
}
}
ChartRedraw();
}
class="kw">return(rates_total);
}
class="kw">input class="type">int period_Bands=class="num">14; class=class="str">"cmt">// iBands 的均化周期
class=class="str">"cmt">//---- 指标缓存区
class="type">class="kw">double UPPER[];
class="type">class="kw">double LOWER[];
class=class="str">"cmt">//---- 指标句柄
class="type">int iBands_handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
 {
 ObjectsDeleteAll(class="num">0,-class="num">1,-class="num">1);
 ChartRedraw();
 iBands_handle=iBands(_Symbol,_Period,period_Bands,class="num">0,class="num">5.00,PRICE_CLOSE);
 ArraySetAsSeries(UPPER,true);
 ArraySetAsSeries(LOWER,true);
 class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
 class="kw">const class="type">int prev_calculated,
 class="kw">const class="type">class="kw">datetime &time[],
 class="kw">const class="type">class="kw">double &open[],
 class="kw">const class="type">class="kw">double &high[],
 class="kw">const class="type">class="kw">double &low[],
 class="kw">const class="type">class="kw">double &close[],
 class="kw">const class="type">long &tick_volume[],
 class="kw">const class="type">long &volume[],
 class="kw">const class="type">int &spread[])
 {
class=class="str">"cmt">//---

用布林带缓冲画直方图的实现细节

这段自定义指标的核心,是把 iBands 的上轨、下轨和中轨缓冲分别拷进数组,再交给 DrawHistogram 按当前柱线时间画出来。注意 OnInit 里 iBands 的偏移参数填了 0、标准差填 10.00,均化周期由外部输入 period_Bands 控制,默认 20。 OnCalculate 开头先把 time 数组设为序列方向(最新在 [0]),随后用 CopyBuffer 取句柄 0/1/2 分别对应 BASE、UPPER、LOWER,只取 1 根柱。若 time[0] 大于 prevTimeBar,说明来了新 K 线,这时才把直方图平移到新柱,避免每 tick 重画。 外汇与贵金属波动剧烈,10 倍标准差的布林带实战中极少被触碰,仅适合做极端边界参考;直接把下面代码贴进 MT5 指标文件,改 period_Bands 就能看不同周期下的边界直方图表现。

MQL5 / C++
class="kw">input class="type">int   period_Bands=class="num">20;      class=class="str">"cmt">// iBands 的均化周期
class=class="str">"cmt">//---- 指标缓存区
class="type">class="kw">double      BASE[];
class="type">class="kw">double      UPPER[];
class="type">class="kw">double      LOWER[];
class=class="str">"cmt">//---- 指标句柄
class="type">int         iBands_handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ObjectsDeleteAll(class="num">0,-class="num">1,-class="num">1);
   ChartRedraw();
   iBands_handle=iBands(_Symbol,_Period,period_Bands,class="num">0,class="num">10.00,PRICE_CLOSE);
   ArraySetAsSeries(BASE,true);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//---
   ArraySetAsSeries(time,true);

   CopyBuffer(iBands_handle,class="num">0,class="num">0,class="num">1,BASE);
   ArraySetAsSeries(time,true);

   CopyBuffer(iBands_handle,class="num">1,class="num">0,class="num">1,UPPER);
   CopyBuffer(iBands_handle,class="num">2,class="num">0,class="num">1,LOWER);
   DrawHistogram(true,"iBands(UPPER)=",UPPER[class="num">0],time[class="num">0],_Digits);
   DrawHistogram(class="kw">false,"iBands(LOWER)=",LOWER[class="num">0],time[class="num">0],_Digits);
class=class="str">"cmt">//--- 将直方图平移到新的柱线
   if(time[class="num">0]>prevTimeBar)
     {
     ...
     }
   class="kw">return(rates_total);
  }

◍ 把布林带上下轨画进直方图

在 MT5 自定义指标里,把 iBands 的缓冲区分段拷出来是最容易写错的一步。上面这段代码先用 CopyBuffer 把句柄 iBands_handle 的第 1 号缓冲(上轨)和第 2 号缓冲(下轨)各取最新 1 根柱线到 UPPER、LOWER 数组,索引 0 是实时柱。 随后三次调用 DrawHistogram:前两次以 true 标记把 UPPER[0]、LOWER[0] 连同 time[0] 和时间戳画成直方图,第三次用 false 把 BASE[0](中轨)也叠在同一坐标。_Digits 保证贵金属或外汇品种的小数位对齐,XAUUSD 的 _Digits 通常是 2,EURUSD 是 5。 平移逻辑靠 time[0] > prevTimeBar 判断:只有新柱生成才进花括号里的重绘分支,避免每 tick 重画浪费资源。return(rates_total) 把已处理柱数交还终端,指标才能持续刷新。开 MT5 把这段塞进 OnCalculate 尾部,加载到 15 分钟图就能直接看到三轨直方图。

MQL5 / C++
  CopyBuffer(iBands_handle,class="num">1,class="num">0,class="num">1,UPPER);
  CopyBuffer(iBands_handle,class="num">2,class="num">0,class="num">1,LOWER);
  DrawHistogram(true,"iBands(UPPER)=",UPPER[class="num">0],time[class="num">0],_Digits);
  DrawHistogram(true,"iBands(LOWER)=",LOWER[class="num">0],time[class="num">0],_Digits);
  DrawHistogram(class="kw">false,"iBands(LOWER)=",BASE[class="num">0],time[class="num">0],_Digits);
class=class="str">"cmt">//--- 将直方图平移到新的柱线
  if(time[class="num">0]>prevTimeBar)
    {
    ...
    }
  class="kw">return(rates_total);
}

「副图里画直方图要注意负值与位数」

在 MT5 的附加窗口里画统计分布直方图,逻辑和主图差不多,但有个坑:副图变量可能为负,而且小数位数没有 _Digits 那种固定约束。主图能用预定义变量 _Digits 直接拿价格刻度,副图里数值正负随意、位数也不可控,不能照抄主图参数。 调用 DrawHistogram() 构建直方图时,必须显式指定附加窗口索引。函数默认零值指向主窗口,不传就会画错地方。外汇和贵金属市场波动剧烈、杠杆高风险大,验证前先在小周期模拟盘跑通再上实盘。 实操一步:打开 MT5 新建指标,把窗口索引参数从 0 改成 1,再编译看直方图是否落到副图。

把重绘逻辑交给小布盯盘
这些图形对象属性的读取与分布重绘,小布盯盘的 AIGC 已内置在对应品种页,你只需看分布形态,把重复劳动交给小布,你专注决策。

常见问题

垂直直方图把价格值放在纵轴、频率放横轴,更贴合主图看价习惯;水平则反过来,用于其它窗口更顺手。
借助图形对象自带的隐藏属性字段,每列绑定一个对象属性,动态范围由对象数量自然承载。
间隔设为当前品种最小点数大小,新报价落入即累加对应列频率,分布形态会更细。
可以,小布盯盘品种页已内置基于图形对象属性的分布视图,打开即见,不用自己写代码。
本篇聚焦可视化与基础特征的实现,释义类内容留到系列其他部分,避免一篇过载。