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

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

MetaTrader 5示例 | 22 八月 2022, 08:02
1 014 1
Daniel Jose
Daniel Jose

概述

在上一篇文章从头开始开发智能交易系统(第 16 部分):访问 web 上的数据(II)中,我们讨论了从 web 抓取数据的问题和后果。 我们还研究了如何在智能系统中使用它,并讨论了三种可能的解决方案,每种方案都有其优缺点。

在第一种解决方案中,其意味着直接经由智能系统抓取数据,我们研究了一个可能的问题,与服务器响应缓慢有关。 我们还提到了这可能对交易系统造成的后果。

在第二种解决方案中,我们实现了基于客户机-服务器模型的信道,其中 EA 充当客户机,脚本充当服务器,对象充当信道。 该模型一直表现优越,直到在您决定更改时间帧那一刻,而它确实在这方面变得不方便。 尽管事实如此,它仍然是最好的系统,因为采用客户机-服务器模型可以确保 EA 不会因等待远程服务器响应而阻塞 — 它简单地读取对象中包含的数据,而不管这些信息来自何处。

第三种也是最后一个解决方案中,我们采用服务改进了客户机-服务器系统。 因此,我们开始使用 MetaTrader 5 平台资源,但很少研究:终端的全局变量。 该方案解决了时间帧变更的问题,而这却是采用脚本的模型的最大缺点。 不过,我们遇到一个新问题:终端的全局变量系统只允许使用双精度型数据。 许多人不知道如何避免这种情况,因此他们通过 MetaTrader 5 提供的通道传递各种信息,例如一段文本。

在本文中,我们将讨论如何绕过此限制。 但不要期待奇迹,因为要让系统按您所希望的方式运行需要大量工作。

这次我们将着手开发一个替代系统。


1. 计划

正如我们所知,我们只能在 MetaTrader 5 提供的通道系统中使用 双精度型变量。 此类型由 8 个字节组成。 您可能会认为这不是非常有用的信息。 但我们来想象计算以下时刻:

计算机系统按字节操作,尽管许多人已经忘记了这个概念。 了解这个系统非常重要。 每个字节由 8 位组成。 1 位是计算系统中的最小数字。 该语言中最小且最简单的类型是布尔型,它由一位组成。 这是最简单的基础。

因此,任何信息,无论多么复杂,都将包含在 1 字节内。 同样,无论有多复杂的信息,它总能纳于一个字节之内,该字节由 8 位组成。 当我们连接 2 个字节时,我们得到系统中的第一个复合集。 第一个复合集称为字(WORD);第二个复合集称为双字(DWORD),其内含 2 个字;第三个复合集称为 QWORD,其内含 2 个双字(4 个字)。 这是汇编语言中所采用的命名法,它是所有现代语言的母语,因此大多数系统延用相同的类型。 仅有的区别在于这些类型的命名方式。

我希望直至此刻,您已能领会这些道理。 为了让初学者更容易理解,请查看下图:

          

                         

上面的图像展示了当前可用的主要类型,它们涵盖 1 到 64 位。 您也许会想,“为什么我需要这个解释?”。 了解这些信息对于理解我们在本文中将要做的事情非常重要,因为我们将操纵这些类型,以便能够传递具有不同内部属性的信息。

取决于所采用的语言,这些类型中的每一种都可以获得不同的名称。对于 MQL5,它们显示在下表中:

名称 字节数量 基于汇编语言的名称(上图) 
bool(布尔)   仅用 1 位;一个字节可以有 8 个布尔值。  仅用 1 位;一个字节可以有 8 个布尔值。
char(字符) 1
 Byte(字节)
short(短整数) 2  Word(整数字)
int(整数)  4  DWord(双字)
long(长整数) 8  QWord(四字)

此表涵盖有符号整数,有关 MQL5 中的更多详细信息,请参见整数型,其中定义了其它名称。 接下来,实类型(real)与整数型有一些相似之处,但有自己的内部格式和样式。 格式化和建模的示例可以在双精度型数字处看到,但基本上它将与下表匹配:

名称 字节数量 基于汇编语言的名称(上图) 
Float(浮点) 4  DWord(双字)
Doble(双精度) 8  QWord(四字)

值得注意的是,浮点和整数模型使用相同的数据根基,但长度不同。 现在我们到了我们真正感兴趣的地方。 如果您理解了逻辑,您最终可以得出以下结论,如下图所示:

QWORD 是 8 个字节,故此 “double(双精度)”允许放置 8 个信息字节。 例如,您可以将 8 个可打印字符传递到终端全局变量中,您将获得服务和 EA 之间的连接结果,如下所示。

数据都可以,我认为这个想法本身是可以理解的。 最大的细节是,如果消息包含超过 8 个可打印字符,则必须将其分割成更多部分。 但是,如果要非常快速地传递信息,譬如说,在一个周期内,那么您必须使用尽可能多的全局终端变量来在一个周期中传输消息。 然后需要将它们粘合在一起以恢复原始消息。 但是,如果它能以分组形式传递,我们必须为服务器创建一个表单,如此服务才能知道客户端(在本例中为 EA),将读取消息,并等待下一个消息块。

这类问题有多种解决方案。 如果您想了解或实现这些解决方案,您不需要从头开始创建所有内容 — 您可以利用网络通信协议(如 TCP/IPUDP)相同的方式建模,并用全局终端变量将该思路适配到信息传输系统。 一旦您了解了协议是如何工作的,这个任务就不再复杂,而是转化为您所采用的语言技能和知识的问题。 这是一个非常广泛的主题,值得针对每种情况和问题进行单独研究。


2. 实现

既然现在我们理解了我们将要采取的思路,我们就可以进行初步实现,来测试系统如何在服务和 EA 之间传输信息。 但我们只传递可打印字符。

2.1. 基本模型

我们将使用来自上一篇文章中的系统,需要修改的文件,将从头文件开始。 其新内容在以下代码中完整展示:

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+


头文件是基本。 它包含全局终端值的声明。 它还具有一个新的结构,一个 union。 union 与 structure 的不同之处在于,structure 数据组合不涉及物理存储空间交错,而 union 总是共享物理存储空间,即较短数据占取较长数据内部的一截。 在前一种情况下,我们使用一个双精度值作为基础,其中包含 8 个字节。 但请注意,我使用了一个系统来捕获长度 sizeof,因此,如果将来双精度变更长的话(这是不可能的),此代码将自动适应它。

我们得到的结果如下:

请注意,这与上面的图片类似,但这是 union 做到的。

下一个要修改的代码是对应于客户端的 EA。 完整代码如下所示:

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


请注意,这里我们调用函数 CharArrayToString 将 uchar 数组转换为字符串。 不过,要注意,我们仍然得到一个双精度值,因为它是唯一可以从终端的全局变量里接收的值。 与之对比,MQL5 中的 sting 遵循 C/C++ 的原则,因此我们不能使用任何扩展字符码,但我们只能自创。 但那是另一回事。 在此,我们不会详细介绍如何做到这一点:您可能希望使用建模数据压缩来突破 8 字节的限制。

但我们仍然需要一个充当服务器的程序。 在我们的例子中,服务器就是一个服务。 下面是测试系统的代码:

//+------------------------------------------------------------------+
#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalNameChannel, loc.value);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+


这是一种简单,但非常有效和实用的方法。

在平台上启动程序,我们将获得以下结果


这也许看起来很愚蠢、毫无重点,但只要有一点创造力,任何人都可以让这个系统变得堪当大任,令它做到他人甚至难以想象的事情。

为了演绎这一点,我们来修改系统,并展示一个非常简单的东西,只是为了引起好奇心和兴趣。 想想对于这样的通信系统,可以发现哪些非常奇特的功能。


2.2. 交换纸条

交换纸条是客户端和服务器之间的信息交换,在此期间,服务器知道客户端想要接收什么信息,因此服务器可以开始生成或查找该信息。

这个概念很容易理解。 但其实现可能是一个相当大的挑战,尤其是在数据建模方面,当通道用于传输数据时,我们只有 8 个字节可用。

