English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 39 部分):铺平道路(三)

开发回放系统(第 39 部分):铺平道路(三)

MetaTrader 5示例 | 31 七月 2024, 10:22
246 1
Daniel Jose
Daniel Jose

概述

在上一篇文章《开发回放系统》(第 38 部分):铺平道路 (II)中 ,我解释并演示了如何发送数据,在我们的例子中,即是从 EA 交易到指标。这样,我们就可以对指标进行配置,使其返回一些合理的信息。

因此,是时候反其道而行之了,即让指标告诉调用者(在我们的例子中就是 EA 交易)一些有意义的事情。我们需要知道该如何继续。

这里有几件事,其中有些很容易解释,有些则不那么容易。相应地,其中一些很容易展示,而另一些则需要更多时间。但无论如何,我们都将在本文中为建立 Chart Trader 奠定必要的基础。这里将在回放/模拟系统中开发的 Chat Trader 与上一篇文章中介绍的非常相似。

但它会与我们在文章中看到的有所不同:从零开始开发交易 EA 交易(第 30 部分):把 Chart Trader 作为指标? 。由于选择了创建 Chart Trader 的方法,我们在这里要做的事情可能看起来像是儿戏。但我们不要破坏这个惊喜,否则就没意思了。为了便于解释我们将要做的事情,我们首先需要再了解一些事情,本文将对此进行介绍。


模型的初步构建

尽管前两篇文章中描述的一切在某种程度上都是公平的,而且效果很好,这也是我基本上想展示的,但现在我们还有另一个问题。我们只从 EA 向指标发送数据。这意味着有通信,然而,我们仍然没有读取指标数据。在这种情况下,我们需要实现从指标读取数据的功能。 

为了让我们更轻松,我们需要准备好我们的代码。 

我们首先要做的是创建一个头文件,如下所示。

头文件

1. #property copyright "Daniel Jose"
2. #property link      ""
3. //+------------------------------------------------------------------+
4. #define def_ShortName        "SWAP MSG"
5. //+------------------------------------------------------------------+

该头文件名为 Defines.mqh。它应保存在 Includes 目录下的 Mode Swap 子文件夹中。唯一让我们感兴趣的是第 4 行。在这里,我们设置了 EA 和指标都将使用的名称,以方便使用。因为您可能会在指标中指定一个名称,而忘记在 EA 中指定相同的名称。我说的不是文件名,我指的是在 MetaTrader 5 中用于标识指标的名称。

为了更清楚起见,让我们看看下面的指标代码。

指标源代码:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 0
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
33. {
34.     return rates_total;
35. }
36. //+------------------------------------------------------------------+

请注意第 08 行的代码,在这一行中,我们要求编译器包含上述头文件中的代码。代码保持不变,只是在指标代码中不再有这个定义。

这样,之前做出的所有解释仍然有效。现在让我们看看下面的 EA 代码。

EA 源代码:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Mode Swap\\Swap MSG.ex5"
08. #resource "\\Indicators\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     m_id = ChartID();
18. 
19.     EraseIndicator();
20.     m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
21.     ChartIndicatorAdd(m_id, 0, m_handle);
22.     
23.     Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed");
24.     
25.     return INIT_SUCCEEDED;
26. }
27. //+------------------------------------------------------------------+
28. void OnDeinit(const int reason)
29. {
30.     EraseIndicator();
31. }
32. //+------------------------------------------------------------------+
33. void OnTick()
34. {
35. }
36. //+------------------------------------------------------------------+
37. void EraseIndicator(void)
38. {
39.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE) return;
40.     ChartIndicatorDelete(m_id, 0, def_ShortName);
41.     IndicatorRelease(m_handle);
42. }
43. //+------------------------------------------------------------------+

我们这里的东西 非常不同,但仔细一看,差别并不大。似乎唯一真正的区别是存在一个名为 EraseIndicator 的函数,它在第 37 行。该函数在两处被调用:第一处在第 19 行,第二处在第 30 行。我想没有人会质疑为什么要在第 30 行调用,那么第 19 行呢?你知道调用的原因吗?

