价格直方图(市场概况)及其在 MQL5 中的实施·综合运用
📊

价格直方图(市场概况)及其在 MQL5 中的实施·综合运用

(3/3)· 从平衡失衡识别到代码落地,前三篇铺垫的概念在这里被拼成完整交易拼图

实战向 第 3/3 篇
把钟形曲线当支撑阻力盲用的人不在少数,却忽略了它是平衡/失衡周期的投影而非静态边界。价格直方图若只画不读,等于拿着地图不看路。外汇贵金属波动受杠杆放大,误读轮廓可能触发连环止损。

◍ 用 tick 厚度挖出当日 POC 与次级区间

这段逻辑干的事很直接:把一天内的高低点按 _Point 拆成离散价格水平,再统计每个水平在 K 线里被 tick 穿过的次数,次数最高的就是成交量剖面里的 POC。外汇和贵金属市场跳空频繁,这种基于 tick 厚度的算法比单纯看收盘价分布更贴近真实成交密集区,但点值误差会直接扭曲层级索引,属于高风险品种下的高精度玩法。 核心先算层级总数:count = (high_day - low_day) / _Point + 1,然后 ArrayResize 出 ThicknessOfLevel 数组并清零。随后双层循环里,对每根 K 线从 iLow[i] 走到 iHigh[i],每跨一个 _Point 就让对应 Index 的计数加一,等于把每根 bar 的实体区间摊平到价格轴上。 找主 POC 只需 ArrayMaximum 取最大计数下标,poc_value = low_day + _Point * MaxLevel。次级 POC 区间则靠 range_percent 容忍度:低于主峰计数 range_min 的水平一律忽略,从两端向中间扫出 DownLine 和 UpLine,若扫空则回退到 MaxLevel,最终得到 second_poc_bottom / second_poc_top。 直方图输出那块有个细节:当 count > 100 时 step = count / 100,把剖面压缩到最多 100 行显示,避免 MT5 文本框被刷爆。Delta 用总时间减去一个 H1 周期再除以主峰计数,估算的是主 POC 上每单位厚度的平均时间跨度,可拿来肉眼判断堆积是快还是慢。

