从基础到中级:对象(三)
概述
在上一篇文章“从基础到进阶:鼠标事件”中,我们以简单、清晰且相对客观的方式探讨了如何处理与鼠标相关的事件,以及鼠标与键盘的组合。这对于创建按下按钮的组合以及执行特定图表操作的按钮按压组合是必要的。
尽管乍一看,上一篇文章中讨论的功能可能相对简单,但当我们付诸实践时,我们会发现它可能远比表面看起来的要复杂。这是因为,根据我们要实现的功能类型及其实现方式,部分用户可能不具备所需的设备。虽然现在这种情况比较常见,但很多人并不使用台式电脑。
即使在那些确实使用台式电脑的人中,也有一些用户的鼠标可能不止三个按键之外并无更多功能。这使得实现某些面向广大用户群体的功能变得更加困难。对于个人使用来说,这不成问题,但如果你学习编程是为了将自己开发的应用投入销售,你会发现并非每个人都拥有必要的设备。因此,你可以根据这部分用户的需求来调整,或者忽视它,冒着失去部分市场的风险。而这一切,都源于一个简单的决定。
好吧,但我们在这里不是要学习如何销售我们开发的应用程序,而是要使用 MQL5 将我们的想法变为现实。鉴于上一篇文章更侧重于技术细节和简单演示,现在是时候将文中所示内容付诸实践,从而开发出 MetaTrader 5 十字准线工具的一种替代实现。
虽然我们的目标不是要创造一个替代品,而是要向你展示,只要你理解了正确的概念,即使知识非常有限,你也可以创造出有用的东西。
自定义指标
我们想在此展示的内容,已经在我们的另一篇文章“开发回放系统(第 30 部分):专家顾问项目 — C_Mouse类(四)”中讨论过。在那里,我们展示了如何使用面向对象编程(OOP)实现基于鼠标的指标。但在这里,我们将看到类似的东西,不过是一个针对初学者的更简单的程序。亲爱的读者,就像你一样。
为何要重复之前已经展示过的内容呢?事实上,我们不会重复任何东西;这只是一个概念。但鉴于到目前为止,我根本没有谈及面向对象编程,而只是展示了结构化方法,我们想证明,为了创造出符合我们需求的东西,我们无需成为专家。我们只需掌握最基本的知识,同时始终尝试应用已经学过的概念,就能做到这一点。然而,最终我们会得到想要的结果。换句话说,即使看似不足,简单的方法也能奏效。
首先,我们将基于上一篇文章中我们研究过的一个代码来编写代码。它如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 09. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 10. //+----------------+ 11. ChartSetInteger(0, CHART_MOUSE_SCROLL, false); 12. ChartSetInteger(0, CHART_CONTEXT_MENU, false); 13. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 14. //+----------------+ 15. ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false); 16. ChartSetInteger(0, CHART_QUICK_NAVIGATION, false); 17. //+----------------+ 18. 19. return INIT_SUCCEEDED; 20. }; 21. //+------------------------------------------------------------------+ 22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 23. { 24. return rates_total; 25. }; 26. //+------------------------------------------------------------------+ 27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 28. { 29. string sz = ""; 30. 31. switch (id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. break; 35. case CHARTEVENT_MOUSE_MOVE: 36. Comment(sz); 37. sz += "Mouse position X: " + (string)(short)lparam; 38. sz += "\nMouse position Y: " + (string)(short)dparam; 39. Comment(sz); 40. if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam); 41. break; 42. case CHARTEVENT_MOUSE_WHEEL: 43. break; 44. } 45. }; 46. //+------------------------------------------------------------------+ 47. void OnDeinit(const int reason) 48. { 49. Comment(""); 50. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 51. //+----------------+ 52. ChartSetInteger(0, CHART_MOUSE_SCROLL, true); 53. ChartSetInteger(0, CHART_CONTEXT_MENU, true); 54. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 55. //+----------------+ 56. ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true); 57. ChartSetInteger(0, CHART_QUICK_NAVIGATION, true); 58. //+----------------+ 59. }; 60. //+------------------------------------------------------------------+
代码 01
代码 01 是对上一篇文章中讨论的代码的修改。这将使我们更容易进行测试。请注意,在第 40 行,我们将在终端输出一些内容。这正是我们需要使用的值。因此,运行代码 01 时,我们将看到类似下图的内容。

图 01
图 01 中高亮显示的正是我们需要用到的按键掩码值。这是必要的,以便我们能够创建一个事件来显示自定义十字准线以进行分析。请注意这一点。我们在图片 01 中看到的值是按下鼠标中键时记录的值。因此,既然按下鼠标中键应该能创建用于分析的自定义十字准线,那么我们可以开始考虑如何实现这一点。还有一件事需要理解。
如果你练习了上一篇文章中展示的代码,你应该已经注意到,在代码 01 的第 06 行 OnInit 函数中进行的每次调用都会启用或禁用 MetaTrader 5 的一个标准属性。然而,由于我们只需要为分析创建一个自定义十字准线,因此无需禁用所有这些事件,因为这样做会过度限制 MetaTrader 5 对某些用户的使用。最好只禁用我们不希望 MetaTrader 5 默认使用的事件。在这种情况下,就是代码 01 的第 13 行的事件。
所以,我们首先需要在代码 01 中更改第一项。但在继续实现下一个代码之前,我们可以先着手处理十字准线。请密切关注我们即将讨论的内容;这非常重要。
在文章“从基础到进阶:对象(二)”中,我们展示了如何同时在图表上操作两个对象:一条垂直线和一条水平线。
上述两个对象均不使用屏幕坐标,因为在计算机图形学中,X 和 Y 坐标是已知的。OBJ_VLINE 和 OBJ_HLINE 这两个对象都使用时间和价格坐标。这些图表坐标基于价格和时间。由于操作系统为我们提供了 X 和 Y 坐标,我们需要将这些屏幕坐标转换为时间和价格坐标。手动执行这项任务相当繁琐,因为需要考虑到最小和最大价格以及最小和最大时间,以便建立价格和时间与操作系统提供的 X 和 Y 值之间的关系。
幸运的是,这项任务是通过 MQL5 标准库中的函数来执行的,这大大简化了流程。因此,在开始查看图表上绘制的十字准线之前,我们先来看看如何根据时间和价格将 X 值和 Y 值转换为图表坐标。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 09. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 10. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. return rates_total; 18. }; 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. string sz = ""; 23. short x, y; 24. int sub; 25. datetime dt; 26. double price; 27. 28. switch (id) 29. { 30. case CHARTEVENT_KEYDOWN: 31. break; 32. case CHARTEVENT_MOUSE_MOVE: 33. x = (short)lparam; 34. y = (short)dparam; 35. ChartXYToTimePrice(0, x, y, sub, dt, price); 36. Comment(sz); 37. sz += "Mouse position X: " + (string)x; 38. sz += "\nMouse position Y: " + (string)y; 39. sz += "\nMouse position time: " + TimeToString(dt, TIME_DATE | TIME_MINUTES); 40. sz += "\nMouse position price: " + (string)price; 41. Comment(sz); 42. if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam); 43. break; 44. case CHARTEVENT_MOUSE_WHEEL: 45. break; 46. } 47. }; 48. //+------------------------------------------------------------------+ 49. void OnDeinit(const int reason) 50. { 51. Comment(""); 52. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 53. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 54. }; 55. //+------------------------------------------------------------------+
代码 02
以下是一个更简单的代码,旨在仅启用或禁用真正必要的功能。其他一切都将保持 MetaTrader 5 的标准行为。请注意,我们对与鼠标事件处理程序相对应的部分进行了小幅修改。在第 35 行,我们看到了用于将值转换为我们实际需要或想要使用的值的库函数。在直接使用这些值来控制 OBJ_VLINE 和 OBJ_HLINE 对象之前,理解这一点非常重要。为了演示这些要点,请看以下动画。

