English Русский Español Deutsch 日本語 Português
preview
从头开始开发智能交易系统(第 31 部分):面向未来((IV)

从头开始开发智能交易系统(第 31 部分):面向未来((IV)

MetaTrader 5交易 | 10 一月 2023, 10:27
1 884 0
Daniel Jose
Daniel Jose

概述

在文章“从头开始开发智能交易系统(第 29 部分)”中,从 EA 中删除了 Chart Trade 之后,我们把 Chart Trade 面板变成了一个指标。 如何做到这一点,以及如何调整和维护保持指标工作所需的函数,在第 30 部分中进行了讲述。 这是可能的方法之一,尽管实际上还有其它具有优、缺点的方式,但我们会在下次再研究它们。

故此,我们仍要从 EA 中删除一些东西。 我们现在就删除它,本文将是本系列的最后一篇。 这次要删除的是声音系统。 如果您没有关注过之前的文章,可能会感到困惑。

为了理解整个过程是什么样子(因为它包含许多需要解释的东西),我们基于前一篇文章,使用近乎相同的模型。 这会令解释简单易懂,即使对于那些不是专业程序员的人来说也是如此。 我们还会把系统搞复杂一点,只是为了让事情变得有趣一点。

这次的新部分是声音系统。 它不再作为 EA 的一部分。 但对于未来这样做能提供许多益处。 不过,我们不要冒进,因为重要的是理解这里将会发生什么。 这篇文章相对较短,但很有趣。


声音服务的概述

所有这些变化都会驱使您发狂。 但请相信我,这个思路不会驱使您发疯,也许只是有点困惑,但也显示出有时微小的改变就能产生巨大的差异,并令 MetaTrader 5 平台上的操作更愉快。 与此同时,您将看到所有这些动作如何开启事物的调制。

通过这种方式,您能够选择自己需要什么和不需要什么。 如果确实用到了某些东西,您在以后能够为其添加改进,令其功能更加实用和赏心悦目。 这不需要针对不久前创建的内容进行大改或重新编程。 思路始终在于重用

功能之一是声音系统。 把该系统留在 EA 中似乎是一个好主意。 从某种意义上说,该系统不会干扰整个 EA 的运行。 但如果我们将其从 EA 中删除,并在它们之间实现一些通信,您将看到以一种非常简单的方式使用声音警报,就如同它是一个声音库一样。 此解决方案将非常有用。

仅在 EA 内设置警报系统毫无意义。 在指标甚至在特定时间运行的脚本中拥有一套声音系统可能很有用。 因为这有助于分析。 故此,MetaTrader 5平台在分析层面能够变身一个真正的异兽,您可在其中进行大量计算,从而在极其特定的时刻更出色地分析行情,无论是入仓还是平仓。 所有这些都可以毫不费力地完成。

有人也许会说:“但我可以将所有声音添加到 MQH 文件(头文件)当中,并把它们嵌入可执行文件内,从而获得所需的行为”。对的,您可以。 但请考虑以下场景:随着时间的推移,该 MQH 文件即会增长,且随之增长,其内一些较旧的程序也许会与头文件(MQH)不兼容。 如果您必须重新编译此类旧文件,就会遇到这个问题。 如果您创建一个模块化系统,其中进程之间存在通信协议,则您可任意扩展平台的功能,同时保持与旧程序的兼容性。

这就是修改的原因:展示出您如何创建和利用任何可能的途径。 我从 EA 中摘出这些东西来证明这一点,同时令事情尽可能接近原始行为。

在上一篇文章中,我展示了如何重新创建 Chart Trade,令其行为与集成到 EA 内时的行为相同。 然而,将其从 EA 中删除后,有必要创建某种方式,令其能继续以相同的模式工作。 我向您展示的方式是众多可能之一,虽然它不是最好的,但它有效。 每个解决方案都需要正确知晓事物的一般运作方式。 有时,限于一个仅有的思路模型无助于解决特定状况,对比明显。 严格说来,由于缺乏认知,许多人认为不可能做到某事,或者他们说系统有局限,而事实上,局限在于负责规划和实施解决方案的人员缺乏认知。

我们在实现无需用到任何结构来存储数据的订单系统时见识到这一点。 许多人认为这是不可能做到的事情,没有途径能做这样的事情。 但我已展示出这是可能的。 重要的是知道和理解您在做什么。 第一步是了解每种解决方案的局限性。

因此,我们来学习如何令声音系统尽可能模块化,请记住,随着系统的进一步成长,我们亦将扩展其功能。

首先,除了需要扩展功能的情况外,我们不会触及 C_Sound 类。 所以,这个类不会有大的变化。 事实上,在此阶段,这个类将保持不变,然而,我们需要对系统进行一些小的补充。 其中首先是如下所示的头文件:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableAlert "Sound Alert"
//+------------------------------------------------------------------+


您可能认为我们将在 EA 中用到此文件,但没有... EA 不会用到此文件,至少现在不会。 它会用到另一个文件,我们将在后面看到。

之后,我们能创建一个文件,该文件将作为声音服务。 它展示在下面的代码当中:

#property service
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Sounds.mqh>
#include <NanoEA-SIMD\Interprocess\Sound.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        union u00
        {
                double value;
                struct s00
                {
                        uint    i0,
                                i1;
                }info;
        }u_local;
        C_Sounds Sound;
        
        while (!IsStopped())
        {
                Sleep(500);
                if (GlobalVariableGet(def_GlobalVariableAlert, u_local.value))
                {
                        GlobalVariableDel(def_GlobalVariableAlert);
                        if (u_local.info.i1 == 0) Sound.PlayAlert((C_Sounds::eTypeSound)u_local.info.i0);
                        else Sound.PlayAlert(u_local.info.i1);
                }
        }
}
//+------------------------------------------------------------------+


此服务监视 MetaTrader 5 全局变量。 一旦任何脚本、EA 或指标启动了头文件中声明的变量名称,它就会播放指定的声音,无论它是什么、以及何时发生。

您需要做的就是指定要播放文件的索引。 基于上述结构,您就能够播放总共 4,294,967,295 种不同的声音,这个数字仅针对外部文件。 您可以播放相同数量的内部声音,故您可以做很多事情。

为了让系统知道要播放哪种声音类型,它会检查 u_local.info.i1 变量的值:如果值为 0,则要再现的声音将嵌入到服务文件中,声音的索引将由 u_local.info.i0 变量指示,但此值表示 C_Sound 类内部的枚举器。

现在我们可以编译服务,并运行它。 一旦满足上述条件,服务就会执行其工作,请记住,当全局变量由服务捕获时,它就被删除,如此即可在其它时间使用它。

在我们更进一步之前,我们思考一下。 不同于 Chart Trade 指标,它仅与 EA 通信,而声音系统可与 MetaTrader 5 平台中的任何类型的程序通信。 为了播放所需的声音,您需要设置变量的值,该值将始终为双精度型。

您也许认为这很容易,但尝试一下,您会发现它并非如此。 甚至,您每次都必须持续用正确的名称创建全局变量。 因此,每次要播放以前保存的声音时,您都必须做很多工作。

但有一个实用的解决方案可以避免所有这些麻烦。 因为它太好了,故在这个早期阶段,我们在最基本的层面上采用了该解决方案。 为了看到它是如何做到的,我们进入下一个主题。


创建访问声音服务的函数库

创建函数库的原因在于它会以某种方式令我们的生活更轻松。 无论如何,但它会让我们的生活更轻松。 在前面的主题中,我提到当程序访问声音服务时,我们不需要知道全局变量的名称,其会访问相应的服务。 也许听起来很奇怪,但在进程之间传递信息的最佳途径就是在系统里加入一层。 这一层就是函数库。

这些函数库将“隐藏”流程之间数据建模的复杂性,这样您就不再担心建模应该采用哪种形式。 您只需关心调用本身和预期的结果。

创建函数库时只关心 2 个问题:

  1. 明确声明将导出的函数。
  2. 尽可能隐藏内部建模的复杂性,这样函数库用户就不需要知道发生了什么。 用户应该只看到传入的数据和结果。

那好,从用户的角度来看,函数库中设计的任何过程或函数,其行为都非常简单。 但在内部,操作级别可能会极其复杂,才能导致最终结果。 但使用函数库的程序员不需要知道其中发生了什么。 重要的是要知道所提供的结果是否正确。

那么,我们来看一下我们的函数库,它将隐藏声音服务中所用的数据建模。 每个程序都应该报告两件事:第一是声音是内部的还是外部的;第二是声音的索引。 听起来很复杂? 我们来看看函数库内部这些调用的代码:

void Sound_WAV(uint index) export { Sound(0, index); }
void Sound_Alert(uint index) export { Sound(index, 0); }


这两个函数隐藏了数据建模中的所有复杂性。 请注意,我们用关键字 export 指示编译器创建指向这些函数的符号链接。 它们实际上是过程,因为它们不返回任何值。 以这种方式,它们就可在文件外部可见,就如同此文件是 DLL。

但如果您查看代码,您不会找到任何名为 Sound 的函数。 它在哪里? 它位于函数库本身中,但在函数库外不可见。 请参见以下完整的函数库代码:

//+------------------------------------------------------------------+
#property library
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Interprocess\Sound.mqh>
//+------------------------------------------------------------------+
void Sound_WAV(uint index) export { Sound(0, index); }
//+------------------------------------------------------------------+
void Sound_Alert(uint index) export { Sound(index, 0); }
//+------------------------------------------------------------------+
void Sound(uint value00, uint value01)
{
        union u00
        {
                double value;
                struct s00
                {
                        uint    i0,
                                i1;
                }info;
        }u_local;
        
        u_local.info.i0 = value00;
        u_local.info.i1 = value01;
        GlobalVariableTemp(def_GlobalVariableAlert);
        GlobalVariableSet(def_GlobalVariableAlert, u_local.value);
}
//+------------------------------------------------------------------+


请注意,Sound 过程将包含装配合格数值所需的所有必要复杂性,如此服务即可执行来自脚本、指标或 EA 请求的任务。 但改换将此段代码放在访问该服务的程序当中,我们仅用简单的调用,这令程序调试更舒适、且不繁琐。

为了理解其工作原理,我们来看一个示例脚本:

#property copyright "Daniel Jose"
#property script_show_inputs
#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import
//+------------------------------------------------------------------+
input uint value00 = 1;         //Internal sound service...
input uint value01 = 10016;     //Sound in WAV file...
//+------------------------------------------------------------------+
void OnStart()
{
        Sound_WAV(value01);
        Sound_Alert(value00);
}
//+------------------------------------------------------------------+


查看上面的代码。 其无必要知道实现了什么类型的通信,声音事件发生的时间和地点 — 它可以发生在任何地方,在平台内、在操作系统内,甚至来自远程,都没关系。 我们仅需通知发往系统的声音来自内部还是外部,以及其索引。

现在,在继续之前,我希望您做一个实验。 交换功能。 在这种情况下,我们先运行 Sound_WAV,然后运行 Sound_Alert。 运行它,并查看结果。 接下来,改变顺序:运行 Sound_Alert,然后是 Sound_WAV,并查看结果。 对于那些还不理解的人,在第一种情况下,OnStart 事件内的代码如下所示:

void OnStart()
{
        Sound_WAV(value01);
        Sound_Alert(value00);
}


而在第二种情况下就像这样:

void OnStart()
{
        Sound_Alert(value00);
        Sound_WAV(value01);
}


虽然看起来很愚蠢,但为了理解一些事情,这个实验是必要的。 不要忽视它,看到的结果会很有趣。

现在我们已经了解了应该添加到程序中的内容,以便能够播放声音,我们简单地需要添加以下代码:

#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import


每当您需要播放声音时,只需以正确参数值调用正确函数,而无需担心它将如何完成。 系统本身将确保一切工作完美。 在我们的 EA 中,代码如下所示:

// ...

#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#include <NanoEA-SIMD\Interprocess\Sound.mqh>

// ...

问题出现了:高亮显示的代码在该处起什么作用? 我们不能简单地调用函数库吗? 是的,但我们可用枚举来识别数码是否为声音,就像以前一样,除非您使用的声音或警报数量非常少,否则仅通过查看代码很难理解每个代码所代表的内容。 出于这个原因,头文件 Sound.mqh 里得到了一个附加内容,在下面的代码中高亮显示:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableAlert "Sound Alert"
//+------------------------------------------------------------------+
enum eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END};
//+------------------------------------------------------------------+


