从网络中获取债券收益率数据

Steven Brown | 2 五月, 2019

简介

自动交易几乎完全基于技术指标,这些指标使用过去的价格行为来预测未来的价格行为。然而,忽略市场运动的基本力量的交易者对将基本数据纳入其交易决策的交易者不利。基于自动收集的基础数据的指标可以提高专家顾问的效率。对汇率影响最大的基础数据是利率,它影响货币的感知价值,虽然央行的利率没有波动,但美国等政府债券的收益率、0年期国债,在全球债券市场的所有时间框架内波动。这些收益率反映了市场对未来央行利率走向的预期。债券收益率通常是利率和汇率的主要指标。在外汇市场中,适用于货币对的度量标准是不同时间段的利率差,特别是delta或利率差变化。图1显示了一种情况,即利率差的变动(以基点表示)在正方向上是欧元/美元货币对在同一方向上变动的领先指标。本文介绍了如何从网络中收集债券收益率数据,并从利率差和增量的数据表达式中推导出债券收益率数据。


利率差异领先指标

图1每小时 EUR/USD 图表上的利率差异指标。

抓取的基础

浏览器中显示的网页通常由许多元素组成:格式化的文本、图形、图像、声音和视频。所有这些元素都存在于Web服务器上的文件中,并由浏览器使用特定地址或URL按顺序下载以访问它们。但是,程序可以下载页面的一个元素,而忽略其余元素,因为该元素具有有用的信息。以这种方式获取信息被称为“抓取”。要做到这一点,抓取程序必须具有包含元素的文件的URL,该元素可能是显示在网页上的数字。它可以下载该文件,搜索代表数字的文本,并将该文本转换为数值。

取得 URL

抓取的第一个任务是获取包含要下载元素的文件的URL。如果元素嵌入到页面的HTML文本中,那么它可以是网页的URL,在这种情况下,可以从页面的HTML文本分析元素。或者,URL可以嵌入到页面上的链接中,浏览器使用该链接来获取元素,而scraper程序可以使用该链接来获取要分析元素的HTML文本。或者,可以通过链接到页面上的脚本将URL传递给浏览器,浏览器将在下载页面和脚本后运行该脚本。在这种情况下,scraper程序不必运行脚本,但可以使用脚本生成的URL,使用Internet Explorer或Google Chrome中提供的开发工具可以发现该URL。无论URL的来源是什么,抓取程序都会使用它从Web服务器下载一个文件,并对其进行解析以获取所需的信息。有几个金融网站报告债券收益率,我们首先应该看看 https://www.marketwatch.com/investing/bond/tmubmusd10y?countrycode=bx, 来创建抓取的例子。

首先,让我们看看当单击上面的链接时浏览器从Web服务器下载的HTML文件。当页面显示在Chrome浏览器中时,单击右上角的“工具”按钮,将鼠标光标移动到“更多工具”,选择“将页面另存为”,下载HTML文件,并在文本编辑器(如记事本)中打开它。很明显,这个网站使抓取程序很容易获得报价,因为它包含在HTML文件头部的一系列元标记中。元数据不由浏览器显示,对显示的内容没有影响,但任何下载HTML文件的程序都可以访问它。报价出现在元标签<meta name=“price”content=“3.066”>中,距标签开头28个字符,与浏览器在页面上突出显示的值相同。抓取程序可以在文件中搜索文本字符串[<meta name=“price”content=],在meta标记开头的索引中增加28,并将结果位置的文本转换为浮点数字。为了避免混淆,本文中使用方括号引用HTML文本,HTML文本经常使用引号。

构建一个更好的程序