动画 01
在动画01中,我们可以看到图表是以日线时间周期显示的。然而,如果我们查看动画本身显示的时间值,就会发现我们处理的是可以被视为日内交易的数据。这可能会让很多人感到困惑,因为如果我们正在查看日线图,怎么会看到日内交易的值呢?
事实上,这些数值之所以会精确显示,是因为 ChartXYToTimePrice 函数并不将数值转换为我们期望的格式,而是将屏幕坐标转换为时间和价格坐标。由于轴线上的小幅波动会导致数值变为小数,因而会得到落在当天内部某个时刻的时间值。这就是为什么我们看到的数值乍一看似乎毫无意义。因此,这并非错误,而只是从一种坐标类型转换到另一种坐标类型时的一个小细节。
无论如何,根据我们在此处所考察的内容,再加上一点想象力和常识,你很快就会明白,我们已经具备了一切必要条件来思考如何控制 OBJ_HLINE 和 OBJ_VLINE 对象。这与文章“从基础到进阶:对象(二)中所示的不同,后者是通过键盘进行控制的。但现在我们将使用鼠标来实现这一目的。
好的,那么我们该如何实现呢?亲爱的读者,最有趣的部分就要开始了。为此,我们首先需要创建一个始终保持在图表上的十字准线。这是通过以下代码实现的。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)ObjectsTotal(0) 07. //+------------------------------------------------------------------+ 08. string gl_Objs[2]; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 15. ObjectSetString(0, gl_Objs[0], OBJPROP_TOOLTIP, "\n"); 16. ObjectSetString(0, gl_Objs[1], OBJPROP_TOOLTIP, "\n"); 17. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrBlue); 18. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrBlue); 19. 20. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 21. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 22. 23. return INIT_SUCCEEDED; 24. }; 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 27. { 28. return rates_total; 29. }; 30. //+------------------------------------------------------------------+ 31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 32. { 33. int sub; 34. datetime dt; 35. double price; 36. 37. switch (id) 38. { 39. case CHARTEVENT_KEYDOWN: 40. break; 41. case CHARTEVENT_MOUSE_MOVE: 42. ChartXYToTimePrice(0, (short)lparam, (short)dparam, sub, dt, price); 43. ObjectMove(0, gl_Objs[0], 0, dt, price); 44. ObjectMove(0, gl_Objs[1], 0, dt, price); 45. if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam); 46. break; 47. case CHARTEVENT_MOUSE_WHEEL: 48. break; 49. } 50. ChartRedraw(); 51. }; 52. //+------------------------------------------------------------------+ 53. void OnDeinit(const int reason) 54. { 55. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 56. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 57. 58. ObjectsDeleteAll(0, def_Prefix); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
代码 03
将代码 03 应用于图表,将得到与此类似的结果:

动画 02
请注意,在动画 02 的开头,时间值会发生变化,尽管垂直线保持不变。这是因为鼠标的微小移动,这是完全正常且可以接受的。请注意,随着鼠标指针的移动,垂直线和水平线会随之移动,这样我们就可以在图表上持续显示十字准线。
由于本文所介绍的内容在文章“从基础到进阶:对象(二)”中已有部分涉及,我们将仅关注本文的新内容。在此情况下,移动现在与将屏幕坐标转换为时间和价格坐标时库程序返回的值具体相关联。
但这并非我们想要的。我们希望只在按下鼠标中键后显示相同的十字准线。因此,为了实现这一点,我们需要对代码进行一些修改。但首先,让我们了解一下为什么需要第 15、16 和 58 行代码。好的,第 15 和 16 行代码可以防止 MetaTrader 5 在鼠标悬停在某个对象上时显示小消息。如果你想显示一条解释对象本身的信息,但又不希望它一直停留在图表上,那么可以使用第 15 和 16 行的技巧,在那里放置一条有意义的消息。当鼠标光标在它指向的对象上停留片刻后,就会显示这条消息。
同时,第 58 行代码将删除图表上所有前缀与指定前缀匹配的对象。由于我们将对象与指标自身的简称相关联,因此删除与指标本身相关的对象变得更加容易。如果你希望某个对象保留在图表上,只需为指标名称使用不同的前缀即可。现在我们可以看一下下一段代码了。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)ObjectsTotal(0) 007. //+------------------------------------------------------------------+ 008. enum eBtnMouse { 009. MOUSE_LEFT = 0x01, 010. MOUSE_RIGHT = 0x02, 011. MOUSE_KEY_SHIFT = 0x04, 012. MOUSE_KEY_CTRL = 0x08, 013. MOUSE_MIDDLE = 0x10, 014. MOUSE_EXTRA_1 = 0x20, 015. MOUSE_EXTRA_2 = 0x40 016. }; 017. //+------------------------------------------------------------------+ 018. struct st_Cross 019. { 020. private : 021. //+----------------+ 022. bool IsView; 023. string Objs[2]; 024. //+----------------+ 025. void Create(char index, ENUM_OBJECT type) 026. { 027. ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0); 028. ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n"); 029. ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue); 030. } 031. //+----------------+ 032. public : 033. //+----------------+ 034. void Show(void) 035. { 036. if (IsView) return; 037. Create(0, OBJ_VLINE); 038. Create(1, OBJ_HLINE); 039. IsView = true; 040. ChartRedraw(); 041. } 042. //+----------------+ 043. void Hide(void) 044. { 045. if (!IsView) return; 046. for(uint c = 0; c < Objs.Size(); c++) 047. ObjectDelete(0, Objs[c]); 048. ChartRedraw(); 049. IsView = false; 050. } 051. //+----------------+ 052. void Move(short x, short y) 053. { 054. int sub; 055. datetime dt; 056. double price; 057. 058. if (!IsView) return; 059. ChartXYToTimePrice(0, x, y, sub, dt, price); 060. ObjectMove(0, Objs[0], 0, dt, price); 061. ObjectMove(0, Objs[1], 0, dt, price); 062. ChartRedraw(); 063. } 064. //+----------------+ 065. }gl_Cross; 066. //+------------------------------------------------------------------+ 067. int OnInit() 068. { 069. gl_Cross.Hide(); 070. 071. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 072. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 073. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 074. 075. return INIT_SUCCEEDED; 076. }; 077. //+------------------------------------------------------------------+ 078. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 079. { 080. return rates_total; 081. }; 082. //+------------------------------------------------------------------+ 083. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 084. { 085. 086. switch (id) 087. { 088. case CHARTEVENT_KEYDOWN: 089. break; 090. case CHARTEVENT_MOUSE_MOVE: 091. if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show(); 092. gl_Cross.Move((ushort)lparam, (ushort)dparam); 093. break; 094. case CHARTEVENT_MOUSE_WHEEL: 095. break; 096. } 097. ChartRedraw(); 098. }; 099. //+------------------------------------------------------------------+ 100. void OnDeinit(const int reason) 101. { 102. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 103. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 104. 105. gl_Cross.Hide(); 106. }; 107. //+------------------------------------------------------------------+
代码 04
好的,但是代码 04 是做什么用的呢?最终,会发生的情况正如我们在以下动画中所看到的那样。

