English Русский Español Deutsch 日本語 Português
preview
通过套接字将 MetaTrader 的 Tick 信息从 MQL5 服务传输到 Python 应用程序

通过套接字将 MetaTrader 的 Tick 信息从 MQL5 服务传输到 Python 应用程序

MetaTrader 5交易系统 |
28 5
Ramesh Maharjan
Ramesh Maharjan

引言

有时候,并非所有东西都能用 MQL5 语言编程实现。即使有可能在 MQL5 中转换现有的高级库,那也将是一项耗时的任务。更好的选择是集成或利用现有库来完成任务。例如,Python 中有许多机器学习库。仅仅为了完成交易中的机器学习任务,而在 MQL5 中创建完全相同的副本,对我们来说并不是最佳选择。最好导出该 Python 机器学习库所需的数据,在 Python 环境中进行必要的处理,然后将结果导入回 MQL5 程序。本文介绍的是如何将此类数据从 MetaTrader 终端传输到 Python 环境中。

有一个名为 MetaTrader 5 的 Python 包,可用于访问 MetaTrader 的图表数据,例如 K 线信息、tick 信息、用户信息、交易信息等。但此软件包仅适用于 Windows 系统,这意味着如果您想使用该软件包,则需要拥有 Windows 操作系统。在本文中,我将向您展示如何使用套接字将 MetaTrader 中的 tick 数据传递给 Python 程序。

1983 年,随着 4.2BSD Unix 操作系统中伯克利套接字的引入,机器间的通信变得简单且普及。更高级的应用层库和协议都依赖于传输层的套接字协议,本文将使用这些协议。

出于演示目的,我将仅通过套接字端口将 tick 数据(包括 tick 数据的买价、卖价和时间)传输到 Python 应用程序中供其使用。我们可以利用本文介绍的方法导出各种图表数据,例如 K 线信息、用户信息、交易信息等。我们不仅可以使用服务,还可以使用脚本、指标或 EA 来完成这项任务。


程序流程

本文重点介绍如何利用 MetaTrader 5 的服务程序将买卖报价和时间等 tick 信息发送到 Python 服务器,而 Python 服务器则会将这些信息广播给所有已连接到服务器的客户端套接字。下图可以更清楚地说明这一点。

套接字数据流

如图所示,MetaTrader 服务程序已连接到在端口 9070 上监听的 Python 服务器。MetaTrader 5 终端中打开的所有图表 tick 数据,都将发送至端口 9070 的 Python 服务器。然后,Python 服务器分析从 MetaTrader 5 接收到的数据,对数据进行必要的分析,并将 tick 信息分发或更准确地说是广播给已连接的客户端。客户端随后可以使用接收到的数据执行必要任务或应用算法以获得所需结果,这些结果可以传输回 MetaTrader 服务程序进行进一步处理。


为什么选择服务和 Python?

选择 MetaTrader 5 服务发送 tick 信息并没有什么特别的原因。脚本、指标和 EA 也可以用来完成这类任务,我将在下一篇文章中介绍这些内容。我只是想说明,可以使用套接字编程,通过 MetaTrader 服务将 tick 数据和其他图表相关信息传输到其他应用程序。除了 Python 服务器和客户端之外,我们还可以使用其他平台和框架来开发服务器和客户端。Python 对我来说很方便。


服务程序

我们都知道,MetaTrader 5 中的服务只有一个函数,即 OnStart,所有操作都必须在该函数内完成。服务不与任何窗口绑定,并且像 _Symbol、_Period、_Point 等内置函数在服务中无法像在脚本、指标和 EA 中那样访问。运行和管理服务只能通过导航窗口完成。服务无法拖拽到打开的图表窗口中运行。你可以在下面的演示部分看到如何运行服务,我在那里附了一个 GIF 文件。

服务程序首先定义套接字、服务器和端口变量。

int socket; string server = "localhost"; int port = 9070;

SocketInit() 函数使用 SocketCreate() 方法创建一个套接字句柄。将套接字句柄以及服务器地址和端口传递给 SocketConnect() 函数以连接到服务器,如果连接成功,则返回 true。

void SocketInit() {    socket=SocketCreate();    bool connect = SocketConnect(socket, server, port, 1000);    if(connect) {       Print("socket is connected", " ", server, " port ", port);    } }

当我们在 MetaTrader 终端的导航窗口中添加并运行服务程序时,会调用 OnStart 函数,其中我们调用 SocketInit() 函数。