之所以要在第 19 行调用,正是为了确保 EA 中报告的值与将发送给指标的值同步。此外,还有一些事情需要仔细考虑。请看 EA 代码的第 39 行,请注意,在第 19 行和第 30 行调用该函数时,我们会检查指标是否在图表上。如果指标不存在,代码将会终止。在这种情况下,不会执行从图表上删除指标的第 40 行和释放句柄的第 41 行。

但为什么要使用第 40 行从图表上删除指标呢?原因不在于 EA 代码,而在于指标代码。

查看指标代码,你会发现如果它在图表上,那么一旦 EA 试图更改其值,第 20 行就会阻止指标。这将阻止其值的更新,因为系统会检测到您正试图在图表上放置一个新指标。虽然实际上并没有添加新指标,但第 20 行会这样认定。这就是我们需要从图表上删除该指标的原因。

让我们回到 EA 代码上来。知道第 07 和 08 行代表什么吗?为什么第 20 行与前一篇文章中讨论的第 20 行不同?

这正是从主区域过渡到另一个区域的起点。第 07 行创建了一个定义,该定义指定了指标代码的位置。因此,当编译器创建 EA 可执行文件并找到第 08 行时,它将会查找第 07 行指定的可执行文件。如果没有可执行文件,编译器将会开始编译负责创建这个可执行文件的代码。也就是说,在第 08 行中,我们将指标作为 EA 的内部资源了。

这很有意思,但有利也有弊。值得注意的优点之一是,编译 EA 后,可以删除指标的可执行文件。不过,您现在还不需要这样做。要做这一点,还需要考虑到一个细节。也有一个值得一提的缺点是,在编译 EA 后,如果指标出现问题,您必须重新编译整个 EA 代码。即使问题仅仅出在指标上。请冷静,有一个细节必须注意。

这是哪种细节呢?这一点相当微妙,几乎不易察觉。这就是为什么每个优秀的程序员同时也需是一个非常优秀的观察者。下面是详细介绍。请看 EA 代码的第 20 行,请注意,在第三个参数的声明中出现了一个小链(::)。定义之前的这个小链就是作用域解析操作符

操作符出现在这里这一事实本身就表明,我们将以一种不同于想象的方式使用某些东西。每次出现这个运算符时,我们都在以某种方式明确告诉编译器应该做什么。通常情况下,编译器应该做出一些决定,但我们需要它明确知道是什么。

在代码所示的特定情况下,我们告诉编译器使用在 EA 可执行文件中作为资源找到的指标。这意味着,在成功编译 EA 后,我们可以简单地删除指标可执行文件,而 EA 交易仍能使用正确的指标。

如果没有添加该作用域解析操作符(如下例所示),那么即使编译器将指标作为 EA 资源包括在内,EA 实际上也将使用位于定义中指定目录下的可执行文件。在这种情况下,我们需要将指标转移到一个单独文件中,和 EA 放在一起。

20.     m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00);

这个简单的细节至关重要。在完整代码第 20 行的情况下,编译 EA 后,可以删除指标的可执行文件。还有一个细节:如果添加了指标并将其用作 EA 资源,那么在编译后者之前,最好删除指标的可执行文件。这将保证所编译的是指标中的最新代码。通常情况下,在使用多重编译的系统中,我们有一个辅助工具 - 一个名为 MAKE 的文件。

如果使用它,每次重新编译代码时,MAKE 都会适当删除可执行文件,是完全自动化的。它将上次编译时间与可执行文件源代码中使用的某些文件的上次修改时间进行比较,如果检测到任何更改,MAKE 将删除可执行文件,以迫使编译器创建一个新的可执行文件。目前,MQL5 还没有这样的功能,但谁知道呢,也许将来开发人员会决定添加它。

所有上述解释都说明了我们能做什么、我们应该如何做,以及我们未来将如何做。但我们仍然无法从指标中读取任何数据。

让我们来实现一个非常简单的可通信版本。为了详细、连贯地解释这一点,让我们转到一个新的主题。


从指标读取数据

实际上,只有一种方法可以读取指标中的任何信息,那就是读取它的缓冲区。但这里有一个小细节:CopyBuffer 函数不允许我们处理缓冲区中的任何类型的数据。不能以其自然的形式。如果你看看这个函数的声明,就会明白我的意思。

CopyBuffer 函数声明:

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // number of indicator buffer
   int       start_pos,            // starting position 
   int       count,                // amount to copy 
   double    buffer[]              // destination array to copy 
   );


为了更清楚起见,你可以看到上面是如何声明 CopyBuffer 函数的一个变体的。我说其中一个变体是因为有三个变体,但我们真正关心的是声明中出现的最后一个变体。请注意它的类型 - double,也就是说,我们只能返回 double 类型的值。这只是理论上的,因为实际上我们可以返回任何东西。而在文章从零开始开发交易智能交易系统(第 17 部分):在网络上访问数据 (III) 中,我展示了一种绕过这一限制的方法。

在本系列文章中,我们也将使用类似的方法,但层次不同。对于初学者来说,这可能会更大一些,也更复杂一些。但思路与上面提到的文章相同。如果您需要了解更多详情,请参阅上述文章,以准确了解我们将做什么。在这里,我们将只是简单去做,我将不再详述细节。

因此,这对我们来说已经是熟悉的事情了。让我们修改指标源代码,如下所示。注意:我将循序渐进,以便你们能够跟上进度并理解将要做的事情。

指标源代码:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. double m_Buff[];
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     m_id = ChartID();
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
21.     if (ChartWindowFind(m_id, def_ShortName) != -1)
22.     {
23.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
24.             Print("Only one instance is allowed...");
25.             return INIT_FAILED;
26.     }
27.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
28.     Print("Indicator configured with the following value:", user00);
29.     
30.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
31.     ArrayInitialize(m_Buff, EMPTY_VALUE);
32. 
33.     return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[rates_total - 1] = user00 * 2.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

如果将上面显示的源代码与文章开头介绍的源代码进行比较,你会发现两者之间只有一些细微的差别。就像我说的,我们会慢慢来。实际上,我们对第 06 行进行了修改,声明指标将有一个缓冲区。该缓冲区将在指标外可见。我们稍后再讨论它。首先,让我们弄清楚指标中会发生什么。

接下来,在第 15 行,我们声明了一个全局变量。不过,只有指标代码或与之相关的部分才能看到这个变量。该变量是一个缓冲区,需要初始化。这是在第 30 和 31 行完成的。现在,我们的缓冲区已经使用一些类型的信息进行了初始化并且可用。

然而,如果你读取它,你只会得到无用的数据。我们需要一些高效的方法将数据加载到缓冲区中。这可以通过不同的方式实现,但从技术上讲,最好和最安全的方式是已经在使用的方式。因此,我们有了第 38 行。

你可能会注意到,在这一行中,我没有指向缓冲区中的任何区域。为什么呢?

原因是,我们需要以某种方式使过程标准化。如果我们把信息,在这种情况下是把 EA 报告的值乘以 2.0 的结果,就很难知道从哪里可以找到真正有用的信息。我们试着冷静地理解事情。但在未来,一切都将变得更加复杂。

因此,我们使用自己的指标计算系统,使信息始终放在相同的位置。

为了更清楚地说明问题,让我们看看下面的 EA 源代码。

EA 源代码:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
08. #resource "\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     double Buff[];
18.     
19.     m_id = ChartID();
20. 
21.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
22.     {
23.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
24.             ChartIndicatorAdd(m_id, 0, m_handle);
25.     }
26.             
27.     Print ("Buffer reading:", (m_handle == INVALID_HANDLE ? "Error..." : CopyBuffer(m_handle, 0, 0, 1, Buff) > 0 ?  (string)Buff[0] : " Failed."));
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     ChartIndicatorDelete(m_id, 0, def_ShortName);
35.     IndicatorRelease(m_handle);
36. }
37. //+------------------------------------------------------------------+
38. void OnTick()
39. {
40. }
41. //+------------------------------------------------------------------+

请注意,代码与之前介绍的相比没有太大变化。我们要记住,这里的指标将作为资源使用,因此在编译 EA 后,我们可以删除其可执行文件。让我们看看代码中添加了哪些内容。

首先,我们在第 17 行有一个变量声明,这将是我们的返回缓冲区或指标读取缓冲区。除了第 17 行,我们还新增了第 27 行。

