市场模拟(第 14 部分):套接字(八)
概述
在上一篇文章“市场模拟(第 13 部分):套接字(七)”中,我们展示了如何创建一个简单的 Python 服务器,即使服务器正在监视的套接字上没有任何活动,它也能继续执行代码。这个实现的有趣之处在于,它不需要使用任何线程。
然而,没有什么能阻止我们将上一篇文章中看到的内容与之前看到的内容结合起来,因为后者展示了如何制作类似的服务器,但在客户端连接时使用了线程。这两种方法都有效,因为它们以相同的方式监视套接字。然而,无论是使用线程的模型,还是使用 select 函数的模型,都不适合我们的实现。
以上两种情况都不是 —— 不是因为它们有缺陷,而是因为代码的编写方式导致它们干扰 Excel。这种干扰迫使 Excel 和 Python 脚本争夺 CPU 使用率,使得 Excel 非常难用。
许多程序员或许会认为,我们应当摒弃使用 Excel,转而直接采用 Python,并利用一些能够使 Python 生成 Excel 文件以便后续对结果进行分析的程序包。在此情形之下,用户始终是正确的。作为程序员,我们必须找到一种让一切都能正常工作的方法。倘若你未能达成既定目标,那便是因为你尚不具备所需的知识水准。力求持续学习,并深入探究自身知识所依托的原理。
仅凭基础知识,无法使您跻身高级程序员之列;您仍将仅是一名平庸的程序员。进步之所以得以实现,是因为部分人超出了寻常水准。在今天的文章中,我想向大家展示如何解决 Excel 和 Python 之间的问题。请稍安勿躁,接下来我们将探讨如何让 Excel 与 Python 协同运作,避免二者争夺 CPU 使用率。
回归本源
你们当中许多人都是最近才开始使用 Python 的,另一些人可能已经使用它很长时间了。然而,由于大多数程序员都在积极使用流行的软件包,我认为很少有人真正理解如何以及为何使用 Python。在此我不打算对此进行解释,因为这超出了本系列文章的范围。本系列文章旨在演示如何在 MetaTrader 5 中开发复制/建模系统。然而,为了更好地利用复制/建模系统,有必要利用某些资源,因此我们不得不暂停其开发,以解释其他乍一看似乎无关但实际上相互关联的主题。
好吧,为了实现我们的目标(在 Excel 内部运行服务器以在 Excel 和 MetaTrader 5 之间传输信息),我们可以使用 VBA。然而,使用 VBA 创建这样的服务器非常繁琐。问题在于,服务器将完全嵌入到创建它的电子表格中,但我们需要它能在多个不同的电子表格中都能使用。下文将对此原因进行讨论。不要以为用 VBA 创建服务器是一件复杂的事情。事实上,虽然如我们所说,它很简单,但也很乏味,因为我们必须不断地重复代码。
然而,使用 Python,我们可以轻松创建与 VBA 中相同的服务器,更不用说如前一篇文章所示,还可以使用 C/C++。毫无疑问,使用 Python 是实现这一目标的最简单方法。然而,在之前的文章中,我们已证明使用流行软件包的直接编程方法并不实际。主要原因是服务器启动后,会与 Excel 争夺 CPU 使用率。我甚至演示了如何使用线程来降低竞争程度。然而,在 VBA 中创建线程时,事情开始变得繁琐,因为每次创建新的电子表格时都必须重复编写代码。
因此,解决方案是用 Python 实现大部分代码,并尽可能少地使用 VBA。是的,我们可以做到,而这正是我们将要做的。但请记住,从现在开始呈现的所有内容可能会相当令人困惑。因此,我建议你冷静地研究此事。请勿在生产系统中使用此处显示的代码。如果你愿意,你可以这样做,但请记住,所提供的代码仅供教育用途。
让我们花点时间来了解一下 COM 是什么
在我们开始之前,我想问你几个问题:你知道 COM 是什么吗?你读过关于 OLE 的资料或使用 OLE 编程过吗?你对 .NET 有所了解吗?如果以上任何一个问题的答案为“否”,那么你还在编程的初级阶段。但就我们的目的而言,理解基本概念并能够使用 COM 就足够了。
COM 代表“组件对象模型”。它由微软于 20 世纪 90 年代初创建,旨在促进进程间通信。它与套接字非常相似,但有一些优势。它使得这种通信的实现能够独立于所使用的语言、架构或设备。而所有这些都不需要你们这些开发者去理解内部实现是如何运作的,因为你们将不再需要担心这个问题,而是使用一个通用的、定义明确的接口。
这些是使用 COM 的基本知识。但是,在开发的这个阶段,它为什么对我们很重要呢?原因在于,利用这项技术,我们可以使 Python 和 Excel 在数据交换过程中完全协调工作,而不会在 CPU 使用上产生冲突或竞争。
但是,COM 技术可以在 Python 中使用吗?是的,而且出乎意料的是,我们不需要安装任何软件包。我们只需要稍微了解一下 Python 的工作原理。但是,如果你掌握了这种 COM 技术,一切都会变得简单得多。在文章的结尾,将提供一个链接,供您开始了解 COM,同时还有一个链接,可帮助您深化对当前讨论主题的了解。
现在我们将开始用 Python 编程。
我们首先要做的事情很简单。很多人会说,使用其他 Python 包也能完成同样的任务。事实上,他们并没有说错,同样的方法也适用于其他软件包。但你必须了解事情的实际运作方式。我们不会玩弄手段来掩盖我们需要了解的东西。因此,我们将使用最简单的 Python 形式。也就是说,如果你下载并安装了 Python,且没有安装任何其他包,那么你将能够完成我们在这里演示的相同操作。记住,你的电脑上必须安装有Excel,但这只是提醒一下 —— 我认为这很明显。
让我们用 COM 技术编写我们的第一个 Python 代码。为此,我们将使用 Python 脚本打开 Excel,并在特定单元格中输入一些信息。如果没有这些额外的软件包,管理起来似乎很困难,不是吗?如果你这么认为,那是因为你还在学习 Python 的初级阶段。是的,这是可行的,而且在使用这个或那个软件包时不会遇到任何麻烦。看看实现这一功能的脚本的源代码。以下是完整代码:
1. from win32com import client as win32 2. 3. excel = win32.Dispatch('Excel.Application') 4. excel.Visible = True 5. 6. while True: 7. pass
Python 代码
运行上述脚本后,您将得到以下结果:

你可能会想:“但是电子表格在哪里?”不用担心,我们除了打开 Excel 之外什么都没做。但请注意我们是怎么做的。在脚本的第 01 行,我们指定要使用的内容。在第 03 行,我们发送创建 Excel 应用程序的请求。它会被创造出来,但却会被隐藏起来。为了使其可见,我们使用第 04 行。如果我们不使用第 06 行的循环,应用程序将打开并立即关闭。第 07 行只是为了确保 Python 不会因为循环为空而发出警告。
现在,让我们给这个脚本再添加一些代码。我们可以稍微加快一点速度,但我想让你理解正在发生的事情。所以,我们别着急。下面可以看到脚本:
1. from win32com import client as win32 2. 3. excel = win32.Dispatch('Excel.Application') 4. excel.Visible = True 5. wb = excel.Workbooks.Add() 6. wb.Activate() 7. 8. while True: 9. pass
Python 代码
结果如下图所示:

如您所见,我们添加了两行代码:05 和 06。目前,我们正在使用 COM 技术。本文末尾提供的链接可供您查阅和研究这些调用,以便在您希望了解本文内容之外的知识时,能够更深入地了解 COM 编程技术。记住,我们在这里只会向你展示基础知识,这些知识足以让你做好准备,并避免在下一阶段产生困惑。请注意,我们打开了 Excel,使其可见,添加了一个电子表格,然后激活了它。一切仍然非常简单明了。
现在想象一下,你想重命名那个相同的电子表格。如何通过 Python 使用 COM 来实现这一操作?其实很简单,只需使用以下修改后的脚本:
01. from win32com import client as win32 02. 03. excel = win32.Dispatch('Excel.Application') 04. excel.Visible = True 05. wb = excel.Workbooks.Add() 06. wb.Activate() 07. ws = wb.Sheets('Sheet1').Name = "New Name" 08. 09. while True: 10. pass
Python 代码
结果如下图所示:

如您所见,我们只在 Python 脚本中添加了一行 —— 第 07 行。结果正如我们预期的那样。但我们还没有在电子表格中输入任何信息。我们该如何做呢?再说一遍,这非常简单明了。我们只需指定位置,并将信息放置在那里即可。由于这一部分的内容取决于我们想要做什么,我们将展示其中一种可能的方法。它看起来会是这样的:
01. from win32com import client as win32 02. 03. excel = win32.Dispatch('Excel.Application') 04. excel.Visible = True 05. wb = excel.Workbooks.Add() 06. wb.Activate() 07. wb.Sheets('Sheet1').Name = "New Name" 08. ws = wb.Sheets('New Name') 09. ws.Range('E6').Value = 'Checking.' 10. 11. while True: 12. pass
Python 代码
因此,我们得到的结果与图中所示完全一致:

哇,这真有趣!请注意,如果你输入一个公式,它将会被计算。现在请注意以下事项:已添加第 08 行和第 09 行。原因在于,通过添加第 08 行,我们确保了不必频繁重复标签名称。然而,第 08 行和第 09 行也可以合并为一行。
“但是等一下,我们在上一个脚本中看到的这段代码看起来很眼熟。”如果你也有同样的想法,那绝非巧合。事实上,当我们打开 Excel 并使其可见时,一切看起来都与在 VBA 中执行的代码非常相似。这就是 COM 接口的魅力所在。请注意,我们正在用 Python 编写一个脚本,但我们所做的大部分工作与 VBA 脚本非常相似。这为我们提供了前所未有的可能性,让我们能够做到更多。
“这很棒,但是这里所做的一切也可以使用 Python 中可以安装的其他软件包来完成。”是的,你是对的。然而,如果我们按照所示操作,你会发现我们使用的 Python 脚本在 VBA 中同样适用。“嗯,当我想打开一个空白电子表格时,我知道该怎么做。但如果 Excel 已经打开,我该如何继续操作?”那样的话,整个过程将会更加简单明了。您只需进入 Excel 并指定更改位置即可。代码如下所示:
1. from win32com import client as win32
2.
3. excel = win32.GetActiveObject('Excel.Application')
4. excel.Worksheets('Sheet1').Range('D5').Value = "Cool..." Python 代码
执行此脚本的结果如下所示:

你会发现,一切都比初看起来要简单和容易得多。请注意,在第 03 行中,我们要求接口为我们提供一个对象来处理应用程序,在本例中是 Excel。在第 04 行,我们指定了我们将要工作的内容和地点。很简单,然而,到目前为止我们所看到的一切都是最基础的。我们的目标是让你明白,没有必要下载和安装大量的 Python 包。你只需知道如何使用默认已包含的内容。当然,除此之外,还要多了解一些已经存在多年的技巧和技术。
好吧,这是我们运行服务器而不干扰用户在 Excel 中工作的核心部分。接下来要了解的是如何在 Excel 中操作某些元素。我说的不是公式或诸如此类的东西。我希望你能明白,我们可以利用 Python 的 COM 技术,实现远超 Python 常规功能的应用。然而,为了将思路分开,以便您更好地理解我们将要解释的内容,让我们继续讨论一个新的话题。
事件接二连三
大多数用户并不知道,许多程序其实都是能够响应事件的系统。这些事件可以是任何性质的:鼠标点击、文本输入,或是与进程或程序间数据交换相关的事件。在多任务操作系统中,事件是最重要的。
这个案例也不例外。尽管在不占用大量 CPU 资源的情况下,我们的 Python 服务器与 Excel 交互最初并不需要太多资源,但我们仍需要了解如何让 Python 监测 Excel 中发生的事件。事实上,我们需要让服务器知道 Excel 正在做什么,以便了解何时应该关闭或激活它。
别忘了:关键是要保持 Excel 处于打开状态,并通过实时数据(RTD)或动态数据交换(DDE)从 MetaTrader 5 接收实时数据。然而,我们也希望 Excel 能以一种特定方式向 MetaTrader 5 发送指令,这样交易者只需“查看” Excel,就能向 MetaTrader 5 发送订单。然后 MetaTrader 5 将允许 EA 交易解读在 Excel 中创建的订单。这样,EA 就可以执行交易者请求的操作。而且所有这一切都是在交易员屏幕上看不到 MetaTrader 5 的情况下完成的。交易者只需查看 Excel,并根据分析(无论是基本面分析还是其他类型的分析),就能直接在 Excel 中买卖任何交易品种,而无需参考图表。
好的,但我们如何让 Python 脚本知道用户在 Excel 中做了什么?请记住,我们只是以 Excel 为例,但我们所解释的内容适用于任何使用 COM 技术的程序。
方法很简单。然而,你需要了解我们想要监控的程序是如何运作的。为此,这些链接将为您提供执行与本文所示内容相关的其他任务所需材料。不要偷懒:在问如何做这个或那个之前,先学习相关资料。毕竟,如果你了解所有事情的运作方式,你就能做得更多,而不仅仅是寻求现成的答案。研究你想通过 COM 使用的程序的材料和文档。
现在让我们看看它实际是如何运作的。为此,我们需要对上述脚本进行小幅修改。为了尽可能保持简洁 —— 因为这里的想法既具有教育意义又具有实用性 —— 我们将假设在执行 Python 脚本时,Excel 已经处于打开状态。这样一来,我们的工作量会大大减少,同时也能更容易地理解和解释正在发生的事情。
主脚本如下所示:
01. from win32com import client as win32 02. import pythoncom as pyCOM 03. import time 04. 05. class AppEvents: 06. def OnSheetActivate(self, *arg): 07. print('Tab ' + str(arg[0].Name) + ' selected...') 08. 09. class SheetEvent: 10. def OnSheetSelectionChange(self, *args): 11. print(args[1].Address) 12. args[0].Cells(1, 1).Value = "Select " + str(args[1].Address) 13. 14. excel = win32.GetActiveObject('Excel.Application') 15. 16. win32.WithEvents(excel, AppEvents) 17. win32.WithEvents(excel.Workbooks('Book1'), SheetEvent) 18. 19. print("Enter in loop...") 20. while True: 21. pyCOM.PumpMessages() 22. print('ping...') 23. time.sleep(1)
Python 代码
运行之后,我们会注意到两件事。第一个是结果将直接显示在 Excel 中,如下图所示:

第二个出现在脚本运行的终端。在我们的案例中,我们使用的是 Visual Studio Code,因此在模拟命令行中通常所见内容的区域,可以观察到以下数据:

请注意:这显示的是在 Excel 中完成的操作。“但这怎么可能呢?”请记住,此代码仅用于教育目的。除了这里展示的内容,还有很多事情可以做,但让我们先来分析一下发生的事情,这样你就可以利用这些知识去做一些真正有趣的事情了。
在我们开始之前,我想提醒大家注意这段代码中的一个细节。我会指出一个具体的时刻,但这个时刻适用于所有情况,无论我们是想再创造一个这样的点,还是已经存在于剧本中的点。这是第 06 行,不过我接下来要讲的内容也适用于第 10 行。
如果您查阅本文的参考文档并搜索“Excel 应用程序事件”,您将找到以下内容: Application.SheetActivate 事件。好吧,但我为什么要说这些呢?原因很简单:在 Python 脚本中,我们不能随意使用任何名称来引用事件。否则,在捕捉事件时,脚本将无法正常工作。
现在,请注意代码中的某些内容。在第 05 行,我们定义了一个类。这个类将包含我们想要拦截的所有应用程序事件,就像在第 09 行中那样,我们创建了另一个类来处理另一种类型的事件。在这种情况下,我们正在处理一个涉及电子表格本身的事件。请注意,这两项活动是相互独立的,且目的不同。类名可以是任何有效名称;然而,过程名并不遵循这一规则。要将某个过程与某个事件关联起来,该过程必须具有特定的名称。
现在,回到文档:由于我们要在 Python 脚本中使用 Application.SheetActivate 事件,因此我们必须执行以下操作:我们要捕获的事件名称必须以 On 开头,紧跟事件名称,在本例中为 SheetActivate 。因此,我们得到了该过程应该具有的名称。文档中需要考虑的另一个方面是每个事件使用的参数或自变量的数量。你可以将我们在此提供的相同结构用于所有事件。这样,Python 将实际参数转换为元组,该元组可以在语言本身中使用。如果你不知道什么是元组或如何使用它,请参阅 Python 文档 —— 对于任何想用这种语言编程的人来说,这是最基础的资源。
但是,回到代码,我们已经可以看到这是如何工作的了。在终端图像中,我们删除了第 22 行,因为它产生了太多信息,干扰了我们对发生情况的理解。然而,出于教育目的,我建议你一开始就保持第 21 行处于活动状态,以便注意到一些重要内容。第 21 行的调用不会阻塞代码,而是使与事件关联的类能够接收这些事件。这个结果让你觉得不清楚吗?让我们把事情说清楚。
请注意,第 14 行包含我们以前见过的内容。它的目的是捕获 Excel 会话。但我们真正感兴趣的是变量本身。我们首先要明确,它将在两个地方使用。第一个在第 16 行。在这一行中,我们指定哪个类将负责处理与应用程序相关的事件。该调用的第二个参数代表类名。我们第二次使用该变量是在第 17 行。现在,请注意:这里我们使用了一个特定的连接。在这种情况下,“连接”指的是在 Excel 中打开的文件的名称。如果我们使用了一个不同名称的文件,那么此时就需要替换它,以便系统能够正确捕获事件。第二个参数表示将处理工作表上发生的事件的类的名称。
亲爱的读者,我留下这段文字供你个人研究:进行实验,并尝试理解发生了什么,以及为什么这个脚本能让我们记录工作表名称以及每个工作表中的选定单元格。如果你花时间深入理解它,你将获得进一步改进脚本的动力。
总结性思考
在今天的文章中,我们以非常简单的方式解释了一个许多人并不了解的知识点:Python 如何通过 COM 接口处理信息和访问程序。我希望这些材料能激发你们作为有抱负的专业程序员,对研究所有事物实际运作方式产生更大的兴趣。我们在这里展示的是我们能力的最基本实现。许多人认为必须安装众多工具并同时操作多个程序,但实际上,对技术的理解和知识已足够高效地完成大量高质量的工作。
尽管我还没有展示出如何将所有这些连接起来,以便 Python 能够维护一个实时服务器,并在不影响 Excel 操作的情况下在电子表格中操作、写入和读取数据,但那些具有更广阔视野和编程经验的人已经能够想象出如何将所有这些集成在一起。但如果这不是你的情况,不用担心。在下一篇文章中,我们将探讨如何实现这一切。期待很快与您见面!别忘了学习链接部分中的材料,如果你立志成为一名专业程序员,这些材料将非常有用。
链接:
| 文件 | 描述 |
|---|---|
| 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 | 创建并维护市场回放/建模服务(整个系统的核心文件)。 |
| Code 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/10683
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA
价格行为分析工具包开发(第二十九部分):暴涨与暴跌拦截EA
新手在交易中的10个基本错误
精通日志记录(第九部分):实现构造器模式并添加默认配置