MQL5 / C++
rates_total=CopyLow(NULL,PERIOD_M1,time_start,time_end,iLow);
if(rates_total<class="num">0)
   PrintErrorOnCopyFunction("CopyLow",_Symbol,PERIOD_M1,GetLastError());
   err++;

} while((rates_time<=class="num">0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS&&!IsStopped());
 if(err>=AMOUNT_OF_ATTEMPTS)
  {
   class="kw">return(false);
  }
 poc_start_time=iTime[class="num">0];
 high_day=iHigh[ArrayMaximum(iHigh,class="num">0,WHOLE_ARRAY)];
 low_day=iLow[ArrayMinimum(iLow,class="num">0,WHOLE_ARRAY)];
 class="type">int count=class="type">int((high_day-low_day)/_Point)+class="num">1;
class=class="str">"cmt">// 对每一个水平,计算某一价格在此持续的时间
 class="type">int ThicknessOfLevel[];    class=class="str">"cmt">// 创建一个数组用于计算ticks
 ArrayResize(ThicknessOfLevel,count);
 ArrayInitialize(ThicknessOfLevel,class="num">0);
 for(class="type">int i=class="num">0;i<rates_total;i++)
  {
   class="type">class="kw">double C=iLow[i];
   while(C<iHigh[i])
     {
      class="type">int Index=class="type">int((C-low_day)/_Point);
      ThicknessOfLevel[Index]++;
      C+=_Point;
     }
  }
 class="type">int MaxLevel=ArrayMaximum(ThicknessOfLevel,class="num">0,count);
 poc_value=low_day+_Point*MaxLevel;
class=class="str">"cmt">// 查找次级POCs
 class="type">int range_min=ThicknessOfLevel[MaxLevel]-ThicknessOfLevel[MaxLevel]*range_percent/class="num">100;
 class="type">int DownLine=class="num">0;
 class="type">int UpLine=class="num">0;
 for(class="type">int i=class="num">0;i<count;i++)
  {
   if(ThicknessOfLevel[i]>=range_min)
     {
      DownLine=i;
      break;
     }
  }
 for(class="type">int i=count-class="num">1;i>class="num">0;i--)
  {
   if(ThicknessOfLevel[i]>=range_min)
     {
      UpLine=i;
      break;
     }
  }
 if(DownLine==class="num">0)
   DownLine=MaxLevel;
 if(UpLine==class="num">0)
   UpLine=MaxLevel;
 second_poc_top=low_day+_Point*UpLine;
 second_poc_bottom=low_day+_Point*DownLine;
class=class="str">"cmt">// 直方图信息 
 if(show_histogram)
  {
   class="type">class="kw">datetime Delta=(iTime[rates_total-class="num">1]-iTime[class="num">0]-PeriodSeconds(PERIOD_H1))/ThicknessOfLevel[MaxLevel];
   class="type">int step=class="num">1;
   if(count>class="num">100)
     step=count/class="num">100;   class=class="str">"cmt">// 计算直方图的步长(最大100行)
   ListHistogramInner.Clear();
   ListHistogramOuter.Clear();
   for(class="type">int i=class="num">0;i<count;i+=step)
     {

用趋势线对象搭出成交量分布带

这段逻辑干的事,是把每个价位档画成一条水平趋势线,从 POC 起始时间拉到当前 bar 之后一段 Δ 距离。内层档(DownLine~UpLine 之间)归 ListHistogramInner,外层档归 ListHistogramOuter,颜色分开管理,方便后续区分「核心密集区」和「边缘稀薄区」。 string name=TimeToString(time_start)+" "+IntegerToString(i); double StartY= low_day+_Point*i; datetime EndX= iTime[0]+(ThicknessOfLevel[i])*Delta; CChartObjectTrend *obj=new CChartObjectTrend(); obj.Create(0,name,0,poc_start_time,StartY,EndX,StartY); obj.Background(true); if(i>=DownLine && i<=UpLine) { obj.Color(inner_color); ListHistogramInner.Add(obj); } else { obj.Color(outer_color); ListHistogramOuter.Add(obj); } 上面代码逐行拆:第1行用起始时间加档位序号拼出对象名,避免重名;第2行 StartY 是 low_day 加上 i 个点的偏移,即该档位对应的价格;第3行 EndX 把线尾推到最新 K 线时间再加 ThicknessOfLevel[i]*Delta,控制每条线长短;第4~5行新建趋势线对象并绑定到图表0、用四个坐标点画水平线;第6行 Background(true) 让它衬在蜡烛后面;后面 if-else 按档位内外决定着色与归队。 数据回填部分从直方图的末时间一路拉到当前 tick,用 CopyTime/CopyHigh/CopyLow 抓 M1 历史,失败重试上限由 AMOUNT_OF_ATTEMPTS 控制。若 rates_time==0 说明当天无任何触碰,标记 virgin=true 表示「原始水平」;否则扫一遍,只要某根 M1 K 线的高低价跨过 poc_value,就记下穿透时间 poc_end_time,否则也判为 virgin。 外汇与贵金属属高风险品种,这种「水平是否被触碰」的判断只反映历史概率,不能推定后续必破或必守。实盘前建议在 MT5 用策略测试器跑一段 XAUUSD 的 M1 数据,把 AMOUNT_OF_ATTEMPTS 调到 3~5 看对象创建会不会漏帧。

MQL5 / C++
class="type">class="kw">string name=TimeToString(time_start)+" "+IntegerToString(i);
class="type">class="kw">double StartY= low_day+_Point*i;
class="type">class="kw">datetime EndX= iTime[class="num">0]+(ThicknessOfLevel[i])*Delta;
CChartObjectTrend *obj=new CChartObjectTrend();
obj.Create(class="num">0,name,class="num">0,poc_start_time,StartY,EndX,StartY);
obj.Background(true);
if(i>=DownLine && i<=UpLine)
  {
   obj.Color(inner_color);
   ListHistogramInner.Add(obj);
  }
else
  {
   obj.Color(outer_color);
   ListHistogramOuter.Add(obj);
  }

class=class="str">"cmt">// 我们从直方图的最后时间开始接收数据,一直到当前时间
   err=class="num">0;
   do
     {
      rates_time=CopyTime(NULL,PERIOD_M1,time_end,last_tick.time,iTime);
      rates_high=CopyHigh(NULL,PERIOD_M1,time_end,last_tick.time,iHigh);
      rates_total=CopyLow(NULL,PERIOD_M1,time_end,last_tick.time,iLow);
      err++;
     }
   while((rates_time<=class="num">0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS);
class=class="str">"cmt">// 如果没有历史数据,当前天是原始水平,则我们提升颜色
   if(rates_time==class="num">0)
     {
      virgin=true;
     }
   else
class=class="str">"cmt">// 否则我们检查历史数据
     {
      for(index=class="num">0;index<rates_total;index++)
        if(poc_value<iHigh[index] && poc_value>iLow[index]) break;
      if(index<rates_total)   class=class="str">"cmt">// 如果水平被穿越
        poc_end_time=iTime[index];
      else
        virgin=true;
     }
   if(POCLine==NULL)
     {     
      POCLine=new CChartObjectTrend();
      POCLine.Create(class="num">0,TimeToString(time_start)+" POC ",class="num">0,poc_start_time,poc_value,class="num">0,class="num">0);
     }
   POCLine.Color(poc_color);
   RefreshPOCs();

「直方图必须配确认信号才靠谱」

价格直方图这类工具本质上靠的是盘手直觉,信号本身容易骗线,实盘里必须叠加确认层——比如成交量突变、关键位假破或更高周期方向,单独看柱宽很容易在震荡里反复打脸。 有读者在 MT5 5.00 Build 3661(AMP Futures 版)编译 ClassPriceHistogram.mqh 时,第 375 行报错,手动替换一处符号后跑通,说明旧版头文件在新构建里存在兼容坑,下载附件包(pricehistogram.mq5 仅 6.75 KB)后先本地编译验证再挂实盘。 外汇与贵金属杠杆高、滑点猛,这类剖面工具只作概率参考,别当成进出场唯一依据;真要用的话,拿历史数据回放调一下分箱宽度,比盲目信「轮廓清晰=突破」更实在。

MQL5 / C++
class="type">void CPriceHistogram::Delete()

◍ 把这条线请下神坛

原文第 104 行把 >= 改回 < 的修正,本质是把直方图判定逻辑从“达标”翻转为“未达标”,这类单行改动在 tickvolume 类指标里极易引发信号反转,验证时请直接开 MT5 调那一行看柱体颜色变化。 社区里用 tickvolume 做标普 500 差价合约、或换 realvolume 做 CME 期货,只是数据源切换,不改喷发点群和 DLL 绑定的底层结构;喷发靠多指标相交生成星云状点群,多货币 EA 只是把老 diversification 思路用最简框架落地。 外汇与贵金属自带高杠杆高风险,这类工具链再花哨也只是辅助,那条被神化的阈值线,调回小于号之后就该从神坛下来了。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时价格直方图与平衡偏移提示,把重复劳动交给小布,你专注决策。

常见问题

前者以价格-时间关系简化呈现市场概况,侧重平衡失衡的周期识别,而非逐根成交量分布,计算开销更低、信号更粗但快。
可以,小布盯盘的 AIGC 模块已预置价格直方图渲染,切换品种即见当日平衡区间与偏移倾向,无需自行编译脚本。
双波峰往往反映价格变动中参与者信心分化,可能意味着趋势中继或假突破,需结合日线平衡级别确认概率。
日线给出转变起点,小时图价格直方图捕捉从平衡到失衡的临界,二者共振时交易质量与耗时更易估计。