English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 78 部分):新 Chart Trade(五)

开发回放系统(第 78 部分):新 Chart Trade(五)

MetaTrader 5示例 |
326 0
Daniel Jose
Daniel Jose

概述

在上一篇文章“开发回放系统(第 77 部分):新 Chart Trade(四)中,我尽可能彻底地解释了这个主题,同时尽量不让事情变得过于复杂,尽管这通常是一个相当复杂的话题。当前的问题是:如何开发通信协议?如果您想在不同的应用程序或程序之间传输信息,无论它们是否在同一环境中运行,这都是必不可少的。在我们的具体案例中,我们希望 Chart Trade 指标能够指导 EA 交易采取什么行动。换句话说,当用户告诉 Chart Trade 他们想要卖出时,EA 交易将执行卖出订单。当用户表示想要买入时,EA 交易将下达市场买入订单。

在最近的文章中,我主要解释了为什么我们应该创建 Chart Trade 指标,最重要的是,在尝试编写其他任何代码之前,如何设计消息协议。然而,到目前为止,我们只关注指标。

然而,如果不首先检查位于 EA 交易中的接收方,那么这种解释是不完整的。这是因为 MetaTrader 5 明确禁止指标发送订单、管理头寸或直接与负责与交易服务器通信的系统进行交互。换句话说,指标不能启动、关闭或修改头寸或订单。

有些人可能会说这里存在漏洞,但事实上,情况完全不同。当我们开发另一个不可或缺的工具时,我们将在未来的文章中探讨这一点。然而,就目前而言,我们仍处于建立订单系统的早期阶段。

此时,我们主要关注的是不同的:了解 EA 交易如何知道正在发生的事情。这很关键,因为用户永远不会直接与 EA 交易交互。相反,用户将只与 Chart Trade 指标进行交互。Chart Trade 反过来必须向 EA 交易传达指令。毕竟,如果负责实际向市场发送请求的组件(无论是开仓还是平仓)不知道发生了什么,那么仅靠指标是无用的。请记住:只有 EA 交易才有权在 MetaTrader 5 中执行这些操作,没有其他程序可以做到这一点。

所以,本文的核心问题是: 


EA 交易如何理解 Chart Trade?

让我们从这个问题开始。如果您还不确定这是如何工作的,我建议您重新阅读上一篇文章,在那里我解释了如何设计消息协议。这个背景是必不可少的,因为在这里我们将看到如何让“魔法”发生。Chart Trade 指标和 EA 交易都不知道对方的存在,并且它们并不需要这样,只要双方相信信息能够被传达和理解就足够了。 

即使不知道图表上有 EA 交易,Chart Trade 指标仍然可以向其传达用户的意图。这是一个引人入胜的观察过程。

更重要的是,一旦你了解了这一机制,你将能够为 MetaTrader 5 设计更多功能的程序和应用程序。而这些知识远远超出了 MetaTrader 5 本身。

是的,亲爱的读者,这种程序或进程之间的消息交换在 Windows、Linux、macOS 等操作系统中也被广泛使用。每个现代系统都依赖于这一原则:开发几个可以相互通信的较小程序,从而创建一个广泛且可持续的生态系统。

每个应用程序都可以优化以执行特定任务,但当它们结合在一起时,它们几乎可以完成任何事情,在实施、维护和改进方面的成本要低得多。

从现在开始,我鼓励您放弃构建维护或更新繁琐的大型、复杂、一体化应用程序的想法。相反,考虑创建更小、更简单的程序。这种简单性带来了灵活性,使增强或改进应用程序变得更加容易。对许多人来说,这可能是新的。但请相信我,大多数行业都是这样运作的。没有人再构建完全单片的系统了,因为这样做不仅昂贵,而且非常不切实际,尤其是在需要修改或改进它们的时候。

更简单的应用程序更容易调试、调整或优化。因此,EA 交易将被编程为仅处理 MetaTrader 5 要求其处理的内容:发送和修改订单和头寸。其他一切都将委托给其他程序和应用程序。

