从基础到中级:对象(二)
概述
在上一篇文章“从基础到进阶:对象(一)”中,我们首次接触了 MQL5 中可用的图形对象,并展示了如何将这些对象放置在图表上,并以一种相对简单(尽管通常效率不高)的方式进行操作。这是因为我们目前还不打算构建应用程序;我们的目标是尽可能保持代码的简洁和易读性。
我必须承认,上一篇文章相当有趣。然而,它远不及我们今天要做的事情那么复杂。关键在于,一旦我们开始通过代码来操作和控制对象,那些过去看似不可能的事情就会变得越来越常见。
但由于我们仍处于旅程的起点,在充分发挥我们的想象力,从程序员的角度将 MetaTrader 5 打造成真正有趣的东西之前,还有几点需要先解决。从用户的角度来看,我们接下来要做的事可能看起来没什么实际意义。那么,像往常一样,让我们开始一个新的话题。
初始操作
我们在上一篇文章中描述的内容,并不一定应被视为对对象的第一次真正意义上的操作。那仅仅是因为我们把它们放在了图表上,仅此而已。要执行任何其他操作,我们必须打开一个对话框,在该对话框中我们可以访问和管理一些对象属性。然而,在编程时,我们可以走得更远,理论上,除了我们的想象力,我们所能做的事情是没有限制的。由于 MQL5 旨在为 MetaTrader 5 创建应用程序,而 MetaTrader 5 平台主要用于展示价格图表,因此,MQL5 的直接应用范围确实是有限的。
但是,亲爱的读者们,请相信我,这个极限实际上远远超出了大多数人的想象。一旦达到该限制,我们可以使用 C 和 C++ 编程语言来对其进行扩展。但这已经是另一个层次了,本系列文章将不会涉及,因为我们只会探讨我认为将中级与高级区分开来的那个界限。当我们需要使用 C 和 C++ 等语言来扩展 MQL5 的功能时,这种高级水平就开始显现出来。
回到我们的主要观点,为了操作对象,我们需要使用输入和用户交互机制。这些机制依赖于键盘和/或鼠标,从而能够以简单的方式处理用户输入。但是,正如您可能已经猜到的那样,使用这种机制意味着我们需要捕获事件,因为操作系统本身,无论是 Windows、Linux 还是其他任何操作系统,本质上都是使用事件在用户和任何应用程序之间进行通信。这一点已在文章“从基础到中级:事件(一)”中有所阐述。但我们在那里只是对这些观点进行了肤浅的讨论。在这里,我们将更深入地探讨这个话题。
首先,让我们编写一个小代码示例,即使没有详细解释,大家也能理解。第一个代码片段如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 09. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 10. 11. return INIT_SUCCEEDED; 12. }; 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. static double p = 0; 17. 18. ObjectMove(0, def_NameObj, 0, 0, p); 19. 20. if (prev_calculated > 0) 21. p = price[prev_calculated - 1]; 22. 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnDeinit(const int reason) 27. { 28. ObjectDelete(0, def_NameObj); 29. ChartRedraw(); 30. }; 31. //+------------------------------------------------------------------+
代码 01
所以,如果你已经学习了我们在这些文章中展示的内容,你就会完全理解这段代码,因为它实际上非常有趣且简单。它之所以有趣,是因为它成功实现了某些目标。也许你正看着它,心里想:“我不明白你为什么觉得这段代码有趣。更糟糕的是,我不明白你为什么要在第 14 行所示的 OnCalculate 函数中执行这样的操作。你能解释一下这里发生了什么吗?”亲爱的读者,如果你对这段代码的工作原理有疑问,那是因为你还没有将这些文章中展示的内容付诸实践。
执行此代码后,结果将与下方动画所示相似。

