English Русский 日本語
preview
Entwicklung eines Expert Advisors in MQL5 für Ausbrüche nach kalenderbasierten Nachrichtenereignissen

Entwicklung eines Expert Advisors in MQL5 für Ausbrüche nach kalenderbasierten Nachrichtenereignissen

MetaTrader 5Beispiele |
131 5
Zhuo Kai Chen
Zhuo Kai Chen

Einführung

Die Volatilität erreicht ihren Höhepunkt in der Regel in der Nähe von Ereignissen mit hohem Nachrichtenwert, wodurch sich erhebliche Ausbruchschancen ergeben. In diesem Artikel werden wir den Implementierungsprozess einer kalenderbasierten Ausbruchsstrategie in MQL5 beschreiben. Wir werden alles von der Erstellung einer Klasse zur Interpretation und Speicherung von Kalenderdaten über die Entwicklung realistischer Backtests mit diesen Daten bis hin zur Implementierung von Ausführungscode für den Live-Handel behandeln.


Motivation

Die MQL5-Community bietet zwar zahlreiche Artikel und Codebases zur Handhabung von MetaTrader 5-Kalendern im Backtesting an, doch können diese Ressourcen für Anfänger, die eine einfache Ausbruchsstrategie entwickeln möchten, zu komplex sein. Dieser Artikel soll den Prozess der Erstellung einer Strategie unter Verwendung des Nachrichtenkalenders in MQL5 vereinfachen und eine umfassende Anleitung für Händler bieten.

Die Motivation für die Entwicklung einer Handelsstrategie für Kalendernachrichten liegt in der Ausnutzung des vorhersehbaren Timings geplanter Nachrichtenereignisse - wie z. B. Wirtschaftsberichte, Gewinnveröffentlichungen oder geopolitische Ankündigungen -, die oft erhebliche Marktvolatilität und Kursbewegungen auslösen. Indem sie diese Ereignisse antizipieren, versuchen Händler, Ausbruchschancen zu nutzen, wenn sich die Kurse im Anschluss an die Nachrichten entscheidend über etablierte Unterstützungs- oder Widerstandsniveaus hinaus bewegen. Diese Strategie zielt darauf ab, die Gewinne aus der erhöhten Liquidität und der Dynamik im Zusammenhang mit Nachrichtenmeldungen zu maximieren und gleichzeitig ein diszipliniertes Risikomanagement anzuwenden, um die erhöhte Unsicherheit zu bewältigen. Letztlich bietet es einen strukturierten Ansatz, um die Muster und Reaktionen zu nutzen, die typischerweise an den Märkten rund um wichtige Kalenderereignisse auftreten.


Kalender Nachrichten Backtest

MQL5 verfügt über Standardoperationen für die Verarbeitung von Nachrichtenkalenderdaten, die von Brokern bereitgestellt werden. Diese Daten können jedoch mit dem vorhandenen Code nicht im Strategietester abgefragt werden. Daher können wir eine von Rene Balke übernommene Include-Datei für den Kalenderverlauf erstellen, die die Daten des Nachrichtenverlaufs verarbeitet und in einer Binärdatei speichert, die wir später verwenden werden.

  • Die Klasse CCalendarEntry repräsentiert ein einzelnes Wirtschaftskalenderereignis mit verschiedenen Eigenschaften, die sich auf das Land, die Einzelheiten des Ereignisses und die damit verbundenen Werte (z. B. Vorhersage, tatsächliche Werte, vorherige Werte usw.) beziehen.
  • Die Methode Compare() vergleicht zwei Kalenderereignisse auf der Grundlage ihres Zeitpunkts und ihrer Bedeutung und gibt einen Wert zurück, der angibt, welches Ereignis als wichtiger angesehen wird. 
  • Die Methode ToString() wandelt die Ereignisdaten in ein für den Menschen lesbares String-Format um, einschließlich der Bedeutung des Ereignisses und anderer relevanter Eigenschaften.

//+------------------------------------------------------------------+
//| A class to represent a single economic calendar event            |
//+------------------------------------------------------------------+
class CCalendarEntry :public CObject {
public:
   ulong country_id;
   string country_name;
   string country_code;
   string country_currency;
   string country_currency_symbol;
   string country_url_name;
   
   ulong event_id;
   ENUM_CALENDAR_EVENT_TYPE event_type;
   ENUM_CALENDAR_EVENT_SECTOR event_sector;
   ENUM_CALENDAR_EVENT_FREQUENCY event_frequency;
   ENUM_CALENDAR_EVENT_TIMEMODE event_time_mode;
   ENUM_CALENDAR_EVENT_UNIT event_unit;
   ENUM_CALENDAR_EVENT_IMPORTANCE event_importance;
   ENUM_CALENDAR_EVENT_MULTIPLIER event_multiplier;
   uint event_digits;
   string event_source_url;
   string event_event_code;
   string event_name;
      
   ulong value_id;
   datetime value_time;
   datetime value_period;
   int value_revision;
   long value_actual_value;
   long value_prev_value;
   long value_revised_prev_value;
   long value_forecast_value;
   ENUM_CALENDAR_EVENT_IMPACT value_impact_type;
   
//+------------------------------------------------------------------+
//| Compare news importance function                                 |
//+------------------------------------------------------------------+
   int Compare(const CObject *node, const int mode = 0) const{
      CCalendarEntry* other = (CCalendarEntry*)node;
      if (value_time==other.value_time){
         return event_importance-other.event_importance;
      }
      return (int)(value_time -other.value_time);
   }
   
//+------------------------------------------------------------------+
//| Convert data to string function                                  |
//+------------------------------------------------------------------+
   string ToString(){
      string txt;
      string importance = "None";
      if(event_importance==CALENDAR_IMPORTANCE_HIGH)importance="High";
      else if(event_importance==CALENDAR_IMPORTANCE_MODERATE) importance = "Moderate";
      else if(event_importance==CALENDAR_IMPORTANCE_LOW)importance = "Low";
      StringConcatenate(txt,value_time,">",event_name,"(",country_code,"|",country_currency,")",importance);
      return txt;
     }
   
};

  • Die Klasse CCalendarHistory verwaltet eine Sammlung von Objekten der Klasse CCalendarEnrtry, die CArrayObj für Array-ähnliche Funktionen erweitert, und bietet Methoden für den Zugriff auf und die Bearbeitung von Kalenderereignisdaten. 
  • Die Methode operator[] wird überschrieben, um ein CCalendarEnrtry-Objekt mit einem bestimmten Index in der Sammlung zurückzugeben, was einen Array-ähnlichen Zugriff auf Kalendereinträge ermöglicht. 
  • Die Methode At() gibt einen Zeiger auf einen CCalendarEnrtry an einem bestimmten Index zurück. Sie stellt sicher, dass der Index gültig ist, bevor auf das Array zugegriffen wird.
  • Die Methode LoadCalendarEntriesFromFile() lädt Kalendereinträge aus einer Binärdatei, liest die relevanten Daten (z. B. Länderinformationen, Ereignisdetails) und füllt CCalendarEnrtry-Objekte.

