English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 53 部分):事情变得复杂(五)

开发回放系统(第 53 部分):事情变得复杂(五)

MetaTrader 5示例 | 17 一月 2025, 08:36
400 0
Daniel Jose
Daniel Jose

概述

在上一篇文章开发回放系统(第 52 部分):事情变得复杂(四)中,我们创建了一个新的数据结构,这样鼠标指标就可以与控制指标进行交互。虽然一开始互动可能会进行得很顺利、很平稳,但我们还是会遇到一些问题,迫使我们不得不做出一些修改。

问题不在于代码,不在于平台,也不在于使用的概念,而在于我们的意图和工作方式。就我个人而言,我向在回放/模拟系统出现之前一直关注这一系列文章的人道歉。老实说,我并没有想到要使用模块化系统中的某些技术,但如果不使用几十年前开发的某些技术,就不可能继续开发回放/模拟器系统。

我在这里要解释的内容对一些读者来说可能非常陌生,而对另一些读者来说则非常熟悉。然而,无论你如何看待它,我将在这里解释的机制以及我们将越来越频繁地使用的机制都存在于 MetaTrader 5 中,MQL5 允许我们使用它。但在使用 MQL5 时,我们受到限制,只能在图表或 MetaTrader 5 本身内部进行操作。这不是一件坏事,相反,这是一种强加的安全措施,因为该平台旨在与金钱合作,我们不想因为奇怪的事情而赔钱。

因此,尽管有这些限制,也没有理由抱怨。作为一名新程序员,我们将在这里开始使用的东西可能会让你在一段时间内感到迷茫。但这只是因为你不知道内容以及如何使用它。然而,我已经使用这种技术有一段时间了,但不是以我们从现在开始使用它的方式。因此,是时候提高标准,在文章中应用更复杂的概念了。


解释概念

我们将开始大量使用程序之间的消息传递,如果你是这个领域的新手,请注意,因为现在有一个快速的介绍。是的,MQL5 允许我们这样做。如果使用和设计得当,这种方法非常强大。但是,如果不了解一些细节,您会感到完全困惑,这将导致程序在 MetaTrader 5 的同一图表上出现错误行为。

到目前为止,我在本系列中介绍的程序都使用了消息,不是在它们之间,而是在代码中,这样一个类就可以与另一个类通信,即使它们处于不同的级别或没有继承关系。看看我的类代码就知道了。它们几乎都有一个共同的过程:DispatchMessage。该过程的主要目的是管理发送给类的信息,当然也可以使用其他函数与类进行通信。DispatchMessage 用于管理发给类的消息。

这个想法对我来说并不新鲜,它已经存在了很长时间,旨在在程序或过程之间创建一个通用的接口。长期从事专业编程工作的人都知道我们在说什么。因此,当您需要将数据、值或查询发送到您根本不知道其代码的另一个程序时,您可以使用此原则。您向一个非常特定的函数发送一条消息,它会返回特定的信息。它们正是通过这一单一函数进行交流的。

它的名称可能不同,但提供给它的数据集和数据序列总是相同的。

这可能看起来很肤浅,完全没有意义,但如果你正在学习编程,在这种情况下是MQL5,那么你可能已经不止一次看到这个函数,而且几乎所有的指标或 EA 交易系统代码都包含这个函数。在 MQL5 中,此函数或过程称为 OnChartEvent。

你可能会想:"这怎么可能?你是说 MetaTrader 5 可以与我的软件通信?"答案是 "是的"。通过分析此调用中整数常量 ID 中指定的值,您可以过滤并确定 MetaTrader 5 向您发送的消息。

这会让你大吃一惊,但对于初学者来说,一切都变得更加困难。但是,为了不使情况复杂化,我将在 MetaTrader 5 的框架内进行解释,但仅限于此。这一切都更加广泛和复杂。因此,你需要很好地理解这一点:MetaTrader 5 向您的程序发送消息,程序中的某些过程会拦截和管理这些消息。在这些过程中,有一个过程更为通用,可以发送更为复杂的元素,而且还使用一个通用的、定义明确的接口。该过程的名称是 OnChartEvent。我们介绍完了,现在,棘手的问题来了。

MQL5(我现在只谈论它,以免使情况复杂化)允许我们定义自定义事件。这些事件用常数和数值表示。该常量的名称是 CHARTEVENT_CUSTOM。因此,使用此常量,我们可以从程序中的任何点向消息处理程序发送消息,这允许我们将特定和一般事件的处理集中在一个点上,但在任何情况下都不需要调用消息调度器。你必须以正确的方式去做,为了让事情变得更容易,MQL5 为此目的为我们提供了一个函数:EventChartCustom。使用该函数,您可以将消息发送到默认消息处理程序,即前面提到的 OnChartEvent,在我的例子中,它调用了 DispatchMessage。