因此,我们最终可以得到这样的代码:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound_Alert(TRADE_ALLOWED);
                return INIT_FAILED;
        }

// ... The rest of the function

它比之用索引替代枚举,相同代码更具代表性:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound_Alert(0);
                return INIT_FAILED;
        }

// ... Rest of the code


哪一个更容易理解?

完成所有这些工作后,您将在平台中拥有信息流,如下图所示:

如您所见,无论谁提供信号,我们都将始终拥有相同的目的地。


结束语

尽管这似乎没什么大不了的,但本文中展示的内容对于提高代码、程序和信息的可用性大有帮助。 随着您的编程越来越少,工作效率越来越高,与此同时您的代码变得更加安全和稳定,这是因为重用代码在许多不同的场景中反复测试。

在此,我们看到了另一条途径,它与上一篇文章中看到的途径不同,但即便如此,这条途径也能改进很多,为我们提供了许多新的可能性。 但我们将在另一个系列中看到这一点,您将学习如何令 MetaTrader 5 中的程序和项目更驱模块化,具有比此处展示的任何方法更高的安全性、可用性和稳定性。

但主要和最重要的是明白如何设计和运用一些不同的解决方案,因为在某些情况下,出于某种原因,某种解决方案会比另一种更好。

在附件中提供了所有代码。 对于那些不太习惯这种使用函数库编程方式的人,我建议好好研究 EA 开发的这一阶段。 不要把您今天能做的事情推迟到明天,因为明天可能不会如您期望的方式而来。

