English Русский Español Português
preview
市场模拟(第 13 部分):套接字(七)

市场模拟(第 13 部分):套接字(七)

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

概述

在上一篇文章“市场模拟(第 12 部分):套接字(四)”中,我们创建了一个能够响应多个客户端的 Python 服务器。它是通过线程工作的。我相信线程如何运作的核心思想现在很清楚了。但最重要的是,我希望你们能够理解服务器是如何在某种意义上实现迷你聊天功能的。这些关于套接字的解释的目的并不是为了涵盖关于套接字的一切。我们希望您了解套接字的工作原理。这些知识在后续的回放/模拟系统中会用到。


让我们回到 Excel 中的问题

然而,尽管 Python 服务器的工作方式可以由多个客户端使用,但它不是为在 Excel 中使用而设计的。也就是说,如果我们使用 xlwings 将此服务器集成到 Excel 中,您在与 Excel 交互时会遇到问题。但是,我们为什么要使用线程来避免阻塞代码执行呢?

这是一个有点复杂的问题 —— 至少对于那些不太熟悉程序如何使用不属于它的另一个程序的人来说是这样。也许这句话有点令人困惑,但让我们试着理解一下。当我们在 Excel 中使用 VBA 脚本时,我们实际上是在运行一个程序。如果该脚本调用另一个应用程序,Excel 通常会等待它执行完毕。但情况并非总是如此。为了防止 Excel 必须等待应用程序完成,我们可以使用一个与 Excel 无关的应用程序。也就是说,我们将 Excel 与另一个使用 VBA 的应用程序一起运行。两者可以共存,互不竞争 CPU。这和让 Excel 打开 Word 差不多。即使 Word 卡死也没关系 —— 这不会影响 Excel 的运行。

然而,这里提出的方案并非如此 —— 即让 MetaTrader 5 和 Excel 能够交换数据。虽然 MetaTrader 5 和 Excel 不争用 CPU,但创建用于数据交换的套接字的 Python 脚本却会争用 CPU。这是由于目前为止所展示的服务器模式造成的。为了使解释更简单,让我们采用上一篇文章中讨论的最后一个脚本。如下所示:

01. import socket as sock
02. import threading as thread
03. 
04. CONN_LIST = []
05. 
06. def NewClientHandler(conn, addr):
07.     global CONN_LIST
08.     CONN_LIST.append(conn)
09.     print(f"Client [%s:%s] is online..." % addr)
10.     while True:
11.         msg = conn.recv(512).decode().rstrip(None)
12.         if msg:
13.             print(f"Message from [%s:%s]: {msg}" % addr)
14.             for slave in CONN_LIST:
15.                 if slave != conn:
16.                     slave.send(f"[{addr[0]}]: {msg}\n\r".encode())
17.             if msg.lower() == "/see you later":
18.                 break
19.     print(f"Client [%s:%s] is disconnecting..." % addr)
20.     conn.close()
21.     CONN_LIST.remove(conn)
22. 
23. server = sock.socket(sock.AF_INET, sock.SOCK_STREAM)
24. server.bind(('0.0.0.0', 27015))
25. server.listen()
26. print("Waiting connections...")
27. while True:
28.     conn, addr = server.accept()
29.     conn.send("Welcome to Server.\n\r".encode())
30.     thread.Thread(target=NewClientHandler, args=(conn, addr)).start()

Python 代码

虽然这个脚本不能直接在 xlwings 中用于创建服务器,但我们可以做一些小的调整,它将变得适合使用。上述代码将被转换为以下代码:

01. import socket as sock
02. import threading as thread
03. 
04. CONN_LIST = []
05. 
06. def NewClientHandler(conn, addr):
07.     global CONN_LIST
08.     CONN_LIST.append(conn)
09.     print(f"Client [%s:%s] is online..." % addr)
10.     while True:
11.         msg = conn.recv(512).decode().rstrip(None)
12.         if msg:
13.             print(f"Message from [%s:%s]: {msg}" % addr)
14.             for slave in CONN_LIST:
15.                 if slave != conn:
16.                     slave.send(f"[{addr[0]}]: {msg}\n\r".encode())
17.             if msg.lower() == "good bye":
18.                 break
19.     print(f"Client [%s:%s] is disconnecting..." % addr)
20.     conn.close()
21.     CONN_LIST.remove(conn)
22. 
23. def InitServer(HOST, PORT):
24.     server = sock.socket(sock.AF_INET, sock.SOCK_STREAM)
25.     server.bind((HOST, PORT))
26.     server.listen()
27.     print("Waiting connections...")
28.     while True:
29.         conn, addr = server.accept()
30.         conn.send("Welcome to Server.\n\r".encode())
31.         thread.Thread(target=NewClientHandler, args=(conn, addr)).start()
32. 
33. if __name__ == '__main__':
34.     InitServer('localhost', 27015)

Python 代码

好吧,如您所见,我们只添加了很少的代码,但这使我们能够直接在 Python 中使用该脚本,或者通过 xlwings 从 Excel 中的 VBA 运行它。在 VBA 中,可以使用以下命令从 Excel 激活服务器:

Public Sub ExecuteServer()
    RunPython ("import Server_Thread; Server_Thread.InitServer('127.0.0.1', 27014)")
End Sub

VBA 代码

让我们看看会发生什么。这是为那些没有遵循此顺序或不知道如何使用 VBA 的人准备的。他的小 VBA 脚本应该被我们需要添加到 Excel 中的一些控件调用。这其实是一项非常简单的任务。我们添加一个控件,例如一个按钮,并在按钮的属性中指定它应该执行上面显示的脚本。

但这段 VBA 脚本究竟做了什么?它指示 xlwings 运行我们之前看到的 Python 脚本。请注意,在 Python 脚本中,您可以指定服务器运行的端口和主机。我们在 Server_Thread.InitServer 中看到的参数准确地指示了主机(更具体地说,是服务器期望连接的地址)以及端口。请注意,我们定义的端口与 Python 脚本中使用的端口不同;这样做是为了明确服务器将使用 Excel 定义和调用。

如果你正确地按照所有步骤操作,你会发现 Excel 打开了 Python,服务器也变得可用了。因此,它的效果与我们直接在 Python 中运行它的效果相同。但有一点很重要:Excel 会出现异常行为,在其中执行操作会变得困难。这是因为 Python 服务器会与 Excel 争夺 CPU 使用率。“但为什么会发生这种情况呢?我们的服务器不是使用线程吗?”是的,服务器使用了线程,但不是在整个服务器级别 —— 有一部分代码会阻塞。这部分代码位于第 29 行或原始代码的第 28 行。查看它,您可以看到此行包含对 accept 函数的调用。正是这个函数会占用 CPU 资源,并与 Excel 争夺 CPU 使用率。

这个问题有解决办法吗?是的,有。关键在于我们愿意在解决方案上投入多少努力。我这么说是因为你可以在 VBA 中创建一个线程,这样 Python 脚本就可以直接在线程中运行。或者,你可以寻找另一种解决方案来解决这个问题。

无论如何,都需要采取一些措施来阻止 accept 函数继续与 Excel 争夺 CPU 使用率。


让我们开始使用 SELECT 函数

最简单的解决方案之一是在服务器代码中使用 select 函数,它不需要复杂的编程操作。该函数已在本套接字系列的其他代码中出现过。你可以查看用 C++ 编写的迷你聊天服务器代码的第 69 行来验证这一点。这段代码可以在这篇文章中找到:市场模拟(第 09 部分):套接字(三)

正如你所看到的,这并不是一个复杂的解决方案。但是,如果你想在 Python 中做类似的事情,代码将与 C++ 版本有所不同。如果使用与 C++ 版本相同的代码,则在调用 select 而不是 accept 时程序会崩溃。为了更清楚,让我们来看看服务器代码是如何在 Python 中实现的。这可以在下面的脚本中看到:

01. import socket as sock
02. import threading as thread
03. import select
04. import xlwings as xl
05. import time
06. 
07. CONN_LIST = []
08. INPUTS = []
09. WB = None
10. LINE = 1
11. 
12. def WriteMessageInExcel(msg):
13.     global WB
14.     global LINE
15.     if WB == None:
16.         WB = xl.Book.caller()
17.     WB.sheets[0].cells(LINE, 1).value = msg
18.     LINE = LINE + 1
19. 
20. def NewClientHandler(conn, addr):
21.     global CONN_LIST
22.     CONN_LIST.append(conn)
23.     print(f"Client [%s:%s] is online..." % addr)
24.     while True:
25.         msg = conn.recv(512).decode().rstrip(None)
26.         if msg:
27.             print(f"Message from [%s:%s]: {msg}" % addr)
28.             for slave in CONN_LIST:
29.                 if slave != conn:
30.                     slave.send(f"[{addr[0]}]: {msg}\n\r".encode())
31.             if msg.lower() == "good bye":
32.                 break
33.     print(f"Client [%s:%s] is disconnecting..." % addr)
34.     conn.close()
35.     CONN_LIST.remove(conn)
36. 
37. def InitServer(HOST, PORT):
38.     global INPUTS
39.     server = sock.socket(sock.AF_INET, sock.SOCK_STREAM)
40.     server.bind((HOST, PORT))
41.     server.listen()
42.     INPUTS.append(server)
43.     WriteMessageInExcel("Waiting connections...")
44.     while True:
45.         read, write, err = select.select(INPUTS, [], [], 1)
46.         for slave in read:
47.             if slave is server:
48.                 conn, addr = slave.accept()
49.                 conn.send("Welcome to Server in Python.\n\r".encode())
50.                 thread.Thread(target=NewClientHandler, args=(conn, addr)).start()
51.                 WriteMessageInExcel(f"Client [%s:%s] is online..." % addr)
52.         WriteMessageInExcel("Ping...")
53.         time.sleep(1)        
54. 
55. if __name__ == '__main__':
56.     InitServer('localhost', 27015)

Python 代码

请注意,仅做了细微的改动。我们正在逐步进行此操作,以便您能够准确了解正在修改的内容。请注意,我们现在在第 08 行和第 09 行之间有一些额外的元素。它们通过 xlwings 为 select 和 Excel 提供支持。

为了使代码编写、调试和信息显示更加容易,我们在第 12 行放置了一个小过程。它的目的是将数据输入到 Excel 中。请注意,在第 15 行,我们检查变量是否已初始化;由于第一次调用时不会初始化,因此第 16 行将执行 —— 但只会执行一次。然后,在第 17 行,向当前工作表行写入一条消息,在第 18 行,行计数器递增,以便下一次调用显示在下一行。

我知道这段代码在视觉上不太吸引人,但美观并不是首要考虑因素 —— 功能性才是。最重要的是它能用,而不是它看起来漂亮或者用 VBA 编写起来很舒服。

现在,我想提请大家注意这个脚本的第 45 行。请注意,这里我们调用了 select 函数。与前面提到的 C++ 代码不同,这里我们多了一个参数。此参数的精确值为 1。它指定 select 函数等待事件发生的时间。此时间以秒表示,因此 select 将等待一秒钟后再继续。你可以使用较小的值,并将其设置为浮点数。但如果您不确定如何操作,请不要担心 —— 我们稍后会解释。但是,需要注意的是:如果我们不指定此值,select 将阻塞,直到服务器套接字上发生某些事件。

为了更好地理解这一点,请注意,在第 52 行,我们向 Excel 打印一条消息,然后在第 53 行,我们再等待一秒钟,从而在循环中创建了两秒的延迟。就我们的目的而言,这已经绰绰有余了。下面会发生的是:如果第 45 行在等待套接字上的事件时阻止执行,则 ping 不会每两秒钟发送一次;如果没有这样的阻止,ping 将被发送。通过 VBA 运行此脚本时,我们将得到以下结果:

好的,但是为什么第 46 行需要 for 循环呢?这是因为我们尚未进入该线程,因此需要检查套接字上发生了哪个事件。因此,如果用户尝试连接,此循环将用于确定发生了什么。这样一来,其他一切都保持不变。有一点很重要:如果没有 for 循环,服务器将挂起等待第 48 行 accept 函数中发生的事情。但是,通过循环内第 47 行的检查,我们可以检测到客户端正在尝试连接。虽然该服务器仍然无法让 Excel 在不与 Python 竞争的情况下运行,但我们已经对系统进行了重大改进。

然而,问题出在第 44 行。在这一行,我们在 Python 脚本中进入了一个无限循环,导致脚本与 Excel 竞争 CPU 使用率。但我们不能移除这个循环,因为如果我们移除它 —— 考虑到脚本的实现方式 —— 服务器就会关闭。因此,我们需要做出一些修改来避免这种情况的发生。也就是说,我们需要能够在服务器不关闭连接的情况下进入和退出脚本。但在此之前,让我们看看需要创建的另一段中间代码。为了稍微缓和一下气氛,让我们转向一个新话题。


类中的迷你聊天服务器

尽管许多人不喜欢开发或实现面向对象的代码,但它有明显的优势。首先,它允许我们使用 Python 实现的服务器更容易操作,至少从编程的角度来看是这样。这是因为我们可以很容易地从上一主题中显示的版本中删除线程。这样做,我们将实现与 C++ 实现非常相似的功能。为了实现这一点,我们将创建新代码,但它将与我们迄今为止所看到的完全兼容。当然,在这个早期阶段,我们不会从 Excel 运行它。我们将使用纯 Python 形式。使用 OOP 的新脚本如下所示:

01. import socket as sock
02. import select
03. import time
04. 
05. class MiniChat:
06.     def __init__(self, HOST : str = 'localhost', PORT : int = 27015) -> None:
07.         self.server = sock.socket(sock.AF_INET, sock.SOCK_STREAM)
08.         self.server.setblocking(0)
09.         self.server.bind((HOST, PORT))
10.         self.server.listen()
11.         self.CONN_LIST = [self.server]
12.         print(f"Waiting connections in {PORT}...")
13. 
14.     def BroadCastData(self, conn, info) -> None:
15.         for sock in self.CONN_LIST:
16.             if sock != self.server and sock != conn:
17.                 sock.send((info + "\n\r").encode())
18. 
19.     def ClientDisconnect(self, conn) -> None:
20.         msg = "Client " + str(conn.getpeername()) + " is disconnected..."
21.         self.BroadCastData(conn, msg)
22.         print(msg)
23.         conn.close()
24.         self.CONN_LIST.remove(conn)
25. 
26.     def Shutdown(self) -> None:
27.         while self.CONN_LIST:
28.             for conn in self.CONN_LIST:
29.                 self.CONN_LIST.remove(conn)
30. 
31.     def LoopMessage(self) -> None:
32.         while self.CONN_LIST:
33.             read, write, err = select.select(self.CONN_LIST, [], [], 1)
34.             for sock in read:
35.                 if sock is self.server:
36.                     conn, addr = sock.accept()
37.                     conn.setblocking(0)
38.                     self.CONN_LIST.append(conn)
39.                     conn.send("Welcome to Server Chat in Python.\n\r".encode())
40.                     self.BroadCastData(conn, "[%s:%s] entered room." % addr)
41.                     print("Client [%s:%s] connecting..." % addr)
42.                 else:
43.                     try:
44.                         data = sock.recv(512).decode().rstrip(None)
45.                         if data:
46.                             if data.lower() == '/shutdown':
47.                                 self.Shutdown()
48.                             self.BroadCastData(sock, str(sock.getpeername()) + f":{data}")
49.                         else:
50.                             self.ClientDisconnect(sock)
51.                     except:
52.                         self.ClientDisconnect(sock)
53.             time.sleep(0.5)
54. 
55. if __name__ == '__main__':
56.     MyChat = MiniChat()
57.     MyChat.LoopMessage()
58.     print("Server Shutdown...")

