直方图形式的统计分布, 无需指标缓冲区和数组(基础篇)
📘

直方图形式的统计分布, 无需指标缓冲区和数组(基础篇)

第 1/3 篇

用直方图画出无缓冲区的分布

在 MT5 里做统计分布展示,不一定非得占用指标缓冲区和额外数组。借助直方图(histogram)形态,可以直接把频数分布画到副图上,省掉维护数组的麻烦。 Sergey Pavlov 在 2017-01-27 发布的示例被查看 5 749 次、收藏 9 次,说明这种轻量画法在实盘辅助分析里确有需求。打开 MT5 按作者名搜该示例,能直接看到直方图怎么绕过常规缓冲区挂载。 对做价格行为统计的交易者,这路子适合快速验证某周期内的收盘价差分布,贵金属与外汇波动大、杠杆高,结论只作概率参考,别当成方向证据。

◍ 把图形内存当统计缓冲区用

直方图的本质,是按样本落入各区间的频率来呈现一组数据的分布形态。传统做法大多依赖指标缓冲区或数组做计数、排序和抽样,MT5 代码库里能翻出不下十种这类例程,但计算开销和代码复杂度都不低。 我们换一条路:直接挪用图形对象的“隐藏内存”。绘制自定义直方图时本就要建图形对象,而它们的属性存储区可以顺手当成轻量统计容器,免去了显式数组管理与重算。 这套思路只解决一件事——用最低成本把市场特征的分布画出来。本文不展开直方图释义,也不谈它在实际交易里的优势,只看怎么在 MT5 里把分布可视化和基础特征跑通。外汇与贵金属波动剧烈,任何分布特征都只是概率描述,不能直接当方向依据。

「用图形对象属性当隐藏内存」

直方图本质是频率柱线图:一根轴排变量值,另一根轴排出现频次,柱高即该区间内的样本数。本文采用垂直直方图,价格按升序落在纵轴,频率落在横轴,买卖价分别贴在当前柱线右侧与左侧,且每个直方图间隔锁定为该品种最小点数(tick size),新报价到来即累加对应列频次。 难点在于题设禁止指标缓冲区、数组与结构。动态数组虽能应对未知价格区间,但被条件排除;同时还需解决重算与重绘时的数据检索排序。MQL5 的图形对象自带一组“隐藏”属性,每个对象创建时即捆绑若干可写变量,本意存对象参数,实际可挪作他用且不损功能——这就是现成的“图形内存”。 把变量写进某个图形对象的属性,等效于在终端里开了块持久存储。普通全局变量自末次访问起仅存活 4 周便自动清除,而图形内存跟着对象存活,对象不删它就一直在,对外汇与贵金属这类高波动、需长周期统计的品种做盘内分布累计更稳。

把价格频率塞进图形对象属性里

在 MT5 里创建一个图形对象时,必须给 OBJPROP_NAME 赋一个独有字符串。这个名称支持内嵌格式化基本类型(整型、布尔、浮点、颜色、时间),所以它能直接携带供给价、采购价这类主数据,而不必另开数组。 相比之下 OBJPROP_TEXT 更自由,是个可读写的文本字段。我们这套做法把频率存进 OBJPROP_TEXT,把价格塞进 OBJPROP_NAME,坐标则交给 OBJPROP_PRICE 与 OBJPROP_TIME,全程不碰指标缓冲区。 有人会问:既然对象本身已有价格坐标属性,为何还要在名字里重复写价格?实测若用 OBJPROP_PRICE 做比对,每当新报价进来就得遍历全部对象、取属性、做 double 比较——浮点直接判等本就不可靠,还会把算法拖慢一个量级。 更干脆的办法是:利用“同名对象不会被重复创建、属性不清零”的机制,直接往目标名字写频率。首次进样本的价格,其 OBJPROP_TEXT 为空,我们写入带星号前缀的 "*1";后续同价再来,就剥掉星号做整数累加。这样免去了存在性检查与克隆开销。 向右偏移直方图也靠同一套:新柱生成后图表左移,我们给时间坐标加一个 hsize 偏移量,把直方图钉在当前柱右侧,避免被推到视野外。

MQL5 / C++
class="type">void DrawHistogram(class="type">bool draw,      class=class="str">"cmt">// 向左侧或右侧绘制直方图
                      class="type">class="kw">string h_name, class=class="str">"cmt">// 对象名称的独有前缀
                      class="type">class="kw">double price,  class=class="str">"cmt">// 价格 (分析参数)
                      class="type">class="kw">datetime time, class=class="str">"cmt">// 将直方图绑定到当前柱线
                      class="type">int span,      class=class="str">"cmt">// 已分析参数的数位容量
                      class="type">int swin=class="num">0)    class=class="str">"cmt">// 直方图窗口
  {
  class="type">class="kw">double y=NormalizeDouble(price,span);
  class="type">class="kw">string pfx=DoubleToString(y,span);
class=class="str">"cmt">// 如果 draw=true, 向右侧绘制直方图
  if(draw)
    {
      class="type">class="kw">string name="+ "+h_name+pfx;                            class=class="str">"cmt">// 对象名称: 前缀+价格
      ObjectCreate(class="num">0,name,OBJ_TREND,swin,time,y);            class=class="str">"cmt">// 创建对象
      ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_R_active); class=class="str">"cmt">// 设置对象颜色
      ObjSet;                                                class=class="str">"cmt">// 宏代码缩写
      if(StringFind(ObjectGetString(class="num">0,name,OBJPROP_TEXT),"*",class="num">0)<class="num">0)
        {class=class="str">"cmt">// 如果结果价格首次进入样本
         ObjectSetString(class="num">0,name,OBJPROP_TEXT,"*class="num">1");          class=class="str">"cmt">// 价格频率为 class="num">1
         ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,time+hsize); class=class="str">"cmt">// 定义时间坐标
        }
      else
        {class=class="str">"cmt">// 如果结果价格并非首次进入样本
         class="type">class="kw">string str=ObjectGetString(class="num">0,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">// 获取频率用于进一步计算
        }
    }
  }

