下载MetaTrader 5

使用WinInet.dll通过网络在终端间进行数据交互

16 十月 2013, 07:17
o_o
0
2 482

MetaTrader 5使用了一系列新的用户界面元素,为用户开辟了独一无二的机会。因此,那些之前不具备的功能现在能被最大限度地使用了。

在本课中我们将学习:

  • 使用基本的因特网技术;
  • 通过服务器在终端间交换数据;
  • 创建一个通用类库,在MQL5环境下操作因特网。

MQL5的代码库有一个脚本例子,它使用wininet.dll动态链接库,并列举了一个请求服务器页面的例子。不过今天我们将更进一步,使服务器不仅仅返回给我们页面,还要发送和存储这些数据,以便再次传输到其他发出请求终端上去。

注意:对于那些通过PHP配置,而没有连接到服务器的用户,我们建议下载 Denwer工具箱,使用它来作为工作平台。并且我们建议你在本地测试时使用Apache服务器和PHP。

要向服务器发送任何请求,我们需要库中的7个主要函数。

InternetAttemptConnect  试图找到并建立一个因特网连接
InternetOpen
初始化结构体来操作WinInet库函数。在使用库中的其他函数前必须先激活此函数。
InternetConnect 打开由HTTP URL或FTP地址确定的资源。返回打开的连接的描述符
HttpOpenRequest 为建立连接的HTTP请求创建描述符
HttpSendRequest 使用创建的描述符发送请求
InternetReadFile 当请求被处理后,读取从服务器返回的数据
InternetCloseHandle 释放已经传输完成的描述符


所有函数以及它们参数的详细描述可以在MSDN的帮助系统中找到。

除了使用统一码调用以及通过链接进行传输外,函数的声明和MQL4中的一样。

#import "wininet.dll"
int InternetAttemptConnect(int x);
int InternetOpenW(string &sAgent,int lAccessType,string &sProxyName,string &sProxyBypass,int lFlags);
int InternetConnectW(int hInternet,string &szServerName,int nServerPort,string &lpszUsername,string &lpszPassword,int dwService,int dwFlags,int dwContext);
int HttpOpenRequestW(int hConnect,string &Verb,string &ObjectName,string &Version,string &Referer,string &AcceptTypes,uint dwFlags,int dwContext);
int HttpSendRequestW(int hRequest,string &lpszHeaders,int dwHeadersLength,uchar &lpOptional[],int dwOptionalLength);
int HttpQueryInfoW(int hRequest,int dwInfoLevel,int &lpvBuffer[],int &lpdwBufferLength,int &lpdwIndex);
int InternetReadFile(int hFile,uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead);
int InternetCloseHandle(int hInet);
#import

//为了清晰起见,我们将使用wininet.h中的常量名
#define OPEN_TYPE_PRECONFIG     0           // 使用默认配置
#define FLAG_KEEP_CONNECTION    0x00400000  // 保持连接
#define FLAG_PRAGMA_NOCACHE     0x00000100  // 页面不缓存
#define FLAG_RELOAD             0x80000000  // 当连接时从服务器接收页面
#define SERVICE_HTTP            3           // 所需的协议

MSDN各函数描述的板块下,有关于这些标识的详细描述。如果你想要见到其他常量和函数的声明,那么你可以下载本文附件中的wininet.h源文件。

1. 创建和删除网络会话的向导

首先我们要做的是建立一个会话,并打开一个同主机的连接。在程序初始化阶段(例如,在 OnInit函数中),一个会话仅能创建一次。或者可以在EA交易系统运行的一开始进行,但非常重要的是,必须确保会话在关闭之前仅仅被成功创建了一次。每次迭代执行 OnStart或者 OnTimer函数时,它也不应被无谓的反复唤醒。每次调用时,要避免频繁调用和创建所需的结构体,这一点是非常重要的。

因此,我们仅使用一个全局的类实例来表示会话和连接描述符。

   string            Host;       // 主机名
   int               Port;       // 端口
   int               Session;    // 会话描述符
   int               Connect;    // 连接描述符

bool MqlNet::Open(string aHost,int aPort)
  {
   if(aHost=="")
     {
      Print("-Host is not specified");
      return(false);
     }
   // 检查终端是否允许使用DLL  
   if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED))
     {
      Print("-DLL is not allowed");
      return(false);
     }
   // 如果会话已经存在,关闭
   if(Session>0 || Connect>0) Close();
   // 记录尝试打开的日志
   Print("+Open Inet...");
   // 如果我们没能检查到网络连接的存在,那么退出
   if(InternetAttemptConnect(0)!=0)
     {
      Print("-Err AttemptConnect");
      return(false);
     }
   string UserAgent="Mozilla"; string nill="";
   // 打开一个会话
   Session=InternetOpenW(UserAgent,OPEN_TYPE_PRECONFIG,nill,nill,0);
   // 如果我们没能打开会话,则退出
   if(Session<=0)
     {
      Print("-Err create Session");
      Close();
      return(false);
     }
   Connect=InternetConnectW(Session,aHost,aPort,nill,nill,SERVICE_HTTP,0,0);
   if(Connect<=0)
     {
      Print("-Err create Connect");
      Close();
      return(false);
     }
   Host=aHost; Port=aPort;
   // 否则所有尝试都成功了
   return(true);
  }

初始化完成后,描述符SessionConnect就能被用在下面所有的函数中了。一但所有的工作都完成且MQL程序被卸载,他们也必须被移除。这是通过使用InternetCloseHandle函数来完成的。

void MqlNet::CloseInet()
  {
   Print("-Close Inet...");
   if(Session>0) InternetCloseHandle(Session); Session=-1;
   if(Connect>0) InternetCloseHandle(Connect); Connect=-1;
  }

注意! 当操作网络函数时,有必要使用InternetCloseHandle释放所有以及由它们派生出来的描述符。

2. 向服务器发送请求并接收页面

作为对本请求的响应,要发送一个请求并接收一张页面,我们将需要用到三个函数:HttpOpenRequest, HttpSendRequest и InternetReadFile。响应请求接收页面的本质和将它的内容保存到一个本地文件中,基本上是一样的。


为了便于操作请求和内容,我们将创建两个通用函数。

发送一个请求:

bool MqlNet::Request(string Verb,string Object,string &Out,bool toFile=false,string addData="",bool fromFile=false)
  {
   if(toFile && Out=="")
     {
      Print("-File is not specified ");
      return(false);
     }
   uchar data[];
   int hRequest,hSend,h;
   string Vers="HTTP/1.1";
   string nill="";
   if(fromFile)
     {
      if(FileToArray(addData,data)<0)
        {
         Print("-Err reading file "+addData);

         return(false);
        }
     } // 读取数组中的文件内容
   else StringToCharArray(addData,data);

   if(Session<=0 || Connect<=0)
     {
      Close();
      if(!Open(Host,Port))
        {
         Print("-Err Connect");
         Close();
         return(false);
        }
     }
   // 创建一个请求描述符
   hRequest=HttpOpenRequestW(Connect,Verb,Object,Vers,nill,nill,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0);
   if(hRequest<=0)
     {
      Print("-Err OpenRequest");
      InternetCloseHandle(Connect);
      return(false);
     }
   // 发送请求
   // 请求的标题
   string head="Content-Type: application/x-www-form-urlencoded";
   // 发送文件
   hSend=HttpSendRequestW(hRequest,head,StringLen(head),data,ArraySize(data)-1);
   if(hSend<=0)
     {
      Print("-Err SendRequest");
      InternetCloseHandle(hRequest);
      Close();
     }
   // 读取页面 
   ReadPage(hRequest,Out,toFile);
   // 关闭所有句柄
   InternetCloseHandle(hRequest); 
   InternetCloseHandle(hSend);
   return(true);
  }

函数MqlNet:: Request的参数:

  • string Verb – 请求的类型“GET”或者“POST”;
  • string Object – 带有传入参数的页面的名称;
  • string &Out – 接收应答的行字符串;
  • bool toFile – 如果toFile=true,那么Out表示接受应答的文件名;
  • string addData - 附加数据;
  • bool fromFile - 如果fromFile = true,那么addData是需要发送的文件的名称。

读取已接收描述符的内容

void MqlNet::ReadPage(int hRequest,string &Out,bool toFile)
  {
   // 读取页面 
   uchar ch[100];
   string toStr="";
   int dwBytes,h;
   while(InternetReadFile(hRequest,ch,100,dwBytes))
     {
      if(dwBytes<=0) break;
      toStr=toStr+CharArrayToString(ch,0,dwBytes);
     }
   if(toFile)
     {
      h=FileOpen(Out,FILE_BIN|FILE_WRITE);
      FileWriteString(h,toStr);
      FileClose(h);
     }
   else Out=toStr;
  }

函数MqlNet:: ReadPage的参数:

  • Int hRequest - 从中读取数据的请求描述符;
  • string &Out – 接收应答的行字符串;
  • bool toFile - 如果toFile=true,那么Out表示接受应答的文件的名称。

将所有这些集中到一起,我们将获得一个操作因特网的MqlNet类库。

class MqlNet
  {
   string            Host;     // 主机名
   int               Port;     // 端口
   int               Session; // 会话描述符
   int               Connect; // 连接描述符
public:
                     MqlNet(); // 类的构造函数
                    ~MqlNet(); // 析构函数
   bool              Open(string aHost,int aPort); // 创建一个会话并打开连接
   void              Close(); // 关闭会话和连接
   bool              Request(string Verb,string Request,string &Out,bool toFile=false,string addData="",bool fromFile=false); // 发送请求
   bool              OpenURL(string URL,string &Out,bool toFile); // 读取页面到文件或变量中
   void              ReadPage(int hRequest,string &Out,bool toFile); // 读取页面
   int               FileToArray(string FileName,uchar &data[]); // 将文件复制到数组中用于发送
  };

这些基本上就是能够满足互联网多样化操作需要的所有函数。考虑它们的使用例子。

例子 1. 自动下载MQL程序到终端的文件夹下。MetaGrabber脚本

让我们从最简单的任务开始测试类的运作:读取页面并将它的内容保存到指定的文件夹下。但是简单的读取页面可能不太有趣,因此为了从脚本的运行中获得些东西,我们让它具备从站点采集mql程序的能力。MetaGrabber脚本的任务是:

  • 分析URL并将其分解为主机,请求和文件名;
  • 发送一个请求到主机,接受和保存文件到终端文件夹 \Files下;
  • 将其从Files下移动到所需的数据文件夹下:
    \Experts, \Indicators, \Scripts, \Include, \Libraries, \Tester(set), \Templates.

我们用MqlNet类来解决第二个问题。我们用Kernel32.dll中的MoveFileEx来完成第三个任务

#import "Kernel32.dll"
bool MoveFileExW(string &lpExistingFileName, string &lpNewFileName, int dwFlags);
#import "Kernel32.dll"

对于第一个问题,让我们做一个简单的服务函数来解析URL链接。

我们要从地址中分解出三行信息:主机,站点路径以及文件名。
例如,在http://www.mysite.com/folder/page.html中

- 主机 = www.mysite.com
- 请求 = / folder / page.html
- 文件名 = page.html

MQL5网站代码库的地址也有一样的结构。例如,https://www.mql5.com/ru/code/79 页面上 ErrorDescription.mq5库的路径看上去如: http://p.mql5.com/data/18/79/ErrorDescription.mqh。此路径很容易通过点击右键并选择“Copy Link(复制链接)”来获得。因此,URl被分隔成两部分,一部分是用来请求的,一部分是要存储的文件名。

- 主机 = p.mql5.com
- 请求 = / data/18/79/5/ErrorDescription.mqh
- 文件名 = ErrorDescription.mqh

这就是下面的ParseURL函数进行线性解析的处理过程。

void ParseURL(string path,string &host,string &request,string &filename)
  {
   host=StringSubstr(URL,7);
   // 移除
   int i=StringFind(host,"/"); 
   request=StringSubstr(host,i);
   host=StringSubstr(host,0,i);
   string file="";
   for(i=StringLen(URL)-1; i>=0; i--)
      if(StringSubstr(URL,i,1)=="/")
        {
         file=StringSubstr(URL,i+1);
         break;
        }
   if(file!="") filename=file;
  }

我们只将为此脚本设置两个外部参数 - URL (mql5文件的路径)以及后续存放的文件夹类型 - 也就是说,你想要将文件存放在哪个终端文件夹下。

因此,我们得到一个简短但非常有用的脚本。

//+------------------------------------------------------------------+
//|                                                  MetaGrabber.mq5 |
//|                                 Copyright © 2010 www.fxmaster.de |
//|                                         Coding by Sergeev Alexey |
//+------------------------------------------------------------------+
#property copyright "www.fxmaster.de  © 2010"
#property link      "www.fxmaster.de"
#property version               "1.00"
#property description  "Download files from internet"

#property script_show_inputs

#include <InternetLib.mqh>

#import "Kernel32.dll"
bool MoveFileExW(string &lpExistingFileName,string &lpNewFileName,int dwFlags);
#import
#define MOVEFILE_REPLACE_EXISTING 0x1

enum _FolderType
  {
   Experts=0,
   Indicators=1,
   Scripts=2,
   Include=3,
   Libraries=4,
   Files=5,
   Templates=6,
   TesterSet=7
  };

