English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
从头开始开发智能交易系统(第 16 部分):访问 web 上的数据(II)

从头开始开发智能交易系统(第 16 部分):访问 web 上的数据(II)

MetaTrader 5积分 | 9 八月 2022, 13:22
1 052 0
Daniel Jose
Daniel Jose

概述

在上一篇文章“从头开始开发智能交易系统(第 15 部分):访问 web 上的数据(I)”中,我们曾介绍过利用 MetaTrader 5 平台从专业网站访问标记数据的方法,及其背后的全部逻辑和思想。

在那篇文章中,我们研究了如何访问这些站点,以及如何从中查找和检索信息,以便在平台中加以使用。 但它并未就此结束,因为简单地捕获数据没有多大意义。 最有趣的部分是学习如何从平台获取这些数据,并在智能交易系统里加以运用。 能这样做的方法并不随处可见,因此如果不了解和理解 MetaTrader 5 中所提供的函数,就很难实现。


规划和实现

如果您尚未阅读并理解前一篇文章,我建议您先去阅读并尝试理解其中的所有概念,因为在此我们将延续这个主题 — 我们将研究大量内容,解决一系列问题,最终我们将找出一个亮丽的解决方案,且因为我们会以尚未探索的方式来使用 MetaTrader 5。 我这样说是因为很难找到平台中某些功能的使用链接,但这里我将尝试解释如何使用这些资源之一。

所以,我们做好准备,开始工作。


1. 通过智能交易系统访问互联网数据

这是在该系统中实现的最有趣的部分。 尽管这是一件简单的事情,但如果规化不当,它也会变成迄今为止最危险的。 危险在于它会令 EA 等待服务器的响应,哪怕只是片刻。

该逻辑如下图所示:

我们来看看 EA 如何与包含我们所需捕获信息的 web 服务器直接交互。 下面您可以看到一个完整的代码示例,其工作原理与此完全相同。

#property copyright "Daniel Jose"
#property version "1.00"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

如果您仔细观察,我们就可以看到代码与前一篇文章中创建的代码完全相同,但现在该代码是 EA 的一部分,并且已经为此进行了调整,即,如果已经在工作的部分,那么在此让它继续工作。 不同之处在于 EA 包含一个新的条件,这意味着代码将每秒执行一次,也就是说,EA 将每秒向所需的 web 服务器发出一个请求,并等待响应。 然后,它将提供捕获的数据,并返回到其它内部函数。 该循环将在 EA 的整个生存期内重复。 执行的结果如下所示。


虽然它确实能以这种方式完成,但我不推荐这种做法,因为 EA 会在等待服务器响应时陷入停顿,即便只是片刻 — 这可能会危及平台的交易系统,以及 EA 本身。 另一方面,如果您有兴趣学习这种方法,您可以通过本系统学到很多东西。

但是,如果您有一个本地服务器,可以在互联网和平台之间转发信息,也许这种方法就足够了。 在这种情况下,如果系统发出请求,则会发生以下情况:本地服务器即使还没得到任何信息,也能快速响应,这将为您省下后续多步。

现在,我们来研究执行此类任务的另一种方式,它更安全一点。 鉴于我们将使用 MetaTrader 5 线程系统来实现一些最低层次的安全性,并避免 EA 受到远程 web 服务器条件的影响,因此我们可以挂机等待远程服务器响应。 我们将为 EA 创造额外的条件,令其能够了解正在发生的事情,从而能够从 Web 上收集信息。


2. 创建通信信道

一种更好、更简单的在线收集数据,并在 EA 中加以运用的方式是信道。 虽然它能工作,但在某些情况下,它并不十分契合我们,因为使用此类信道存在诸多限制。 但至少 EA 能够从所访问的 web 上收集信息,且不必等待来自远程服务器的响应。

上面曾提到过,解决这个问题的最简单方式是数据转发:创建一个本地服务器,下载数据,并将其提供给 MetaTrader 5 平台。 但这需要一定的知识和计算能力,并令几乎所有情况复杂化。 然而,我们可以利用 MetaTrader 5 功能创建一个非常类似的信道,这比经由本地服务器进行转发要简单得多。

下图显示了我们将如何推广此类信道。

它是利用对象创建的。 请注意,EA 将会在对象内部查找脚本放置在其中的信息。 为了理解它实际上是如何工作的,我们来看下面三段完整的代码。 其一是 EA,另一个是包含对象的标题,第三个是脚本。

#property copyright "Daniel Jose"
#property description "Testing Inner Channel"
#property version "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetInfoInnerChannel());
}
//+------------------------------------------------------------------+

下面的代码是我们需要用到的头文件。 请注意,这里的对象声明可在 EA 和脚本之间共享。

