English Русский Español Deutsch 日本語 Português
preview
从基础到中级:指标(二)

从基础到中级:指标(二)

MetaTrader 5示例 |
17 2
CODE X
CODE X

在上一篇文章“从基础到中级:指标(一)”中,我们开始就如何利用最少的知识创建一个非常简单轻便的指标展开实际讨论。虽然许多人可能认为实现结果需要广泛的编码知识,但事实证明,即使是初学者,只要稍加努力,也可以创建相对简单和实用的东西。此外,由于大部分工作都由 MetaTrader 5 处理,因此处理方式相当优雅。我们有责任对事件做出正确的回应。然后,MetaTrader 5 将知道如何处理我们在这些事件中处理的剩余信息。

你可能会想,“但是我们设法创建的这段代码与我以前见过的任何指标都不一样。虽然我们成功地在图表上画出了一些东西,但这与我的预期不太一样 —— 我希望我能做得更多。”

好吧,亲爱的读者,你可能会认为这一切看起来并不令人印象深刻,但你必须记住,知识不是一夜之间获得的:它需要培养。本系列文章的目标正是:培养和传播知识。这不是遵循“这样做是因为它只会以这种方式工作”的原则。那么,让我们回顾一下上一篇文章的内容。


如何实现周期为 X 的移动平均线

在上一篇文章中,我们介绍了如何在图表上绘制信息。我们用简单易懂的方式完成了这项工作。然而,本文的目的是表明有更好的方法来使用 MetaTrader 5 中的可用资源,因此我们没有深入探讨具体的标准或可能性。

然而,上一篇文章中所展示的内容可以通过其他方式来实现。但我想很多人都想看看如何实现更常见的移动平均线系统。换句话说,能够创建类似于著名的九期指数移动平均线的移动平均线是很有趣的,因为它是一种非常有利可图的交易模式,尽管它非常简单,但很难遵循。

因此,您可能正在考虑如何将上一篇文章中实现的无聊代码变成更有趣的东西。实现这一目标可能更具挑战性。现在,我认为你必须用更多的代码行来创建一些东西,而不是像前一篇文章中所示的那样。

好吧,挑战已经接受:今天我们将用最少的代码创建一个移动平均线指标 —— 代码越少越好。但如果对许多人来说这是一个挑战,我觉得它无聊而单调。在我们了解如何做到这一点之前,让我们回顾一下上一篇文章中的代码:

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. //+------------------------------------------------------------------+

代码 01

我认为你已经练习过代码 01 了,因为它非常简单有趣,而且能帮助我们理解一些原本难以理解的东西。现在,回到正题:我们如何让这段代码计算并绘制移动平均线?为了实现这一目标,我们必须首先做出一些决定。它们不是强制性的,但对于练习如何思考我们想要实现的代码很有用。

我们需要做的第一个决定是我们想要使用的计算类型。是的,移动平均线计算方法之间存在差异。有加权和非加权移动平均线。这种权重可以基于数量、价格或时间,了解这一点并知道如何选择非常重要。对于指数移动平均线,它按时间或包含的周期数加权。VWAP 是按交易量加权的价格值,还是反过来?谁知道答案?无论哪一种情况,时间都不会被考虑在内。对于算术移动平均值,我们没有这样的加权,并且仅考虑数据本身而不是其相对于其他数据的变化来计算值。为简便起见,我们将这些平均值分别称为“指数移动平均值”和“简单移动平均值”。

信息之间的关系决定了加权因子。然而,我们不会深入研究无聊的数学细节,因为在这种情况下没有必要。但我们至少需要知道我们要实现的计算类型的公式。如果我们不了解它是如何计算的,我们怎么能实现它呢?由于算术移动平均值的计算非常简单(我们只需要在一定数量的周期内加上和减去值),我们将考虑一个稍微复杂的情况:指数平均值,许多人认为这是这样的。

我们将采用的公式如下:

图 01

图 01 显示了计算任意周期数的指数平均值的表达式。在这个公式中,P 代表当前价格值。M 值是前一个指数平均值。N 是用于计算平均值的周期数。有了这些信息,我们就可以进入计算的实现阶段了。首先,我要澄清一下我最近提到的一个小细节:算术平均值是通过将 X 个值相加,然后将结果除以所使用的 X 值来计算的。

但是,当我们计算 X + 1,即下一个周期时,我们只需要从总和中减去第一个条目的值,再加上新条目的值即可。然后我们把结果除以 X。这就是为什么我说算术平均值很无聊,因为我们只需要加和减。然而,指数平均被认为需要大量的计算。然而,你会注意到它几乎与算术平均值相同。

