MеtaTrader5 图表上的水平示意图
概述
水平示意图在终端图表上并不常见,但在很多任务中仍然会用到它们,例如在开发显示特定周期的交易量或价格分布的指标时,当创建各种版本的市场深度时。 关于定制(标准)指标数值的分布可能有更多的奇特任务。 但无论如何,它们都有一个共同的特征 — 示意图应可创建,放置,缩放,移动和删除。 我们要强调以下几点:
- 可能有若干个示意图(大多数情况都是这种情况)。
- 大多数我们感兴趣的示意图都由柱状线组成。
- 示意图柱状条水平排布。
我们拿一个众所周知的例子来看看这些示意图的外观:
下面是另一个示例。 由图形基元绘制的相同示意图:
在这种情况下,这是按天显示逐笔报价交易量分布的指标。 此示例非常清晰地揭示了开发者必须解决的任务:
- 创建众多图形对象,为它们指定唯一的名称,并将它们放置在图表上。
- 根据需要缩放和移动对象。
- 指标工作完成后,将其从图表中删除。
再一次,我们应该注意到有若干图表,这在未来可令我们论述示意图数组。
以下是最后一个示例:
在此,我们可以看到更复杂的逐笔报价交易量分布版本。 尽管事实上示意图看起来完整,但它包含由位于同一位置的三个不同的示意图组成,即:
- "卖出逐笔报价交易量"
- "买入逐笔报价交易量"
- "总计逐笔报价交易量"
您也许会问:“有没有更简单的方法来显示数据? 我们是否必须管理如此众多的图形基元?“ 确实可以找到更简单的方法,并且应该分析它们的效率。 但是,解决本文开头提到的整个任务集的最简单方法是使用示例中展示的水平示意图。
设立任务
我们分两个主要部分来概括我们的计划:
- 由于所有图形基元的坐标都由时间和价格形式绑定,因此很明显我们应该接收示意图在图表上的定位坐标绑定数组。
- 使用在第一阶段获得的数组,它是显示示意图并对其进行管理所必需的。
以我们的示例为准,定义绑定图形对象的主要方法。 第一个屏幕截图所显示的,大体是水平示意图的最常见排布。 它们界定在某个周期开始。 在此,它是一天的开始。
当然,这并不会耗尽时间坐标绑定选项列表。 另一个选项是绑定终端窗口的左侧或右侧。 例如,当示意图覆盖很长一段时间,并且其开始在可视窗口之外时,它可以被使用。 在这种情况下,可以使用绑定的窗口左侧。
另一种选项是将示意图绑定到当前周期,同时避免在图表工作部分上有过多的对象。 在这种情况下,可以使用终端的右侧。 在任何情况下,构成示意图的图形基元的时间坐标之一保持不变。 计算另一个坐标(计算示意图的“列水平长度”坐标)。
关于图形基元的价格绑定,情况要简单得多。 某个价格段被划分为具有固定步幅的间隔。 例如,可以假设价格段等于百分之百,而步幅等于百分之十。 在这种情况下,我们可以获得具有恒定数量的“水平柱状线”的示意图,在某些情况下这会导致结果有相当大的舍入。 所以,在本文中,我们将应用一种更有效的方法,稍后会研究这种方法。
从上面可以得出结论,在这种情况下可能不需要价格绑定数组。 我们可以使用以下简单的等式来计算第 i 个价格绑定:
第 i 个价格绑定 = 示意图喀什价格间隔 + i * 步幅间距。
对于所提供的逐笔报价交易量示例,可以构建示意图的价格区间由检查周期的 最低价 和 最高价 之间的间隔表示。
示意图显示问题也应详细说明。 因此,我们已经按照以下原则获得了示意图的绑定数组:“一个示意图 – 一组用于绑定的数组”。 另一个明显的观察结果:图表所含示意图通常基于相同颜色和相似“样式”的图形基元(例如,第二个和第一个屏幕截图上的示意图)。 在第三个屏幕截图中,所有示意图都根据颜色和“方向”而有所不同。 一个示意图的列从右到左“定向”,而另外两个 – 从左到右。 将“相同类型”示意图(如前两个屏幕截图)合并成一个数组,并分配一个管理器来控制它是合乎逻辑的。 我们使用以下原则来进一步将任务标准化:
- 即使集合仅由一个示意图组成,每组“相同类型”示意图也应该有自己的管理器。 因此,为前两个屏幕截图创建由一个管理器管理的至少三个示意图的数组,而对于第三个屏幕截图的示意图,我们将必须创建三个数组(每个含一个示意图)和三个管理器( 每个数组对应一个)。
至此,我们已概括了主要的开发点。 但是,这里有一些基本的特殊性。 您可能还记得,水平示意图显示各种分布,如逐笔报价交易量,价格,等等。 因此,获取用于绑定图形基元源数组的方法和原理可能非常不同,并且为该部分任务创建库文件是不合理的。
特别是,当开发按天分布的逐笔报价交易量培训指标时,将使用更有效的方法。 此文章 提供另一种方法。 换句话说,任务的第一部分将针对各种情况以不同的方式解决。 相反,任务的第二部分(开发一组示意图且由管理员帮助管理数组,或开发若干个“管理器 - 示意图数组”关联)在所有情况下几乎都是相同的。 这允许创建一个库文件,可包含在涉及水平示意图的所有项目中。
我们应该指定的最后一件事是示意图究竟是由什么构成的。 示意图将由水平线段或矩形组成。 这是两种最自然的选择。
现在我们直接进入代码。
常量与输入
很明显,许多提及的示意图参数都是使用枚举类型存储的。
示意图位置:
enum HD_POSITION { HD_LEFT = -1, HD_RIGHT = 1, HD_CNDLE = 2 };
有三个示意图放置选项 — 绑定到终端左侧(HD_LEFT),绑定到右侧(HD_RIGHT),与蜡烛(HD_CNDLE)绑定。 在本文开头的所有三个屏幕截图中,使用 HD_CNDLE 放置示意图。 在前两个中,执行的是与某些时段开始处(日线开始)的蜡烛绑定,而第三个,则与当天开始位置的单个蜡烛绑定。
示意图外观(图形基元):
enum HD_STYLE { HD_LINE = OBJ_HLINE, HD_RECTANGLE = OBJ_RECTANGLE, };
有两种外观选项 — 水平线段(HD_LINE)和矩形(HD_RECTANGLE)。 在本文开头的第一个和第三个屏幕截图中,示意图由 HD_LINE 原语组成,而在第二个屏幕截图中,它们由 HD_RECTANGLE 组成。
示意图“水平柱状条”的方向:
enum HD_DIRECT { HD_LEFTRIGHT = -1, HD_RIGHTLEFT = 1 };
在第三个屏幕截图中,由红色线段组成的示意图显示为 HD_RIGHTLEFT,而另外两个显示为 HD_LEFTRIGHT。
最后一个枚举类型与示意图级别的数量有关。 我曾提到过用于计算级别数量的最好方法,并不是将价格区间简单地划分成给定的级别数量。 请务必注意,此枚举类型与 设立任务 的第一部分相关,因此,它不会包含在最终库文件中。
应用的方法非常简单,将价格级数四舍五入到最接近的十或百。 相应地,价格级数的步幅也将等于十或一百。 通过这种方法,价格级数的数量会有所变化。 对于那些愿意获得最大计算货币(以及增加的资源消耗)的人,保留无舍入的 HD_MIN 方法:
enum HD_ZOOM { HD_MIN = 0, //1 HD_MIDDLE = 1, //10 HD_BIG = 2 //100 };
默认情况下应用 HD_MIDDLE 方法。
我们研究以下示例。 我们将用显示逐笔报价交易量分布的培训指标作为开发目标。 在本文开头的前两个屏幕截图中所提供的类似指标的工作可作为示例。
我们转进输入模块:
input HD_STYLE hdStyle = HD_LINE; input int hdHorSize = 20; input color hdColor = clrDeepSkyBlue; input int hdWidth = 2; input ENUM_TIMEFRAMES TargetPeriod = PERIOD_D1; input ENUM_TIMEFRAMES SourcePeriod = PERIOD_M1; input HD_ZOOM hdStep = HD_MIDDLE; input int MaxHDcount = 5; input int iTimer = 1;
为什么没有参数负责定位? 答案很明显。 只有一种定位方法适用于所需的指标 - HD_CNDLE。 因此,我们也许无需特意指明。
HD_STYLE 参数功能非常明显,无需进一步说明。
- hdHorSize 参数起着重要作用。 它定义了蜡烛中示意图“水平柱状条”的最大尺寸。 在这种情况下,最长的“水平柱状条”不能超过二十根蜡烛。 很明显,这个参数值越大,示意图就越精准。 不过,如果参数值太大,示意图会开始相互重叠。
- hdColor 和 hdWidth 参数处理示意图的外观(相应的颜色和线宽)。
- TargetPeriod 包含分析的时间帧。 在这种情况下,指标显示一天内逐笔报价交易量的分布。
- SourcePeriod 参数包含指标构建分布时提取源数据的时间帧。 在这种情况下默认使用 M1 时间帧。 请谨慎使用此参数。 如果选择月度时间帧作为分析时间帧,则计算可能需要很长时间。
- hdStep 参数是舍入的价格级数。 我上面已经描述过了。
- MaxHDcount 参数包含图表上的最大示意图数量。 请记住,每个示意图都包含多个图形基元。 过多的示意图可能会降低终端操作的速度。
- iTimer 参数包含定时器触发频率。 触发后,将检查新蜡烛的创建并执行必要的操作。 PeriodSeconds(SourcePeriod) 调用结果可能已放在此处。 但默认值是一秒,这令我们可以更准确地判断新蜡烛出现的确切时刻。
初始化
在这个阶段,我们需要创建示意图管理器的对象。 由于所有示意图都是相同的类型,我们只需要一个管理器。 由于管理器本身的类尚未编写,只需记住它是在 OnInit() 处理程序中创建的。 在此还创建了两个示意图(但未绘制):
- 显示当前周期逐笔报价交易量分布的示意图。 此示意图将定期重绘。
- 基于历史记录显示逐笔报价交易量的示意图。 这些分布不会重新绘制,因此示意图在显示后会“忘记”它们,并将控制权交予终端。
所提方法的效率是指标不管理示意图的图形基元,所以外观也不会改变。
接下来,初始化变量,以便随后的按步幅计算价格级数。 有两个这样的变量。 它们派生自 Digit() 和 Point():
hdDigit = Digits() - (int)hdStep; switch (hdStep) { case HD_MIN: hdPoint = Point(); break; case HD_MIDDLE: hdPoint = 10 * Point(); break; case HD_BIG: hdPoint = 100 * Point(); break; default: return (INIT_FAILED); }
一些小动作和开启计时器也在这个处理程序中发生。
基本计算
下一个任务分为两个阶段:
- 计算必要的数据,并绘制除当前数据之外的所需数量的示意图。 这些示意图不会再更改,只需显示一次即可。
- 计算当前周期包括当前时间的必要数据并绘制示意图。 应该定期重复计算这个点。 这可以在 OnTimer() 处理程序中完成。
我们以伪代码的形式在 OnCalculate() 处理程序中执行一些工作:
int OnCalculate(...) { if (prev_calculated == 0 || rates_total > prev_calculated + 1) { }else { if (!bCreateHis) { int br = 1; while (br < MaxHDcount) { { if(Calculate for bar "br") { sdata.bRemovePrev = false; Print("Send data to the new Diagramm"); } } ChartRedraw(); bCreateHis = true; } } return(rates_total); }
执行必要的计算,并在此处绘制除当前那个之外所需数量的示意图。 为实现此目的,在 TargetPeriod 时间帧循环中计算每根柱线,从第一根柱线开始,至 MaxHDcount 结束。 如果计算成功,则会向管理器发出一个命令,将同一循环中管理器收到的新数据绘制为示意图。 在整个循环结束时,重新绘制是鱼体,并设置一个标志以指示无需再做这部分工作。 示意图本身现在由终端控制。
在 OnTimer() 处理程序中执行创建和绘制包含当前周期的示意图。 由于问题明显浅显并清晰,我不打算在这里出示伪代码:
- 在 SourcePeriod 时间帧内等待新蜡烛。
- 执行必要的计算。
- 将数据发送到当前周期的示意图以便创建和绘制新图元。
其他处理程序和指标函数不是很有趣,可以在附带的代码中找到。 现在是时候来描述负责创建、绘制和管理示意图的类了。
示意图管理类
我们从处理水平示意图的管理器开始。 这个类本身不含图形基元,但却管理包含此类基元的其他类的数组。 由于所有示意图在单个管理器中都是相同的类型(如上所述),因此这些示意图的许多属性是相同的。 所以,在每个示意图中保持相同的属性集是没有意义的。 代之,在管理器中放置单组属性则是值得的。 我们将管理器类命名为“CHDiags”并开始编写代码:
- CHDiags 类包含的封装字段,其中一组属性,对于此管理器控制下的所有示意图都是相同的:
private: HD_POSITION m_position; HD_STYLE m_style; HD_DIRECT m_dir; int m_iHorSize; color m_cColor; int m_iWidth; int m_id; int m_imCount; long m_chart; datetime m_dtVis; static const string m_BaseName; CHDiagDraw* m_pHdArray[];
数据集说明:
- m_position, m_style, m_dir — 这三个参数描述了示意图的绑定和外观。 我们已经描述过了。
- m_iHorSize — 示意图的最大可能水平尺寸。 我们也已描述过它。
- m_cColor and m_iWidth — 示意图颜色和线宽。
- m_id — 独有的管理器 ID。 鉴于可能有多个管理器,每个都应该有一个唯一的 ID。 需要形成唯一的对象名称。
- m_chart — 显示示意图的图表 ID。 默认情况下,此字段的值为零(当前图表)。
- m_imCount — 图表上的最大示意图数量。 最终,图表上的示意图数量由此字段和以下字段确定。
- m_dtVis — 不要在此时间戳的左侧创建示意图。
- m_BaseName — 定义“base”名称的极其重要的参数。 示意图的所有元素以及示意图本身都应具有唯一的名称才能成功创建。 这些名称是在“base”名称后面给出的。
- m_pHdArray[] — 包含指向独立示意图对象的指针数组。 该字段不是属性,并且没有对应的 GetXXXX() 函数。
该属性也没有对应的 SetXXXX() 函数。 所有这些(m_BaseName 除外)都在类构造函数中设置。 m_dtVis 字段是另一个例外。 它由 bool 类型参数在构造函数中设置,具有以下含义:
- 仅在终端窗口中可见的蜡烛上显示示意图。 这样做是为了不显示位于终端边界左侧的示意图,减轻终端负载。 默认值为“true”。
创建管理器后,我们便可以创建对象 — 示意图。 这是由 CHDiags 类方法完成的:
int CHDiags::AddHDiag(datetime dtCreatedIn)
该方法返回管理器的 m_pHdArray 数组中创建的 CHDiagDraw 类对象的索引,如果发生错误,则返回 -1。 示意图开始计时作为参数传递给 dtCreatedIn 方法。 例如,每日蜡烛开盘时间在此处传递给所分析的指标。 如果未使用时间戳(蜡烛绑定到终端窗口边框),则应在此处传递 TimeCurrent()。 如果示意图位于 m_dtVis 字段时间戳的左侧,则不会创建该对象。 以下代码展示了该方法的工作原理:
int CHDiags::AddHDiag(datetime dtCreatedIn) { if(dtCreatedIn < m_dtVis ) return (-1); int iSize = ArraySize(m_pHdArray); if (iSize >= m_imCount) return (-1); if (ArrayResize(m_pHdArray,iSize+1) == -1) return (-1); m_pHdArray[iSize] = new CHDiagDraw(GetPointer(this) ); if (m_pHdArray[iSize] == NULL) { return (-1); } return (iSize); }//AddHDiag()
如您所见,该方法进行一些检查,增加数组的大小以保存示意图,并创建所需的对象,将指针传递给管理器本身以便以后访问属性。
管理器还提供了其他方法,允许您与示意图交互,虽然并非直接但仅通过示意图管理器:
bool RemoveDiag(const string& dname); void RemoveContext(int index, bool bRemovePrev); int SetData(const HDDATA& hddata, int index);
- 从第一个方法的名称中可以清楚地看到,它以示意图名称为参数从管理器和图表上彻底删除示意图。 目前,这是一个保留选项。
- 第二个仅删除示意图所包含的图形基元。 该示意图从图表中删除但依然存在于管理器中,而此时为“空”。 bRemovePrev 标志值进一步说明。
- 第三个方法使用输入数据传递结构,以便创建图形基元并绘制示意图。 管理器的 m_pHdArray 数组中的示意图索引用作当前和先前方法中的参数。
值得一提的最后一个方法是 CHDiags 类方法:
void Align();
如果示意图绑定到终端窗口的左侧或右侧,则调用该方法。 在这种情况下,在 CHARTEVENT_CHART_CHANGE 事件的 OnChartEvent 处理程序中调用该方法,将示意图返回到先前的位置。
CHDiags 示意图管理器类的其他方法是辅助的,可在附加文件中找到。
用于绘制和管理示意图图形基元的类
我们将这个类称为“CHDiagDraw”,并从 CObject 派生出来。 在类构造函数中,我们获取指向管理器的指针(将其保存在 m_pProp 字段中)。 此处还定义了唯一的示意图名称。
接下来,我们应该实现 Type() 方法:
int CHDiagDraw::Type() const { switch (m_pProp.GetHDStyle() ) { case HD_RECTANGLE: return (OBJ_RECTANGLE); case HD_LINE: return (OBJ_TREND); } return (0); }
所显示的示意图类型与应用的图形基元的类型相匹配,这是非常合理的。
在 CHDiagDraw 类中执行主要计算的方法由管理器的 SetData 方法调用:
int SetData(const HDDATA& hddata);
该方法的目标是定义示意图大小并在图表的某个点创建必要数量的图元。 为此,在调用点传递结构实例的链接:
struct HDDATA { double pcur[]; double prmax; double prmin; int prsize; double vcur[]; datetime dtLastTime; bool bRemovePrev; };
我们更详细地描述结构字段:
- pcur[] — 示意图价位数组。 对于所创建的图形基元,这是价格绑定的数组。
- prmax — 示意图可以具有的最大水平值。 在这种情况下,这是在特定级别交易的逐笔报价交易量的最大值。
- prmin — 保留参数。
- prsize — 示意图数量级数。 换句话说,这是示意图将包含的基元数量。
- vcur[] — 定义示意图柱状条“水平尺寸”的数值数组。 在这种情况下,数组包含 pcur[] 数组内相应级别交易的逐笔报价交易量。 pcur 和 vcur 数组的大小应该匹配并且等于 prsize。
- dtLastTime — 示意图位置。 对于图形基元,这是一个时间绑定。 此字段的优先级高于管理器的 AddHDiag 方法的参数。
- bRemovePrev — 若为 'true',彻底重绘该示意图,而以前的图形基元被删除。 如果设置为 “false”,则示意图将停止管理以前的图形基元,绘制新示意图而不删除它们,就像“忘记”它们一样。
由于其重要性,下面提供了 SetData 方法代码:
int CHDiagDraw::SetData(const HDDATA &hddata) { RemoveContext(hddata.bRemovePrev); if(hddata.prmax == 0.0 || hddata.prsize == 0) return (0); double dZoom=NormalizeDouble(hddata.prmax/m_pProp.GetHDHorSize(),Digits()); if(dZoom==0.0) dZoom=1; ArrayResize(m_hItem,hddata.prsize); m_hItemCount=hddata.prsize; int iTo,t; datetime dtTo; string n; double dl=hddata.pcur[0],dh=0; GetBorders(hddata); for(int i=0; i<hddata.prsize; i++) { if (hddata.vcur[i] == 0) continue; t=(int)MathCeil(hddata.vcur[i]/dZoom); switch(m_pProp.GetHDPosition()) { case HD_LEFT: case HD_RIGHT: iTo=m_iFrom+m_pProp.GetHDPosition()*t; dtTo=m_pProp.GetBarTime(iTo); break; case HD_CNDLE: iTo = m_iFrom + m_pProp.GetHDDirect() * t; dtTo = m_pProp.GetBarTime(iTo); break; default: return (-1); }//switch (m_pProp.m_position) n=CHDiags::GetUnicObjNameByPart(m_pProp.GetChartID(),m_hname,m_iNameBase); m_iNameBase++; bool b=false; switch(m_pProp.GetHDStyle()) { case HD_LINE: b=CHDiags::ObjectCreateRay(m_pProp.GetChartID(),n,dtTo,hddata.pcur[i],m_dtFrom,hddata.pcur[i]); break; case HD_RECTANGLE: if(dl!=hddata.pcur[i]) dl=dh; dh=(i == hddata.prsize-1) ? hddata.pcur[i] :(hddata.pcur[i]+hddata.pcur[i+1])/2; b = ObjectCreate(m_pProp.GetChartID(),n,OBJ_RECTANGLE,0,dtTo,dl,m_dtFrom,dh); break; }//switch(m_pProp.m_style) if(!b) { Print("ERROR while creating graphic item: ",n); return (-1); } else { m_hItem[i]=n; ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_COLOR, m_pProp.GetHDColor() ); ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_WIDTH, m_pProp.GeHDWidth() ); ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_SELECTABLE, false); ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_BACK, true); }//if (!ObjectCreateRay(n, dtTo, hddata.pcur[i], m_dtFrom, hddata.pcur[i]) ) }// for (int i = 0; i < l; i++) return (hddata.prsize); }//int CHDiagDraw::SetData(const HDDATA& hddata)
该方法的第一件事是清理并计算比例。 它还具有示意图“水平柱状条”的最大长度,和以蜡烛为单位的示意图最大水平尺寸。 我们可以得到该比例。 很容易注意到,由于图表大小“以蜡烛为单位”是一个整数,因此强制执行舍入。
接下来准备用于存储基元名称的数组。 计算其他示意图绑定参数 — 蜡烛索引和 GetBorders 方法中的临时绑定。 在此之后的循环中定义示意图的第二个时间绑定。 因此,创建图形基元的所有绑定已就绪。 我们获得基元的唯一名称,并使用接收的参数顺序创建它们。 基元的名称保存在数组中,并且它们的属性已得到校正。 该示意图已创建。 该方法返回示意图级数的数量。
也许,这种方法看起来太长了。 可以将用于创建和呈现基元的代码移到单独的保护方法中。 然而,该方法的两个部分看起来都是有机兼容的,第二部分明显是第一部分的延续。 考虑到不愿意创建使用大量参数的新方法,导致所开发的 SetData 方法以当前形式存在。
CHDiagDraw 类的其他函数是辅助函数,用来与管理器集成。
用户不直接调用 CHDiagDraw 类的任何方法。 代之,它们仅通过水平示意图管理器来执行。
上面提到的水平示意图管理器类,示意图绘制类,结构和枚举的整个代码可以在附带的 HDiagsE.mqh 文件中找到。
现在我们可以返回 EA 代码并详查其内容,而无需使用伪代码。
返回指标
在全局上下文中声明两个对象和变量:
CHDiags *pHd; int iCurr, iCurr0; HDDATA sdata;
这些是指向示意图管理器,示意图索引和将数据传递到示意图结构的指针。
根据指标输入立即在 OnInit() 中创建管理器和示意图:
pHd = new CHDiags(HD_CNDLE, hdStyle, HD_LEFTRIGHT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount); if(pHd == NULL) return (INIT_FAILED); iCurr = pHd.AddHDiag(TimeCurrent() ); if(iCurr == -1) return (INIT_FAILED); iCurr0 = pHd.AddHDiag(TimeCurrent() ); if(iCurr0 == -1) return (INIT_FAILED);
下面是 OnCalculate 处理程序的一部分,我们不得不使用近来的伪代码:
{ int br=1; while(br<MaxHDcount) { if(PrepareForBar(br++,sdata)) { sdata.bRemovePrev = false; if(iCurr!=-1) { Print(br-1," diag level: ",pHd.SetData(sdata,iCurr)); } } } ChartRedraw(); bCreateHis=true; }
现在我们只需要考虑填充 HDDATA 类型结构的函数,其中包含来自分布(TargetPeriod)时间帧内选定柱状条构建的数据:
bool PrepareForBar(int bar, HDDATA& hdta) { hdta.prmax = hdta.prmin = hdta.prsize = 0; int iSCount; datetime dtStart, dtEnd; dtEnd = (bar == 0)? TimeCurrent() : iTime(Symbol(), TargetPeriod, bar - 1); hdta.dtLastTime = dtStart = iTime(Symbol(), TargetPeriod, bar); hdta.prmax = iHigh(Symbol(), TargetPeriod, bar); if(hdta.prmax == 0) return (false); hdta.prmax = (int)MathCeil(NormalizeDouble(hdta.prmax, hdDigit) / hdPoint ); hdta.prmin = iLow(Symbol(), TargetPeriod, bar); if(hdta.prmin == 0) return (false); hdta.prmin = (int)MathCeil(NormalizeDouble(hdta.prmin, hdDigit) / hdPoint ); iSCount = CopyRates(Symbol(), SourcePeriod, dtStart, dtEnd, source); if (iSCount < 1) return (false); hdta.prsize = (int)hdta.prmax - (int)hdta.prmin + 10; ArrayResize(hdta.pcur, hdta.prsize); ArrayResize(hdta.vcur, hdta.prsize); ArrayInitialize(hdta.pcur, 0); ArrayInitialize(hdta.vcur, 0); double avTick; int i, delta; hdta.prmax = 0; for (i = 0; i < hdta.prsize; i++) hdta.pcur[i] = (hdta.prmin + i) * hdPoint; int rs = 0; for (i = 1; i < iSCount; i++) { if (source[i].tick_volume == 0.0) continue; if (!MqlRatesRound(source[i], (int)hdta.prmin) ) continue; delta = (int)(source[i].high - source[i].low); if (delta == 0) delta = 1; avTick = (double)(source[i].tick_volume / delta); int j; for (j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++) { if (j >= hdta.prsize) { Print("Internal ERROR. Wait for next source period or switch timeframe"); return false; } hdta.vcur[j] += avTick; if (hdta.vcur[j] > hdta.prmax) hdta.prmax = (int)hdta.vcur[j]; }//for (int j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++) if (j > rs) rs = j; //real size }//for (int i = 1; i < iSCount; i++) hdta.prsize = rs + 1; return (true); }
在最开始,该方法找出它应该执行计算的时间周期。 此处计算工作价格范围的边界,同时考虑进一步划分为绘图级数。
MqlRates 从时间帧的最新指定周期开始读取源数据。 对于获得的每个 MqlRates 结构,认为 tick_volume 均匀分布在从结构的低到高的范围内。 由于已知整个未来示意图的价格范围边界,因此逐笔报价交易量分布位于示意图中。 因此,形成了在所寻求的时期内的逐笔报价交易量分布数组。
计算结束时定义了逐笔报价交易量分布数组的实际大小,并由此定义了未来示意图中图形基元的数量。
因此,示意图的数据结构字段已填充并准备好传递给 SetData(...) 方法。
通常,使用所描述的类如下所示:
- Connect HDiagsE.mqh.
- 为每组“相同类型”示意图创建一个管理器对象。
- 调用 AddHDiag 管理器方法创建示意图。 该方法返回管理器已知的数组中的索引。
- 调用 RemoveContext 管理器方法清除无关示意图的数据。 调用 SetData 管理器方法并将带有数据的 HDDATA 类型结构传递给它,将新数据传递给示意图。 由调用方责任承担正确填写这一结构字段。
- 如有必要,可以通过调用A lign 管理器方法将示意图对齐到终端窗口的左侧或右侧。
- 所有示意图都在 CHDiags 管理器类析构函数中注销。
完整的指标代码可以在附带的 VolChart.mq5 文件中找到。 库文件 HDiagsE.mqh 也附在下面。
名称
几乎所有与水平示意图相关的对象都有名称。 它们按层次结构形成如下:
- 管理器含有定义“base”名称的 m_BaseName 私有字段。 其余所有的名字都从它开始。
- 创建管理器对象时,会为其分配唯一 ID。 调用代码负责此参数的唯一性。 m_BaseName 字段和 ID 构成管理器名称。
- 创建示意图对象时,它还会根据管理员名称接收唯一名称。
- 最后,在示意图对象中创建的图形基元根据示意图对象的名称获得其唯一名称。
这样就可以轻松地整理出必要的对象并对其进行管理。
还有一个指标
我们将已经开发的指标重新返工改造为显示逐笔报价交易量分布的图表,并与终端窗口右侧对齐:
为此,稍微更改一下代码:
- 初始化代码应略微更改
pHd = new CHDiags(HD_RIGHT, hdStyle, HD_RIGHTLEFT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount); if(pHd == NULL) return (INIT_FAILED); iCurr = pHd.AddHDiag(TimeCurrent() ); if(iCurr == -1) return (INIT_FAILED);
- 从 OnCalculate 处理程序中删除所有代码。
- 添加 OnChartEvent 处理程序
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: pHd.Align(); break; default: break; }//switch (id) }
结束语
现在我们可以通过启用单个文件来创建水平示意图。 开发人员的任务将包括准备构造数据,而这在本文的主题之外。 对于保留的方法,库文件代码建议尽可能的改进。 可以添加其他图形基元用于绘图。
不要忘记附加的指标仅用于演示和培训。 不要在真实交易中应用它们。 特别是,请记住,在用作数据源的时间帧内不应存在任何控件。
本文中使用的程序和文件
# | 名称 |
类型 |
描述 |
---|---|---|---|
1 | VolChart.mq5 | 指标 |
逐笔报价交易量分布指标。 |
2 |
HDiagsE.mqh | 库文件 |
含有水平示意图管理器和水平示意图的库文件。 |
3 |
VolChart1.mq5 |
指标 |
与终端窗口右侧对齐的逐笔报价交易量分布指标。 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4907