市场模拟(第 17 部分):套接字(十一)
概述
在前两篇文章中,我们介绍了如何配置 Excel 以便与 MetaTrader 5 进行交互。特别是,在上一篇文章“市场模拟(第 16 部分):套接字(十)”中,我们讲解了如何在 VBA 中编写代码。这对于确保 Excel 与用 Python 编写的服务器之间的正确交互是必要的,即使在你看来,这种交互似乎不会引发任何问题。
在我们解释什么是套接字的那一小节中,我们展示了当 Excel 使用不当时可能出现的问题。在 Python 中使用服务器时,会出现这种情况。然而,正如前两篇文章所展示的,编写得当的 VBA 代码能够解决 Excel 与 Python 之间的交互问题,并确保它们之间的协调顺畅。但要彻底完成实现,还需要一个必要步骤。这必须在 MetaTrader 5 的专用部分进行,我们需要在那里做出几个决定。
实现前的规划
我们需要做出的主要决定是,我们将使用哪种编程语言来实现将在 MetaTrader 5 上运行的部分。我提到这一点是因为我们既可以用 Python 来实现,也可以直接使用 MQL5 本身来完成。这两种语言将使我们能够做我们想做的大部分事情,即使不是所有事情。然而,如果我们选择 Python,我们将面临一个限制:由于它是一个脚本,因此必须附加到图表上。相比之下,如果我们使用 MQL5,就可以应用一种更适合执行该任务的方法或模型。
但如果我们要使用 MQL5,我们可以将解决方案定位为一项服务。换句话说,与需要打开图表才能操作的 Python 不同,MQL5 允许我们使用服务与 Excel 进行通信。我可以确认,这种方法有几个优点。
其主要优势在于该服务具有独立性,可以与图表进行交互,几乎可以完全控制 MetaTrader 5,甚至根本不与其交互。通过使用服务,我们可以打开和关闭图表,向图表中添加元素,修改正在运行的进程等等。但最重要的是,由于服务在图表之外运行,即使 MetaTrader 5 正用于与市场无直接关联的目的,服务也可使用。
是的,MetaTrader 5 的用途不仅限于交易资产。我们可以下次再解释这个 — 但前提是我认为演示这个真的很有趣。好的,就这样定了,我们将使用 MQL5,并确保在 MetaTrader 5 中运行的代码部分作为服务运行。现在我们进入实现阶段。
实现
在 MetaTrader 5 中运行的那部分代码的实现没有任何困难。然而,有几点需要考虑。这是必要的,这样你才能让系统正常工作。记住一件重要的事情:不会只有一个程序在运行。事实上,我们必须同时运行三个程序。重要的是,要确保每个部分都能以一种能够相互交流和沟通的方式实施和构建,并且每个部分都能理解其他部分正在尝试或打算做什么。因此,逐步、分阶段地对系统进行测试至关重要。
首先,我们将编写充当服务器的 Python 代码。接下来,我们将创建并测试将在 Excel 中运行的 VBA 代码。只有当 Excel 能够有效地与 Python 服务器交互并向其发送命令时,我们才能在 MetaTrader 5 中进入这一阶段。
因此,对于那些想要创建或实施类似系统,但同时处理多个程序经验不足的用户,我的建议是循序渐进。不要试图一次性完成所有事情。首先,你需要制定一个计划,然后一步步地向前推进。那么,让我们来看看源代码,下面可以查看完整内容:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. input string user01 = "127.0.0.1"; //Address Server 07. input int user02 = 9090; //Port Server 08. //+------------------------------------------------------------------+ 09. #define def_SizeBuff 2048 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. bool tab; 14. int sock; 15. uint len, id, iMem; 16. char buff[], ArrMem[]; 17. string szMsg, szCmd, szRet; 18. 19. sock = INVALID_HANDLE; 20. ArrayResize(ArrMem, def_SizeBuff); 21. while (!IsStopped()) 22. { 23. Print("Trying to connect..."); 24. while (!IsStopped()) 25. { 26. if (sock != INVALID_HANDLE) 27. SocketClose(sock); 28. if ((sock = SocketCreate()) != INVALID_HANDLE) 29. if (SocketConnect(sock, user01, user02, 1000)) 30. break; 31. else 32. Sleep(500); 33. } 34. if (!IsStopped()) 35. { 36. szMsg = "<MT5 with Excel>:MT5"; 37. len = StringToCharArray(szMsg, buff) - 1; 38. SocketSend(sock, buff, len); 39. Print("Excel is online..."); 40. } 41. ArrayInitialize(ArrMem, 0); 42. iMem = 0; 43. while (!IsStopped() && SocketIsConnected(sock)) 44. { 45. szCmd = szRet = ""; 46. id = 0; 47. do 48. { 49. len = SocketIsReadable(sock); 50. len = ((iMem + len) < def_SizeBuff ? len : def_SizeBuff - iMem); 51. if (SocketRead(sock, buff, len, 500) > 0) 52. ArrayInsert(ArrMem, buff, iMem); 53. for (int c0 = 0; (ArrMem[c0] != 0) && (c0 < def_SizeBuff); c0++) switch (ArrMem[c0]) 54. { 55. case '[': 56. id = 1; 57. case ';': 58. if ((SymbolExist(szMsg, tab)) && (id < 2)) 59. szRet += StringFormat("%f%s", iClose(szMsg, PERIOD_D1, 0), (ArrMem[c0] == ';' ? ";" : "")); 60. szMsg = ""; 61. break; 62. case ']': 63. id = 2; 64. iMem = 0; 65. break; 66. default: 67. switch (id) 68. { 69. case 0: 70. szMsg += StringFormat("%c", ArrMem[c0]); 71. break; 72. case 1: 73. szCmd += StringFormat("%c", ArrMem[c0]); 74. break; 75. case 2: 76. for (iMem = 0; (ArrMem[c0 + iMem] != 0) && ((c0 + iMem) < def_SizeBuff); iMem++); 77. ArrayCopy(ArrMem, ArrMem, 0, c0, iMem); 78. break; 79. } 80. } 81. }while (SocketIsConnected(sock) && (!IsStopped()) && (id != 2)); 82. if (!IsStopped() && SocketIsConnected(sock)) 83. { 84. if (szCmd != "") 85. { 86. Print(szCmd, "<CMD<"); 87. szRet = "N/D"; 88. } 89. len = StringToCharArray(szRet, buff) - (szCmd != "" ? 1 : 0); 90. SocketSend(sock, buff, len); 91. } 92. } 93. } 94. if (sock != INVALID_HANDLE) 95. SocketClose(sock); 96. ArrayFree(ArrMem); 97. Print("Shutting down..."); 98. } 99. //+------------------------------------------------------------------+
MQL5 的代码
在我们开始解释代码之前,我想先说明,这段代码仅供教学使用。因此,我们在此将仅对此话题进行简略的探讨。别以为这就是我们能做的全部。然而,亲爱的读者们,如果你们能理解正在发生的事情,你们将能够创造出许多有趣的东西。你可以将上述代码作为第一步,并作为可能的参考。
好了,我们开始分析这段代码的作用。但为了激发你的热情和专注,我们先来看看当一切正常运转时会发生什么。这正是你下面所能看到的。首先,我们来观察一下 Excel 中发生了什么。请注意,在制作此动画时,该服务已在 MetaTrader 5 中运行,您可以在另一段动画中看到这一点。

