市场深度相关开发库的逻辑学延伸,从那以后,从仓位访问分时历史已经在 MQL5 中实现了,MetaQuotes 也发布了 CGraphic 开发库,它可以把自定义数据可视化为复杂的统计图形。CGraphic 的目标和R语言中的plot函数类似,
这些功能的引入使得可以把之前提供的市场深度大幅度地现代化。除了订单簿之外,在新版本中还显示了最近交易的分时图表:
图 1. 带有分时图表的市场深度.
之前的开发库版本包含了两个主要模块: 用于操作市场深度的 CMarketBook 类,和一个用于渲染它的图形面板。代码已经有了大量修改和提高,修正了许多错误,而市场深度的图形部分现在有了它自己的简单和轻量级的 CPanel 图形开发库。
让我们回到 CGraphic 以及它的绘制复杂图表和在独立窗口中绘制线形图表的功能。看起来这种特别的功能似乎只对统计问题有用,但是不是这样的!在本文中,我将尝试展示 CGraphic 的除了用于统计方面的功能 — 例如,当创建一个剥头皮市场深度。
从上个版本发布以来的修改
:
- 在市场深度中,最初的图形非常简陋,订单簿表格单元是使用了几个基本元件类来显示的,一段时间之后,在这些类中实现了更多的功能,而它们的简单性和轻量级证明了在设计其他类型的面板时也非常方便,结果,这些类也被加到一个独立的项目中 - CPanel 开发库。它位于 Include 文件夹。
- 市场深度的外观也有了提高,例如,用于打开和关闭市场深度窗口的小三角形已经被一个大的方形按钮替代,元件之间的覆盖也修改了 (如果表格重新打开,市场深度元件会在已有的元件上方重新绘制).
- 新的设置可以改变打开/关闭市场深度按钮在图表上X轴和Y轴的位置。经常会出现,因为交易品种名称不标准和另外还有交易面板的原因,市场深度的打开/关闭按钮可能会覆盖图表上的其他活动元件,这种可以人工设置按钮位置的功能可以避免这样的覆盖。
- CMarketBook 类也有了很大变化。在这个类中已经改正了下面的错误: 超出数组范围错误; 订单簿空白或者部分填充错误; 当改变交易品种时出现的除0错误。CMarketBook 类现在是一个独立的模块了,它位于 MQL5\Include\Trade;
- 实现了一些小的修改以提高指标的整体稳定性。
现在我们将从这个提高和修改过的版本开始工作,试着把它逐渐改为一个剥头皮市场深度工具。
CPanel 图形开发库概览
有很多关于使用MQL5创建用户界面的文章,Anatoly Kazharsky 的 "图形化界面"系列在它们之中是比较突出的,在这些文章之后已经很难加上任何新的内容了,所以,我们将不会详细分析图形界面的创建。之前在上面已经说过,市场深度的图形部分现在用的是完整功能的 CPanel 开发库,它的基础架构需要阐述一下,因为我们将会基于这个开发库来创建一个特别的图形元件 "分时图表"。它将与订单簿相结合,来创建一个含有几个元件的完整功能面板。
让我们详细探讨 CPanel 来了解进一步的操作原则。MQL5 中的图形化元件体现为几个图形元素,它们包括:
- 文本标签
- 按钮
- 输入框
- 长方形标签
- 位图标签
它们全部都有一些相同的属性,例如,长方形标签、按钮和输入框可以配置,所以很难区分它们。所以,元件几乎可以基于任何图形元素。例如,输入框可以显示为按钮,而按钮可以显示为长方形标签,从视觉上,这样的替代将不会被注意到,而用户点击输入框会想到他实际按下了一个按钮。
也许这样的替代看起来奇怪,而且使用户界面的创建原则更加复杂,但是我们必须明白,除了一般特性之外,每个基本元素还是会有自身独特的属性。例如,输入框不能是透明的,而长方形标签可以。所以,可以使用相同的类来创建独特外观的元件,
这可以通过一个例子来展示,假定我们需要使用一个传统的文本标签来创建一个图形化面板:
图 2. 一个使用无边框文本标签的表单.
但是如果我们想要加上边框,我们将会遇到问题,因为文本标签没有 "frame(边框)" 属性。解决方案很简单: 我们将会使用按钮,而不是文本标签!这里是一个使用按钮的表单:
图 3. 一个使用含边框文本的表单.
当创建图形界面时,会出现很多类似的情况,无法预测用户可能的需求,所以,最好不要基于一个特定的元素来创建图形元件,而是让用户有机会来决定。
这就是 CPanel 开发库中元件的组织方式。CPanel 是一些类,它们中的每个类都表示高级别图形化界面中的某个元件。为了初始化这样的元件,我们需要制定它所基于图形元素的类型。每个这样的类都有一个通用的父类,CNode 类, 它只执行一个功能,也就是保存基础元素的类型,它只有保护的构造函数,需要在元件创建时指定类型。
还有几个独特的高级图形元件,所谓的独特性是以来于一系列属性的,它们要被赋予到基础元素中才能使它独特。在 CPanel 中, 一个这样的独特元件是 CElChart 类。与所有其他类类似,它派生于 CNode, 而且它包含了用于配置以下属性的方法:
- 元件的长度和高度,
- 元件相对图表的 X 和 Y 坐标,
- 元件的宽度和高度,
- 元件的背景和边框颜色 (如果支持这样的属性),
- 元件边框的类型 (如果支持这样的属性),
- 元件内的文字,它的字体、大小和对齐方式 (如果支持这样的属性).
CElChart 提供了用于设置属性的方法,但是它不保证这些属性会真正设置。只有基本元素才能确定 CElChart 是否将支持某个属性。与 CNode 类似, CElChart 需要制定它所基于的图形化元素的类型。这样,使用 CElChart 可以让您创建通常的表单,以及,例如按钮或者文本栏位。这在实际应用中非常方便。
例子 让我们像图3那样绘制面板。我们需要两个元件:一个含有边框的背景和一个含有边框的文字,它们都是相同的 CElChart 类的实例。但是它们内部使用了两个不同的图形元素: OBJ_RECTANGLE_LABEL 和 BJ_BUTTON. 这里是结果代码:
//+------------------------------------------------------------------+ //| MarketBookArticlePanel.mq5 | //| Copyright 2017, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include <Panel\ElChart.mqh> CElChart Fon(OBJ_RECTANGLE_LABEL); CElChart Label(OBJ_BUTTON); //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- Fon.Height(100); Fon.Width(250); Fon.YCoord(200); Fon.XCoord(200); Fon.BackgroundColor(clrWhite); Fon.BorderType(BORDER_FLAT); Fon.BorderColor(clrBlack); Fon.Show(); Label.Text("Meta Quotes 语言"); Label.BorderType(BORDER_FLAT); Label.BorderColor(clrBlack); Label.Width(150); Label.Show(); Label.YCoord(240); Label.XCoord(250); ChartRedraw(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA交易订单分时函数 | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
当所有元件创建之后,我们需要在 OnInit 函数中设置它们的属性。现在元件可以通过调用它们的 Show 方法来显示在图表上了。
通过组合基本元素和 CElChart 类, 我们可以创建强大的、灵活的,而且最重要的、简单的图形界面。市场深度的图形化显示是类似的,订单簿包含了多个 CBookCell 元件,它们是基于 CElChart 的。
CPanel 图形化引擎支持嵌套,这意味着一个元件中可以放入更多元件,嵌套可以进行统一控制,例如,一个命令可以从全局表单中发送到它所有的子元件中,让我们把上面的例子这样修改:
//+------------------------------------------------------------------+ //| MarketBookArticlePanel.mq5 | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include <Panel\ElChart.mqh> CElChart Fon(OBJ_RECTANGLE_LABEL); CElChart *Label; //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- Fon.Height(100); Fon.Width(250); Fon.YCoord(200); Fon.XCoord(200); Fon.BackgroundColor(clrWhite); Fon.BorderType(BORDER_FLAT); Fon.BorderColor(clrBlack); Label = new CElChart(OBJ_BUTTON); Label.Text("Meta Quotes 语言"); Label.BorderType(BORDER_FLAT); Label.BorderColor(clrBlack); Label.Width(150); Label.YCoord(240); Label.XCoord(250); //Label.Show(); Fon.AddElement(Label); Fon.Show(); ChartRedraw(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA交易订单分时函数 | //+------------------------------------------------------------------+ void OnTick() { //--- }
现在,在程序执行时,会动态创建 CLabel, 它是一个指向 CElCahrt 元件的指针,在创建和对应的配置之后,它会被加到表单上。现在不需要使用一个单独的 Show 命令来显示它了,而是只要运行 Fon 元件的 Show 命令,它是我们应用程序的主表单。根据规格需求,这个命令要为所有子元件执行,包含了标签。
除了设置元件属性,CPanel 还支持高级的事件模型,不仅包括从图表中接收的事件,还有任何可以发生于 CPanel 中的事件。这个功能是由 CEvent 类和 Event 方法提供的。CEvent 是一个抽象类,很多特定的类都是基于它的,例如 CEventChartObjClick。
假定我们的用户表单有几个嵌套的子元件,用户可以创建一个事件,例如它可以点击这些元件中的任何一个,我们怎么知道使用哪个类实例来处理这个事件呢?为此我们将使用 CEventChartObjClick 事件: 让我们创建一个类实例并把它发送到中央表单:
CElChart Fon(OBJ_RECTANGLE_LABEL); ... ... void OnChartEvent(const int id, // 事件标识符 const long& lparam, // 长整数类型的事件参数 const double& dparam, // 双精度浮点数类型的事件参数 const string& sparam // 字符串类型的事件参数 ) { CEvent* event = NULL; switch(id) { case CHARTEVENT_OBJECT_CLICK: event = new CEventChartObjClick(sparam); break; } if(event != NULL) { Fon.Event(event); delete event; } }
使用这个方法,我们已经发送了 CEventChartObjClick 广播事件,所有在 Fon 实例内的元件都回收到它。事件是否将被处理,这依赖于表单的内部逻辑。
让我们让 MetaQuotes 语言中的标签来处理这次点击,然后把它的文字变成"Enjoy(开心)"。为此,我们创建 CEnjoy 类并为它提供所需的逻辑: 重载 OnClick 方法,它就是对应事件的处理函数:
//+------------------------------------------------------------------+ //| MarketBookArticlePanel.mq5 | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include <Panel\ElChart.mqh> #include <Panel\Node.mqh> CElChart Fon(OBJ_RECTANGLE_LABEL); //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ class CEnjoy : public CElChart { protected: virtual void OnClick(void); public: CEnjoy(void); }; CEnjoy::CEnjoy(void) : CElChart(OBJ_BUTTON) { } void CEnjoy::OnClick(void) { if(Text() != "Enjoy!") Text("Enjoy!"); else Text("MetaQuotes 语言"); } CEnjoy Label; //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- Fon.Height(100); Fon.Width(250); Fon.YCoord(200); Fon.XCoord(200); Fon.BackgroundColor(clrWhite); Fon.BorderType(BORDER_FLAT); Fon.BorderColor(clrBlack); Label.Text("Meta Quotes 语言"); Label.BorderType(BORDER_FLAT); Label.BorderColor(clrBlack); Label.Width(150); Label.YCoord(240); Label.XCoord(250); //Label.Show(); Fon.AddElement(&Label); Fon.Show(); ChartRedraw(); //--- return(INIT_SUCCEEDED); } void OnChartEvent(const int id, // 事件标识符 const long& lparam, // 长整数类型的事件参数 const double& dparam, // 双精度浮点数类型的事件参数 const string& sparam // 字符串类型的事件参数 ) { CEvent* event = NULL; switch(id) { case CHARTEVENT_OBJECT_CLICK: event = new CEventChartObjClick(sparam); break; } if(event != NULL) { Fon.Event(event); delete event; } ChartRedraw(); } //+------------------------------------------------------------------+ //| EA交易订单分时函数 | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
我们通过 Event 方法来向表单发送 CEventObjClick 事件,而在 OnClick 中处理它,这看起来可能有些奇怪。很多标准事件 (例如,鼠标的点击) 有它们自己特定的事件方法,如果我们重载了它们,它们将会接收对应的事件。不使用重载,所有的事件将会在高一层处理,也就是在 Event 方法中,它也是一个虚拟方法,并且它也可以像 OnClick 那样进行重载。在这个水平上,所有事件都通过接收到的 CEvent 实例的分析来进行处理,
我们现在不会详细讨论它。让我们设置 CPanel 的主要属性,
- 所有实现 GUI 元件的 CPanel 类都是可以基于任何所选的图形元素的,它是在类实例的创建中选择和设置的。
- 一个绝对的 CPanel 元件可以包含不限数量的其他 CPanel 元件,这就是嵌套和统一控制是如何实现的。所有事件都通过嵌套树发布,所以每个元件都能访问每个事件。
- CPanel 的事件模型有两层,低层模型是基于 Event 方法和 CEvent 类型的类的,这使得可以处理几乎任何事件,甚至是那些 MQL 中不支持的,通过 CEvent 发送的事件总是广播的。在高层, 标准事件被转换为对应方法的调用,例如,CEventChartObjClick 事件被转换为 OnClick 调用, 而 Show 方法调用在所有子元件中会生成递归的 OnShow 方法调用。在这个层次中可以直接进行事件的调用,例如,如果我们调用 Show(), 它将显示一个面板。调用 Refresh 将会刷新面板的显示。
回顾是简明扼要的,但是总的思路对于了解我们进一步的开发是足够了的,我们将会花一点时间来创建一个剥头皮市场深度工具。
与订单簿同步分时数据流
订单簿是一个动态结构,它的数值在动荡的市场中每秒可能改变很多次,为了访问订单簿的当前状态,您必须在对应的事件处理函数,即 OnBookEvent 函数中处理一个特定的 BookEvent。当订单簿改变时,终端调用 OnBookEvent, 指示对应改变的交易品种。在前面的文章中,我们开发了 CMarketBook 类,它提供了对当前订单簿状态的方便访问,订单簿的当前状态可以在这个类中通过在 OnBookEvent 函数中调用 Refresh() 方法来得到,这就是它看起来的样子:
//+------------------------------------------------------------------+ //| MarketBook.mq5 | //| Copyright 2017, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 #include <MarketBook.mqh> CMarketBook MarketBook.mqh double fake_buffer[]; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { MarketBook.SetMarketBookSymbol(Symbol()); //--- 指标缓冲区映射 SetIndexBuffer(0,fake_buffer,INDICATOR_CALCULATIONS); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| MarketBook 改变事件 | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { if(symbol != MarketBook.GetMarketBookSymbol()) return; MarketBook.Refresh(); ChartRedraw(); }
在新版本中,除了订单簿之外,市场深度还显示了一个实时的分时图表,所以我们需要实现额外的函数来处理新收到的分时信息。在 MQL5 中, 有三种基本的机制来分析分时,它们包括:
- 使用 SymbolInfoTick 函数来取得最新收到的分时,
- 对于EA交易,在 OnTick 函数中处理新分时的到来,而对于指标,函数为 OnCalculate,
- 使用 CopyTicks 和 CopyTicksRange 函数来取得分时历史。
前两种方法可以结合起来,例如,SymbolInfoTick 函数可以在 OnTick 或者 OnCalculate 中调用以取得最新分时的参数,但是,这两种方法对于我们来说没有用,因为分时流的出现。
价格, 每盎司黄金的价格$ | 盎司数量 (合约数) |
---|---|
1280.8 | 17 |
1280.3 | 3 |
1280.1 | 5 |
1280.0 | 1 |
1279.8 | 2 |
1279.7 | 15 |
1279.3 | 3 |
1278.8 | 13 |
图 4. 一个市场深度的实例
假定在市场深度更新时,我们请求分时历史并且使用一种特定算法来确定自从上次更新后又有多少分时到达.从理论上讲,每个分时必定对应着订单簿上的至少一次变化,意思是每次订单簿有变化时不能有分时到来.但是现实中是不同的,并且我们需要在分时历史上处理,来进行一次正常的同步。
假定有一个买家想买9个合约,买家将会在买入黄金时至少有三次交易,如果有更多买家想要在 1280.1 买入或者 1280.3 买入, 就会有更多交易。通过进行一个操作 (购买), 买家创建 多个 交易同时进行。这样,会有"一包"分时到达 MetaTrader 5 终端。并且如果我们再 OnCalculate 中使用 SymbolInfoTick 函数,只返回这个系列的最后的分时,而之前的分时会丢失。
这就是为什么我们需要更可靠的机制来使用 CopyTicks 函数取得分时。和 SymbolInfoTick 不同, 这个函数允许接到一系列分时,和 CopyTicksRange 类似. 这就提供了不丢失分时的正确显示分时历史。
但是 CopyTiks 函数不允许请求最新的 N 个分时,它提供的是在制定时刻之后来到的所有分时信息。这使任务更加复杂。我们需要执行一个请求,接受分时数组的数据并把它与前一次更新后的分时数组做比较。这样我们就可以知道那个分时没有包含在前面一次的包裹中。但是分时不能直接相互比较,因为在它们之间外观没有明显区别。例如,参考下面的订单簿例子:
图 5. 一个含有相同交易例子的订单簿
我们可以看到两组绝对相同的分时,它们被使用红色边框做标记: 它们有相同的时间,仓位大小,方向和价格。所以,很明显我们不能比较它们之间的订单分时,
但是我们可以比较一组分时。如果两组分时是相同的,这些分时以及更多的分时都已经在前面价格更新时分析过了。
让我们把 CMarketBook 类和分时流同步: 向其中加上 MqlTiks 数组,它包含了从上次更新后收到的新的分时。新的分时将会通过内部的 CompareTiks 方法进行计算:
//+------------------------------------------------------------------+ //| 比较两个分时集合并寻找新的分时 | //+------------------------------------------------------------------+ void CMarketBook::CompareTiks(void) { MqlTick n_tiks[]; ulong t_begin = (TimeCurrent()-(1*20))*1000; // 从20秒之前开始 int total = CopyTicks(m_symbol, n_tiks, COPY_TICKS_ALL, t_begin, 1000); if(total<1) { printf("接收分时失败"); return; } if(ArraySize(m_ticks) == 0) { ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY); return; } int k = ArraySize(m_ticks)-1; int n_t = 0; int limit_comp = 20; int comp_sucess = 0; //遍历从上次开始所有接收到的交易 for(int i = ArraySize(n_tiks)-1; i >= 0 && k >= 0; i--) { if(!CompareTiks(n_tiks[i], m_ticks[k])) { n_t = ArraySize(n_tiks) - i; k = ArraySize(m_ticks)-1; comp_sucess = 0; } else { comp_sucess += 1; if(comp_sucess >= limit_comp) break; k--; } } //记住接收到的分时 ArrayResize(m_ticks, total); ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY); //计算新分时起始的索引并把它们复制到缓冲区来访问 ArrayResize(LastTicks, n_t); if(n_t > 0) { int index = ArraySize(n_tiks)-n_t; ArrayCopy(LastTicks, m_ticks, 0, index, n_t); } }
这个算法不算简单,CompareTicks 会请求最近20秒的所有分时,然后把它们和之前记录的分时数组做比较,如果当前数组有连续20个分时和之前数组的20个分时相同,就假定这20个分时之后的所有分时都是新的。
这里是一个简单框架,可以解释这个算法。假定我们在很短的时间段之间调用两次 CopyTiks,这个函数可能会返回含有0和1的数组,而不是分时,得到了这两个数组之后,我们检查在第二个数组中有多少不与第一个数组中元件匹配的独特的最新元件,这就是自从上次更新后接收到的新的分时的数量。这可能看起来就像下面的概要图:
图 6. 重复序列同步框架.
比较显示,在第一个数组中,从序号6到14与第二个数组中的从1到8的数据是相同的,所以数组 2 有5个新的数值,也就是序号从9到14的元素。这个算法在不同的组合下都是有效的: 数组可以有不同的长度,也许没有相同的元素或者完全相同。在所有这些情况下,新数值的数量都可以正确得到。
当确定了新分时的数量之后,我们把它们复制到 LastTiks 数组中,这个数组是 CMarketBook 类中的一个公有栏位。
这是一个新版本的 CMarketBook 类,加上了包含了分时的数组,其中是之前和当前更新之间的分时信息。例如,下面的代码可以用于找到新的分时:
//+------------------------------------------------------------------+ //| MarketBook 改变事件 | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { if(symbol != MarketBook.GetMarketBookSymbol()) return; MarketBook.Refresh(); string new_tiks = (string)ArraySize(MarketBook.LastTicks); printf("已经收到了 " + new_tiks + " 个新的分时信息"); }
在市场深度类中含有分时,这可以使当前的订单与分时流同步,在每次订单簿更新的时刻,我们都有一个在此更新前的分时列表,这样,分时流就能和订单簿完全同步了。晚些时候我们将会使用这个重要的质量保证,现在我们继续转到 CGraphic 图形开发库。
CGraphic 基础
CGraphic 开发库包含了线形,柱形图,点,以及复杂的几何图形。根据我们的目标,我们将只使用它功能的一小部分,我们将需要两条线来显示卖家报价和买家报价的水平,以及特定的点来显示最新的交易。CGraphic 是一个 CCurve 对象的容器,就像名称中暗示的,这些对象中的每一个都是一条曲线,包含有X和Y坐标的点。根据显示类型,它们可以使用线来连接,可以显示为柱形图,或者显示为点。
因为最新交易显示的特性,我们将使用二维图表,让我们试着创建一个简单的二维线性图表:
//+------------------------------------------------------------------+ //| TestCanvas.mq5 | //| Copyright 2017, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Vasiliy Sokolov." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> CGraphic Graph; //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { double x[] = {1,2,3,4,5,6,7,8,9,10}; double y[] = {1,2,3,2,4,3,5,6,4,3}; CCurve* cur = Graph.CurveAdd(x, y, CURVE_LINES, "Line"); Graph.CurvePlotAll(); Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); Graph.Redraw(true); Graph.Update(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA交易订单分时函数 | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
如果您以 EA 交易的形式运行它,将在图表上出现下面的画面:
图 7. 使用 CGraphic 创建的二维线性图标实例
现在我们将试着改变表现形式,我们把显示类型从二维曲线改成点:
//+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { double x[] = {1,2,3,4,5,6,7,8,9,10}; double y[] = {1,2,3,2,4,3,5,6,4,3}; CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points"); cur.PointsType(POINT_CIRCLE); Graph.CurvePlotAll(); Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); Graph.Redraw(true); Graph.Update(); return(INIT_SUCCEEDED); }
以点的形式表现的相同图表看起来如下:
图 8. 一个使用 CGraphic 创建的二维点图表实例
您可以看到,这里我们创建一个曲线对象,它的数值必须包含在 x 和 y 数组中:
double x[] = {1,2,3,4,5,6,7,8,9,10}; double y[] = {1,2,3,2,4,3,5,6,4,3}; CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points");
创建的对象位于 CGraphic 内部, 而 CurveAdd 方法可以返回它的一个引用。这就可以设置曲线所需的对象,我们已经在第二个例子中这样做了,我们把曲线类型设为 CURVE_POINTS 并且把点的类型设为圆形:
cur.PointsType(POINT_CIRCLE);
在加上所有的线之后,我们需要在图表上绘制并显示它们,使用的是 Create 和 Redraw 命令。
我们在我们的市场深度项目中重复相同的操作序列,但是用于曲线的数据将以特别方式准备,所有的命令都放在从 CElChart 派生的特定的 CElTickGraph 类中。
使用 CPanel 开发库做 CGraphic 的整合
我们已经讨论了使用 CGraphic 进行工作的要点,现在是时候把这个类加到 CPanel 开发库中了。我们已经谈到,Panel 提供了对事件的访问,正确组织图形元件并管理它们的属性,所有这些都是在市场深度面板中制作分时图表所需要的。所以,我们首先要些一个特别的 CElTickGraph 元件,它是 CPanel 的一部分,把 CGraphic 整合到面板中。另外,CElTickGraph 将会接收到更新的分时价格流并且重绘我们的分时图表。最后的任务是最困难的一个,这里是 CElTickGraph 应该可以完成任务的简要枚举。
- CElTickGraph 标识了市场深度面板内部的一个长方形区域,该区域使用黑色边框标识。
- 在 CElTickGraph 区域之内,有一个 CGraphic 图表显示了分时价格流。
- 分时流显示了最新的N个分时,N 数字可以在设置中修改。
- CElTickGraph 更新 CGraphic 中包含的 CCurve 曲线的值,这样旧的分时就被从图表上删除,而新的分时会加到图表上。这使得 CElTickGraph 可以创建一种平滑改变的分时图表的效果。
让我们创建四个环形缓冲区来显示下面的数值: