English Русский Español Deutsch 日本語 Português
preview
市场模拟(第 17 部分):套接字(十一)

市场模拟(第 17 部分):套接字(十一)

MetaTrader 5测试者 |
18 1
Daniel Jose
Daniel Jose

概述

在前两篇文章中,我们介绍了如何配置 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

附加的文件 |
Anexo.zip (560.03 KB)
最近评论 | 前往讨论 (1)
William da Paz
William da Paz | 27 4月 2025 在 03:07
干得漂亮,老兄!

有没有办法将“交易记录”和/或“持仓与交易”中的数据导入Excel,甚至Python?

感谢您的关注!
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
金融时间序列中的保形预测探索 金融时间序列中的保形预测探索
本文将介绍保形预测(conformal predictions)及其实现库MAPIE。这是一种较新的机器学习方法,重点不在于发现数据规律,而在于为现有模型提供风险管理与不确定性量化能力。保形预测本身并非用于挖掘数据中的规律,而仅用于评估现有模型对特定样本预测的置信度,并筛选出可靠的预测结果。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格走势角度分析:用于预测金融市场的混合模型 价格走势角度分析:用于预测金融市场的混合模型
什么是金融市场角度分析?如何利用价格变动角度和机器学习实现准确率达 67% 的精准预测?如何将回归和分类模型与角度特征相结合,并获得一个可运行的算法?这与江恩理论有什么关系?为什么价格走势角度是机器学习的良好指标?