English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 57 部分):了解测试服务

开发回放系统(第 57 部分):了解测试服务

MetaTrader 5示例 |
313 3
Daniel Jose
Daniel Jose

概述

在上一篇文章开发回放系统(第 56 部分):调整模块中,我们对控制指标模块以及最重要的鼠标指标模块做了一些修改。

由于这篇文章已经包含了很多信息,我决定不再添加任何新数据,因为亲爱的读者,这很可能只会让你感到困惑,而不是帮助澄清和解释事情的实际运作方式。

上一篇文章的附件包含鼠标指标和服务。当您运行该服务时,它将创建一个自定义交易品种,并添加一个带有鼠标指标和控制指标的面板。两个模块都放置在自定义交易品种图表上,虽然它们不执行任何服务相关的操作,但是可以看到用户和这两个模块之间的一些交互活动。

您可能不知道如何做到这一点,特别是如果您是 MQL5 的新手。如果你查看这两个指标的代码,你可能看不到任何可以防止指标中数据丢失的活动,或者更确切地说,控制模块中的数据丢失。

因此,本文将重点解释该服务实际上是如何工作的。这种解释至关重要,因为正确理解服务的工作原理对于理解回放/模拟器系统的工作原理至关重要。这是因为用更少的组件创建和解释代码总是比立即尝试理解结构复杂得多的代码更容易。

因此,即使我们实际上不会使用接下来将解释的代码,但详细理解它非常重要。如果你能很好地理解这个更简单的代码,那么控制模块、鼠标模块和服务交互的整个基础就会更好地理解。

所以,闲话少说,让我们来看看上一篇文章中实现和演示的服务的源代码。让我们试着理解这篇文章末尾提供的视频。


我们来分析一下服务代码

该服务的完整源代码如下:

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. #property link "https://www.mql5.com/en/articles/12000"
06. #property version   "1.00"
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
11. #resource "\\" + def_IndicatorControl
12. //+------------------------------------------------------------------+
13. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
14. //+------------------------------------------------------------------+
15. void OnStart()
16. {
17.    uCast_Double info;
18.    long id;
19.    int handle;
20.    short iPos, iMode;
21.    double Buff[];
22.    MqlRates Rate[1];
23.    
24.    SymbolSelect(def_SymbolReplay, false);
25.    CustomSymbolDelete(def_SymbolReplay);
26.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
27.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
28.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
29.    Rate[0].close = 110;
30.    Rate[0].open = 100;
31.    Rate[0].high = 120;
32.    Rate[0].low = 90;
33.    Rate[0].tick_volume = 5;
34.    Rate[0].time = D'06.01.2023 09:00';
35.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
36.    SymbolSelect(def_SymbolReplay, true);
37.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);   
38.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE)
39.       ChartIndicatorAdd(id, 0, handle);
40.    IndicatorRelease(handle);      
41.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE)
42.       ChartIndicatorAdd(id, 0, handle);
43.    IndicatorRelease(handle);   
44.    Print("Service maintaining sync state. Version Demo...");
45.    iPos = 0;
46.    iMode = SHORT_MIN;
47.    while (def_Loop)
48.    {
49.       while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50);
50.       info.dValue = 0;
51.       if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0];
52.       IndicatorRelease(handle);
53.       if ((short)(info._16b[0]) == SHORT_MIN)
54.       {
55.          info._16b[0] = (ushort)iPos;
56.          info._16b[1] = (ushort)iMode;
57.          EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, "");
58.       }else if (info._16b[1] != 0)
59.       {
60.          iPos = (short)info._16b[0];
61.          iMode = (short)info._16b[1];
62.       }
63.       Sleep(250);
64.    }
65.    ChartClose(id);
66.    SymbolSelect(def_SymbolReplay, false);
67.    CustomSymbolDelete(def_SymbolReplay);
68.    Print("Finished service...");   
69. }
70. //+------------------------------------------------------------------+

测试服务源代码

为了确保每个人都能真正理解正在发生的事情,即使是那些对 MQL 没有太多经验的人,尤其是那些还不太熟悉如何为 MetaTrader 5 编程服务的人,让我们从第 2 行开始仔细看看代码。