为了真正理解 EA 交易如何解释 Chart Trade,让我们首先创建一段非常简单的代码。我的意思确实是简单,在这个阶段,我们不会发送订单、平仓或撤销交易 —— 目前还不会。现在这样做只会使事情变得不必要地复杂化。

我们需要的是最简单的例子,这样你就可以理解接收方是如何工作的。这将使您更清楚地了解消息协议如何允许 Chart Trade 指标(不知道 EA 交易的存在)在其中触发操作。

同时,同样不了解 Chart Trade 的 EA 交易仍然可以响应用户请求。请记住:用户永远不会直接与 EA 交易进行交互。

实现此目的的最简单的代码如下所示,它已完整呈现。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Demo version between interaction"
04. #property description "of Chart Trade and Expert Advisor"
05. #property version   "1.78"
06. #property link "https://www.mql5.com/pt/articles/11760"
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. class C_Decode
11. {
12.    private   :
13.       struct stInfoEvent
14.       {
15.          EnumEvents ev;
16.          string     szSymbol;
17.          bool       IsDayTrade;
18.          ushort     Leverange;
19.          double     PointsTake,
20.                     PointsStop;
21.       }info[1];
22.    public   :
23. //+------------------------------------------------------------------+
24.       C_Decode()
25.          {
26.             info[0].szSymbol = _Symbol;
27.          }
28. //+------------------------------------------------------------------+   
29.       bool Decode(const int id, const string sparam)
30.       {
31.          string Res[];
32.       
33.          if (StringSplit(sparam, '?', Res) != 6) return false;
34.          stInfoEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], (bool)(Res[2] == "D"), (ushort) StringToInteger(Res[3]), StringToDouble(Res[4]), StringToDouble(Res[5])};
35.          if ((id == loc.ev) && (loc.szSymbol == info[0].szSymbol)) info[0] = loc;
36.          
37.          ArrayPrint(info, 2);
38.       
39.          return true;
40.       }
41. //+------------------------------------------------------------------+   
42. }*GL_Decode;
43. //+------------------------------------------------------------------+
44. int OnInit()
45. {
46.    GL_Decode = new C_Decode;
47.    
48.    return INIT_SUCCEEDED;
49. }
50. //+------------------------------------------------------------------+
51. void OnTick() {}
52. //+------------------------------------------------------------------+
53. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
54. {
55.    switch (id)
56.    {
57.       case CHARTEVENT_CUSTOM + evChartTradeBuy     :
58.       case CHARTEVENT_CUSTOM + evChartTradeSell    :
59.       case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
60.          (*GL_Decode).Decode(id - CHARTEVENT_CUSTOM, sparam);
61.          break;
62.    }
63. }
64. //+------------------------------------------------------------------+
65. void OnDeinit(const int reason)
66. {
67.    delete GL_Decode;
68. }
69. //+------------------------------------------------------------------+

EA 交易源代码

拜托,你是说上面的代码很简单?老实说,我不明白。在我看来,这看起来非常复杂,我几乎什么都看不懂。我感觉自己在这里彻底迷失了。如果这就是你所说的简单,那么我只能想象复杂的东西会是什么样子!是的,这段代码真的很简单。但它确实使用了你们中许多人通常不会遇到的某些元素。或者,更确切地说,你们中的许多人甚至没有意识到的事情在 MQL5 中是可能的。所以,如果您的情况如此,并且您真的打算将来成为一名专业程序员,请继续关注我。遵循我即将给出的解释,因为事实上事情比看起来要简单得多。该代码简单明了,非常直接地实现了它需要做的事情。

我即将分享的知识将帮助你放慢速度,更清晰地思考,更冷静地处理问题。它也提醒我们,编程可以而且应该是有趣的。如果你认为这只是无聊、困难或过于复杂的工作,那么也许你应该考虑完全做点别的。忘记编程或开发,因为当你编码的时候,你应该真的感觉自己就像糖果店里的孩子 —— 永远不要确定你会先挑哪种糖果。