◍ 直方图随新K线平移的底层逻辑

这段逻辑处理的是指标对象在右侧(draw=true)与左侧(draw=false)两种模式下,如何用趋势线对象模拟直方图柱的累加与定位。右侧每次命中就在文本前加「*」并让 n 自增 1,时间坐标按 time+hsize*n 向右推;左侧对称地用 time-hsize*n 向左排,首次出现写「*1」,之后抽取子串转整型再 +1。 新柱线抵达由 time[0]>prevTimeBar 判定,一旦成立就遍历全图对象(ObjectsTotal 取总数倒序循环),只筛首字符等于前缀 R 的条目。外汇与贵金属市场跳空频繁,hsize 若取错周期量级,左侧直方图可能叠到不可见区域,属高风险调试点。 下面拆右侧递增核心三行: n++; // 数值递增 1 ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // 新数值写入到属性 ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//定义时间坐标 左侧首次与迭代分支同理,只是坐标减 hsize*n,且用 StringFind(...,"*",0)<0 区分首建与累加。跑 MT5 时把 R 前缀和 hsize 打印出来,能直接看到对象是否按预期平移。

MQL5 / C++
 n++; class=class="str">"cmt">// 数值递增 class="num">1
 ObjectSetString(class="num">0,name,OBJPROP_TEXT,"*"+(class="type">class="kw">string)n); class=class="str">"cmt">// 新数值写入到属性
 ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,time+hsize*n);class=class="str">"cmt">//定义时间坐标
}
 }
class=class="str">"cmt">// 如果 draw=class="kw">false, 向左侧写入直方图
 if(!draw)
 {
 class="type">class="kw">string name="- "+h_name+pfx;
 ObjectCreate(class="num">0,name,OBJ_TREND,swin,time,y);
 ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_L_active);
 ObjSet;
 if(StringFind(ObjectGetString(class="num">0,name,OBJPROP_TEXT),"*",class="num">0)<class="num">0)
 {
 ObjectSetString(class="num">0,name,OBJPROP_TEXT,"*class="num">1");
 ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,time-hsize);
 }
 else
 {
 class="type">class="kw">string str=ObjectGetString(class="num">0,name,OBJPROP_TEXT);
 class="type">class="kw">string strint=StringSubstr(str,class="num">1);
 class="type">long n=StringToInteger(strint);
 n++;
 ObjectSetString(class="num">0,name,OBJPROP_TEXT,"*"+(class="type">class="kw">string)n);
 ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,time-hsize*n);
 }
 }
 ChartRedraw();
}
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">// 设置新坐标值

「用对象文本字段驱动直方图锚点位移」

这段逻辑处理的是图表上两类自定义直方图对象(前缀 R / L)随新 tick 到来的重定位。核心思路是把位移步数编码进对象文本属性的第一个字符之后,运行时再解析出来算新时间坐标。 对 R 类对象,先读 OBJPROP_TEXT,用 StringSubstr 从索引 1 截掉前缀,StringToInteger 转成步数 n,再把锚点 1 的时间设为 time[0]+hsize*n,同时把颜色刷成 color_R_passive。L 类完全对称,只是时间方向改成 time[0]-hsize*n,颜色用 color_L_passive。 验证时可在 MT5 新建一个脚本,画两个前缀分别为 R3、L2 的 OBJ_HISTOGRAM 对象,hsize 取 3600(1 小时秒数),新 tick 后 R 对象右移 3 小时、L 对象左移 2 小时。外汇与贵金属杠杆品种波动剧烈,这类自动重绘对象仅作辅助参考,实盘须自行承担滑点与跳空风险。 最后 ObjectSetInteger 改完坐标和颜色,必须调 ChartRedraw 才会立刻生效,否则要等下一帧重绘。

MQL5 / C++
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_TIME,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(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();
   }

常见问题

把每个图形对象的隐藏属性字段当内存用,频率值直接写进对象属性,避免开数组和缓冲区。
用对象文本字段记录锚点偏移量,每来一根新K线就改字段值重算坐标,图形自然平移。
可以,小布能读取品种页上的对象属性与文本字段,提示偏移逻辑异常和重复劳动点,你专注调参。
对象属性读写开销极低,比维护大数组轻量,正常品种量级下肉眼无卡顿。
重点核对文本字段解析类型和锚点索引范围,对象多了建议按价格分桶命名方便排查。