链接到上面的HTML文件从服务器下载时,包含大量样式表信息,总大小为295千字节。但是,感兴趣的meta标记与文件开头的距离仅为3千字节。一个表现较好的抓取程序下载的数据不会超过它所需要的数量,所以每次获取报价时只下载前四个千字节是合理的。不幸的是,mql 函数 WebRequest()无法限制要下载的数据量。包含服务器响应数据的数组必须是动态数组。如果使用指定大小的静态数组,程序将可以正常编译,但在运行时发生错误,导致终端崩溃。可以包含在对服务器的请求中包含 Range 头,但大多数服务器不支持 Range 头。所以,从Web服务器下载的数据量几乎总是所请求的HTML文件的大小。更好的方法是使用wininet.dll中的函数,wininet.dll是Windows的一个组件。其中一个函数 InternetReadFile()可以下载指定数量的字节,即使不支持范围头并且下载从文件开头开始。WinINet 函数可以导入到 MQL 脚本或 EA 交易中。本文所附的 ScraperBot01.mq5 文件是一个脚本,它下载HTML文件的前4千字节,定位所下载文本中感兴趣的元标记,查找该标记中表示10年 T-note上最后一次引用的收益率的文本,将该文本转换为浮点数,并将其值打印到终端。

ScraperBot 01

源代码文件 ScraperBot01.mq5 首先导入wininet.dll 并原型化将调用的函数,将所有参数声明为具有与 mql5 兼容的类型。WinINet 函数的文档位于 https://docs.microsoft.com/en-us/windows/desktop/wininet/wininet-reference.

#import "wininet.dll"
  int InternetCheckConnectionW(string& lpszUrl, uint dwFlags, uint dwReserved);
  int InternetOpenW(string& lpszAgent, uint dwAccessType, string& lpszProxyName, string& lpszProxyBypass, uint dwFlags);
  int InternetOpenUrlW(int hInternetSession, string& lpszUrl, string& lpszHeaders, uint dwHeadersLength, uint dwFlags, uint dwContext);
  int InternetReadFile(int hFile, uchar& lpBuffer[], uint dwNumberOfBytesToRead, uint& lpdwNumberOfBytesRead);
  int InternetCloseHandle(int hInternet);
#import

uchar uc_Buffer[4096]; // InternetReadFile() 需要静态的缓冲区.
float f_US;

静态数组 uc_Buffer 将用于接收从网络服务器上下载的 html 文本,而 f_US 变量用于设置从文本中分析到的数字值,它们在全局范围内声明。本文和本文附加的其他文件中的约定是通过类型说明符和名称之间的下划线来表示全局变量。uc_Buffer的大小设置为适应将要下载的特定字节数。

在 OnStart()的顶部声明了一些局部变量,其他变量则根据需要声明,以明确它们的用途。首先,我们检查一下互联网连接是否可用。此脚本中调用的函数的返回值将打印到终端,以指示成功或失败,如果发生错误,则关闭打开的句柄,并使用“返回”语句终止脚本。如果有可用的Internet连接,则初始化句柄 iNet1,以便随后调用 WinINet函数。有效的句柄值大于零。