2.2.1. 客户端-服务器通信测试

查看服务代码,如下所示:

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc, loc1, loc2;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalValueInChannel, loc.value);
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

注意在新的服务代码(充当服务器)中一些特别有趣的部分。 现在我们有三个变量,替代原来的一个。 它们要做的就是创建一个足够大的通道,以便支持客户端(或在某些情况下是 EA)和服务器(我们的服务)之间的通信。 请注意以下几行:

Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);

这些是客户端发布的数据。 注意,我们使用 2 个变量传递 3 条不同的信息。 但这怎么可能呢? 为了理解这一点,我们需要查看头文件代码,如下所示。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo                      "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint    Position[2];
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+


您也许会认为这个 union 中的每个变量都是彼此独立的。 我建议您看看本文的开头,因为尽管我们有不同名称的变量,但在这里它们被视为一个变量,宽度为 8 字节。 为了更清楚,请查看下面的图片,它准确地反映了正在发生的情况:

此规划图展示出 uDataServer 内部逻辑。

如果您觉得它太复杂,您应该尝试使用 union 来了解它们的实际工作方式,因为它们在编程中非常有用。

但我们还是回到系统上来。 接下来要做的是为客户机 EA 创建代码。 它如下面所见。

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;     //Search
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        uDataServer loc;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc.value);
                Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer)), "  ", GlobalVariableGet(def_GlobalValueInChannel));
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175484;
                                loc2.Position[1] = 176156;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+


注意,在这个 EA 中,我们发送和接收信息,也就是说,我们可以控制服务应该如何工作。 在一个变量中,我们传递一个小字符串,指示服务应该查找什么;而在另一个变量里,我们传递 2 个地址点。

作为响应,服务将返回信息。 但为了理解第一点,请查看下面视频中的结果:



3.1.2.2 - 创建实用版本

现在我们已经看明白了系统的工作原理,我们可以让一些东西真正起作用。 这次我们将从 web 服务器收集信息。 这需要大量的修改,来确保对正在发生的事情有完美的理解。 有时我们也许会想象我们正在接收更新的数据,尽管事实上我们在分析中所用的都是垃圾。 在编程阶段,您必须非常小心,不要让自己暴露在这种风险中。 您可以做的是添加尽可能多的测试,并尝试让系统运行时尽可能报告检测到的任何异常活动。

记住: 信息只有在您信任它时才会对您有用。

首先,我们来编辑头文件,令其看起来像这样:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo              "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
#define def_MSG_FailedConnection        "BAD"
#define def_MSG_FailedReturn            "FAILED"
#define def_MSG_FailedMask              "ERROR"
#define def_MSG_FinishServer            "FINISH"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint            Position[2];
        char            Info[sizeof(double)];
};
//+------------------------------------------------------------------+


高亮显示的部分表示我们将用于报告某些奇怪活动的代码。 您应该使用最多 8 个字符,但您还需要创建一个不太可能由市场创建的序列,这不是一件容易的事情。 即使一切看起来都很好,市场仍有可能生成一个数值,对应于您所用的服务器错误消息序列。 无论如何,您也可以为此目的使用终端的全局变量,这将增加可能的组合数量,从而允许您创建更多的东西。 但我打算尽可能少的采用全局终端变量。 不过,在实际情况下,我会考虑它,并可能采用一个变量来指示和报告错误或异常活动。

下一部分是 EA 的完整代码。

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;             //Search
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        ClientServer();
}
//+------------------------------------------------------------------+
inline void ClientServer(void)
{
        uDataServer loc1, loc2;
        string          sz0;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                loc2.value = GlobalVariableGet(def_GlobalValueInChannel);
                sz0 = CharArrayToString(loc2.Info, 0, sizeof(uDataServer));
                if (sz0 == def_MSG_FailedConnection) Print("Failed in connection."); else
                if (sz0 == def_MSG_FailedReturn) Print("Error in Server Web."); else
                if (sz0 == def_MSG_FailedMask) Print("Bad Mask or position."); else
                if (sz0 == def_MSG_FinishServer) Print("Service Stop."); else
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "  ", loc2.value);
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175487;
                                loc2.Position[1] = 176159;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+


