
开发回放系统(第 48 部分):了解服务的概念
概述
在上一篇文章开发回放系统(第 47 部分):Chart Trade 项目(六)中,我们成功地使 Chart Trade 指标发挥作用。现在,我们可以重新关注我们真正需要开发的东西了。
在这一系列关于回放/模拟系统的文章开头,我花了一些时间尝试让服务能够在图表上放置一个控制指标。虽然一开始没有成功,但我没有放弃,并且一直在努力。尽管我多次尝试,但在这件事上我始终未能成功。但由于这个项目再也无法停止,那一刻我决定另辟蹊径。
这真的很困扰我,我可以用脚本做点什么,但当我试图用服务做同样的事情时,我无法让它正常工作。
你可能会想:“那又怎样?你能用脚本做些什么并不意味着什么。“不过,如果您真的这么想,恐怕是因为缺乏 MQL5 编程知识。在 MQL5 中创建的任何脚本都可以转换为服务。基本上,服务和脚本有两点不同。当然,还有更多,但这两点是最明显的,每个人都能注意到。
第一个不同点是,脚本始终与特定图表相连,并保持到图表关闭为止。值得注意的是,在改变时间框架的时候,MetaTrader 5 实际上会发送重绘图表的命令。为了加快这一过程,它会关闭图形对象(不是窗口,而是窗口内的对象)并创建一个新对象。这样就可以快速重绘图表。但是,脚本不会在图表上重新载入,因为它不具备此功能(因为它不支持某些事件)。
因此,如果要使用脚本监控图表,您不能直接在图表上使用。而这可以借助在图表之外但能够观察到图表的东西来实现。这看起来非常复杂和令人困惑,不过,在大多数情况下,只需更改代码的某些细节并将脚本转换为服务即可实现这一目标。然后,作为一个服务,脚本将不再与特定图表相连,而是可以持续监控资产图表。
您可能很难理解为什么会出现这种情况的逻辑和原因。但如果没有这一点,就不可能进入进一步的开发阶段。因此,创建一个可以监控图表上发生的事情的服务对于我们要做的事情至关重要。不是因为我们必须创造一些东西,而是因为我们需要观察。
您可以将这篇文章作为宝贵的研究资源,因为其中的内容对回放/模拟系统至关重要。此外,它对于更复杂、更详细的项目也非常有用,因为在这些项目中,我们需要分析图表,而不需要与图表相关连。
实现示例脚本
为了真正理解这是如何工作的以及我们将面临的挑战程度,让我们从一个非常简单的例子开始。虽然简单,但功能齐全,足以为日后实现更复杂的功能奠定基础。
让我们从最基本的层面开始理解事物。因此,首先我们将使用一个非常好但非常简单的脚本,如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. void OnStart() 06. { 07. long id; 08. string szSymbol; 09. int handle; 10. bool bRet; 11. ENUM_TIMEFRAMES tf; 12. 13. id = ChartID(); 14. szSymbol = ChartSymbol(id); 15. tf = ChartPeriod(id); 16. handle = iMA(szSymbol, tf, 9, 0, MODE_EMA, PRICE_CLOSE); 17. bRet = ChartIndicatorAdd(id, 0, handle); 18. 19. Print(ChartIndicatorName(id, 0, 0), " ", szSymbol, " ", handle, " ", bRet, " >> ",id); 20. 21. Print("*******"); 22. } 23. //+------------------------------------------------------------------+
脚本源代码示例
虽然脚本非常简单,但它可以帮助我解释我们到底要做什么。在第 2 行和第 3 行中,我们有关于代码属性的数据。它们对我们的解释并不那么重要。第 5 行是我们真正开始编写代码的地方。由于这是一个非常简单的脚本,真正需要的函数只有 OnStart。
第 7 至 11 行是我们的局部变量。这里有一点很重要。实际上,我们并不需要在一个脚本中使用所有这些变量。至少对于我们要做的事情来说是这样。事实上,我们不需要这些变量中的任何一个。但由于第 19 行的原因,我们将在脚本中使用它们。
实际上,在脚本中会发生以下情况:在第 13 行中,我们得到了将放置脚本的图表的标识符。然后,在第 14 行中,我们取得图表中的资产名称。在第 15 行,我们取得了执行脚本的时间框架。这三个步骤都是可选的。虽然第 13 行中的步骤是必需的,但第 14 和 15 行中的步骤对脚本来说并不是强制性的。不过,很快就会明白它们来这里的原因了。
在第 16 行,我们指示 MetaTrader 5 在图表上放置一个标准技术指标。在这个例子中,它是移动平均线。现在,请注意以下几点:添加的移动平均线将是根据收盘价计算的 9 周期数的指数移动平均。如果一切正常,该指标将返回一个句柄。
在第 17 行中,我们使用在第 16 行中创建的句柄,并通知 MetaTrader 5 在图表上启动该句柄所属的指标。在第 19 行,我们将打印出脚本取得和使用的所有值。这将是一种脚本调试形式,它还有另一个目的,我们很快就会了解。
请注意,我们所做的一切都可以通过许多其他方式完成。你可能会想,“多么无用的代码!为什么有人想创造这样的东西?"我不怪你这么想。但说到编程,没有什么是无用的,除非你创造了一些完全没有意义的东西。
如果编译此脚本并将其放在图表上,就会看到它将显示基于收盘价的 9 周期指数移动平均线。由于脚本代码执行和完成的速度非常快,而且主要用于测试其他内容,因此不包含任何错误检查。发生的任何错误都将在 MetaTrader 5 日志中显示。
现在,有趣的部分开始了。让我们想一想:如果把第 16 行换成另一行,例如:
handle = iCustom(szSymbol, tf, "Replay\\Chart Trade.ex5", id);
我们可以使用脚本将我们的 Chart Trade 指标放在图表上。但这样做的真正好处是什么呢?正如本文开头提到的,当我们开始将此脚本不仅用作脚本,而且用作服务时,事情会变得有趣。
将脚本转换为服务
将脚本转换为服务并不是最难的事情,至少在现阶段是这样。这是因为,从本质上讲,代码中唯一的区别就是使用了一个属性来支出脚本实际上是一个服务。基本上就是这样。不过,尽管表面上看似简单,但要做好这项工作,还是需要了解一些细节。
让我们回到简单的例子。如果你试图将此脚本转换为服务,保持代码不变,只添加一个表示它是服务的属性,那么一切都不会奏效。为什么呢?
原因是,与脚本不同,服务实际上并不与图表相关连。而这种情况在某种程度上使情况变得更加复杂。
为了便于理解,我们先来了解一下 MetaTrader 5 的工作原理。让我们先在没有开启图表的情况下启动平台。我们可以添加服务,但我们不能做其他任何事情。一旦第一张图表打开,我们就可以在上面放置指标、脚本和 EA 交易。不过,我们也可以利用服务对图表进行一些操作。
您可能从来没试过,也没见过别人这么做,直到此刻。是的,您可以让服务在图表上放置某些元素。我们会成功的,但不要操之过急。如果不正确理解我试图展示的这个基础,你就不会理解我的新文章,因为我将积极使用类似的方法来加速和简化其他事情。
在 MetaTrader 5 中添加图表时,如果不关闭之前的图表,就会创建一系列图表。但它们的访问顺序与 MetaTrader 5 终端中的组织顺序不同。访问顺序取决于打开的顺序,这是第一点。幸运的是,MQL5 为我们提供了浏览图表列表的功能,这对我们来说非常重要。
因此,如果您想开发一种为特定资产添加特定指标的方法,而不是创建一个模板,就需要使用一个服务。看起来很复杂吧?其实,如果你能真正理解发生了什么,这并不难。
让我们回到上一主题中的代码,在这里我们使用脚本添加了一条 9 周期指数移动平均线。现在,让我们执行以下操作:打开第一张图表时,它应该得到这条移动平均线,无论资产是什么。重要的是,这是打开的第一个图表。在这种情况下,我们需要将相同的脚本转换成下图所示的服务代码。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. void OnStart() 07. { 08. long id; 09. string szSymbol; 10. int handle; 11. bool bRet; 12. ENUM_TIMEFRAMES tf; 13. 14. Print("Waiting for the chart of the first asset to be opened..."); 15. while ((id = ChartFirst()) < 0); 16. szSymbol = ChartSymbol(id); 17. tf = ChartPeriod(id); 18. handle = iMA(szSymbol, tf, 9, 0, MODE_EMA, PRICE_CLOSE); 19. bRet = ChartIndicatorAdd(id, 0, handle); 20. 21. Print(ChartIndicatorName(id, 0, 0), " ", szSymbol, " ", handle, " ", bRet, " >> ",id); 22. 23. Print("*******"); 24. } 25. //+------------------------------------------------------------------+
服务源代码
将上面的代码与前一个主题中的代码进行对比,它们之间有什么区别?你可能会说,在第 2 行,现在有一个属性将此代码定义为服务。确实是这样,但还有些别的东西。我们还有两条不同的代码行。第 14 行是我们输出一条消息的地方,即服务正在等待图表被放置在 MetaTrader 5 终端中,我们预计图表将在第 15 行打开。
这就是事情变得更有趣的地方。要了解实际发生的情况,请编写上面的代码并使用 MetaEditor 进行编译。打开 MetaTrader 5,然后关闭所有打开的图表,绝对要关闭所有图表。然后,运行我们刚刚创建的服务。打开日志窗口,你会看到第 14 行显示的信息。如果出现这种情况,那么你所做的一切都是正确的。现在打开任何交易品种的图表。您将立即看到第 21 行的消息,服务将关闭。但看看图表,你会注意到,正如预期的那样,有一个 9 周期的指数移动平均线。那是因为服务把它放在那里。
亲爱的读者,我希望你能仔细阅读,因为现在我们要把事情弄得更复杂一些。
确保标准图表
这才是使用服务的真正乐趣所在。许多人喜欢使用标准化的图形模型来创建模板。预设模板在这方面对我们有所帮助。它确保所有图表在创建时都符合标准。但是放置模板后,我们可能会意外删除一些指标或更改其中一个指标的设置。这在很多情况下都不是问题,您只需恢复一个指标即可。但是,使用 MetaTrader 5 可以避免这个问题吗?这样的话,这个平台将始终非常接近彭博(Bloomberg)终端。
是的,可以。事实上,我们可以防止用户或我们自己在终端中取消配置任何内容。在这个例子中,我谈论的是指标。在之前的文章中,我介绍了如何强制系统支持图表上的特定指标。在这个回放/模拟系统中,这样做已经有一段时间了,以避免从图表中移除控制指标。但还有一种更通用的解决方案,可以让您在图表中保留任何内容,或阻止其他内容出现。
就预防而言,我认为展示它是如何做到的没有多大意义,但就保持而言,我觉得这真的很有趣。因此,再次更改上一主题中显示的代码,我们得到如下代码:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. void OnStart() 07. { 08. long id; 09. string szSymbol; 10. int handle; 11. 12. Print("Waiting for the chart of the first asset to be opened..."); 13. while (!_StopFlag) 14. { 15. while (((id = ChartFirst()) < 0) && (!_StopFlag)); 16. while ((id > 0) && (!_StopFlag)) 17. { 18. if (ChartIndicatorName(id, 0, 0) != "MA(9)") 19. { 20. handle = iMA(szSymbol = ChartSymbol(id), ChartPeriod(id), 9, 0, MODE_EMA, PRICE_CLOSE); 21. ChartIndicatorAdd(id, 0, handle); 22. IndicatorRelease(handle); 23. } 24. id = ChartNext(id); 25. } 26. Sleep(250); 27. } 28. Print("Service finish..."); 29. } 30. //+------------------------------------------------------------------+
改进的服务代码
您可能会注意到,这段代码包含一些看似奇怪的元素。不过,每个元素都有其存在于代码中的理由。代码本身与上一主题中讨论的内容没有太大不同,但它能够做一些非常有趣的事情。让我们看看这段代码的作用,然后您可以根据自己的需求和兴趣进行调整。
在第 12 行,我们输出一条消息,表示服务正在等待。就像第 28 行一样,我们通知该服务已被删除,不再运行。这两行代码之间发生的一切才是我们真正感兴趣的。为确保服务正确关闭而不会在 MetaTrader 5 中造成问题,我们会检查 _StopFlag 常量,在我们请求关闭服务之前,该常量一直为 true。
因此,在第 13 行,我们进入了一个无限循环。这个循环实际上并不是无限的,因为当我们发出要关闭服务的信号时,这个循环就会终止。
在第15行中,我们得到了与前一代码中相同的所有内容。但现在我们正在添加一个检查,以避免关闭服务时出现问题。请注意:当 ChartFirst 返回一个值时,这将是您在 MetaTrader 5 终端中打开的第一个窗口的值。该值将存入 "id " 变量。请记住这一点,因为从现在起,必须考虑到行动的顺序。
在第 18 行,我们检查窗口是否包含 iMA 指标,即 9 周期移动平均线。如果不满足这一条件,即在图表中找不到 MA,我们将添加它。为此,我们首先在第 20 行创建一个处理函数,然后在第 21 行将 MA 添加到图表中。完成此操作后,我们删除句柄,因为我们不再需要它。因此,如果从图表中删除 MA,它将被重新添加。这将在服务运行时自动完成。
第 24 行将查找下一个窗口。现在请注意,我们需要当前窗口的索引,以便 MetaTrader 5 可以知道下一个索引是什么。不要把事情搞混。如果找到另一个窗口,循环将重复。该过程将重复进行,直到 ChartNext 返回值为 -1。从第 16 行开始的循环就此终止。
当然,我们也不希望服务表现得像个没头脑的疯子。因此,在第 26 行中,我们在循环的下一次迭代之前产生了一个小的延迟,大约 250 毫秒。这样,服务将每秒运行 4 次,确保指定的移动平均线始终出现在 MetaTrader 5 终端打开的任何图表上。这样的事情非常有趣,不是吗?
但我们可以做得更多一些。我们可以强制将元素放置在特定交易品种的特定图表上。这正是我自这一系列文章开始以来多次试图实现的目标,但由于这样或那样的原因,最终未能如愿。现在,假设您是 B3(巴西证券交易所)的交易员,您决定只对特定的交易品种使用某个指标。而且永远只针对那个交易品种。其中一种方法就是使用模板。但很有可能,在每天的忙碌中,你最终会关闭这个交易品种,当你再次打开图表时,才会注意到急需的指标不见了,直到为时已晚。
使用该服务时,这种情况不会发生,因为只要在 MetaTrader 5 终端中打开交易品种图表,该服务就会负责将指标添加到图表中。如果您想使用模板添加其他元素,也可以这样做。但模板的使用将在下一次讨论,因为这涉及到我们将在本系列未来文章中讨论的一些问题。
那么,让我们来看看如何做到这一点。为了避免混淆,让我们在一个新的主题中探讨这个问题。
确保终端质量
如果你觉得上一个话题很有趣,那就准备好吧,因为这个话题会变得更加有趣。因为我们将能够完全按照自己的意愿行事,也就是说,我们将能够使交易品种图表始终与预先配置的终端相对应。这就好比我们可以让 MetaTrader 5 看起来像彭博终端一样,但好处是配置不会被意外更改,因为它不会与模板相关联,而是服务配置的一部分。
为了说明这一点,让我们看看上一个主题中讨论的服务代码是如何修改的。完整代码如下:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. void OnStart() 07. { 08. long id; 09. string szSymbol; 10. int handle; 11. ENUM_TIMEFRAMES tf; 12. 13. Print("Waiting for the chart of the first asset to be opened..."); 14. while (!_StopFlag) 15. { 16. while (((id = ChartFirst()) < 0) && (!_StopFlag)); 17. while ((id > 0) && (!_StopFlag)) 18. { 19. szSymbol = ChartSymbol(id); 20. tf = ChartPeriod(id); 21. if ((StringSubstr(szSymbol, 0, 3) == "WDO") && (ChartWindowFind(id, "Stoch(8,3,3)") < 0)) 22. { 23. handle = iStochastic(szSymbol, tf, 8, 3, 3,MODE_SMA, STO_CLOSECLOSE); 24. ChartIndicatorAdd(id, (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL), handle); 25. IndicatorRelease(handle); 26. } 27. if (ChartIndicatorName(id, 0, 0) != "MA(9)") 28. { 29. handle = iMA(szSymbol, tf, 9, 0, MODE_EMA, PRICE_CLOSE); 30. ChartIndicatorAdd(id, 0, handle); 31. IndicatorRelease(handle); 32. } 33. id = ChartNext(id); 34. } 35. Sleep(250); 36. } 37. Print("Service finish..."); 38. } 39. //+------------------------------------------------------------------+
服务代码
请注意,变化很小,但结果令人震惊。在我们的示例中,我们将使用 B3(巴西证券交易所)的一个交易品种。这是一种迷你美元期货合约。这份合约每个月都会到期,但这不是问题。要理解这一点,我们先来解释一下代码本身。这并不难,但你需要注意细节,否则你就不会得到你所期望的。
我不会介绍我已经介绍过的内容,所以让我们专注于代码的新部分。在第 19 行和第 20 行,我们输入服务将检查的图表数据。我们需要交易品种名称和图表时间框架,以避免将来出错。然后,我们将这些值放入两个变量中。
在第 21 行,我们进行了两次检查:
- 第一个是我们要配置的交易品种名称
- 二是有无特定指标
如果这两项检查都为 true,我们将执行进一步的代码。现在,请注意我们只使用了交易品种名称的前三个字母,因为这是一份有到期日的期货合约。由于我们不想不断更改交易品种名称,因此无论当前合约如何,我们都使用交易品种名称的通用部分。
需要注意的第二点与指标有关。我们必须指定其短名称。如果不知道该名称,只需将该指标放在图表上,然后使用简单的脚本将其名称打印到终端。这样,您就能清楚地知道在那里应该使用什么名称。在这个例子中,我们使用一个 8 个周期、MA 值为 3、常数为 3 的随机振荡指标。但如果指标不同,则应将字符串改为正确的名称。否则,下一步就会出现问题。
那么,让我们回到指标不在指定交易品种图表上的情况。在这种情况下,我们将首先执行第 23 行,在该行设置所需的指标。然后,在第 24 行中,我们将指标添加到新的子窗口中。此时请注意:如果指定了子窗口编号,MetaTrader 5 将在指定窗口中放置指标。这可能会导致混乱。但根据现有代码,MetaTrader 5 将创建一个新的子窗口,并在其中添加指标。
第 25 行释放了句柄,因为我们不再需要它了。只有当指标不在图表上时,才会发生所描述的一切。如果您尝试删除它,该服务将强制 MetaTrader 5 再次将其放置在图表上。但如果它已经出现在图表上,该服务将不会被激活,只会观察正在发生的事情。
重要提示:虽然代码可以工作并且具有足够的性能,但出于实际原因,建议将第 27 行所示的检查更改为更有效的检查。在单个图表上使用多个指标时,最好按名称进行测试,而不是像代码中那样按索引进行测试。因此,只需用以下代码替换第 27 行即可:
27. if (ChartIndicatorGet(id, 0, "MA(9)") == INVALID_HANDLE)
因此,无论指标的索引是多少,图表上只会出现一个指标,即服务在图表上放置的指标。
结论
在这篇文章中,我向大家展示了一些很少有人真正知道如何做的事情。如果你是这样的话,我很高兴向你展示 MetaTrader 5 可以是一个比看起来更强大的工具。对于那些真正了解自己在做什么并能够使用其所有功能的人来说,这可能是最好的平台。
在视频中,您可以看到 MetaTrader 5 在使用本文服务时的行为。但除了这里展示的内容之外,这只是对我们仍需做的事情的介绍,我很高兴亲爱的读者们在阅读这篇文章后感到好奇,并试图了解更多。然而,我想强调的是,学习不仅包括阅读教育文章,还包括独立寻找解决方案。
尽量充分利用一切资源。当你达到可能的极限时,寻找更多合格的人来找到解决方案,同时总是试图超越以前已经做过的事情。这是进化道路的本质,这就是你成为真正专业人士的方式。
演示视频
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11781