题外话已经够多了,让我们看看这段代码实际上是如何工作的。前 7 行对于任何人来说都应该不成问题,即使是初学者也应该能够轻松地理解它们。而第 8 行是我们熟悉的,这里我们在代码中包含一个文件。该文件是头文件,包含我们很快将需要的定义。现在,让我们跳过第 10 行到第 42 行之间的代码,这是因为我们稍后会仔细讨论它。首先,我们需要介绍一些小而重要的细节。

这就引出了代码中第一个真正重要的函数。它就是 OnInit,从第 44 行开始。由于第 42 行发生的事情,事情可能与许多人预期的略有不同。在第46行,我们使用 new 运算符分配内存并初始化类 C_Decode。为什么这样分配内存?让编译器来处理它不是更简单吗?是的,这样会更容易。但作为一名程序员,您必须学会控制何时何地使用类。如果你把它留给编译器,你有时可能会使用带有你不期望的值的类。

相信我,这并不罕见。特别是在大型代码库中,类在多个地方声明,混乱是不可避免的。在某些时候,事情会变得错综复杂。通过明确使用 new 和 delete,您可以确保完全控制代码的开始和结束位置。许多初级程序员都难以理解这个概念。尽管听起来很奇怪,但有些人无法清楚地确定他们的代码在哪里结束。甚至从哪里开始。但它确实发生了。

所以当第 46 行运行时,会为 C_Decode 类分配内存,同时调用第 24 行。此行是 C_Decode 类的构造函数。在此构造函数中,仅初始化一个变量。它是交易品种名称,或者换句话说,是运行 EA 交易的资产的名称。现在,请密切注意:在此模型中,未启用跨订单权限。到目前为止,您可能并不完全清楚。但请继续阅读 —— 您很快就会明白为什么这里不允许跨订单操作。只需稍作修改(它们真的很简单),就可以启用跨订单。但就目前而言,别担心。请记住:此代码仅用于演示消息协议如何工作。它实际上并不是为了向交易服务器发送任何请求。

回到 OnInit 函数。执行完第 46 行后,第 48 行返回一个值,表示初始化成功。这很重要,因为如果返回值不是 INIT_SUCCEEDED ,MetaTrader 5 将自动采取措施停用 EA 交易。其中一个步骤是触发 Deinit 事件,该事件调用 EA 交易代码中的 OnDeinit 过程。

如果您的代码不包含此过程,MetaTrader 5 将恢复默认措施。无论哪种方式,您的应用程序都将不再获得 CPU 时间。换句话说,它不会被安排执行。

并且图表上留下的任何属于该应用程序的信息都将被忽略。应用程序在图表上创建和放置对象是很常见的,但是如果 MetaTrader 5 触发 Deinit 并且您的代码不会删除这些对象,它们将保留在图表上。这通常会误导性地显示虚假或无效的数据。

因此,始终执行 OnDeinit 过程是一种很好的做法。通常它的作用很小,但在我们的例子中,它包含一行重要的内容:第 67 行。这里我们使用 delete 操作符来释放我们分配的内存。此时,C_Decode 的析构函数被调用。由于我们没有明确声明析构函数,因此当使用 delete 时编译器会自动提供一个析构函数。因为 delete 操作符需要析构函数。你不必担心这个,但了解其内部工作原理还是值得的。

现在我们已经了解了 EA 交易代码的开始和结束位置,让我们继续。在第 51 行,我们遇到了资产收到的每个报价时调用的过程。此过程对于每个 EA 交易来说都是强制性的。但是,您应该避免在其中放置太多的逻辑。关于构建全自动 EA 交易的系列文章解释了其中的原因。如果你有兴趣创建一个具有一定自动化程度的 EA,我强烈建议你阅读该系列。

它由 15 篇文章组成,首先是创建自动运行的 EA(第 01 部分):概念和结构。阅读这个系列将对你有极大的帮助。事实上,在这个回放/模拟系列中,我们将重复使用自动化 EA 系列中的一些概念。无论如何,不要跳过它。