void OnStart() 
{ bool bResult;  int i, iNet1, iNet2;  

  string stURL = "http://www.msn.com"; 
  bResult = InternetCheckConnectionW(stURL, 1, 0); // 1 == FLAG_ICC_FORCE_CONNECTION
  Print("InternetCheckConnectionW() returned ", bResult);
  if(!bResult) return;
  
  string stAgent = "Mozilla/5.0", stNull = "";
  iNet1 = InternetOpenW(stAgent, // _In_ LPCTSTR lpszAgent 
                        1,       // 1 == INTERNET_OPEN_TYPE_DIRECT
                        stNull,  // _In_ LPCTSTR lpszProxyName
                        stNull,  // _In_ LPCTSTR lpszProxyBypass
                        NULL);   // _In_ DWORD dwFlags
  Print("iNet1 == ", iNet1);
  if(iNet1==0) return;


接下来建立到Web服务器的连接,初始化句柄 iNet2 以下载 html 文件。

  stURL = "https://www.marketwatch.com/investing/bond/tmubmusd10y?countrycode=bx";
  string stHdr = "Accept: text/*";
  iNet2 = InternetOpenUrlW(iNet1,            // HINTERNET hInternet,
                           stURL,            // LPCWSTR   lpszUrl,
                           stHdr,            // LPCWSTR   lpszHeaders,
                           StringLen(stHdr), // DWORD     dwHeadersLength,
                           0x00080000,       // DWORD     dwFlags, 0x00080000 == INTERNET_FLAG_NO_COOKIES
                           NULL);            // DWORD_PTR dwContext
  Print("iNet2 == ", iNet2);
  if(iNet2==0) 
  { InternetCloseHandle(iNet1);
    return;
  }


现在我们可以从Web服务器下载数据。

  uint uGet, uGot;
  uGet = 4080; // 要下载的字节数
  bResult = InternetReadFile(iNet2,     // _In_  HINTERNET hFile
                             uc_Buffer, // _Out_ LPVOID lpBuffer
                             uGet,      // _In_  DWORD dwNumberOfBytesToRead
                             uGot);     // _Out_ LPDWORD lpdwNumberOfBytesRead

  Print("InternetReadFile() returned ", bResult, ". Number of bytes read: ", uGot);
  InternetCloseHandle(iNet2);  // download done
  if(!bResult) {InternetCloseHandle(iNet1); return;}
  uc_Buffer[uGot] = 0// Terminate string in uc_Buffer by appending a null character.


现在我们在下载的文本中搜索感兴趣的meta标签,如果找到了它,在偏移量中添加28,这样我们就可以使用它作为 uc_Buffer 中表示数字的文本的索引。通过调用 StringSubstr()访问该文本,并在变量“i”中将索引传递给它。如果该索引处的文本不代表数字,则StringToDouble()将返回零,指示错误,除非债券收益率恰好为零。请注意,字符串中的引号被编码为\“,以将其与字符串开头和结尾的引号区分开来。

  i = StringFind(CharArrayToString(uc_Buffer), "<meta name=\"price\" content=", 0); // 0 == 开始搜索的位置 
  Print("Offset of \'<meta name=\"price\" content=\' == ", i); 
  if(i == -1) {Print("String not found.");  InternetCloseHandle(iNet1);  return;} 
  i += 28; // 将索引提前到表示债券收益的文本的已知位置。
  f_US = StringToDouble(StringSubstr(CharArrayToString(uc_Buffer), i, 8));
  Print("US 10-year T-note yield, stored in variable f_US: ", f_US);
  InternetCloseHandle(iNet1); // wininet 完成.
}//END void OnStart()

脚本 ScraperBot01可以在任何图表上运行,在终端中报告其进度,并打印从Web上获取的值。

另一种情况

既然已经演示了从网页中获取数据的过程,那么让我们考虑一下如果债券收益率数据不在元标记中,该怎么做。在这种情况下,我们将检查网页的HTML文件,以查找该网页上显示的数字的来源。了解债券收益率的最新报价后,我们可以使用文本编辑器的搜索功能来查找表示数字的文本字符串。在我们下载的HTML文件中,除了meta标签,还可以在其他三个位置找到报价。这三个元素中的第一个是 JSON-LD 结构化数据片段,它是一个HTML元素,旨在使搜索引擎和网络爬虫很容易访问网页信息。 这里是一部分数据,为了更清晰表示使用了分行显示。

<script type="application/ld+json">
{ "@context":"http://schema.org/",
  "@type":"Intangible/FinancialQuote",
  "url":"https://www.marketwatch.com/investing/bond/tmubmusd10y?countrycode=bx",
  "name":"U.S. 10 Year Treasury Note",
  "tickerSymbol":"TMUBMUSD10Y",
  "exchange":"Tullett Prebon",
  "price":"3.061",
  "priceChange":"0.007",
  "priceChangePercent":"0.22%",
  "quoteTime":"Sep 28, 2018 5:07 p.m.",
  "priceCurrency":"PERCENT"
}
</script>

算法将首先搜索标记的偏移量<script type=“application/ld+json”>,然后从该位置查找“[”price“:”]和的偏移量。如果[“price”:“]的偏移量小于</script>的偏移量,表明它在数据段内,则我们将9添加到[“price”:“]的偏移量中,以到达表示报价的数字的偏移量。该过程在附加的脚本 ScraperBot02.mq5中演示。

ScraperBot 02

此脚本下载的HTML文件的大小可以达到umax指定的大小。首次运行时,umax应设置为远大于预期下载大小的值,例如100万。脚本报告下载的字节数,如果该字节数位于或接近uMax,则应增加uMax的值。脚本还报告标记<script type=\“application/ld+json\”>文件中的偏移量,然后,可以将 uMax 的值设置为略高于标记的偏移量。在这种情况下,偏移量为166696,因此uMax设置为180224以下载足够的文件,以包含 JSON-LD 片段,而不是整个文件。该脚本使用静态数组下载16千字节的块,这些块被复制到动态数组中并累积到动态数组中,这些数组是用全局范围声明的。