input string URL="";
input _FolderType FolderType=0;
//------------------------------------------------------------------ OnStart
int OnStart()
  {
   MqlNet INet; // 在因特网中执行操作的变量
   string Host,Request,FileName="Recieve_"+TimeToString(TimeCurrent())+".mq5";

   // 解析url
   ParseURL(URL,Host,Request,FileName);

   // 打开会话
   if(!INet.Open(Host,80)) return(0);
   Print("+Copy "+FileName+" from  http://"+Host+" to "+GetFolder(FolderType));

   // 获取文件
   if(!INet.Request("GET",Request,FileName,true))
     {
      Print("-Err download "+URL);
      return(0);
     }
   Print("+Ok download "+FileName);

   // 移动到目标文件夹
   string to,from,dir;
   // 如果没有必要移动到其他地方
   if(FolderType==Files) return(0);

   // 来自
   from=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"+FileName;

   // 去到
   to=TerminalInfoString(TERMINAL_DATA_PATH)+"\\";
   if(FolderType!=Templates && FolderType!=TesterSet) to+="MQL5\\";
   to+=GetFolder(FolderType)+"\\"+FileName;

   // 移动文件 
   if(!MoveFileExW(from,to,MOVEFILE_REPLACE_EXISTING))
     {
      Print("-Err move to "+to);
      return(0);
     }
   Print("+Ok move "+FileName+" to "+GetFolder(FolderType));

   return(0);
  }
//------------------------------------------------------------------ GetFolder
string GetFolder(_FolderType foldertype)
  {
   if(foldertype==Experts) return("Experts");
   if(foldertype==Indicators) return("Indicators");
   if(foldertype==Scripts) return("Scripts");
   if(foldertype==Include) return("Include");
   if(foldertype==Libraries) return("Libraries");
   if(foldertype==Files) return("Files");
   if(foldertype==Templates) return("Profiles\\Templates");
   if(foldertype==TesterSet) return("Tester");
   return("");
  }
//------------------------------------------------------------------ ParseURL
void ParseURL(string path,string &host,string &request,string &filename)
  {
   host=StringSubstr(URL,7);
   // 移除
   int i=StringFind(host,"/"); 
   request=StringSubstr(host,i);
   host=StringSubstr(host,0,i);
   string file="";
   for(i=StringLen(URL)-1; i>=0; i--)
      if(StringSubstr(URL,i,1)=="/")
        {
         file=StringSubstr(URL,i+1);
         break;
        }
   if(file!="") filename=file;
  }
//+------------------------------------------------------------------+


让我们在我们最喜欢的版块 https://www.mql5.com/en/code上进行试验。下载下来的文件会立即出现在编辑器的文件导航中,并且不需要重启终端或编辑器,它们就能够被编译。无需为了移动这些文件,而在文件系统的冗长路径中漫步查找想要的文件夹。

注意!很多网站都有防止内容被大规模下载的安全机制,如果你的IP地址有这类大规模下载的动作,则很可能被封。因此,如果你不想被禁用的话,必须非常小心的在你经常连接的资源上使用“机器”自动下载文件。

那些想更进一步改进上述功能的读者,可以使用 Clipboard脚本来截取剪贴板的内容,并进一步实施自动化下载。

例子 2. 在一个图表上监视多个经纪商的报价

我们已经学习了如何从互联网上获取文件。现在让我们考虑一个更为有意思的问题 - 如何发送和存储这些数据到服务器上。我们需要一个额外的放置在服务器上的小PHP脚本程序。使用已经编写好的MqlNet类,我们创建一个EA交易程序MetaArbitrage 。这个专家系统和PHP脚本结合的目的是:

  • 发送一个EA的请求到服务器;
  • 在服务器上形成响应页面(PHP);
  • 通过此EA接收这个页面;
  • 分析并将结果发送到屏幕。

MQL模块和PHP脚本相互作用的原理图如下:


我们将使用MqlNet类来实现这些任务。

为了避免重复的数据,以及淘汰过时的报价,我们将传送4个主要参数:经纪商服务器的名称(当前价格的来源),货币对,价格以及UTC报价时间。例如,从我们公司的资源发起访问脚本的请求如下:

www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794

这些参数和真实报价存储在服务器上,并将同这个货币对的其他所有已存报价一起,被发布在响应页面上。

这种交换的“附带”好处在于报价可以来自MT5也可以来自MT4!

由服务器生成的页面通常为CSV文件。在这个脚本中形如:

ServerName1; Bid1; Time1
ServerName 2; Bid2; Time2
ServerName 3; Bid3; Time3

ServerName N; BidN; TimeN

但是你可以为自己添加额外的参数(如,服务器类型 - 模拟或者实盘)。我们存储这个CSV文件并且逐行进行解析,以表格的形式在屏幕上输出价格值。

实现对这个文件的处理有多种不同的方式,为每一个特定的情景选择一种方法。例如,对从MetaTrader4模拟服务器接收到的报价进行过滤,等等。


使用因特网服务器的好处是显而易见的,你发送你自己的报价,它可以被其他交易者接收和浏览。同样,你也将可以接收到发送给其他交易者的报价。也就是说,终端之间的交互是双边的,下面是实现数据交换的方案:


该方案是任意数量终端之间进行信息交换的基本方式。完整且有注释的MetaArbitrage专家交易系统和PHP脚本可以从附件的链接中下载。更多关于PHP使用到的函数,可以到这个站点php.su阅读。

例子 3. 在终端内交换信息(mini图表)MetaChat Expert Advisor

让我们暂别交易和数字,创建一个应用程序,让我们能够和几个人聊天,而不必退出终端。为了实现这一点,我们需要更多的和之前类似的PHP脚本。不同的是在这个脚本中,我们将分析文件中的行,而不是分析时间报价。这个EA系统的目的是:

  • 发送一个文本行到服务器;
  • 将这行添加到共享文件中,控制文件的大小,发布到响应文件(php)上;
  • 接收当前对话并且在屏幕上显示。

MetaChat的功能和之前的EA交易系统没什么两样。同样的原理,同样简单的CSV输出文件。


MetaChat 和 MetaArbitrage 在其开发者的网站上。运行他们的PHP脚本也在那里。
因此,如果你想测试或使用这项服务,你可以通过下面的链接访问它:
MetaСhat - www.fxmaster.de/metachat.php
MetaArbitrage - www.fxmaster.de/metaarbitr.php

总结

至此,我们已经熟悉了HTTP请求。我们获得了通过网络发送和接收数据的能力,并将其工作过程组织的更加舒适了。但是任何功能总是存在改进的余地。以下是可以考虑的新的潜在改进方向:

  • 直接将新闻读到终端中或者在终端中接收其他信息,来给EA交易系统进行分析;
  • 远程控制EA交易系统;
  • 自动更新EA交易系统/指标;
  • 复制/翻译交易,发送信号;
  • 为EA交易系统下载模板和配置文件;
  • 以及其他很多很多 ...

本文中我们使用GET类型的请求。当你使用少数参数获取一个文件或者发送一个请求来分析服务器时,他们足够胜任这些任务了。

在下一课中,我们将仔细看看POST请求,发送文件到服务器或终端之间共享文件,我们会介绍他们的用例。

有用的资源

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/73

附加的文件 |
internetlib.mqh (15.84 KB)
metaarbitrage.mq5 (17.93 KB)
metachat.mq5 (11.23 KB)
metagrabber.mq5 (6.35 KB)
metaarbitr.zip (0.81 KB)
wininet.zip (12.81 KB)
在 MQL5 中寻找趋势的几种方法 在 MQL5 中寻找趋势的几种方法

任何一位交易人都有可能面临准确把握趋势的好机会。也许这就是每个人都想找到的万能圣杯。在本文中,我们将讨论几个判断趋势的方法。更准确地说,是如何通过 MQL5 方法,制定几个经典的趋势判断程序。

MetaTrader应用商店2013年第三季度业绩 MetaTrader应用商店2013年第三季度业绩

又过了一个季度,我们已决定统计MetaTrader 应用商店的业绩 - MetaTrader平台最大的交易机器人和技术指标商店。 直至报告季度末期,有500多名开发者已经将他们的1200个产品放入MetaTrader 应用商店。

MetaTrader 5 中进行测试的原理 MetaTrader 5 中进行测试的原理

MetaTrader 5 中三种测试模式有何区别?应该特别注意什么?如何测试在几个工具上同时进行交易的 EA?在测试期间何时及如何计算指标值?如何处理事件?如何在测试期间以一种仅开盘价模式同步处理来自不同工具的指标柱?本文旨在回答这些问题以及很多其他问题。

在 MQL5 中使用 WinInet。第二部分:POST 请求和文件 在 MQL5 中使用 WinInet。第二部分:POST 请求和文件

在本文中,我们将继续学习使用 HTTP 请求处理互联网和与服务器进行信息交换的原则。它介绍了 CMqlNet 类的新函数、从表单发送信息的方法、使用 POST 请求发送文件的方法以及使用 Cookie 在您登录网站时进行身份验证。