动画 03
“有意思。但我看到你已经改用结构化代码了。为什么?”亲爱的读者,原因正是结构化代码对于十字准线本身来说更容易创建和维护。请记住,结构的目的是创建一整套与特定目标直接相关的函数和例程。
在文章“从基础到进阶:结构(七)”中,我们阐述了如何构建代码,所有内容都以抽象的形式呈现。但如果我们从较为具体的角度来审视,一切便开始变得明朗起来。
在这种情况下,在代码 04 中,我们正介于结构化编程与面向对象编程之间。这是因为,在代码 04 中,显然某些成员必须先初始化,然后我们才能使用结构本身。这也是创建面向对象编程的原因之一。请注意以下情况:当在第 65 行声明 st_Cross 结构以使用 gl_Cross 变量时,我们无法确定 IsView 和 objs 数组中包含的值。这使得创建某些类型的代码变得困难,尤其是那些旨在处理特定类型数据的代码。
不过由于这里的逻辑相对简单 ,我们可以使用第 69 行来准备结构并开始使用它。这不是最好的方法,但正如之前提到的,我们这里处理的是相对简单的问题。因此,在第 91 行检查是否按下中间按钮时,我们可以选择是否显示创建简单十字准线的对象以进行分析。这是通过调用 st_Cross 结构的 Show 函数来实现的。
总之,我们在第 92 行调整了对象的位置,使它们与鼠标指针关联起来。但是,如果图表上不存在这些对象,则 IsView 值将为 false。在第 58 行,这将停止执行并立即将控制权返回给调用者,在本例中即为第 92 行。这种情况将持续,直到按下鼠标中键,之后十字准线最终将被创建。从这一刻起,每次鼠标移动都会进行更新,因为在这种情况下,IsView 的值将为 true,从而允许第 92 行更新位置,因为第 58 行的条件将不再满足。
好的,但是十字准线变得可见后,我们该如何让它消失呢?这取决于你打算如何使用十字准线以及它的功能。但是,无需花费太多精力,也无需使用太多代码,我们就可以添加一个键来在图表中隐藏十字准线,直到这种行为完全实现。为此,我们更改了事件处理代码,以实现从图表中移除十字准线的方法,如下面的代码片段所示。
. . . 082. //+------------------------------------------------------------------+ 083. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 084. { 085. 086. switch (id) 087. { 088. case CHARTEVENT_KEYDOWN: 089. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 090. break; 091. case CHARTEVENT_MOUSE_MOVE: 092. if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show(); 093. gl_Cross.Move((ushort)lparam, (ushort)dparam); 094. break; 095. case CHARTEVENT_MOUSE_WHEEL: 096. break; 097. } 098. ChartRedraw(); 099. }; 100. //+------------------------------------------------------------------+ . . .
代码 05
这一简单的改动(见第 89 行)使我们能够实现以下动画中展示的功能。

动画 04
请注意,现在我们可以根据需要显示或隐藏十字准线以进行分析。但在这里,在动画 04 中,出现了一个重要问题:合约的报价变动单位,即价格变动的点数。与许多情况不同,这个期货合约(此处为美元期货合约)的变动不是以 1 个单位为单位,而是以 0.5 个单位为单位。为了验证这一点,我们可以查看合约规格:

图 02
那么,图 02 向我们展示了什么?从中我们可以看到交易品种价格的变化幅度。这正是我要强调的。这些信息很重要,这样我们才能开发出一种合适的方法,使价格线始终反映有效的市场价格。在价格线中指定一个永远不会使用的值是没有意义的。因为这样的值无法使用,例如,无法用于发送订单。
为了解决这个问题,我们需要再次修改代码。但这次我们不会更改事件处理函数;我们将更改 st_Cross 结构,如下所示。
. . . 017. //+------------------------------------------------------------------+ 018. struct st_Cross 019. { 020. private : 021. //+----------------+ 022. bool IsView; 023. string Objs[2]; 024. int nDigits; 025. double PointPerTick; 026. //+----------------+ 027. void Create(char index, ENUM_OBJECT type) 028. { 029. ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0); 030. ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n"); 031. ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue); 032. } 033. //+----------------+ 034. public : 035. //+----------------+ 036. void Init(void) 037. { 038. nDigits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); 039. PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); 040. Hide(); 041. } 042. //+----------------+ . . . 060. //+----------------+ 061. void Move(short x, short y) 062. { 063. int sub; 064. datetime dt; 065. double price; 066. 067. if (!IsView) return; 068. ChartXYToTimePrice(0, x, y, sub, dt, price); 069. price = NormalizeDouble(MathRound(price / PointPerTick) * PointPerTick, nDigits); 070. ObjectMove(0, Objs[0], 0, dt, price); 071. ObjectMove(0, Objs[1], 0, dt, price); 072. ChartRedraw(); 073. } 074. //+----------------+ 075. }gl_Cross; 076. //+------------------------------------------------------------------+ 077. int OnInit() 078. { 079. gl_Cross.Init(); 080. 081. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 082. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 083. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 084. 085. return INIT_SUCCEEDED; 086. }; 087. //+------------------------------------------------------------------+ . . .
代码 06
请注意,我们在代码片段 06 中进行了非常简单的修改。首先,我们在结构中添加了新变量。这有助于我们避免不断读取相同的信息。紧接着,我们添加了一个新函数:Init。该函数出现在第 36 行,我们以一种更合适的方式对结构进行了初始化。因此,在 OnInit 方法中,我们更改了之前对 Hide 函数的调用,并将其重定向到 Init,如第 79 行所示。
现在,为了修正价格线的偏差,我们使用第69行代码来相应地调整价格。最后,我们看到了以下动画所展示的内容:

动画 05
简直完美,我们用极其简单的代码就成功创建了基本且完全可定制的指标。现在问题来了:我们可以用这个指标进行哪些分析呢?因为我们在这里并没有专注于任何特定类型的分析。亲爱的读者,这个问题没有简单的答案。这是因为我们完全可以进行任何类型的分析。这完全取决于我们真正想要实现的目标,以及我们计划如何使用我们之后创建的东西。
请记住,这些文章的内容本质上是教育性的。由于这里所考虑的任何观点都不与我们试图实现的预定义目标相对应,因此不要拘泥于某一种实现方式;只需尝试理解这些概念。一旦你理解了这些概念,剩下的就是实现你想要或需要的东西。
我们如何将这个指标变得真正实用、有趣且吸引人?让我们采取以下步骤:将 st_Cross 结构移入头文件中。这样,我们就可以轻松实现各种指标。此外,我们还可以对其进行准备,以便在我们开始讨论类时,它能够成为一个类。因此,现在我们有两个文件。下面显示的是其中一个 — 这是头文件。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. enum eBtnMouse { 05. MOUSE_LEFT = 0x01, 06. MOUSE_RIGHT = 0x02, 07. MOUSE_KEY_SHIFT = 0x04, 08. MOUSE_KEY_CTRL = 0x08, 09. MOUSE_MIDDLE = 0x10, 10. MOUSE_EXTRA_1 = 0x20, 11. MOUSE_EXTRA_2 = 0x40 12. }; 13. //+------------------------------------------------------------------+ 14. struct st_TimePrice 15. { 16. double Price; 17. datetime Time; 18. }; 19. //+------------------------------------------------------------------+ 20. struct st_Cross 21. { 22. private : 23. //+----------------+ 24. bool IsView; 25. string Objs[2]; 26. int nDigits; 27. double PointPerTick; 28. //+----------------+ 29. void Create(char index, ENUM_OBJECT type) 30. { 31. ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0); 32. ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n"); 33. ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue); 34. } 35. //+----------------+ 36. public : 37. //+----------------+ 38. void Init(void) 39. { 40. nDigits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); 41. PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); 42. Hide(); 43. } 44. //+----------------+ 45. void Show(void) 46. { 47. if (IsView) return; 48. Create(0, OBJ_VLINE); 49. Create(1, OBJ_HLINE); 50. IsView = true; 51. ChartRedraw(); 52. } 53. //+----------------+ 54. void Hide(void) 55. { 56. if (!IsView) return; 57. for(uint c = 0; c < Objs.Size(); c++) 58. ObjectDelete(0, Objs[c]); 59. ChartRedraw(); 60. IsView = false; 61. } 62. //+----------------+ 63. st_TimePrice Move(short x, short y) 64. { 65. int sub; 66. st_TimePrice tp; 67. 68. ChartXYToTimePrice(0, x, y, sub, tp.Time, tp.Price); 69. tp.Price = NormalizeDouble(MathRound(tp.Price / PointPerTick) * PointPerTick, nDigits); 70. if (IsView) 71. { 72. ObjectMove(0, Objs[0], 0, tp.Time, tp.Price); 73. ObjectMove(0, Objs[1], 0, tp.Time, tp.Price); 74. ChartRedraw(); 75. } 76. 77. return tp; 78. } 79. //+----------------+ 80. }; 81. //+------------------------------------------------------------------+
代码 07
而这个指标正是我们想要实现的:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)ObjectsTotal(0) 07. //+------------------------------------------------------------------+ 08. #include <Tutorial\File 01.mqh> 09. //+------------------------------------------------------------------+ 10. st_Cross gl_Cross; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. gl_Cross.Init(); 15. 16. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 17. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 18. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 19. 20. return INIT_SUCCEEDED; 21. }; 22. //+------------------------------------------------------------------+ 23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 24. { 25. return rates_total; 26. }; 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. 31. switch (id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 35. break; 36. case CHARTEVENT_MOUSE_MOVE: 37. if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show(); 38. gl_Cross.Move((ushort)lparam, (ushort)dparam); 39. break; 40. case CHARTEVENT_MOUSE_WHEEL: 41. break; 42. } 43. ChartRedraw(); 44. }; 45. //+------------------------------------------------------------------+ 46. void OnDeinit(const int reason) 47. { 48. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 49. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 50. 51. gl_Cross.Hide(); 52. }; 53. //+------------------------------------------------------------------+
代码 08
请注意,在代码 07 中我们添加了一个新的结构。其目的是根据鼠标的移动和位置返回当前值。在代码 08 中,请注意头文件是在定义之后添加的。这一点很重要,以便文件能够被编译。这样一来,我们接下来很长一段时间都有可继续扩展的基础了。
那么,让我们从一个相对简单且轻量级的指标开始。我说“轻量级”是因为其实现并不需要太多努力,只需要稍加注意。但要正确区分材料,我们得转向一个新的话题。
趋势线指标
最简单、最容易实现、代价最低的指标之一就是趋势线。考虑以下示例:我们想要创建一个指标,该指标允许在无需在 MetaTrader 5 菜单中选择对象的情况下添加趋势线。因此,我们决定创建一个指标来优化此类活动。基于代码 08,这是一个可靠的工具,可用于创建使用对象的所有其他类型的指标,我们将对代码进行小幅修改,以创建一个使用趋势线的指标。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)ObjectsTotal(0) 07. //+------------------------------------------------------------------+ 08. #include <Tutorial\File 01.mqh> 09. //+------------------------------------------------------------------+ 10. st_Cross gl_Cross; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. gl_Cross.Init(); 15. 16. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 17. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 18. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 19. 20. return INIT_SUCCEEDED; 21. }; 22. //+------------------------------------------------------------------+ 23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 24. { 25. return rates_total; 26. }; 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. st_TimePrice tp; 31. static string isPaint = ""; 32. 33. switch (id) 34. { 35. case CHARTEVENT_KEYDOWN: 36. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 37. break; 38. case CHARTEVENT_MOUSE_MOVE: 39. if (((uchar)sparam & MOUSE_MIDDLE) != 0) 40. { 41. gl_Cross.Show(); 42. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 43. if (isPaint == "") 44. { 45. ObjectCreate(0, isPaint = macro_NameObject, OBJ_TREND, 0, tp.Time, tp.Price); 46. ObjectSetInteger(0, isPaint, OBJPROP_SELECTABLE, true); 47. ObjectSetInteger(0, isPaint, OBJPROP_COLOR, clrMagenta); 48. ObjectSetInteger(0, isPaint, OBJPROP_WIDTH, 3); 49. ObjectSetInteger(0, isPaint, OBJPROP_RAY_RIGHT, true); 50. } 51. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 52. }else 53. { 54. isPaint = ""; 55. gl_Cross.Hide(); 56. } 57. break; 58. case CHARTEVENT_MOUSE_WHEEL: 59. break; 60. } 61. ChartRedraw(); 62. }; 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 67. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 68. 69. gl_Cross.Hide(); 70. 71. if (reason == REASON_REMOVE) 72. ObjectsDeleteAll(0, def_Prefix); 73. }; 74. //+------------------------------------------------------------------+
代码 09
代码 09 将有助于解释一些许多初学者不知道的内容。
在编程中,因子的顺序会改变乘积。
这个简单的短语似乎对每个程序员生活中的大多数决策都没有影响,但它是许多初学者不知道的基本原则。确实如此,因为如果我们在图表上运行代码 09,我们将得到以下结果:

动画 06
换句话说,我们的想法是可行的。但你可能会想:如果出于某种原因我想改变趋势线的绘制方式,该怎么办?我该怎么做?亲爱的读者,这是最简单的部分。通常情况下,要选择一个对象,需要双击它。但是,如果你改变下图所示的参数,则只需单击一次即可选择该对象。

图 03
请记住,这仅适用于可选择的对象。但我们稍后会对此进行更详细的讨论。目前,MetaTrader 5 中所有对象最初都是可选的。好的,按图 03 所示启用该选项后,我们就可以这样操作:

动画 07
换句话说,我们立刻感到自豪,因为我们成功地将一段代码转换成了另一段代码。但即便如此,这里还是存在一个缺点。如果我们尝试创建一条新的趋势线,如下面动画所示,看看会发生什么:

动画 08
“哦,现在我什么都不懂了。真奇怪。为什么之前的趋势线被删除了?为什么我们在尝试创建新的趋势线时,其中一条交叉线没有出现?这种情况非常奇怪。我没想到会发生这种情况,因为从动画06来看,代码似乎运行得很好。”所以,亲爱的读者,问题在于对象的创建顺序错了。这听起来很奇怪。但是,是的,我们在动画 08 中看到的正是创建对象顺序错误的结果。
如果你查看代码 09,你会注意到我们首先创建了十字准线。这是在第 41 行完成的。紧接着,在第 45 行,我们构建了趋势线。这样的创建顺序不会有问题,当然前提是没有尝试创建新的对象。当发生这种情况时,正如动画 08 所示,我们就会遇到问题。实际上,会出现两个问题。第一个问题是,代码 09 中第 6 行的宏没有检查图表上已经存在的对象。因此,如果对象以相同的前缀列出,在某个时候我们会遇到图表上已经存在的对象。
但这怎么可能呢?好的,让我们假设图表上最初没有任何对象。创建十字准线后,我们将有两个对象:一个命名为 “Demo0”,另一个命名为 “Demo1”。一切进展顺利。当在代码 09 的第 45 行创建趋势线时,我们将有第三个对象 “Demo2”。
现在我们已经到达了动画 06 中所示线条所对应的位置。当尝试创建新的趋势线时,十字准线会尝试创建名为 “Demo1” 和 “Demo2” 的对象。但等一下:图表上已经有一个名为 “Demo2” 的对象,它代表之前的趋势线。是的,亲爱的读者,这正是其中一条十字准线无法创建的原因。而这导致了动画 08 开头所看到的情况。然而,如果移除十字准线,那么 “Demo1” 和 “Demo2” 都会消失,之前的趋势线也会随之消失。到那时,图表上将不再有任何对象,我们也就回到了构建趋势线的初始状态。
需要注意的是,第一个问题的产生是由于对图表上存在的对象进行了计数。但第二个问题与对象的创建顺序有关。因此,为了消除这两个问题,我们需要修改代码 09,并保持其这种形式:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. #include <Tutorial\File 01.mqh> 09. //+------------------------------------------------------------------+ 10. st_Cross gl_Cross; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. gl_Cross.Init(); 15. 16. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 17. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 18. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 19. 20. return INIT_SUCCEEDED; 21. }; 22. //+------------------------------------------------------------------+ 23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 24. { 25. return rates_total; 26. }; 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. st_TimePrice tp; 31. static string isPaint = ""; 32. 33. switch (id) 34. { 35. case CHARTEVENT_KEYDOWN: 36. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 37. break; 38. case CHARTEVENT_MOUSE_MOVE: 39. if (((uchar)sparam & MOUSE_MIDDLE) != 0) 40. { 41. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 42. if (isPaint == "") 43. { 44. ObjectCreate(0, isPaint = macro_NameObject, OBJ_TREND, 0, tp.Time, tp.Price); 45. ObjectSetInteger(0, isPaint, OBJPROP_SELECTABLE, true); 46. ObjectSetInteger(0, isPaint, OBJPROP_COLOR, clrMagenta); 47. ObjectSetInteger(0, isPaint, OBJPROP_WIDTH, 3); 48. ObjectSetInteger(0, isPaint, OBJPROP_RAY_RIGHT, true); 49. } 50. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 51. gl_Cross.Show(); 52. }else 53. { 54. isPaint = ""; 55. gl_Cross.Hide(); 56. } 57. break; 58. case CHARTEVENT_MOUSE_WHEEL: 59. break; 60. } 61. ChartRedraw(); 62. }; 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 67. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 68. 69. gl_Cross.Hide(); 70. 71. if (reason == REASON_REMOVE) 72. ObjectsDeleteAll(0, def_Prefix); 73. }; 74. //+------------------------------------------------------------------+
代码 10
运行代码 10 后,我们将通过以下动画看到结果:

动画 09
请注意,现在我们可以根据需要添加任意数量的趋势线。这样做不会出现任何问题,因为代码 10 中的第 06 行解决了名称冲突问题。只有在创建了将放置在图表上的趋势线后,才会显示十字准线。这可以在第 51 行看到,我们实际上在那里创建了一个简单的十字准线用于分析。换句话说,执行顺序的简单改变就解决了第二个问题,即阻止所需对象保留在图表上的问题。
最后的想法
在本文中,我们探讨了如何在实际操作中实现许多人认为困难或至少找不到实际方法来实现的操作 — 直接在指标中使用对象,将它们放置在图表上,然后使用它们。
的确,我们在这里展示了如何使用一个相对简单的对象,即趋势线。但不要因此认为这里介绍的应用毫无用处。亲爱的读者,你可以利用这里展示的内容来训练和练习各种技能,同时尝试开发一个对你真正有用的应用程序。试着研究一下,看看本文展示的内容有哪些可以改进的地方。
记住,我们目前还未使用类,这使得任务变得更加有趣。根据你计划实现解决方案的方式,你将开始注意到其实现中的某些方面有待改进。这些要点将我们引向一种新的编程风格,这种风格尚未在这篇简短的系列文章中介绍,但很快就会成为我们的常用技巧。
所以,请享用附件中的代码,下一篇文章见,我们将探讨如何对图表上的对象执行其他操作。
| MQ5 文件 | 描述 |
|---|---|
| 代码 01 | 对象演示示例 |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16021
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
人工原子算法(A3)
神经网络实践:熟能生巧
从新手到专家:用MQL5实现动态新闻头条(八)—— 新闻交易快捷交易按钮