动画 01
在这种情况下,仅使用一张图片是不够的,因为观看动画时,你可以看到紫色线总是比价格线滞后一拍。有时这条紫色线会与当前价格线重合。但最有趣的问题是:为什么会发生这种情况?因为在大多数情况下,紫色线总是指向前一个报价的价格。然而,有时那个价格与当前价格一致。看起来代码 01 中显示的指标存在问题。
亲爱的读者,代码本身没有问题,平台也没有问题。事实上,紫色线正好起到了预期的作用:它指示了最后一个报价的位置。但是,如果 MetaTrader 在收到新报价后触发新的计算事件,而没有改变新报价的价格,则紫色线将与最新价格线重合。这正是我们在动画 01 中看到的现象。原因在第 18 行。因此,如果我们取最后的价格和新报价的价格,这两个值之间可能会有差异。在这种情况下,紫色线将始终滞后于价格线。然而,如果价格与前一个价格相同,则紫色线将与最新价格线重合。
非常有用,不是吗?许多交易者和程序员并不认同你刚刚学到的观点,他们认为,每当触发 Calculate 事件时,都是因为价格发生了变化。然而,你刚刚已经看到,情况并非总是如此。因此,深入理解代码优化至关重要,以免导致 MetaTrader 5 应用程序的性能大幅下降。
那么,在处理了一些简单且看似琐碎的事情之后,让我们来尝试一下处理一个事件。在本例中,我们将从最简单的开始:键盘事件处理。我认为键盘事件更简单,因为我们不需要启用或禁用它们。每次按下按键,MetaTrader 5 都会自动触发一个事件。如果图表上的任何应用程序具有 ChartEvent 事件处理函数,我们将能够相应地处理按键并按其做出反应。为了演示这个首个系统,我们将编写响应键盘事件的新代码;它易于理解,对于初学者来说,这段代码的用途很有意思。这段代码如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. switch(id) 17. { 18. case CHARTEVENT_KEYDOWN: 19. Print(lparam); 20. break; 21. } 22. }; 23. //+------------------------------------------------------------------+
代码 02
你是否曾好奇程序员是如何判断哪个键被按下的?程序员如何判断是按下了箭头键,还是按下了如 Home 或 End 这样的键?当你想到字母数字键时,可能会觉得这像是有一种魔力。但是其他键呢?事实上,程序员并不直接了解这一点,但他确切地知道如何去发现。此处使用的代码与上述类似。通过运行这段代码,你可以找出按下特定键时会生成哪个数字代码。
实际上,当按下某个键时,实际生成的键码与此处显示的值不同。然而,由于操作系统(可以这么说)会“翻译”所按按键的值,因此当我们运行像代码 02 中所示的应用程序时,MetaTrader 5 会告诉我们应该使用哪个值。下面的动画展示了结果。

动画 02
请注意,这一点很容易理解。当按下某个键时,其值会在终端中显示出来。因此,这个值可以用来检查按下了哪个键。请记住,MQL5 标准库中有一个函数,可以让你检查是否按下了某些特殊键。但该函数对你刚刚学到的知识进行了一个抽象。非常有用,不是吗?
好的,但是我们该如何使用它呢?处理这件事似乎有点毫无意义。那么,让我们来看看如何利用通过代码 02 获得的信息。为此,我们将修改代码 01,使其执行不同的操作。修改如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. #define def_KEY_UP 38 06. #define def_KEY_DOWN 40 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 11. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 12. 13. return INIT_SUCCEEDED; 14. }; 15. //+------------------------------------------------------------------+ 16. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 17. { 18. return rates_total; 19. }; 20. //+------------------------------------------------------------------+ 21. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 22. { 23. static int p = 0; 24. MqlRates rate[1]; 25. 26. switch(id) 27. { 28. case CHARTEVENT_KEYDOWN: 29. switch ((int)lparam) 30. { 31. case def_KEY_DOWN: 32. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 33. break; 34. case def_KEY_UP: 35. p = (p > 0 ? p - 1 : p); 36. break; 37. default: 38. return; 39. } 40. Comment(StringFormat("Current bar analyzed: %d", p)); 41. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 42. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 43. break; 44. } 45. ChartRedraw(); 46. }; 47. //+------------------------------------------------------------------+ 48. void OnDeinit(const int reason) 49. { 50. Comment(""); 51. ObjectDelete(0, def_NameObj); 52. ChartRedraw(); 53. }; 54. //+------------------------------------------------------------------+
代码 03
代码 03 比今天文章中讨论的其他代码要复杂一些。即便如此,这里也没有什么新内容。因此,我们将仅关注新内容,而忽略其余部分,因为其余部分更容易理解。
好的,在第 23 行,我们启动了一个本地计数器。这将显示我们相对于图表上最新 K 线偏移了多少根 K 线。为了明确这一点,我们使用了第 40 行代码直接在图表上显示某些信息。第 29 行至第 39 行之间的代码已用于确保仅捕获和处理之前定义的键。在第 41 行,现在将为计数器 p 当前指示的 K 线检索价格数据。请注意,我们仅检索一根 K 线。第 42 行随后在图表上移动对象。因此,当这段代码被执行时,我们会得到下面动画中所示的结果。

动画 03
这确实很有意思,但也稍微有些让人困惑,因为我们必须数清这些 K 线才能明白当前正在使用的是哪一个。然而,我们差点忘了提及第 50 行的作用。当我们从图表中移除指标时,我们使用第 50 行来移除原有的文本。这是一个非常简单的操作,但有些人却会忘记执行,从而导致图表变得不必要地杂乱无章。
目前为止,一切进展顺利。我们已经知道如何对对象进行一些简单的小操作。然而,我们现在需要学会做一件事,因为,你可能已经注意到了,到目前为止,所有对象都被赋予了相同的名称,这既不有趣也不实用。由于图表上的每个对象都必须有唯一的名称,因此我们需要想出一种方法,以便同时将多个对象放置在图表上。为了解释如何做到这一点,让我们继续讨论一个新的话题。
同一张图表上有多个对象
有一个问题让许多初学者感到困惑:他们并不完全理解代码是如何工作的,但却对此很感兴趣。当我们决定将不同的代码片段组合起来,甚至在同一图表上使用不同的程序,且每个程序都旨在图表上创建某个对象时,最终会出现问题。而且,我这么说还算客气,用户并没有手动将对象添加到图表中。我只考虑了用户正在尝试使用代码来为他们完成这项任务这一事实。
问题在于,为了简化开发人员和 MetaTrader 5 平台本身的工作,图表上的每个对象都必须拥有唯一名称。如果我们尝试创建一个名称已经在图表上存在的对象,这将会引发问题。这是因为,当我们试图操纵图表上实际并不存在的东西时,我们仍然会操纵图表上存在的其他东西。这会给初级程序员甚至经验较少的用户带来极大的困惑。
由于在大多数情况下,程序或应用程序都是为特定目的而开发的,因此开发人员基本上不会费心向用户解释,让他们注意图表上对象的名称。原则上,每个开发者都认为用户不会犯试图将多个应用程序合并为一个的错误,甚至不会在同一时间在同一图表上使用不同的应用程序。
但回到编程本身,真正的问题在于许多初学者只使用复制粘贴技术,而没有真正理解他们在代码中做什么,以及这些操作在运行时会产生什么后果。为了展示许多初学者使用的这种复制粘贴方法可能引发的问题,让我们来编写一个小代码示例。如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. #define def_KEY_UP 38 06. #define def_KEY_DOWN 40 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. ObjectCreate(0, def_NameObj, OBJ_VLINE, 0, 0, 0); 11. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrRoyalBlue); 12. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 13. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 14. 15. return INIT_SUCCEEDED; 16. }; 17. //+------------------------------------------------------------------+ 18. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 19. { 20. return rates_total; 21. }; 22. //+------------------------------------------------------------------+ 23. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 24. { 25. static int p = 0; 26. MqlRates rate[1]; 27. 28. switch(id) 29. { 30. case CHARTEVENT_KEYDOWN: 31. switch ((int)lparam) 32. { 33. case def_KEY_DOWN: 34. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 35. break; 36. case def_KEY_UP: 37. p = (p > 0 ? p - 1 : p); 38. break; 39. default: 40. return; 41. } 42. Comment(StringFormat("Current bar analyzed: %d", p)); 43. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 44. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 45. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 46. break; 47. } 48. ChartRedraw(); 49. }; 50. //+------------------------------------------------------------------+ 51. void OnDeinit(const int reason) 52. { 53. Comment(""); 54. ObjectDelete(0, def_NameObj); 55. ChartRedraw(); 56. }; 57. //+------------------------------------------------------------------+
代码 04
这里我们展示了一个编程初学者常犯的相当粗糙但又很常见的错误,他们的目标显然是在图表上画一个十字形标记。然而,当这段代码执行时,结果如下所示:

动画 04
嗯,奇怪。在 OnInit 事件处理程序中,我们创建两个对象:一个是水平线,另一个是垂直线。那么,为什么只显示了垂直线呢?但这还不是全部。如果你仔细观察,你会发现动画 04 中显示的垂直线原本并不是这个颜色。那么,这里究竟发生了什么?
问题在于,尽管我们指定了应创建两个对象,一个在第 10 行,另一个在第 12 行,但只有第 10 行声明的对象被创建。这是因为,当执行第 12 行时,MetaTrader 5 会报告图表中已经存在一个与第 12 行定义的对象同名的对象。实际上,它尚未真正显示在图表上,而只是进入了执行队列,随时准备尽快被添加到图表上。
无论如何,第 12 行声明的对象的创建都将被拒绝。由于大多数代码相对简单,许多人最终会忽略这些细节,更糟糕的是,他们往往对研究文档缺乏兴趣。但这主要是因为缺乏经验:初学者程序员最终会认为错误可能出现在其他地方。但当这种情况发生在试图在同一图表上同时使用两个或更多应用程序的用户身上,且对象名称相同时,情况就会变得非常混乱。尽管这种情况极为罕见,但你仍有可能遇到。
在图表上处理多个对象的方法有多种,但无论如何,如果我们尝试创建一个与图表上已存在对象同名的对象,在处理最终会出现的错误时可能会遇到困难。在某些情况下,我们可以使用具有相同名称的不同对象,但不能同时使用。鉴于这是一个非常特殊的情况,我们暂时不会深入细节,以免让那些正在学习如何正确操作以避免奇怪错误的人感到困惑。
无论如何,最简单的解决方案之一,适用于几乎所有类型的情况,就是创建一个对象数组,其中图表上的每个对象都由该数组中的一个元素来表示。在我看来,这或许是目前能想到的最简单、最通用的方案之一。因此,同样的代码 04 可以按如下方式更改:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 15. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 16. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch(id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. switch ((int)lparam) 35. { 36. case def_KEY_DOWN: 37. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 38. break; 39. case def_KEY_UP: 40. p = (p > 0 ? p - 1 : p); 41. break; 42. default: 43. return; 44. } 45. Comment(StringFormat("Current bar analyzed: %d", p)); 46. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 47. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 48. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 49. break; 50. } 51. ChartRedraw(); 52. }; 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. Comment(""); 57. for (uint c = 0; c < gl_Objs.Size(); c++) 58. ObjectDelete(0, gl_Objs[c]); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
代码 05
当然,我们在这里所做的一切都力求做到最简单。任何阅读过这些文章并将其付诸实践的人都知道,以目前的知识水平,完全有可能开发出更为复杂的解决方案。然而,真正让我们感兴趣的始终是最终结果。这可以从以下动画中看到:

动画 05
请注意,结果与动画 04 中显示的结果完全不同。然而,值得注意的是,如果我们对比代码 05 和代码 04,可以发现为了解决代码 04 造成的问题,我们几乎没有对代码进行任何修改。最显著的变化是代码 05 第 09 行可见的声明。但仅凭这一点还不足以解决所有问题,因此还需要做出另一项改变。可以在代码 05 的第 07 行看到。
请注意,在这种情况下,我们将对象名称定义更改为宏。在这个宏中,真正重要的事情是:使用了 ObjectsTotal 函数。MQL5 标准库中的这个函数告诉我们图表上存在多少个对象,或者有多少个对象在队列中等待放置到图表上。正因如此,无论我们在图表上放置多少个对象,即使我们使用的是来自不同作者的不同程序,每个对象也总是会有一个唯一的名称。如果我们删除一个对象,然后创建一个新的对象,即使它们的名称非常相似,这两个对象也会是唯一的,这正是因为我们在宏中看到的 ObjectsTotal 函数。
一个非常简单但非常有效的方法。至于其他部分,对于研究过这些文章的人来说,所做的改动非常容易理解,因此无需赘述细节。尽管这一切都很简单,但以下几点仍需牢记:由于宏总是会创建一个唯一的名称,因此我们需要将已创建对象的名称存储在某个地方。在本例中,名称存储在第 09 行的数组中。如果没有它,我们就无法找到图表上已经存在的对象。
在结束这篇文章之前,我想再解释一件事,在未来的几篇文章中,我们会经常用到它。但是,为了更好地划分各个主题,让我们进入新的章节。
简称
到目前为止,所有展示的代码实际上就像一个单独的代码,通常它们应该一个接一个地使用。然而,在实际操作和真实条件下,事情并不总是那么简单和令人愉快。因此,作为程序员,我们需要采取一定的预防措施,并知道如何去做某些事情。其中之一是需要能够访问图表上显示的指标。我们稍后会看到这样做的原因,那时我们开始开发一些,可以说,更复杂的东西。
作为用户,您可能会想:“我当然知道如何访问指标;我只需要按下CTRL + I ,就会打开一个窗口,让我可以访问图表上的指标。如果我想把它放在图表上,我只需要进入导航器,找到所需的指标,就完成了。然后我把它拖到图表上,剩下的就交给 MetaTrader 5 来完成。很简单。”
好的,正如我们之前所说,这是从用户的角度来看。然而,作为程序员,我们采用不同的工作方式,因此我们需要从不同的视角来理解这些事物是如何运作的。在这种情况下,这样的用户界面对我们毫无帮助;相反,它甚至可能使我们更难理解我们稍后会探讨的一些细节。
既然我希望你们开始习惯像程序员一样思考,我们需要更多地谈谈,站在程序员的角度,如何看待 MetaTrader 5 中哪些操作可行、哪些不可行。我们还需要了解 MetaTrader 5 如何识别我们在 MQL5 代码中输入的内容。事实上,MQL5 的设计初衷就是为了告诉 MetaTrader 5 我们想要做什么。
与用户通过 MetaTrader 5 图表查看运行的应用程序的方式不同,我们程序员有办法向 MetaTrader 5 查询当前正在运行的应用程序的信息。这样我们就可以自动添加或删除列表中的应用程序。在大多数情况下,问题在于识别图表上显示的指标。之所以会出现这种情况,是因为与普遍看法相反,一个指标可以有一个代码内部使用的名称,而这个名称与程序可执行文件的名称完全不同。
这个名称将在代码中显示,用户不能 — 或者更确切地说,不应该 — 更改它。尽管作为程序员的你当然可以允许这种情况发生。然而,不建议这样做,以避免出现目前难以解释的问题。
所以,这意味着我们在处理指标时,可以为我们的应用程序指定一个名称。好的,我们该怎么做呢?这是通过 MQL5 标准库中提供的函数实现的:IndicatorSetString 。尽管这看似微不足道,甚至有些多余,但为我们的指标赋予一个简短的名称将大大简化多项任务。此函数不仅可用于指定指标名称。它还有其他更重要的用途。但首先,让我们看看如何定义指标的内部名称。这是通过类似以下的方式完成的:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. IndicatorSetString(INDICATOR_SHORTNAME, "Version Demo"); 07. 08. return INIT_SUCCEEDED; 09. }; 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. return rates_total; 14. }; 15. //+------------------------------------------------------------------+
代码 06
用户给代码 06 的可执行文件取什么名字都无关紧要。最终,我们感兴趣的是第 06 行指定的名称。指标名称可以指定为任何内容。但是,最好选择一个合适的名称,用简短的词语描述指标的用途。这就是我们所说的简称。
但为何要定义这些东西呢?这是做什么用的?嗯,现在解释起来有点困难。为了真正清楚地解释为什么我们需要为指标设定一个简短的名称,我们必须在现阶段展示一些东西,但这只会使情况变得更加复杂,而非澄清。然而,我希望你总是能为你打算实施的任何指标起一个简短而有意义的名字,因为这样以后当你需要使用显示的指标时,就不必再费心去想这样的名字了。无论如何,这只是一行代码,但它能让你在未来避免许多问题。
最后的探讨
在今天的文章中,我们探讨了如何以简单的方式控制一些对象。我们看到,自定义程序如何在同一图表上放置多个对象。此外,我们开始认识到,为我们计划实现的任何指标分配一个简短的名称非常重要。
所有这些乍一看似乎并无多大关联的概念,其实应被视为一个整体。无论如何,今天的文章仅旨在介绍一种解决各种问题的简单方法,因为当我们开始学习时,往往会犯许多错误,尽管这些错误简单且易于避免,但它们会严重打击我们的士气。我们开始认为编程只是少数人的专属,而事实上,每个人都应该学习编程,以便将自己的想法变为现实。
以防万一,我会把文中展示的代码作为附件附上,方便大家练习和学习,并避免我们在学习时都会犯的同样错误。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15987
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
您应该了解的MQL5向导技巧(第七十八部分):鳄鱼振荡器与AD指标的市场韧性策略
MQL5中的量子神经网络(第三部分):基于量子比特的虚拟量子处理器
从新手到专家:用MQL5实现动态新闻头条(八)—— 新闻交易快捷交易按钮
MQL5 向导技巧(第七十七部分):使用 Gator 振荡器与积累/派发振荡器(AD Oscillator)