当你遇到 MQL5 代码第二行中声明的属性时,你应该明白它是一个服务。如果缺少此属性,则必须将代码视为脚本。服务和脚本之间的主要区别首先在于代码中是否存在此属性。但从执行的角度来看,主要区别在于脚本将始终链接到图表,而服务不依赖于任何图表。

剩下的 3 到 6 行,对于那些至少对 MQL5 编程知之甚少的人来说也是易于理解的,因此,我们可以跳过它们的描述。

在第 8 行中,我们添加了 include 指令,以将头文件包含在此代码中。请注意,在这种情况下,头文件将位于“Market Replay”文件夹内的“Include”文件夹中,名为“Defines.mqh”。“Include”文件夹位于 MQL5 主目录的根目录中,该文件夹将在编译期间被 MetaEditor 引用。

现在我们需要特别注意第 10 行和第 11 行。这些代码行确保指标,或者更确切地说是控制模块,成为编译的服务代码的内部资源。这是何意?这意味着,当您迁移已编译的代码时,您不需要同时迁移控制模块代码,因为它已经作为服务资源嵌入。因此,上一篇文章的附件只包含两个可执行文件,尽管在启动服务时,实际上有三个文件在运行。

但是,为什么我们没有像控制模块那样,将指标,或者更确切地说是鼠标模块,也作为服务资源包含在内呢?原因很简单:允许用户使用鼠标模块,使其更容易访问。如果此模块嵌入在服务可执行文件中,用户将很难访问鼠标模块,将其放置在与服务创建的图表不同的图表上。

因此,我们经常需要决定哪些元素,尤其是为什么应该成为特定可执行文件的内部资源。

接下来看第 13 行。在这一行中,我们声明了一个定义,以简化或更好地标准化我们将要执行的一些测试。在复杂的程序中,在不同的地方运行相同类型的测试是很常见的。创建一个定义来标准化这些测试,除了使代码更简单、更易于维护外,还可以确保我们始终执行相同类型的测试。在许多情况下,这对大多数程序员来说是非常可取的,因为可能会忘记修改某些测试点,然后由于某种原因,代码在特定测试中表现良好,但在其他点失败。这通常会引起很多麻烦。

好的,在第 15 行,我们实际上进入了代码的可执行部分。请注意,OnStart 是 MQL5 代码中脚本和服务的相同入口点。然而,从第 2 行的声明中我们知道我们正在处理的是服务。MetaTrader 5 每次执行代码时只会生成一个 OnStart 事件调用,因此需要一些操作来确保代码在所需的时间内运行。这只会发生在第 47 行的特定情况下。但首先,我们需要做更多的事情。

也就是说,我们需要初始化和调整服务,以便它能为我们做一些有用的事情。其中一个必要的步骤是声明和初始化我们在 MetaTrader 5 中的服务将加入的变量和条件。所有这些都将在服务启动后完成。考虑到这一点,在第 17 至 22 行中,我们声明了将使用的变量,并继续正确初始化所需的规则。从现在开始讨论的很多内容可能看起来很奇怪。

这些看似简单的东西最初可能是另一种类型的应用程序的一部分,但因为我们想将所有东西集中在一个代码中,所以我们必须这样工作。

因此,在第 24 行,我们告诉 MetaTrader 5,我们所指出的交易品种应该从市场报价窗口中删除。紧接着,在第 25 行,我们将其从交易品种列表中删除。该列表包含我们可以访问的所有交易品种。但我们真正感兴趣的是第 26 行,在这一行中,我们告诉 MetaTrader 5 我们想要创建一个自定义交易品种并指定应该在何处创建它。您应该注意这一点,因为如果您不小心,您可能会在此操作期间覆盖市场交易品种,并且稍后在尝试访问真实资产工具时,您实际上是在访问自定义资产工具。但通常我们总是采取一些预防措施来防止这种情况发生。

第 26 行和第 27 行是必需的;如果没有它们,鼠标模块将无法在您将鼠标移动到图表上时正确地将价格线设置到正确的位置。

