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

9
5 292

概论

直方图允许研究人员根据其浸透到某个 (预判) 间隔的频率来直观评估统计数据组的分布。

直方图及其在统计数据分析中的使用是一个研究充分的主题, 已发表了多篇文章 [1, 2, 3, 4, 5, 6, 7], 且在代码库中有大量例程 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。不过, 采用的算法均基于使用指标缓冲区或数组。在本文中, 我们考虑无需复杂的计算、排序、抽样等即能建立不同市场特征统计分布的可能性。为了实现这一点, 我们将使用 "图形" 内存 — 图形对象属性的部分存储。由于在绘制自定义直方图时我们需要 图形对象, 我们可以使用它们的 "隐藏" 能力和丰富的函数来满足需求。

文章的目标是为标准统计问题提供简单的解决方案。本文主要关注统计分布的可视化及其基本特征。我们不打算讨论直方图的释义及其实际优势。

直方图绘制基础

直方图是频率的柱线图。其中一根数轴代表变量值, 另一根数轴代表这些值出现的频率。每根柱线的高度代表那些间隔等于列宽的数值的频率 (数量)。这些示意图通常水平显示, 即, 变量值位于水平轴上, 而频率 — 坐落于垂直轴。使用直方图来表示研究的数据, 使得统计数据更直观, 并且更容易理解和分析。

在本文中, 我们将关注变化序列的垂直直方图: 分析参数的价格值将按照升序位于垂直轴上, 而频率位于水平轴上 (图例. 1)。传入的价格数据在当前柱线上分布并分组, 并且可以从左侧、右侧或两侧同时相对于其数轴显示。

图例. 1. 买卖价格分布的垂直直方图 

图例. 1. 买卖价格分布的垂直直方图

让我们来研究一个具体的任务:

  • 绘制买卖价格分布直方图; 
  • 将采购价数据定位在当前柱线的右侧, 而供给价 — 位于左侧;
  • 当新的即时报价抵达, 计算每笔入价值的频率, 即, 直方图间隔等于当前品种的最小点数大小。

现在, 我们令条件更复杂: 无指标缓冲区, 数组或结构。

如何解决这个问题? 

首先, 我们要确定在哪里保存每个直方图列的累积频率。即便在图例. 1 上, 我们可以看到, 可能存在不确定数量的直方图条。首先进入脑海的是使用动态数组, 因为在价格图表的选定时间间隔上, 可能的价格范围 (柱线数量) 不可预知。但数组不允许这种问题条件。

其次, 我们应该解决搜索和排序任务: 何处以及如何搜索重新计算和重绘直方图的数据。

结果是 MQL5 语言开发人员已经创建了必要的 (和相当强大的) 功能。它基于使用图形对象功能组的 "隐藏" (非显性) 特征。每个对象都有自己的 属性 — 此变量与对象一起创建, 并用于存储各类多个参数。一些属性在使用时可以完全不同于它们的设计用途, 同时保持功能完整。我们将这些属性称之为 "图形" 内存。换言之, 如果您需要保存一个变量并接收其数值, 创建一个图形对象并将变量值赋给一个特定的属性。

所以, 图形对象属性 实际上可作为更有效的终端全局变量。全局变量自其最后访问以来可在客户终端中存在四个星期, 且随后被自动删除。图形内存在图形对象被移除之前会一直存在, 从而为我们提供了大量的机会。 

我们可以在图形内存中使用哪些图形对象属性

当创建图形对象时, 我们应为它分配一个独有的名称。该名称是可以由子串组成的文本字符串,而子字符串可以包含格式化的基本数据类型:整数,布尔值,浮点数,颜色,日期和时间。因此, OBJPROP_NAME 可保存主要用来读取数据的变量。

另外其它可用的属性是 OBJPROP_TEXT 对象描述。这是一个文本字符串, 与前一个属性相比具有更大可能性的。在属性字段中可以读取和写入变量。

任何图形对象均有其自己的坐标: 价格 OBJPROP_PRICE 和时间 OBJPROP_TIME。它们也可以用在图形内存中。

让我们回到我们的目标。频率将存储在 OBJPROP_TEXT 属性, 而供给价和采购价 — 在 OBJPROP_NAME 中。创建对象和收集频率的函数代码如下:

void DrawHistogram(bool draw,     // 向左侧或右侧绘制直方图                    string h_name, // 对象名称的独有前缀                    double price,  // 价格 (分析参数)                    datetime time, // 将直方图绑定到当前柱线                    int span,      // 已分析参数的数位容量                    int swin=0)    // 直方图窗口   {    double y=NormalizeDouble(price,span);    string pfx=DoubleToString(y,span); // 如果 draw=true, 向右侧绘制直方图    if(draw)      {       string name="+ "+h_name+pfx;                   // 对象名称: 前缀+价格       ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // 创建对象       ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // 设置对象颜色       ObjSet;                                                // 宏代码缩写       if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)         {// 如果结果价格首次进入样本          ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // 价格频率为 1          ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // 定义时间坐标         }       else         {// 如果结果价格并非首次进入样本          string str=ObjectGetString(0,name,OBJPROP_TEXT);    // 获取属性值          string strint=StringSubstr(str,1);                  // 获得子字符串          long n=StringToInteger(strint);                     // 获取频率用于进一步计算          n++;                                                // 数值递增 1          ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // 新数值写入到属性          ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//定义时间坐标         }      } // 如果 draw=false, 向左侧写入直方图    if(!draw)      {       string name="- "+h_name+pfx;       ObjectCreate(0,name,OBJ_TREND,swin,time,y);       ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);       ObjSet;       if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)         {          ObjectSetString(0,name,OBJPROP_TEXT,"*1");          ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);         }       else         {          string str=ObjectGetString(0,name,OBJPROP_TEXT);          string strint=StringSubstr(str,1);          long n=StringToInteger(strint);          n++;          ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);          ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);         }      }    ChartRedraw();   } 

函数由两个类似的部分组成, 分别作用于供给价和采购价。因此, 注释仅存在于第一个模块中。

您也许会问, 如果在 OBJPROP_PRICE 属性可用, 则对象名中的价格翻倍说明什么?

我们来多说一点。当新价格抵达时, 我们应为其定义适当的列, 并相应地增加频率值。 如果我们使用来自其本地属性的价格坐标, 我们务必通过所有图形对象传递, 请求属性值并将其与接收的价格进行比较。只有在这之后, 我们才能将新值写入适当的列。然而,比较实际的 double-类型数字是相当一个扯后腿的任务, 大大降低了算法的效率。。更优雅的解决方案是在新的价格值到达后, 将新频率直接写入必要的目的地。我们如何实现呢?当我们创建一个名称类似于已存在对象的新图形对象时, 新对象不会被创建, 且属性字段不会设置为零。换言之, 我们创建对象时忽略了它们将被加倍的事实。不需要额外的检查, 因为不需要创建副本和克隆。终端和图形对象功能将负责于此。我们只需要定义价格是否是首次进入样本。为此,请在 DrawHistogram() 函数的 OBJPROP_TEXT 对象属性中使用星号 (*) 前缀。没有星号表示首次进入样本的价格。实际上, 当我们创建一个新对象时, 该字段为空。具有指定前缀的频率值将在后续调用期间存储在那里。

接下来, 让我们向右侧平移直方图。当出现新的柱线时, 图表向左移动, 但是我们需要在当前柱线上显示直方图。换言之, 它应该在相反的方向上移动。以下是我们如何实现:

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

全部就这些了。供给价和采购价直方图绘制在图表上, 而程序未使用单独数组或指标缓冲区。解决方案的完整代码如下。


视频展示了使用图形存储器解决类似问题的现成方案。 代码本身可以在 代码库: "供给价和采购价直方图" 指标中找到。

在主窗口中绘制直方图

现在我们已研究过使用图形存储器的功能编程, 我们来绘制一些标准指标的直方图。绘图原理与已研究的原理类似。不过, 会使用当前柱线上的指标值代替供给价和采购价。

     1. iMA 指标。我们采用具有不同平均周期的两个指标来构建直方图。代码在此:

input int               period_MA1=20;       // iMA1 的均化周期 input int               period_MA2=14;       // iMA2 的均化周期 //---- 指标缓存区 double      MA1[]; double      MA2[]; //---- 指标句柄 int         iMA1_handle; int         iMA2_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iMA1_handle=iMA(_Symbol,_Period,period_MA1,0,MODE_SMA,PRICE_CLOSE);    iMA2_handle=iMA(_Symbol,_Period,period_MA2,0,MODE_SMA,PRICE_CLOSE);    ArraySetAsSeries(MA1,true);    ArraySetAsSeries(MA2,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   {    ArraySetAsSeries(time,true);       CopyBuffer(iMA1_handle,0,0,1,MA1);    CopyBuffer(iMA2_handle,0,0,1,MA2);    DrawHistogram(true,"iMA("+(string)period_MA1+")=",MA1[0],time[0],_Digits);    DrawHistogram(false,"iMA("+(string)period_MA2+")=",MA2[0],time[0],_Digits); //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar) // 定义新柱线抵达      {       prevTimeBar=time[0];       // 通过所有图形对象传递       for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--)         {          string obj_name=ObjectName(0,obj,-1,-1);               // 获取已发现对象的名称          if(obj_name[0]==R)                                     // 搜索直方图元素前缀            {                                                    // 如果已发现直方图元素             ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // 设置新坐标值                              0,time[0]);                        // 对于锚点 "0"             string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// 从对象属性中读取变量             string strint=StringSubstr(str,1);                  // 从接收的变量中分离出一个子字符串             long n=StringToInteger(strint);                     // 将字符串转换为长整型变量             ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // 计算新坐标值                              1,time[0]+hsize*n);                // 对于锚点 "1"             ObjectSetInteger(0,obj_name,OBJPROP_COLOR,                              color_R_passive);                  // 改变已移动直方图元素的颜色            }          if(obj_name[0]==L)            {             ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]);             string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);             string strint=StringSubstr(str,1);             long n=StringToInteger(strint);             ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n);             ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive);            }         }       ChartRedraw();      }    return(rates_total);   } 

图例. 2. 两条 iMA 指标的直方图

图例. 2. 两条 iMA 指标的直方图 

     2. iBands 指标。 我们来绘制指标的上边界 (UPPER_BAND) 和下边界 (LOWER_BAND) 的直方图。简化代码如下所示。

input int   period_Bands=14;           // iBands 的均化周期 //---- 指标缓存区 double      UPPER[]; double      LOWER[]; //---- 指标句柄 int         iBands_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iBands_handle=iBands(_Symbol,_Period,period_Bands,0,5.00,PRICE_CLOSE);    ArraySetAsSeries(UPPER,true);    ArraySetAsSeries(LOWER,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   { //---    ArraySetAsSeries(time,true);       CopyBuffer(iBands_handle,1,0,1,UPPER);    CopyBuffer(iBands_handle,2,0,1,LOWER);    DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);    DrawHistogram(false,"iBands(LOWER)=",LOWER[0],time[0],_Digits); //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar)      {      ...      }    return(rates_total);   } 

图例. 3. iBands 指标的布林带直方图

图例. 3. iBands 指标的布林带直方图 

如果我们使用所有三个指标缓冲区 (0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND), 我们可以得到非常有趣的布林带指标直方图。我们将上、下边带的直方图放置到当前柱线的右侧, 而主线直方图 — 放于零号柱线左侧。结果如下图所示:

图例. 4. 使用三个指标缓冲区的布林带直方图

图例. 4. 使用三个指标缓冲区的布林带直方图  

直方图版本的代码如下。注意, 两个直方图 (对于指标的上、下边界) 实际上显示在右侧。在视觉上, 它们是一个单一的图示。

input int   period_Bands=20;       // iBands 的均化周期 //---- 指标缓存区 double      BASE[]; double      UPPER[]; double      LOWER[]; //---- 指标句柄 int         iBands_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iBands_handle=iBands(_Symbol,_Period,period_Bands,0,10.00,PRICE_CLOSE);    ArraySetAsSeries(BASE,true);    ArraySetAsSeries(UPPER,true);    ArraySetAsSeries(LOWER,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   { //---    ArraySetAsSeries(time,true);       CopyBuffer(iBands_handle,0,0,1,BASE);    CopyBuffer(iBands_handle,1,0,1,UPPER);    CopyBuffer(iBands_handle,2,0,1,LOWER);    DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);    DrawHistogram(true,"iBands(LOWER)=",LOWER[0],time[0],_Digits);    DrawHistogram(false,"iBands(LOWER)=",BASE[0],time[0],_Digits); //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar)      {      ...      }    return(rates_total);   } 

迄今为止, 我们实验了变化序列, 其中分析值 (变体) 的范围等于当前品种的最小点数 (_Point) 大小。换言之, 所有价格值都绘制为单独的直方图列。

应该强调如何使用终端的内置功能来优雅和无缝地对变化序列进行排序 (排位)。为此并非编写一行代码即可。

现在, 我们使用单个变量和图形对象的能力来修改具有多个变量值的分布范围。上述的 DrawHistogram() 函数 span 输入 — 分析参数的数位容量。通过数位容量的变化, 我们能够改变计算直方图频率的间隔。由于价格包含在图形对象名称中, 因此在创建和调整对象属性时会自动执行选择。了解如何使用无需编程的图形内存轻松解决排序和分组任务。

图例. 5. 具有增加列间隔的供给价和采购价的直方图

图例. 5. 具有增加列间隔的供给价和采购价的直方图 

因此, 我们可以在主图表窗口中轻松绘制直方图。现在, 我们来看看如何在附加图表窗口中绘制类似的图表。

在其它窗口中显示直方图

在附加窗口中创建统计分布的原理类似于主窗口。不同之处在于分析变量的值可以是负的。此外, 它们可以具有任意数位容量。在主窗口中, 可以使用预定义的 _Digits 变量获取价格刻度。在附加窗口中, 变量可正可负。此外, 数位的数量不受限制。当设置 DrawHistogram() 函数的输入参数来构建直方图时, 应考虑这一点。此外, 请务必设置附加窗口索引。省缺情况下, 在函数中使用等于零的主窗口。

附加窗口中的直方图示例

消化信息的最好方式是使用示例。我们来研究几个例子, 在标准技术指标的附加窗口中绘制直方图。

     1. iChaikin 指标。

图例. 6. Chaikin 振荡器直方图

图例. 6. Chaikin 振荡器直方图

指标的简化代码如下:

input int   period_fast=3;       // iChaikin 的快速均化周期 input int   period_slow=10;      // iChaikin 的慢速均化周期 //---- 指标缓存区 double      CHO[]; //---- 指标句柄 int         iChaikin_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iChaikin_handle=iChaikin(_Symbol,_Period,period_fast,period_slow,MODE_SMA,VOLUME_TICK);    ArraySetAsSeries(CHO,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   { //---    ArraySetAsSeries(time,true);       CopyBuffer(iChaikin_handle,0,0,2,CHO);    DrawHistogram(true,"iChaikin=",CHO[0],time[0],0,1);    //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar)      {      ...      }    return(rates_total);   } 

     2. iCCI 指标。

图例. 7. 商品通道指数直方图

图例. 7. 商品通道指数直方图

简化代码:

input int   period_CCI=14;             // iCCI 的均化周期 //---- 指标缓存区 double      CCI[]; //---- 指标句柄 int         iCCI_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iCCI_handle=iCCI(_Symbol,_Period,period_CCI,PRICE_CLOSE);    ArraySetAsSeries(CCI,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   { //---    ArraySetAsSeries(time,true);       CopyBuffer(iCCI_handle,0,0,2,CCI);    DrawHistogram(true,"iCCI=",CCI[0],time[0],0,1);    //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar)      {      ...      }    return(rates_total);   } 

     3. 指标: iEnvelopesiATR, iMACD。这三款指标的组合直方图。每款指标显示在单独的窗口中。

图例. 8. 三款指标的直方图: iEnvelopes, iATR 和 iMACD

图例. 8. 三款指标的直方图: iEnvelopes, iATR 和 iMACD 

图例. 8. 上直方图集合的简约代码

input int   period_Envelopes=14;       // iEnvelopes 的均化周期 input int   period_ATR=14;             // iATR 的均化周期 input int   period_fast=12;            // iMACD 的快速均化周期 input int   period_slow=26;            // iMACD 的慢速均化周期 //---- 指标缓存区 double      UPPER[]; double      LOWER[]; double      ATR[]; double      MAIN[]; double      SIGNAL[]; //---- 指标句柄 int         iEnvelopes_handle; int         iATR_handle; int         iMACD_handle; //+------------------------------------------------------------------+ //| 自定义指标初始化函数                                              | //+------------------------------------------------------------------+ int OnInit()   {    ObjectsDeleteAll(0,-1,-1);    ChartRedraw();    iEnvelopes_handle=iEnvelopes(_Symbol,_Period,period_Envelopes,0,MODE_SMA,PRICE_CLOSE,0.1);    iATR_handle=iATR(_Symbol,_Period,period_ATR);    iMACD_handle=iMACD(_Symbol,_Period,period_fast,period_slow,9,PRICE_CLOSE);    ArraySetAsSeries(UPPER,true);    ArraySetAsSeries(LOWER,true);    ArraySetAsSeries(ATR,true);    ArraySetAsSeries(MAIN,true);    ArraySetAsSeries(SIGNAL,true);    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| 自定义指标迭代函数                                                | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,                 const int prev_calculated,                 const datetime &time[],                 const double &open[],                 const double &high[],                 const double &low[],                 const double &close[],                 const long &tick_volume[],                 const long &volume[],                 const int &spread[])   { //---    ArraySetAsSeries(time,true);       CopyBuffer(iEnvelopes_handle,0,0,1,UPPER);    CopyBuffer(iEnvelopes_handle,1,0,1,LOWER);    CopyBuffer(iATR_handle,0,0,1,ATR);    CopyBuffer(iMACD_handle,0,0,1,MAIN);    CopyBuffer(iMACD_handle,1,0,1,SIGNAL);    DrawHistogram(true,"iEnvelopes.UPPER=",UPPER[0],time[0],_Digits);    DrawHistogram(false,"iEnvelopes.LOWER=",LOWER[0],time[0],_Digits);    DrawHistogram(true,"iATR=",ATR[0],time[0],_Digits+1,1);    DrawHistogram(true,"iMACD.SIGNAL=",SIGNAL[0],time[0],_Digits+1,2);    DrawHistogram(false,"iMACD.MAIN=",MAIN[0],time[0],_Digits+1,2); //--- 将直方图平移到新的柱线    if(time[0]>prevTimeBar)      {      ...      }    return(rates_total);   } 

在研究了依据市场参数绘制的各种直方图的统计分布之后, 我们可以得出结论:

  • МТ5 交易终端功能;
  • MQL5 图形对象的功能;
  • 图形内存 (图形对象鲜为人知的功能);
  • 函数 DrawHistogram() 例程

令我们可以计算并可视化多个指标的累积直方图 — 从标准到自定义。然而, 这不足以对市场参数进行更详细的分析。

统计分布的数值参数

本文的第一部分论及适合程序员新手的直方图绘制方法。所提供的示例可轻松地投影到与统计分布及其可视化相关的一大类问题上。不过, 为了进行更深入的直方图分析, 我们需要最通用和最先进的编程方法, 例如 OOP。我们来定义读者最感兴趣的统计分布参数。

首先, 我们对变化序列的数值参数感兴趣:

  • 算术平均值 (平均频率);
  • 加权算术平均值;
  • 方差;
  • 标准偏差。

其次, 我们需要将它们可视化。当然, 所有这些都可以使用过程编程风格来实现, 但是我们将在本文中应用面向对象的编程来解决任务。

CHistogram 类

类的功能允许在图表上显示直方图和主要的统计分布参数。我们来研究一下基本的方法。

方法: CHistogram 类构造器。

初始化类实例。 

void CHistogram(
   string name,                     // 独有名称前缀
   int    hsize,                    // 图刻度
   int    width,                    // 直方图列宽
   color  active,                   // 主动线颜色
   color  passive,                  // 被动线颜色
   bool   Left_Right=true,          // 左侧=false 或 右侧=true
   bool   relative_frequency=false, // 相对或绝对直方图
   int    sub_win=0                 // 直方图窗口索引
   );

参数:

 name

    [in] 所有直方图列的独有名称前缀。 

 hsize

    [in] 直方图刻度。

 width

    [in] 直方图列宽。

 active

    [in] 在当前柱线上更新的直方图列的颜色。

 passive

    [in] 在当前柱线上未更新的直方图列的颜色。

 Left_Right=true

    [in] 直方图方向。false — 位于当前柱线左侧的直方图, true — 位于右侧。

 relative_frequency=false

    [in] 频率计量方法。false — 绝对频率值, true — 相对频率值。

 sub_win=0

    [in] 直方图窗口索引。0 — 主图表窗口。 

返回值:

 无返回值。如果成功, 将创建具有指定参数的类实例。

 

方法: DrawHistogram 显示。

显示直方图列: 创建新列, 编辑现有列, 将频率值保存在图形内存中。在当前柱线上显示直方图。

void DrawHistogram(
   double price,  // 变量值
   datetime time  // 当前柱线时间
   );

参数:

 price

    [in] 分析市场参数变量值。

 time

    [in] 当前柱线的时间。落在柱线上的直方图轴。  

返回值:

 无返回值。如果成功, 则创建新的直方图柱线, 或已创建的现有直方图柱线。如果出现新的柱线, 则平移直方图, 使轴位于当前柱线上。 

 

方法: 计算 HistogramCharacteristics 直方图参数。 

返回 sVseries 类型变量中计算的变化序列参数。

sVseries HistogramCharacteristics();

参数:

 无输入参数。

返回值:

 如果成功, 将返回 sVseries 类型的变量值。

 

用于接收直方图参数当前值的结构 (sVseries)。

用于存储统计分布参数最后值的结构。此结构设计用于接收最需要的变化序列数据。 

struct sVseries
  {
   long     N;    // 总观测值
   double   Na;   // 频率平均值
   double   Vmax; // 最大变化值
   double   Vmin; // 最小变化值
   double   A;    // 序列幅度
   double   Mean; // 加权算术平均值
   double   D;    // 方差
   double   SD;   // 标准偏差
  };

sVseries-类型变量可令您接收变化序列的主要参数, 显示作为单次 HistogramCharacteristics() 函数调用的直方图。

 

方法: DrawMean 数值可视化。 

显示图表上变化序列的加权算术平均值。 

void DrawMean(
   double coord,     // 加权算术平均值
   datetime time,    // 当前柱线时间
   bool marker=false,// 显示/隐藏记号
   bool save=false   // 在历史中保存数值
   );

参数:

 coord

    [in] 加权算术平均值。

 time

    [in] 当前柱线的时间。加权算术平均值显示在当前柱线。

 marker=false

    [in] 显示/隐藏图表记号false — 隐藏记号, true — 在图表上显示记号。

 save=false

    [in]  在历史中保存加权算术平均值。false — 隐藏值, true — 在图表上显示数值。

返回值:

如果成功, 图表显示对应于加权算术平均值的水平线。 

 

方法: DrawSD 标准偏差可视化。

将标准偏差值显示为矩形, 其宽度与平均频率匹配, 高度和标准差匹配, 依据加权算术平均值向上和向下绘制。

void DrawSD(
   sVseries &coord,        // sVseries-类型变量
   datetime time,          // 当前柱线时间
   double deviation=1.0,   // 偏差
   color clr=clrYellow     // 显示颜色
   );

参数:

 coord

    [in] sVseries-类型变量值。

 time

    [in] 当前柱线的时间。

 deviation=1.0

    [in] 标准偏差增加率。

 clr=clrYellow

    [in] 矩形可视化的颜色标准偏差。

返回值

 如果成功, 则将加权算术平均值的矩形可视化标准偏差显示在图表上。

  

使用 CHistogram 类绘制直方图

我们来绘制供给价和采购价统计分布的直方图, 以及 ATRMACD 指标 (图例. 9)。与前面的例子不同, 这个类允许绘制相对图。除了直方图, 我们还创建了加权算术平均值和标准偏差的图形表述。平均值使用指标缓冲区保存。在下面的截图中, 标准偏差显示为矩形, 其宽度对应于平均频率, 而它们的高度等于两个偏差增加若干倍, 以便获得更好的可视性。 

 图例. 9. iATR 和 iMACD 指标的直方图, 以及供给价和采购价

图例. 9. iATR 和 iMACD 指标的直方图, 以及供给价和采购价


示例代码如下。程序中使用的指标缓冲区高亮显示类和指标功能的交互。

结论

  • 可以使用 "图形" 存储器来实现直方图形式的变化序列的统计分布, 无需指标缓冲器和数组。
  • 当处理涉及图形对象的任务时, 利用图形对象属性作为更节省资源的存储器通常是合理的。此外, 我们能够访问广泛的终端和 MQL5 语言功能, 如排序, 分组, 搜索, 采样, 直接访问元素及其它。
  • 我们已研究了基本的, 原始的 "图形" 存储器应用方法。在真正的实践中, 可以在图形对象属性中存储小的数组和结构。
下一篇文章 >>
Sergey Pavlov:
//+------------------------------------------------------------------+ //|draw_histogram.mqh //| 2016 年 MetaQuotes 软件公司版权所有。| //| https://www.mql5.com | | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, MetaQuotes Software Corp." #property link        " " //--- Макросы #define   R         43      // значения префикса (+) для гистограмм справа #define   L         45      // значения префикса (-) для гистограмм слева #define   WIDTH     2      // толщина линий #define   ObjSet1   ObjectSetInteger ( 0 ,name, OBJPROP_WIDTH ,WIDTH) #define   ObjSet2   ObjectSetDouble ( 0 ,name, OBJPROP_PRICE , 0 ,y) #define   ObjSet3   ObjectSetInteger ( 0 ,name, OBJPROP_TIME , 0 ,time) #define   ObjSet4   ObjectSetDouble ( 0 ,name, OBJPROP_PRICE , 1 ,y) #define   ObjSet5   ObjectSetInteger ( 0 ,name, OBJPROP_BACK , true ) #define   ObjSet   ObjSet1;ObjSet2;ObjSet3;ObjSet4;ObjSet5 //--- int       hsize= 10 ;                     // масштаб гистограммы color     color_R_active= clrRed ;         // цвет активных линий справа color     color_R_passive= clrLightCoral ; // цвет пассивных линий справа color     color_L_active= clrBlue ;       // цвет активных линий слева color     color_L_passive= clrSkyBlue ;   // цвет пассивных линий слева void DrawHistogram( bool draw,     // 向左或向右绘制直方图                    string h_name, // 对象名称的唯一前缀                    double price,   // 价格(分析参数)                    datetime time, // 将直方图绑定到当前条形图                    int span,       // 分析的参数位数容量                    int swin= 0 )     // 直方图窗口   {    double y= NormalizeDouble (price,span);    string pfx= DoubleToString (y,span); // 如果绘制=true,则向右绘制直方图    if (draw)      {        string name= "+ " +h_name+pfx;                   // 对象名称:前缀+价格        ObjectCreate ( 0 ,name, OBJ_TREND ,swin,time,y);     // 创建对象        ObjectSetInteger ( 0 ,name, OBJPROP_COLOR ,color_R_active); // 设置对象颜色       ObjSet;                                                 // 代码缩写宏        if ( StringFind ( ObjectGetString ( 0 ,name, OBJPROP_TEXT ), "*" , 0 ) 0 )         { // 如果结果价格首次进入样本          ObjectSetString ( 0 ,name, OBJPROP_TEXT , "*1" );           // 价格频率为 1          ObjectSetInteger ( 0 ,name, OBJPROP_TIME , 1 ,time+hsize); // 定义时间坐标         }        else         { // 如果结果价格不是首次进入样本          string str= ObjectGetString ( 0 ,name, OBJPROP_TEXT );     // 获取属性值          string strint= StringSubstr (str, 1 );                   // 突出显示子串          long n= StringToInteger (strint);                     // 获得频率,以便进一步计算          n++;                                                 // 数值增加 1          ObjectSetString ( 0 ,name, OBJPROP_TEXT , "*" +( string )n); // 向属性写入新值          ObjectSetInteger ( 0 ,name, OBJPROP_TIME , 1 ,time+hsize*n); // 定义时间坐标         }      } // 如果 draw=false,则将直方图写到左边    if (!draw)      {        string name= "- " +h_name+pfx;        ObjectCreate ( 0 ,name, OBJ_TREND ,swin,time,y);        ObjectSetInteger ( 0 ,name, OBJPROP_COLOR ,color_L_active);       ObjSet;        if ( StringFind ( ObjectGetString ( 0 ,name, OBJPROP_TEXT ), "*" , 0 ) 0 )         {          ObjectSetString ( 0 ,name, OBJPROP_TEXT , "*1" );          ObjectSetInteger ( 0 ,name, OBJPROP_TIME , 1 ,time-hsize);         }        else         {          string str= ObjectGetString ( 0 ,name, OBJPROP_TEXT );          string strint= StringSubstr (str, 1 );          long n= StringToInteger (strint);          n++;          ObjectSetString ( 0 ,name, OBJPROP_TEXT , "*" +( string )n);          ObjectSetInteger ( 0 ,name, OBJPROP_TIME , 1 ,time-hsize*n);         }      }    ChartRedraw (); }
Charles Stangor:
谢谢 !
frcardim:
谢尔盖先生 我想用买入和卖出的成交量来代替买入和卖出的成交量,以获得类似于价格指标的成交量,这可能吗? 您有可以发布的带买入/卖出成交量的代码吗? 非常感谢! 我想绘制的数据示例,它在单独窗口中绘制买入/卖出刻度线 成交量 的水平柱状图,我想在主图表窗口中绘制垂直柱状图: