Bondrenditen aus dem Web kratzen

Steven Brown | 20 Mai, 2019

Einführung

Der automatisierte Handel basiert fast ausschließlich auf technischen Indikatoren, die vergangene Preisaktionen nutzen, um zukünftige Preisbewegungen vorherzusagen. Der Händler, der fundamentale Kräfte ignoriert, die die Märkte bewegen, ist jedoch im Nachteil gegenüber Händlern, die fundamentale Daten in ihre Handelsentscheidungen einbeziehen. Ein Indikator, der auf automatisch erfassten Fundamentaldaten basiert, kann die Leistung eines Expert Advisors verbessern. Die fundamentalen Daten, die sich am stärksten auf die Wechselkurse auswirken, sind die Zinssätze, die den wahrgenommenen Wert der Währungen beeinflussen. Während die Leitzinsen selbst nicht so volatil sind, schwanken die Renditen der Staatsanleihen wie den 10-jährige Treasury Note der USA auf allen Zeitrahmen an den globalen Anleihemärkten. Diese Renditen spiegeln die Erwartung des Marktes wider, wohin die zukünftigen Zentralbankzinsen gehen werden. Anleiherenditen sind oft ein wichtiger Indikator für Zinsen und Wechselkurse. Im Devisenmarkt ist die Metrik, die für ein Währungspaar gilt, die Zinsdifferenz, insbesondere das Delta, oder die Änderung der Zinsdifferenz in verschiedenen Zeitrahmen. Abbildung 1 zeigt einen Fall, in dem die Bewegung der Zinsdifferenz, ausgedrückt in Basispunkten, in die positive Richtung ein Frühindikator für die Bewegung des Währungspaares EUR/USD in die gleiche Richtung war. Dieser Artikel zeigt, wie man die Renditen aus dem Internet sammelt und daraus abgeleitete Daten wie Zinsdifferenz und Delta ermittelt.


Die Zinsdifferenz als Frühindikator

Bild 1. Der Indikator der Zinsdifferenz auf dem Chart H1 EUR/USD

Scraping 101

Eine Webseite, die in einem Browser angezeigt wird, besteht in der Regel aus vielen Elementen: formatiertem Text, Grafiken, Bildern, Sound und Video. Alle diese Elemente befinden sich in Dateien auf Webservern und werden vom Browser über bestimmte Adressen oder URLs nacheinander heruntergeladen. Es ist jedoch möglich, dass ein Programm ein Element der Seite herunterlädt und den Rest ignoriert, da nur dieses Element nützliche Informationen enthält. Informationen auf diese Weise zu erhalten, nennt man "Scraping" (Abkratzen). Dazu muss das Scraper-Programm die URL zu der Datei haben, die das Element enthält, welches eine Zahl sein kann, die auf einer Webseite angezeigt wird. Es kann diese Datei herunterladen, nach dem Text suchen, der die Zahl darstellt, und diesen Text in einen numerischen Wert konvertieren.

Abrufen der URL

Die erste Aufgabe beim Scraping besteht darin, die URL der Datei zu erhalten, die das herunterzuladende Element enthält. Das kann die URL der Webseite sein, wenn das Element in den HTML-Text der Seite eingebettet ist. In diesem Fall kann das Element aus dem HTML-Text der Seite analysiert werden. Oder die URL kann in einen Link auf der Seite eingebettet werden, mit dem der Browser das Element abruft und mit dem das Scraper-Programm den HTML-Text abrufen kann, aus dem das Element analysiert werden soll. Oder die URL kann über ein auf der Seite verlinktes Skript an den Browser übergeben werden, das der Browser nach dem Herunterladen der Seite und des Skripts ausführt. In diesem Fall muss das Scraper-Programm das Skript nicht ausführen, sondern kann die URL verwenden, die das Skript generiert. Diese URL kann mit Hilfe der Entwickler-Tools in Internet Explorer oder Google Chrome ermittelt werden. Unabhängig von der Quelle der URL verwendet das Scraper-Programm sie, um eine Datei vom Webserver herunterzuladen und für die gewünschten Informationen zu analysieren. Es gibt mehrere Finanz-Websites, die Anleiherenditen melden. Zuerst schauen wir uns diese Url an: https://www.marketwatch.com/investing/bond/tmubmusd10y?countrycode=bx. Damit erstellen wir unser Scraping-Beispiel.