uchar uc_Buffer[16400], uc_DynBuf[];

然后,ScraperBot02 与 ScraperBot01 相同,直到从Web服务器下载数据的部分,该部分将数据分块打包。在 do-while 循环中反复调用 InternetReadFile,直到下载所需的数据量。

  uint uGet, uGot, uDst, uMax;
  uGet = 16384;    // 每次调用InternetReadFile时要下载的字节数,必须至少比 uc_Buffer 大小小1个字节。
  uGot = uDst = 0; // uGot是调用 InternetReadFile 时下载的字节数;uDst 是下载的字节总数。
  uMax = 180224;   // 最大下载字节数

  do
  { bResult = InternetReadFile(iNet2,     // _In_  HINTERNET hFile
                               uc_Buffer, // _Out_ LPVOID lpBuffer
                               uGet,      // _In_  DWORD dwNumberOfBytesToRead
                               uGot);     // _Out_ LPDWORD lpdwNumberOfBytesRead

    uc_Buffer[uGot] = 0; // 使用空字符来结束 uc_Buffer 中的字符串。

    ArrayCopy(uc_DynBuf, // 目标数组 
              uc_Buffer, // 源数组 
              uDst,      // 开始写到目标数组的索引 
              0,         // 开始从源数组复制的索引 
              uGot);     // 要复制的元素数量 
    uDst += uGot; // 为了下一次循环调整索引
  }while(bResult && uGot > 0 && uDst < uMax);
 
  Print("Size of uc_DynBuf == ", ArraySize(uc_DynBuf));
  Print("Bytes downloaded  == ", uDst);


ScraperBot02 现在定位标签<script type=\“application/ld+json\”> 并将偏移存储在索引变量i中,从该偏移量开始,它定位到[“price”:“]并将该偏移量存储在j中,然后它在代码段的末尾找到</script>并将该偏移量存储在k中。如果j小于k,则将9添加到j中,这将成为表示数字的文本的偏移量,然后在f_US中转换为浮点值并打印到终端。

  int i, j, k; // 索引

  i = StringFind(CharArrayToString(uc_DynBuf), "<script type=\"application/ld+json\">", 0); // 0 == 开始搜索的位置 
  Print("Offset of <script type=\"application/ld+json\"> == ", i); 
  if(i == -1) {Print("<script type=\"application/ld+json\"> not found.");  InternetCloseHandle(iNet1);  return;}

  j = StringFind(CharArrayToString(uc_DynBuf), "\"price\":\"", i); // i == 开始搜索的位置 
  if(j == -1) {Print("\"price\":\" not found.");  InternetCloseHandle(iNet1);  return;}
  Print("Offset of \"price\":\" == ", j); 

  k = StringFind(CharArrayToString(uc_DynBuf), "</script>", i); // i == 开始搜索的位置
  Print("Offset of </script> == ", k); 
  if(j > k) {Print("Offset of \"price\":\" is greater than offset of </script>");  InternetCloseHandle(iNet1);  return;}

  j += 9; // 将索引提前到表示债券收益的文本的已知位置。
  f_US = StringToDouble(StringSubstr(CharArrayToString(uc_DynBuf), j, 8));
  Print("US 10-year T-note yield, stored in variable f_US: ", f_US);
  InternetCloseHandle(iNet1); // wininet 结束.
}//END void OnStart()

使用开发者工具

