价格直方图(市场概况)及其在 MQL5 中的实施·进阶篇
📊

价格直方图(市场概况)及其在 MQL5 中的实施·进阶篇

(2/3)· 钟形曲线背后是拍卖逻辑,本篇拆解 MQL5 如何把价格与时间关系落成可运行的价格直方图

偏理论 第 2/3 篇
很多人把市场概况当成现成指标直接拖进图表,却没意识到它本质是供求拍卖的可视化。忽略平衡与失衡的周期切换,直方图只是好看的钟形,给不了你位置感。

把价格直方图做成无交易EA而非指标

早期在 MT4 用 MQL4 写的价格直方图碰过不少坑:M1、M5 历史柱编号太短,假日与 CFD schedule 要自己写函数处理,切周期还会重复计算拖慢终端。MT5 测试版出来后,我直接转去 MQL5 重写。 指标方案先被否决。OnCalculate() 跑 260 天分钟数据要数分钟,而切时间表时系统会销毁旧指标、新建并重算,同样的活重复花大量时间。MQL5 文档里藏着解法:把它写成不交易的 EA。 EA 的好处很实在:OnTick() 里的 Init 不卡运行时;DeInit 带 reason 参数,切表只发 REASON_CHARTCHANGE,不卸载 EA 和全局变量。这样附加后一次性算完,新交易日进数据再增量更新。 源码拆成 4 个文件,主文件 PriceHistogram.mq5 同目录放 \MQL5\Experts\PriceHistogram,其余 .mqh 是类描述。下面这段是主文件开头,输入参数决定直方图天数与回看长度。 类设计上,CExpert 管事件,CPriceHistogram 管单日直方图,CProgressBar 管进度条——因为 500 天分钟历史处理久,必须可视化免得以为终端死了。

MQL5 / C++
class="macro">#include "ClassExpert.mqh"
class=class="str">"cmt">// 输入参数区块
class="kw">input class="type">int      DayTheHistogram   = class="num">10;       class=class="str">"cmt">// 直方图天数
class="kw">input class="type">int      DaysForCalculation= class="num">500;      class=class="str">"cmt">// 用于计算的天数(-1代表所有)

「初始化时先卡住行情同步再灌参数」

MT5 自定义指标或 EA 在 OnInit 里最容易翻车的不是画图,而是历史数据还没同步完就开算。上面这段逻辑在正式计算前用 while 循环去查 SERIES_SYNCHRONIZED,每次睡 500 毫秒、最多试 AMOUNT_OF_ATTEMPTS 次,外汇和贵金属品种在切换周期时尤其容易卡这一步,不处理就可能拿到半截日线。 RangePercent 默认给 70,代表内部范围取成交量分布里占比 70% 的价格带;InnerRange 填 Indigo、OuterRange 填 Magenta、ControlPoint 填 Orange,分别对应范围内、范围外和 POC 控制点的颜色。ShowValue 置 true 时会在图上直接标出数值。 这些 input 在 OnInit 里被逐一赋给 ExtExpert 实例,再调 ExtExpert.Init() 完成 CExpert 类启动。DaysForCalculation 设 -1 表示吃全部历史日线,DayTheHistogram 控制直方图对应的具体交易日,参数不对,后面分布图就会偏。 [CODE] 里 CExpert 类用 CList 挂了一串 CObject 派生对象,name_symbol 存品种名、count_bars 存日线根数、event_on 管事件开关。想验证就开 MT5 把 RangePercent 改成 50 或 90,看内部范围收缩或扩张是否符合预期,贵金属跳空日可能让分布断层,属正常。

