市场模拟(第七部分):套接字(一)
概述
在上一篇文章,市场模拟(第六部分):将信息从 MetaTrader 5 传输到 Excel 中,我解释了如何使用简单的方法和纯 MQL5 将 MetaTrader 5 中的报价数据传输到 Excel 中。当然,这只是一个非常简单的例子,其目的是为了尽可能地起到教学作用。但是,您可能已经注意到,报价更新并非是实时进行的。尽管如此,上一篇文章中介绍的知识旨在展示我们实际需要做的其他事情。
但是,由于有几种方法可以实现同样的目标,对我们来说真正重要的总是结果,我想证明,是的,有一种简单的方法可以将数据从 MetaTrader 5 传输到其他程序,如 Excel。然而,主要目的不是将数据从 MetaTrader 5 传输到 Excel,而是相反,即将数据从 Excel 或任何其他程序传输到 MetaTrader 5。
有几种方法可以实现这一点。有些更简单,有些更精致。有些可以实时完成,而另一些则会引入轻微的延迟。但重要的是:我们可以使用外部数据控制 MetaTrader 5 或其中运行的某种类型的应用程序吗?这类问题可能看起来微不足道,但它开辟了重要的可能性。例如,您可以使用专门为此类任务设计的外部程序进行某种研究或分析。您可以根据价格或报价定义感兴趣的点,并将此类数据放置在应用程序(EA 交易系统、服务、脚本,甚至是指标)可以使用它的地方。这样一来,它可以直接在图表上为我们提供信号,或者,如果是 EA 交易系统,则可以帮助我们做出决策。
在这一点上,只有你的想象力和兴趣水平会限制你能做什么以及你能走多远。但在我们着手开发回放/模拟器真正需要的功能之前,我想先展示一些其他内容。为此,需要介绍这些概念、想法和可能性,以便您能够真正理解稍后将开发和实施的内容,从而尽可能地利用所有这些知识。
本文将展示在之前讨论过的同一主题上取得的进展。因为,正如我所说的,前面演示的方法不允许我们实时发送数据。虽然这对于回放/模拟器来说已经足够了,但如果我们想要实时信息,它就无法满足我们的需求。
理解概念
正如我在上一篇文章中提到的,使用 RTD 或 DDE 并不能保证 MetaTrader 5 与任何外部程序之间实现双向通信。但是 RTD 或 DDE 中使用的相同概念可以以一种方式应用,为我们提供我们想要的东西:双向通信。为了实现这一目标,我们需要做一些与我们迄今为止所做的略有不同的事情。在整个开发阶段,我始终坚持使用纯 MQL5。然而,有些情况下,纯 MQL5 并不真正足够 —— 不是因为它能做什么,而是因为外部程序在没有帮助的情况下无法做什么。问题归根结底只有一个:套接字。
套接字技术深深嵌入在计算机领域。如果您不知道套接字是什么或它的用途,我建议您查阅一下相关资料,特别是如果您打算使用某些类型的解决方案。使用套接字给了我们极大的自由度。我不会在这里深入探讨这个话题,因为它内容广泛,充满各种可能性,如果你真的想使用套接字,就需要仔细研究。但我会提供一个简短的解释,这样亲爱的读者就不会感到完全迷失,这样你就能理解我们将要做什么,正在发生什么。
TCP 协议
首先你需要明白的是,套接字的种类数不胜数。它们各自有不同的用途。然而,最常见的是流套接字和数据报套接字。流类型,也称为流套接字,通常用于可靠的双向通信。“可靠” 一词指的是它总是经过测试以确保接收到信息。流套接字使用传输控制协议,通常称为 TCP。
这种类型的套接字还有另一个特点:它的设计使数据包总是以特定的顺序到达。换句话说,您可以将信息传输为展开的列表,因为数据包是按顺序到达的。这清楚地表明了它最常用的地方:例如,在远程应用程序控制中,在传输过程中丢失数据是不可接受的,我们需要保证信息会被接收。然而,这使得该协议在实际应用中速度稍慢一些。通常情况下,其结构如下:

但就编程而言,重要的却是另一方面。这就是关于如何在我们将创建的代码中选择和实现此协议。根据 Windows 和 Linux 系统中是否使用 TCP,代码在结构上存在一些细微差别。虽然存在这些差异,但它们并不影响通信本身。也就是说,Windows 应用程序可以通过 TCP 与 Linux 应用程序通信,并且两者可以完美地相互理解。前提是遵守通信规则。同样的道理也适用于下一个协议,我们将在下一节中讨论该协议。
UD P协议
了解了流类型之后,现在让我们来了解数据报类型。数据报套接字与流/TCP 套接字完全不同。这是因为数据报套接字是为单向、不可靠的通信而设计的。但这意味着什么?通信怎么可能是单向的且不可靠的呢?让我们来详细分析一下。
首先,该套接字使用称为 UDP 的协议。该协议不是真正的连接,而仅仅是一种发送数据的方法。为了帮助你理解,想象一下写一封信并寄出去。一旦你发送了它,你就不知道它是否或何时会真正到达目的地。
但事情变得更加复杂。寄出这封信后,你意识到你忘了一些细节。所以你再写一封信并寄出去。这种情况可能会多次发生 —— 每封信都是单独寄出的。
关键细节如下:如果邮政服务正常工作,没有丢失任何信件,收件人可能会一次收到所有信件,或者按照您发送的顺序,或者完全随机的顺序。
想想作为收件人,你需要做些什么来理解信息。如果你是期待回复的发件人,你无法保证收件人确实收到或理解了这些信件。因此,UDP 协议被认为是单向的、不可靠的。尽管听起来不太好,但它仍然非常有用。
在某些情况下,例如数据探测,我们不需要对数据进行排序,也不需要所有数据。我们可以失去一些,但仍然有足够的信息来理解。
一个典型的例子是视频或语音通话,其中速度是优先事项。另一个应用场景是远程探测器或传感器,其目的只是为了确认某种特定情况。在这种情况下,只要有足够的数据到达进行分析,就不需要确认收到。该协议通常遵循以下结构:

希望这已经激发了你的好奇心,促使你更深入地探索这个主题。事实上,它内容极其丰富且引人入胜。在 MQL5 社区中,您可以在文章和论坛讨论中找到许多对该主题的引用。无论如何,这个话题非常广泛。这个简短的介绍只是为了帮助您了解我们接下来要做什么。
编写一个简易系统的程序
这里我们需要加一个括号。为了演示将要做什么,我们必须使用非常简单的代码。由于 MQL5 不允许我们直接在语言中创建服务器,我们将使用 MQL5 创建客户端。是的,我们将构建一个客户端-服务器模型。服务器端将使用外部编程语言编写。在这种情况下,您可以使用支持网络服务器的任何语言或平台创建它。但是,您必须注意一些重要的细节,例如端口和协议类型。为了尽可能简化事情,我将使用您可以直接从源代码学习的代码。换句话说,我们将使用网上公开的代码。服务器代码可以在微软网站上找到,可通过以下链接访问:服务器 WinSock.
在本文的最后,我将再次留下此参考资料,以便您可以逐步学习代码,以及与套接字相关的许多其他细节。这不仅适用于服务器,也适用于客户端,这在同一页上也有解释。
重要提示:我所引用的服务器模型非常简单。它接收一个连接,捕获发送给它的内容,并立即关闭连接,无法接收另一个连接。它返回客户端发送的同样数据。换句话说,它是一个仅用于演示的回声服务器。
但是,我们会为客户端做一些略有不同的处理。我们将直接使用 MQL5 创建客户端,跳过网站上提供的 C/C++ 客户端代码。这将帮助你了解通信的实际运作方式。请记住,您甚至可以将 MetaTrader 5 变成服务器,但这需要使用 DLL。我目前不想介绍这类资源,至少现在不想。
那么,让我们开始吧。你可以使用我提供的代码,也可以用任何语言或平台创建自己的代码。这里的想法很简单,就是构建一个简易的系统,一个尽可能简单的系统,但可以作为你理解事物运作方式的触发器。服务器编译并运行后,我们就可以开始进行 MQL5 部分了。为此,请打开 MetaEditor 并用纯 MQL5 创建客户端。
运行 MetaTrader 端
在与本地服务器建立连接之前,我们需要了解如何实际建立连接。默认情况下,MetaTrader 5 会阻止您连接到交易服务器以外的任何服务器。这是您必须接受和理解的安全措施。MetaTrader 这样的平台不应该随意连接到任意地址。因此,在 MetaTrader 允许这种连接之前,必须迈出一小步。
为了以一种非常简单的方式解释这一点,我们将使用直接在 MQL5 文档中找到的内容。因此,下面的代码不是我的,而是来自官方文档,可以在 SocketCreate 下找到。源代码如下所示,与文档中显示的完全一致:
001. //+------------------------------------------------------------------+ 002. //| SocketExample.mq5 | 003. //| Copyright 2018, MetaQuotes Software Corp. | 004. //| https://www.mql5.com | 005. //+------------------------------------------------------------------+ 006. #property copyright "Copyright 2018, MetaQuotes Software Corp." 007. #property link "https://www.mql5.com" 008. #property version "1.00" 009. #property description "Add Address to the list of allowed ones in the terminal settings to let the example work" 010. #property script_show_inputs 011. 012. input string Address="www.mql5.com"; 013. input int Port =80; 014. bool ExtTLS =false; 015. //+------------------------------------------------------------------+ 016. //| Send command to the server | 017. //+------------------------------------------------------------------+ 018. bool HTTPSend(int socket,string request) 019. { 020. char req[]; 021. int len=StringToCharArray(request,req)-1; 022. if(len<0) 023. return(false); 024. //--- if secure TLS connection is used via the port 443 025. if(ExtTLS) 026. return(SocketTlsSend(socket,req,len)==len); 027. //--- if standard TCP connection is used 028. return(SocketSend(socket,req,len)==len); 029. } 030. //+------------------------------------------------------------------+ 031. //| Read server response | 032. //+------------------------------------------------------------------+ 033. bool HTTPRecv(int socket,uint timeout) 034. { 035. char rsp[]; 036. string result; 037. uint timeout_check=GetTickCount()+timeout; 038. //--- read data from sockets till they are still present but not longer than timeout 039. do 040. { 041. uint len=SocketIsReadable(socket); 042. if(len) 043. { 044. int rsp_len; 045. //--- various reading commands depending on whether the connection is secure or not 046. if(ExtTLS) 047. rsp_len=SocketTlsRead(socket,rsp,len); 048. else 049. rsp_len=SocketRead(socket,rsp,len,timeout); 050. //--- analyze the response 051. if(rsp_len>0) 052. { 053. result+=CharArrayToString(rsp,0,rsp_len); 054. //--- print only the response header 055. int header_end=StringFind(result,"\r\n\r\n"); 056. if(header_end>0) 057. { 058. Print("HTTP answer header received:"); 059. Print(StringSubstr(result,0,header_end)); 060. return(true); 061. } 062. } 063. } 064. } 065. while(GetTickCount()<timeout_check && !IsStopped()); 066. return(false); 067. } 068. //+------------------------------------------------------------------+ 069. //| Script program start function | 070. //+------------------------------------------------------------------+ 071. void OnStart() 072. { 073. int socket=SocketCreate(); 074. //--- check the handle 075. if(socket!=INVALID_HANDLE) 076. { 077. //--- connect if all is well 078. if(SocketConnect(socket,Address,Port,1000)) 079. { 080. Print("Established connection to ",Address,":",Port); 081. 082. string subject,issuer,serial,thumbprint; 083. datetime expiration; 084. //--- if connection is secured by the certificate, display its data 085. if(SocketTlsCertificate(socket,subject,issuer,serial,thumbprint,expiration)) 086. { 087. Print("TLS certificate:"); 088. Print(" Owner: ",subject); 089. Print(" Issuer: ",issuer); 090. Print(" Number: ",serial); 091. Print(" Print: ",thumbprint); 092. Print(" Expiration: ",expiration); 093. ExtTLS=true; 094. } 095. //--- send GET request to the server 096. if(HTTPSend(socket,"GET / HTTP/1.1\r\nHost: www.mql5.com\r\nUser-Agent: MT5\r\n\r\n")) 097. { 098. Print("GET request sent"); 099. //--- read the response 100. if(!HTTPRecv(socket,1000)) 101. Print("Failed to get a response, error ",GetLastError()); 102. } 103. else 104. Print("Failed to send GET request, error ",GetLastError()); 105. } 106. else 107. { 108. Print("Connection to ",Address,":",Port," failed, error ",GetLastError()); 109. } 110. //--- close a socket after using 111. SocketClose(socket); 112. } 113. else 114. Print("Failed to create a socket, error ",GetLastError()); 115. } 116. //+------------------------------------------------------------------+
MQL5 文档中的源代码
此代码非常适合解释如何在 MetaTrader 5 中使用和调整连接。因为除了非常简单之外,它还为我们提供了远程访问的可能性。换句话说,我们实际上会访问一个网页。我相信许多有网络编程经验的读者会毫不费力地理解这段代码。但对于那些对这个主题一无所知的人,我会快速解释一下,因为我们稍后需要理解这里显示的一些概念。
这段代码是脚本,必须按脚本方式编译。指标内部不能使用套接字。请阅读文档了解更多详情。现在让我们看看它是如何工作的。在第 12 行,我们指定要访问的网站名称。在第 13 行,我们指定要使用的端口。具体来说,是指响应 HTTP 请求的端口。换句话说,就是一个 web 服务器。请参阅文档,了解每种端口类型的功能。
嗯,当代码开始时(第 71 行),我们做的第一件事是创建一个套接字(第 73 行)。到目前为止,一切都很简单。但是,我们必须验证创建的套接字是否有效,这是在第 75 行完成的。在第 78 行,我们尝试连接到第 12 行和第 13 行定义的服务器和端口。如果此操作失败,您将看到类似下图的内容:

这可能是由于 MetaTrader 5 阻止了连接。要启用连接,请按 CTRL + O 并向规则添加例外,如下所示:

一旦您进行了上面显示的更改,您将能够再次运行脚本,结果将与下图相似:

但这是怎样发生的呢?我们是如何从 web 服务器获取信息的?这样做难道不需要调用 WebRequest 吗?事实上,这不是必需的。WebRequest 调用几乎与我们刚才所做的完全相同,只是级别略高一些。因为 WebRequest 在底层套接字周围创建了一个抽象层。因此,两种方法的结果是相同的。我们在之前的文章中探讨过 WebRequest 函数。如需了解更多详情,请参阅:
- 从零开始开发 EA 交易(第 15 部分):访问 web 数据(一)
- 从零开始开发 EA 交易(第 16 部分):访问 web 数据(二)
- 从零开始开发 EA 交易(第 17 部分):访问 web 数据(三)
这三篇文章中所做的事情也可以直接使用套接字来完成。但是,您需要编写更多的代码来实现相同的结果。但让我们在本文中回到我们的系统。编译完上一节中引用的服务器代码后,在执行我稍后将展示的脚本之前,您必须让它运行。请记住,您可以用任何语言创建自己的服务器。现在唯一的要求是将服务器接收到的数据发送回客户端,以便我们得到我将解释的行为。
那么让我们来看一下客户端的 MQL5 代码。下面是完整代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property script_show_inputs 05. //+------------------------------------------------------------------+ 06. input string user00 = "127.0.0.1"; //Address 07. input int user01 = 27015; //Port 08. //+------------------------------------------------------------------+ 09. bool ConnectionWrite(int socket, string szMsg, bool TLS = false) 10. { 11. char buff[]; 12. int len = StringToCharArray(szMsg, buff) - 1; 13. 14. if (len < 0) return false; 15. Print("To Server:", szMsg); 16. return (TLS ? SocketTlsSend(socket, buff, len) : SocketSend(socket, buff, len)) == len; 17. } 18. //+------------------------------------------------------------------+ 19. string ConnectionRead(int socket, uint timeout) 20. { 21. uint len; 22. int ret; 23. char resp[]; 24. string info; 25. uint timeout_check = GetTickCount() + timeout; 26. 27. info = ""; 28. do 29. { 30. len = SocketIsReadable(socket); 31. ret = SocketRead(socket, resp, len, timeout); 32. if (ret > 0) 33. info += CharArrayToString(resp, 0, ret); 34. }while ((GetTickCount() < timeout_check) && (!_StopFlag)); 35. 36. return info; 37. } 38. //+------------------------------------------------------------------+ 39. void OnStart() 40. { 41. int socket = SocketCreate(); 42. 43. if (socket == INVALID_HANDLE) 44. { 45. Print("Unable to create socket. Error: ", GetLastError()); 46. return; 47. } 48. if (!SocketConnect(socket, user00, user01, 1000)) 49. { 50. Print("Connection with the address [", user00,"] in port: ",user01, " failed. Error code: ", GetLastError()); 51. SocketClose(socket); 52. return; 53. } 54. 55. Print("Trying to send ", ConnectionWrite(socket, "Testing Server Echo...") ? "Ok..." : "Failed..."); 56. 57. Print("From Server:", ConnectionRead(socket, 1000)); 58. 59. SocketClose(socket); 60. } 61. //+------------------------------------------------------------------+
演示代码
在了解这段代码在 MetaTrader 5 中的运行情况之前,让我们先来了解一下其中的一些细节。第一个重点是第 6 行。这一行告诉我们服务器位于哪里。在这个例子中,我们指定它与 MetaTrader 5 在同一台机器上。第 7 行指定服务器端口,这一点非常重要:此端口必须与服务器代码中定义的端口相同。这就是为什么我引用了网站上明确解释的公开代码。如有疑问,请研究该网站的文档,我会在本文末尾再次将其作为参考文献列出。
基本上,此 MQL5 脚本中的所有函数都是不言自明的。如果您有任何疑问,只需阅读文档即可了解正在发生的事情的细节。然而,有一部分事情并不那么清楚,值得一些解释。
注意,我们在第 28 行进入了一个循环。它的目的是读取服务器发布到套接字的内容。但为什么要使用循环呢?仅仅读取一次套接字不就足够了吗?这可能看起来很奇怪,但这是有原因的。当数据发送到套接字时,我们并不知道它将如何发送。我们只知道它会被发送。即使消息相对较短,信息也可能以单个数据包或几个小数据包的形式到达。
这就是为什么许多套接字编程新手遇到困难的原因。他们假设信息将在单个块中到达,但它实际上可能被划分为许多块或数据包。我们能提前知道吗?事实上,没有简单的方法可以知道。我们所能预期的是,使用 TCP 时,发送的数据将会被接收,正如前面解释的那样。但这些信息通常会以数据包的形式发布和接收。
为了更好地理解,请看第 55 行。这里我们调用第 9 行,它返回要放入套接字的信息。在第 15 行,我们定义消息。在第 16 行,我们发送它。服务器如何接收此消息,以及它将被分成多少个数据包,已不再由我们控制 —— 系统本身会处理这些。
然后,在第 57 行,我们读取服务器发布的内容。请注意:我们读取的不是我们发送的内容,而是服务器发布的内容。因为我们使用的服务器是回声服务器,所以我们发送的消息将返回给我们。这本质上就相当于对服务器执行 PING 操作。
但是,正如我们无法控制消息的传输方式一样,我们也不知道服务器将如何根据数据包计数返回它。因此,如果您删除第 28 行的循环,仅执行第 31 行的调用,然后尝试使用第 33 行将服务器响应转换为字符串,您可能会注意到消息可能显得支离破碎,好像缺少了某些部分。然而,在其他时候,它会完整到达,因为消息很短。而正是这种行为,让很多人无法理解。
我知道这可能看起来很混乱,甚至效率低下。这似乎没有任何意义。但我想要强调一点:如果你了解这两个代码的工作原理 —— 服务器端代码和在 MetaTrader 5 中执行的代码 —— 你就会意识到我们可以取得很大的成就。
但在运行此脚本之前,您必须通知 MetaTrader 5 应允许使用该地址。因此,第 6 行中指示的相同值必须出现在 MetaTrader 5 的允许地址列表中。您应该看到类似下图的内容:

有了这个修改,我们就可以运行这两个程序了。为了演示如何做到这一点,特别是如果你缺乏运行多个进程的经验,我在下面的视频中演示了如何在本地测试这两个程序。我想强调的是,视频的目的不是推广任何特定的工具,而只是向那些不熟悉该过程的人展示如何在多个工作线程中运行程序,从而使开发没有重大困难。
最后的探讨
在本文中,我简要介绍了 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 | 实现图形控件和用户之间的交互(操作回放模拟器和实时市场交易所需) |
| Servicios\Market Replay.mq5 | 创建并维护市场回放和模拟服务(整个系统的主文件) |
| VS 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/12621
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第二十部分):外部资金流(4)——相关性路径探索器
风险管理(第二部分):在图形界面中实现手数计算
将人工智能(AI)模型集成到已有的MQL5交易策略中
交易中的神经网络:具有层化记忆的智代(终篇)