MetaTrader 5使用了一系列新的用户界面元素,为用户开辟了独一无二的机会。因此,那些之前不具备的功能现在能被最大限度地使用了。
在本课中我们将学习:
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); }
初始化完成后,描述符Session和Connect就能被用在下面所有的函数中了。一但所有的工作都完成且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的参数:
读取已接收描述符的内容
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的参数:
将所有这些集中到一起,我们将获得一个操作因特网的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脚本的任务是:
我们用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脚本结合的目的是:
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系统的目的是:
MetaChat的功能和之前的EA交易系统没什么两样。同样的原理,同样简单的CSV输出文件。
MetaChat 和 MetaArbitrage 在其开发者的网站上。运行他们的PHP脚本也在那里。
因此,如果你想测试或使用这项服务,你可以通过下面的链接访问它:
MetaСhat - www.fxmaster.de/metachat.php
MetaArbitrage - www.fxmaster.de/metaarbitr.php
至此,我们已经熟悉了HTTP请求。我们获得了通过网络发送和接收数据的能力,并将其工作过程组织的更加舒适了。但是任何功能总是存在改进的余地。以下是可以考虑的新的潜在改进方向:
本文中我们使用GET类型的请求。当你使用少数参数获取一个文件或者发送一个请求来分析服务器时,他们足够胜任这些任务了。
在下一课中,我们将仔细看看POST请求,发送文件到服务器或终端之间共享文件,我们会介绍他们的用例。
有用的资源
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程