使用Chrome浏览器中的开发人员工具可以找到该Web服务器报价的另一个来源。从右上角的菜单按钮,打开开发者工具并输入https://www.marketwatch.com/investment/bond/tmubmusd10y?countrycode=bx进入地址栏。选择中间窗格中的网络选项卡,并选择“XHR”作为要监视的事件类型。选择这些事件中的任何一个将打开右侧的窗格,其中显示诸如标题和响应等详细信息。标记为“quoteByDialect…”的事件有一个有趣的响应,右键单击该窗格并选择“全选”可突出显示该响应。按Ctrl+C将突出显示的文本复制到剪贴板,然后将其粘贴到文本编辑器中。在字符串 ["CompositeTrading":{"Last":{"Price":{"Iso":"PERCENT","Value":] 后的文本块中可以找到报价。可以在“标题”选项卡下找到获取该文本块的URL。在这种情况下它有些长: https://api.wsj.net/api/dylan/quotes/v2/comp/quoteByDialect?dialect=official&needed=CompositeTrading|BluegrassChannels&MaxInstrumentMatches=1&accept=application/json&EntitlementToken=cecc4267a0194af89ca343805a3e57af&ckey=cecc4267a0&dialects=Charting&id=Bond-BX-TMUBMUSD10Y,Bond-BX-TMBMKDE-10Y. 单击本文右侧的链接将在浏览器窗口中显示文本块。它实际上是两块文本,一块接一块,因为在URL的末尾有两个由逗号分隔的断续器符号字符串。第一个“Bond-BX-TMUBMUSD10Y”用于美国10年期国债,第二个“Bond-BX-TMBMKDE-10Y”用于德国10年期国债。从URL中删除第二个断续器符号字符串会将下载文本的大小从7.1千字节减少到3.6千字节。