void OnStart() {    SocketInit();

然后定义了三个变量,第一个变量用于存储打开图表的 tick 信息,第二个变量用于存储图表 ID,第三个变量用于存储要发送到服务器的 tick 信息。

MqlTick latestTick; long next; string payload;

由于我们需要不断地向服务器发送 tick 信息,定义了一个条件为 true 的 while 循环,然后检查服务器是否已连接。如果套接字连接不成功,则 SocketIsConnected(socket handle) 将返回 false,服务将停止。 

while(true) {
   if(!SocketIsConnected(socket)){
      Print("socket is not initialized yet so stopping the service");
      break;
   }

next 和 payload 变量分别用第一个图表 ID 和空字符串初始化。

next = ChartFirst();
payload = "";

接下来,我们将使用另一个 while 循环遍历所有图表窗口,然后获取图表交易品种。

while (next != -1) {
   string chartSymbol = ChartSymbol(next);

提取图表的最新 tick 信息,例如买价、卖价和时间。 

SymbolInfoTick(chartSymbol, latestTick);
double bid = latestTick.bid;
double ask = latestTick.ask;
string tickTime = TimeToString(latestTick.time, TIME_SECONDS);

使用交易品种、时间、买价和卖价生成要发送到服务器的要发送的数据。我尝试为有效负载创建一个 JSON 字符串值,以便可以使用 Python 服务器中的 JSON 库将 JSON 字符串解码为 JSON 对象以提取数据。您可以使用任何您觉得方便的格式。

bool stringAdded = StringAdd(payload, StringFormat("{\"pair\": \"%s\", \"time\": \"%s\", \"bid\": %f, \"ask\": %f}", chartSymbol, tickTime, bid, ask));

如果打开了多个图表窗口,则使用 #@# 作为分隔符将下一个图表窗口的 tick 信息附加到图表窗口。

next = ChartNext(next);
if (next != -1 && stringAdded) {
   stringAdded = StringAdd(payload, "#@#");
}

当所有打开的图表窗口都扫描了 tick 数据并填充了要发送的数据后,即可准备发送到服务器。SocketSend 方法将数据发送到已定义套接字句柄的套接字端口。请确保长度字段中的值正确,否则数据中会包含额外的字符,这将给服务器端解析数据带来额外的负担。

uchar data[];
int len = StringToCharArray(payload, data);
SocketSend(socket, data, len-1);

然后循环会从第一个已打开的图表窗口重新开始,只要与服务器的连接保持打开状态,就会持续执行。


Python 服务器程序

如程序流程所述,该服务器应监听 MetaTrader 终端在 9070 端口发送的 tick 信息,同时在 9071 端口监听客户端连接,以便向客户端发送接收到的 tick 信息。因此,两个套接字连接绑定到这些端口,并持续监听这些端口。

host = '127.0.0.1'
MT5_RECIEVING_PORT = 9070
CLIENT_SENDING_PORT = 9071
mt5Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mt5Socket.bind((host, MT5_RECIEVING_PORT))
mt5Socket.listen()
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientSocket.bind((host, CLIENT_SENDING_PORT))
clientSocket.listen()

我已将服务器编程为只允许一个 MetaTrader 5 终端实例连接并进行通信。这样做是为了避免多个 MetaTrader 5 连接可能出现的管理不善和混乱情况。另一方面,客户端将收到 tick 数据以进行进一步处理,不同的客户端可以使用相同的数据应用不同的算法来达到预期的结果。因此,需要对客户端连接进行管理。

以下字典变量用于存储实时连接的客户端,以便在服务器端管理已连接的客户端。

connectedClients = {}

MetaTrader 和客户流作为单独的线程启动,以便它们可以彼此独立运行。

if __name__ == "__main__":

   thread = threading.Thread(target=acceptFromMt5)
   thread.start()
   thread = threading.Thread(target=acceptFromClients)
   thread.start()

MetaTrader 5 线程会持续等待,直到 MetaTrader 服务程序连接到它。连接被接受后,将调用 processMt5Data 函数,该函数接收、处理数据并将其发送到相应的客户端。我稍后会详细讨论这个问题。

如果接受来自 MetaTrader 服务程序的连接时出现错误,则会等待下一个连接。

def acceptFromMt5():
   try:
       print("Server is listening for mt5")
       mt5Client, address = mt5Socket.accept()
       print(f"mt5 service is connected at {str(address)}")
       processMt5Data(mt5Client)

   except Exception as ex:
       print(f"error in accepting mt5 client {ex}")
       acceptFromMt5()

在讨论 tick 信息分析部分之前,了解客户端的连接和管理方式至关重要。与 MetaTrader 5 服务一样,服务器会等待新的客户端连接。客户需要发送其身份信息,以便服务器对其进行管理。标识是图表窗口中的交易品种对。该标识用作字典对象的键,并将客户端添加到该字典中。使用 while 循环是为了让多个客户端可以连接。

def acceptFromClients():
   try:
       while True:

           print("Server is listening for clients")
           client, address = clientSocket.accept()
           pair = client.recv(7).decode("ascii")
           print(f"{pair} client service is connected at {str(address)}")
           clients = connectedClients[pair] if connectedClients and pair in connectedClients.keys() else []
           clients.append(client)
           connectedClients[pair] = clients

   except Exception as ex:
       print(f"error in accepting other clients {ex}")
       acceptFromClients()

现在我们已经了解了如何添加新连接的客户端,就很容易理解如何将接收到的 tick 数据广播给这些客户端。将交易数据转换为 JSON 对象,并从中提取交易品种。根据客户端在连接时发送给服务器的识别密钥,将 tick 信息广播给客户端。 

Tick 数据使用与 MetaTrader 5 服务程序中相同的分隔符进行分割。如果图表的多笔 tick 数据一次性发送,则需要进行额外的拆分。第二次拆分是因为如果 MetaTrader 服务程序一次性向服务器发送多笔 tick 信息,Python JSON 解析库将无法解析 tick 信息。

def processMt5Data(mt5Client):
   data = "mt5 client connected"
   repeatativeEmpty = 0
   while(len(data) > 0):

       try:
          data = mt5Client.recv(1024000).decode("ascii")

           if len(data) > 0:
               for jsn in data.split("#@#"):

                   if "}{" in jsn:
                       splittedTickData = jsn.split("}{")
                       jsn = splittedTickData[0] + "}"

                   jsonTickData = json.loads(jsn)
                   pair = jsonTickData["pair"]

                   if pair in connectedClients.keys():
                       broadcastToClients(pair, jsonTickData)
           repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0

           if repeatativeEmpty > 10:
               print(f"data is not recieved for 10 times in a row {data}")
               break

       except Exception as ex:
           print(f"error in processing mt5 data {ex}")
           break

       time.sleep(0.1)
   acceptFromMt5()

向客户端发送数据很简单。如果在发送数据时发生错误,则假定该客户端未连接,将其从已连接客户端列表中移除,并更新字典。

def broadcastToClients(pair, message):
   for client in connectedClients[pair]:

       try:
           client.send(str(message).encode("ascii"))

       except Exception as ex:
           print(f"error while sending {message} to {client} for {pair}")
           client.close()
           clients = connectedClients[pair]
           clients.remove(client)
           connectedClients[pair] = clients
服务器程序至此编写完成。


Python 客户端程序

如程序流程中所述,客户端程序通过端口 9071 与服务器建立连接。如上文所述,为了在服务器上正确管理客户端,服务器中需要一个代表交易品种/货币对的密钥。因此,我们需要选项来显示可用的密钥列表,以便从列表中选择要发送到服务器的标识 ID。

CURRENCY_PAIRS = [

   "AUDUSD",
   "AUDJPY",
   "AUDCAD",
   "AUDNZD",
   "AUDCHF",
   "CADJPY",
   "CADCHF",
   "CHFJPY",
   "EURUSD",
   "EURJPY",
   "EURGBP",
   "EURCAD",
   "EURAUD",
   "EURNZD",
   "EURCHF",
   "GBPUSD",
   "GBPJPY",
   "GBPCAD",
   "GBPAUD",
   "GBPNZD",
   "GBPCHF",
   "NZDUSD",
   "NZDJPY",
   "NZDCAD",
   "USDCHF",
   "USDJPY",
   "USDCAD",
   "NZDCHF"
]
server = "127.0.0.1"
port = 9071

如您所见,连接成功后,会从上面的列表中选择一个选项并将其发送到服务器,然后等待服务器返回数据。 

if __name__ == "__main__":
   print(f"please choose from the currency pairs given below as name \n {', '.join(CURRENCY_PAIRS)}")
   name = input("enter the name for the client : ")

   if name in CURRENCY_PAIRS:
       client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

       if client.connect_ex((server, port)) == 0:
           client.send(name.encode("ascii"))
           receiveData(client)

       else:
           print("server could not be connected.")

   else:
       print("you didn't choose from the above list")

我编写的客户端程序只是打印 tick 数据,但我们可以根据我们的需求对它进行任何操作,例如使用可用的框架和库进一步处理数据、应用机器学习库等等。

def receiveData(client):
   repeatativeEmpty = 0

   while True:
       data = client.recv(1024).decode("ascii")
       print("data received ", data)
       repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0

       if repeatativeEmpty > 10:
           print(f"data is not recieved for 10 times in a row {data}")

           break

至此,客户端程序就完成了。


数据流演示

为了实现数据转换的顺利进行,需要先启动服务器,否则任何服务都无法运行。出于测试目的,Python 服务器在虚拟环境中运行,如下图所示。

服务器已启动

我们需要服务器接收 tick 数据,然后将其广播给客户端,因此第二个任务是启动 MetaTrader 服务程序,如下图所示。

MetaTrader 服务程序已启动

如果 MetaTrader 服务程序未运行,则已连接的客户端将等待接收数据。一旦客户端向服务器发送识别密钥,并且 MetaTrader 服务程序发送了 tick 数据,则该数据目前会打印在客户端终端上。这是针对 EURUSD 和 GBPUSD 进行的,因为此时 MetaTrader 5 终端中这两个图表窗口都已打开。这一点可以从下面的截图中看出来。

EURUSD 客户端已启动

GBPUSD 客户端已启动

我尝试将上述所有截图过程制作成 GIF 动图,以便整个过程更加一目了然。

从 MetaTrader 到 Python 的 tick 数据传输完整流程


结论

本文讨论了如何使用套接字编程,通过 MetaTrader 服务程序和 Python 服务器及客户端,将 MetaTrader 5 中的 tick 数据发送到 Python 应用程序。既然我们可以像本文中那样传输 tick 数据,我们也可以传输有关图表的其他信息。我使用的是服务,但我们也可以使用脚本、指标或 EA。就像 Python 一样,我们也可以使用其他编程语言。

此外,本文还试图消除对 Windows 操作系统的依赖。MetaTrader 和 MetaTrader 5 等 Python 库都依赖于 Windows 操作系统。我们可以使用套接字协议将数据从 MetaTrader 传输到更适合此类处理的库,而不是使用 MetaTrader 5 Python 库,以便更轻松、更高级地处理 MetaTrader 数据。

文件名称描述
TickSocketService.mq5
MQL 文件包含连接到 9070 端口套接字服务器并发送 tick 数据的代码。
tick_server.pyPython 服务器套接字为 MetaTrader 开放端口 9070,为其他客户端开放端口 9071
tick_client.pyPython客户端通过套接字连接到 9071 端口,并接收服务器发送的数据。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18680

附加的文件 |
tick_server.py (2.86 KB)
tick_client.py (1.32 KB)
最近评论 | 前往讨论 (5)
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 5 8月 2025 在 16:51
一篇很有意思的文章。是否可以通过这种方式为监听该服务器的终端网络构建一个风险管理服务器?
Alain Verleyen
Alain Verleyen | 5 8月 2025 在 23:02
这是一篇很好且有用的文章,祝贺你。
Ramesh Maharjan
Ramesh Maharjan | 8 8月 2025 在 17:23
Alain Verleyen #:
这是一篇写得很好且很有用的文章,祝贺你。

谢谢

Ramesh Maharjan
Ramesh Maharjan | 8 8月 2025 在 17:26
Yevgeniy Koshtenko #:
这工作挺有意思的。是否可以以此方式为监听该服务器的终端网络构建一个风险管理服务器?

是的,这是可行的

Delane Tendai Nyaruni
Delane Tendai Nyaruni | 8 8月 2025 在 21:32
Yevgeniy Koshtenko #:
很有意思的工作。是否可以以此方式为一个监听该服务器的终端网络构建一个风险管理服务器?
是的,这是可行的,具体取决于你能容忍的延迟程度。
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
交易中的神经网络:摆脱特定数据依赖的时间序列泛化(结论) 交易中的神经网络:摆脱特定数据依赖的时间序列泛化(结论)
本文将向您展示Mamba4Cast如何把理论转化为可运行的交易算法,并为您开展自主实验奠定基础。不要错过这次机会,获取用于自研策略的全面的知识与策略开发灵感。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
在 MQL5 中实现来自其他语言的实用模块(第 03 部分):移植 Python 的 Schedule 模块,打造增强版 OnTimer 在 MQL5 中实现来自其他语言的实用模块(第 03 部分):移植 Python 的 Schedule 模块,打造增强版 OnTimer
Python 中的 schedule 模块提供了一种简单的方法来安排重复性任务。虽然 MQL5 缺乏内置的等效功能,但在本文中,我们将实现一个类似的库,以便在 MetaTrader 5 中更轻松地设置定时事件。