Betrachten wir zunächst die html-Datei, die der Browser vom Webserver herunterlädt, wenn auf den obigen Link geklickt wird. Wenn die Seite im Chrome-Browser angezeigt wird, klicken Sie auf die Schaltfläche Tools in der rechten oberen Ecke, bewegen Sie den Mauszeiger auf "Weitere Tools", wählen Sie "Seite speichern unter", laden Sie die HTML-Datei herunter und öffnen Sie sie in einem Texteditor wie Notepad. Es wird deutlich, dass diese Website es Bots leicht macht hat, die Zahl zu erhalten, da sie in einem einer Reihe von Meta-Tags im Kopf der HTML-Datei enthalten ist. Metadaten werden von Browsern nicht angezeigt und haben keinen Einfluss auf die Anzeige, aber sie sind für jedes Programm zugänglich, das die html-Datei herunterlädt. Die Zahl erscheint im Meta-Tag <meta name="price" content="3.066">, 28 Zeichen vom Anfang des Tags, und es ist derselbe Wert, der vom Browser auf der Seite prominent angezeigt wird. Das Scraper-Programm kann die Datei nach der Textzeichenkette [<meta name="price" content=] durchsuchen, 28 zum Index des Beginns des Meta-Tags hinzufügen und den Text an der resultierenden Stelle in eine Fließkommazahl umwandeln. Um Verwirrung zu vermeiden, werden in diesem Artikel eckige Klammern verwendet, um HTML-Text zu zitieren, der häufig Anführungszeichen verwendet.

Einen besseren Bot erstellen

Die oben verlinkte HTML-Datei enthält beim Herunterladen vom Server große Blöcke mit Stylesheet-Informationen, und die Gesamtgröße beträgt 295 Kilobyte. Der interessierende Meta-Tag liegt jedoch nur 3 Kilobyte vom Anfang der Datei entfernt. Ein gut erzogener Bot lädt nicht mehr Daten herunter, als er benötigt, daher wäre es sinnvoll, jedes Mal, wenn er das Angebot erhält, nur die ersten 4 Kilobyte herunterzuladen. Leider hat die mql-Funktion WebRequest() keine Möglichkeit, die Datenmenge für den Download zu begrenzen. Das Array, das die Serverantwortdaten enthält, muss ein dynamisches Array sein. Wenn ein statisches Array mit einer bestimmten Größe verwendet wird, kompiliert das Programm, aber zur Laufzeit tritt ein Fehler auf, der das Terminal zum Absturz bringt. Ein Range-Header könnte in die Anfrage an den Server aufgenommen werden, aber die meisten Server unterstützen den Range-Header nicht. Daher ist die Menge der von einem Webserver heruntergeladenen Daten fast immer die Größe der angeforderten html-Datei. Ein besserer Weg ist die Verwendung der Funktionen in wininet.dll, einer Komponente von Windows. Eine dieser Funktionen, InternetReadFile(), kann eine bestimmte Anzahl von Bytes herunterladen, auch wenn der Range-Header nicht unterstützt wird und der Download am Anfang der Datei beginnt. WinINet-Funktionen können in ein SQL-Skript oder einen Expert Advisor importiert werden. Die an diesen Artikel angehängte Datei ScraperBot01.mq5 ist ein Skript, das die ersten 4 Kilobyte der HTML-Datei herunterlädt, das Meta-Tag von Interesse im heruntergeladenen Text findet, den Text in diesem Tag findet, der die zuletzt angegebene Rendite auf der 10-jährigen T-Note darstellt, diesen Text in eine Gleitkommazahl konvertiert und seinen Wert an das Terminal übergibt.

ScraperBot 01

Die Quellcodedatei ScraperBot01.mq5 beginnt mit dem Import von wininet.dll und dem Prototyping der aufzurufenden Funktionen, wobei alle Parameter mit mql5 kompatibel sind. Die WinINet-Funktionen sind dokumentiert unter 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() erwartet einen 'static' Puffer.
float f_US;