高亮显示的行非常重要,应该仔细思考,因为我们确实想知道发生了什么。 正如您所看到的,我们可以告诉用户一些比头文件中创建的序列更详细的信息,从而令编程、以及维护解决方案更轻松。 代码的其余部分没有太大变化。 查看下面的服务代码。

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc1, loc2;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                if (!_StopFlag)
                {
                        GlobalVariableSet(def_GlobalValueInChannel, GetDataURL(
                                                                                "https://tradingeconomics.com/stocks",
                                                                                100,
                                                                                "<!doctype html>",
                                                                                2,
                                                                                CharArrayToString(loc1.Info, 0, sizeof(uDataServer)),
                                                                                loc2.Position[0],
                                                                                loc2.Position[1],
                                                                                0x0D
                                                                               )
                                        );
                        Sleep(1000);
                }
        }
        GlobalVariableSet(def_GlobalValueInChannel, Codification(def_MSG_FinishServer));
}
//+------------------------------------------------------------------+
double 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 Codification(def_MSG_FailedConnection);
        for (int c0 = 0, c1 = StringLen(szTest); (c0 < c1) && (!_StopFlag); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return Codification(def_MSG_FailedReturn);
        for (int c0 = 0, c1 = StringLen(szFind); (c0 < c1) && (!_StopFlag); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return Codification(def_MSG_FailedMask);
        if (_StopFlag) return Codification(def_MSG_FinishServer);
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return StringToDouble(szInfo);
}
//+------------------------------------------------------------------+
inline double Codification(const string arg)
{
        uDataServer loc;
        StringToCharArray(arg, loc.Info, 0, sizeof(uDataServer));
        
        return loc.value;
}
//+------------------------------------------------------------------+


高亮显示的行也很重要 — 服务将发出警告:它不再运行。

那么,当您执行此系统时,将得到以下结果:


结束语

我希望我已经解释清楚了在 MetaTrader 5 平台上研究、搜索和使用 web 数据的相关思路。 我明白这在一开始可能不是很清晰,特别是对于那些在编程方面尚无深厚、广泛基础知识的人,但随着时间的推移,通过纪律和学习,您最终会掌握大部分的材料。 在此,我尝试分享至少一点我所知道的。

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

附加的文件 |
Servi0o_-_EA.zip (10.71 KB)
最近评论 | 前往讨论 (1)
yaxi wu
yaxi wu | 23 8月 2022 在 17:04
该几个系统程序基本不能通过file compilation!
学习如何基于建仓/派发(AD)设计交易系统 学习如何基于建仓/派发(AD)设计交易系统
欢迎阅读本系列的新文章,了解如何基于最流行的技术指标设计交易系统。 在本文中,我们将学习一种新的技术指标,称为建仓/派发指标,并了解如何基于简单的 AD 交易策略设计一款 MQL5 交易系统。
神经网络变得轻松(第十六部分):聚类运用实践 神经网络变得轻松(第十六部分):聚类运用实践
在上一篇文章中,我们为数据聚类创建了一个类。 在本文中,我想分享在解决实际交易任务时应用所获结果会遇到的可能变体。
视频:如何为简单自动交易设置 MetaTrader 5 和 MQL5 视频:如何为简单自动交易设置 MetaTrader 5 和 MQL5
在这个小视频课程中,您将学习如何下载、安装和设置 MetaTrader 5,并实现自动交易。 您还将学习如何调整图表设置,和自动交易选项。 您将完成第一次回溯测试,在本课程结束时,您就会知道如何导入一个可以全天候自动交易的智能交易系统,而无需呆坐在屏幕前。
数据科学与机器学习(第 03 部分):矩阵回归 数据科学与机器学习(第 03 部分):矩阵回归
这一次,我们的模型是由矩阵构建的,它更具灵活性,同时它允许我们构建更强大的模型,不仅可以处理五个独立变量,但凡我们保持在计算机的计算极限之内,它还可以处理更多变量,这篇文章肯定会是一篇阅读起来很有趣的文章。