
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 21): Testen mit Wirtschaftskalenderdaten
Einführung
Wir setzen die Serie über assistentengestützte Expert Advisors fort, indem wir uns ansehen, wie Nachrichten des Wirtschaftskalenders in einen Expert Advisor integriert werden können, um entweder eine Idee zu bestätigen oder ein robusteres Handelssystem aufzubauen, nicht zuletzt dank dieses Artikels. Dieser Artikel ist der erste Teil einer Serie. Daher ermutige ich die Leser zu lesen und zu folgen. Wir konzentrieren uns aber hier nur darauf, wie der Assistent einen Expert Advisors erstellen könnte, der von diesen MQL5 IDE-Tools profitieren. Für neue Leser gibt es hier und hier einführende Artikel über die Entwicklung und Zusammenstellung von Expert Advisors mit dem MQL5-Assistenten.
Wirtschaftsdaten können die Quelle eines Handelsvorteils oder eines Vorteils in einem Handelssystem sein, da sie sich mehr an den „Fundamentaldaten“ von Wertpapieren orientieren, im Gegensatz zu den „technischen Daten“, die in Form von traditionellen Indikatoren, nutzerdefinierten Indikatoren und anderen Preisaktionsinstrumenten vorherrschen. Diese „Fundamentaldaten“ können die Form von Inflationsraten, Zinssätzen der Zentralbanken, Arbeitslosenquoten, Produktivitätsdaten und einer Reihe anderer Nachrichten haben, die sich in der Regel stark auf die Wertpapierkurse auswirken, was sich in ihrer Volatilität bei jeder Veröffentlichung zeigt. Am bekanntesten dürfte die Beschäftigung außerhalb der Landwirtschaft (Non-Farm-Payrolls) sein, die fast jeden ersten Freitag im Monat veröffentlicht werden. Darüber hinaus gibt es sicherlich noch andere wichtige Nachrichtenpunkte, die nicht das nötige Rampenlicht erhalten und daher von den meisten Händlern übersehen werden, weshalb das Testen von Strategien, die auf diesen Wirtschaftsnachrichtenpunkten basieren, dazu beitragen könnte, einige dieser Punkte aufzudecken und somit dem angehenden Händler einen Vorteil zu verschaffen.
Die Datenbank SQLite kann innerhalb der MetaEditor-IDE erstellt werden, und da es sich dabei um Datenspeicher handelt, sollten wir auf dem Papier in der Lage sein, diese als Datenquelle für einen Expert Advisor zu verwenden, sodass sie als Indikatorpuffer fungieren. Darüber hinaus können sie die Wirtschaftsdaten lokal speichern, was Offline-Tests und auch die Verwendung für den Fall ermöglicht, dass die Nachrichtendatenquelle aus unbekannten Gründen beschädigt wird, was ein ständiges Risiko darstellt, da einige (oder zwangsläufig die meisten) Datenpunkte veralten. In diesem Artikel untersuchen wir, wie SQLite-Datenbanken verwendet werden können, um Wirtschaftskalender-Nachrichten zu archivieren, sodass assistentengestützte Expert Advisors diese nutzen können, um Handelssignale zu generieren.
Aktuelle Einschränkungen und Umgehungsmöglichkeiten
Allerdings gibt es einen Haken. Abgesehen von der Unfähigkeit, Wirtschaftskalenderdaten im Strategietester zu lesen, scheint es bei meinen Tests zum Lesen von Datenbanken im Strategietester eine ähnliche Einschränkung zu geben. Zum Zeitpunkt des Verfassens dieses Artikels könnte es sich um einen Programmierfehler meinerseits handeln, aber ich versuche, mit diesem Eintrag Datenbankdaten zu lesen:
//+------------------------------------------------------------------+ //| Read Data //+------------------------------------------------------------------+ double CSignalEconData::Read(string DB, datetime Time, string Event) { double _data = 0.0; //--- create or open the database in the common terminal folder ResetLastError(); int _db_handle = DatabaseOpen(DB, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(_db_handle == INVALID_HANDLE) { Print("DB: ", DB, " open failed with err: ", GetLastError()); return(_data); } string _sql = " SELECT ACTUAL " + " FROM " + " ( " + " SELECT ACTUAL " + " FROM PRICES " + " WHERE DATE <= '" + TimeToString(Time) + "' " + " AND EVENT = '" + Event + "' " + " ORDER BY DATE DESC " + " LIMIT 1 " + " ) "; int _request = DatabasePrepare(_db_handle, _sql); if(_request == INVALID_HANDLE) { Print("request failed with err: ", GetLastError()); DatabaseClose(_db_handle); return(_data); } while(DatabaseRead(_request)) { //--- read the values of each field from the obtained entry ResetLastError(); if(!DatabaseColumnDouble(_request, 0, _data)) { Print(" DatabaseRead() failed with err: ", GetLastError()); DatabaseFinalize(_request); DatabaseClose(_db_handle); } } return(_data); }
Ergibt den Fehler 5601 mit der Meldung, dass die Tabelle, auf die ich zuzugreifen versuche, nicht existiert! Wenn ich jedoch das exakte SQL-Skript entweder in der Datenbank-IDE von MetaEditor oder in einem Skript ausführe, das an ein Chart angehängt ist, habe ich keine solchen Probleme, da das erwartete Ergebnis zurückgegeben wird. Es könnte sich also um ein Versehen meinerseits handeln, bei dem es einen zusätzlichen Code gibt, den ich einfügen muss, damit das Programm im Strategietester läuft, ODER der Zugriff auf Datenbanken im Strategietester ist nicht erlaubt. Der Chatbot von Service Desk kann nicht helfen!
Was könnten wir also in dieser Situation tun? Wie bereits erwähnt, bietet die lokale Archivierung von Wirtschaftsdaten in einer Datenbank zweifellos Vorteile, sodass es schade wäre, dies nicht weiterzuentwickeln und darauf aufbauende Expert Advisors zu testen und zu entwickeln. Als Abhilfe schlage ich vor, die Wirtschaftsdaten in eine CSV-Datei zu exportieren und diese im Strategietester zu lesen.
Obwohl man sich in diesem Fall auf CSV-Dateien verlässt und sie als Behelfslösung verwendet, gibt es eine Reihe von Herausforderungen und Einschränkungen, wenn man glaubt, dass sie Datenbanken ersetzen könnten. Man könnte argumentieren, dass man die Daten nicht erst in eine Datenbank und dann in eine CSV-Datei exportieren sollte, sondern einfach direkt in die CSV-Datei. Nun, hier ist der Grund dafür.
CSV-Dateien sind bei der Speicherung von Daten wesentlich ineffizienter als Datenbanken. Dies zeigt sich an einer Reihe von Faktoren, zu denen vor allem die Datenintegrität und -validierung gehören. Datenbanken erzwingen Integritäts- und Beschränkungsprüfungen über Primär- und Fremdschlüssel, während CSV-Dateien diese Fähigkeit eindeutig vermissen lassen. Zweitens sind Leistung und Skalierbarkeit eine Stärke von Datenbanken dank der Indexierung, mit der große Datensätze sehr effizient durchsucht werden können, während CSV-Dateien immer auf eine lineare Suche angewiesen sind, die bei großen Datenmengen sehr langsam sein kann.
Drittens ist der gleichzeitige Zugriff in den meisten Datenbanken eingebaut, was den Echtzeit-Zugriff für mehrere Nutzer ermöglichen kann, während dies bei CSV-Dateien nicht möglich ist. Darüber hinaus bieten Datenbanken einen gesicherten Zugang mit Funktionen wie Nutzerauthentifizierung, rollenbasierte Zugangskontrolle und Verschlüsselung. CSV-Dateien bieten standardmäßig keine Sicherheit, was den Schutz sensibler Daten erschwert.
Darüber hinaus bieten Datenbanken automatisierte Tools für die Sicherung und Wiederherstellung, was bei CSV nicht der Fall ist; Datenbanken unterstützen komplexe Abfragen, die Joins und Manipulationen mit SQL für eine gründliche Analyse verwenden, während CSV-Dateien Skripte von Drittanbietern erfordern würden, um die gleiche Fähigkeit zu erreichen. Datenbanken bieten die Regeltreue der Transaktionen durch ACID, was bei CSV-Dateien nicht der Fall ist.
Um fortzufahren, unterstützen Datenbanken auch eine Normalisierung, was die Redundanz der Daten reduziert und daher eine kompaktere, effizientere Speicherung mit weniger Duplikaten ermöglicht, während die flache Struktur von CSV zwangsläufig zu einer Menge Redundanz führt. Datenbanken unterstützen auch die Versionskontrolle (was wichtig ist, da viele Daten im Laufe der Zeit aktualisiert werden können), eine Funktion, die für Prüfungen von entscheidender Bedeutung ist und die CSV-Dateien nicht bieten. CSV-Dateien können bei Datenaktualisierungen beschädigt werden, und die Verwaltung komplexer Datenstrukturen stellt eine Herausforderung dar. Es gibt noch viele andere entscheidende Vorteile von Datenbanken gegenüber CSV-Dateien, aber wir beschränken uns auf diese, um die Vorteile hervorzuheben. Jeder der genannten Vorteile kann eine entscheidende Rolle bei der Kuratierung von Wirtschaftsdaten für Analysen und Studien spielen, insbesondere über längere Zeiträume hinweg, was bei CSV-Dateien unhandlich ist.
Bevor wir jedoch die Daten in eine CSV-Datei exportieren, auf die der Strategietester zugreifen kann, sollten wir die Datenbank aufbauen und die Wirtschaftsdaten in sie laden, was wir im Folgenden behandeln.
Aufbau der SQLite-Datenbank
Um unsere SQLite-Datenbank zu erstellen, verwenden wir ein Skript. Die Liste dazu finden Sie weiter unten:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ //| Sourced from: https://www.mql5.com/en/articles/7463#database_functions //| and here: https://www.mql5.com/en/docs/database/databasebind //+------------------------------------------------------------------+ void OnStart() { //--- create or open a database string _db_file = __currency + "_econ_cal.sqlite"; int _db_handle = DatabaseOpen(_db_file, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(_db_handle == INVALID_HANDLE) { Print("DB: ", _db_file, " open failed with code ", GetLastError()); return; } else Print("Database: ", _db_file, " opened successfully"); ... ... }
Dieser Code stammt mit ein paar Änderungen, größtenteils von hier. Die Erstellung einer Datenbank erfolgt über ein Handle, ähnlich wie bei der Deklaration eines Handles zum Lesen oder Schreiben einer Datei. Wir erstellen eine Datenbank für jedes Währungspaar, was zugegebenermaßen verschwenderisch und sehr unhandlich ist. Ein besserer Ansatz wäre es gewesen, all diese wirtschaftlichen Datenpunkte über alle Währungen hinweg in einer einzigen Datenbank zu speichern. Sobald unser Handle erstellt ist, müssen wir überprüfen, ob dieses Handle gültig ist, bevor wir fortfahren. Wenn sie gültig ist, bedeutet dies, dass wir eine leere Datenbank haben und daher mit der Erstellung der Tabelle für unsere Daten fortfahren können. Wir nennen unsere Tabellenpreise, weil wir uns in diesem Artikel nur auf den Sektor Kalenderereignis vom Typ Sektorpreise („sector prices“). Dies ist ein übergeordneter Sektor, der nicht nur Daten zur Inflationsrate, sondern auch Verbraucher- und Erzeugerpreisindizes umfasst und auf den wir uns konzentrieren, da wir eine nutzerdefinierte Signalklasse entwickeln wollen, die ihre Kauf- oder Verkaufs-Bedingungen auf die relativen Inflationsraten des gehandelten Währungspaares stützt. Für die Entwicklung dieser langen und kurzen Zustandssignale könnten viele alternative Ansätze gewählt werden, der hier gewählte Weg ist wahrscheinlich einer der einfachsten. Bei der Erstellung der Tabelle wird, wie bei den meisten Datenbankobjekten, zunächst geprüft, ob sie existiert, und wenn ja, wird sie gelöscht (fallen gelassen), damit wir die Tabelle erstellen können, die wir befüllen und verwenden wollen. Der entsprechende Eintrag ist weiter unten zu finden:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ... //--- if the PRICES table exists, delete it if(DatabaseTableExists(_db_handle, "PRICES")) { //--- delete the table if(!DatabaseExecute(_db_handle, "DROP TABLE PRICES")) { Print("Failed to drop table PRICES with code ", GetLastError()); DatabaseClose(_db_handle); return; } } //--- create the PRICES table if(!DatabaseExecute(_db_handle, "CREATE TABLE PRICES(" "DATE TEXT ," "FORECAST REAL ," "ACTUAL REAL ," "EVENT TEXT);")) { Print("DB: ", _db_file, " create table failed with code ", GetLastError()); DatabaseClose(_db_handle); return; } //--- display the list of all fields in the PRICES table if(DatabasePrint(_db_handle, "PRAGMA TABLE_INFO(PRICES)", 0) < 0) { PrintFormat("DatabasePrint(\"PRAGMA TABLE_INFO(PRICES)\") failed, error code=%d at line %d", GetLastError(), __LINE__); DatabaseClose(_db_handle); return; } ... }
Unsere erstellte Tabelle wird einfach 4 Spalten enthalten, nämlich die Spalte „DATE“, die vom Typ Text ist und den Zeitpunkt der Veröffentlichung der Wirtschaftsnachrichten festhält, die Spalte „FORECAST“ für prognostizierte Wirtschaftsdaten, die vom Typ reell ist, die Spalte „ACTUAL“, die ebenfalls vom Typ reell ist und die aktuellen Wirtschaftsdaten für das jeweilige Datum enthält, und schließlich die Spalte „EVENT“, die vom Typ Text ist und dazu beiträgt, diesen Datenpunkt ordnungsgemäß zu kennzeichnen, da wir an jedem Datum für eine bestimmte Währung mehrere Datenpunkte innerhalb der Kategorie „Preise im Ereignissektor“ haben können. Die Art der für jeden Datenpunkt verwendeten Kennzeichnung entspricht also dem Ereigniscode der Daten. Der Grund dafür ist, dass wir beim Abrufen der Wirtschaftskalenderdaten die Funktion „CalendarValueHistoryByEvent“ verwenden, um Kalendernachrichtenwerte zurückzugeben, die mit bestimmten Ereignissen verknüpft sind. Jedes dieser Ereignisse hat einen beschreibenden Code, und diese Codes ordnen wir unseren Daten zu, wenn wir sie in der Datenbank speichern. Das Listing für die Funktion „Get“, die diese Wirtschaftskalenderdaten abruft, ist nachstehend aufgeführt:
//+------------------------------------------------------------------+ //| Get Currency Events //+------------------------------------------------------------------+ bool Get(string Currency, datetime Start, datetime Stop, ENUM_CALENDAR_EVENT_SECTOR Sector, string &Data[][4]) { ResetLastError(); MqlCalendarEvent _event[]; int _events = CalendarEventByCurrency(Currency, _event); printf(__FUNCSIG__ + " for Currency: " + Currency + " events are: " + IntegerToString(_events)); // MqlCalendarValue _value[]; int _rows = 1; ArrayResize(Data, __COLS * _rows); for(int e = 0; e < _events; e++) { int _values = CalendarValueHistoryByEvent(_event[e].id, _value, Start, Stop); // if(_event[e].sector != Sector) { continue; } printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", belongs to sector: " + EnumToString(_event[e].sector)); // _rows += _values; ArrayResize(Data, __COLS * _rows); for(int v = 0; v < _values; v++) { // printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", for value: " + TimeToString(_value[v].period) + " on: " + TimeToString(_value[v].time) + ", has... "); // Data[_rows - _values + v - 1][0] = TimeToString(_value[v].time); // if(_value[v].HasForecastValue()) { Data[_rows - _values + v - 1][1] = DoubleToString(_value[v].GetForecastValue()); } if(_value[v].HasActualValue()) { Data[_rows - _values + v - 1][2] = DoubleToString(_value[v].GetActualValue()); } // Data[_rows - _values + v - 1][3] = _event[e].event_code; } } return(true); }
Wir verwenden ein mehrdimensionales Text-Array mit dem Namen „_data“, um die Daten des Wirtschaftskalenders abzurufen, und seine zweite Dimension entspricht der Anzahl der Spalten in der Tabelle „PRICES“, die wir zum Speichern der Daten verwenden werden, was bedeutet, dass die Anzahl seiner Zeilen der Anzahl der Datenzeilen entspricht, die wir in die Tabelle „PREISE“ einfügen werden. Um das Laden der Daten aus unserem Array in die Tabelle zu beschleunigen, verwenden wir zunächst die Funktionen „DatabaseTransactionBegin()“ und „DatabaseTransactionCommit()“, um die Datenschreiboperationen einzuleiten bzw. zu beenden. Dies wird hier in dem oben bereits erwähnten Artikel als effizienterer Weg im Vergleich zur Arbeit ohne sie erklärt. Zweitens verwenden wir die Funktion der Datenbindung, um unsere Array-Daten tatsächlich in die Datenbank zu schreiben. Da unsere Datenspalten mit der Zieldatentabelle übereinstimmen, ist auch dieser Vorgang relativ einfach und sehr effizient, auch wenn er etwas langwierig ist, wie die folgende Auflistung zeigt:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ //| Sourced from: https://www.mql5.com/en/articles/7463#database_functions //| and here: https://www.mql5.com/en/docs/database/databasebind //+------------------------------------------------------------------+ void OnStart() { ... //--- create a parametrized _sql_request to add _points to the PRICES table string _sql = "INSERT INTO PRICES (DATE,FORECAST,ACTUAL,EVENT)" " VALUES (?1,?2,?3,?4);"; // _sql_request parameters int _sql_request = DatabasePrepare(_db_handle, _sql); if(_sql_request == INVALID_HANDLE) { PrintFormat("DatabasePrepare() failed with code=%d", GetLastError()); Print("SQL _sql_request: ", _sql); DatabaseClose(_db_handle); return; } //--- go through all the _points and add them to the PRICES table string _data[][__COLS]; Get(__currency, __start_date, __stop_date, __event_sector, _data); int _points = int(_data.Size() / __COLS); bool _request_err = false; DatabaseTransactionBegin(_db_handle); for(int i = 0; i < _points; i++) { //--- set the values of the parameters before adding a data point ResetLastError(); string _date = _data[i][0]; if(!DatabaseBind(_sql_request, 0, _date)) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } //--- if the previous DatabaseBind() call was successful, set the next parameter if(!DatabaseBind(_sql_request, 1, _data[i][1])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } if(!DatabaseBind(_sql_request, 2, _data[i][2])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } if(!DatabaseBind(_sql_request, 3, _data[i][3])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } //--- execute a _sql_request for inserting the entry and check for an error if(!DatabaseRead(_sql_request) && (GetLastError() != ERR_DATABASE_NO_MORE_DATA)) { PrintFormat("DatabaseRead() failed with code=%d", GetLastError()); DatabaseFinalize(_sql_request); _request_err = true; break; } else PrintFormat("%d: added data for %s", i + 1, _date); //--- reset the _sql_request before the next parameter update if(!DatabaseReset(_sql_request)) { PrintFormat("DatabaseReset() failed with code=%d", GetLastError()); DatabaseFinalize(_sql_request); _request_err = true; break; } } //--- done going through all the data points //--- transactions status if(_request_err) { PrintFormat("Table PRICES: failed to add %s data", _points); DatabaseTransactionRollback(_db_handle); DatabaseClose(_db_handle); return; } else { DatabaseTransactionCommit(_db_handle); PrintFormat("Table PRICES: added %d data", _points); } ... }
Mit den in die Tabelle „PRICE“ eingefügten Daten müssen wir nun eine CSV-Datei aus unserer Datenbank erstellen, da der Zugriff mit dem Strategietester anscheinend gesperrt ist. Zusammenfassend lässt sich sagen, dass unsere Funktion „Read()“, die SQL zum Lesen der Datenbank enthält, in MetaEditor problemlos ausgeführt werden kann, wie in der folgenden Abbildung dargestellt:
Wenn wir das Skript „sql_read“ (vollständiger Quelltext unten) auf einem beliebigen Chart mit ähnlichen Zeiteingaben starten und die USD-Datenbank abfragen, erhalten wir das gleiche Ergebnis, was bedeutet, dass es kein Problem mit der Datenbank unter MetaEditor IDE oder der MT5-Terminalumgebung gibt. Bitte sehen Sie sich das nachstehende Bild des Protokolls an:
Das Anhängen und Ausführen des Skripts bedeutet möglicherweise, dass ein auf einem Chart gestarteter Expert Advisor problemlos Datenbankwerte lesen kann. Für unsere Zwecke können wir jedoch derzeit keine Datenbankwerte lesen, wenn wir den Strategietester ausführen, und das bedeutet, dass wir uns auf CSV-Dateien verlassen müssen.
Exportieren von Wirtschaftskalenderdaten
Um Daten in eine CSV-Datei zu exportieren, verwenden wir einfach eine der eingebauten Funktionen „DatabaseExport()“, wie im folgenden Code gezeigt:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ... //--- save the PRICES table to a CSV file string _csv_file = "PRICES.csv"; if(DatabaseExport(_db_handle, "SELECT * FROM PRICES", _csv_file, DATABASE_EXPORT_HEADER | DATABASE_EXPORT_INDEX | DATABASE_EXPORT_QUOTED_STRINGS, ";")) Print("Database: table PRICES saved in ", _csv_file); else Print("Database: DatabaseExport(\"SELECT * FROM PRICES\") failed with code", GetLastError()); //--- close the database file and inform of that DatabaseClose(_db_handle); PrintFormat("Database: %s created and closed", _db_file); }
Dieser Ansatz ist der am wenigsten codeintensive Weg, denn wenn wir z. B. zuerst die Daten in ein Objekt (z. B. Array) auswählen und dann eine Schleife durch alle Array-Werte ziehen und sie in einer kommagetrennten Zeichenkette speichern würden, die wir dann exportieren, würden wir das gleiche Ergebnis erzielen, aber ich bin mir fast sicher, dass das, was wir hier angenommen haben, abgesehen von der Codierungshektik, eine viel kürzere Ausführungszeit hat als der Ansatz einer for-Schleife. Das könnte daran liegen, dass SQLite eine C-Sprachbibliothek ist und MQL5 ebenfalls stark auf C basiert.
Unsere Datenbanktabelle für „PRICES“ hat keinen expliziten Primärschlüssel, und diese Schlüssel sind bei großen Datensätzen wichtig, um Indizes zu erstellen, die Datenbanken zu einem schnellen und leistungsfähigen Werkzeug machen. Eine Änderung dieser Tabelle könnte darin bestehen, entweder eine automatisch inkrementierende Spalte hinzuzufügen, die als Primärschlüssel dient, oder die Spalten „EVENT“ und „DATE“ zu kombinieren, damit beide Primärschlüssel sind, da die kombinierten Werte in beiden Spalten vom Standpunkt des Designs aus gesehen in allen Datenzeilen eindeutig sind. Die Mehrdeutigkeit der Codes, die zur Kennzeichnung der in der Spalte „EVENT“ gespeicherten Ereignisse verwendet werden, erfordert zusätzliche Sorgfalt, um sicherzustellen, dass der Datenpunkt, an dem wir interessiert sind, auch der ist, den wir tatsächlich abrufen.
In diesem Artikel konzentrieren wir uns zum Beispiel auf das Währungspaar GBPUSD, d. h. die beiden Währungen, die uns interessieren, sind GBP und USD. (Man beachte, dass wir den EUR umgangen haben, da es mehrere Datenpunkte nicht nur aus der Eurozone, sondern auch aus ihren Mitgliedsländern gibt!) Wenn wir uns die Ereigniscodes für Inflationsdaten für diese weniger mehrdeutigen Währungen ansehen, haben wir für GBP den „cpi-yy“ und für USD den „core-pce-price-index-yy“. Denken Sie daran, dass es noch andere Codes für die jährliche Verbraucherpreisinflation (cpi) in USD gibt, die wir nicht berücksichtigen werden, daher sollten Sie bei der Auswahl sorgfältig vorgehen. Außerdem ist diese Kennzeichnung an sich nicht genormt, was bedeutet, dass sie in einigen Jahren oder sogar noch später überarbeitet werden könnte, sodass auch alle automatisierten Systeme ihren Code aktualisieren müssen. Dies könnte auf die Idee hindeuten, dass man seine eigene nutzerdefinierte Kennzeichnungen mit einer Datenvalidierung aus den Kalenderdaten hat, um sicherzustellen, dass die richtigen Daten korrekt kodiert werden, aber wie bereits erwähnt, wäre von Zeit zu Zeit eine menschliche Kontrolle erforderlich, da die Kodierung in einem Augenblick geändert werden könnte.
MQL5 Signalklasse
Wie bereits erwähnt, verwenden wir hierfür CSV-Dateien, und obwohl dies ein unkomplizierter Prozess sein sollte, könnte die Formatierung als ANSI oder UTF8 beim Lesen dieser Daten einige Herausforderungen mit sich bringen, wenn man sich der Unterschiede nicht bewusst ist. Wir haben eine Standard-CSV-Lesefunktion von hier übernommen, um die exportierten CSV-Daten zu lesen, und sie werden in die Funktion geladen, die die Indikatoren für jede Währung initialisiert. Die Margin-Währung (GBP) und die Profit-Währung (USD). Dabei gibt es zwangsläufig Einschränkungen beim Lesen großer CSV-Dateien, da sie den Arbeitsspeicher belasten. Eine mögliche Lösung könnte darin bestehen, die CSV-Datei nach Zeit zu partitionieren, sodass bei der Initialisierung nur eine der Dateien geladen wird, und wenn der letzte Datenpunkt für die aktuelle Zeit im Strategietester zu alt ist, wird diese Datei „freigegeben“ und eine neuere CSV-Datei wird geladen.
Diese Umgehungslösungen beheben alle Probleme, die nicht bestehen würden, wenn der Datenbankzugriff in Strategy Tester möglich wäre. Da unsere Signalklasse also nicht aus einer Datenbank liest, nimmt sie einfach die Namen der CSV-Dateien für die Margin-Währung und die Profit-Währung als Input. Bei unserer Signalklasse wird der einzige Zeitreihenpuffer, den wir verwenden werden, die Klasse „m_time“ sein, und streng genommen brauchen wir den Puffer gar nicht, da die aktuelle Zeit ausreicht, aber er wird hier verwendet, um die Zeit bei Index Null zu erhalten. Der Abruf der Kalenderdatenwerte aus der geladenen CSV-Datei erfolgt über die Funktion „Read“, deren Code unten dargestellt ist:
//+------------------------------------------------------------------+ //| Read Data //+------------------------------------------------------------------+ double CSignalEconData::Read(datetime Time, SLine &Data[]) { double _data = 0.0; int _rows = ArraySize(Data); _data = StringToDouble(Data[0].field[1]); for(int i = 0; i < _rows; i++) { if(Time >= StringToTime(Data[i].field[0])) { _data = StringToDouble(Data[i].field[1]); } else if(Time < StringToTime(Data[i].field[0])) { break; } } return(_data); }
Sie ist iterativ, da sie eine for-Schleife verwendet. Hätten wir jedoch auf die gleichen Daten aus einer indizierten Datenbank zugreifen können, wäre der gleiche Vorgang viel schneller ausgeführt worden. Bei kleinen Datensätzen, wie sie für diesen Artikel verwendet wurden, könnte dieser Leistungsunterschied übersehen werden, aber je größer der Datensatz wird und je mehr Verlaufsdaten betrachtet werden, desto stärker wird das Argument für das Lesen von SQLite innerhalb von Strategy Tester.
Die Lesefunktion wird sowohl für die Margin-Währung als auch für die Profit-Währung aufgerufen und liefert die letzten Inflationswerte. Unser Signal wird einfach auf der Grundlage der relativen Größe dieser Werte erzeugt. Wenn die Margin-Währung eine höhere Inflationsrate hat als die Profit-Währung, dann würden wir das Paar verkaufen. Ist die Inflationsrate der Margin-Währung hingegen geringer, dann würden wir kaufen. Diese Logik ist unten als Teil der Funktion „LongCondition()“ dargestellt:
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalEconData::LongCondition(void) { int result = 0; m_time.Refresh(-1); double _m = Read(m_time.GetData(0), m_margin_data); double _p = Read(m_time.GetData(0), m_profit_data); if(_m < _p) { result = int(100.0 * ((_p - _m) / _p)); } return(result); }
Wenn wir den Expert Advisor ohne jegliche Optimierung mit den allerersten Standardeinstellungen nach der Zusammenstellung mit dem Assistenten und der Kompilierung ausführen, erhalten wir die folgenden Ergebnisse:
Die Inflation ist eindeutig ein entscheidender Faktor für die Entwicklung der Währungspaare. Die von uns verwendeten Inflationsdaten werden monatlich veröffentlicht, sodass unser Testzeitraum ebenfalls monatlich ist. Dies muss jedoch nicht zwangsläufig der Fall sein, denn auch bei kleineren Zeitrahmen kann die gleiche Position beibehalten werden, während nach schärferen oder besseren Einstiegspunkten gesucht wird. Das getestete Paar ist GBPUSD.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass SQLite-Datenbanken viele Vorteile bieten, da sie die Speicherung und Pflege von nutzerdefinierten Datensätzen ermöglichen. Wirtschaftskalenderdaten, die zu wichtigen Nachrichtenereignissen veröffentlicht werden, sind ein solcher Datensatz, der für weitere Analysen archiviert werden könnte, um zu verstehen, was die Haupttreiber des Marktgeschehens sind. Eine sehr einfache Strategie, wie die in diesem Artikel beschriebene, die sich auf die Inflation konzentriert, kann den Unterschied zu einem System ausmachen, das auch technische Indikatoren verwendet. Wie immer handelt es sich hierbei nicht um eine Anlageberatung, und der Leser wird dazu angehalten, seine eigene, sorgfältige Prüfung durchzuführen, bevor er die in diesem Artikel oder dieser Serie vorgestellten Ideen umsetzt.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/14993





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.