Das 'static' Array uc_Buffer, das den vom Webserver heruntergeladenen HTML-Text übernimmt, und die Variable f_US, der der aus dem Text konvertierten Zahlenwert zugewiesen wird, werden im globalen Bereich deklariert. Die Konvention in dieser und anderen an diesen Artikel angehängten Dateien besteht darin, globale Variablen durch einen Unterstrich zwischen Typbezeichner und Name zu bezeichnen. Die Größe von uc_Buffer wird so eingestellt, dass sie der spezifischen Anzahl der Bytes entspricht, die heruntergeladen werden.

Ein paar lokale Variablen werden am Anfang von OnStart() deklariert, andere werden bei Bedarf deklariert, um die Zweckmäßigkeit zu verdeutlichen. Zuerst prüfen wir, ob eine Internetverbindung vorhanden ist. Die Rückgabewerte von Funktionen, die in diesem Skript aufgerufen werden, werden in das Log des Terminals gedruckt, um Erfolg oder Misserfolg anzuzeigen. Wenn ein Fehler auftritt, werden offene Handles geschlossen und das Skript wird durch die Anweisung "return" beendet. Wenn eine Internetverbindung verfügbar ist, wird das Handle iNet1 für nachfolgende Aufrufe von WinINet-Funktionen initialisiert. Ein gültiger Handle-Wert ist größer als Null.

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;


Als Nächstes wird eine Verbindung zum Webserver hergestellt, indem das Handle iNet2 zum Herunterladen der HTML-Datei initialisiert wird.

  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;
  }


Jetzt können wir die Daten vom Webserver herunterladen.

  uint uGet, uGot;
  uGet = 4080; // Anzahl der herunterzuladenden Bytes
  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);  // Fertig
  if(!bResult) {InternetCloseHandle(iNet1); return;}
  uc_Buffer[uGot] = 0// Terminieren der Zeichenkette im uc_Buffer durch das Anfügen des Zeichens NULL.


Jetzt suchen wir nach dem Meta-Tag, der im heruntergeladenen Text von Interesse ist, und wenn er gefunden wird, fügen Sie 28 zum Offset hinzu, damit wir ihn als Index in uc_Buffer für den Text verwenden können, der die Zahl darstellt. Auf diesen Text wird durch den Aufruf von StringSubstr() zugegriffen, wobei der Index in der Variable "i" an ihn übergeben wird. Wenn der Text auf diesem Index keine Zahl darstellt, gibt StringToDouble() Null zurück, was auf einen Fehler hinweist, es sei denn, die Bondrendite ist genau Null. Beachten Sie, dass ein Anführungszeichen innerhalb einer Zeichenkette als \" kodiert ist, um es von den Anführungszeichen am Anfang und Ende einer Zeichenkette zu unterscheiden.

  i = StringFind(CharArrayToString(uc_Buffer), "<meta name=\"price\" content=", 0); // 0 == Position ab der gesucht wird
  Print("Offset of \'<meta name=\"price\" content=\' == ", i); 
  if(i == -1) {Print("String not found.");  InternetCloseHandle(iNet1);  return;} 
  i += 28; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  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); // Fertig mit wininet.
}//END void OnStart()

Das Skript ScraperBot01 kann auf jedem Chart ausgeführt werden, berichtet über seinen Fortschritt im Terminal und druckt den aus dem Web abgekratzten Wert aus.

Eine Alternative

Nachdem nun das Verfahren zum Abkratzen von Daten von einer Webseite demonstriert wurde, lassen Sie uns überlegen, was zu tun ist, wenn die Rendite der Anleihe nicht in einem Meta-Tag vorhanden waren. In diesem Fall würden wir die HTML-Datei der Webseite auf eine Quelle der Nummer untersuchen, die auf der Seite angezeigt wird. Da wir den neuesten Kurs der Anleiherendite kennen, können wir die Suchfunktion eines Texteditors nutzen, um eine Zeichenkette zu finden, die die Zahl darstellt. In der html-Datei, die wir heruntergeladen haben, befindet sich das Angebot neben dem Meta-Tag an drei weiteren Stellen. Der erste dieser drei Elemente ist ein JSON-LD strukturierter Datenausschnitt, ein HTML-Element, das Informationen über eine Webseite für Suchmaschinen und Webcrawler leicht zugänglich macht. Hier ist dieser Datenausschnitt, formatiert mit Zeilenumbrüchen zur besseren Übersichtlichkeit.

