开发回放系统(第 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