Python 代码

如果您对这段代码有任何疑问,可以参考 Python 文档 。但我相信你理解脚本不会有太大困难。虽然乍一看可能很奇怪,但这都是关于 Python 是如何实现的。请参阅文档以了解一切是如何工作的。但为了更清楚起见,让我们做一个简短的概述。

该类从第 05 行开始,到第 53 行结束。在第 56 行,我们使用类构造函数 —— 即位于第 6 行的 __init__ 过程。此过程实际上创建了服务器,但它此时还不会监听连接。但是,当 init 完成时,调用发生在第 57 行。它将直接执行到第 31 行。请注意,在此过程中,第 31 行,我们已经删除了线程。也就是说,服务器现在将使用与 C++ 服务器中相同类型的控制方式。请注意,我们在第 32 行仍然处于循环中。但是,我希望您了解以下内容:此循环可以在类内部或外部,服务器仍将能够执行其工作。

为什么这对我们如此重要?原因是,如果我们将循环放在类之外,我们将需要一种方法来检查服务器是否可以处于活动状态。在类内部有循环的情况下,要创建服务器,我们只需要第 56 行和第 57 行。这样我们就可以将此服务器打包,以便导入到其他 Python 脚本中。

现在,有趣的部分来了:如果我们忽略第 32 行存在循环的事实,那么同一个服务器就可以打包并包含在 Excel 中,以便 xlwings 可以使用它 —— 而不是像现在这样使用。然而,这种实现方法是故意的。这样,我们就可以很容易地理解 Python 服务器版本之间的变化。因此,我们可以很容易地看出如何阻止 Python 服务器与 Excel 争夺 CPU 使用率。现在,假设您真正理解了代码,我们将对其进行修改,以便可以轻松打包并在任何脚本中使用。

01. import socket as sock
02. import select as sel
03. 
04. class MiniChat:
05.     def __init__(self, HOST : str = 'localhost', PORT : int = 27015) -> None:
06.         self.server = sock.socket(sock.AF_INET, sock.SOCK_STREAM)
07.         self.server.bind((HOST, PORT))
08.         self.server.listen()
09.         self.CONN_LIST = [self.server]
10.         print(f"Waiting connections in {PORT}...")
11. 
12.     def __BroadCastData(self, conn, info) -> None:
13.         for sock in self.CONN_LIST:
14.             if sock != self.server and sock != conn:
15.                 sock.send((info + "\n\r").encode())
16. 
17.     def __ClientDisconnect(self, conn) -> None:
18.         msg = "Client " + str(conn.getpeername()) + " is disconnected..."
19.         self.BroadCastData(conn, msg)
20.         print(msg)
21.         conn.close()
22.         self.CONN_LIST.remove(conn)
23. 
24.     def ExistConnection(self) -> bool:
25.         if self.CONN_LIST:
26.             return True
27.         return False
28.     
29.     def CheckMessage(self, sleep) -> str:
30.         read, write, err = sel.select(self.CONN_LIST, [], [], sleep)
31.         for sock in read:
32.             if sock is self.server:
33.                 conn, addr = sock.accept()
34.                 self.CONN_LIST.append(conn)
35.                 conn.send("Welcome to Server Chat in Python.\n\r".encode())
36.                 self.__BroadCastData(conn, "[%s:%s] entered room." % addr)
37.                 print("Client [%s:%s] connecting..." % addr)
38.             else:
39.                 try:
40.                     data = sock.recv(512).decode().rstrip(None)
41.                     if data:
42.                         if data.lower() == '/shutdown':
43.                             self.CONN_LIST.clear()
44.                         self.__BroadCastData(sock, str(sock.getpeername()) + f":{data}")
45.                         return str(sock.getpeername()) + f"> {data}"
46.                     else:
47.                         self.__ClientDisconnect(sock)
48.                 except:
49.                     self.__ClientDisconnect(sock)
50.         return ""
51. 
52. if __name__ == '__main__':
53.     MyChat = MiniChat()
54.     while MyChat.ExistConnection(0.2):
55.         info = MyChat.CheckMessage()
56.         if info:
57.             print(f"{info}")
58.     print("Server Shutdown...")