MQL5 / C++
class="kw">input class="type">uint        RangePercent    = class="num">70;            class=class="str">"cmt">// 百分比范围
class="kw">input class="type">class="kw">color       InnerRange      =Indigo;        class=class="str">"cmt">// 内部范围
class="kw">input class="type">class="kw">color       OuterRange      =Magenta;       class=class="str">"cmt">// 外部范围
class="kw">input class="type">class="kw">color       ControlPoint    =Orange;        class=class="str">"cmt">// 控制点
class="kw">input class="type">bool        ShowValue       =true;          class=class="str">"cmt">// 显示值
class="type">int OnInit()
  {
class=class="str">"cmt">//---
class=class="str">"cmt">// 在开始计算前,检查交易品种的同步情况
   class="type">int err=class="num">0;
   while(!(class="type">bool)SeriesInfoInteger(Symbol(),class="num">0,SERIES_SYNCRONIZED) && err<AMOUNT_OF_ATTEMPTS)
     {
       Sleep(class="num">500);
       err++;
   }
class=class="str">"cmt">// CExpert 类的初始化
   ExtExpert.RangePercent=RangePercent;
   ExtExpert.InnerRange=InnerRange;
   ExtExpert.OuterRange=OuterRange;
   ExtExpert.ControlPoint=ControlPoint;
   ExtExpert.ShowValue=ShowValue;
   ExtExpert.DaysForCalculation=DaysForCalculation;
   ExtExpert.DayTheHistogram=DayTheHistogram;
   ExtExpert.Init();
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  CExpert类                                                        |
class=class="str">"cmt">//|  类的描述                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CExpert
  {
class="kw">public:
   class="type">int            DaysForCalculation; class=class="str">"cmt">// 计算的天数(-1代表所有)
   class="type">int            DayTheHistogram;    class=class="str">"cmt">// 直方图的天数
   class="type">int            RangePercent;       class=class="str">"cmt">// 百分比范围
   class="type">class="kw">color          InnerRange;         class=class="str">"cmt">//  范围内的颜色
   class="type">class="kw">color          OuterRange;         class=class="str">"cmt">// 范围外的颜色
   class="type">class="kw">color          ControlPoint;       class=class="str">"cmt">// 控制点(POC)的颜色
   class="type">bool           ShowValue;          class=class="str">"cmt">// 显示值
class="kw">private:
   CList          list_object;        class=class="str">"cmt">// CObject类的动态列表
   class="type">class="kw">string         name_symbol;        class=class="str">"cmt">// 交易品种名称
   class="type">int            count_bars;         class=class="str">"cmt">// 日线数量
   class="type">bool           event_on;           class=class="str">"cmt">// 事件处理标识
class="kw">public:

◍ EA 骨架与日线历史加载的实作细节

要在 MT5 里搭一个价格直方图 EA,先得把 CExpert 派生类的生命周期方法定清楚。构造函数留空,析构函数直接调 Deinit(REASON_CHARTCLOSE) 做资源回收;Init() 返回 bool 供外部判断启动是否成功,Deinit(const int reason) 接收退出原因,OnTick / OnEvent / OnTimer 则分别承接报价、图表交互和定时器事件。 日线历史不是一次就能拿稳的。下面这段 do-while 会用 Bars(NULL,PERIOD_D1) 算可用天數,若 DaysForCalculation+1 小于总 bars 就只取前者,否则取全量;随后 CopyTime 拉取日线开盘时间,失败就 Sleep(1) 重试,直到 rates_total>0 或 err 达到 AMOUNT_OF_ATTEMPTS。若重试耗尽仍拿不到,Print 报错并把 name_symbol 置 NULL 后返回 false。 拿到数据后建一个 CProgressBar 盯加载进度,Maximum 设为 rates_total。随后 for 循环里逐个 new CPriceHistogram,配置 ShowLevel、ColorPOCs、ColorInner、ColorOuter、RangePercent 等参数,再 Init 当根日线区间;成功就塞进 list_object,失败立刻 delete 防内存泄漏,同时 progress.Value(i) 推进进度条。 定时器靠 EventSetTimer(秒数) 起、EventKillTimer() 收,别漏了 Kill,否则 EA 卸載后定时器仍可能空跑。外汇与贵金属波动剧烈、杠杆风险高,这类历史加载逻辑在实盘前务必在策略测试器用真实点差验证。

MQL5 / C++
   class=class="str">"cmt">// 类的构造函数
                CExpert();
   class=class="str">"cmt">// 类的析构函数
                ~CExpert(){Deinit(REASON_CHARTCLOSE);}
   class=class="str">"cmt">// 初始化方法
    class="type">bool              Init();
   class=class="str">"cmt">// 反初始化方法
    class="type">void              Deinit(const class="type">int reason);
   class=class="str">"cmt">// OnTick方法
    class="type">void              OnTick();
   class=class="str">"cmt">// OnChartEvent()事件处理方法
    class="type">void              OnEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam);
   class=class="str">"cmt">// OnTimer() 事件处理方法
    class="type">void              OnTimer();
};
class="type">int err=class="num">0;
do
  {
   class=class="str">"cmt">// 计算历史数据中的可用天数
   count_bars=Bars(NULL,PERIOD_D1);
   if(DaysForCalculation+class="num">1<count_bars)
      count=DaysForCalculation+class="num">1;
   else
      count=count_bars;
   if(DaysForCalculation<=class="num">0) count=count_bars;
   rates_total=CopyTime(NULL,PERIOD_D1,class="num">0,count,day_time_open);
   Sleep(class="num">1);
   err++;

}
while(rates_total<=class="num">0 && err<AMOUNT_OF_ATTEMPTS);
if(err>=AMOUNT_OF_ATTEMPTS)
  {
   Print("There is no accessible history PERIOD_D1");
   name_symbol=NULL;
   class="kw">return(false);
  }