<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>

Der Algorithmus sucht zunächst nach dem Offset des Tags <script type="application/ld+json"> und findet von dort aus die Offsets von ["price":"] und von </script>. Wenn der Offset von ["price":"] kleiner als der Offset von </script> ist, deutet das darauf hin, dass er innerhalb des Datenausschnitts liegt, addieren wir 9 zum Offset von ["price":"], um zum Offset der Zahl zu gelangen, die den Zinssatz darstellt. Dieses Verfahren wird im beigefügten Skript ScraperBot02.mq5 demonstriert.

ScraperBot 02

Dieses Skript lädt die html-Datei bis zu der von uMax angegebenen Größe herunter. Bei der ersten Ausführung sollte uMax auf einen Wert gesetzt werden, der weit über der erwarteten Größe des Downloads liegt, z.B. 1 Million. Das Skript meldet die Anzahl der heruntergeladenen Bytes, und wenn sich diese gleich oder in der Nähe von uMax befindet, sollte der Wert von uMax erhöht werden. Das Skript meldet auch den Offset in der Datei des Tags <script type=\"application/ld+json\">. Der Wert von uMax kann dann etwas höher eingestellt werden als der Offset des Tags. In diesem Fall stellt sich der Offset als 166696 heraus, so dass uMax auf 180224 gesetzt ist, um genug von der Datei herunterzuladen, um den JSON-LD-Ausschnitt, aber nicht die gesamte Datei aufzunehmen. Das Skript verwendet ein statisches Array, um den Block von 16 Kilobyte herunterzuladen, die in ein dynamisches Array kopiert und gesammelt werden. Diese Arrays werden im globalen Geltungsbereich deklariert.

uchar uc_Buffer[16400], uc_DynBuf[];

ScraperBot02 gleicht ScraperBot01 bis zu dem Teil, in dem die Daten vom Webserver heruntergeladen werden und der die Daten in Blöcken unterteilt. InternetReadFile wird wiederholt in einer Do-while-Schleife aufgerufen, bis die gewünschte Datenmenge heruntergeladen wurde.

  uint uGet, uGot, uDst, uMax;
  uGet = 16384;    // Anzahl der bei einem Aufruf herunterzuladenden Bytes für die Datei InternetReadFile, es müssen mindestens 1 Byte weniger als die Größe von uc_Buffer sein.
  uGot = uDst = 0; // uGot ist Anzahl der bei einem Aufruf heruntergeladenen Bytes für die Datei InternetReadFile; uDst ist die Gesamtzahl der heruntergeladenen Bytes.
  uMax = 180224;   // Maximalzahl der herunterzuladenden Bytes.

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

    uc_Buffer[uGot] = 0; // Terminieren der Zeichenkette im uc_Buffer durch das Anfügen des Zeichens NULL.

    ArrayCopy(uc_DynBuf, // Zielarray 
              uc_Buffer, // Quellarray 
              uDst,      // Index, ab dem das Zielarray beschrieben wird 
              0,         // Index, ab dem das Kopieren des Quellarrays beginnt 
              uGot);     // Anzahl der zu kopierenden Elemente 
    uDst += uGot; // Vorschub des Index des Zielindex für den nächsten Schleifendurchlauf
  }while(bResult && uGot > 0 && uDst < uMax);
 
  Print("Size of uc_DynBuf == ", ArraySize(uc_DynBuf));
  Print("Bytes downloaded  == ", uDst);