现在,继续前进:另一个重要过程出现在第 53 行。它就是 OnChartEvent。这就是事情开始变得真正有趣的地方。但为了保持有序,我们将在下一节中对此进行研究。


如何从事件中解码消息

在本回放/模拟器系列的前几篇文章中,我解释了 EventChartCustom 和 OnChartEvent 是如何互连的。我们大量使用此连接来使回放/模拟器服务能够将数据发送到控制指标。这是必需的,以便指标能够准确知道我们在模拟或回放中的位置。如果您还没有读过这些文章(并且刚刚读到本系列的这一部分),我建议您回去查看一下。

一个例子是开发回放系统(第 60 部分):玩转服务(一) 。关于这个主题有七篇文章。阅读所有这些内容不仅会帮助你理解技术细节,还会帮助你理解为什么这种方法是有意义的。

当时,该服务使用数字参数与控制指标进行通信。这是最简单的部分,因为信息可以在 lparam 或 dparam 等参数中轻松获得。但在这里,情况发生了变化。这次我们将使用 sparam 参数。换句话说,消息现在将是一个字符串。这个字符串里面是我们需要的信息。问题在于信息是经过编码的,其编码方式由消息协议定义。我在上一篇文章中解释了这个概念。在这里,我们将重点根据协议解码该消息。

回想一下,在上一篇文章中,我提到消息将包含事件本身。这很重要,因为它允许接收者(在我们的例子中是 EA 交易)验证消息是否已损坏。

由于三种可识别的事件类型使用相同的协议,因此它们都可以得到一致的处理。在第 57 至 59 行中,您将看到 EA 交易必须拦截的这三个事件。由于它们都可以经历相同的解码过程,因此唯一真正相关的行是第 60 行。该行调用从第 29 行开始的代码。现在请密切关注,如果你不明白这个解释,请再读一遍,因为虽然它不常见,但它非常重要。

在第 31 行,我们声明一个局部变量:一个动态数组。由于它是动态的,程序本身可以处理内存分配,所以我们不必担心。然后,在第 33 行,我们调用 StringSplit,它用给定字符分隔的子字符串填充数组。在我们的例子中,该字符是一个问号(?)。为什么选择这个特定的选项?

我在上一篇文章中解释过这一点。选择问号作为分组值的分隔符。在拆分过程中,我们希望获得固定数量的子字符串。如果消息有效(或至少最低限度有效),我们应该得到 6 个字符串。如果 StringSplit 返回更多或更少,则测试失败,我们返回 false。如果正好返回 6 个,我们就继续。

现在,第 34 行是事情变得非常有趣的地方。这不是常见的代码,但也不是外来代码。为了理解它,您需要将它与第 13 行声明的结构一起查看。请密切关注此处。第 34 行的每个元素直接对应于 stInfoEvent 结构中的一个字段。我在这里所做的是故意的。注意 stInfoEvent 中声明的变量的顺序,这非常重要。现在查看第 34 行中值的顺序,哇!它们是匹配的!结果相当于编写以下更明确的代码:

      stInfoEvent loc;
          
      loc.ev = (EnumEvents) StringToInteger(Res[0]);
      loc.szSymbol = Res[1];
      loc.IsDayTrade = (Res[2] == "D");
      loc.Leverange = (ushort) StringToInteger(Res[3]);
      loc.PointsTake = StringToDouble(Res[4]);
      loc.PointsStop = StringToDouble(Res[5]);

代码片段 - 模型 01

这两种方法在功能上是相同的。然而,有一个重要的警告:第 34 行存在风险。上述代码版本避免了这种风险。如果您更改 stInfoEvent 的结构,就会出现这种风险。为什么呢?当编写如第 34 行所示的代码时,更改 stInfoEvent 结构会出现问题,但在编写代码片段所示的部分时则不是。