现在,让我们看看图 01 中的这个表达式是什么意思:要计算当前的 EMA(指数移动平均值),我们需要前一个平均值。嗯 …… 看来我们有问题了,因为当我们开始计算时,我们还没有那个值。这就是为什么我们需要一个小的数学解决方法。解决方法很简单,只需将 M 设置为 0 即可。现在我们就可以进行计算了。要做到这一点,只需查看代码 01 并找到需要更改的地方就足够了。在这种情况下,只需要修改第 22 行。但为了避免误解,并向您展示没有必要使代码复杂化,我将提供完整的代码:

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] - (c ? gl_buffer[c - 1] : 0)) * (2. / (1.0 + 9)) + (c ? gl_buffer[c - 1] : 0);
23. 
24.    return rates_total;
25. };
26. //+------------------------------------------------------------------+

代码 02

请注意,我们在代码中唯一更改的是第 22 行,其他内容均未被修改。但是,执行代码后,我们会看到以下结果:

图 02

您可能想知道:“但是,我们在这张图 02 中看到的真的是九周期指数移动平均值吗?很难相信设置指数移动平均线所需的条件竟然如此之少。”为了验证这一点,我们将使用 MetaTrader 5 默认提供的指标,这将使我们能够确认我们所做的一切都是正确的。如下面的动画所示:

动画 01

在此动画中,我们看到背景是图 02 中的平均值,而前景是验证指标。请注意我们使用的数值。根据这些数值,指标将按照动画所示的方式放置在图表上。一切都很完美。实际上,代码 02 计算的是九周期指数平均值。但这怎么可能呢?

如果你已经学习了文章中提出的概念,你就会明白我们只是使用了图 01 中所示的表达式。但是,正如前面提到的,需要进行一些小的调整,因为我们需要将计算链中的第一个值设置为 0。因此,乍一看,这个表达可能有点令人困惑。但如果需要,您可以修改第 22 行的代码,使其更加简洁。因此,Calculate 事件处理函数将如下所示:

                   .
                   .
                   .
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21. #define def_N     9
22.    
23.    for (int c = prev_calculated; c < rates_total; c++)
24.    {
25.       double M = (c ? gl_buffer[c - 1] : 0);
26.    
27.       gl_buffer[c] = (price[c] - M) * (2. / (1.0 + def_N)) + M;
28.    }
29. 
30.    return rates_total;
31. 
32. #undef def_N
33. };
34. //+------------------------------------------------------------------+

片段 01

片段 01 的功能与代码 02 相同,只是多了几行代码。很好,但在我们继续之前,我必须提醒你,这段代码中存在一个小问题。不过,我并不打算解释这个问题是什么 —— 让我们稍微激起你的好奇心吧。这样,你肯定会试图弄清楚这个问题,并可以对编程的某些方面形成更清晰的看法,例如我们不应该总是以某种方式做事,因为数学表达式告诉我们这样做。

为了解决这个问题,需要将代码更改为如下所示:

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 > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
22.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * 2. / (1 + 9)) + gl_buffer[c - 1] : price[c] * 2. / (1 + 9));
23. 
24.    return rates_total;
25. };
26. //+------------------------------------------------------------------+

代码 03

无论如何,别担心;在附录中,您将找到这两个代码,以便您可以进行实验并尝试理解问题所在,当查看代码时,这可能看起来毫无意义。不管怎样,现在我想我们可以谈点别的了。我相信你可能会想:“亲爱的同事,如果我想使用不同周期数的指数移动平均线,该怎么办?每次我想使用不同的周期值时,都需要重新编译代码吗?这看起来不太好啊。”确实,到目前为止,我们看到的实现类型只涉及一个非常具体的模型。然而,如果你不掌握每个实现点,你就很难理解新出现的概念。

但考虑到已经解释的一切,我认为你已经准备好进入下一个实现阶段了。但是,为了避免混淆,让我们换个话题。


用户交互

很少有人想要实现上一主题中显示的代码,正是因为我们无法更改计算周期数。诚然,在某些情况下,例如计算 VWAP 时,图表上绘制的平均值不依赖于时间。然而,这只是一个相当特殊的例子,不会削弱其优点或解释和理解本主题将讨论的内容的必要性。

接下来,我们将研究如何允许用户更改一些计算参数,而无需重新编译代码。为此,我们必须通知 MQL5 编译器,我们希望允许用户或其他应用程序传达我们代码中实现的内部值。换句话说,我们将有一种常量,对于用户而言,它将是一个变量。但是,从我们应用程序的角度来看,我们即将创建的将被视为一个常量。是的,正如我们稍后将看到的,可以直接通过其他应用程序进行这些更改,尽管原则上,此资源旨在提供与用户更直接的交互。