Python 代码

好吧,好吧,在上面所示的脚本中,我们对代码进行了一些改进,使其能够实现之前讨论过的功能 —— 即,它可以被打包并在其他应用程序中使用。但是让我们看看现在会发生什么,因为这段代码将不再等待网络事件。

首先,BroadCastData 和 ClientDisconnect 过程在类内是私有的,这意味着它们不能直接从类体外部访问。虽然有办法获取这些私有过程,但我不会解释如何做到这一点,以免你在其他情况下受到诱惑而这样做。请注意,在 Python 中,您可以定义私有元素,并且应该避免尝试访问被视为私有的过程或函数。如果我们尝试这样做,Python 将会报错。同样,Python 中也有一种方法可以访问私有过程,但我把这部分留给你们自己去探索。但是,我强烈建议不要使用这种做法,因为它违反了代码封装的前提之一。如果某个过程或函数是私有的,那肯定是有原因的。

只剩下两项公有过程了:ExistConnection 和 CheckMessage。ExistConnection 返回一个布尔值,如第 24 行所示。此返回值的目的是为了在类主体之外,我们可以检查服务器是否处于活动状态 —— 即是否存在活动连接。

位于第 29 行的 CheckMessage 过程返回某个客户端发送的消息。但情况并非总是如此。请注意,在第 43 行,服务器应客户端请求而关闭。这似乎有点激进,因为原则上,任何客户端都不应该做这样的事情。但由于这段代码仅用于教学目的,我认为这样实现并没有问题。

总之,在第 45 行,我们将返回客户端编写的内容。我们将看到,我们同时使用客户端的名称和键入的消息作为函数结果。这对于分析和实验至关重要。

但真正让我们感兴趣的是 —— 这也是这段代码中最有趣的部分 —— 第 53 行到第 58 行的内容。请注意,前面代码中类内部的循环已被移除并移至此处。更准确地说,它现在位于第 54 行。在第 55 行,我们拦截客户端发送到套接字的任何内容,如果该值是相关的 —— 也就是说,它包含任何数据 —— 我们使用 Python 将接收到的信息输出到控制台;这在第 57 行完成。因此,任何客户端发送的所有内容都将对其他客户端可见,此外,还可以直接在服务器运行的终端上进行监控。

现在,您可能会感兴趣的一点是:如果它所监视的套接字上没有发生任何事情,这台服务器将保持空闲多长时间?还记得吗?在之前的代码中,我们提到过 select 函数会等待一秒钟?这是由于某个特定数值造成的。

在这里,我们将等待较短的时间。由于我们不想更改类代码,我们将直接将值传递给类。这是在第 54 行完成的。请注意,这里我们定义的值小于 1。在目前的情况下,设置的值是 0.2。这是 select 函数中断并继续执行代码之前将等待的时间(以秒为单位)—— 即不到一秒。

根据发送到套接字的信息需要完成的工作和处理时间,该值可以进一步降低。请记住,与我们使用线程的模型不同,这里的服务器只会监视每个连接的功能。在我们使用线程的模型中,每个连接都是独立的。这使得此模型中的处理比使用线程对套接字进行读写时稍慢。

现在,请注意以下细节。从上面的代码可以看出,这个服务器在命令行中运行时运行完美。即使有客户端数量的限制,它也会按顺序读取连接。

但是,对于 Excel 和 MetaTrader 5 之间的双向数据交换,这个 Python 脚本还不完全适用。虽然现在它已经达到了一个更高的水平,我们目前看到的一切都能正常运行,但是这种方式无法让 Python 脚本在不与 Excel 争夺 CPU 使用率的情况下运行。