原因很简单。这可能看起来很愚蠢,但它仍然会引起很多头痛和数小时的努力,试图弄清楚为什么一切都出了问题。假设您决定交换第 19 行和第 20 行声明的变量。使用上面的明确版本,没有任何问题 —— 分配仍然正确映射。

但是由于第 34 行的紧凑分配,一切都变得不合适了。原本应该分配给 PointsTake 的内容最终却分配给了 PointsStop,反之亦然。听起来像是无稽之谈,这不是玩笑,这确实发生了。

这与编译器如何在内存中组织变量有关。这与传统 C 语言编程中有时有意使用的原理相同。开发人员直接操纵内存布局来强制某些行为。但这远远超出了本文的范围。

结论很简单:只有在结构完全定义并最终确定后,才能使用紧凑的分配风格。一旦确定,就不要更改。如果这样做,您必须重新审视并调整以紧凑风格编写的每项分配(如第 34 行)。要查看这一点,请使用此代码测试系统,只需更改第 34 行或 stInfoEvent 结构中的变量声明顺序。

让我们回到代码。在第 35 行,我们检查数据是否与预期值匹配 —— 资产名称和事件类型。如果是的话,我们将值分配给静态数组。请注意:此分配仅用于测试和演示目的。目前,我们只需要将 stInfoEvent 放在一个数组中。原因在于第 37 行。在这一行中,我们使用 MQL5 函数 ArrayPrint 。该函数在终端中显示数组内容。下图显示了一个这样的结果。

图片


结论

正如本文所解释的,EA 交易仍然无法向交易服务器发送请求。这是故意的。在这样做之前,您必须充分了解代码的工作原理 —— 更重要的是,了解 Chart Trade 和 EA 交易之间的通信协议如何运作。没有这个基础,您可能会遇到严重的问题。

尽管如此,以这种方式呈现它也有其好处。在此过程中,我能够解释您将来可能会遇到的概念,但不会立即理解它为什么起作用(或不起作用)。无论如何,我们现在已经为接下来的事情做好了准备。在下面的视频中,您可以看到系统在实践中的表现。所以,在下一篇文章之前,事情会变得更加有趣。


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

附加的文件 |
Anexo.zip (420.65 KB)
基于Python与MQL5的特征工程(第三部分):价格角度(2)——极坐标(Polar Coordinates)法 基于Python与MQL5的特征工程(第三部分):价格角度(2)——极坐标(Polar Coordinates)法
在本文中,我们将第二次尝试将任意市场的价格水平变化转化为对应的角度变化。此次,我们选择了比首次尝试更具数学复杂性的方法,而获得的结果表明,这一调整或许是正确的决策。今天,让我们共同探讨如何通过极坐标以有意义的方式计算价格水平变化所形成的角度,无论您分析的是何种市场。
交易中的神经网络:搭配预测编码的混合交易框架(终篇) 交易中的神经网络:搭配预测编码的混合交易框架(终篇)
我们继续研习 StockFormer 混合交易系统,其结合了预测编码和强化学习算法,来分析金融时间序列。该系统基于三个变换器分支,搭配多样化多头注意力(DMH-Attn)机制,能够捕获资产之间的复杂形态、和相互依赖关系。之前,我们已领略了该框架的理论层面,并实现了 DMH-Attn 机制。今天,我们就来聊聊模型架构和训练。
价格行为分析工具包开发(第10部分):外部资金流(二)VWAP 价格行为分析工具包开发(第10部分):外部资金流(二)VWAP
通过我们的综合指南,掌握VWAP的强大力量!学习如何使用MQL5和Python将VWAP分析集成到您的交易策略中。最大化您的市场洞察力,并改善您今天的交易决策。
MQL5 交易工具包(第 4 部分):开发历史管理 EX5 库 MQL5 交易工具包(第 4 部分):开发历史管理 EX5 库
通过详细的分步方法创建扩展的历史管理 EX5 库,学习如何使用 MQL5 检索、处理、分类、排序、分析和管理已平仓头寸、订单和交易历史。