在执行过程中,通过观察 Excel 中的 MetaTrader 5 消息,可以注意到以下操作:

这是一个有趣的结果,不是吗?请记住,这两个应用程序,即 Excel 和 MetaTrader 5,可以安装在不同的计算机上,即使在这种情况下,我们也能得到相同的结果。既然你的好奇心已经被激发,那么让我们来看看这段代码在 MQL5 中是如何工作的,因为其他部分已经在之前的文章中解释过了。
MQL5 代码分析
在第二行,我们指明代码是作为服务实现的。如果缺少这行代码,相同的代码将作为脚本运行。在之前的文章中,我们已经解释过服务和脚本之间的区别。但如果您有任何疑问,请记住:服务与图表没有直接关联。相反,脚本与图表相关联。然而,服务代码和脚本代码实际上将完全相同。它们之间唯一的区别就是第 02 行。
接下来需要理解的是第 06 行和第 07 行。使用它们是为了在 MetaTrader 5 中运行可执行文件时,用户可以向其传递特定参数。这些是需要调整的参数。即,Python 服务器运行的地址及其端口。
请注意:您需要指定的不是启动 Excel 的位置,而是 Python 服务器运行的位置。这类事情可能会引起混淆。但请记住:Python 服务器并未嵌入到 Excel 中,它可能位于另一台计算机上。但 Excel 必须知道如何找到这个服务器。为了更好地理解,请参考上一篇文章,其中我们解释了 Excel 中存在的、用 VBA 编写的代码部分。
完成初始(和基本)部分后,我们继续到第 11 行,代码的主要部分从这里开始。在第 13 行到第 17 行之间,声明了一些将在本代码中使用的变量。现在让我们继续来看这一部分,乍一看可能会有些令人困惑,但稍后一切都会变得非常清晰。请注意,在第 19 行,我们初始化了其中一个变量。原因在于,整个代码都被完全包含在一个大循环中。这个循环从第 21 行开始,一直持续到第 93 行,因此,要结束这个循环,唯一的办法就是手动停止服务或关闭 MetaTrader 5。
为了了解我们与服务器连接的状态,我们将在 MetaTrader 5 的消息窗口中输出几条消息,如上图动画所示。第一条消息在第 23 行输出。在此,我们仅表明客户端(即 MetaTrader 5)正在尝试连接。这是因为我们在第 24 行代码中进入了一个无限循环。这个循环是无限的,因为它只有在程序结束时才会结束。
“但是等一下。将一个无限循环嵌入到另一个循环中的想法是没有意义的。为什么要这样做?”亲爱的读者们,请放心,第 24 行的循环是无限的,但它会在非常特定的时刻结束。然而,由于保存套接字的变量可能已经包含了一些值,我们首先需要释放旧的套接字。该操作的检查和执行在第 26 行完成,释放发生在第 27 行。因此,第 19 行是至关重要的。如果我们没有设置一个变量来表示连接不存在,否则,在第 27 行释放该套接字时,就无法可靠判断当前是否确实存在可释放的连接。
所以我们一致同意这样做。如果连接已存在,会将其关闭。如果它不存在,我们就什么也不做。但是,在第 28 行,我们尝试创建一个套接字。这极有可能成功。但是,创建套接字并不一定意味着连接已经建立。因此,如果我们在第 29 行连接服务器失败,则将执行第 32 行。这将造成短暂的停顿,以便我们再次尝试。
然而,这就是第 24 行无限循环问题的本质所在,如果我们无法连接到服务器,系统会为我们分配一个套接字。在这种情况下,我们必须将其返回给操作系统。那么,让我们回到第 26 行的检查。然而,这一次检查将会成功,第 27 行代码将会执行,从而释放分配的套接字。我们重复这一分配和释放套接字的过程,直到成功建立连接或用户终止程序为止。
现在,如果我们看一下上面的动画,我们会看到在某个时刻我们开始发送和接收数据。“但是如果第 24 行的循环永远不会结束,那又怎么可能发生这种情况呢?”原因就在第 29 行。请注意,如果我们成功连接到服务器,第30行代码将会执行,并且我们将退出从第 24 行开始的循环。
现在,让我们来看一些非常有趣的东西。在第 34 行,只有当程序未被用户终止时,程序才会成功执行。换句话说,在这种情况下,我们已经连接到服务器。因此,我们需要告知服务器我们的身份。因此,第 36 行创建了要发送到服务器的消息。请注意这条信息,服务器必须能够识别它。否则,服务器将断开客户端连接,我们将返回到第 24 行的循环。因此,如果我们计划在未来对系统进行任何更改,我们必须格外谨慎。但如果一切顺利,服务器能识别我们为有效客户端,那么必须采取一系列行动和特定预防措施。因此,在第 41 行,内存缓冲区被清空。紧接着,在第 42 行,我们指出该缓冲区的零位置。
如果你刚开始探索套接字编程的世界,你可能无法理解第 41 行和第 42 行中描述的操作的原因。但如果你已经有一些经验,你可能就会知道,在向套接字写入数据时,累积的信息量可能会远超我们在读取时所预期看到的。更精确地说,这种情况在 TCP 连接中经常发生。由于我们不希望在通过服务器传输时丢失从 Excel 接收到的任何请求或数据,我们必须采取预防措施以避免数据丢失。因此,必须采取一系列行动。但是,为了更好地理解如何做到这一点以及为什么它是必要的,我们需要更详细地查看代码。
然后我们进入一个新的循环,这是主循环,即负责在 Excel 和 MetaTrader 5 之间交换消息的循环。这个循环从第 43 行开始。现在来看一下循环声明。请注意,我们正在检查程序是否终止以及套接字是否已连接。我们将假设一切正常,我们可以进入这个循环并长时间保持。
因此,首先要做的是初始化一些变量。这是在第 45 行和第 46 行完成的,紧接着,我们进入另一个循环,该循环从第 47 行运行到第 81 行。密切关注这个循环内部发生的情况。这一点很重要,因为如果我们想要或需要做其他事情,除了我们已经展示和评论的内容之外,正是在这里我们需要做出改变或改进。这极有可能发生在需要向 Excel 传输更多信息时,或者甚至是在需要理解将发送给 Excel 的命令时。
例如,在第 48 行,我们可以看到套接字中有多少信息。然后,在第 51 行,我们尝试读取相同的信息。不过,有一个小细节你可能想改一下。在第 49 行,我们检查要放入内存缓冲区的信息量是否超过了分配的缓冲区大小。在这种情况下,我们只需调整读取的数据量,使其不超过已分配的大小。但是,如果需要,我们可以更改分配区域的大小。总之,最终结果将非常相似。
在我们解释了第 50 行出现的原因之后,我们可以继续看第 51 行。请注意,如果读取任何数据,则在第 52 行,该数据将被插入到已分配的内存中。在解释的这个阶段,第 52 行似乎没有意义;它给人的印象是它根本不存在。但让我们继续探讨这段代码是如何工作的;这样,这行代码就会变得容易理解了。然而,重要的是要理解,从套接字读取的数据将从特定位置开始放置。该位置由 iMem 变量指示。我建议不要忘记这一点,因为在第一次读取时,iMem 变量包含零值。然而,在后续读取过程中,它的值可能会有所不同。
这就是我们如何推导到第53行的过程。在这里,我们启动了一个循环,这个循环构成了这段代码的核心,因为它将执行大部分工作。请注意,在此过程中,我们会检查某些条件;如果满足这些条件,还会执行一系列小步骤。这些步骤描述了 MetaTrader 5 和 Excel 之间的通信协议。因此,此处所做的任何更改也必须在 VBA 代码中加以考虑,因为 VBA 将处理来自 MetaTrader 5 的响应。如果处理工作是在服务器代码中完成的,那么相应的更改也需要在 Python 代码中进行。
这个循环(由第 53 行组成)将逐个处理传入的消息字符,以理解正在发生的情况,以及最重要的是,如何继续进行。但我们必须立即澄清,这段代码仅用于教育目的。然而,如果你理解这里的所有工作原理,你将能够根据你的特定需求调整这段代码。
你或许能想出其他方法来完成这里所做之事。一种方法是使用 StringSplit 函数。如果你那么做,肯定不会出错,但可能会稍微复杂一些。当然,这一切都取决于所使用的消息传递协议。在这种情况下,协议非常简单。在方括号前有一个需要解释的字符。而方括号内表示要执行的命令。很简单,然而,由于这部分指令并不负责市场上的买卖操作,因此您无法使用此功能。但别担心。
在文章的最后,我们将提供实现此功能的必要链接,因为此功能已在复制/建模系统中实现。但是那些不遵循这个顺序的人将无法学会如何去做。然而,这并不难。您只需要创建一个协议。为了制定这个协议,我们需要知道如何执行指定的命令。如果你不知道该怎么做,一定要查看我们留下的文章作为参考资料。
那么,回到代码,我们发现了一些可能会让很多人,尤其是初学者感到困惑的地方。请看第 55 行。此时,我们有一个左方括号。这表示品种符号名称已经结束,后面开始的是来自 Excel 的命令。为了更清楚地说明,在第 56 行,我们将 id 变量的值(原为 0)更改为 1。请不要忘记这一点。循环开始时,由于第 46 行代码的作用,id 变量的值为零;这种情况发生在第 43 行代码的循环第一次运行时。请注意以下几点。
由于左大括号表示已经获得了交易品种名称,我们需要获取该交易品种的数据。在这种情况下,我们只考虑收盘价。就这些,但我们可以获取我们想要或需要的任何信息。为此,只需制定规则来指明应获取哪个值即可。但我们还是回到主要问题上来吧。请注意,第 55 行的 case 语句会一直执行到第 57 行的 case 语句,两者之间没有 break 命令。这可能吗?是的,可以做到,但在解释第 57 行的 case 之前,请注意这是一个不同的字符。为什么呢?
现在我们来到了真正关键的部分。第 57 行 case 后面出现的字符表示可以拆分多个交易品种。换句话说,如果 Excel 想要使用多个交易品种查找信息,它可以用这个字符将它们分隔开来。但是,如果我们参考上一篇文章中的 VBA 代码,就会发现这个功能并没有被使用。只需将返回值输出到单个单元格中即可。事实上,亲爱的读者,这是你们的一项任务:将这些数据拆分并转移到相应的单元格中。由于我不知道每个程序员是如何创建他们的电子表格的,因此执行这种拆分的方法将根据具体情况而有所不同。但这并不难;它只取决于你是如何创建电子表格的。
总之,在第 58 行,我们将检查我们要查找的交易品种是否存在。在这种情况下,我们将在第 59 行创建返回字符串。但请注意这一点。虽然我们正在创建返回字符串,但它实际上并不是我们将要返回给服务器(也可能是 Excel)的字符串。我们稍后会解释原因。现在,由于我们刚刚处理了该交易品种,因此在第 60 行,我们清除字符串以尝试捕获下一个交易品种,并且在第 61 行,我们使用 break 命令来避免进入下一个情况。
接下来,在第 62 行,我们看到一个右括号。这意味着我们已经捕获了 Excel 给出的命令,接下来我们将确定下一个交易品种的名称。即使从套接字中读取到额外信息,现在也不会使用。这是因为在检查第 58 行时,会发现第 63 行表明该数据不应该使用。现在我们来拆分命令交易品种。但我们需要知道这些东西分别代表什么。如果没有执行任何 case,则执行第 66 行中指示的默认选项。
请注意,信息的类型及其处理方式取决于 id 变量的值。因此,即使值为零,交易品种名称也会被捕获。这是在第 70 行完成的。如果 id 为 1,则会捕获 Excel 提供的命令。这是在第 73 行完成的。如果该值为 2,我们将把数据从当前位置移动到内存区域的开头。
现在请注意,这会导致从第 47 行开始的循环在第 81 行结束。其中一个原因正是命令块的结尾被捕获。因此,我们继续进行工作的最后一部分。但首先,我们需要在第 82 行执行另一项检查。如果我们成功了,我们会收到新的检查。我们的目标是验证是否有任何命令被发送。如果该命令被发送,则其优先级高于其他所有命令。正是在这一点上,即第 86 行,应该执行本系列其他文章中描述的操作。这样,我们就可以在 Excel 中生成买入或卖出请求。
为了正确完成任务,我建议首先学习我们在参考文献部分指出的补充材料。在收到命令执行请求后,MetaTrader 5 的返回值或响应会发生变化,不再显示捕获的交易品种数据,而是变成一个特定的字符串。最后,在第 89 行和第 90 行,发送来自 MetaTrader 5 的响应。请注意,此响应将取决于我们是在执行命令还是从交易品种中捕获数据。
最终想法
尽管今天的代码中并未包含如何执行 Excel 所给命令的具体实现,但你可以看到,Excel 与 MetaTrader 5 之间的交互相当流畅,并未出现重大问题。然而,你可能需要对这个实现进行一些小的改进和修正。但如果这种情况真的发生了,那就意味着我们在这个阶段已经真正实现了我们的目标,即解释了如何使用套接字,因为很多人之前并不知道,在市场上进行交易时,可以不查看 MetaTrader 5 图表,也不使用任何基本面分析。
尽管实现负责发送订单的部分需要我们实现该系统的其他部分,但我认为在此不展示如何实现这部分没有任何问题。我需要你清楚地明白你要做什么。因此,我们将不会展示最终版本。然而,如果你研究过上述文章,你几乎肯定能够实现这一功能。在下一篇文章中,我们将探讨另一个话题,与套接字的话题类似,在我们回到复制/建模系统之前,也需要先研究这个话题。
参考:
开发回放系统(第 74 部分):新 Chart Trade(一)
开发回放系统(第 75 部分):新 Chart Trade(二)
开发回放系统(第 76 部分):新 Chart Trade(三)
开发回放系统(第 77 部分):新 Chart Trade(四)
开发回放系统(第 78 部分):新 Chart Trade(五)
| 文件 | 描述 |
|---|---|
| Experts\Expert Advisor.mq5 | 演示 Chart Trade 与 EA 之间的交互(需要 Mouse Study 才能进行交互)。 |
| Indicators\Chart Trade.mq5 | 创建一个窗口来配置要发送的订单(需要 Mouse Study 进行交互)。 |
| Indicators\Market Replay.mq5 | 创建用于与复制/建模服务交互的控件(交互需要 Mouse Study)。 |
| Indicators\Mouse Study.mq5 | 提供图形控件与用户之间的交互(这对于复制系统和实际市场都是必要的)。 |
| Services\Market Replay.mq5 | 创建并维护市场复制/建模服务(整个系统的主文件)。 |
| VS C++ Server.cpp | 创建并维护一个用 C++ 开发的套接字服务器(迷你聊天版本)。 |
| Python Server.py | 创建并维护用于 MetaTrader 5 和 Excel 之间通信的 Python 套接字。 |
| ScriptsCheckSocket.mq5 | 允许您测试与外部套接字的连接 |
| Indicators\Mini Chat.mq5 | 允许通过指标实现迷你聊天(需要使用服务器) |
| Experts\Mini Chat.mq5 | 允许通过 EA 实现迷你聊天(需要使用服务器) |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12829
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
金融时间序列中的保形预测探索
新手在交易中的10个基本错误
价格走势角度分析:用于预测金融市场的混合模型
有没有办法将“交易记录”和/或“持仓与交易”中的数据导入Excel,甚至Python?
感谢您的关注!