许多人可能会觉得第 27 行非常混乱,但其实它只是两个嵌套的三元运算符。首先,我们要检查指标句柄是否有效;如果无效,终端消息窗口会出现相应的警告。如果句柄有效,我们就从指标缓冲区读取一个位置。将读取哪个位置?第一个。不是太清楚?冷静下来,放松,我稍后会解释一切。如果缓冲区读取成功,我们将打印其中包含的值。否则,将显示另一条错误信息。它将与第一个不同,表明失败的原因是不同的。

现在我们来看看为什么当我们读取第 27 行所示的缓冲区时,我们读取的是第一个位置,而不是最后一个位置。


理解缓冲区的写入和读取过程

这是 MQL5 文档中的图片,它清楚地说明了我接下来要解释的问题。见下图:

图 01

图 01 - 写入和读取指标缓冲区

这张图片清楚地说明了正在发生的事情。总之,让我们详细了解一下如何将数据写入指标缓冲区以及如何从中读取数据。

在向指标缓冲区写入数据时,必须始终写入指定位置。当出现新的柱形时,新的位置会自动出现在缓冲区中。问题是,在每个时间框架内,缓冲区的大小都不同。

您可能会好奇问题出在哪里。问题不只一个,有好几个。首先,假设在指标中,我们决定使用以下代码写入零位。

代码片段 - 模型 01:

35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[0] = user00 * 3.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

如果将上一主题中讨论的指标代码中的几行改为这段代码,就会像第 38 行所示,写入缓冲区的零位。但这种方法存在一个问题。

问题不是出现在写入时,而是出现在试图读取指标代码外的缓冲区时,例如在 EA 中。

读取缓冲区时,MetaTrader 5 通常最多返回 2000 个位置。如图 01 所示。如果指标中的数据量偶然超过这 2000 个位置,就会出现问题。请记住,我们写入了零位,但零位与 CopyBuffer 引用的零位是不同的。对于 CopyBuffer,指标代码中的零位实际上可能是位置 2001,如果是这种情况,就无法从指标缓冲区中读取零位。该值将固定在指标中。

因此,我们不会将数据写入第零位,而是从缓冲区末端的位置开始写入。还不清楚吗?

指标缓冲区中的零位应始终视为 "rate_total - 1" 的位置。这就是我们在上一主题中讨论的指标代码中的写入位置。正因为如此,当我们通过 CopyBuffer 读取指标缓冲区时,在输出值时,我们实际上使用的是零号索引。

也许这一点还不完全清楚,为了说明问题,让我们来看另一个代码示例,在这个示例中,我们向指标传递数据,但返回的值不像之前那样只有一个,而是多个。其中一个将是一个简单的字符串。

值得注意的是,我们可以按照相反的顺序或通常的方式,或者说按照以下顺序向缓冲区写入信息:第一条信息 - 第一个值,第二条信息 - 第二个值。如果反过来,则会是这样:第一个信息 - 最后一个值,第二个信息 - 最后一个值减去一个位置,以此类推。为了便于解释,不是在写入时,而是在读取时,让我们按照通常的方式来做。

让我们先修改头文件,如下所示。

Defines.mqh 文件源代码

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_ShortName       "SWAP MSG"
04. //+------------------------------------------------------------------+
05. union uCharDouble
06. {
07.     double  dValue;
08.     char    cInfo[sizeof(double)];
09. };
10. //+------------------------------------------------------------------+

正如您所看到的,在第 05 行和第 09 行之间有一个联合(union),通过这个联合,我们可以使用 double 类型值传递文本类型数据。如果您是第一次看到,可能会觉得很奇怪。但我们以前也这样做过。这篇文章中就举了一个例子:从零开始开发交易智能交易系统(第 17 部分):通过网络获取数据 (III)但让我们回到我们的问题上来。现在,我们有办法从指标向 EA 发送一个小字符串了。使用 double 值的原因是,我们无法通过 CopyBuffer 发送其他类型的值,我们必须使用 double 类型。

修改 Defines.mqh 文件后,我们就可以继续查看指标源代码了。

