市场模拟(第 16 部分):套接字(十)
概述
在上一篇文章 “市场建模(第 15 部分):套接字(九)”中,我们解释并展示了部分将用 Python 编写的服务器代码。然而,仅凭这段代码还不足以实现 MetaTrader 5 与 Excel 之间的通信系统。除却该文中所提及的要素之外,我们仍需更多要素。很多人可以直接用这段 Python 代码实现整个解决方案,而且他们这样做并没有错。如果我们这样做,我们就会把自己限制在一个完全封闭的系统中。换言之,我们将无法适应执行某些动作或涵盖更多特定的脚本。然而,通过将系统划分为多个部分,我们可以更灵活地实现它,并利用每种语言的优势。这样,我们可以充分利用每种语言或程序所能提供的最佳功能。
你或许会觉得这很愚蠢。但需知,我们此举仅是在证明 MetaTrader 5 能够以全然未被发掘的方式加以运用。正如我们展示了如何使用 Excel 一样,我们也可以使用任何其他程序。此外,我们可以利用专门为此目的开发的程序来创建自己的方法,从而为特定类型的工作提供特定的分析和操作模型。这样,MetaTrader 5 只需在我们所创建的订单和市场订单之间进行传输,从而避免了开发交易平台所需的所有工作。
因此,可认为所有解释的涵盖范围均远超我们此处所展示的内容。要做这样的事情,你需要了解并学习很多知识。但如果你真心希望这样的事情能成为现实,那么你可以把这些文章看作是我们能力的一个小缩影。然而,在我们了解还需要实施哪些内容才能使 Python 服务器真正发挥作用之前,所有这些都不会有任何实际意义。因此,在本文中,我们将介绍需要在 VBA(Excel 的一部分)中实现的部分。
Excel 入门
需要在 VBA 中实现的代码并不复杂。事实上,将要展示的大部分内容,那些用 VBA 编写脚本的人已经完成了。然而,其中存在几点,其难度或大或小,对部分人而言或许显得颇为复杂。然而,我们想强调的是,这里的一切都尽可能地安排得简单且具有教育意义。此外,前一篇文章中提供的代码将保持不变。你所需要做的就是在 VBA 中创建元素,以便根据你打算执行的操作来执行必要的任务或设置。
首先,我们来看一下 Excel 的界面。我们可以在下面看到:

如果你会使用 Excel,你应该已经注意到,在上图中,按钮是图形。它们允许您创建带有圆角的界面。然而,它们的操作与标准控件没有太大区别,因为它们可以与宏相关联。因此,当您单击这些图形之一时,Excel 会将其视为与常规按钮进行交互,并发出适当的调用,以执行与该形状或按钮上的单击事件相关联的宏。
实际上,当我们仅关注图像中显示的界面时,我们感兴趣的是单元格的内容。请注意,单元格 B2 包含的信息表明服务器处于离线状态。同样,单元格 B5 包含一些其他信息,在本例中,这些信息指明了我们想要从 MetaTrader 5 中获取信息的交易品种。正是在这一刻,一个想法开始成形。即使我们还不知道 MQL5 中会编写什么程序,但我们已经能够大致了解我们需要什么以及我们能做什么。
还记得在解释服务器代码时,我们提到它会从单元中捕获信息并将其发送到 MetaTrader 5 以获取某些数据吗?正是在这个单元格(其值为 WDON23)中,我们需要采取行动。然而,如果我们要手动更改此单元格中的信息,则必须先停止服务器,更改单元格 B5 中的值,然后重新启动服务器。之后,MetaTrader 5 将接收到新的参数并进行分析。
这些更改非常简单,可以随时进行,服务器永远不会知道发生了什么,因为所有操作都将在 MetaTrader 5 和 Excel之 间进行。但是,正如之前提到的,我再次重申,这里的目的是为了教学。但是,没有任何措施可以阻止我们创建自己的协议来获取更多信息或访问其他交易品种。只需将数据放入指定的 Excel 单元格中即可。但你可能会问自己:“为什么是这个特定的单元格?为什么不使用另一个呢?”关键在于服务器代码使用索引,而根据该索引使用的就是单元格 B5。然而,在本文中,我们将展示如何在无需修改上一篇文章中所示的服务器端代码的情况下,使用不同的单元格。
如果你理解了这一点,我们就可以进入下一阶段了。现在让我们来看看如何在 VBA 中完成所有操作。
让我们开始编写 VBA 代码吧。
为了实现真正有用的功能,并确保用户不会意识到幕后发生的事情,我们需要用 VBA 做一些工作。因此,首先要做的是创建一个模块。此模块的目的是容纳所有需要实现且不适合放在其他地方的代码。首先需要以代码形式实现的内容如下所示:
01. Private Const szMSG_START_SERVER As String = "Start Server" 02. Private Const szMSG_STOP_SERVER As String = "Stop Server" 03. Private Const szSERVER_SHUTDOWN As String = """/Force Server ShutDown""" 04. Private Const szMSG_MT5_OFFLINE As String = "MetaTrader 5 is offline." 05. Private Const szMSG_MT5_ONLINE As String = "MetaTrader 5 is online." 06. Private Const szMSG_MT5_BUY As String = """Buy in Market >""" 07. Private Const szMSG_MT5_SELL As String = """Sell in Market >""" 08. Private Const iPort As Integer = 9090 09. Private gl_ServerInExec As Boolean 10. Public Const CELL_MT5 As String = "$B$4" 11. 12. Public Sub SwapStatusMT5() 13. Dim c1, c2, c3 As Long 14. 15. If Sheets("System").Range(CELL_MT5).Value = szMSG_MT5_ONLINE Then 16. c1 = RGB(217, 238, 16) 17. c2 = RGB(51, 153, 102) 18. c3 = RGB(255, 80, 80) 19. Else 20. c1 = RGB(132, 130, 122) 21. c2 = c1 22. c3 = c1 23. End If 24. With Sheets("System") 25. .Shapes("Btn_MT5").Fill.ForeColor.RGB = c1 26. .Shapes("Btn_BUY").Fill.ForeColor.RGB = c2 27. .Shapes("Btn_SELL").Fill.ForeColor.RGB = c3 28. End With 29. End Sub 30. 31. Private Sub SwapMsgBtnServer(text As String) 32. Dim c As Long 33. If text = szMSG_START_SERVER Then 34. c = RGB(213, 87, 99) 35. gl_ServerInExec = False 36. Else 37. c = RGB(84, 130, 53) 38. gl_ServerInExec = True 39. SwapStatusMT5 40. End If 41. With Sheets("System").Shapes("Btn_Server") 42. .TextFrame2.TextRange.text = text 43. .Fill.ForeColor.RGB = c 44. End With 45. End Sub 46. 47. Public Sub BtnServer_Click() 48. If gl_ServerInExec Then 49. StopServer 50. Else 51. InitServer 52. End If 53. End Sub 54. 55. Public Sub InitServer() 56. Dim szScript, szCmd As String 57. 58. szScript = Chr$(34) & Application.ActiveWorkbook.Path & "\Server.py" & Chr$(34) 59. szCmd = " ""127.0.0.1""" & Str(iPort) & " ""System"" ""B$2""" 60. VBA.CreateObject("WScript.shell").Run """Python""" & szScript & szCmd, vbHide 61. SwapMsgBtnServer szMSG_STOP_SERVER 62. End Sub 63. 64. Public Sub StopServer() 65. szPath = Chr$(34) + Application.ActiveWorkbook.Path + "\MsgFromExcel.py" + Chr$(34) 66. VBA.CreateObject("WScript.shell").Run """Python""" & szPath & " " & Str(iPort) & " " & szSERVER_SHUTDOWN, vbHide, True 67. SwapMsgBtnServer szMSG_START_SERVER 68. End Sub 69. 70. Public Sub MT5_BuyInMarket() 71. szPath = Chr$(34) + Application.ActiveWorkbook.Path + "\MsgFromExcel.py" + Chr$(34) 72. VBA.CreateObject("WScript.shell").Run """Python""" & szPath & " " & Str(iPort) & " " & szMSG_MT5_BUY, vbHide, True 73. End Sub 74. 75. Public Sub MT5_SellInMarket() 76. szPath = Chr$(34) + Application.ActiveWorkbook.Path + "\MsgFromExcel.py" + Chr$(34) 77. VBA.CreateObject("WScript.shell").Run """Python""" & szPath & " " & Str(iPort) & " " & szMSG_MT5_SELL, vbHide, True 78. End Sub
VBA 源代码
这个模块包含了我们接下来所需的一切内容。如您所见,这里声明了几个常量和几个过程。常量有助于我们更快地修改代码。每个过程都有其特定的目的,且在整体代码中,它们都扮演着重要的角色。请注意细节,以便正确地在 VBA 中修改此模块。
首先,我们来看第 55 行。我们实际上并不会按照常规顺序来执行这个过程。我们将根据函数和过程在使 Excel 能够管理 Python 服务器方面的重要性,对其进行考察。第 55 行有一个启动服务器的过程。请注意,Python 脚本必须与 Excel 文件位于同一文件夹中。这是因为,在第 58 行,我们使用 VBA 来获取 Excel 文件所在的目录。这样,我们就构建出了包含 Python 脚本名称的完整路径。
现在,在第 59 行,我们声明将传递给脚本的参数。要正确理解这行代码,请回顾上一篇文章,并复习脚本应接收的参数的正确顺序。第 60 行是神奇之处,因为这行代码调用了并运行了 Python 脚本。但随后主要问题出现了:这行代码行不会等待代码返回 —— 也就是说,VBA 会调用服务器脚本并像执行单独线程一样执行它。请注意,这并非字面陈述,而是观察结果。如果你查看任务管理器,你会发现 Python 脚本会与 Excel 应用程序出现在同一个任务块中。这可以从下图看出:

这种行为表明,当 Excel 关闭时,Python 脚本也会关闭。如果我们在任务管理器中观察到脚本位于 Excel 应用程序块之外,那么当 Excel 关闭时,脚本将继续运行,需要手动关闭。在第 61 行的过程末尾,我们调用第 31 行的过程。在此过程中,我们对代表启动和停止服务器的按钮的对象进行了更改。由于这个过程非常简单,而且与形状直接相关,所以没有什么好说的了。
但是,请注意以下事项:第 31 行的过程和第 55 行的过程都没有直接与启动或停止服务器的形状关联。事实上,该任务是在第 47 行由另一个过程执行的。也就是说,服务器启动/停止按钮将执行 BtnServer_Click 过程。这样,我们就可以简单有效地控制 Excel 中显示的内容。然而,这仅仅是个开始;本模块中还有四个过程需要理解。
现在,让我们继续了解服务器控制按钮的形状与什么相关联。请注意,在 BtnServer_Click 过程中,除了调用服务器启动外,还调用了服务器停止。这可以在第 49 行找到,并引用了第 64 行。在这个 StopServer 过程中,我们的操作方式略有不同。为什么会这样?原因是,如果我们研究 Excel 和 Python 之间的交互,就会发现有一些方法可以读取、执行或写入在 VBA 中声明的 Python 过程或变量。然后我们可以简单地向 VBA 变量写入信息,指示服务器应该终止。这样,当 Python 脚本访问此变量时,它就知道需要停止服务器。
然而,正如我们可以将服务器与 Excel 置于同一台主机上一样,我们也可以通过将两个服务器置于不同的机器上来实现这一点。通信将通过套接字进行。因此,我们采用了一种略有不同的过程。在第 65 行,我们指定了向服务器发送消息的 Python 脚本的位置。这条消息实际上是一条命令。在第 66 行,我们包含了要发送到服务器的命令的相关信息。现在,先不要担心这个 Python 脚本;我们稍后会再来看它,就在文章快结束的时候。
位于第 70 行的下一个过程旨在向 MetaTrader 5 发送请求,第 75 行的过程也是如此。这两个过程都应该链接到相应的按钮,以便用户可以直接从 Excel 中与它们进行交互。但如果你仔细观察,就会发现 StopServer 程序和 MT5_BuyInMarket 和 MT5_SellInMarket 程序都使用了相同的 Python 脚本。然而,与已经完全实现功能的 StopServer 过程不同,MT5_BuyInMarket 和 MT5_SellInMarket 过程实际上尚未最终完成。原因是这些程序完全是为了教育目的而进行的 —— 也就是说,它们除了在 MetaTrader 5 终端中显示一条消息之外,什么也不做,但我们将在下一篇文章中讨论这一点。
为了使这两个过程正常工作,需要实施一个通信协议,该协议告知 MetaTrader 5 有关交易品种的信息,并定义诸如仓位的止损和止盈等方面。我认为,对于大多数爱好者而言,这将是一项颇具趣味性的任务,甚至会成为他们个人的挑战。但实现起来却一点也不难。只需指导 VBA 如何创建 MetaTrader 5 能够解读的消息,便可直接通过该平台进行交易。下一篇文章中,我们将更详细地讨论这个过程。目前,不必为此担心。
要完成此模块,第 12 行还有另一个过程。此过程的目的是确保 Excel 交互和控制按钮与 MetaTrader 5 的当前状态相对应。换句话说,当 MetaTrader 5 离线时,交互按钮将显示为灰色;当 MetaTrader 5 在线时,交互按钮将显示为彩色。这样,用户就能知道何时可以向 MetaTrader 5 发送指令。
至此,我们已经完成了模块中 VBA 部分的学习。但这并不意味着我们已经完成了 Excel 主要元素的编程。我们仍需做一些工作来改进支持和互动。接下来需要执行的行动如下所示:

为了消除关于需要使用哪个代码的任何疑问,下面提供了与图片中相同的代码:
1. Private Sub Worksheet_Change(ByVal Target As Range) 2. If Target.Address = CELL_MT5 Then 3. SwapStatusMT5 4. End If 5. End Sub
VBA 源代码
但是这段代码具体做了什么?如果您还不熟悉 VBA 编程,尤其是不习惯为 Excel 编写代码,这可能会让您感到有些困惑。然而,这段代码只是配置了向 MetaTrader 5 发送命令的按钮的状态。为此,会不断检查服务器确定的单元格的值。当此单元格的值因任何原因发生变化时,将触发第 3 行。因此,按钮的颜色将根据 MetaTrader 5 相对于服务器的状态自动变化。
因此,您和最终用户都不必读取特定单元格的内容;只需查看按钮的颜色,即可了解 MetaTrader 5 是处于在线状态还是离线状态。有一点很重要,尤其是对于那些不知道如何在 Excel 中执行此类任务的人来说:请查看上图,了解代码需要放在哪个工作表上才能正常工作。如果你将这段相同的代码放在错误的工作表上,我们将无法得到预期的结果。所以要注意这个细节。
如我们之前所做的那样,还有另一项任务也需要着手处理。正如我们在放置代码时需要谨慎选择位置一样,这里的代码也需要放置在特定的位置。因此,您需要插入以下代码,如下所示:

如果你不确定那里到底需要放什么,可以参考上图中的相同代码。
1. Private Sub Workbook_BeforeClose(Cancel As Boolean) 2. ThisWorkbook.Save 3. StopServer 4. End Sub 5. 6. Private Sub Workbook_Open() 7. InitServer 8. End Sub
VBA 源代码
这个选项更为简单,因为我们只有两个相当直接的事件处理函数。第一个函数 Workbook_BeforeClose 将在用户请求关闭 Excel 工作簿时调用。这种情况发生时,将会发生两件事。第一个可以在第 02 行看到,该行指示 Excel 保存工作簿。因此,即使我们对工作簿进行了更改,Excel 也不会要求我们确认保存,因为当程序关闭时,工作簿会自动保存。第二个操作在第 03 行显示,其中我们调用了模块中显示的过程。我们的目标是关闭服务器。
好的,第 06 行的内容更简单一些。在这一行中,我们有一个 Workbook_Open 事件处理函数,我们只需在第 07 行调用启动服务器的模块过程即可。当工作簿在 Excel 中打开时,将执行此 Workbook_Open 调用。因此,当您或最终用户请求打开它时,它将自动启动服务器,该服务器将等待开始从 MetaTrader 5 接收数据。
这一切都运行得非常完美。您可以创建一个 Python 服务器,代码与上一篇文章中所示的完全相同,创建一个简单的电子表格,添加一些按钮或形状以进行交互,当然,还要确保 VBA 代码中使用的形状名称完全相同。之后,您就可以测试系统了。你会发现可以毫无问题地初始化服务器。但还缺少一些东西:Excel 与 Python 服务器交互的代码。
如果你仔细阅读了整个解释,就会发现我们还需要另一段 Python 代码。该文件应命名为 MsgFromExcel.py。要完成 Excel 和 VBA 课程的这一部分,我们必须查看最终脚本。不要灰心丧气,认为自己什么也不懂;让我们停下来,好好想一想。
Excel 启动时,会调用 Workbook_Open 过程。这将调用 VBA 模块中的 InitServer 过程。此过程将在与工作簿相同的文件夹中查找 Server.py 文件。这是上一篇文章中提到的 Python 服务器脚本。这部分完成后,Excel 将以常规的方式响应用户的命令和操作。
如果我们尝试启动系统,但服务器未启动,那么让我们返回并重复刚才解释的步骤。这是因为服务器应该自动启动。如果没有,请在继续之前先找出代码中的错误。
下一个阶段是在我们关闭服务器时。当我们请求关闭 Excel 应用程序或执行调用 StopServer 过程的操作时,就会发生这种情况。当执行这些操作中的任何一个时,都会执行模块中的第 65 行代码。到目前为止,你还没有看到此刻正在运行的代码。为维系您的兴趣并确保秩序井然,我们将在另一主题中探讨此问题。
Exce l客户端
是的,这正是你读到的内容。MsgFromExcel.py 脚本实际上是一个客户端,它将与同样已打开的服务器进行交互,并在 Excel 会话中保持活动状态。为便于理解,下文提供了该文件的代码:
01. import socket as sock 02. import sys 03. 04. if sys.argv[2]: 05. conn = sock.socket(sock.AF_INET, sock.SOCK_STREAM) 06. try: 07. msg = '<mt5 with excel>:' + sys.argv[2] 08. conn.connect(('localhost', int(sys.argv[1]))) 09. conn.send(msg.encode()) 10. conn.close() 11. except: 12. sys.exit(1)
Python 源代码
如果你有一些 Python 经验,那么通过分析这段代码,你可能已经注意到了一些东西,并产生了以下想法:“但这就是一个打开连接、发送数据,然后关闭连接的客户端。这样做有什么意义呢?”确实,上面的代码就是一个用 Python 编写的 TCP 客户端,它就是这样做的。它打开连接,向服务器发送一些信息,然后关闭连接,准备再次被调用。
原因是这个客户端不需要一直保持与服务器的连接。与运行在 MetaTrader 5 上的 MQL5 客户端不同,它仅用于向服务器发送某些命令,以执行某些操作或发送信息。因此,你可以看到相同的代码是如何从 StopServer、MT5_BuyInMarket 和 MT5_SellInMarket 过程中被调用的。其目的是让它执行一个如果服务器和 Excel 位于不同的机器上我们就无法执行的操作。
因此,该脚本接受两个参数。第一个参数指定要使用的端口,第二个参数是服务器将执行或传递给 MetaTrader 5 的命令或消息。完成此操作后,即可关闭此客户端刚刚打开的连接。
在此,我想就一些可能很重要甚至会引起疑问的事情发表一下看法。如果你刚开始学习 Python 或 VBA 编程,并且觉得这些材料很有趣,我想让你知道这只是我发现的一种教学方法。这里所做的大部分工作都有改进的空间,而且改进空间很大。主要原因是我们在代码中没有加入很多检查。但我想强调的是,这个用 Python 创建的特定客户端可以很容易地转换为 VBA 代码。
因此,唯一用 Python 编写的模块就是创建服务器的模块。然而,由于用 Python 编写的代码比用 VBA 实现相同的客户端要简单得多,也清晰得多,我们决定保留 Python 代码。但如果你想尝试使用 VBA 来做同样的事情,请务必小心,因为通过 VBA 来完成与这里相同的事情,过程会变得更加复杂。
总结性思考
尽管在这篇文章中,我们还没有完全解释清楚如何在 Excel 和 MetaTrader 5 之间建立通信。我们即将完成这项挑战。然而,在我们开始之前,我希望你们试着理解这两篇文章——这篇文章和上一篇文章。这样,你就能真正理解下一篇文章的内容,在那篇文章中,我将专门介绍与 MQL5 编程相关的部分。但我会尽量让它通俗易懂。如果你不理解最后这两篇文章,那么你很难理解下一篇,因为内容是连贯的。要做的事情越多,为了实现目标,你需要创造和理解的东西就越多。
我希望,当你尝试阅读并实践下一篇文章中将要讨论的内容时,你已经能够理解并应用至今所学到的知识。如果你有疑问,我建议你先看看之前的文章,因为正如我们一开始所说的,套接字这个话题内容相当丰富,且包含许多细节。有些比较简单,有些则相当复杂。然而,了解这个主题非常重要,这样你才能知道将来可能会出现什么情况。
尽管你可能认为套接字的话题与回放/模拟系统无关,但我想让你重新考虑一下这个想法。如果你想创建一个不依赖于 MetaTrader 5 内部运行的程序的回放/模拟系统(就像目前为止所做的那样),你就需要了解套接字的工作原理。这样,你就能模拟所需的一切 —— 不是按照我演示的那样,而是通过模拟一个交易服务器,然后简单地告诉 MetaTrader 5 连接到你自己模拟的那个服务器。
但既然我们的目标不同,这些问题我们下次再讨论。下篇文章再见!
| 文件 | 描述 |
|---|---|
| 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 Code C++ Server.cpp | 创建并维护一个用 C++ 开发的套接字服务器(迷你聊天版本)。 |
| Python Code 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/12828
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
从基础到中级:结构(四)
价格行为分析工具开发(第 31 部分):基于Python的K线识别引擎(一)—— 手动检测
新手在交易中的10个基本错误
MQL5自优化智能交易系统(第八部分):多策略分析(3)—— 加权投票机制