class=class="str">"cmt">// 我们在图标上创建进度条,来观察加载进度
CProgressBar  *progress=new CProgressBar;
progress.Create(class="num">0,"Loading",class="num">0,class="num">150,class="num">20);
progress.Text("Calculation:");
progress.Maximum=rates_total; 
class=class="str">"cmt">// 在本循环中,创建CPriceHistogram的对象并初始化
class=class="str">"cmt">// 以及添加到对象列表中
for(class="type">int i=class="num">0;i<rates_total;i++)
  {
    CPriceHistogram  *hist_obj=new CPriceHistogram();
    class=class="str">"cmt">//               hist_obj.StepHistigram(step);
    class=class="str">"cmt">// 设置标志显示文本标签
    hist_obj.ShowLevel(ShowValue);
    class=class="str">"cmt">// 设置POCs颜色
    hist_obj.ColorPOCs(ControlPoint);
    class=class="str">"cmt">// 设置范围内的颜色
   hist_obj.ColorInner(InnerRange);
    class=class="str">"cmt">// 设置范围外的颜色
    hist_obj.ColorOuter(OuterRange);
    class=class="str">"cmt">// 设置范围百分比
    hist_obj.RangePercent(RangePercent);
    class=class="str">"cmt">//  hist_obj.ShowSecondaryPOCs((i>=rates_total-DayTheHistogram),PeriodSeconds(PERIOD_D1));
    if(hist_obj.Init(day_time_open[i],day_time_open[i]+PeriodSeconds(PERIOD_D1),(i>=rates_total-DayTheHistogram)))
       list_object.Add(hist_obj);
    else
       class="kw">delete hist_obj; class=class="str">"cmt">// 如果有错误则删除对象
    progress.Value(i);
  };
EventSetTimer(time in seconds);
EventKillTimer(); 

用类把日内价格直方图管起来

在 MT5 里做价格分布分析,最忌讳把绘图逻辑和数值计算搅在一根脚本里。把单日直方图封装成一个 CPriceHistogram 类,后面要叠加多日、做回看扫描都只是调对象的事。 这个类从 CObject 继承,私有段先存了当天高低点 high_day / low_day,以及 Init_passed 这个初始化闸门——没过初始化就别想刷新 POC,能少踩很多空指针坑。 它用两个 CList 分别装内部行和外部行(ListHistogramInner / ListHistogramOuter),再把 POC 主线和次级 POC 线做成 CChartObjectTrend 指针。实盘里黄金 M15 一天能堆出 40~60 根内部行,列表结构比数组好扩。 对外暴露的接口很直白:Init() 吃开盘收盘时间和是否画直方图的开关;ShowLevel(bool) 控制 POC 水平线显隐,且只在 Init_passed 后调 RefreshPOCs();ShowHistogram() 同理管柱子本身。 次级 POC(second_poc_top / second_poc_bottom)和主 poc_value 都留了 double 字段,配合 range_percent 做百分比边界。外汇和贵金属波动大,这类分布工具只帮你看筹码重心,进场仍属高风险,别当信号神灯。

