从基础到中级:指标(一)
在上一篇文章,“从基础到中级:事件(二)”中,我们介绍了如何在 MetaTrader 5 平台的不同会话之间以或多或少永久的方式保存信息。我知道有些人可能认为这样的材料是不必要的。但是,当您需要在会话之间保存信息时,您会记住这一点并说:“朋友,谢谢你的建议,非常感谢。” 总之,
我们的探索才刚刚开始。由于我们已经掌握了创建小程序的基本概念,并且已经了解了事件驱动编程的工作原理,现在是时候了解一些许多人试图自己实现的基本概念了,即使它们已经设计好了,默认情况下不需要实现,只需根据需要使用即可。
在这种情况下,我指的是几乎所有 MetaTrader 5 应用程序中默认存在的选项卡,尽管我们可以有更多或更少的选项卡。然而,作为程序员,如果你学习如何使用这些选项卡,你将成为其他程序员提供给你的界面的观察力更强、要求更高的用户。
那么,我们今天开始一个新的话题。
默认选项卡
任何完全用 MQL5 编写的代码都可以有一个基本设置窗口。此窗口不会出现在所有应用程序中;在脚本和服务等应用程序中,它可能存在也可能不存在。然而,在指标和 EA 交易中,我们总是会看到一些元素,或者更确切地说,是一些默认选项卡。它们并非由程序员创建,而是在 MetaTrader 5 环境中定义,这意味着我们有一个配置任何指标或 EA 交易的标准。
但出于各种原因,许多程序员经常添加不必要的配置元素。这是因为这些元素已经可以放入默认情况下向用户显示的选项卡中。问题是,您知道如何访问默认选项卡上定义的元素吗?我是以程序员的身份问这个问题,而不是以用户的身份。
我所说的默认选项卡,指的是下面这组图片中展示的内容:

图 01

图 02

图 03

图 04
这些是默认的指标选项卡。由于我们目前专注于学习事件在指标中的作用方式,正是因为它更简单,我们应该在继续处理更复杂的事情之前尽可能多地了解它。不过,这个默认的选项卡系统相当有趣且设计精良,因为它完美地适应了各种情况。事实上,在大多数情况下,不需要实现任何额外的输入参数。但是为什么许多程序员要创建额外的输入参数呢?原因是,当 MetaTrader 5 提供的默认方案无法满足我们的需求时,这是必要的。但是,在开始添加各种不必要的元素之前,最好先了解如何使用这些选项卡,这样我们的应用程序就不会偏离 MetaTrader 5 的标准。
本质上,如果我们认为有必要,唯一需要管理的选项卡是图像 01 中的选项卡。这是因为在此选项卡上,我们可以添加元素,使其在指定此应用程序的目的方面更具说明性。这样做非常简单。然而,由于我们将继续只关注教育方面,我们不会在此选项卡中添加元素。总之,你应该考虑如何处理第一个选项卡,特别是如果你想让你的应用程序易于访问的话。
要添加或修改图 01 中所示的选项卡,我们需要告诉编译器要在其中放置什么内容。为此,我们将使用编译指令。此指令用作 #property 。该指令允许我们定义许多内容,从应用程序版本到互联网上特定位置的链接。更详细的信息可以在文档中找到。你可能已经看到这个指令是如何应用于我所有的文章代码的。实际上,我默认定义的唯一内容就是版权。让我们看看如何定义另一个例子,以便您了解我们可以做什么。为此,我们将使用以下代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.0" 04. #property icon "Example.ico" 05. #property description "This is a demonstration application whose purpose is only didactic.\nIt is part of a sequence of articles published in the MQL5 community." 06. #property link "https://www.mql5.com/pt/articles/15794" 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. return INIT_SUCCEEDED; 11. }; 12. //+------------------------------------------------------------------+ 13. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 14. { 15. return rates_total; 16. }; 17. //+------------------------------------------------------------------+
代码 01
因此,执行代码 01 时,我们会看到图 01 的显示方式有所不同。这一变化可以从下面看出:

图 05
请注意,现在我们有一个与标准图标不同的图标,以及对应用程序用途的描述。所有这些都在第 02 行和第 05 行之间定义。请注意,我们需要指定用作图标的目录和图像文件。在附录中,我设置了所有内容,以便您可以进行实验并了解如何放置所需的图标。此时,如代码 01 所示,图标文件应位于指标代码所在的文件夹中。但是,它也可以放在其他位置;只需指定位置,编译器就能找到所需的文件。
“但是等一下。这里缺少一条信息:我们在代码 01 的第 06 行声明的信息。它去哪儿了?”实际上,之前显示在选项卡上的这些信息,现在已经与其他信息关联起来了。例如,将光标悬停在版权信息上,我们将看到如下图所示的内容:

图 06
这样,我们就可以确认所有信息都存在,如图 06 所示。如果我们点击链接处,浏览器就会打开,并将我们带到代码 01 的第 06 行指定的位置。因为在大多数情况下,正如我所假设的那样,你是在创建个人使用的东西,所以你可以忽略这些东西,因为它们不需要实现。尽管在某些情况下,我们需要指示编译器如何生成代码,或者更确切地说(因为这个短语可能会让人感到困惑),但在某些特殊情况下,即使将代码用于个人目的,我们也需要设置这些编译属性。
其中一个例子正是创建一个指标,其目标是在图表上绘制一些东西。但是,您可以跳过本主题开头提到的其他选项卡的解释。请大家保持冷静和耐心,我们稍后会再谈到它们。但现在我们需要做些别的事情来赋予这些选项卡意义。
基本指标
到目前为止,我们所做的只是生成大量信息并将其打印到终端上以便显示。最后,是时候在图表上看到一些东西了。为了使其更美观、更易于理解,我们将使用标准的 MetaTrader 5 风格的图表。我这样做只是为了演示,但您可以使用最适合您或您最喜欢的配置的图形。
好吧,在前两篇文章中,我们解释了 MetaTrader 5 生成的两个主要事件是如何工作的:去初始化和初始化。两者都是为了指导我们的应用程序如何运行。但是,所有指标都需要两个事件:一个是已经讨论过的 Init 事件,另一个是 Calculate 事件。
亲爱的读者们,现在请密切关注,因为这非常重要。OnCalculate 函数必须捕获 Calculate 事件,该事件必须存在于指标代码中。但是,我们有同一个函数的两个版本 —— 也就是说,MQL5 默认提供了使用重载函数的功能。接下来展示其中一个版本,另一个版本随后演示。
int OnCalculate( const int rates_total, // price[] array size const int prev_calculated, // number of handled bars at the previous call const int begin, // index number in the price[] array meaningful data starts from const double& price[] // array of values for calculation );
int OnCalculate( const int rates_total, // size of input time series const int prev_calculated, // number of handled bars at the previous call const datetime& time[], // Time array const double& open[], // Open array const double& high[], // High array const double& low[], // Low array const double& close[], // Close array const long& tick_volume[], // Tick Volume array const long& volume[], // Real Volume array const int& spread[] // Spread array );
但 MQL5 开发人员为什么要这样做呢?我认为主要原因是使指标的运行更加高效和稳定。事实上,有几种计算方法,特别是考虑到指标最常计算平均值。因此,开发人员注意到他们可以提高 MetaTrader 5 平台的性能,决定允许指标跳过其中一些计算,因为平台本身可以提前向我们提供结果。现在这对你来说可能没有多大意义,但我向你保证,随着你的练习和学习,你会明白这有很大的价值。
那么,我应该使用哪个重载版本呢?答案是:这完全取决于具体情况。这并不是说必须以牺牲另一个版本为代价来使用一个版本。这两个版本本质上做的是同一件事 —— 它们都接收 Calculate 事件。就是这些,接下来会发生什么,取决于我们的计划和我们需要做的事情。
然而,有一点值得注意,必须承认这是平台开发人员的一项成功改进。根据用户使用的 OnCalculate 的版本,他们可以访问或不访问图 02 中显示的选项卡。如果您搜索包含旧版本 MetaTrader 5 的图像、动画或视频的文章,您会注意到图 02 中的选项卡几乎每次都会出现,即使它完全不必要。
但在某个时候,这一切都消失了。如果使用参数较少的版本,则可以访问图 02 中显示的选项卡。如果使用带有更多参数的版本,则此选项卡将不可用。这正是为什么我说一切都会在适当的时候得到解释。如果早些时候提到,那就没有任何意义了。但现在一切都说得通了,尤其是当我们实现指标之后,就更说得通了。
首先,我们将创建一个非常简单的版本,几乎所有事情都由 MetaTrader 5 完成和处理。作为程序员,我们将尽可能做到最低限度,使其发挥作用,并尽可能具有教育性。我们将一步一步地进行。虽然代码本身很容易理解和执行,但只要循序渐进,任何人,即使是只有基本知识的初学者,只要学习这些文章,都能学会。
我们的代码开头如下:
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. //+------------------------------------------------------------------+
代码 02
请注意,目前尚未实现任何内容。该代码仅包含基本内容,以便编译器知道如何创建所需的应用程序 —— 一个将显示本文开头所述所有选项卡的指标。接下来,我们将介绍我们的指标。为此,我们必须告诉编译器需要创建什么。这可以通过两种方式实现:动态实现(这要复杂得多,以后再解释)和静态实现(这要简单得多,也更直接)。后者非常适合小代码,针对那些刚刚起步或只是想创建一个简单快速的指标的人。为此,我们将修改代码 02 以获得以下版本:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_type1 DRAW_LINE 05. #property indicator_color1 clrBlack 06. //+------------------------------------------------------------------+ 07. int OnInit() 08. { 09. return INIT_SUCCEEDED; 10. }; 11. //+------------------------------------------------------------------+ 12. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 13. { 14. return rates_total; 15. }; 16. //+------------------------------------------------------------------+
代码 03
这是我们需要采取的第二步。尽管第 05 行中的语句与使用标准 MetaTrader 5 系统时仍然存在的一个小错误有关。如果没有这行代码,即使是非常简单的指标,您也无法更改指标图的颜色。然而,最重要的语句在第 04 行。在这一行中,我们精确地指定了要在图表上显示的内容。它将是一条价格线,但具体是哪个价格呢?要绘制的线的定义和特性是什么?别担心,我们会知道的,耐心点。
在定义了我们正在创建的指标类型之后,我们需要定义更多的特征。为此,我们必须参考 MQL5 文档来了解如何声明以下项目。然后我们返回文档,搜索术语 #property,以查找与第 04 行声明的内容(即指标类型)相关的信息。当我们搜索此信息时,我们会在下图中看到一些突出的内容:

图 07
图 07 中所示的项目对我们非常重要,因为如果没有它,我们将无法指示 MetaTrader 5 如何显示指标。因此,我们搜索枚举 ENUM_DRAW_TYPE。如果我们查阅相关文档,会看到一个类似于下方的表格:

图 08
我们需要的信息正是图 08 中绿色突出显示的内容,即我们需要一个数据缓冲区。不同类型的指标需要不同的内容,由于我们创建的程序非常简单,我们只需要一个缓冲区。有了这些信息,我们就可以回到代码 03,并向其中添加一些元素。然而,我们首先需要更多的信息。这涉及到指标需要在屏幕上绘制的元素数量。要查找上述信息,我们必须查看 #property。如下所示。

图 09
为了获得第二条信息,我们将参考同一张表。现在我们需要使用一个等于图 10 中这两个值之和的值:

图 10
收集到所有信息后,我们可以回到代码 03,并向其中添加一些元素。代码如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_type1 DRAW_LINE 05. #property indicator_color1 clrBlack 06. //+----------------+ 07. #property indicator_buffers 1 08. #property indicator_plots 1 09. //+------------------------------------------------------------------+ 10. int OnInit() 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. //+------------------------------------------------------------------+
代码 04
查看代码 04,我们在第 07 行看到了我们正在寻找的语句,该语句我们在图 08 中找到了。第 08 行指定了另一条信息,该信息等于图 10 中值的总和。现在我们可以让这个指标发挥作用了。最后一步如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_type1 DRAW_LINE 05. #property indicator_color1 clrBlack 06. //+----------------+ 07. #property indicator_buffers 1 08. #property indicator_plots 1 09. //+----------------+ 10. double gl_buffer[]; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. SetIndexBuffer(0, gl_buffer, INDICATOR_DATA); 15. 16. return INIT_SUCCEEDED; 17. }; 18. //+------------------------------------------------------------------+ 19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 20. { 21. for (int c = prev_calculated; c < rates_total; c++) 22. gl_buffer[c] = price[c]; 23. 24. return rates_total; 25. }; 26. //+------------------------------------------------------------------+
代码 05
您眼前的就是一个完整且功能齐全的指标。我们可以要求它显示 MetaTrader 5 能够提供的任何信息,它都能完美地做到这一点。“但这怎么可能呢?我已经看过其他指标的代码,它们的代码要长得多。这不可能。我已经尝试创建了好几个指标,它们的代码量都比你展示的要多得多。我觉得这行不通;我想验证一下它的功能。”
亲爱的读者们,我理解你们的惊讶。这是因为你学过的任何指标代码都包含比我们这里所用到的元素多得多的元素。但是,为了向您展示此代码的工作原理,让我们看看动画:

动画 01
在动画 01 中,我演示了如何放置指标,然后更改其颜色。请注意,它非常简单直观。

动画 02
在动画 02 中,我们将展示如何更改指标线的样式。再次强调,非常直观。

动画 03
在动画 03 中,我们可以看到如何改变指标所用线条的粗细。在这种情况下,如果 MetaTrader 5 能够支持不同粗细的线条样式就好了。在开发者实现此功能之前,我们将不得不适应 MetaTrader 5 目前的现状。

动画 04
最后,在动画 04 中,我们看到如何更改指标的计算类型。如您所见,一切都非常简单明了,而且不会影响指标的运行。我们只能猜测图片 04 中的选项卡是如何工作的,但由于这类东西非常直观和简单,我们只需要保持好奇心并使用此选项卡即可。
我觉得现在没必要解释,因为这个选项卡仅用于配置操作系统环境。新手交易者很少会尝试操纵这个选项卡。但是,作为程序员,你们需要了解它的工作原理以及如何在代码中实现它。是的,无需触碰此选项卡即可配置指标的操作 —— 使用纯 MQL5 —— 但我们稍后会在更合适的时间讨论这个问题。
现在我们已经看到了代码的工作原理,让我们看看在最后的实现步骤中添加了什么。这里解释的内容稍后将以更高级的形式介绍。但是,你需要从一开始就理解为什么以及如何实现最后一步(从第 10 行开始)中的代码行。
正如本系列第一篇文章所解释的那样,通常不建议使用全局变量。然而,第 10 行声明的变量是一个特例:我们确实需要一个全局变量,因为需要实现我们稍后将要探讨的几个要点。但现在,您需要了解此变量将包含将在图表上绘制的数据,从而创建动画中可见的指标。
为此,我们必须指示 MetaTrader 5 使用哪个变量,因为在实际代码中可以声明多个变量。第 14 行的内容提供了这类信息。原则上,就是这些。之后,我们可以开始捕获 Calculate 事件,并使用 MetaTrader 5 提供的数据为第 10 行声明的变量赋值。
这其实并不难;事实上,在大多数情况下,这都很简单。我们需要做的就是计算一个值,并将其分配到第 10 行声明的数组中的特定位置。现在,请注意。在解释数组这个主题时,我们说过数组有两种类型:动态数组和静态数组。然而,用于存储指标绘图数据的每一个数组都必须是动态的。但是,您绝对不能为其分配内存。MetaTrader 5 会根据需要自动分配。因此,我们可以使用第 22 行的代码。在第 14 行,我们指定该变量将用于存储要在图表上绘制的指标数据。之后,MetaTrader 5 将完全控制内存分配。
但还有另一个重要的方面,理想情况下,任何 MetaTrader 5 应用程序都应该尽可能高效。这样做是为了避免耗费过多时间,并保持平台稳定性。因此,请注意第 21 行是如何创建循环的。你可能会觉得每次触发 Calculate 事件时,都会从头到尾读取价格数组中的所有数据。然而,事实并非如此,或者至少不应该如此。
从本质上讲,MetaTrader 5 中存储了一些内部值来管理应用程序操作。然后,在我们的指标第一次捕获 Calculate 事件期间,rates_total 值将指向价格数组中存在的数据量。prev_calculated 值可能为零,也可能不为零,但它肯定会指向序列中的第一个数据点。这是可以改变的,我们稍后会看到。现在,您只需要理解所解释的内容:如果您的代码正确实现并遵循规则,则只有第一次才会满足此条件。
在下一次调用中,prev_calculated 值可能等于 rates_total,也可能不等于。如果保持不变,则意味着没有发生重大变化。如果它较小,则执行循环以更新要绘制的值。
因此,如果我们正确实现代码,第一次执行将很慢。然而,在 Calculate 事件的所有后续捕获中,将执行最少数量的交易,因此平台可以快速进行任何分析并在屏幕上显示结果。
“好的,但我有一点不明白:如果 prev_calculated 和 rates_total 的值相同,怎么会发生 Calculate 事件呢?”这样做岂不是浪费时间吗?因为第 21 行的循环根本不会执行任何操作。”是的,没错。但是,MetaTrader 5 不会随意触发计算事件。只有当该交易品种的价格发生变化时,它才会触发。因此,在市场剧烈波动时期,将会发生大量的事件。如果你的代码优化不好,平台随着时间的推移会变慢,但问题不在于平台,而在于优化不好的应用程序消耗了时间和资源。
总结性思考
在本文中,我们将创建第一个功能齐全、实用的指标。我们的目标不是展示如何创建应用程序,而是帮助读者了解如何安全、简单、实用地开发自己的想法,并让你有机会以安全、简单和实用的方式应用它们。
由于这个主题应该被彻底理解和研究,我不会将其扩展到必要的范围之外,因为这会使材料更加密集和复杂,阻碍冷静的学习。附录中包含此处列出的两个代码。有了它们,您可以按照文章中提供的分步说明进行操作。
下一篇文章我们将继续今天的主题,因为声明 OnCalculate 函数的第二种形式还需要进一步研究。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15794
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5 交易策略自动化(第 23 部分):带追踪止损与篮子交易的区间补仓系统
您应当知道的 MQL5 向导技术(第 64 部分):运用 DeMarker 和包络通道形态,搭配白噪内核