现在,在第 29 行和第 34 行之间,我们定义图表上可见的第一个柱形。出于我不完全理解的原因,这个柱形总是以一种阻止你看到高点和低点的方式定位。但是,由于我们的目标只是在图表上显示柱形,并防止模块产生任何范围错误,因此我们并不真正关心整个柱形是否可见。

到目前为止,我们在图表上什么都没有,甚至图表本身也没有。在第 35 行中,我们告诉 MetaTrader 5,在前面描述柱形的代码行中,定义的值应该被放置为我们正在创建的资产的第一个柱形。在第 36 行,我们告诉平台应该将资产放置在市场报价窗口中。如果代码在此时结束,我们可以手动打开自定义交易品种并显示在其中设置的柱形。但由于我们想要进一步实现自动化,所以我们有了第 37 行。

在执行第 37 行时,MetaTrader 5 将打开具有指定交易品种和时间框架的图表。在本系列的某个时候,我已经解释了告知模块有关图表 ID 的原因。记住这一点非常重要,我们现在打开了一个图表,并且由于我们没有指定应该使用哪个模板,因此图表将使用默认模板打开。这个思路对于理解下面代码行的内容是必要的。

让我们从这里开始:请注意,在第 38 行,我们尝试生成一个句柄,以尝试将鼠标模块放置在图表上。特别注意指定的可执行文件的位置和名称。如果创建句柄的尝试由于任何原因失败,则第 39 行将不会被执行。通常原因是可执行文件不在指定位置,我稍后将对此进行更详细的解释。但是,如果成功创建了句柄,那么在第 39 行,我们将把模块添加到图表中。这样,指标将可用,不一定可见,但会显示在图表上运行的指标列表中。

因此,在第 40 行中,我们告诉 MetaTrader 5 不再需要句柄,因此为其分配的内存可以返回给系统。但是,由于该模块已经在图表上,MetaTrader 5 不会将其从图表中删除,除非您特别告诉终端这样做。

可以删除相同的脚本,效果相同,即鼠标模块将放置在图表上。如果图表模板包含该模块,则会发生这种情况。为此,您可以打开任何图表,向其中添加鼠标指标,然后将此模板另存为 Default.tpl。或者,为了更清楚,您现在可以创建一个已经包含鼠标指标的默认模板。这样就不需要第 38 至 40 行的显示命令,同时仍然允许我们将鼠标指标放在最方便的位置。

对于控制模块来说,情况稍有不同。这是由于控制模块已集成到服务的可执行文件中。这使得服务可以更轻松地将模块放置在图表上。这是在代码的第 41 行和第 42 行完成的。在第 41 行,我们生成一个句柄来访问该模块,并在第 42 行将其添加到图表中。请注意,如果我们不使用第 42 行,该模块将不会被放置在图表上,它只会被加载到内存中,但 MetaTrader 5 不会在我们需要的图表上运行它。

在第 43 行,我们删除了句柄,因为我们不再需要它,就像我们在第 40 行所做的那样。

直到此时,该服务的行为就像一个即将终止的程序。但在此之前,我们使用第 44 行将消息打印到消息框中,这样我们就知道我们在执行方面取得了多大的进展。然后,在第 45 行和第 46 行中,我们初始化我们实际要使用的最后一个变量。请注意,我们使用将用于启动控制模块的值初始化这些变量。它刚刚在图表上启动,但尚未初始化,因此未显示在图表上。

最后,在第 47 行我们进入一个循环。如果满足第 13 行中定义的任何条件,则此循环将终止,这将导致条件变为 false,循环将终止。从这一刻起,我们将不再以任何随意的方式做事。从这一点开始,服务将停止执行操作,并负责管理已经运行的内容。重要的是要牢记这一概念,并知道如何进行区分。否则,你可能会试图做一些不应该在循环中的事情,这将再次使一切变得非常不稳定和有问题。

然后我们在第 49 行看到一个新的循环。如果规划不当,第 49 行开始的这个循环是一种危险的循环。原因是,如果我们不使用第 13 行中的定义,我们可能会无限期地陷入这个循环。这是因为控制模块可能不在图表上,服务将等待 MetaTrader 5 告诉它句柄值,然后才能访问指标或控制模块。