ScraperBot02 findet nun das Tag <script type=\"application/ld+json\"> und speichert den Offset in der Indexvariablen i. Ausgehend von diesem Offset findet es ["price":"] und speichert diesen Offset in j. Dann findet es </script> am Ende des Ausschnitts und speichert es in k. Wenn j kleiner als k ist, wird 9 zu j hinzugefügt, was der Offset des Textes wird, der die Zahl darstellt, die dann in f_US in eine Dezimalzahl umgewandelt und im Log des Terminals ausgedruckt wird.

  int i, j, k; // Indices

  i = StringFind(CharArrayToString(uc_DynBuf), "<script type=\"application/ld+json\">", 0); // 0 == Position ab der gesucht wird 
  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 =Position ab der gesucht wird 
  if(j == -1) {Print("\"price\":\" not found.");  InternetCloseHandle(iNet1);  return;}
  Print("Offset of \"price\":\" == ", j); 

  k = StringFind(CharArrayToString(uc_DynBuf), "</script>", i); // i == Position ab der gesucht wird
  Print("Offset of </script> == ", k); 
  if(j > k) {Print("Offset of \"price\":\" is greater than offset of </script>");  InternetCloseHandle(iNet1);  return;}

  j += 9; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  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); // Fertig mit wininet.
}//END void OnStart()

Using developer tools

Eine weitere Quelle für den Zinssatz von diesem Webserver kann mit Hilfe von Entwicklerwerkzeugen des Chrome-Browsers gefunden werden. Öffnen Sie über die Menütaste in der rechten oberen Ecke die Entwicklertools und geben Sie https://www.marketwatch.com/investing/bond/tmubmusd10y?countrycode=bx in die Adressleiste ein. Wählen Sie die Registerkarte Netzwerk im mittleren Bereich und wählen Sie "XHR" als Art der zu überwachenden Ereignisse. Wenn Sie eines dieser Ereignisse auswählen, wird ein Fenster auf der rechten Seite geöffnet, in dem Details wie Überschriften und Antworten angezeigt werden. Das Ereignis mit der Bezeichnung "quoteByDialect..." hat eine interessante Antwort, die durch einen Rechtsklick in diesem Bereich und die Auswahl von "Select all" hervorgehoben werden kann. Drücken Sie Strg+C, um den markierten Text in die Zwischenablage zu kopieren, und fügen Sie ihn dann in einen Texteditor ein. Der Zinssatz befindet sich im Textblock nach der Zeichenkette ["CompositeTrading":{"Last":{"Price":{"Iso": "PERCENT", "Value":]. Die URL, die den Textblock abrufen soll, finden Sie unter der Registerkarte Kopfzeilen. In diesem Fall ist sie ziemlich lang: 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. Wenn Sie auf diesen Link direkt neben diesem Artikel klicken, wird der Textblock in einem Browserfenster angezeigt. Eigentlich sind es zwei Textblöcke hintereinander, denn am Ende der URL befinden sich zwei Zeichenketten des Tickersymbols, die durch ein Komma getrennt sind. Die erste, "Bond-BX-TMUBMUSD10Y", ist für die 10-jährige US-Schatzanweisung, und die zweite, "Bond-BX-TMBMKDE-10Y", ist für die 10-jährige deutsche Staatsanleihe. Das Löschen der zweiten Tickersymbolzeichenkette aus der URL reduziert die Größe des heruntergeladenen Textes von 7,1 Kilobyte auf 3,6 Kilobyte.