//+------------------------------------------------------------------+
//|                                          Canal Intra Process.mqh |
//|                                                      Daniel Jose |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_NameObjectChannel   "Inner Channel Info WEB"
//+------------------------------------------------------------------+
void CreateInnerChannel(void)
{
        long id;
        
        ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE);
}
//+------------------------------------------------------------------+
void RemoveInnerChannel(void)
{
        ObjectDelete(ChartID(), def_NameObjectChannel);
}
//+------------------------------------------------------------------+
inline void SetInfoInnerChannel(string szArg)
{
        ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg);
}
//+------------------------------------------------------------------+
inline string GetInfoInnerChannel(void)
{
        return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT);
}
//+------------------------------------------------------------------+

最后,此处为该脚本。 它将取代在本地创建服务器,并能实际完成服务器的工作。

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        CreateInnerChannel();
        while (!IsStopped())
        {
                SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
                Sleep(200);
        }
        RemoveInnerChannel();
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++);
        for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return (_StopFlag ? "" : szInfo);
}
//+------------------------------------------------------------------+

在此我们有一个 EA 可以看到的对象,它是由脚本创建的。 其思路是 EA 和脚本可在同一个图表中共存,然后该对象将成为 EA 和脚本之间的通信信道。 EA 作为客户端,脚本作为服务器,对象则作为它们之间的通信信道。 因此,脚本负责捕获远程 web 服务器上的值,并将所需的值放入对象当中。 EA 会不时地查看对象中的值(如果对象存在),因为如果脚本尚未运行,对象就不应该可用。 无论如何,EA 查看对象值的时间不会以任何方式与脚本冲突。 如果脚本因等待远程服务器的响应而被阻塞,它不会影响 EA,因为无论脚本做什么,EA 都将继续运行。

虽然这是一个很好的解决方案,但并不完美:问题在于脚本。

为了理解这一点,请观看以下视频,注意每个细节。


一切都工作完美。 这符合期望,因为在开发客户机-服务器程序时,这种解决方案已被广泛应用,我们不希望一个环节阻塞另一个。 换言之,我们利用一个信道在进程之间进行通信。 通常,当它们处于同一环境中时,信道将在内存里创建 — 为此专门分配了一个隔离区域,但该区域是共享的,且对客户端和服务器都可见。 服务器往里添加数据,客户端访问同一区域,从而获取所有存在的数据。 如此,彼此互不依赖,而两者间又是连通的。

我们的思路是使用相同的原理。 但是脚本的操作方式会产生一个问题。 当我们切换时间帧时,脚本将被关闭,即使我们使用无限循环,它也将由 MetaTrader 5 强制关闭。 由于会发生这种情况,我们必须重新初始化脚本,并将其放回图表。 但如果我们需要不断切换时间帧,这就是一个很大问题,更不用说每次都需要在图表上重新启动脚本了。

此外,我们也许忘记检查脚本是否在图表上,并由此导致 EA 代码最终得到错误的信息。我们现在还无法判定脚本是否在图表上。 这可以通过检查脚本是否在图表上来解决。 此项任务并不困难:简单地在对象中编写一段检查脚本最后一次启动时间。 这就可以解决问题。

不过,可以创建更好的解决方案(至少在某些情况下),诚恳地讲,这几乎是理想的解决方案,我们将采用上述的相同概念,只需把我们的脚本替换为服务。


3. 创建一个服务

这是一个极端的解决方案,但由于脚本在每次时间帧变更时都会出现终止问题,因此我们必须要用另一种方式。 但在解决一个问题的同时,我们创造了另一个问题。 无论如何,知道哪些解决方案是可行的,以及如何运用,都是可取的。 但最重要的是要知道每种解决方案的局限性,从而尝试在中间找到一些平衡,从而尽可能以最好的方式解决问题。

编程就是这样的,当我们试图解决一个问题时,我们会创建一个新的问题。

我们的目标是创建类似下图的内容:

虽然这看起来很简单,但其所涉及的资源通常很少被探究。 因此,我将尝试深入了解细节,来帮助任何希望了解更多有关信息的人,如何来使用这些资源。


3.1. 访问全局变量

这一部分研究甚少,起初我甚至想过创建一个 dll 来支持这个函数,但在查看 MQL5 文档后,我发现了它。 问题是我们需要在服务和 EA 之间创建并访问一个公共点。 当我们使用脚本时,这个点是一个对象,但当我们使用服务时,我们不能这样做。 解决方案是使用外部变量,但当我尝试这样做时,性能并没有达到预期。 有关更多详细信息,您可以阅读与外部变量相关的文档。 它解释了该怎么做。

所以,这个思路并不完美,所以我决定使用 dll。 然而,我仍然想学习 MetaTrader 5 和 MQL5,因此通过查看终端,我发现了您可以在下图中看到的内容:

         

这就是我们要寻找的:我们添加了一个变量,来检查如何配置这个过程。 但是,我们只能使用double(双精度)值。 您可能会认为这就是问题所在(虽然这确实是一个限制),但当我们只想发送短消息,对于我们的情况,这就足够了,。 实际上,double(双精度)类型是一个简短的 8 字符字符串,因此我们可以在程序之间传递数值或短消息。