更新了指标源代码,允许写入多个位置

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. #property indicator_buffers 1
06. //+------------------------------------------------------------------+
07. #include <Mode Swap\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_ShortNameTmp    def_ShortName + "_Tmp"
10. //+------------------------------------------------------------------+
11. input double user00 = 0.0;
12. //+------------------------------------------------------------------+
13. long m_id;
14. double m_Buff[];
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
30.     ArrayInitialize(m_Buff, EMPTY_VALUE);
31. 
32.     return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.     uCharDouble info;
38.     int pos = rates_total - 3;
39.     
40.     StringToCharArray("Config", info.cInfo);
41.     m_Buff[pos + 0] = info.dValue;
42.     m_Buff[pos + 1] = user00 * 2.0;
43.     m_Buff[pos + 2] = user00 * 2.5;
44.             
45.     return rates_total;
46. }
47. //+------------------------------------------------------------------+

让我们仔细看看上面的代码。从第 37 行开始,与文章开头介绍的代码相比才有了一些变化。为什么会这样?因为我们现在要对通过 CopyBuffer 返回的更广泛信息进行组合。无需修改代码的其他部分,只需修改从第 37 行开始的代码。

现在,让我们来弄清楚到底发生了什么。在第 37 行,我们声明了一个变量,用于将字符串转换为 double 型数值。请注意,字符串长度限制为 8 个字符。如果信息包含更多字符,则必须为此提供一个数组,并始终以 8 个字符为一组组织信息。

在第 38 行,我们声明了一个变量,该变量将用于以正常方式写入信息,即从左到右写入文本。在阿拉伯语中,书写将从右到左进行。我认为思路很明确,在同一行 38 行中,我们标明了要发送的 double 型值的数量。在我们的例子中,我们有 3 个值。

在第 40 行,我们将字符串转换为字符数组。这将为我们提供第一个位置使用的值。然后,在第 41 行,我们将该值放入缓冲区。 

在第 42 行和第 43 行,我们进行了一个简单的计算,以便读取一些数据。这样,当需要访问缓冲区中的更多位置时,就可以显示读取是如何发生的。

基本上,这都是指标中的问题。此时此刻,关于通信已经没有什么可讨论的了。让我们进一步看看如何识别和读取在指标中创建的缓冲区。为此,让我们来看看下面介绍的已经更新过的 EA 代码。

EA 源代码已更新,可读取多个位置

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. //+------------------------------------------------------------------+
04. #include <Mode Swap\Defines.mqh>
05. //+------------------------------------------------------------------+
06. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
07. #resource "\\" + def_SWAP
08. //+------------------------------------------------------------------+
09. input double user00 = 2.2;
10. //+------------------------------------------------------------------+
11. int m_handle;
12. long m_id;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {   
16.     double Buff[];
17.     uCharDouble Info;
18.     int iRet;
19.     string szInfo;
20.     
21.     m_id = ChartID();
22.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
23.     {
24.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
25.             ChartIndicatorAdd(m_id, 0, m_handle);
26.     }
27.     ArraySetAsSeries(Buff, false);
28.     if (m_handle == INVALID_HANDLE) szInfo = "Invalid handler to read the buffer.";
29.     else
30.     {
31.             if ((iRet = CopyBuffer(m_handle, 0, 0, 3, Buff)) < 3) szInfo = "Buffer reading failed.";
32.             else
33.             {
34.                     Info.dValue = Buff[0];
35.                     szInfo = CharArrayToString(Info.cInfo) + " [ " + (string)Buff[1] + " ] [ " + (string)Buff[2] + " ]";
36.             }
37.     }
38.     Print("Return => ", szInfo);
39.             
40.     return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     ChartIndicatorDelete(m_id, 0, def_ShortName);
46.     IndicatorRelease(m_handle);
47. }
48. //+------------------------------------------------------------------+
49. void OnTick()
50. {
51. }
52. //+------------------------------------------------------------------+

上述更新后的 EA 代码与我们最初看到的并无太大区别,但这里仍有一些新元素值得解释。

在第 17 行和第 19 行之间声明了一些新变量,这些变量将用于解密缓冲区中的信息。这看起来很奇怪,但事实上,缓冲区中的信息是经过编码的,因为我们在其中一个位置上传递了一个字符串。

真正有趣的是第 27 行和第 38 行之间。此处是在读取缓冲区,那么,就让我们从这一部分开始吧。