Das beigefügte Skript ScraperBot03 lädt den Textblock für das Tickersymbol "TMUBMUSD10Y" herunter, findet die Zeichenkette ["CompositeTrading":{"Last":{"Price":{"Iso": "PERCENT", "Value":], fügt 61 zum Offset des Zeichenkettenanfangs hinzu, verwendet diesen als Index für den Text, der die Zahl darstellt, wandelt den Text in eine Dezimalzahl um und druckt ihn im Log des Terminals aus. Das Skript ist nach dem Vorbild von ScrapterBot01 aufgebaut, so dass der Code hier nicht aufgeführt wird. Ein Vorteil dieser Ressource ist die geringe Größe des Downloads. Da die durch die URL adressierte Datei nur 3,6 Kilobyte groß ist, kann die mql5-Funktion WebRequest anstelle von Funktionen in wininet.dll zum Herunterladen verwendet werden, ohne weitaus mehr Daten als benötigt herunterzuladen zu müssen.

ScraperBot 04

Dieses Skript lädt die gleichen Daten wie ScraperBot 03 herunter und verwendet WebRequest anstelle von WinINet-Funktionen. Damit WebRequest funktioniert, muss die Basis-URL für den Server, in diesem Fall "https://api.wsj.net", in die Liste der erlaubten Server unter "Tools\Options\Expert Advisors" auf der MetaTrader-Plattform aufgenommen werden. Das globale Char-Array ch_Data übergibt keine Daten an WebRequest und existiert nur, um die Anforderung an einen Parameter dieses Typs zu erfüllen.

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; // Header der Antwort
  
  i = WebRequest("GET",     // const string  method,    HTTP method 
                 stURL,     // const string  url,       URL 
                 stHdr,     // const string  headers,  
                 1024,      // int           timeout, 
                 ch_Data,   // const char    &data[],   das Array des Körpers der HTTP-Nachricht
                 ch_Buffer, // char          &result[], ein Array mit der Antwort des Servers
                 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 == Position ab der gesucht wird 
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  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()


Andere Anleihen

Um eine Zinsdifferenz ableiten zu können, sind zwei Zinssätze erforderlich. Die Skripte, die die Rendite der 10-jährigen US-Staatsanleihe erzielen, können an die Rendite der 10-jährigen deutschen Staatsanleihe angepasst werden, indem in den URLs das Tickersymbol "TMBMKDE-10Y" durch "TMUBMUSD10Y" ersetzt wird. Die Tickersymbole für andere Nationen können ebenfalls verwendet werden. Andere Finanzwebsites können unterschiedliche Tickersymbole verwenden, aber es gilt das gleiche Prinzip. Die deutsche Anleihe wird häufig als Indikator für den Zinssatz des Euro verwendet. Das vernachlässigt dabei aber den Einfluss anderer Nationen der Europäischen Union. Ein zusammengesetzter Zinssatz für den Euro kann jedoch aus zwei oder mehr europäischen Staatsanleihen abgeleitet werden. In diesem Beispiel werden die 10-jährigen Staatsanleiherenditen der drei wichtigsten EU-Mitgliedstaaten, gemessen am BIP, dessen Währung der Euro ist, zur Ableitung eines zusammengesetzten "europäischen Anleihenzinssatzes" verwendet. Diese drei Nationen sind Deutschland, Frankreich und Italien. Ihre Anleiherenditen werden entsprechend der relativen Größe ihrer Volkswirtschaften gewichtet, wie in der folgenden Tabelle dargestellt.


2017 Bruttoinlandsprodukt, in Milliarden Euros

Deutschland3,197
Frankreich2,241
Italien1,681
Gesamt7,119


Der Gewichtungsfaktor jedes Landes ist das Verhältnis seines BIP zur Summe der drei Länder. Für Deutschland sind es 0,449, Frankreich 0,315 und Italien 0,236. Diese Faktoren ergeben zusammen 1 und werden als Koeffizienten bei der Berechnung des zusammengesetzten Wertes der Anleiherendite für den Euro gemäß der folgenden Gleichung verwendet.

f_EU = 0,449*f_DE + 0,315*f_FR + 0,236*f_IT

wobei f_EU die europäische Anleiherendite ist, f_DE die deutsche Anleiherendite ist, f_FR die französische Anleiherendite und f_IT die italienische Anleiherendite.

Zinssatzdifferenz

Das Währungspaar EUR/USD wird als Wert des Euro in US-Dollar angegeben, so dass es sich tendenziell in die gleiche Richtung wie der Wert des Euro und in die entgegengesetzte Richtung zum Dollar bewegt. Für die Bewegung der Zinsdifferenz zur Prognose der Bewegung des Währungspaares in die gleiche Richtung sollte eine Erhöhung der Rendite der europäischen Verbundanleihe die Zinsdifferenz in die positive Richtung bewegen, und eine Erhöhung der Rendite der US-T-Note sollte sie in die negative Richtung bewegen. Daher wird die Zinsdifferenz als f_EU - f_US berechnet. Zum Zeitpunkt der Erstellung dieses Artikels ist der berechnete Wert von f_EU 1,255 und der Wert von f_US 3,142. Daher ist die Zinsdifferenz 1,255 - 3,142 = -1,887. Eine Bewegung in die positive Richtung, also von -1,887 auf -1,798, würde eine Aufwärtsbewegung von EUR/USD prognostizieren. Eine Bewegung in die negative Richtung, von -1,887 auf -1,975, würde eine Abwärtsbewegung von EUR/USD prognostizieren. Die Stärke oder Zuverlässigkeit des Indikators hängt von der Größe der Bewegung der Zinsdifferenz ab. Die Veränderung der Zinssätze wird in der Regel in Basispunkten oder einem Hundertstel Prozentpunkt angegeben. Die Veränderung der Zinsdifferenz von -1,887 auf -1,975 wäre eine Bewegung von 8,8 Basispunkten in die negative Richtung, eine ziemlich starke Bewegung auf Intraday-Basis, was wahrscheinlich auf eine Abwärtsbewegung des Währungspaares im stündlichen Zeitrahmen hinweist. Eine Bewegung von nur einem oder zwei Basispunkten ist im Bereich des Rauschen des Marktes und dürfte kein zuverlässiger Indikator für die Bewegung des Währungspaares sein.

ScraperBot 05

Dieses Skript holt alle vier Anleiherenditen, berechnet die europäische Anleiherendite und druckt die Zinsdifferenz in das Log des Terminals. Es ist nach dem Vorbild von ScraperBot 04 aufgebaut, aber anstatt für jede Bondrendite eine separate Anfrage an den Server zu stellen, werden alle vier Tickersymbole an die URL angehängt und alle vier Zinssätze werden in einem 14-Kilobyte-Download zurückgegeben, der vier aufeinanderfolgende Textblöcke enthält. ScraperBot 05 lokalisiert das Tickersymbol in der Datei, bevor es die Zeichenkette des Zinssatzes findet, und kehrt mit einer Fehlermeldung zurück, wenn eine der Zeichenketten nicht gefunden wird.

char ch_Buffer[], ch_Data[16];      // global buffers
float f_US, f_DE, f_FR, f_IT, f_EU; // globale Variablen, die die Zinssätze aufnehmen

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"; // vier Tickersymbole

  string stHdr = "Accept: text/*, User-Agent: Mozilla/5.0";
  string stRspHdr; // Header der Antwort

  i = WebRequest("GET",     // const string  method,    HTTP method 
                 stURL,     // const string  url,       URL 
                 stHdr,     // const string  headers,  
                 1024,      // int           timeout, 
                 ch_Data,   // const char    &data[],   das Array des Körpers der HTTP-Nachricht
                 ch_Buffer, // char          &result[], ein Array mit der Antwort des Servers
                 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\":";

// Abrufen des Zinssatzes der 10-jährigen US-Schatzwechsel.
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMUBMUSD10Y\"", 0); // 0 == Position ab der gesucht wird
  if(i == -1) {Print("\"Ticker\":\"TMUBMUSD10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == Position ab der gesucht wird
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  f_US = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("US 10-year T-note yield, stored in variable f_US: ", f_US);

// Abrufen des Zinssatzes der 10-jährigen Staatsanleihen Deutschlands.
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKDE-10Y\"", i); // i == Position ab der gesucht wird 
  if(i == -1) {Print("\"Ticker\":\"TMBMKDE-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == Position ab der gesucht wird
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  f_DE = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("German 10-year government bond yield, stored in variable f_DE: ", f_DE);

// Abrufen des Zinssatzes der 10-jährigen Staatsanleihen Frankreichs.
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKFR-10Y\"", i); // i == Position ab der gesucht wird 
  if(i == -1) {Print("\"Ticker\":\"TMBMKFR-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == Position ab der gesucht wird
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  f_FR = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("French 10-year government bond yield, stored in variable f_FR: ", f_FR);

// Abrufen des Zinssatzes der 10-jährigen Italiens.
  i = StringFind(CharArrayToString(ch_Buffer),"\"Ticker\":\"TMBMKIT-10Y\"", i); // i == Position ab der gesucht wird 
  if(i == -1) {Print("\"Ticker\":\"TMBMKIT-10Y\" not found.");  return;}
  i = StringFind(CharArrayToString(ch_Buffer), stSearch, i); // i == Position ab der gesucht wird
  Print("Offset of ", stSearch, " == ", i); 
  if(i == -1) {Print(stSearch, " not found.");  return;}
  i += 61; // Index auf den bekannten Ort vorschieben, der den Zinssatz enthält.
  f_IT = StringToDouble(StringSubstr(CharArrayToString(ch_Buffer), i, 8));
  Print("Italian 10-year government bond yield, stored in variable f_IT: ", f_IT);

// Berechnen eines zusammengesetzten, europäischen Anleihezinssatzes
  f_EU = 0.449*f_DE + 0.315*f_FR + 0.236*f_IT;
  Print("European composite bond yield: ", f_EU);

// Berechnen der Differenz der Zinssätze.
  Print("Interest rate differential, f_EU-f_US = ", f_EU-f_US);
}//END void OnStart()

ScraperBot06.mq4 implementiert ScraperBot05.mq5 mit WinINet anstelle von WebRequest, das auf der MT4-Plattform als unzuverlässig gilt.


Delta

Änderungen der Zinsdifferenz sind für den Handel mit einem Währungspaar relevanter als die Zinsdifferenz selbst. Delta kann ausgedrückt werden als der Wert der Zinsdifferenz am Ende eines Balkens abzüglich seines Wertes am Ende des vorhergehenden Balkens. Während dieses einperiodige Delta bei längeren Zeiträumen nützlich sein kann, ist ein exponentieller gleitender Durchschnitt des Deltas bei kürzeren Zeiträumen sinnvoller, da es die Änderung mehrerer Balken berücksichtigt. Ein EMA wird berechnet, indem ein Glättungsfaktor oder Alpha auf das aktuelle Delta angewendet wird und das (1-alpha-)fache des vorherigen Wertes des EMA, genannt EMAp, hinzugefügt wird.

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

Da Delta positiv oder negativ sein kann und einen Mittelwert von Null hat, kann EMAp auf Null initialisiert werden, falls kein vorheriger Wert der EMA verfügbar ist. Der Wert von alpha kann beliebig zugewiesen werden, oder er kann aus einer beliebig zugewiesenen Anzahl von Perioden des gleitenden Durchschnitts berechnet werden. In diesem Fall,

a = 2.0 / (n+1)

wobei n die Anzahl der EMA-Perioden ist, die eine ganze oder gebrochene Zahl sein kann. Der normale Bereich von Alpha ist größer als Null, kleiner als oder gleich 1, oder 0 < a <= 1. Wenn der Wert von Alpha 1 ist, berücksichtigt die Berechnung des EMA nicht den vorherigen Wert des EMA, und der EMA wird zum aktuellen Delta.

Wenn eine historische Datenbank der Zinsdifferenz verfügbar ist, kann ein Indikator kodiert werden, um eine Zeitreihe der Zinsdifferenz oder eines EMA des Deltas auf einem Diagramm eines Währungspaares anzuzeigen. Indikatoren dürfen keine Daten aus dem Internet abrufen, aber ein Indikator kann Daten aus einer lokalen Datei der Festplatte abrufen, die durch ein Skript aktualisiert wird, das die Rendite der Anleihe aus dem Internet abruft. Die Programmierung eines solchen Indikators ist ein Thema für einen weiteren Artikel.

Schlussfolgerung

Der Quellcode aus den angehängten Skripten kann in Expert Advisors kopiert werden, um die Erfassung grundlegender Daten zu automatisieren. Die in diesem Artikel demonstrierten Techniken, um die Renditen von Anleihen aus dem Internet zu kratzen, können auf andere Finanz-Websites und andere Währungspaare als die in den Beispielen verwendeten angewendet werden. Wenn sich eine durch eine URL angesprochene Ressource ändert und nicht mehr funktioniert, hat der Programmierer das Wissen und die Mittel, um herauszufinden, was falsch ist und eine Lösung zu finden. Ein braver Bot lädt nicht mehr Daten herunter als nötig und stellt keine zu häufigen Anfragen, schon gar nicht bei jedem Tick eines Währungspaares. Die Anleiherenditen sind weniger volatil als die Wechselkurse und sind wahrscheinlich alle fünf Minuten die höchste Frequenz, die zur Aktualisierung der Anleiherenditen erforderlich ist, die als Indikator für den Handel dient. Ebenso sind Informationen, die von Websites bezogen werden, nicht unbedingt öffentlich zugänglich und sollten nicht weiterverteilt werden. Richtig eingesetzt, hat die automatisierte Erfassung von Fundamentaldaten das Potenzial, den automatisierten Handel profitabel zu machen.