但是,删除图表控制模块将导致 MetaTrader 5 关闭图表。这使得第 13 行的定义为 false,因此第 49 行和第 47 行的循环将终止。

这就是为什么我以一个更简单的系统为例解释这一切是如何运作的。在更复杂的系统中,注意并理解这些细微差别会更加困难。因此,无论何时你想测试什么,都可以用一些简单的程序来完成,这些程序遵循与稍后设计的系统相同的操作逻辑。

因此,假设 MetaTrader 5 返回一个有效的句柄,则在第 50 行中,我们将在第 51 行中定义的控制指标缓冲区读取成功时将该值清零。

如果读取缓冲区因任何原因失败,该变量将被清零。但如果读取成功,我们将获得控制指标缓冲区中的数据。

当我们收到数据后,我们就可以删除句柄,这是在第 52 行完成的。原因是,接下来的步骤中可能发生任何事情,并且我们不希望在循环重复时得到错误。这似乎会降低系统的整体性能,但与其分析可能无效或不必要的数据,不如损失一点性能。

现在请注意以下几点:指标,或者更确切地说是控制模块,尚未初始化。但是,其缓冲区包含在指标运行的第一阶段放置在那里的值。为了理解这一点,请参阅本系列前面的文章。因此,您不应该期望在读取缓冲区时会返回空值,这不会发生。

对此我们有两个条件测试。一个允许我们初始化控制模块,以便它知道要怎样控制显示。第二个测试允许我们在服务中存储有关控制模块中发生的事情的所有信息。这样做是为了当我们需要再次报告模块的最后一个工作状态时,我们知道要传递给它什么值。

因此,在第 53 行我们检查模块是否已添加到图表中。这可能发生在两个时刻。第一个是当我们仍处于从第 47 行开始的循环的第一次执行中时。在这种情况下,要报告给控制模块的值来自代码的第 45 行和第 46 行。第二个时刻是当图表时间框架由于某种原因发生了变化, MetaTrader 5 必须重置并重新放置图表上的控制模块的时候。在这种情况下,将使用 MetaTrader 5 将控制指标放回图表之前该指标的最后值。

但无论如何,最后我们将执行第 57 行,这将导致 MetaTrader 5 在服务监控的图表上生成自定义事件。这样,控制指标将获得新值或旧值,给人一种神奇地没有丢失任何东西的印象。这种情况只会发生,因为在指标的控制模块中,我们在缓冲区中放置了一个特定的值,表明指标想要并且应该从外部来源接收更新后的值。在这种情况下,它是在 MetaTrader 5 平台中运行的服务。

现在,如果它不是来自指标缓冲区的内容,我们将在第 58 行执行新的检查,检查该值是否非零。这样我们就可以了解控制指标是处于播放模式还是暂停模式。在演示服务中,两种情况都表明服务应该捕获指标缓冲区的内容并保存将要使用的值,如果 MetaTrader 5 决定由于图表时间框架的变化而删除并重新将指标放回图表上时就会用到该值。

由于执行可以非常快地发生并且我们不需要超快速的更新,我们使用第 63 行让服务休息一下,在此期间它不会执行任何重要操作。这一行还告诉我们从第 47 行开始的循环已经结束。

此外,当用户关闭服务或图表因任何原因关闭时,我们将退出从第 47 行开始的循环并首先执行第 65 行。在这一行中,我们要关闭图表,因为如果用户终止服务,图表仍将保持打开状态。为了避免这种情况,我们告诉 MetaTrader 5 它现在可以关闭我们正在使用的图表。

关闭图表后,我们尝试从市场报价窗口中删除创建的自定义交易品种。这在第 66 行完成的。如果没有其它带有此交易品种的打开图表,我们可以强制 MetaTrader 5 将其从市场报价中删除。然而,即使删除之后,它仍然会出现在创建它的位置,即第 26 行。