MQL5 / C++
class CPriceHistogram : class="kw">public CObject
  {
class="kw">private:
   class=class="str">"cmt">// 类的变量
   class="type">class="kw">double            high_day,low_day;
   class="type">bool              Init_passed;        class=class="str">"cmt">// 初始化是否通过的标识
   CChartObjectTrend *POCLine;
   CChartObjectTrend *SecondTopPOCLine,*SecondBottomPOCLine;
   CChartObjectText  *POCLable;
   CList             ListHistogramInner; class=class="str">"cmt">// 存储内部行的列表
   CList             ListHistogramOuter;class=class="str">"cmt">// 存储外部行的列表
   class="type">bool              show_level;        class=class="str">"cmt">// 显示水平的值
   class="type">bool              virgin;            class=class="str">"cmt">// 原始的
   class="type">bool              show_second_poc;   class=class="str">"cmt">// 显示次级POCs水平
   class="type">class="kw">double            second_poc_top;    class=class="str">"cmt">// 顶部次级POC水平值
   class="type">class="kw">double            second_poc_bottom; class=class="str">"cmt">// 底部次级POC水平值
   class="type">class="kw">double            poc_value;         class=class="str">"cmt">// POC水平值
   class="type">class="kw">color             poc_color;         class=class="str">"cmt">// POC水平颜色
   class="type">class="kw">datetime          poc_start_time;
   class="type">class="kw">datetime          poc_end_time;
   class="type">bool              show_histogram;    class=class="str">"cmt">// 显示直方图 
   class="type">class="kw">color             inner_color;       class=class="str">"cmt">// 直方图内部颜色
   class="type">class="kw">color             outer_color;       class=class="str">"cmt">// 直方图外部颜色
   class="type">uint              range_percent;     class=class="str">"cmt">// 百分比范围
   class="type">class="kw">datetime          time_start;        class=class="str">"cmt">// 构建的开始时间
   class="type">class="kw">datetime          time_end;          class=class="str">"cmt">// 构建的最后时间
class="kw">public:
   class=class="str">"cmt">// 类的构造函数
                     CPriceHistogram();
   class=class="str">"cmt">// 类的析构函数
                    ~CPriceHistogram(){Delete();}
   class=class="str">"cmt">// 类的初始化
   class="type">bool              Init(class="type">class="kw">datetime time_open,class="type">class="kw">datetime time_close,class="type">bool showhistogram);
   class=class="str">"cmt">// 水平值
   class="type">void              ShowLevel(class="type">bool show){show_level=show; if(Init_passed) RefreshPOCs();}
   class="type">bool              ShowLevel(){class="kw">return(show_level);}
   class=class="str">"cmt">// 显示直方图
   class="type">void              ShowHistogram(class="type">bool show);
   class="type">bool              ShowHistogram(){class="kw">return(show_histogram);}
   class=class="str">"cmt">// 显示次级POC水平

「把 POC 控制接口和 M1 数据抓取拆开看」

这个类把 POC 的显示开关、颜色、百分比区间都做成了成对的 setter/getter。比如 ShowSecondaryPOCs(bool) 负责写开关并触发 RefreshPOCs(),无参版本则返回当前状态;ColorPOCs() 与 ColorInner()/ColorOuter() 同理,只是后两者在原文里函数体被略去,只留了声明。 RangePercent(uint) 值得单独说:它改的是 range_percent 成员,并且直接调 calculationPOCs() 重算,而不是只刷新画线。这意味着你动一次百分比参数,背后是整套直方图重新统计,不是简单挪根线。 计算核心落在 calculationPOCs() 里,它用 do 循环从 time_start 到 time_end 拉 M1 数据。CopyTime 取每根柱开盘时间,CopyHigh/CopyLow 取高低价;任意一次返回负值就打错误日志。注意这里写死 PERIOD_M1,所以你的直方图粒度被锁在 1 分钟级,想切 H1 得改源码。 开 MT5 随便挂个品种,把 range_percent 从默认调到 70 看重算耗时,能直观感受这套接口的性能边界。外汇和贵金属波动大,POC 仅是概率集中区,实盘须自担高风险。

MQL5 / C++
class="type">void ShowSecondaryPOCs(class="type">bool show){show_second_poc=show;if(Init_passed)RefreshPOCs();}
class="type">bool ShowSecondaryPOCs(){class="kw">return(show_second_poc);}
class=class="str">"cmt">// 设置POC水平颜色
class="type">void ColorPOCs(class="type">class="kw">color col){poc_color=col; if(Init_passed)RefreshPOCs();}
class="type">class="kw">color ColorPOCs(){class="kw">return(poc_color);}
class=class="str">"cmt">// 设置直方图的内部颜色
class="type">void ColorInner(class="type">class="kw">color col);
class="type">class="kw">color ColorInner(){class="kw">return(inner_color);}
class=class="str">"cmt">// 设置直方图的外部颜色
class="type">void ColorOuter(class="type">class="kw">color col);
class="type">class="kw">color ColorOuter(){class="kw">return(outer_color);}
class=class="str">"cmt">// 设置百分比范围
class="type">void RangePercent(class="type">uint percent){range_percent=percent; if(Init_passed)calculationPOCs();}
class="type">uint RangePercent(){class="kw">return(range_percent);}
class=class="str">"cmt">// 返回POC水平原始值
class="type">bool VirginPOCs(){class="kw">return(virgin);}
class=class="str">"cmt">// 返回直方图的构建时间
class="type">class="kw">datetime GetStartDateTime(){class="kw">return(time_start);}
class=class="str">"cmt">// 更新POC水平
class="type">bool RefreshPOCs();
class="kw">private:
class=class="str">"cmt">// 计算直方图和POC水平
class="type">bool calculationPOCs();
class=class="str">"cmt">// 删除类
class="type">void Delete();
};
class="type">bool CPriceHistogram::Init(class="type">class="kw">datetime time_open,class="type">class="kw">datetime time_close,class="type">bool showhistogram)
class="type">bool CPriceHistogram::calculationPOCs()
class=class="str">"cmt">// 从 time_start 到 time_end 中获取数据
class="type">int err=class="num">0;
do
  {
  class=class="str">"cmt">//--- 复制每个柱形的开盘时间
  rates_time=CopyTime(NULL,PERIOD_M1,time_start,time_end,iTime);
  if(rates_time<class="num">0)
     PrintErrorOnCopyFunction("CopyTime",_Symbol,PERIOD_M1,GetLastError());
  class=class="str">"cmt">//--- 复制每个柱形的最高价
  rates_high=CopyHigh(NULL,PERIOD_M1,time_start,time_end,iHigh);
  if(rates_high<class="num">0)
     PrintErrorOnCopyFunction("CopyHigh",_Symbol,PERIOD_M1,GetLastError());
  class=class="str">"cmt">//--- 复制每个柱形的最低价
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到由价格直方图生成的平衡区间提示,把重复劳动交给小布,你专注决策。

常见问题

不是。市场概况包含更完整的钟形曲线与成交量分布逻辑,本文实现的 price histogram 是简化版,只基于价格与时间关系绘制。
日线聚合了多数参与者的交易背景,供求均势或失衡的起点在日线级别更不易被噪音干扰,便于识别转变发生的大致位置。
可以。小布盯盘对应品种页已内置该类 AIGC 诊断,会自动标注由直方图推导的均衡偏移,省去手动跑脚本。
可能反映价格变动中市场参与者信心分化,中心偏移之外出现次级聚集,往往对应趋势中的中继或分歧,需结合时段判断概率。