随附的脚本 ScraperBot03 下载了滚动条符号“TMUBMUSD10Y”的文本块,找到字符串["CompositeTrading":{"Last":{"Price":{"Iso":"PERCENT","Value":],,在字符串开头的偏移量中添加61,将其用作表示数字的文本的索引,将文本转换为浮点数并把它打印到终端中。该脚本是根据ScrapterBot01建模的,因此此处不引用代码。这个资源的一个优点是下载的规模很小,由于URL所寻址的文件只有3.6千字节,因此可以使用mql5函数WebRequest(而不是wininet.dll中的函数)来下载该文件,而无需下载远远超过需要的数据。

ScraperBot 04

此脚本使用 WebRequest 而不是 WinINet函数下载与 ScraperBot 03相同的数据。为了使 WebRequest工作,服务器的基本URL(在本例中为“https://api.wsj.net”)需要包含在 MetaTrader平台的“工具\选项\专家顾问”下的允许服务器列表中。全局 char 数组 ch_Data 不会将任何数据传递给 WebRequest,它的存在只是为了满足对该类型参数的要求。

char ch_Buffer[], ch_Data[16];
float f_US;

void OnStart() 
{ int i;   
  string stURL = "https://api.wsj.net/api/dylan/quotes/v2/comp/quoteByDialect?dialect=official&needed=CompositeTrading|BluegrassChannels&"
                 "MaxInstrumentMatches=1&accept=application/json&EntitlementToken=cecc4267a0194af89ca343805a3e57af&ckey=cecc4267a0&"
                 "dialects=Charting&id=Bond-BX-TMUBMUSD10Y";
  string stHdr = "Accept: text/*, User-Agent: Mozilla/5.0";
  string stRspHdr; // 回应头
  
  i = WebRequest("GET",     // const string  method,    HTTP 方法 
                 stURL,     // const string  url,       URL 
                 stHdr,     // const string  headers,  
                 1024,      // int           timeout, 
                 ch_Data,   // const char    &data[],  HTTP 消息体的数组
                 ch_Buffer, // char          &result[], 包含服务器回应数据的数组
                 stRspHdr); // string        &result_headers 

  Print("Server response code: ", i);
  if(i == -1) {Print("GetLastError == ", GetLastError());  return;}
  Print("Size of ch_Buffer (bytes downloaded) == ", ArraySize(ch_Buffer));
  Print("Response header:\n", stRspHdr);   
 
  string stSearch = "\"CompositeTrading\":{\"Last\":{\"Price\":{\"Iso\":\"PERCENT\",\"Value\":";
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, 0); // 0 == 开始搜索的位置 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // 将索引提前到表示债券收益的文本的已知位置。
  f_US = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("US 10-year T-note yield, stored in variable f_US: ", f_US);
}//END void OnStart()


其它债券

为了得到一个利率差,需要有两个利率,获取美国10年期国债收益率的脚本可以通过在URL中用勾号“TMBMKDE-10Y”替换“TMUBMUSD10Y”来获得德国10年期政府债券的收益率。也可以使用其他国家的交易品种名称。其他金融网站可能使用不同的交易品种名称,但同样的原则适用。德国债券经常被用来代表欧元的利率,但这样做忽略了欧盟其他国家的影响。然而,欧元的综合利率可以从两个或更多的欧洲政府债券中得出。在本例中,前三个欧盟成员国的10年期政府债券收益率(以欧元为货币的国内生产总值为单位)将用于得出一个综合“欧洲债券”利率。这三个国家分别是德国、法国和意大利。其债券收益率将根据其经济体的相对规模进行加权,如下表所示。


2017国内生产总值(十亿欧元)

德国3,197
法国2,241
意大利1,681
总共7,119


每个国家的加权系数将是其国内生产总值占三个国家总产值的比率。德国为0.449,法国为0.315,意大利为0.236。根据以下方程式,这些因素加起来为1,并用作计算欧元债券收益率复合值的系数。

f_EU = 0.449*f_DE + 0.315*f_FR + 0.236*f_IT

其中,f_EU 是欧洲综合债券收益率,f_DE 是德国债券收益率,f_FR 是法国债券收益率,f_IT 是意大利债券收益率。

利率差

EUR/USD 货币对的报价是以美元表示的欧元价值,因此它倾向于朝着与欧元价值相同的方向移动,并朝着与美元价值相反的方向移动。对于利率差的变动以预测货币对在同一方向的变动,欧洲复合债券收益率的增加应使利率差向正方向移动,而美国国债收益率的增加应使其向负方向移动。因此,利率差额计算为“f_EU - f_US”。在撰写本文时,f_EU的计算值为1.255,f_US 的计算值为3.142。所以,利率差为 1.255 - 3.142 = -1.887. 正向波动,如-1.887至-1.798,将预测欧元/美元上涨。负向移动,从-1.887到-1.975,预计欧元/美元将下跌。指标的强度或可靠性取决于利率差的变动幅度,利率变动通常用基点或百分之一个百分点来表示。利率差从-1.887移动到-1.975将是朝负方向移动8.8个基点,这是一个相当强劲的日内移动,可能表明货币对在每小时时间框架内的向下移动。只有一个或两个基点的波动属于市场噪音的范畴,不太可能成为货币对波动的可靠指标。

ScraperBot 05

这个脚本获取所有四个债券收益率,计算欧洲债券综合收益率,并将利率差异打印到终端。它是仿照scraperbot 04建模的,但不是为每个债券收益率向服务器发出单独的请求,而是将所有四个股票代码符号附加到URL,并在一个14千字节的下载中返回所有四个引号,其中包含四个连续的文本块。ScraperBot05 在定位引号前面的字符串之前,会在文件中定位勾号符号,如果找不到字符串,则返回错误消息。

char ch_Buffer[], ch_Data[16];      // 全局缓冲区
float f_US, f_DE, f_FR, f_IT, f_EU; // 用于保存债券利率的全局变量

void OnStart() 
{ int i;   
  string stURL = "https://api.wsj.net/api/dylan/quotes/v2/comp/quoteByDialect?dialect=official&needed=CompositeTrading|BluegrassChannels&"
                 "MaxInstrumentMatches=1&accept=application/json&EntitlementToken=cecc4267a0194af89ca343805a3e57af&ckey=cecc4267a0&"
                 "dialects=Charting&id=Bond-BX-TMUBMUSD10Y,Bond-BX-TMBMKDE-10Y,Bond-BX-TMBMKFR-10Y,Bond-BX-TMBMKIT-10Y"; // 四个品种名称

  string stHdr = "Accept: text/*, User-Agent: Mozilla/5.0";
  string stRspHdr; // 回应头

  i = WebRequest("GET",     // const string  method,    HTTP 方法 
                 stURL,     // const string  url,       URL 
                 stHdr,     // const string  headers,  
                 1024,      // int           timeout, 
                 ch_Data,   // const char    &data[],  HTTP 消息体的数组
                 ch_Buffer, // char          &result[], 包含服务器回应数据的数组
                 stRspHdr); // string        &result_headers 

  Print("Server response code: ", i);
  if(i == -1) {Print("GetLastError == ", GetLastError());  return;}
  Print("Size of ch_Buffer (bytes downloaded) == ", ArraySize(ch_Buffer));
   
  string stSearch = "\"CompositeTrading\":{\"Last\":{\"Price\":{\"Iso\":\"PERCENT\",\"Value\":";

// Get US 10-year treasury note yield.
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMUBMUSD10Y\"", 0); // 0 == 开始搜索的位置 
  if(i == -1) {Print("\"Ticker\":\"TMUBMUSD10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == 开始搜索的位置 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // 将索引提前到表示债券收益的文本的已知位置。
  f_US = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("US 10-year T-note yield, stored in variable f_US: ", f_US);

// 获得德国10年期政府债券收益率。
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKDE-10Y\"", i); // i == 开始搜索的位置 
  if(i == -1) {Print("\"Ticker\":\"TMBMKDE-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == 开始搜索的位置 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // 将索引提前到表示债券收益的文本的已知位置。
  f_DE = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("German 10-year government bond yield, stored in variable f_DE: ", f_DE);

// 获得法国10年期政府债券收益率。
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKFR-10Y\"", i); // i == 开始搜索的位置 
  if(i == -1) {Print("\"Ticker\":\"TMBMKFR-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == 开始搜索的位置 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // 将索引提前到表示债券收益的文本的已知位置。
  f_FR = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("French 10-year government bond yield, stored in variable f_FR: ", f_FR);

// 获得意大利10年期政府债券收益率。
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKIT-10Y\"", i); // i == 开始搜索的位置 
  if(i == -1) {Print("\"Ticker\":\"TMBMKIT-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == 开始搜索的位置 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // 将索引提前到表示债券收益的文本的已知位置。
  f_IT = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("Italian 10-year government bond yield, stored in variable f_IT: ", f_IT);

// 计算欧洲综合债券收益率。
  f_EU = 0.449*f_DE + 0.315*f_FR + 0.236*f_IT;
  Print("European composite bond yield: ", f_EU);

// 计算利率差额。
  Print("Interest rate differential, f_EU-f_US = ", f_EU-f_US);
}//END void OnStart()

ScraperBot06.mq4使用 WinINet而不是 WebRequest 来实现 ScraperBot05.mq5,这在mt4平台上是不可靠的。


Delta

利率差异的变化与一对货币的交易比利率差异本身更相关。Delta可以表示为一个条形图结束时的利率差减去前一个条形图结束时的利率差。虽然单周期增量可能在较长的时间框架上有用,但增量的指数移动平均值在较短的时间框架上更有用,因为它考虑了许多条的变化。EMA的计算方法是将平滑因子或 alpha 应用于当前的 delta,并将其添加到(1-alpha)乘以EMA的前一个值,即EMAP。

EMA = a*Delta + (1-a)*EMAp

因为delta可以是正的或负的,并且其平均值为零,所以在EMA的先前值不可用的情况下,EMAP可以初始化为零。alpha的值可以任意分配,也可以根据移动平均值任意分配的周期数计算。在这种情况下,

a = 2.0 / (n+1)

其中n是EMA周期数,可以是整数或分数。alpha的正常范围大于零、小于或等于1或0<a<=1。当alpha值为1时,EMA的计算不考虑EMA的前一个值,EMA成为当前的delta。

如果有利率差额的历史数据库可用,则可以对指标进行编码,以便在货币对的图表上显示利率差额的时间序列或Delta的EMA。不允许指标从 Internet 获取数据,但指标可以从本地磁盘文件中获取数据,该文件由从Internet获取债券收益率数据的脚本更新。编写这样的指标是另一篇文章的主题。

结论

附加脚本中的源代码可以复制到EA交易中,以自动收集基础数据。本文所演示的从网络中获取债券收益率数据的技术可以应用于其他金融网站和其他货币对,示例中使用的除外。如果一个由URL寻址的资源改变了并且不再工作,程序员就有了发现错误并找到解决方案的知识和方法。一个行为良好的机器人程序不会下载比需要更多的数据,也不会发出太频繁的请求,当然不是货币对每个分时。债券收益率比汇率波动性小,每五分钟一次可能是更新债券收益率(用作交易指标)所需的最高频率。同样,从网站上获得的信息不一定是公共领域的,不应该重新分发。明智地使用,自动收集基础数据有可能使自动交易盈利。