首先,我们需要调整代码 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. 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.    double k = 2. / (1 + 9);
22. 
23.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
24.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price[c] * k);
25. 
26.    return rates_total;
27. };
28. //+------------------------------------------------------------------+

代码 04

请注意,更改非常简单,因为只添加了第 21 行,该行创建了一个用于调整指数平均计算的常数。现在,一个重要的点:我们需要在这里更改的值正是我们在代码 04 的第 21 行看到的“9”。要做到这一点,只需在代码 04 中添加一行新的代码。这样,我们就可以指定用于计算平均值的周期数。这一更改如下所示:

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. input uchar user01 = 9;
11. //+----------------+
12. double   gl_buffer[];
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
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.    double k = 2. / (1 + user01);
24. 
25.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
26.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price[c] * k);
27. 
28.    return rates_total;
29. };
30. //+------------------------------------------------------------------+

代码 05

就是这样,现在用户可以指定要使用的指数移动平均线的周期。然而,我们仍然需要解释一个小问题。当我们将此指标放在图表上时,我们将看到以下内容:

图 03

请注意,图中我们突出显示的是一个新选项卡。此选项卡之前并不存在,但它正是由于代码 05 中的第 10 行而创建的。也就是说,这是一个与用户交互的点,允许他们配置一些我们将在代码中使用的常量。目前为止,一切都很顺利。但是,选择新选项卡时,我们会看到下图:

图 04

问题就在这里,请注意图 04 中突出显示的信息。对于我们开发这款应用来说,这个值的作用非常清楚。然而,当我们添加更多值时,事情会变得有点混乱,甚至实现代码的人也可能很难理解每个值代表什么。在这里,我们需要关注另一个小细节。这条信息将以一种相当不寻常的方式传递给编译器 —— 至少,起初我觉得有点奇怪,尽管后来我习惯了。

如图 04 所示,突出显示的行与代码 05 第 10 行中提供常量名称的行相匹配。是的,亲爱的读者,第 10 行我们声明的不是变量,而是常量。在简单的情况下,我们可以给我们的代码和应用程序用户一个代表性的名称。然而,这并不总是适用的,因为在大多数情况下,最合适的是为用户显示简短的文字。那么,我们如何将文字放在图 04 中的这个突出位置呢?

这是最不寻常的部分。为此,我们需要添加一个字符串类型的注释。由于这种类型的注释必须放在常量声明之后,因此我们必须执行以下操作:

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. input uchar user01 = 9;             //Exponential average periods
11. //+----------------+
                   .
                   .
                   .

代码 06

仔细查看代码 06,特别是第 10 行。添加到这一行的内容就是我们所说的字符串注释。当我们在图 04 中显示的选项卡上的常量中执行此操作时,我们指示编译器使用此文本作为要显示给用户的字符串。为了更清楚地说明这一点,并确保您真正理解它,请看如果我们查看图 04 中显示的同一个选项卡,但使用代码 06 中显示的内容,将会发生什么。如下图所示:

图 05

借助图 05,任何人都会知道应用程序中配置了什么,这大大简化了它的使用。

你有没有注意到创建指标是多么容易?我们只需要很少的东西就能让一切正常工作,并使元素完美地适应特定类型的兴趣。为了使指标更有用,更好地将其集成到 MetaTrader 5 中,并将其用于其他目的,还需要做一些事情。但我们将逐步解决这些问题,以便您了解指标中 MQL5 标准库的功能是如何相互配合的。如您所见,我们需要几个函数才能使指标正常工作。然而,该指标尚未真正通用。

好吧,这是第一部分。现在我们将了解如何使用 OnCalculate 的第二个版本。第一个版本符合某些标准,但我们可能需要更多信息。因此,我们需要另一个版本。但是,为了避免将所有内容混合在一起,我们将在另一个主题中介绍这一点。


如何使用 OnCalculate 的第二个版本

在之前的文章中,我们提到 OnCalculate 函数是 MQL5 标准库中唯一重载的函数。这是因为在某些情况下,我们可以使用一个版本,而在其他情况下,则可以使用另一个版本。然而,版本的选择不仅会影响用户界面中显示的内容,还会影响您必须如何实现。如果没有 MetaTrader 5 的帮助,我们在实现过程中必须更加谨慎。这是因为,与第一个版本不同,第二个版本提供了更多可用的数据,根据使用方式的不同,可以得到不同的结果。