如果该交易品种确实已从市场报价中删除,我们将使用第 67 行将其从系统可用交易品种列表中删除。最后,在第 68 行,如果一切顺利,所有程序均按预期执行,我们将在终端消息窗口中打印一条消息,通知服务已终止并且不再运行。


结论

这种情况,虽然从长远来看可能看起来不重要或无用,但实际上为我们以后真正需要的东西提供了有用和必要的知识。通常,在开发解决方案或应用程序时,我们必须处理未知或不利的情况。因此,每当你需要设计、编程或开发一些可能比最初想象的要复杂得多的东西时,可以创建一个易于理解和学习的小程序,或许从长远来看,它仍然可以完成一些你需要做的工作。

如果你做得对,你将能够参与你创建的小程序的一部分,并在你真正的大型复杂项目中使用它。因此,我决定写这篇文章。这样你就会明白,一个有时极其复杂、需要不同层次知识的程序,并不是凭空创造出来的。

它总是分阶段、分功能部分创建的,然后将部分想法用于更大的解决方案中。这正是我们在回放/模拟器系统中要做的事情。这里描述的服务的工作方式为我们提供了实现主要系统所需的一切。

请注意,如果没有本文允许我们进行的适当分析和研究,我们不太可能以最小的努力使模块在回放/模拟器系统中正常工作。我们必须对代码进行大量调整和更改,但它们都集中在一点上:C_Replay 类。但是,如果不知道在哪里、如何和以何种方式调整 C_Replay 类,我们就会走进死胡同,使得我们在这里看到的内容的实现变得极其困难。

现在我们知道系统实际上将如何运行,我们需要做的就是对 C_Replay 类进行一些小的修改(我希望),以便第 47 至 64 行的循环可以成为我们系统代码的一部分。但有一个小地方:与我们在此处看到的不同,在回放/模拟器系统中,我们将执行计算以遵循图表中已经显示的内容。但这将在后续文章中讨论。花点时间研究本文中显示的内容,了解和研究系统的工作原理,以便更好地理解将进一步介绍的所有内容。我们将使回放/模拟器服务使用上一篇文章视频中展示的模块。

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

附加的文件 |
Anexo.zip (420.65 KB)
最近评论 | 前往讨论 (3)
Levison Da Silva Barbosa
Levison Da Silva Barbosa | 20 7月 2024 在 14:07
非常好的课程。
Alexey Viktorov
Alexey Viktorov | 20 12月 2024 在 18:10

非常有趣的是,这篇文章今天才发表

而这篇文章是在七月份发表的


Andrey Dik
Andrey Dik | 22 12月 2024 在 10:21
Alexey Viktorov #:

非常有趣的是,这篇文章怎么会在今天发表呢?

而这篇文章是在七月份发表的

这是文章的译文,评论是同步的。
使用MQL5和Python构建自优化EA(第二部分):调整深度神经网络 使用MQL5和Python构建自优化EA(第二部分):调整深度神经网络
机器学习模型带有各种可调节的参数。在本系列文章中,我们将探讨如何使用SciPy库来定制您的AI模型,使其适应特定的市场。
您应当知道的 MQL5 向导技术(第 22 部分):条件化生成式对抗网络(cGAN) 您应当知道的 MQL5 向导技术(第 22 部分):条件化生成式对抗网络(cGAN)
生成式对抗网络是一对神经网络,它们彼此相互训练,以便结果更精准。我们采用这些网络的条件化类型,作为我们正在寻找的可选项,应用于智能信号类之内预测金融时间序列。
神经网络变得简单(第 94 部分):优化输入序列 神经网络变得简单(第 94 部分):优化输入序列
在处理时间序列时,我们始终按其历史序列使用源数据。但这是最好的选项吗?有一种观点认为,改变输入数据顺序将提高训练模型的效率。在本文中,我邀请您领略其中一种优化输入序列的方法。
神经网络变得简单(第 93 部分):频域和时域中的自适应预测(终篇) 神经网络变得简单(第 93 部分):频域和时域中的自适应预测(终篇)
在本文中,我们继续实现 ATFNet 模型的方式,其在时间序列预测内可自适应地结合 2 个模块(频域和时域)的结果。