直方图形式的统计分布, 无需指标缓冲区和数组·进阶篇
(2/3)·想画买卖价分布却不想碰指标缓冲区和数组?这篇拆解图形对象隐藏属性的用法
不少人在 MT5 里画统计分布第一反应是开数组、建缓冲区,结果行情范围一变就堆满内存和排序逻辑。其实图形对象自带的隐藏属性就能当存储用,只是多数人没往这边想。
主图里直接画指标直方图
把标准指标值搬进主窗口画直方图,逻辑和之前用图形内存画价量分布一致,只是把供给价/采购价换成了指标在当前柱的输出。iMA 可叠两条不同均化周期做对照:比如周期 20 与 14 的双 SMA 直方图,能直观看出快慢线价差结构。 布林带也能拆成三条缓冲区的直方图:BASE_LINE 放零号柱左侧,UPPER_BAND 和 LOWER_BAND 推到当前柱右侧,视觉上上下边带像合成一张图。这样带宽扩张/收缩在柱线排列上一眼可辨,外汇与贵金属品种上这种形态往往伴随波动率突变,属高风险信号,仅作概率参考。 之前实验里变体范围固定为 _Point 最小点数,每列就是独立价格值。其实 DrawHistogram() 的 span 入参控制数位容量,改它就能调频率统计的间隔;价格写进图形对象名后,创建和调整时自动归类,不用手写排序。下面这段双 iMA 直方图代码可直接贴进 MT5 自定义指标验证。
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 附近,能直接看到双均线直方图随每根新柱自动右移;外汇与贵金属波动剧烈,这类绘制仅作辅助参考,实际信号误判概率不低。
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 倍标准差下通道外缘在黄金活价里触价概率会明显下降,属于可立刻验证的现象。外汇与贵金属杠杆高,参数回测结论仅代表历史样本,实盘仍可能随时反向扫损。
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 就能看不同周期下的边界直方图表现。
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 分钟图就能直接看到三轨直方图。
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,再编译看直方图是否落到副图。