鉴于 OnCalculate 的第二个版本相当复杂,我们将只研究同一指标的代码在这个增强版本中的样子。需要注意的是,使用不同版本之间存在差异,但这些差异是由你们程序员造成的。因此,如果使用 OnCalculate 的第二个版本,上一主题中讨论的相同代码 06 看起来像这样:

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. input uchar user01 = 9;             //Exponential average periods
11. //+----------------+
12. double   gl_buffer[];
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
17. 
18.    return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[])
22. {
23.    double k = 2. / (1 + user01);
24. 
25.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
26.       gl_buffer[c] = (c ? ((Close[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : Close[c] * k);
27. 
28.    return rates_total;
29. };
30. //+------------------------------------------------------------------+

代码 07

请注意一点,与之前的情况不同,在之前的情况下我们可以选择使用哪种类型的信息来计算平均值,但在代码 07 中,这已不再可能。原因是我们将计算结果“固定”在其中一个值上 —— 在本例中,即柱形的收盘价。所有其他信息都将被忽略,因为它不会被使用。但这并非唯一的改变 —— 记得我们说过用户界面也会发生变化吗?当我们把代码 07 中所示的指标放在图表上时,我们会注意到缺少一个选项卡,如下图所示:

图 06

在图 06 中,我们可以看到之前的一个选项卡现在不见了。原因是该指标的计算方式固定为特定类型的值。因此,用户无法像以前那样更改数据来源。然而,这并不排除使用其他类型的计算 —— 我们可以让用户选择在计算中使用的输入值类型。为此,只需添加新的输入参数即可。

这部分很有意思。由于操作非常简单,本文余下部分将解释如何操作。然后,我们可以以有趣和创造性的方式创建各种解决方案。

为了让用户访问其他类型的输入值,我们可以使用几种不同的方法。我更喜欢使用枚举。枚举易于实现和修改,这就是我喜欢它们的原因。从本质上讲,我们需要一系列简单的步骤,使所有内容都足够优雅和直观,无论是对您、实现者还是使用您的应用程序的用户。

第一步是创建枚举。为了简单明了,我们将创建一个非常基础的版本。因此,我们将对代码 07 进行如下修改:

                   .
                   .
                   .
09. //+----------------+
10. enum Enum_TypeInput {
11.             HIGH,
12.             OPEN,
13.             CLOSE,
14.             LOW
15.                     };
16. //+----------------+
17. input uchar user01 = 9;             //Exponential average periods
18. input uchar user02 = HIGH;          //Data type for calculation
19. //+----------------+
                   .
                   .
                   .

代码 08

按照代码 08 中指定的修改执行代码 07 后,我们将看到以下内容:

图 07

“哇!但这并非我想要做的。我想向用户显示第 10 行枚举中定义的数据或类型,以便他们可以根据该枚举类型进行选择。为什么没成功?”好吧,原因在于第 18 行中预期的数据类型。请注意:我们声明了一个枚举,这很好;问题是枚举被转换为整数值。而编译器不明白我们打算做什么。为了解决这个问题,我们只需将期望值的类型更改为常量。我们还将把类型从最高价改为收盘价,这在大多数情况下是最常用的。因此,代码 08 将修改为:

                   .
                   .
                   .
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = CLOSE;         //Data type for calculation
19. //+----------------+
                   .
                   .
                   .

代码 09

将代码 08 修改为 09 后,在终端运行代码时,我们可以看到如下结果:

图 08

看看它是如何运行的,但不仅仅是这个问题。由于编译器理解我们想要做什么,我们还可以从代码 08 第 10 行声明的枚举中选择使用相同文本的项。但是请注意:与之前依赖 MetaTrader 5 的支持不同,现在我们只能靠自己了。换句话说,仅仅按照上述方法配置所有内容并不足以使其立即生效。我们需要修改事件处理函数。在这种情况下,我们继续进行下一步,即让我们的代码使用用户的选择。为此,我们只需按如下方式修改代码:

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. enum Enum_TypeInput {
11.             HIGH,
12.             OPEN,
13.             CLOSE,
14.             LOW
15.                     };
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = CLOSE;         //Data type for calculation
19. //+----------------+
20. double   gl_buffer[];
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
25. 
26.    return INIT_SUCCEEDED;
27. };
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[])
30. {
31.    double   k = 2. / (1 + user01),
32.             price = 0;
33. 
34.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
35.    {
36.         switch (user02)
37.         {
38.             case HIGH   :
39.                 price = High[c];
40.                 break;
41.             case OPEN   :
42.                 price = Open[c];
43.                 break;
44.             case CLOSE  :
45.                 price = Close[c];
46.                 break;
47.             case LOW    :
48.                 price = Low[c];
49.                 break;
50.         }
51.         gl_buffer[c] = (c ? ((price - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price * k);
52.    }
53. 
54.    return rates_total;
55. };
56. //+------------------------------------------------------------------+

代码 10

在这段代码中,我们可以看到第 32 行添加了一个新变量。它的目的是为了方便我们管理所有元素。在第 36 行,我们添加了一个 switch 语句来确定价格变量的值。这不需要对计算代码进行太多更改,因为我们只需要调整计算代码行以使用价格变量,如第 51 行所示。很棒,对吧?但在结束本文之前,我们还可以做一件事。

请注意,在第 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. enum Enum_TypeInput {
11.             en_HIGH,           //Use maximum prices
12.             en_OPEN,           //Use opening prices
13.             en_CLOSE,          //Use closing prices
14.             en_LOW             //Use minimum prices
15.                     };
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = en_CLOSE;      //Data type for calculation
19. //+----------------+
20. double   gl_buffer[];
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
25. 
26.    return INIT_SUCCEEDED;
27. };
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[])
30. {
31.    double   k = 2. / (1 + user01),
32.             price = 0;
33. 
34.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
35.    {
36.         switch (user02)
37.         {
38.             case en_HIGH   :
39.                 price = High[c];
40.                 break;
41.             case en_OPEN   :
42.                 price = Open[c];
43.                 break;
44.             case en_CLOSE  :
45.                 price = Close[c];
46.                 break;
47.             case en_LOW    :
48.                 price = Low[c];
49.                 break;
50.         }
51.         gl_buffer[c] = (c ? ((price - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price * k);
52.    }
53. 
54.    return rates_total;
55. };
56. //+------------------------------------------------------------------+

代码 11

看看最终结果。此代码将包含在附录中,并为您提供学习和实践的机会。运行后,您将在 MetaTrader 5 终端中看到以下内容。

图片 09

如上所述,我们只是向用户隐藏了与代码内部结构相关的所有信息。对用户而言,一切照旧。然而,对你来说,这段代码的实现会有点复杂,因为我们有很多规则,而 MetaTrader 5 的帮助比以前少。但由于每种情况都略有不同,你现在知道如何实现一个简单的指标,尽管如此,它仍然可以教会你很多其他东西。


总结性思考

在本文中,我们通过一系列步骤展示了如何相对容易地实现一个简单的指标。我没有介绍移动绘图线之类的操作,但由于它非常简单(只需在代码中添加另一个常量并在 OnCalculate 事件函数中使用它),所以我们不会深入探讨它是如何实现的。让我们把这些任务留给那些想练习并试图更多地了解指标的人。

在下一篇文章中,我们将探讨其他与指标相关的内容。那么,回头见。

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15802

附加的文件 |
Anexo.zip (3.04 KB)
最近评论 | 前往讨论 (2)
Osmar Sandoval Espinosa
Osmar Sandoval Espinosa | 11 2月 2026 在 03:21

你好!


很喜欢这篇文章!


我认为,如果能用更多的图片来描述你在每个步骤中都做了什么,会更令人愉快。

CODE X
CODE X | 12 2月 2026 在 11:19
Osmar Sandoval Espinosa # :

Oi!


Adorei o artigo!


我觉得用更多的图片来描述你在每个阶段所做的事情更有意思。

关于这一点,提示是:您可能会注意到,在某些时候,我们会暂停显示代码,通常是在显示图像时。这样我们就可以稍后更改。因此,我的建议是,不要使用附件中的预制代码:打开文章,按照显示的内容键入代码,并在构建代码时测试会发生什么。这样,你就能更好地跟踪所发生的事情,甚至理解为什么要走这条路而不走另一条路,因为图像中可能没有显示。这样,您就可以测试假设,并更深入地了解如何根据特定目标实际构建事物。
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
基于混沌理论的超买超卖分析 基于混沌理论的超买超卖分析
我们依据混沌理论判定市场超买超卖状态:通过整合混沌理论、分形几何与神经网络原理,构建金融市场预测模型。研究采用李雅普诺夫(Lyapunov)指数量化市场的随机性,并实现交易信号的动态适配。方法论涵盖三大核心组件:分形噪声生成算法、双曲正切激活函数和动量优化技术。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
基于机器学习的黄金单向趋势交易策略研究 基于机器学习的黄金单向趋势交易策略研究
本文讨论一种仅沿选定方向(买入或卖出)进行交易的方法。为此,采用了因果推断技术和机器学习方法。