因此,问题的第一部分得到了解决。 MetaTrader 5 提供了在不必创建dll的情况下创建通道的方法,但现在我们又遇到了另一个问题:如何通过程序访问这些变量? 是否可以在程序中创建全局变量 — 在智能交易系统、脚本、指表、或服务的内部? 亦或我们应该只能用终端中声明的那些?

如果我们真的打算采用这个解决方案,这些问题非常重要。 如果无法通过程序使用它们,我们就不得不用 DLL。 但这是可能的。 有关更多信息,请参见终端的全局变量


3.2. 利用终端变量交换信息

现在我们已经研究了基础知识,我们来创建一些简单的东西,如此我们就可以测试和理解利用终端变量的过程在实践中是如何工作的。

为此目的,我创建了以下代码。 第一个是头文件:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+

在此,我们简单地定义了全局终端变量的名称,对于将在图形终端上运行的两个进程,该名称必须是相同的。

以下代码则代表欲运行服务。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        double count = 0;
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                GlobalVariableSet(def_GlobalNameChannel, count);
                count += 1.0;
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

其操作很简单:它检查变量是否已经声明,以及 GlobalVariableCheck 正在做什么。 如果变量不存在,它将由 GlobalVariableTemp 函数临时创建,然后将从 GlobalVariableSet 函数接收一个值。 换言之,我们正在测试、创建和编写信息,服务扮演一个服务器角色,就像一个脚本,只是我们还没有访问网站。 首先,我们应该了解系统是如何工作的。

下一步是创建一个客户端,在我们的案例中,该客户端是一个智能交易系统:

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        double value;
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, value);
                Print(value);           
        }
}
//+------------------------------------------------------------------+

代码很简单:EA 每秒钟都去检查变量是否存在。 如果它存在,EA 将调用 GlobalVariableGet 读取该值,并将该值输出到终端。

我们来看看这个过程是如何实现的。 首先,我们运行服务。 它完成如下操作:

但也可能是另一种场景,即当服务停止,并重新启动时。 在这种情况下,我们将按照以下步骤进行:

之后,我们检查终端变量,并得到以下结果:

请注意,系统显然正在工作,但目前我们必须将 EA 放在图表上,获取数值,从而确认信道上的连接。 因此,将 EA 放在图表上后,我们得到以下结果:

全部就这样,系统按我们想要的方式工作。 我们实际上有一个模型,如下所示。 这是一种典型的客户机-服务器形式,这正是我们想要做的。 鉴于我前面提到的优点,我们正试图实现这种形式。

现在我们只需要添加一个系统来读取和获取 web 上的值。 然后我们就会得到最终的模型进行测试。 这一部分非常简单:把我们从一开始就用的代码挪到到服务当中。 为了执行测试,我们只需要修改服务器文件以便从网站读取值,并发布该值,供客户端读取。 新的服务代码如下。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        string szRet;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D);
                GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet));
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

我们现在就有了一个系统,如下图所示:

所以,它已经准备就绪,我们能得到以下结果。 甚至,改变时间帧也不再是一个问题。



结束语

今天,我们研究了一些 MetaTrader 5 的功能,这些功能很少被探究。 其中之一是通信信道。 然而,我们仍然没有充分利用这一功能。 但我们可以更加深入 — 我们将在下一篇文章中会这样做。 到目前为止,我们在本系列中看到的一切,都向我们展示了在 MetaTrader 5 平台上可以完成很多事情。 只需选择一条路径,并继续前进,直到我们获得所需的结果;尽量了解每一条可能路径的限制、益处和风险都是很有用的。


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

附加的文件 |
Script_e_EA.zip (3.03 KB)
Serviso_e_EA.zip (2.39 KB)
神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类 神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类
我们继续研究聚类方法。 在本文中,我们将创建一个新的 CKmeans 类来实现最常见的聚类方法之一:k-均值。 在测试期间,该模型成功地识别了大约 500 种形态。
视频:简单自动交易 — 如何利用 MQL5 创建简单的智能交易系统 视频:简单自动交易 — 如何利用 MQL5 创建简单的智能交易系统
在我的课程中,大多数学生认为 MQL5 真的很难理解。 除此之外,他们还在寻找一种直接的方法来把一些过程自动化。 那么阅读本文中归纳的信息,就能立刻发现如何利用 MQL5 开始运作。 即使您以前从未接触过任何形式的编程。 即使您无法领会之前您所观察到的插图的情况下。
学习如何基于 OBV 设计交易系统 学习如何基于 OBV 设计交易系统
这是一篇新文章,将针对初学者继续我们的系列,介绍如何基于一些流行指标设计交易系统。 我们将学习一个新的指标,即能量潮(OBV),我们将学习如何使用并基于它来设计交易系统。
神经网络变得轻松(第十四部分):数据聚类 神经网络变得轻松(第十四部分):数据聚类
我的上一篇文章已经发表一年多了。 这令我有了大量时间考虑修改思路和发展新方法。 在这篇新文章中,我想转移一下以前使用的监督学习方法。 这次我们将深入研究无监督学习算法。 特别是,我们将考虑一种聚类算法 — k-均值。