本文完成了这个 EA 开发阶段。 很快,我将介绍另一种类型的素材,专注于另一种状况,其中所涉及的复杂性要高得多,但无论如何依然会更有趣。 给大家一个大大的拥抱,以后见。


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

附加的文件 |
EA_-_j_Parte_31_f.zip (14533.52 KB)
学习如何基于相对活力(Vigor)指数设计交易系统 学习如何基于相对活力(Vigor)指数设计交易系统
我们系列中的新篇章,介绍如何基于最流行的技术指标设计交易系统。 在本文中,我们将学习如何基于相对活力(Vigor)指数指标来做到这一点。
从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标? 从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?
今天我们将再次用到 Chart Trade,但这回它作为一个图表上的指标,或许也可能不在图表上出现。
DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能 DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能
在本文中,我将在 TabControl WinForms 对象中放置滚动标题控件的按钮,以防标题栏不适配控件的尺寸。 此外,我还将实现单击裁剪过的选项卡标题时,标题栏的平移。
从头开始开发智能交易系统(第 29 部分):谈话平台 从头开始开发智能交易系统(第 29 部分):谈话平台
在本文中,我们将学习如何让 MetaTrader 5 平台谈话。 我们如何才能让 EA 更有趣呢? 金融市场交易往往过于无聊和单调,但我们能够令这项工作少些无趣。 请注意,对于那些经历过上瘾等问题的人来说,这个项目可能是危险的。 然而,在一般情况下,它只会让事情聊胜于无。