这一切看起来很棒,效果也很棒,让我们可以做很多事情。然而,这里存在着一个危险。我说的是 CHARTEVENT_CUSTOM。这些用户调用的最大危险不在于我的程序、你的程序或任何其他优秀程序员的程序,而在于用户不知道每个程序的功能。用户往往不知道到底发生了什么。这就是为什么在不了解其真实含义的情况下,永远不要使用它是很重要的。该程序可能在多种情况下都能正常工作,但只有一种情况下,程序之间的交互会变成一场彻底的噩梦:它可能会导致平台崩溃、对象消失或突然出现,你将无法理解正在发生的事情。当效果明显时,这很好,但如果它们完全没有被注意到呢?在你看来,一切都很顺利,但实际上,情况可能就像你正处于深渊的边缘。

如果你是一名专业程序员,正在开发解决方案,不要误解我的意思,但你必须向客户解释,你的程序可能会以一种会给其他程序带来问题的方式进行交互,或者其他程序可能会给你的程序带来问题。用户应该小心,不要把一个程序员的代码和另一个程序员混合在一起。这就是为什么有时候 Windows 经常会莫名其妙地崩溃,人们会简单地归咎于某个程序,而事实上,崩溃通常发生在同一环境下同时运行多个程序的时候。

我不会深入探讨这个话题,因为它超出了这个解释的主要目的,但理解一些东西很重要:MetaTrader 5 类似于 Windows 操作系统。如果我们以正确的方式使用正确的工具,我们就永远不会遇到平台问题。但是,如果你开始混合所有的东西,就要小心了,因为你可能会遇到很多问题。你要知道 MetaTrader 5 是一个专为金融市场交易而设计的图形环境,问题就出现了:如果两个程序使用 EventChartCustom,会发生什么,因为此函数使 MetaTrader 5 根据程序员的要求传输消息?

好吧,这个问题很简单,而且确实是个正确的问题。要理解这一点,让我们从一个更简单的情况开始:当一个程序使用 EventChartCustom 函数时会发生什么?从本质上讲,MetaTrader 5 将发送一个自定义事件,由 OnChartEvent 过程处理。这似乎很明显,不是吗?事实上,不是的。没那么明显,而这正是危险所在。

EventChartCustom 的一个典型用法见上一篇文章中的控制指标代码。在控制指标代码的第 30 行,您可以看到以下内容:

30.     EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, "");

因此,当 MetaTrader 5 执行这一行时,将出现一个 ChartEvent,这将导致图表上的代码执行 OnChartEvent。这个过程将一直持续到我们找到 DispatchMessage 函数为止,该函数位于 C_Control 类中,在上一篇文章所示代码的第 161 行。为方便起见,我将在此添加该片段。

