概论
直方图允许研究人员根据其浸透到某个 (预判) 间隔的频率来直观评估统计数据组的分布。
直方图及其在统计数据分析中的使用是一个研究充分的主题, 已发表了多篇文章 [1, 2, 3, 4, 5, 6, 7], 且在代码库中有大量例程 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。不过, 采用的算法均基于使用指标缓冲区或数组。在本文中, 赫兹量化考虑无需复杂的计算、排序、抽样等即能建立不同市场特征统计分布的可能性。为了实现这一点, 我们将使用 "图形" 内存 — 图形对象属性的部分存储。由于在绘制自定义直方图时我们需要 图形对象, 我们可以使用它们的 "隐藏" 能力和丰富的函数来满足需求。
文章的目标是为标准统计问题提供简单的解决方案。本文主要关注统计分布的可视化及其基本特征。赫兹量化不打算讨论直方图的释义及其实际优势。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
直方图绘制基础
直方图是频率的柱线图。其中一根数轴代表变量值, 另一根数轴代表这些值出现的频率。每根柱线的高度代表那些间隔等于列宽的数值的频率 (数量)。这些示意图通常水平显示, 即, 变量值位于水平轴上, 而频率 — 坐落于垂直轴。使用直方图来表示研究的数据, 使得统计数据更直观, 并且更容易理解和分析。
在本文中, 赫兹量化将关注变化序列的垂直直方图: 分析参数的价格值将按照升序位于垂直轴上, 而频率位于水平轴上 (图例. 1)。传入的价格数据在当前柱线上分布并分组, 并且可以从左侧、右侧或两侧同时相对于其数轴显示。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
图例. 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(); }