
Handel mit dem MQL5 Wirtschaftskalender (Teil 6): Automatisierung des Handelseinstiegs mit der Analyse von Nachrichtenereignissen und Countdown-Timern
Einführung
In diesem Artikel gehen wir den nächsten Schritt in unserer MQL5-Wirtschaftskalender-Serie, indem wir Handelseinträge auf der Grundlage von Echtzeit-Nachrichtenanalysen automatisieren. Aufbauend auf unseren früheren Dashboard-Erweiterungen (Teil 5) integrieren wir nun eine Handelslogik, die Nachrichtenereignisse mit Hilfe von nutzerdefinierten Filtern und Zeitabständen scannt, Prognosen und vorherige Werte vergleicht und je nach Markterwartung automatisch Aufträge für einen KAUF oder VERKAUF ausführt. Außerdem implementieren wir dynamische Countdown-Timer, die die verbleibende Zeit bis zur Veröffentlichung von Nachrichten anzeigen und das System nach der Ausführung zurücksetzen, um sicherzustellen, dass unsere Handelsstrategie auf veränderte Bedingungen reagieren kann. Wir strukturieren den Artikel anhand der folgenden Themen:
- Verstehen der Anforderungen an die Handelslogik
- Implementierung der Handelslogik in MQL5
- Erstellen und Verwalten von Countdown-Timern
- Testen der Handelslogik
- Schlussfolgerung
Schauen wir uns an, wie diese Komponenten zusammenwirken, um den Einstieg in den Handel mit Präzision und Zuverlässigkeit zu automatisieren.
Verstehen der Anforderungen an die Handelslogik
Für unser automatisches Handelssystem besteht der erste Schritt darin, herauszufinden, welche Nachrichtenereignisse für einen Handel geeignet sind. Wir definieren einen Ereigniskandidaten als ein Ereignis, das in ein bestimmtes Zeitfenster fällt, das durch nutzerdefinierte Offset-Eingaben in Bezug auf seine geplante Veröffentlichung bestimmt wird. Wir werden dann Eingaben für Handelsmodi machen, wie z.B. den Handel vor der Veröffentlichung von Nachrichten. Im Modus „trade before“ wird ein Ereignis beispielsweise nur dann berücksichtigt, wenn die aktuelle Zeit zwischen der geplanten Veröffentlichungszeit des Ereignisses abzüglich des Offsets (z. B. 5 Minuten) und der tatsächlichen Veröffentlichungszeit des Ereignisses liegt. Wir werden also 5 Minuten vor der eigentlichen Veröffentlichung handeln.
Die Filterung ist entscheidend, um sicherzustellen, dass wir nur relevante Nachrichten berücksichtigen. Unser System wird daher mehrere Filter verwenden: einen Währungsfilter, um sich auf ausgewählte Währungspaare zu konzentrieren, einen Auswirkungsfilter, um die Ereignisse auf diejenigen mit einem bestimmten Signifikanzniveau zu beschränken, und einen Zeitfilter, der die Ereignisse auf diejenigen innerhalb einer vordefinierten Gesamtspanne einschränkt. Der Nutzer wählt dies über das Dashboard aus. Diese mehrstufige Filterung trägt dazu bei, das Rauschen zu minimieren und sicherzustellen, dass nur die relevantesten Nachrichtenereignisse verarbeitet werden.
Sobald ein Ereignis die Filterkriterien erfüllt, vergleicht die Handelslogik die Schlüsseldaten des Nachrichtenereignisses, insbesondere den Prognosewert mit dem vorherigen Wert. Wenn beide Werte verfügbar und ungleich Null sind und die Prognose höher als der vorherige Wert ist, eröffnet das System einen KAUF-Auftrag; ist die Prognose niedriger, eröffnet es einen VERKAUF-Auftrag. Fehlen beide Werte oder sind sie gleich, wird das Ereignis übersprungen. Dieser Entscheidungsprozess ermöglicht es dem EA, rohe Nachrichtendaten in klare Handelssignale zu übersetzen und die Eingabe von Handelsgeschäften mit Präzision zu automatisieren. Der Entscheidungsfindungsprozess und die Handelsrichtung hängen hier vollständig vom Nutzer ab, aber für diesen Artikel und die Demonstration werden wir das oben beschriebene Schema verwenden.
Um die Vorgänge zu visualisieren, werden wir Debug-Ausdrucke verwenden und außerdem Schaltflächen und Beschriftungen auf dem Chart direkt über dem Dashboard erstellen, um die gehandelten Nachrichten und die verbleibende Zeit bis zu ihrer Veröffentlichung anzuzeigen. Hier ist ein vollständiger Entwurf.
Implementierung der Handelslogik in MQL5
Um die Handelslogik in MQL5 zu implementieren, müssen wir die Handelsdateien einbinden, die die Handelsmethoden enthalten, und einige Eingaben definieren, die dem Nutzer die Steuerung des Systems ermöglichen, sowie globale Variablen, die wir im gesamten Programm wiederverwenden werden. Um dies zu erreichen, definieren wir sie im globalen Rahmen.
#include <Trade\Trade.mqh> // Trading library for order execution CTrade trade; // Global trade object //================== Trade Settings ==================// // Trade mode options: enum ETradeMode { TRADE_BEFORE, // Trade before the news event occurs TRADE_AFTER, // Trade after the news event occurs NO_TRADE, // Do not trade PAUSE_TRADING // Pause trading activity (no trades until resumed) }; input ETradeMode tradeMode = TRADE_BEFORE; // Choose the trade mode // Trade offset inputs: input int tradeOffsetHours = 12; // Offset hours (e.g., 12 hours) input int tradeOffsetMinutes = 5; // Offset minutes (e.g., 5 minutes before) input int tradeOffsetSeconds = 0; // Offset seconds input double tradeLotSize = 0.01; // Lot size for the trade //================== Global Trade Control ==================// // Once a trade is executed for one news event, no further trades occur. bool tradeExecuted = false; // Store the traded event’s scheduled news time for the post–trade countdown. datetime tradedNewsTime = 0; // Global array to store event IDs that have already triggered a trade. int triggeredNewsEvents[];
Auf globaler Ebene binden wir die Bibliothek „Trade\Trade.mqh“ mit #include ein, um die Auftragsausführung zu ermöglichen, und deklarieren ein globales „CTrade“-Objekt mit dem Namen „trade“ für die Verarbeitung von Handelsgeschäften. Wir definieren die Enumeration „ETradeMode“ mit den Optionen „TRADE_BEFORE“, „TRADE_AFTER“, „NO_TRADE“, und „PAUSE_TRADING“ und verwenden die Eingabevariable „tradeMode“ (mit der Voreinstellung „TRADE_BEFORE“, die wir für das Programm verwenden werden), um zu bestimmen, wann Handelsgeschäfte im Verhältnis zu Nachrichtenereignissen eröffnet werden sollen. Zusätzlich richten wir die „input“-Variablen „tradeOffsetHours“, „tradeOffsetMinutes“, „tradeOffsetSeconds“ und „tradeLotSize“ ein, um den Zeitversatz und die Handelsgröße festzulegen, während die globalen Variablen „tradeExecuted“ (ein Boolescher Wert), „tradedNewsTime“ (ein Datumswert) und das Array „triggeredNewsEvents“ (ein int-Array) helfen uns, den Handel zu kontrollieren und zu verhindern, dass dasselbe Nachrichtenereignis erneut gehandelt wird. Wir können dann die Handelslogik in eine Funktion einbauen.
//--- Function to scan for news events and execute trades based on selected criteria //--- It handles both pre-trade candidate selection and post-trade countdown updates void CheckForNewsTrade() { //--- Log the call to CheckForNewsTrade with the current server time Print("CheckForNewsTrade called at: ", TimeToString(TimeTradeServer(), TIME_SECONDS)); //--- If trading is disabled (either NO_TRADE or PAUSE_TRADING), remove countdown objects and exit if(tradeMode == NO_TRADE || tradeMode == PAUSE_TRADING) { //--- Check if a countdown object exists on the chart if(ObjectFind(0, "NewsCountdown") >= 0) { //--- Delete the countdown object from the chart ObjectDelete(0, "NewsCountdown"); //--- Log that the trading is disabled and the countdown has been removed Print("Trading disabled. Countdown removed."); } //--- Exit the function since trading is not allowed return; } //--- Begin pre-trade candidate selection section //--- Define the lower bound of the event time range based on the user-defined start time offset datetime lowerBound = currentTime - PeriodSeconds(start_time); //--- Define the upper bound of the event time range based on the user-defined end time offset datetime upperBound = currentTime + PeriodSeconds(end_time); //--- Log the overall event time range for debugging purposes Print("Event time range: ", TimeToString(lowerBound, TIME_SECONDS), " to ", TimeToString(upperBound, TIME_SECONDS)); //--- Retrieve historical calendar values (news events) within the defined time range MqlCalendarValue values[]; int totalValues = CalendarValueHistory(values, lowerBound, upperBound, NULL, NULL); //--- Log the total number of events found in the specified time range Print("Total events found: ", totalValues); //--- If no events are found, delete any existing countdown and exit the function if(totalValues <= 0) { if(ObjectFind(0, "NewsCountdown") >= 0) ObjectDelete(0, "NewsCountdown"); return; } }
Hier definieren wir die Funktion „CheckForNewsTrade“, die nach Nachrichtenereignissen sucht und auf der Grundlage der von uns ausgewählten Kriterien Trades ausführt. Wir beginnen mit der Protokollierung des Aufrufs mit der Funktion Print, die die aktuelle Serverzeit anzeigt, die über die Funktion TimeTradeServer ermittelt wurde. Anschließend wird geprüft, ob der Handel deaktiviert ist, indem die Variable „tradeMode“ mit den Modi „NO_TRADE“ oder „PAUSE_TRADING“ verglichen wird. Ist dies der Fall, wird mit der Funktion ObjectFind festgestellt, ob ein Countdown-Objekt mit dem Namen „NewsCountdown“ existiert, und wenn es gefunden wurde, wird es mit ObjectDelete gelöscht, bevor die Funktion beendet wird.
Als Nächstes berechnet die Funktion den Gesamtzeitbereich des Ereignisses, indem sie „lowerBound“ auf die aktuelle Zeit minus die Anzahl der Sekunden aus der Eingabe „start_time“ (umgerechnet über die Funktion PeriodSeconds) und „upperBound“ auf die aktuelle Zeit plus die Sekunden aus der Eingabe „end_time“ setzt. Diese gesamte Zeitspanne wird dann mit Print protokolliert. Schließlich ruft die Funktion CalendarValueHistory auf, um alle Nachrichtenereignisse innerhalb des definierten Zeitraums abzurufen; werden keine Ereignisse gefunden, bereinigt sie ein eventuell vorhandenes Countdown-Objekt und beendet sich, wodurch das System für die nachfolgende Auswahl von Kandidatenereignissen und die Ausführung von Handelsgeschäften vorbereitet wird.
//--- Initialize candidate event variables for trade selection datetime candidateEventTime = 0; string candidateEventName = ""; string candidateTradeSide = ""; int candidateEventID = -1; //--- Loop through all retrieved events to evaluate each candidate for trading for(int i = 0; i < totalValues; i++) { //--- Declare an event structure to hold event details MqlCalendarEvent event; //--- Attempt to populate the event structure by its ID; if it fails, skip to the next event if(!CalendarEventById(values[i].event_id, event)) continue; //----- Apply Filters ----- //--- If currency filtering is enabled, check if the event's currency matches the selected filters if(enableCurrencyFilter) { //--- Declare a country structure to hold country details MqlCalendarCountry country; //--- Populate the country structure based on the event's country ID CalendarCountryById(event.country_id, country); //--- Initialize a flag to determine if there is a matching currency bool currencyMatch = false; //--- Loop through each selected currency filter for(int k = 0; k < ArraySize(curr_filter_selected); k++) { //--- Check if the event's country currency matches the current filter selection if(country.currency == curr_filter_selected[k]) { //--- Set flag to true if a match is found and break out of the loop currencyMatch = true; break; } } //--- If no matching currency is found, log the skip and continue to the next event if(!currencyMatch) { Print("Event ", event.name, " skipped due to currency filter."); continue; } } //--- If importance filtering is enabled, check if the event's impact matches the selected filters if(enableImportanceFilter) { //--- Initialize a flag to determine if the event's impact matches any filter selection bool impactMatch = false; //--- Loop through each selected impact filter option for(int k = 0; k < ArraySize(imp_filter_selected); k++) { //--- Check if the event's importance matches the current filter selection if(event.importance == imp_filter_selected[k]) { //--- Set flag to true if a match is found and break out of the loop impactMatch = true; break; } } //--- If no matching impact is found, log the skip and continue to the next event if(!impactMatch) { Print("Event ", event.name, " skipped due to impact filter."); continue; } } //--- If time filtering is enabled and the event time exceeds the upper bound, skip the event if(enableTimeFilter && values[i].time > upperBound) { Print("Event ", event.name, " skipped due to time filter."); continue; } //--- Check if the event has already triggered a trade by comparing its ID to recorded events bool alreadyTriggered = false; //--- Loop through the list of already triggered news events for(int j = 0; j < ArraySize(triggeredNewsEvents); j++) { //--- If the event ID matches one that has been triggered, mark it and break out of the loop if(triggeredNewsEvents[j] == values[i].event_id) { alreadyTriggered = true; break; } } //--- If the event has already triggered a trade, log the skip and continue to the next event if(alreadyTriggered) { Print("Event ", event.name, " already triggered a trade. Skipping."); continue; }
Hier initialisieren wir die Variablen für die Kandidatenereignisse mit einer Variablen des Typs Datetime („candidateEventTime“), zwei „String“-Variablen („candidateEventName“ und „candidateTradeSide“) und einer „int“-Variablen („candidateEventID“), die auf -1 gesetzt wird. Dann gehen wir mit einer Schleife durch jedes Ereignis, das von der Funktion CalendarValueHistory abgerufen wurde (gespeichert in einem Array der Strukturen MqlCalendarValue), und die Funktion CalendarEventById verwendet, um eine Struktur MqlCalendarEvent mit den Details des Ereignisses zu füllen.
Als Nächstes wenden wir unsere Filter an: Wenn die Währungsfilterung aktiviert ist, rufen wir die entsprechende Struktur MqlCalendarCountry des Ereignisses über CalendarCountryById ab und prüfen, ob das Feld „currency“ mit einem Eintrag im Array „curr_filter_selected“ übereinstimmt; wenn nicht, protokollieren wir eine Meldung und überspringen das Ereignis. Wenn der Wichtigkeitsfilter aktiviert ist, wird das Array „imp_filter_selected“ durchlaufen, um sicherzustellen, dass die „Wichtigkeit“ des Ereignisses mit einer der ausgewählten Stufen übereinstimmt; ist dies nicht der Fall, wird das Ereignis protokolliert und übersprungen.
Abschließend wird geprüft, ob das Ereignis bereits einen Handel ausgelöst hat, indem die Ereignis-ID mit den im Array „triggeredNewsEvents“ gespeicherten Ereignissen verglichen wird; ist dies der Fall, wird das Ereignis protokolliert und übersprungen. Diese Schleife stellt sicher, dass nur Ereignisse, die alle Kriterien - Währung, Auswirkung, Zeitspanne und Einmaligkeit - erfüllen, als Kandidaten für die Handelsausführung in Frage kommen. Wenn alles klappt und wir Ereignisse haben, können wir fortfahren, das Ereignis über den Zeitrahmen zu filtern, wie vom Nutzer erlaubt.
//--- For TRADE_BEFORE mode, check if the current time is within the valid window (event time minus offset to event time) if(tradeMode == TRADE_BEFORE) { if(currentTime >= (values[i].time - offsetSeconds) && currentTime < values[i].time) { //--- Retrieve the forecast and previous values for the event MqlCalendarValue calValue; //--- If unable to retrieve calendar values, log the error and skip this event if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for event: ", event.name); continue; } //--- Get the forecast value from the calendar data double forecast = calValue.GetForecastValue(); //--- Get the previous value from the calendar data double previous = calValue.GetPreviousValue(); //--- If either forecast or previous is zero, log the skip and continue to the next event if(forecast == 0.0 || previous == 0.0) { Print("Skipping event ", event.name, " because forecast or previous value is empty."); continue; } //--- If forecast equals previous, log the skip and continue to the next event if(forecast == previous) { Print("Skipping event ", event.name, " because forecast equals previous."); continue; } //--- If this candidate event is earlier than any previously found candidate, record its details if(candidateEventTime == 0 || values[i].time < candidateEventTime) { candidateEventTime = values[i].time; candidateEventName = event.name; candidateEventID = (int)values[i].event_id; candidateTradeSide = (forecast > previous) ? "BUY" : "SELL"; //--- Log the candidate event details including its time and trade side Print("Candidate event: ", event.name, " with event time: ", TimeToString(values[i].time, TIME_SECONDS), " Side: ", candidateTradeSide); } } }
Hier bewerten wir die in Frage kommenden Nachrichtenereignisse, wenn wir im Modus „TRADE_BEFORE“ arbeiten. Wir prüfen, ob die aktuelle Zeit, die wir über die Funktion TimeTradeServer erhalten, in das gültige Handelsfenster fällt, das sich von der geplanten Zeit des Ereignisses abzüglich des nutzerdefinierten Offsets („offsetSeconds“) bis zur exakten Ereigniszeit erstreckt, wie unten definiert.
//--- Get the current trading server time datetime currentTime = TimeTradeServer(); //--- Calculate the offset in seconds based on trade offset hours, minutes, and seconds int offsetSeconds = tradeOffsetHours * 3600 + tradeOffsetMinutes * 60 + tradeOffsetSeconds;
Wenn die Bedingung erfüllt ist, werden die Vorhersage- und Vorgängerwerte des Ereignisses mit der Funktion CalendarValueById abgerufen, um eine Struktur MqlCalendarValue zu füllen. Wenn der Abruf fehlschlägt, wird eine Fehlermeldung protokolliert und das Ereignis übersprungen. Anschließend extrahieren wir die Vorhersage- und Vorgängerwerte mit den Methoden „GetForecastValue“ bzw. „GetPreviousValue“. Wenn einer der beiden Werte Null ist oder wenn sie gleich sind, wird eine Meldung protokolliert und zum nächsten Ereignis übergegangen, um sicherzustellen, dass nur Ereignisse mit aussagekräftigen Daten verarbeitet werden.
Wenn sich das Ereignis qualifiziert und früher eintritt als ein zuvor identifizierter Kandidat, aktualisieren wir unsere Kandidatenvariablen: „candidateEventTime“ speichert die Uhrzeit des Ereignisses, „candidateEventName“ den Namen des Ereignisses, „candidateEventID“ die ID des Ereignisses, und „candidateTradeSide“ bestimmt, ob es sich bei dem Handelsgeschäft um einen KAUF (wenn die Prognose höher als der vorherige Wert ist) oder einen VERKAUF (wenn die Prognose niedriger ist) handelt. Schließlich protokollieren wir die Details des ausgewählten Kandidatenereignisses, um sicherzustellen, dass wir das früheste gültige Ereignis für die Handelsausführung verfolgen. Wir können dann das Ereignis für die Handelsausführung auswählen.
//--- If a candidate event has been selected and the trade mode is TRADE_BEFORE, attempt to execute the trade if(tradeMode == TRADE_BEFORE && candidateEventTime > 0) { //--- Calculate the target time to start trading by subtracting the offset from the candidate event time datetime targetTime = candidateEventTime - offsetSeconds; //--- Log the candidate target time for debugging purposes Print("Candidate target time: ", TimeToString(targetTime, TIME_SECONDS)); //--- Check if the current time falls within the trading window (target time to candidate event time) if(currentTime >= targetTime && currentTime < candidateEventTime) { //--- Loop through events again to get detailed information for the candidate event for(int i = 0; i < totalValues; i++) { //--- Identify the candidate event by matching its time if(values[i].time == candidateEventTime) { //--- Declare an event structure to store event details MqlCalendarEvent event; } } } }
Wir prüfen, ob ein Kandidatenereignis ausgewählt wurde und der Handelsmodus „TRADE_BEFORE“ ist, indem wir überprüfen, ob „candidateEventTime“ größer als Null ist. Anschließend berechnen wir die „targetTime“, indem wir den nutzerdefinierten Offset („offsetSeconds“) von der geplanten Zeit des Kandidatenereignisses abziehen, und protokollieren diese Zielzeit zur Fehlersuche mit der Funktion Print. Als Nächstes stellen wir fest, ob die aktuelle Zeit innerhalb des gültigen Handelsfensters liegt - zwischen der „targetTime“ und der Zeit des Kandidatenereignisses - und wenn ja, durchlaufen wir eine Schleife durch das Array der Ereignisse, um das Kandidatenereignis anhand seiner Zeit zu identifizieren, sodass wir dann mit dem Abrufen weiterer Details und der Ausführung des Handels fortfahren können.
//--- Attempt to retrieve the event details; if it fails, skip to the next event if(!CalendarEventById(values[i].event_id, event)) continue; //--- If the current time is past the event time, log the skip and continue if(currentTime >= values[i].time) { Print("Skipping candidate ", event.name, " because current time is past event time."); continue; } //--- Retrieve detailed calendar values for the candidate event MqlCalendarValue calValue; //--- If retrieval fails, log the error and skip the candidate if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for candidate event: ", event.name); continue; } //--- Get the forecast value for the candidate event double forecast = calValue.GetForecastValue(); //--- Get the previous value for the candidate event double previous = calValue.GetPreviousValue(); //--- If forecast or previous is zero, or if they are equal, log the skip and continue if(forecast == 0.0 || previous == 0.0 || forecast == previous) { Print("Skipping candidate ", event.name, " due to invalid forecast/previous values."); continue; } //--- Construct a news information string for the candidate event string newsInfo = "Trading on news: " + event.name + " (Time: " + TimeToString(values[i].time, TIME_SECONDS)+")"; //--- Log the news trading information Print(newsInfo); //--- Create a label on the chart to display the news trading information createLabel1("NewsTradeInfo", 355, 22, newsInfo, clrBlue, 11);
Bevor wir die Handelsgeschäfte eröffnen, versuchen wir, mit der Funktion CalendarEventById detaillierte Informationen für das Kandidatenereignis abzurufen, um eine Struktur MqlCalendarEvent zu füllen; schlägt dieser Abruf fehl, gehen wir sofort zum nächsten Ereignis über. Anschließend wird geprüft, ob die aktuelle Zeit (die über TimeTradeServer ermittelt wird) bereits nach der geplanten Zeit des Kandidatenereignisses liegt. Ist dies der Fall, wird eine Meldung protokolliert und die Verarbeitung dieses Ereignisses übersprungen.
Als Nächstes werden die detaillierten Kalenderwerte für das Ereignis mithilfe von CalendarValueById abgerufen, um eine Struktur MqlCalendarValue zu füllen. Dann werden die Werte „forecast“ und „previous“ mithilfe der Methoden „GetForecastValue“ bzw. „GetPreviousValue“ extrahiert; wenn einer der beiden Werte Null ist oder beide gleich sind, wird der Grund protokolliert und das Kandidatenereignis übersprungen. Schließlich konstruieren wir eine Zeichenkette mit den wichtigsten Nachrichteninformationen und protokollieren sie, während wir diese Informationen mit der Funktion „createLabel1“ auch im Chart anzeigen. Das Codefragment der Funktion lautet wie folgt.
//--- Function to create a label on the chart with specified properties bool createLabel1(string objName, int x, int y, string text, color txtColor, int fontSize) { //--- Attempt to create the label object; if it fails, log the error and return false if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Print error message with the label name and the error code Print("Error creating label ", objName, " : ", GetLastError()); //--- Return false to indicate label creation failure return false; } //--- Set the horizontal distance (X coordinate) for the label ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set the vertical distance (Y coordinate) for the label ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set the text that will appear on the label ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set the color of the label's text ObjectSetInteger(0, objName, OBJPROP_COLOR, txtColor); //--- Set the font size for the label text ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set the font style to "Arial Bold" for the label text ObjectSetString(0, objName, OBJPROP_FONT, "Arial Bold"); //--- Set the label's anchor corner to the top left of the chart ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Redraw the chart to reflect the new label ChartRedraw(); //--- Return true indicating that the label was created successfully return true; }
Die Logik dieser Funktion ist nicht neu, und wir brauchen nicht viel dazu zu erklären, da wir dies bereits bei der Erstellung des Dashboards getan haben. Wir gehen also einfach dazu über, auf der Grundlage der erhaltenen Werte Handelsgeschäfte zu eröffnen.
//--- Initialize a flag to store the result of the trade execution bool tradeResult = false; //--- If the candidate trade side is BUY, attempt to execute a buy order if(candidateTradeSide == "BUY") { tradeResult = trade.Buy(tradeLotSize, _Symbol, 0, 0, 0, event.name); } //--- Otherwise, if the candidate trade side is SELL, attempt to execute a sell order else if(candidateTradeSide == "SELL") { tradeResult = trade.Sell(tradeLotSize, _Symbol, 0, 0, 0, event.name); } //--- If the trade was executed successfully, update the triggered events and trade flags if(tradeResult) { Print("Trade executed for candidate event: ", event.name, " Side: ", candidateTradeSide); int size = ArraySize(triggeredNewsEvents); ArrayResize(triggeredNewsEvents, size + 1); triggeredNewsEvents[size] = (int)values[i].event_id; tradeExecuted = true; tradedNewsTime = values[i].time; } else { //--- If trade execution failed, log the error message with the error code Print("Trade execution failed for candidate event: ", event.name, " Error: ", GetLastError()); } //--- Break out of the loop after processing the candidate event break;
Zunächst initialisieren wir das boolesches Flag „tradeResult“, um das Ergebnis unseres Handelsversuchs zu speichern. Dann prüfen wir die „candidateTradeSide“ - wenn es ein KAUF ist, rufen wir die Funktion „trade.Buy“ mit der angegebenen „tradeLotSize“ und dem Symbol (_Symbol) auf und verwenden den Namen des Ereignisses als Kommentar, um die Eindeutigkeit zu gewährleisten und die Identifizierung zu erleichtern; wenn „candidateTradeSide“ einen VERKAUF nahelegt, rufen wir auf ähnliche Weise „trade.Sell“ auf. Wenn der Handel erfolgreich ausgeführt wird (d.h. „tradeResult“ ist true), protokollieren wir die Ausführungsdetails, aktualisieren unser Array „triggeredNewsEvents“, indem wir es mit der Funktion ArrayResize verkleinern und die Ereignis-ID anhängen, setzen „tradeExecuted“ auf true und zeichnen die geplante Zeit des Ereignisses in „tradedNewsTime“ auf; andernfalls protokollieren wir eine Fehlermeldung mit der Funktion „GetLastError“ und brechen dann die Schleife ab, um die Verarbeitung weiterer möglicher Ereignisse zu verhindern. Hier ist ein Beispiel für einen Handel, der in einem Ereignisbereich eröffnet wurde.
Nachdem das Handelsgeschäft eröffnet wurde, müssen wir nur noch die Logik des Ereignis-Countdowns initialisieren, was im nächsten Abschnitt geschieht.
Erstellen und Verwalten von Countdown-Timern
Um die Countdown-Timer zu erstellen und zu verwalten, benötigen wir einige Hilfsfunktionen, um die Schaltfläche zu erstellen, die die Zeit anzeigt, und um die Beschriftung bei Bedarf zu aktualisieren.
//--- Function to create a button on the chart with specified properties bool createButton1(string objName, int x, int y, int width, int height, string text, color txtColor, int fontSize, color bgColor, color borderColor) { //--- Attempt to create the button object; if it fails, log the error and return false if(!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Print error message with the button name and the error code Print("Error creating button ", objName, " : ", GetLastError()); //--- Return false to indicate button creation failure return false; } //--- Set the horizontal distance (X coordinate) for the button ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set the vertical distance (Y coordinate) for the button ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set the width of the button ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set the height of the button ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set the text that will appear on the button ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set the color of the button's text ObjectSetInteger(0, objName, OBJPROP_COLOR, txtColor); //--- Set the font size for the button text ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set the font style to "Arial Bold" for the button text ObjectSetString(0, objName, OBJPROP_FONT, "Arial Bold"); //--- Set the background color of the button ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set the border color of the button ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set the button's anchor corner to the top left of the chart ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Enable the background display for the button ObjectSetInteger(0, objName, OBJPROP_BACK, true); //--- Redraw the chart to reflect the new button ChartRedraw(); //--- Return true indicating that the button was created successfully return true; } //--- Function to update the text of an existing label bool updateLabel1(string objName, string text) { //--- Check if the label exists on the chart; if not, log the error and return false if(ObjectFind(0, objName) < 0) { //--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); //--- Return false because the label does not exist return false; } //--- Update the label's text property with the new text ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Redraw the chart to update the label display ChartRedraw(); //--- Return true indicating that the label was updated successfully return true; } //--- Function to update the text of an existing label bool updateLabel1(string objName, string text) { //--- Check if the label exists on the chart; if not, log the error and return false if(ObjectFind(0, objName) < 0) { //--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); //--- Return false because the label does not exist return false; } //--- Update the label's text property with the new text ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Redraw the chart to update the label display ChartRedraw(); //--- Return true indicating that the label was updated successfully return true; }
Hier erstellen wir nur die Hilfsfunktionen, die es uns ermöglichen, die Zeitschaltfläche zu erstellen, sowie die Aktualisierungsfunktion, um das Etikett zu aktualisieren. Wir brauchen die Funktionen nicht zu erklären, da wir ihre Logik bereits in ähnlichen Funktionen in früheren Teilen der Serie erläutert haben. Also gehen wir direkt zur Umsetzung über.
//--- Begin handling the post-trade countdown scenario if(tradeExecuted) { //--- If the current time is before the traded news time, display the countdown until news release if(currentTime < tradedNewsTime) { //--- Calculate the remaining seconds until the traded news time int remainingSeconds = (int)(tradedNewsTime - currentTime); //--- Calculate hours from the remaining seconds int hrs = remainingSeconds / 3600; //--- Calculate minutes from the remaining seconds int mins = (remainingSeconds % 3600) / 60; //--- Calculate seconds remainder int secs = remainingSeconds % 60; //--- Construct the countdown text string string countdownText = "News in: " + IntegerToString(hrs) + "h " + IntegerToString(mins) + "m " + IntegerToString(secs) + "s"; //--- If the countdown object does not exist, create it with a blue background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrBlue, clrBlack); //--- Log that the post-trade countdown was created Print("Post-trade countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text updateLabel1("NewsCountdown", countdownText); //--- Log that the post-trade countdown was updated Print("Post-trade countdown updated: ", countdownText); } } else { //--- If current time is past the traded news time, calculate elapsed time since trade int elapsed = (int)(currentTime - tradedNewsTime); //--- If less than 15 seconds have elapsed, show a reset countdown if(elapsed < 15) { //--- Calculate the remaining delay for reset int remainingDelay = 15 - elapsed; //--- Construct the reset countdown text string countdownText = "News Released, resetting in: " + IntegerToString(remainingDelay) + "s"; //--- If the countdown object does not exist, create it with a red background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrRed, clrBlack); //--- Set the background color property explicitly to red ObjectSetInteger(0,"NewsCountdown",OBJPROP_BGCOLOR,clrRed); //--- Log that the post-trade reset countdown was created Print("Post-trade reset countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text and background color updateLabel1("NewsCountdown", countdownText); ObjectSetInteger(0,"NewsCountdown",OBJPROP_BGCOLOR,clrRed); //--- Log that the post-trade reset countdown was updated Print("Post-trade reset countdown updated: ", countdownText); } } else { //--- If 15 seconds have elapsed since traded news time, log the reset action Print("News Released. Resetting trade status after 15 seconds."); //--- If the countdown object exists, delete it from the chart if(ObjectFind(0, "NewsCountdown") >= 0) ObjectDelete(0, "NewsCountdown"); //--- Reset the tradeExecuted flag to allow new trades tradeExecuted = false; } } //--- Exit the function as post-trade processing is complete return; }
Hier wird das Countdown-Szenario nach einem Handelsgeschäft im Detail behandelt. Sobald ein Handel ausgeführt wird, verwenden wir zunächst die Funktion TimeTradeServer, um die aktuelle Serverzeit zu ermitteln und sie mit der „tradedNewsTime“ zu vergleichen, in der die geplante Veröffentlichungszeit des Kandidatenereignisses gespeichert ist. Wenn die aktuelle Zeit noch vor „tradedNewsTime“ liegt, berechnen wir die verbleibenden Sekunden und konvertieren diese in Stunden, Minuten und Sekunden, um einen Countdown als Text zu erstellen, der formatiert ist als „News in: __h __m __s“ unter Verwendung der Funktion IntegerToString.
Dann prüfen wir mit ObjectFind, ob das Objekt „NewsCountdown“ vorhanden ist, und erstellen es entweder (mit unserer nutzerdefinierten Funktion „createButton1“) bei X=50, Y=17 mit einer Breite von 300 und einer Höhe von 30 und einem blauen Hintergrund, oder wir aktualisieren es (mit „updateLabel1“), wenn es bereits vorhanden ist. Wenn die aktuelle Zeit jedoch „tradedNewsTime“ überschritten hat, berechnen wir die verstrichene Zeit; wenn diese verstrichene Zeit weniger als 15 Sekunden beträgt, zeigen wir im Countdown-Objekt eine Rücksetzungsmeldung an - „News Released, resetting in: XXs“ - und setzen Sie die Hintergrundfarbe mit der Funktion ObjectSetInteger explizit auf Rot.
Sobald die 15 Sekunden abgelaufen sind, löschen wir das Objekt „NewsCountdown“ und setzen das Flag „tradeExecuted“ zurück, um neue Handelsgeschäfte zuzulassen. So wird sichergestellt, dass unser System dynamisch auf Änderungen des Nachrichten-Timings reagiert und eine kontrollierte Handelsausführung gewährleistet. Außerdem müssen wir den Countdown anzeigen, wenn wir den Handel haben und noch nicht freigegeben wurden. Das erreichen wir durch die folgende Logik.
if(currentTime >= targetTime && currentTime < candidateEventTime) { //--- } else { //--- If current time is before the candidate event window, show a pre-trade countdown int remainingSeconds = (int)(candidateEventTime - currentTime); int hrs = remainingSeconds / 3600; int mins = (remainingSeconds % 3600) / 60; int secs = remainingSeconds % 60; //--- Construct the pre-trade countdown text string countdownText = "News in: " + IntegerToString(hrs) + "h " + IntegerToString(mins) + "m " + IntegerToString(secs) + "s"; //--- If the countdown object does not exist, create it with specified dimensions and blue background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrBlue, clrBlack); //--- Log that the pre-trade countdown was created Print("Pre-trade countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text updateLabel1("NewsCountdown", countdownText); //--- Log that the pre-trade countdown was updated Print("Pre-trade countdown updated: ", countdownText); } }
Wenn die aktuelle Zeit nicht in das Handelsfenster des Ereigniskandidaten fällt, d. h. wenn die aktuelle Zeit nicht größer oder gleich „targetTime“ (berechnet als geplante Zeit des Ereigniskandidaten abzüglich des Offsets) und nicht kleiner als die geplante Zeit des Ereigniskandidaten ist, gehen wir davon aus, dass die aktuelle Zeit noch vor dem Handelsfenster liegt, sodass wir die verbleibende Zeit bis zum Ereigniskandidaten berechnen, indem wir die aktuelle Zeit von der geplanten Zeit des Ereigniskandidaten abziehen und diese Differenz in Stunden, Minuten und Sekunden umrechnen.
Mit IntegerToString konstruieren wir einen Countdown-Textstring im Format „News in: __h __m __s“. Dann prüfen wir mit der Funktion ObjectFind, ob das Objekt „NewsCountdown“ bereits existiert; wenn nicht, erstellen wir es mit der Funktion „createButton1“ mit den angegebenen Abmessungen (X=50, Y=17, width=300, height=30) und einem blauen Hintergrund und protokollieren, dass der Vor-Handels-Countdown erstellt wurde, andernfalls aktualisieren wir seinen Text mit „updateLabel1“ und protokollieren die Aktualisierung. Wenn nach der Analyse kein Ereignis mehr ausgewählt wird, löschen wir unsere Objekte einfach.
//--- If no candidate event is selected, delete any existing countdown and trade info objects if(ObjectFind(0, "NewsCountdown") >= 0) { ObjectDelete(0, "NewsCountdown"); ObjectDelete(0, "NewsTradeInfo"); //--- Log that the pre-trade countdown was deleted Print("Pre-trade countdown deleted."); }
Wenn kein Ereignis ausgewählt wird, d. h. wenn kein Ereignis die Kriterien für die Handelsausführung erfüllt, wird mit der Funktion ObjectFind geprüft, ob das Objekt „NewsCountdown“ vorhanden ist. Wenn wir sie finden, entfernen wir sowohl das „NewsCountdown“- als auch das „NewsTradeInfo“-Objekt aus dem Chart, indem wir die Funktion ObjectDelete aufrufen, um sicherzustellen, dass keine veralteten Countdown- oder Handelsinformationen angezeigt werden.
Der Nutzer kann das Programm jedoch explizit beenden, was bedeutet, dass wir unser Chart auch in diesem Fall bereinigen müssen. Wir können also eine Funktion definieren, die das Bereinigen leicht macht.
//--- Function to delete trade-related objects from the chart and redraw the chart void deleteTradeObjects(){ //--- Delete the countdown object from the chart ObjectDelete(0, "NewsCountdown"); //--- Delete the news trade information label from the chart ObjectDelete(0, "NewsTradeInfo"); //--- Redraw the chart to reflect the deletion of objects ChartRedraw(); }
Nachdem wir die Funktion definiert haben, rufen wir sie einfach in der Ereignisbehandlung durch OnDeinit auf, wobei wir auch das bestehende Dashboard zerstören und eine vollständige Bereinigung sicherstellen, wie unten gelb hervorgehoben.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- destroy_Dashboard(); deleteTradeObjects(); }
Eine Sache, die bleibt, ist die Verfolgung der aktualisierten Filterinformationen, wenn der Nutzer auf das Dashboard klickt, sodass wir mit aktuellen Informationen bleiben können. Das bedeutet also, dass wir die Ereignisse inn der Ereignisbehandlung durch OnChartEvent verfolgen müssen. Wir erstellen eine Funktion, um dies einfach zu implementieren.
//--- Function to log active filter selections in the Experts log void UpdateFilterInfo() { //--- Initialize the filter information string with the prefix "Filters: " string filterInfo = "Filters: "; //--- Check if the currency filter is enabled if(enableCurrencyFilter) { //--- Append the currency filter label to the string filterInfo += "Currency: "; //--- Loop through each selected currency filter option for(int i = 0; i < ArraySize(curr_filter_selected); i++) { //--- Append the current currency filter value filterInfo += curr_filter_selected[i]; //--- If not the last element, add a comma separator if(i < ArraySize(curr_filter_selected) - 1) filterInfo += ","; } //--- Append a semicolon to separate this filter's information filterInfo += "; "; } else { //--- Indicate that the currency filter is turned off filterInfo += "Currency: Off; "; } //--- Check if the importance filter is enabled if(enableImportanceFilter) { //--- Append the impact filter label to the string filterInfo += "Impact: "; //--- Loop through each selected impact filter option for(int i = 0; i < ArraySize(imp_filter_selected); i++) { //--- Append the string representation of the current importance filter value filterInfo += EnumToString(imp_filter_selected[i]); //--- If not the last element, add a comma separator if(i < ArraySize(imp_filter_selected) - 1) filterInfo += ","; } //--- Append a semicolon to separate this filter's information filterInfo += "; "; } else { //--- Indicate that the impact filter is turned off filterInfo += "Impact: Off; "; } //--- Check if the time filter is enabled if(enableTimeFilter) { //--- Append the time filter information with the upper limit filterInfo += "Time: Up to " + EnumToString(end_time); } else { //--- Indicate that the time filter is turned off filterInfo += "Time: Off"; } //--- Print the complete filter information to the Experts log Print("Filter Info: ", filterInfo); }
Wir erstellen eine ungültige Funktion namens „UpdateFilterInfo“. Zunächst wird eine Zeichenkette mit dem Präfix „Filters: “ initialisiert und dann geprüft, ob der Währungsfilter aktiviert ist - wenn ja, wird „Currency: “ angehängt und das Array „curr_filter_selected“ mit „ArraySize“ in einer Schleife durchlaufen, wobei jede Währung (mit Kommas dazwischen) hinzugefügt und mit einem Semikolon abgeschlossen wird; ist sie deaktiviert, wird einfach „Currency: Off; “ vermerkt. Als Nächstes führen wir einen ähnlichen Prozess für den Auswirkungsfilter durch: Wenn er aktiviert ist, fügen wir „Impact:“ an und iterieren über „imp_filter_selected“, wobei wir jede ausgewählte Auswirkungsstufe mit EnumToString in eine Zeichenkette umwandeln, bevor wir sie anhängen, oder wir geben „Auswirkung: Off; “ an, wenn nicht aktiviert.
Schließlich wird der Zeitfilter durch Anhängen von „Time: Up to “ zusammen mit der textlichen Repräsentation der Eingabe „end_time“ (wiederum mit „EnumToString“) oder „Time: Off“, wenn deaktiviert. Sobald alle Segmente verkettet sind, geben wir die vollständigen Filterinformationen mit der Druckfunktion in das Expertenprotokoll aus. So erhalten wir einen klaren Echtzeit-Schnappschuss der wirksamen Filter für die Fehlersuche und Überprüfung. Anschließend rufen wir die Funktionen der Ereignisbehandlung von OnChartEvent sowie OnTick auf.
//+------------------------------------------------------------------+ //| OnChartEvent handler function | //+------------------------------------------------------------------+ void OnChartEvent( const int id, // event ID const long& lparam, // long type event parameter const double& dparam, // double type event parameter const string& sparam // string type event parameter ){ if (id == CHARTEVENT_OBJECT_CLICK){ //--- Check if the event is a click on an object UpdateFilterInfo(); CheckForNewsTrade(); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- UpdateFilterInfo(); CheckForNewsTrade(); if (isDashboardUpdate){ update_dashboard_values(curr_filter_selected,imp_filter_selected); } }
Beim Ausführen des Programms sehen wir Folgendes.
Aus dem Bild können wir ersehen, dass wir auf der Grundlage der vom Nutzer gewählten Einstellungen Handelsgeschäfte eröffnen können. Wenn die Nachricht gehandelt wird, erstellen wir den Countdown-Timer und die Kennzeichnung, um die Informationen anzuzeigen und zu aktualisieren, und protokollieren dann die Aktualisierungen, womit wir unser Ziel erreichen. Das Einzige, was noch bleibt, ist das Testen unserer Logik, und das wird im nächsten Abschnitt behandelt.
Testen der Handelslogik
Bei den Backtests warteten wir auf Live-Handelsereignisse, und beim Testen war das Ergebnis wie im Video unten dargestellt.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir durch die Verwendung von nutzerdefinierten Filtern, präzisen Zeitverschiebungen und dynamischen Countdown-Timern erfolgreich einen automatischen Handelseingang in unser MQL5-Wirtschaftskalendersystem integriert haben. Unsere Lösung sucht nach neuen Ereignissen, vergleicht Prognosen und frühere Werte und führt automatisch die Aufträge für KAUF oder VERKAUF auf der Grundlage eindeutiger Kalendersignale aus.
Es sind jedoch noch weitere Fortschritte erforderlich, um das System für reale Handelsbedingungen zu verfeinern. Wir befürworten eine kontinuierliche Entwicklung und Erprobung - insbesondere bei der Verbesserung des Risikomanagements und der Feinabstimmung der Filterkriterien - um eine optimale Leistung zu gewährleisten. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17271





- 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.
Guten Tag! Vielen Dank für die geleistete Arbeit.
Ich habe Probleme mit der Tabellenanzeige auf dem 2K-Monitor.
Guten Tag! Ich danke Ihnen für die geleistete Arbeit.
Ich habe Probleme mit der Tabellenanzeige auf dem 2K-Monitor.
Hallo. Herzlich willkommen. Dazu müssen Sie die Schriftgröße ändern, damit alles perfekt passt.