161.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
162.                    {
163.                            u_Interprocess Info;
164.                            
165.                            switch (id)
166.                            {
167.                                    case CHARTEVENT_CUSTOM + C_Controls::evInit:
168.                                            Info.df_Value = dparam;
169.                                            m_Slider.Minimal = Info.s_Infos.iPosShift;
170.                                            SetPlay(Info.s_Infos.isPlay);
171.                                            if (!Info.s_Infos.isPlay) CreateCtrlSlider();
172.                                            break;

C_Control 类中的部分代码

现在,由于事件是自定义的,MetaTrader 5 将在 OnChartEvent 过程调用中设置 ID 值,使 ID 为 CHARTEVENT_CUSTOM 对应的值再加上一个值。在所演示的例子中,该值将与 C_Control:evInit 的值相对应。但 C_Control:evInit 的值是多少呢?要查明这一点,您需要转到 C_Control 类代码的第 35 行并检查该值。

035.            enum EventCustom {evInit};

该值是枚举的一部分。由于枚举只有这个值,而且没有初始化,因此它将从默认值 NULL 开始。这样,MetaTrader 5 将对控制指标中的第 30 行会进行如下解释:

30.     EventChartCustom(user00, CHARTEVENT_CUSTOM + 0, Info.s_Infos.iPosShift, Info.df_Value, "");

这段代码将会完美安全地运行,允许我们作为程序员随时进行自定义调用,以强制 C_Control 类中存在的 DispatchMessage 过程初始化类构造函数中未设置的一些值。这样的事情经常发生,是一种非常合适的编程形式,在各种情况下都非常有用。

鼠标指标也是如此。现在让我们来了解一下其他内容,但仍是在一个程序中使用 MetaTrader 5 中的自定义事件。

在整个鼠标指标代码中,看不到一次对 EventChartCustom 的调用。不过,消息处理程序包含响应自定义事件的代码。该代码存在了相当长的时间,预计将来会被使用。你可以在 C_Mouse 类的第 196 和 199 行看到这些处理代码。如果你仔细观察,就会发现一些问题。为了更好地解释这个观点,我在文章中加入了这个片段。

189. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
190.                    {
191.                            int w = 0;
192.                            static double memPrice = 0;
193.                            
194.                            if (m_Mem.szShortName == NULL) switch (id)
195.                            {
196.                                    case (CHARTEVENT_CUSTOM + ev_HideMouse):
197.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
198.                                            break;
199.                                    case (CHARTEVENT_CUSTOM + ev_ShowMouse):
200.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
201.                                            break;

C_Mouse 类中的部分代码

请注意,在此代码段中,我们使用了 ev_HideMouse 和 ev_ShowMouse。如果有程序想隐藏鼠标指标线,我们只需要求 MetaTrader 5 向鼠标指标发送一个自定义事件即可。这样就可以隐藏或显示鼠标线。请注意,我们并没有销毁对象,只是更改了颜色属性。

ev_HideMouse 和 ev_ShowMouse 的值是枚举,但它们从何而来?你可以在 C_Mouse 类的第 34 行看到它们。为了便于解释,我在这里再次展示代码。

034.            enum eEventsMouse {ev_HideMouse, ev_ShowMouse};

你可能还不明白我想解释什么。请观看下面的视频 01,以了解该系统的功能。


视频 01 - 演示

一切都很和谐,不会产生任何问题。在初始化过程中,控制指标会告诉 MetaTrader 5 需要处理一个自定义事件,而鼠标指标则会等待某个程序的自定义事件来隐藏或显示鼠标线。如果将这两个指标分开,就不会产生问题,也不会相互冲突。然而,当它们发现自己在一起时,情况变得更加复杂了。您可以在视频 02 中看到这一点。


视频 02 - 冲突

为什么会出现这种情况?为什么它们分开时没事,但放在一起时却会发生冲突?这是一个大问题,可能会让很多初学编程的人望而却步,那些没有真正学习编程动力的人也会在这个阶段放弃。然而,专业程序员或有志于成为专业程序员的人却将此视为学习的机会,并努力获取更多知识,解决这一矛盾。这正是使用来自不同程序员的程序的问题,甚至是来自同一个程序员的程序,他们没有花时间更创造性地测试程序。不过,让我们抛开这个问题,重点关注视频 02 中的冲突。

为什么两个似乎彼此独立运作良好的指标以如此奇怪的方式协同工作?


了解工作环境的概念

如果您是一位经验丰富的用户,那么很可能已经听说过或使用过工作环境。这一概念的基础是将各种元素分开,使它们能够在一个生态系统中独立共存。

在 MetaTrader 5 中,这个环境问题被非常重视。如果你不理解这个想法,你就不会理解视频 02 中显示的冲突的原因。最重要的是,你将无法解决这场冲突。请记住:我们希望鼠标指标能与其他任何指标完全协调工作。因此,我们消除了许多问题,它成为了一个更大系统的模块。

许多人会轻易放弃,但你不能放弃。我们正在将系统模块化,因此需要鼠标指标与控制指标一起工作。

观看视频 01 后,我们可以看到这些指标并不冲突,但在视频 02 中,当鼠标指标和控制指标放在同一图表上时,它们会发生奇怪的情况。可以清楚地看到,MetaTrader 5 将每个图表都视为与其他图表完全隔离的环境。

这种观察非常重要,今后还会使用,但在 02 号视频中还有另一个同样重要的点。当 MetaTrader 5 根据自定义事件更新图表以更改时间框架时,会发生几件事。要理解这一点,您需要了解 MetaTrader 5 的工作原理。

当我们请求更改时间框架时(如视频所示),MetaTrader 5 会暂时删除图表中的所有内容,然后再恢复必要的内容。因此,只能在图表上还原指标和 EA 交易系统。如果程序员不考虑这一点,所有程序都会发生冲突。这是因为 MetaTrader 5 恢复对象的顺序很可能与用户在图表上放置对象的顺序不一致。有可能用户在图表上放置的所有内容都是按照这样的顺序排列的,以至于什么都没发生。但是,一旦发生意外,MetaTrader 5 会重建图表,顺序可能会有所不同,然后问题就会开始出现。许多人可能会责怪平台,一些人会责怪操作系统,一些人可能会怪罪上帝或魔鬼。但真正的问题是,你或其他人创建的程序不允许共享多个元素。许多代码都是封闭的,这使得理解冲突的原因变得更加困难。

让我们回到主要问题上来。在本文中,我提到鼠标指标不使用任何自定义事件。但它会响应两个自定义事件:一个是鼠标最小化时,另一个是显示线的时候。至于控制指标,它既使用自定义事件,也响应自定义事件,其作用之一是初始化控制类中的某些元素。

现在让我们回到代码上来。现在,我希望你们仔细听好我接下来要解释的内容。如果您能理解这一点,您将在了解 MetaTrader 5 如何运行方面迈出一大步。

控制和鼠标指标单独编译。在代码中,我们使用的枚举必须与响应消息的类有某种关联。然而,这种推理意味着我们仍然在做一些假设,而在编程中你不应该做假设。对于编译器来说,这些自定义事件代码将始终是一个从常量移位的值。常量是 CHARTEVENT_CUSTOM,由于枚举以默认值(0)开始,因此处理鼠标指标和控制指标消息的代码都以相同的索引开始 - CHARTEVENT_CUSTOM 加 0。

你可能会认为这无法解释冲突的原因,但你错了。这就是原因所在。甚至进一步,这也解释了 MetaTrader 5 工作环境的结构。

每个图表都代表 MetaTrader 5 的工作环境。当程序向 MetaTrader 5 发送自定义事件时,该事件不会发送到程序或任何其他特定程序,而是发送到 MetaTrader 5。平台将为工作环境中(即图表上)存在的任何程序触发此事件。在视频 02 中,你可以看到这是如何发生的。这是因为图表中的所有程序,一旦触发自定义事件,就会收到 MetaTrader 5 的相同通知。现在想想使用自定义事件开仓和平仓的 EA 交易系统。如果同一事件在与 EA 位于同一图表上的指标中使用了与发送信号的事件相等的索引,那么当 EA 交易或指标触发自定义事件时会发生什么情况?你会有大麻烦的。 

我正试着一步一步地讲述,这个问题比表面上看起来要复杂得多,我们将把这个系统用于其他目的。这就是为什么你有必要了解正在发生的事情。如果你不这样做,你最终会陷入一条悬崖尽头的小巷。

您可能会问自己:难道就没有解决这个问题的办法吗?有,而且非常简单,不过需要一些知识。然而,我们不会在本文中详细讨论它,因为我们必须做出一些改动,并解释一些在现阶段过于复杂的观点。


结论

在这篇文章中,我开始概述下一篇文章的内容。我知道对许多人来说,这个话题非常复杂,很难马上理解。但重要的是,你要开始准备和研究这个话题,我在这篇文章中已经谈到了。我们将在接下来的文章中讨论这个主要问题。

为了更好地理解本文所讨论的内容,您可以创建一些小程序,如简单的指标,来生成和响应自定义事件。将它们放在一张或多张图表上,观察它们在一起和分开时的行为。但首先,这是本文的金钥匙:试着让一个指标改变另一个指标,无论是在同一图表上还是在不同的图表上。

如果你不能让它们在不同的图表上通过自定义事件进行交互,别担心。不要认为自己是个糟糕的程序员,也许你只是还不具备必要的知识。我不仅会向您演示如何操作,而且还会将其提升到另一个理解层次,因为您可能已经注意到,我尽可能避免使用一些东西(如外部 DLL)来弥补 MQL5 的一些不足,而 MQL5 到目前为止已经很好地完成了我们的工作,无需花哨的解决方案。

祝大家学习顺利,下期文章再见!做好迎接未来严峻挑战的准备。

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11932

附加的文件 |
Anexo.zip (420.65 KB)
神经网络变得简单(第 87 部分):时间序列补片化 神经网络变得简单(第 87 部分):时间序列补片化
预测在时间序列分析中扮演重要角色。在新文章中,我们将谈谈时间序列补片化的益处。
MQL5 交易工具包(第 1 部分):开发仓位管理 EX5 库 MQL5 交易工具包(第 1 部分):开发仓位管理 EX5 库
了解如何创建面向开发人员的工具包,使用 MQL5 管理各种仓位操作。在本文中,我将演示如何创建一个函数库 (ex5),以执行从简单到高级的仓位管理操作,包括自动处理和报告使用 MQL5 处理仓位管理任务时出现的各种错误。
开发多币种 EA 交易 (第 13 部分):自动化第二阶段 — 分组选择 开发多币种 EA 交易 (第 13 部分):自动化第二阶段 — 分组选择
我们已经实现了自动化优化的第一阶段。我们根据若干标准对不同的交易品种和时间框架进行优化,并将每次通过的结果信息存储在数据库中。现在我们将从第一阶段找到的参数集中选择最佳组。
情绪分析与深度学习在交易策略中的应用以及使用Python进行回测 情绪分析与深度学习在交易策略中的应用以及使用Python进行回测
在本文中,我们将介绍如何使用Python中的情绪分析和ONNX模型,并将它们应用于EA中。使用一个脚本运行TensorFlow训练的ONNX模型,以进行深度学习预测;而通过另一个脚本获取新闻标题,并使用人工智能技术量化情绪。