即使是最后一个脚本也不允许我们实现这一点,尽管它很好地说明了如何做到这一点。如果你不确定,别担心 —— 实际上并不容易注意到;你需要一些并行编程方面的经验。

您可能已经注意到,在最近的大多数脚本中,我们没有使用 xlwings。为什么呢?它不适合这个用途吗?事实上,在将 Excel 与 Python 结合使用时,你使用的任何 Python 基础之外的东西都不会提供良好的体验。让我澄清一下,以避免误解。

当我们在 xlwings 或任何其他允许直接读写 Excel 的软件包中开发某些内容时,我们必须注意,所有程序、函数或过程都是执行之后就完成了其任务。它们不会停留在一个循环中,无论你多么努力地尝试以不同的方式做事,这样的解决方案都永远无法实现我们真正需要的东西。问题是,服务器脚本必须始终保持在循环中,无论它是否在等待套接字上的某些内容。

关键是脚本会循环运行。如果是客户端,方法可能会有所不同。客户端连接到服务器,一旦发生这种情况,服务器将发送客户端应该接收的信息。当客户端收到它时,它可以关闭连接,从而终止脚本并使数据可用于在 Excel 中进行适当处理。这个过程既可以用 Python 完成,也可以用 VBA 完成。

但这实际上并不是最重要的一点。我想指出的是,如果我们使用的是客户端而不是服务器,代码将完全不同。这是因为我们可以使用在 VBA 中创建的计时器在 Python 中运行客户端,而不会出现与 Excel 争夺 CPU 的问题。因此,我认为许多人经常使用的软件包是不够的。我们需要不同的解决方案。


总结性思考

在今天的文章中,我们探讨了如何创建一个在等待套接字事件时不会阻塞的 Python 服务器。然而,所提供的代码都没有完全实现预期目标。作为程序员,我们必须走出舒适区。编程的本质是寻找问题的解决方案,这与许多人想象的截然不同 —— 它并非简单的复制粘贴。编程更像是一门艺术,要求我们超越、研究和寻求问题的解决方案。

如前文所述,使用 Python 开发的客户端配合 Excel 可以确保 Excel 不会出现异常行为以至于无法使用。但是,我想向您展示如何使 Python 服务器在 Excel 中工作,同时仍然允许 Excel 正常使用。很多人可能会想:“但是为什么要让Excel保持打开状态呢?执行计算,使用 Pandas、xlwings 或任何其他可以处理 Excel 文件的软件包更新所有内容,就完成了。”

那将是最简单的解决方案。但我想要证明,我们最初的设想是可以实现的。为了理解这种动机,不妨想象一下一个普通用户。无论用 Python 创建的界面多么美观简洁 —— 以至于用户不需要 Excel 来进行数据分析 —— 许多人仍然会对我们的解决方案表示怀疑。他们会正确地寻找一位能够以适应其工作流程的方式解决问题的程序员。

关键在于,这个用户还希望使用 Excel 来控制 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/MarketReplay.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/12790

附加的文件 |
Anexo.zip (560.03 KB)
最近评论 | 前往讨论 (1)
Hebert wilfredo Herrera Ortiz
Hebert wilfredo Herrera Ortiz | 7 11月 2025 在 08:37
感谢
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
量化趋势分析:基于Python的统计建模 量化趋势分析:基于Python的统计建模
什么是外汇市场的量化趋势分析?以欧元兑美元(EURUSD)货币对为例,系统将统计趋势的规模、持续时间及分布规律。并阐述如何利用这些数据构建盈利的EA。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
从基础到中级:指标(三) 从基础到中级:指标(三)
在本文中,我们将探讨如何声明各种图形表现形式的指标,例如 DRAW_COLOR_LINE 和 DRAW_FILLING。此外,当然,我们将学习如何以简单、实用和快速的方式使用多个指标绘制图表。这确实可以改变你对 MetaTrader 5 和整个市场的看法。