第 27 行包含的代码有时会在读取时出现,甚至在读取多个缓冲区位置时也是如此。这是因为默认情况下将直接进行读取。从图 01 中可以看到,数组是按直接顺序排列的,但也有读取顺序颠倒的时候。然后,我们不再使用反向索引来访问数组,而是使用第 27 行定义的函数来指定以反向顺序读取。在这种情况下,与我们看到的相反,传递给函数的值将是false。通过这种方法,我们就可以像直接访问一样使用访问索引。

虽然第 27 行在我们当前的代码中可能意义不大,因为我们是直接写入的,但添加这一行是为了解释前面所说的内容。

没有必要详细讨论其他大部分代码行,因为其中许多代码行是不言自明的。不过,第 31 行的内容需要花点心思。

当我们向指标内的缓冲区写入数据时,我们知道要发送多少个位置的信息。第 31 行就是这样做的:读取预期位置数量。使用头文件进行更好的组合会更合适。因此,如果包含信息的位置数量增加,EA 和指标都将始终意识到这一点。但由于这些例子只是为了解释稍后会用到的概念,所以暂时可以忽略这一点。

如果读取的位置数量少于预期,这将是一个错误,相关信息将显示在终端中。现在,如果读取数量符合预期,我们就转到第 34 行,将以 double 形式提供的信息转换为字符串。因此,我们要恢复指标在缓冲区中的信息。

请注意,这种情况下的索引为零,与指标中的规定相同。如果在指标中进行反向写入,我们仍然可以在 EA 中使用相同的索引。只需更改第 27 行中的值,EA 就会使用相同的索引方法来理解信息。

可以尝试这样做,以了解事情的真实情况。了解这些细节对于理解本系列接下来的文章非常重要。

转换完成后,我们使用第 35 行生成一条信息,显示在终端中。就这么简单。


结论

在我们的回放/模拟系统项目中出现的这个小插曲中,我展示了未来文章中将涉及的基本内容。

这只是等待我们的一切中最简单的一部分。我们尚未添加或实现的一些功能将成为回放/模拟系统的一部分。这里介绍的所有内容对今后的文章都非常重要。如果不了解指标和其他过程之间数据传输的基本原理,就会迷失方向。我还没有介绍如何从指标窗口中隐藏指标,以防止用户删除它。此外,我们还没有添加一些元素,这些元素在各种可能的组合中会大大增加理解的复杂性。

我真心希望你们能理解后三篇文章提出的概念和真正意义。这样一来,一切都会变得更加复杂。

因为我想让你们形成一种机械记忆,所以本文将不附带任何文件。如果您想进行实验并学习此处所示的基础知识,就必须编写代码。不过,由于代码很短,应该不会有什么问题。

我们将在下一篇文章中介绍如何将 Chart Trader 集成到回放/模拟系统中。


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

附加的文件 |
EA.mq5 (1.81 KB)
swap.mq5 (1.72 KB)
Defines.mqh (0.24 KB)
最近评论 | 前往讨论 (1)
Jin Rong Xia
Jin Rong Xia | 1 8月 2024 在 05:17
辛苦了,学了很多
开发回放系统(第 40 部分):启动第二阶段(一) 开发回放系统(第 40 部分):启动第二阶段(一)
今天我们将讨论回放/模拟器系统的新阶段。在这个阶段,谈话才会变得真正有趣,内容也相当丰富。我强烈建议您仔细阅读本文并使用其中提供的链接。这将帮助您更好地理解内容。
理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式 理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式
了解编程范式及利用 MQL5 代码的应用。本文探讨了过程化编程的细节,并通过一个实际示例提供了实经验。您将学习如何利用 EMA 指标和烛条价格数据开发价格行为智能系统。额外,本文还介绍了函数化编程范式。
改编版 MQL5 网格对冲 EA(第 1 部分):制作一个简单的对冲 EA 改编版 MQL5 网格对冲 EA(第 1 部分):制作一个简单的对冲 EA
我们将创建一个简单的对冲 EA,作为我们更高级的 Grid-Hedge EA 的基础,它将是经典网格和经典对冲策略的混合体。在本文结束时,您将知晓如何创建一个简单的对冲策略,并且您还将知晓人们对于该策略是否能真正 100% 盈利的说法。
在 MQL5 中创建做市商算法 在 MQL5 中创建做市商算法
做市商是如何运作的?让我们探讨一下这个问题,创建一个初级的做市商算法。