
开发回放系统(第 38 部分):铺路(II)
概述
在上一篇文章《 开发回放系统(第 37 部分):铺路(I)》中,我们已看到了一种简单的方式来防止用户在图表上重叠指标。在本文中,我们验证了通过非常简单的代码(仅包含几行代码),当我们仅需要一个副本指标出现,MetaTrader 5 平台能帮助我们避免在图表上出现第二个指标。重要的是,指标不能重叠。
我希望您理解这个想法,以及如何实现预期的结果。在任何情况下该指标都不应重叠。
在确认 MetaTrader 5 中不重叠原则的同时,请注意,指标和智能系统之间的双向通信还有很长的路要走。我们离此还很远。不过,保证指标不会在图表上重叠至少可以令人心态平和。因为当指标和 EA 交互时,我们能知道我们正在与正确的指标打交道。
在本文中,我们将开始通过 EA 的眼睛来看待事物。但我们还必须对指标进行一些修改,因为不幸的是,上一篇文章中的指标根本无法对 EA 做出反应。当 EA 想知道某些事情时,就会发生这种情况。
在流程之间创建第一个交互
为了更好地解释正在发生的事情,我们需要创建一些控制点。我们从指标代码开始。别担心,这一切都很容易理解。
完整的指标代码如下所示:
01. #property copyright "Daniel Jose" 02. #property link "" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. #define def_ShortName "SWAP MSG" 08. #define def_ShortNameTmp def_ShortName + "_Tmp" 09. //+------------------------------------------------------------------+ 10. input double user00 = 0.0; 11. //+------------------------------------------------------------------+ 12. long m_id; 13. //+------------------------------------------------------------------+ 14. int OnInit() 15. { 16. m_id = ChartID(); 17. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp); 18. if (ChartWindowFind(m_id, def_ShortName) != -1) 19. { 20. ChartIndicatorDelete(m_id, 0, def_ShortNameTmp); 21. Print("Only one instance is allowed..."); 22. return INIT_FAILED; 23. } 24. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 25. Print("Indicator configured with the following value:", user00); 26. 27. return INIT_SUCCEEDED; 28. } 29. //+------------------------------------------------------------------+ 30. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 31. { 32. return rates_total; 33. } 34. //+------------------------------------------------------------------+
请注意,代码的唯一添加是第 25 行。这将是我们的验证行,至少对于第一次。此刻,我们对 EA 如何与指标交互,以及我们是否可以向其发送信息或数据感兴趣。
注意,在第 10 行中,指标预期的信息类型为 double(双精度)。这是有意为之的,因为目标是创建一种机制,可以替换全局终端变量系统,用于 EA 和指标之间的通信。如果开发或使用的手段被证明是不合适的,那么我们可以轻松地用终端全局变量模型替换这个模型。
您还可用其它类型的数据来提供通信,至少对于从 EA 到指标的数据传输。
现在我们已经修改了指标代码,我们可以继续讨论 EA 代码。它的完整代码显示如下:
01. #property copyright "Daniel Jose" 02. #property link "" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. input double user00 = 2.0; 06. //+------------------------------------------------------------------+ 07. int m_handle; 08. long m_id; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. m_id = ChartID(); 13. if ((m_handle = ChartIndicatorGet(m_id, 0, "SWAP MSG")) == INVALID_HANDLE) 14. { 15. m_handle = iCustom(NULL, PERIOD_CURRENT, "Mode Swap\\Swap MSG.ex5", user00); 16. ChartIndicatorAdd(m_id, 0, m_handle); 17. } 18. Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed"); 19. 20. return INIT_SUCCEEDED; 21. } 22. //+------------------------------------------------------------------+ 23. void OnDeinit(const int reason) 24. { 25. ChartIndicatorDelete(m_id, 0, "SWAP MSG"); 26. IndicatorRelease(m_handle); 27. } 28. //+------------------------------------------------------------------+ 29. void OnTick() 30. { 31. } 32. //+------------------------------------------------------------------+
对于许多人来说,这个 EA 代码也许看似十分奇怪。我们看看它是如何工作的。这是我们真正需要做的第一步。
在第 05 行,我们让用户有机会设置传递给指标的数值。记住:我们打算测试,并了解通信将如何发生。第 07 行和第 08 行在 EA 代码中包含两个内部全局变量。我通常不喜欢使用全局变量,但在这种情况下,您可以做一个例外。这些变量的内容将自第 10 行开始的 OnInit 命令的代码中判定。
这也许看似有点复杂。这是因为在这个阶段我们需要了解一些事情。
在第 12 行,我们初始化一个变量,该变量是正在运行的 EA 时间帧索引。然后来到第 13 行,该处初学者经常会遇到问题。许多人根本不将此行添加到他们的代码当中。缺少此行不会立即中断您的代码,但包含它可以防止发生某些类型的错误。其中许多错误是运行时错误,随时可能发生,因此难以解决。
现在我们已经在第 13 行添加了代码,如果 MetaTrader 5 已在图表上加载了我们所需的指标,它将返回该指标的句柄。因此,我们就无需费心将指标放在图表上。
但是,如果第 13 行显示所需的指标不在图表上,我们就会运行将指标添加到图表中的代码。记住以下几点:我们需要并将使用的指标是自定义指标。因此,我们称之为 iCustom,可以在第 15 行看到。
现在棘手的部分来了:为什么第 15 行的 iCustom 是这样声明的?您知道为什么吗?为了明白这一点,我们来看看文档是怎么说的:
int iCustom( string symbol, // symbol name ENUM_TIMEFRAMES period, // period string name // folder/custom indicator name ... // list of indicator parameters );
从上表中,您可看到函数的第一个参数是品种名称。但在代码的第 15 行中,第一个参数为 NULL。为什么会这样?为什么我们不使用 _Symbol 常量?原因是对于编译器来说,NULL 和 _Symbol 是相同的。这样,指标将取 EA 所在图表的相同品种创建。
清单中的下一个参数是图表周期。同样,我们有一些与许多人期望不同的东西。在第 15 行,我们采用值 PERIOD_CURRENT,但为什么呢?原因是我们想要指标与智能系统在同一周期保持同步。一个细节:如果您希望指标在不同的图表周期进行分析,只需在该参数中指定所需的周期即可。因此,指标将在特定周期内固定,而 EA 能够在不同的周期前行。
当涉及到自定义指标时,我认为这是可能导致严重后果的第三个参数。许多人不知道该在此处放什么,有时会留空或指定错误的位置,因为他们不理解其含义。如果我们指定了错误的位置,MetaTrader 5 就无法找到我们需要的指标。
查看第 15 行的第三个参数中声明的内容。我们不仅指定名称,还指定名称和路径。这些信息来自哪里?为了找出答案,我们必须回到上一篇文章,找出这些信息的来源。参见图例 01。
图例 01 - 创建一个指标
在图例 01 中,您可以看到第三个参数的信息来自何处。这些实际上是相同的事情。进行以下更改:首先我们删除根目录,在本例中,它将是 indicators。这是因为 MetaTrader 5 将首先在该目录中查找指标。第二点是我们添加了 .ex5 文件。以这种方式,我们就能获得可执行文件的正确位置。当 MetaTrader 5 将 EA 放在图表上,并执行第 15 行时,它将知道在哪里、以及使用哪个指标。
下面我们将查看可能浮现的一些故障和问题。但首先,我们把基础知识弄清楚。
第三个参数和随后的参数是可选的。如果我们想为指标提供任何值,我们必须从第四个参数开始这样做。但是,您必须确保操作时,按照参数在指标上所示的相同顺序进行。您还需要确保它们的类型正确。如果指标预期接收浮点值或长整数值,则不能输入双精度值。在这种情况下,MetaTrader 5 将在启动指标后立即生成错误。
还有其它方式可以将参数传递给指标,但现在,为了简单起见,我们将调用 iCustom。如果您查看指标代码,您就会看到在第 10 行中预期为双精度值。该值通过 EA 提供给指标,位于 EA 代码第 15 行的第四个参数之中。
现在出现了一个许多人忽视的重要问题,其会导致整个系统的问题。它是 EA 代码中的第 16 行。为什么这一行如此重要?
在某些情况下,当涉及到自定义指标时,可以忽略此行。当我们想要 MetaTrader 5 仅支持在图表上放置一个实例时,这对于那些我们需要的指标来说此刻为真。我们不能以任何方式或形式忽略第 16 行,当存在第 16 行时,也必须添加第 25 行。
这两行避免了问题。第 16 行将在图表上运行该指标。因此,如果用户尝试在同一图表上运行指标的另一个实例,MetaTrader 5 和指标代码将阻止新实例运行。如果从 EA 代码中删除第 16 行,用户可能会意外地在图表上放置指标的一个新实例,从而导致用户(而不是 EA)对指标执行的计算感到困惑。
对于所计算指标,我们遇到了一定的问题,但是对于像 ChartTrader 这样的指标,问题就更加复杂了。因为在这种情况下,我们正在与图形对象打交道。了解如何正确地运作每件事很重要。然后会更容易理解如何运作更复杂的事情。
我认为现在很清楚第 16 行为何重要了。25 行起什么作用?它将从图表中删除指定的指标。请注意一件事:我们需要指定要索要删除指标的名称。它放置在指标代码的第 24 行的指标当中。名称必须相同,否则 MetaTrader 5 会无法理解需要删除哪个指标。此外,在某些情况下,我们还必须判断使用哪个子窗口。由于我们没有用到子窗口,因此我们将此值保持等于零。
然后,在第 26 行,我们删除了指标处理程序,因为我们不再需要它。
现在我们来查看 MetaTrader 5 如何使用 EA 和指标之间的这种交互方案。为了理解这一点,您需要做一些事情。因此,请密切关注该过程的细节。此外,您应该测试每处修改,并尝试生成的代码,因为每处修改都会为 MetaTrader 5 带来自己的处理状况的方式。重要的是,不仅要阅读上述解释,还要了解正在发生的事情。为了理解当 EA 以某种方式编写时会发生什么,或者当同一 EA 以不同的方式编程时会发生什么,有必要考虑三种情况。EA 和指标中使用的代码,与我们到目前为止解释的代码相同,唯一的区别在于 EA 代码行的存在与否。
现在执行以下操作:
- 首先,编译包含所有行的代码,并查看系统在 MetaTrader 5 中是如何工作的。
- 然后删除第 16 行和第 25 行,并在 MetaTrader 5 中重新测试系统。
- 最后,仅删除第 16 行,保留第 25 行。之后,再次测试系统。
您也许认为这一切都很无趣,而您作为一位有经验的程序员,永远不应犯这样的错误。但是,了解每行代码的作用,以及在运行 MetaTrader 5 时产生的结果,比看起来更重要。
如果您已经达到了这个阶段,那么您就会明白第二阶段,在这个阶段中,我们可以在不使用全局终端变量的情况下将数据从 EA 传输到指标。您已经朝着成为一位高品级程序员迈出了一大步。但如果您仍然不明白,不要气馁。返回到本文或上一篇文章的开头,并尝试理解该步骤。现在事情变得非常复杂,我没有开玩笑。我们需要让指标向 EA 发送数据。
在此,指标将数据发送到智能系统。在某些情况下,这可能非常简单。但是任何关注我文章的人都可能已经注意到,我喜欢把事情发挥到极致,让电脑汗流浃背,并要求它以不同的方式做事。这是因为我真的希望语言和平台都能在可能的极限下工作。
IndicatorCreate 或 iCustom — 采用哪一个?
许多初学者可能想知道如何调用自定义指标。这可以通过两种方式完成。我们在上一个主题中介绍了第一个,其中我们解释了 iCustom 的运用。但是还有另一种方式,在某些类型的建模中可能更具吸引力,而对于其它,调用 iCustom 就足够了。
这与必须使用一种方法或另一种无关。我在此的目的是展示如何调用 IndicatorCreate 来加载您自己的指标。
在开始之前,我们来了解一件事。IndicatorCreate 函数不是特殊函数。这实际上是一个基本函数。这是什么意思?基本函数是一种函数,它本质上是为提请另一个函数,但有一个细节:“派生”函数使用基本函数,但令其更易于使用。
这是因为不需要像我们对基本函数所期望的那样对调用进行建模。我们可以使用更简单的建模。其它函数就是这样产生的。您可以在文档中将它们视为技术指标。在这些指标中,有 iCustom,它本质上是一个派生函数,可以简化建模。
那么,若我们使用 IndicatorCreate 替代 iCustom,那么本文开头讨论的智能系统的源代码会如何?它的代码如下所见:
01. #property copyright "Daniel Jose" 02. #property link "" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. input double user00 = 2.2; 06. //+------------------------------------------------------------------+ 07. int m_handle; 08. long m_id; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. MqlParam params[]; 13. 14. m_id = ChartID(); 15. if ((m_handle = ChartIndicatorGet(m_id, 0, "SWAP MSG")) == INVALID_HANDLE) 16. { 17. ArrayResize(params, 2); 18. params[0].type = TYPE_STRING; 19. params[0].string_value = "Mode Swap\\SWAP MSG"; 20. params[1].type = TYPE_DOUBLE; 21. params[1].double_value = user00; 22. m_handle = IndicatorCreate(NULL, PERIOD_CURRENT, IND_CUSTOM, ArraySize(params), params); 23. ChartIndicatorAdd(m_id, 0, m_handle); 24. } 25. Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed"); 26. 27. return INIT_SUCCEEDED; 28. } 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. ChartIndicatorDelete(m_id, 0, "SWAP MSG"); 33. IndicatorRelease(m_handle); 34. } 35. //+------------------------------------------------------------------+ 36. void OnTick() 37. { 38. } 39. //+------------------------------------------------------------------+
比较上面的代码与我们文章开头展示的代码之间的差别。两者都做同样的事情。主要问题是:为什么调用这个 IndicatorCreate 的代码需要以这种方式执行?
iCustom 函数实际上隐藏了我们需要做的事情,但我们要弄清楚发生了什么。
首先,我们需要声明一个变量,该变量在第 12 行声明为数组。以这种方式完全能做到。如果图表上没有该指标,我们就需要创建它。为此,我们必须告诉 IndicatorCreate 函数该做什么、以及如何做。
除了我们在指标中所用的参数数量之外,我们还必须告诉 IndicatorCreate 函数指标的名称。因此,如果我们只输入指标的名称,则第 17 行的 ArrayResize 函数不应选择两个元素,而应是一个元素。如果我们向指标传递了 5 个参数,我们将不得不分配 6 个元素,依此类推。我们来查看我们需要发送多少个参数,并分配一个额外的位置。
如此,现在我们需要配置这个数组。此处第 22 行含有 IndicatorCreate 函数,表示我们将使用自定义指标。这是使用 IND_CUSTOM 枚举完成的。我们需要做若干件事。在第 18 行,我们看到如何继续处理数组的第一个位置。记住,MQL5 与 C++ 很相似,因此我们从零开始计数。第一个位置的信息类型必须是 STRING,因此声明的编写方式如第 18 行所示。
在第 19 行,我们输入所用自定义指标的名称。注意,指标名称与调用 iCustom 时所要用的名称相同。唯一的区别是我们没有指定文件扩展名。这必须是扩展名为 .ex5 的可执行文件,因此指定扩展名是可选项。
重要:第 18 行和第 19 行中的所做必须在每次使用自定义指标时完成。如果您使用任何其它指标,无论它是什么,那么一旦您开始设置数组数据,您就必须指定参数,因为您之后还会看到它,即从第 20 行开始。
现在我们已经告诉 IndicatorCreate 函数要使用哪个自定义指标,我们开始填充将传递给它的参数。必须按正确的顺序指定这些参数。无论您使用哪个指标,类型声明错误都会导致运行时出错。因此,在填写此信息时要非常小心。
在第 20 行,我们指定第一个参数的类型为双精度。但它可以是 ENUM_DATATYPE 枚举中定义的任何类型。您可以选用它们中的任何一个,但有一些事项需要注意。
类型 TYPE_DOUBLE 和 TYPE_FLOAT 将用于 double_value,在本例中在第 21 行。
如果第 20 行使用了类型 TYPE_STRING,则在第 21 行中,该值将强制转换为 string_value 变量。对于在第 20 行声明的任何其它可能类型,我们在第 21 行输入值使用 integer_value 变量。
这非常重要,所以我们要把这些信息说清楚。为了更好地理解,请查看下表:
所用的标识符 | 数据类型 | 何处放置数值 | ||
---|---|---|---|---|
TYPE_BOOL | 布尔 | integer_value | ||
TYPE_CHAR | 字符 | integer_value | ||
TYPE_UCHAR | 无符号字符 | integer_value | ||
TYPE_SHORT | 短整数 | integer_value | ||
TYPE_USHORT | 无符号短整数 | integer_value | ||
TYPE_COLOUR | 颜色 | integer_value | ||
TYPE_INT | 整数 | integer_value | ||
TYPE_UINT | 无符号整数 | integer_value | ||
TYPE_DATETIME | 日期时间 | integer_value | ||
TYPE_LONG | 长整数 | integer_value | ||
TYPE_ULONG | 无符号长整数 | integer_value | ||
TYPE_FLOAT | 浮点数 | double_value | ||
TYPE_DOUBLE | 双精度 | double_value | ||
TYPE_STRING | 字符串 | string_value |
表格或对应关系。
该表清楚地显示了我们将在第 20 行使用的内容(所用的标识符),以及将在第 21 行接收哪个变量值。我们必须针对将要传递给自定义指标的每个参数执行此操作。由于我们只使用一个参数,因此我们只操控那一个。
注意,在我们的特定情况下,调用 IndicatorCreate 比其衍生函数 iCustom 要耗费更多劳力。
在我们设置了我们想要定义的所有参数之后,我们转到第 22 行,在该处我们会实际调用该函数。这样做是为了令我们可以很容易地增加或减少事物。所有字段都已填充,因此,如果您需要更改指标或参数数量,则不必再次编辑第 22 行。
我们的思路是始终简化事情,而非令它们复杂化。
由于在 MetaTrader 5 中设置相同类型的执行需要做额外的工作,因此我们不会经常在当前代码中看到 IndicatorCreate 函数。但没有什么能阻止您去使用它。
在结束本文之前,我想简要介绍一下另一个函数:IndicatorParameters。该函数允许检查有关未知指标的某些信息。假设您的图表上有若干个不同的指标,每个指标都以特定方式进行了初始化。您可能希望自动执行一些基于指标的策略,但由于市场变化如此突然,所有指标也许需要几分钟才能反馈正确设置。
为了稍微加快该过程,您可以调用 IndicatorParameters 函数。一旦调用该函数,MetaTrader 5 将填充它,从而准确告诉我们特定指标是如何配置的。然后调用另一个函数(通常是 IndicatorCreate)来更改该指标的配置。如果 EA 基于该指标买入或卖出,它会立即明白该怎么做,因为我们已有一个触发器。
这个问题曾有过详细讨论,并在一系列关于交易自动化的文章中进行了演示。在文章《创建一个自动工作的 EA(第 15 部分):自动化(VII)》中,我们研究了如何依据指标作为买入或卖出的触发器。
但正如我刚才提到的,我们可以调用 IndicatorParameters 函数来令同一 EA 更加有趣。不过,我们不会探讨 IndicatorParameters 函数的使用,至少在本系列的回放/模拟中不会。我只是想提一下这个函数有多么实用。
结束语
在本文中,我们见证了如何向指标发送数据。虽然我们使用智能交易系统执行此操作,但您亦可用其它类型的流程,例如脚本。不幸的是,至少在撰写本文时,由于服务的性质,不可能使用服务作为完成这项工作的一种方式。
但没关系。我们已尽我们所能配合它工作。然而,这个问题还没有完全完结。我们仍然需要了解如何将数据从指标传输到智能系统。我们需要一种方式在回放/模拟系统中使用 Chat Trader,但我们不想使用全局终端变量来做到这一点,我们不想编译多个程序,并冒着忘记编译其中任何一个程序的风险。
为了了解我们离 Chart Trader 所需的目标有多近,我们需要另一篇文章。因此,在本系列的下一篇文章中,我们将了解如何处理创建图表交易者所需的方式。
不要错过下一篇文章:这个话题会非常有趣和令人兴奋。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11591
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