//+------------------------------------------------------------------+
//| A class to manage a collection of CCalendarEntry objects         |
//+------------------------------------------------------------------+
class CCalendarHistory :public CArrayObj{
public:
//overriding existing operators to better deal with calendar format data
   CCalendarEntry *operator[](const int index) const{return(CCalendarEntry*)At(index);}
   CCalendarEntry *At (const int index) const;
   bool LoadCalendarEntriesFromFile(string fileName);
   bool SaveCalendarValuesToFile(string filename);
   
};

CCalendarEntry *CCalendarHistory::At(const int index)const{
   if(index<0||index>=m_data_total)return(NULL);
   return (CCalendarEntry*)m_data[index];
   
}

//+------------------------------------------------------------------+
//| A function to load calendar events from your saved binary file   |
//+------------------------------------------------------------------+
bool CCalendarHistory::LoadCalendarEntriesFromFile(string fileName){
   CFileBin file;
   if(file.Open(fileName,FILE_READ|FILE_COMMON)>0){
      while(!file.IsEnding()){
         CCalendarEntry*entry = new CCalendarEntry();
         int len;
         file.ReadLong(entry.country_id);
         file.ReadInteger(len);
         file.ReadString(entry.country_name,len);
         file.ReadInteger(len);
         file.ReadString(entry.country_code,len);
         file.ReadInteger(len);
         file.ReadString(entry.country_currency,len);
         file.ReadInteger(len);
         file.ReadString(entry.country_currency_symbol,len);
         file.ReadInteger(len);
         file.ReadString(entry.country_url_name,len);
         
         file.ReadLong(entry.event_id);
         file.ReadEnum(entry.event_type);
         file.ReadEnum(entry.event_sector);
         file.ReadEnum(entry.event_frequency);
         file.ReadEnum(entry.event_time_mode);
         file.ReadEnum(entry.event_unit);
         file.ReadEnum(entry.event_importance);
         file.ReadEnum(entry.event_multiplier);
         file.ReadInteger(entry.event_digits);
         file.ReadInteger(len);
         file.ReadString(entry.event_source_url,len);
         file.ReadInteger(len);
         file.ReadString(entry.event_event_code,len);
         file.ReadInteger(len);
         file.ReadString(entry.event_name,len);
         
         file.ReadLong(entry.value_id);
         file.ReadLong(entry.value_time);
         file.ReadLong(entry.value_period);
         file.ReadInteger(entry.value_revision);
         file.ReadLong(entry.value_actual_value);
         file.ReadLong(entry.value_prev_value);
         file.ReadLong(entry.value_revised_prev_value);
         file.ReadLong(entry.value_forecast_value);
         file.ReadEnum(entry.value_impact_type);
         
         CArrayObj::Add(entry);        
      }
      Print(__FUNCTION__,">Loaded",CArrayObj::Total(),"Calendar Entries From",fileName,"...");
      CArray::Sort();
      file.Close();
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| A function to save calendar values into a binary file            |
//+------------------------------------------------------------------+
bool CCalendarHistory::SaveCalendarValuesToFile(string fileName){
   CFileBin file;
   if(file.Open(fileName,FILE_WRITE|FILE_COMMON)>0){
     datetime chunk_end   = TimeTradeServer();
      // Let's do ~12 months (adjust as needed).
      int months_to_fetch  = 12*25;  
      
      while(months_to_fetch > 0)
      {
         // For each month, we go back ~30 days
         datetime chunk_start = chunk_end - 30*24*60*60;
         if(chunk_start < 1) // Just a safety check
            chunk_start = 1;
         MqlCalendarValue values[];
         if(CalendarValueHistory(values, chunk_start, chunk_end))
         {
            // Write to file
            for(uint i = 0; i < values.Size(); i++)
            {
               MqlCalendarEvent event;
               if(!CalendarEventById(values[i].event_id,event))
                  continue;  // skip if not found
               
               MqlCalendarCountry country;
               if(!CalendarCountryById(event.country_id,country))
                  continue;  // skip if not found
      
       file.WriteLong(country.id);
       file.WriteInteger(country.name.Length());
       file.WriteString(country.name,country.name.Length());
       file.WriteInteger(country.code.Length());
       file.WriteString(country.code,country.code.Length());
       file.WriteInteger(country.currency.Length());
       file.WriteString(country.currency,country.currency.Length());
       file.WriteInteger(country.currency_symbol.Length());
       file.WriteString(country.currency_symbol, country.currency_symbol.Length());
       file.WriteInteger(country.url_name.Length());
       file.WriteString(country.url_name,country.url_name.Length());
       
       file.WriteLong(event.id);
       file.WriteEnum(event.type);
       file.WriteEnum(event.sector);
       file.WriteEnum(event.frequency);
       file.WriteEnum(event.time_mode);
       file.WriteEnum(event.unit);
       file.WriteEnum(event.importance);
       file.WriteEnum(event.multiplier);
       file.WriteInteger(event.digits);
       file.WriteInteger(event.source_url.Length());
       file.WriteString(event.source_url,event.source_url.Length());
       file.WriteInteger(event.event_code.Length());
       file.WriteString(event.event_code,event.event_code.Length());
       file.WriteInteger(event.name.Length());
       file.WriteString(event.name,event.name.Length());
       
       file.WriteLong(values[i].id);
       file.WriteLong(values[i].time);
       file.WriteLong(values[i].period);
       file.WriteInteger(values[i].revision);
       file.WriteLong(values[i].actual_value);
       file.WriteLong(values[i].prev_value);
       file.WriteLong(values[i].revised_prev_value);
       file.WriteLong(values[i].forecast_value);
       file.WriteEnum(values[i].impact_type);
     }
            Print(__FUNCTION__, " >> chunk ", 
                  TimeToString(chunk_start), " - ", TimeToString(chunk_end), 
                  ": saved ", values.Size(), " events.");
         }
         
         // Move to the previous chunk
         chunk_end = chunk_start;
         months_to_fetch--;
         
         // short pause to avoid spamming server:
         Sleep(500);
      }
     
     file.Close();
     return true;
   }
   return false;
}

Als Nächstes erstellen wir den Expert Advisor, der für die Backtest-Ergebnisse verantwortlich ist.

Für diesen Expert Advisor wird ein 5-Minuten-Zeitrahmen verwendet. Für jeden geschlossenen Balken wird geprüft, ob es innerhalb der nächsten 5 Minuten ein wichtiges Ereignis gibt. Ist dies der Fall, platzieren wir Kauf- und Verkaufsstopps innerhalb einer bestimmten Abweichung vom aktuellen Geldkurs mit einem optionalen Stop Loss. Außerdem beenden wir alle offenen Positionen, sobald eine bestimmte Zeit erreicht ist.

Die Entwicklung einer Handelsstrategie für Nachrichtenausbrüche - bei der Kauf-/Verkaufsstopps kurz vor wichtigen Nachrichtenereignissen auf wichtigen Niveaus platziert werden - kann durch mehrere strategische und taktische Überlegungen motiviert sein:

  • Explosive Preisbewegung: Aufsehenerregende Nachrichten lösen oft erhebliche Kursschwankungen aus. Die Positionierung von Stopps in der Nähe von Schlüsselniveaus kann Händlern dabei helfen, einzusteigen, sobald ein Ausbruch erfolgt, und große Kursbewegungen zu erfassen.
  • Verbessertes Risiko-Ertrags-Verhältnis: Schnelle, volatile Bewegungen können günstige Risiko-Ertrags-Setups darstellen, wenn die Stopps des Händlers in Richtung des Ausbruchs ausgelöst werden.
  • Vorhersehbarkeit von Pressemitteilungen: Da der Zeitpunkt von wichtigen Nachrichten im Voraus bekannt ist, können die Händler ihre Ein- und Ausstiege genauer planen und so die Unsicherheit in Bezug auf das Markt-Timing verringern.
  • Antizipation des Liquiditätsanstiegs: Nachrichten ziehen oft eine stärkere Marktbeteiligung nach sich, was zu zuverlässigeren Ausbrüchen führen kann, wenn Schlüsselwerte durchbrochen werden.
  • Vorgeplante Ausführung: Das Setzen von Stopps auf wichtigen technischen Niveaus vor den Nachrichten hilft, emotionale Entscheidungen im Moment des Marktschocks zu vermeiden, was zu einer disziplinierteren Ausführung führt.
  • Möglichkeit der Automatisierung: Die Platzierung von Aufträgen im Voraus kann eine automatisierte Ausführung ermöglichen, sobald die Nachricht eintrifft, sodass eine schnelle Reaktion auf Marktbewegungen gewährleistet ist, ohne dass ein manueller Eingriff erforderlich ist.

Durch die Kombination dieser Motivationen wollen wir einen systematischen Ansatz entwickeln, der die Vorhersehbarkeit und Volatilität von Nachrichtenereignissen mit großer Tragweite ausnutzt und gleichzeitig ein diszipliniertes Risikomanagement und klare Ausführungsregeln beibehält.

Der Expertenberater beginnt mit der Einbindung der erforderlichen Hilfsdateien und der Erstellung von Objekten für die entsprechenden Klassen. Sie deklariert auch die relevanten globalen Variablen, die später verwendet werden.

#define FILE_NAME "CalendarHistory.bin"
#include <Trade/Trade.mqh>
#include <CalendarHistory.mqh>
#include <Arrays/ArrayString.mqh>
CCalendarHistory calendar;
CTrade trade;
CArrayString curr;

ulong poss, buypos = 0, sellpos=0;
input int Magic = 0;
int barsTotal = 0;
int currentIndex = 0;
datetime s_lastUpdate = 0;
input int closeTime = 18;
input int slp = 1000;
input int Deviation = 1000;
input string Currencies = "USD";
input ENUM_CALENDAR_EVENT_IMPORTANCE Importance = CALENDAR_IMPORTANCE_HIGH;
input bool saveFile = true;

Die Initialisierungsfunktion OnInit() stellt Folgendes sicher:

  • Wenn saveFile true ist, werden die Kalendereinträge in einer Datei namens „CalendarHistory.bin“ gespeichert.
  • Die Kalenderereignisse werden dann aus dieser Datei geladen. Sie können jedoch nicht gleichzeitig speichern und laden, da die Speichermethode die Datei am Ende schließt.
  • Die Eingabezeichenfolge der Variablen Währungen wird in ein Array einzelner Währungen aufgeteilt, und das Array wird sortiert. Wenn Sie also sowohl USD- als auch EUR-währungsbezogene Ereignisse wünschen, geben Sie einfach „USD“; „EUR“ ein.
  • Die Magic-Nummer wird dem CTrade-Objekt zugewiesen, um die von diesem EA initiierten Trades zu identifizieren.

//+------------------------------------------------------------------+
//| Initializer function                                             |
//+------------------------------------------------------------------+
int OnInit() {
   if(saveFile==true)calendar.SaveCalendarValuesToFile(FILE_NAME);
   calendar.LoadCalendarEntriesFromFile(FILE_NAME);
   string arr[];
   StringSplit(Currencies,StringGetCharacter(";",0),arr);
   curr.AddArray(arr);
   curr.Sort();
   trade.SetExpertMagicNumber(Magic);  
   return(INIT_SUCCEEDED);
}

Hier finden Sie die für die Ausführung der Aufgaben erforderlichen Funktionen.

  • OnTradeTransaction: Überwacht eingehende Handelstransaktionen und aktualisiert die Buypos oder Sellpos mit dem Orderticket, wenn ein Kauf- oder Verkaufsauftrag mit der angegebenen magischen Nummer hinzugefügt wird.
  • executeBuy: Platziert eine Kauf-Stop-Order zum angegebenen Preis mit einem berechneten Stop-Loss und zeichnet das resultierende Orderticket in buypos auf.
  • executeSell: Platziert eine Verkaufs-Stopp-Order zum angegebenen Preis mit einem berechneten Stop-Loss und zeichnet das resultierende Orderticket in sellpos auf.
  • IsCloseTime: Überprüft die aktuelle Serverzeit, um festzustellen, ob sie die vordefinierte Sperrstunde überschritten hat.
//+------------------------------------------------------------------+
//| A function for handling trade transaction                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) {
    if (trans.type == TRADE_TRANSACTION_ORDER_ADD) {
        COrderInfo order;
        if (order.Select(trans.order)) {
            if (order.Magic() == Magic) {
                if (order.OrderType() == ORDER_TYPE_BUY) {
                    buypos = order.Ticket();
                } else if (order.OrderType() == ORDER_TYPE_SELL) {
                    sellpos = order.Ticket();
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Buy execution function                                           |
//+------------------------------------------------------------------+
void executeBuy(double price) {
       double sl = price- slp*_Point;
       sl = NormalizeDouble(sl, _Digits);
       double lots=0.1;
       trade.BuyStop(lots,price,_Symbol,sl,0,ORDER_TIME_DAY,1);
       buypos = trade.ResultOrder();
       }

//+------------------------------------------------------------------+
//| Sell execution function                                          |
//+------------------------------------------------------------------+
void executeSell(double price) {
       double sl = price + slp * _Point;
       sl = NormalizeDouble(sl, _Digits);
       double lots=0.1;
       trade.SellStop(lots,price,_Symbol,sl,0,ORDER_TIME_DAY,1);
       sellpos = trade.ResultOrder();
       }
       
//+------------------------------------------------------------------+
//| Exit time boolean function                                       |
//+------------------------------------------------------------------+
bool IsCloseTime(){
   datetime currentTime = TimeTradeServer();
   MqlDateTime timeStruct;
   TimeToStruct(currentTime,timeStruct);
   int currentHour =timeStruct.hour;
   return(currentHour>closeTime);
}

Schließlich implementieren wir nur noch die Ausführungslogik in der Funktion OnTick().

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
    int bars = iBars(_Symbol,PERIOD_CURRENT);
  
    if (barsTotal!= bars){
      barsTotal = bars;
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   datetime now = TimeTradeServer();
   datetime horizon = now + 5*60; // 5 minutes from now

   while (currentIndex < calendar.Total())
   {   
         CCalendarEntry*entry=calendar.At(currentIndex);
         if (entry.value_time < now)
         {
            currentIndex++;
            continue;
         }
         // Now if the next event time is beyond horizon, break out
         if (entry.value_time > horizon) 
            break;
         
         // If it is within the next 5 minutes, check other conditions:
         if (entry.event_importance >= Importance && curr.SearchFirst(entry.country_currency) >= 0 && buypos == sellpos )
         {
             double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
             executeBuy(bid + Deviation*_Point);
             executeSell(bid - Deviation*_Point);
         }
         currentIndex++;
      }
    if(IsCloseTime()){
       for(int i = 0; i<PositionsTotal(); i++){
         poss = PositionGetTicket(i);
         if(PositionGetInteger(POSITION_MAGIC) == Magic) trade.PositionClose(poss);      
      }
    }
     if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      buypos = 0;
      }
     if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      sellpos = 0;
      }     

   }
}

Dadurch wird sichergestellt, dass wir den Zustand neuer geschlossener Balken überprüfen. Wenn der aktuelle Balken mit dem zuletzt gespeicherten Balken identisch ist, bedeutet dies, dass es sich noch nicht um einen neuen Balken handelt, sodass wir zurückkehren würden, ohne den Rest der Handelslogik auszuführen.

    int bars = iBars(_Symbol,PERIOD_CURRENT);
  
    if (barsTotal!= bars){
      barsTotal = bars;

Die Logik der while-Schleife gewährleistet ein effizientes Backtesting, indem sie am Anfang der Daten beginnt. Wenn die aktuelle Zeit nach einem bestimmten Ereignis liegt, wird der Index der globalen Variable erhöht, sodass die Schleife nicht erneut von vorne beginnen muss. Dies trägt dazu bei, die Rechenzeit und den Speicherverbrauch während unserer Backtests zu reduzieren, was den Prozess drastisch beschleunigt und viel Zeit spart - vor allem bei langen Testzeiträumen.

while (currentIndex < calendar.Total())
   {   
         CCalendarEntry*entry=calendar.At(currentIndex);
         if (entry.value_time < now)
         {
            currentIndex++;
            continue;
         }
         // Now if the next event time is beyond horizon, break out
         if (entry.value_time > horizon) 
            break;
         
         // If it is within the next 5 minutes, check other conditions:
         if (entry.event_importance >= Importance && curr.SearchFirst(entry.country_currency) >= 0 && buypos == sellpos )
         {
             double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
             executeBuy(bid + Deviation*_Point);
             executeSell(bid - Deviation*_Point);
         }
         currentIndex++;
      }  

Dieser Teil prüft, ob die aktuelle Stunde nach der Schlusszeit liegt. Ist dies der Fall, durchläuft er das Portfolio, sucht nach offenen Positionen mit der magischen Zahl des EA und schließt sie. Nach dem Schließen wird das Positionsticket auf Null zurückgesetzt.

    if(IsCloseTime()){
       for(int i = 0; i<PositionsTotal(); i++){
         poss = PositionGetTicket(i);
         if(PositionGetInteger(POSITION_MAGIC) == Magic) trade.PositionClose(poss);      
      }
    }
     if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      buypos = 0;
      }
     if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      sellpos = 0;
      }     

Kompilieren Sie nun das Programm und gehen Sie zum MetaTrader 5 Terminal. Öffnen Sie einen beliebigen Chart und ziehen Sie diesen EA wie folgt auf den Chart:

Ziehen des EAs auf den Chart

Stellen Sie sicher, dass die Einstellung saveFile auf „true“ gesetzt ist.

auf wahr setzen

Die von Ihrem Broker bereitgestellten Ereignisdaten sind für alle Charts gleich, die Symbole spielen also keine Rolle. Der EA wird sofort nach dem Anhängen an Ihren Chart initialisiert, d.h. die Datei wird zu diesem Zeitpunkt gespeichert. Nach ein paar Sekunden können Sie den EA entfernen. Die Datei wird in dem allgemeinen Dateipfad auf Ihrem Computer gespeichert, und Sie können dorthin navigieren, um zu überprüfen, ob die Binärdatei gespeichert wurde.

Nun können Sie die Strategie im Strategie-Tester testen.

Parameter

Wichtige Hinweise zu Ihren Backtest-Parametern:

  • Setzen Sie die Variable saveFile auf „false“, um zu verhindern, dass die binäre Datendatei bei der Initialisierung geschlossen wird.

  • Wählen Sie eine angemessene Abweichung und einen Stopp-Loss. Eine zu große Abweichung wird die Volatilität von Nachrichtenereignissen nicht erfassen, während eine zu enge Abweichung oder ein zu enger Stop-Loss bei Nachrichtenspitzen anfällig für Ausrutscher ist.

  • Wählen Sie einen angemessenen Zeitpunkt für den Positionsschluss. Ich empfehle, eine Stunde nahe dem Börsenschluss oder dem Ende des Handelstages zu wählen, damit Sie die gesamte nachrichtengetriebene Bewegung erfassen können. Die entsprechende Eingabestunde hängt von der Serverzeit Ihres Brokers ab.

Hier ist mein Backtest auf SPIUSDc von 2019.1.1 - 2024.12.1 auf dem 5-Minuten-Zeitrahmen.

Einstellung

Kapitalkurve

Ergebnis

Die wichtigsten Ergebnisse:

  • Profit-Faktor: 1.26
  • Sharpe Ratio: 2.66
  • Anzahl der Handelsgeschäfte: 1604

Schließlich empfehle ich den Lesern, echte Ticks zu verwenden, falls vorhanden. Wählen Sie Stresstests mit hoher Latenz, um Ausrutscher und hohe Streuungen bei wichtigen Nachrichtenereignissen zu berücksichtigen. Zu guter Letzt sollten Sie einen Demohandel unter Live-Bedingungen durchführen, um zu überprüfen, ob Ihre Backtest-Ergebnisse zuverlässig sind. Bei einigen Brokern ist die Abweichung erheblich, während sie bei anderen nur minimal ist. Wenn Sie mit einer Strategie handeln, die sich auf eine hohe Volatilität stützt, sollten Sie immer zusätzliche Maßnahmen ergreifen, um sicherzustellen, dass die Strategie im Live-Handel profitabel ist.


Live-Handel Implementierung

Für den Live-Handel werden wir einen separaten Expert Advisor Code verwenden. Wir können nun die Kalenderoperationen von MQL5 verwenden und dabei die gleiche Logik wie zuvor implementieren. Der einzige Unterschied besteht darin, dass wir eine Funktion erstellen, um die anstehenden Nachrichtenereignisse zu aktualisieren und sie im Array des von uns erstellten Kalenderobjekts zu speichern, wobei wir es stündlich aktualisieren.

Die Logik dieses Codes aktualisiert einen Kalenderverlauf, indem neue Ereignisdaten von einer Kalender-API abgerufen werden, wenn seit der letzten Aktualisierung mehr als eine Stunde vergangen ist. Anschließend werden die Ereignisdetails, einschließlich Nation, Wert und Prognosedaten, verarbeitet und in einem neuen CCalendarEntry-Objekt für jedes Ereignis gespeichert.

//+------------------------------------------------------------------+
//| Update upcoming news events                                      |
//+------------------------------------------------------------------+
void UpdateCalendarHistory(CCalendarHistory &history)
{
   //upcoming event in the next hour
   datetime fromTime = TimeTradeServer()+3600;
   // For example, if it's been > 1hr since last update:
   if(fromTime - s_lastUpdate > 3600)
   {
      // Determine the time range to fetch new events
      // For instance, from s_lastUpdate to 'now'
      MqlCalendarValue values[];
      if(CalendarValueHistory(values, s_lastUpdate, fromTime))
      {
         for(uint i = 0; i < values.Size(); i++)
         {
            MqlCalendarEvent event;
            if(!CalendarEventById(values[i].event_id,event)) 
               continue;           
            MqlCalendarCountry country;
            if(!CalendarCountryById(event.country_id, country))
               continue;          
            // Create a new CCalendarEntry and fill from 'values[i]', 'event', 'country'
            CCalendarEntry *entry = new CCalendarEntry();
            entry.country_id = country.id;
            entry.value_time               = values[i].time;
            entry.value_period             = values[i].period;
            entry.value_revision           = values[i].revision;
            entry.value_actual_value       = values[i].actual_value;
            entry.value_prev_value         = values[i].prev_value;
            entry.value_revised_prev_value = values[i].revised_prev_value;
            entry.value_forecast_value     = values[i].forecast_value;
            entry.value_impact_type        = values[i].impact_type;
            // event data
            entry.event_id             = event.id;
            entry.event_type           = event.type;
            entry.event_sector         = event.sector;
            entry.event_frequency      = event.frequency;
            entry.event_time_mode      = event.time_mode;
            entry.event_unit           = event.unit;
            entry.event_importance     = event.importance;
            entry.event_multiplier     = event.multiplier;
            entry.event_digits         = event.digits;
            entry.event_source_url     = event.source_url;
            entry.event_event_code     = event.event_code;
            entry.event_name           = event.name;
            // country data
            entry.country_name         = country.name;
            entry.country_code         = country.code;
            entry.country_currency     = country.currency;
            entry.country_currency_symbol = country.currency_symbol;
            entry.country_url_name     = country.url_name;
            // Add to your in-memory calendar
            history.Add(entry);
         }
      }
      // Sort to keep chronological order
      history.Sort();     
      // Mark the last update time
      s_lastUpdate = fromTime;
   }
}

Der Rest des Codes ist fast genau derselbe wie der Backtest-EA. Sie können den Code einfach so in den Ausführungs-EA integrieren, und das war's.

#include <Trade/Trade.mqh>
#include <CalendarHistory.mqh>
#include <Arrays/ArrayString.mqh>
CCalendarHistory calendar;
CArrayString curr;
CTrade trade;

ulong poss, buypos = 0, sellpos=0;
input int Magic = 0;
int barsTotal = 0;
datetime s_lastUpdate = 0;
input int closeTime = 18;
input int slp = 1000;
input int Deviation = 1000;
input string Currencies = "USD";
input ENUM_CALENDAR_EVENT_IMPORTANCE Importance = CALENDAR_IMPORTANCE_HIGH;

//+------------------------------------------------------------------+
//| Initializer function                                             |
//+------------------------------------------------------------------+
int OnInit() {
   trade.SetExpertMagicNumber(Magic);  
   string arr[];
   StringSplit(Currencies,StringGetCharacter(";",0),arr);
   curr.AddArray(arr);
   curr.Sort();
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Destructor function                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   
  }

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
    int bars = iBars(_Symbol,PERIOD_CURRENT);
  
    if (barsTotal!= bars){
      barsTotal = bars;
      UpdateCalendarHistory(calendar);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   datetime now = TimeTradeServer();
   datetime horizon = now + 5*60; // 5 minutes from now

   // Loop over all loaded events
   for(int i = 0; i < calendar.Total(); i++)
   {
      CCalendarEntry *entry = calendar.At(i);

      // If event time is between 'now' and 'now+5min'
      if(entry.value_time > now && entry.value_time <= horizon&&buypos==sellpos&&entry.event_importance>=Importance&&curr.SearchFirst(entry.country_currency)>=0)
      {
        executeBuy(bid+Deviation*_Point);
        executeSell(bid-Deviation*_Point);
        }
     }
    if(IsCloseTime()){
       for(int i = 0; i<PositionsTotal(); i++){
         poss = PositionGetTicket(i);
         if(PositionGetInteger(POSITION_MAGIC) == Magic) trade.PositionClose(poss);      
      }
    }
     if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      buypos = 0;
      }
     if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      sellpos = 0;
      }     
   }
}

//+------------------------------------------------------------------+
//| A function for handling trade transaction                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) {
    if (trans.type == TRADE_TRANSACTION_ORDER_ADD) {
        COrderInfo order;
        if (order.Select(trans.order)) {
            if (order.Magic() == Magic) {
                if (order.OrderType() == ORDER_TYPE_BUY) {
                    buypos = order.Ticket();
                } else if (order.OrderType() == ORDER_TYPE_SELL) {
                    sellpos = order.Ticket();
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Buy execution function                                           |
//+------------------------------------------------------------------+
void executeBuy(double price) {
       double sl = price- slp*_Point;
       sl = NormalizeDouble(sl, _Digits);
       double lots=0.1;
       trade.BuyStop(lots,price,_Symbol,sl,0,ORDER_TIME_DAY,1);
       buypos = trade.ResultOrder();
       }

//+------------------------------------------------------------------+
//| Sell execution function                                          |
//+------------------------------------------------------------------+
void executeSell(double price) {
       double sl = price + slp * _Point;
       sl = NormalizeDouble(sl, _Digits);
       double lots=0.1;
       trade.SellStop(lots,price,_Symbol,sl,0,ORDER_TIME_DAY,1);
       sellpos = trade.ResultOrder();
       }

//+------------------------------------------------------------------+
//| Update upcoming news events                                      |
//+------------------------------------------------------------------+
void UpdateCalendarHistory(CCalendarHistory &history)
{
   //upcoming event in the next hour
   datetime fromTime = TimeTradeServer()+3600;
   // For example, if it's been > 1hr since last update:
   if(fromTime - s_lastUpdate > 3600)
   {
      // Determine the time range to fetch new events
      // For instance, from s_lastUpdate to 'now'
      MqlCalendarValue values[];
      if(CalendarValueHistory(values, s_lastUpdate, fromTime))
      {
         for(uint i = 0; i < values.Size(); i++)
         {
            MqlCalendarEvent event;
            if(!CalendarEventById(values[i].event_id,event)) 
               continue;           
            MqlCalendarCountry country;
            if(!CalendarCountryById(event.country_id, country))
               continue;          
            // Create a new CCalendarEntry and fill from 'values[i]', 'event', 'country'
            CCalendarEntry *entry = new CCalendarEntry();
            entry.country_id = country.id;
            entry.value_time               = values[i].time;
            entry.value_period             = values[i].period;
            entry.value_revision           = values[i].revision;
            entry.value_actual_value       = values[i].actual_value;
            entry.value_prev_value         = values[i].prev_value;
            entry.value_revised_prev_value = values[i].revised_prev_value;
            entry.value_forecast_value     = values[i].forecast_value;
            entry.value_impact_type        = values[i].impact_type;
            // event data
            entry.event_id             = event.id;
            entry.event_type           = event.type;
            entry.event_sector         = event.sector;
            entry.event_frequency      = event.frequency;
            entry.event_time_mode      = event.time_mode;
            entry.event_unit           = event.unit;
            entry.event_importance     = event.importance;
            entry.event_multiplier     = event.multiplier;
            entry.event_digits         = event.digits;
            entry.event_source_url     = event.source_url;
            entry.event_event_code     = event.event_code;
            entry.event_name           = event.name;
            // country data
            entry.country_name         = country.name;
            entry.country_code         = country.code;
            entry.country_currency     = country.currency;
            entry.country_currency_symbol = country.currency_symbol;
            entry.country_url_name     = country.url_name;
            // Add to your in-memory calendar
            history.Add(entry);
         }
      }
      // Sort to keep chronological order
      history.Sort();     
      // Mark the last update time
      s_lastUpdate = fromTime;
   }
}

//+------------------------------------------------------------------+
//| Exit time boolean function                                       |
//+------------------------------------------------------------------+
bool IsCloseTime(){
   datetime currentTime = TimeTradeServer();
   MqlDateTime timeStruct;
   TimeToStruct(currentTime,timeStruct);
   int currentHour =timeStruct.hour;
   return(currentHour>closeTime);
}

Bei der Entwicklung künftiger Strategien können Sie auf den in diesem Artikel dargelegten Grundlagen aufbauen und Ideen erkunden, die sich auf den nachrichtenbasierten Handel konzentrieren. Zum Beispiel:

Handel mit Ausbrüchen auf wichtigen Niveaus
Anstatt sich auf feste Abweichungen zu verlassen, konzentriert sich dieser Ansatz auf Kursbewegungen, die durch wichtige Nachrichten ausgelöst werden, die wichtige Unterstützungs- oder Widerstandsniveaus durchbrechen. Wenn beispielsweise wichtige Wirtschaftsberichte oder Unternehmensankündigungen einen Ausbruch verursachen, können Sie in die Richtung dieser Bewegung handeln. Dazu sollten Sie die Nachrichtenlage beobachten und kritische Kursniveaus im Voraus ermitteln.

Ausblenden der Nachrichten
Bei dieser Strategie wird gegen die erste Reaktion des Marktes auf ein Nachrichtenereignis gehandelt, wobei davon ausgegangen wird, dass die erste Bewegung eine Überreaktion ist. Nach einem starken Kursanstieg im Anschluss an die Nachricht warten Sie darauf, dass der Markt sich korrigiert, und tätigen dann einen Handel in die entgegengesetzte Richtung.

Filtern von Nachrichtenereignissen
Wenn Ihre Strategie am besten in Märkten mit geringer Volatilität funktioniert, können Sie den Handel während aufsehenerregender Nachrichtenereignisse vermeiden. Indem Sie den Nachrichtenkalender im Voraus prüfen, können Sie Ihr Handelssystem so programmieren, dass es offene Positionen schließt oder neue Geschäfte bis nach dem Ereignis pausiert, was stabilere Marktbedingungen gewährleistet.

Nachrichten Scalping
Bei diesem Konzept geht es darum, kleine Gewinne aus kurzfristigen, durch Nachrichten verursachten Kursbewegungen zu erzielen. Dazu gehören schnelle Ein- und Ausstiege, enge Stop-Losses und schnelle Gewinnmitnahmen. Diese Strategie ist besonders wirksam bei unbeständigen Ereignissen, die schnelle Kursschwankungen verursachen.

Den Wirtschaftskalender handeln
Dieser Ansatz orientiert sich an eingetragenen Ereignissen im Wirtschaftskalender, wie z. B. Zinsentscheidungen, BIP-Ankündigungen oder Beschäftigungsmeldungen. Indem Sie analysieren, wie die Märkte in der Vergangenheit auf ähnliche Nachrichten reagiert haben, und die aktuellen Erwartungen einbeziehen, können Sie mögliche Kursbewegungen vorhersehen und sich entsprechend vorbereiten.

Bei jeder dieser Strategien geht es darum, relevante Nachrichtendaten im Voraus zu sammeln und zu analysieren, damit Sie die durch wichtige Ereignisse verursachte Marktvolatilität optimal nutzen können.



Schlussfolgerung

In diesem Artikel haben wir eine Hilfs-Include-Datei zur Interpretation, Formatierung und Speicherung von Kalenderereignisdaten erstellt. Als Nächstes haben wir einen Backtest-Expertenberater entwickelt, der die von den Brokern bereitgestellten Daten zu Nachrichtenereignissen abruft, die Strategielogik implementiert und die Ergebnisse unserer Handelsstrategie anhand dieser Daten testet. Die Strategie zeigte eine vielversprechende Rentabilität mit über 1.600 Stichproben in 5 Jahren Tickdaten. Abschließend haben wir den Code des Expert Advisors für den Live-Handel vorgestellt und künftige Bestrebungen skizziert, die zur weiteren Entwicklung von Strategien auf der Grundlage des in diesem Artikel vorgestellten Rahmens anregen.


Datei-Tabelle

Datei Name Dateiverwendung
CalendarHistory.mqh Die Include-Hilfsdatei für die Verarbeitung von Kalenderereignisdaten.
News Breakout Backtest.mq5 Der Expert Advisor für die Speicherung von Nachrichtenereignisdaten und die Durchführung von Backtests.
News Breakout.mq5 Der Expert Advisor für den Live-Handel.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16752

Letzte Kommentare | Zur Diskussion im Händlerforum (5)
hrawoodward
hrawoodward | 16 Mai 2025 in 06:47

Hallo, das ist großartig, danke! Ich bin ein wenig verwirrt über die Eingabe mehrerer Währungen. Ich habe es versucht:

"USD"; "GBP"

USD"; "GBP".

"USD" "GBP";

Nur die letzte Eingabe führt nicht zu einem Fehler, aber ich bin mir nicht sicher, ob sie richtig funktioniert. Vielleicht wird nur der USD erfasst. Können Sie mir einen Rat geben?

Zhuo Kai Chen
Zhuo Kai Chen | 16 Mai 2025 in 10:28
hrawoodward #:

Hallo, das ist großartig, danke! Ich bin ein wenig verwirrt über die Eingabe mehrerer Währungen. Ich habe es versucht:

"USD"; "GBP"

USD"; "GBP".

"USD" "GBP";

Nur die letzte Option führt nicht zu einem Fehler, aber ich bin mir nicht sicher, ob sie korrekt funktioniert. Vielleicht wird nur der USD erfasst. Können Sie mir einen Rat geben?

Hallo, wenn Sie sich den Code in der Initialisierungsfunktion ansehen, wird er den Doppelpunkt aufteilen und verschiedene Währungen im Attribut curr object speichern. Ihre erste sollte funktionieren, obwohl Sie die Anführungszeichen nicht hinzufügen müssen. Der Speicherprozess speichert alle Ereignisse in der Binärdatei, unabhängig von ihren Attributen. Erst in der Handelslogik werden wir nach den Attributen filtern. Hier ist, was ich gerade ausgeführt habe:

Einstellungen

Ergebnis

Stanislav Korotky
Stanislav Korotky | 16 Mai 2025 in 16:22
Es sieht so aus, als ob diese Implementierung die Zeitzonenumstellung (DST) auf dem Broker-Server nicht berücksichtigt und daher beim Backtesting und bei Optimierungen ungenaue Ergebnisse liefert.
Zhuo Kai Chen
Zhuo Kai Chen | 17 Mai 2025 in 03:17
Stanislav Korotky #:
Es sieht so aus, als ob diese Implementierung die Zeitzonenumstellung (DST) auf dem Broker-Server nicht berücksichtigt und daher beim Backtesting und bei Optimierungen ungenaue Ergebnisse liefert.

Danke, dass Sie mich daran erinnern! Ich habe vergessen, dies in dem Artikel zu berücksichtigen, weil ich einen Broker ohne Sommerzeit zur Demonstration verwendet habe.

https://www.mql5.com/de/book/advanced/calendar

Aus dieser Quelle wissen wir, dass die Kalenderdaten von der MQL5-Seite bereitgestellt werden und automatisch an die aktuelle Timetradeserver()-Zeitzone des Brokers angepasst werden, was bedeutet, dass ich bei Brokern mit Sommerzeit meinen Code anpassen und berücksichtigen müsste.

Stanislav Korotky
Stanislav Korotky | 17 Mai 2025 in 13:05
Zhuo Kai Chen #:

Aus dieser Quelle wissen wir, dass die Kalenderdaten von der MQL5-Seite bereitgestellt werden und dass sie automatisch an die aktuelle Timetradeserver()-Zeitzone des Brokers angepasst werden, was bedeutet, dass ich bei Brokern mit Sommerzeit meinen Code anpassen und berücksichtigen müsste.

Da die im Buch veröffentlichte Implementierung ein wenig veraltet ist, kann die aktuelle (aktualisierte) Geschichte im Blog und in der Codebase (Indikator) und in der Codebase (Skript) nachgelesen werden.

Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 8): Metrics Board Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 8): Metrics Board
Als eines der leistungsstärksten Toolkits zur Analyse von Preisaktionen wurde das „Metrics Board“ entwickelt, um die Marktanalyse zu rationalisieren, indem es wichtige Marktmetriken mit nur einem Mausklick bereitstellt. Jede Schaltfläche dient einer bestimmten Funktion, sei es die Analyse von Hoch-/Tief-Trends, Volumen oder anderen Schlüsselindikatoren. Dieses Tool liefert genaue Daten in Echtzeit, wenn Sie sie am meisten brauchen. In diesem Artikel wollen wir uns die Funktionen genauer ansehen.
Die Handelsstrategie Inverse Fair Value Gap Die Handelsstrategie Inverse Fair Value Gap
Eine Inverse Fair Value Gap (IFVG) liegt vor, wenn der Kurs in eine zuvor ermittelte „Fair Value Gap“ abprallt und statt der erwarteten unterstützenden oder Widerstandsreaktion diese nicht einhält. Dieses Scheitern kann eine potenzielle Veränderung der Marktrichtung signalisieren und einen konträren Handelsvorteil bieten. In diesem Artikel werde ich meinen selbst entwickelten Ansatz zur Quantifizierung und Nutzung der inversen Fair Value Gap als Strategie für MetaTrader 5 Expert Advisors vorstellen.
MQL5 Handels-Toolkit (Teil 7): Erweitern der History Management EX5-Bibliothek um die Funktionen für den zuletzt stornierten, schwebenden Auftrag MQL5 Handels-Toolkit (Teil 7): Erweitern der History Management EX5-Bibliothek um die Funktionen für den zuletzt stornierten, schwebenden Auftrag
Erfahren Sie, wie Sie das letzte Modul in der Bibliothek des History Manager EX5 erstellen, wobei Sie sich auf die Funktionen konzentrieren, die für die Bearbeitung des zuletzt stornierten, schwebenden Auftrags verantwortlich sind. Damit haben Sie die Möglichkeit, wichtige Details zu stornierten offenen Aufträgen mit MQL5 effizient abzurufen und zu speichern.
Integration von Broker-APIs mit Expert Advisors unter Verwendung von MQL5 und Python Integration von Broker-APIs mit Expert Advisors unter Verwendung von MQL5 und Python
In diesem Artikel besprechen wir die Implementierung von MQL5 in Verbindung mit Python, um brokerbezogene Operationen durchzuführen. Stellen Sie sich vor, dass ein kontinuierlich laufender Expert Advisor (EA) auf einem VPS gehostet wird, der in Ihrem Namen handelt. An einem bestimmten Punkt wird die Fähigkeit des EA, Mittel zu verwalten, von entscheidender Bedeutung. Dazu gehören Vorgänge wie die Aufladung Ihres Handelskontos und die Einleitung von Abhebungen. In dieser Diskussion werden wir die Vorteile und die praktische Umsetzung dieser Funktionen beleuchten, um eine nahtlose Integration des Fondsmanagements in Ihre Handelsstrategie zu gewährleisten. Bleiben Sie dran!