English 日本語
preview
Erstellen eines MQL5 Expert Advisors basierend auf der Strategie „Daily Range Breakout“

Erstellen eines MQL5 Expert Advisors basierend auf der Strategie „Daily Range Breakout“

MetaTrader 5Handel | 14 Januar 2025, 09:07
215 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In diesem Artikel werden wir untersuchen, wie man einen Expert Advisor (EA) in MetaQuotes Language 5 (MQL5) auf der Grundlage der Daily Range Breakout-Strategie erstellt. Da Händler ständig auf der Suche nach effektiven automatisierten Handelslösungen sind, bietet die Daily Range Breakout-Strategie einen systematischen Ansatz, der Kursbewegungen jenseits einer definierten Bandbreite ausnutzt, was sie zu einer attraktiven Option für Forex-Händler auf MetaTrader 5 macht.

Wir werden zunächst die grundlegenden Prinzipien der Daily Range Breakout-Strategie erläutern und damit eine solide Grundlage für ihre Umsetzung im automatisierten Handel schaffen. Als Nächstes werden wir uns mit den Besonderheiten der Identifizierung von Ausbruchsbedingungen und der Festlegung von Einstiegs- und Ausstiegspunkten befassen. Anschließend führen wir Sie durch den Kodierungsprozess in MQL5 und heben dabei die wesentlichen Funktionen und die Logik hervor, die die Strategie bestimmen. Darüber hinaus werden wir die Bedeutung des Backtestings und der Optimierung des Programms erörtern, um seine Effektivität unter Handelsbedingungen sicherzustellen. In diesem Artikel werden wir unter anderem folgende Themen behandeln:

  1. Verstehen der Strategie des Daily Range Breakout
  2. Die Blaupause des Expert Advisors
  3. Implementieren des Daily Range Breakout in MQL5
  4. Backtesting und Optimierung
  5. Schlussfolgerung

Am Ende dieses Artikels werden Sie mit dem Wissen ausgestattet sein, um einen MQL5 Expert Advisor zu entwickeln, der die Daily Range Breakout-Strategie effektiv einsetzt und Ihren Handelsansatz verbessert. Fangen wir an.


Verstehen der Strategie des Daily Range Breakout

Der Daily Range Breakout ist unter Forex-Händlern eine bekannte Handelsmethode. So können sie von großen Kursbewegungen profitieren, die auftreten, nachdem der Markt eine tägliche Spanne gebildet hat. Die Strategie nutzt die Kursentwicklung des Marktes, um herauszufinden, wo wichtige Unterstützungs- und Widerstandsniveaus liegen. Sobald diese Händler wissen, wo diese Niveaus liegen, handeln sie mit deren Ausbrüchen und halten Ausschau nach den wahrscheinlichen großen Bewegungen, die in der Regel auftreten, nachdem der Markt eines dieser Niveaus durchbrochen hat.

Diese Strategie konzentriert sich auf die tägliche Spanne, die sich aus der Differenz zwischen dem höchsten und dem niedrigsten Kurs eines Währungspaares innerhalb eines Handelstages ergibt. Die Ausbruchspunkte werden aus der Spanne des vorangegangenen Handelstages abgeleitet. Ein Ausbruch findet statt, wenn der Kurs über ein etabliertes Widerstandsniveau oder unter ein Unterstützungsniveau steigt. Im Nachhinein betrachtet, scheinen die Kurse des letzten Tages sehr gut definierte Niveaus festzulegen, die als potenzielle Ausbruchspunkte genutzt werden können. Wenn der Kurs das Widerstandsniveau nach oben durchbricht, wird eine Kauf-Position eingenommen. Wenn der Kurs die Unterstützungsmarke durchbricht, wird eine Verkaufs-Position eingegangen. Hier ist eine Illustration dessen, was wir meinen.

ILLUSTRATION DES TÄGLICHEN BEREICHSAUSBRUCHS

Um eine maximale Wirksamkeit zu erzielen, wird diese Strategie entweder auf dem 1-Stunden- oder dem 4-Stunden-Chart angewendet. Wenn Händler diese Vorlage für diese Zeitrahmen verwenden, können sie oft die größeren, bedeutenderen Kursbewegungen erfassen. Dies liegt daran, dass die Strategie das Rauschen in den unteren Zeitrahmen weitgehend ausblendet. Bei der Breakout-Strategie wird in der Regel das Kursgeschehen der asiatischen Sitzung genutzt, um die Tagesspanne zu bestimmen, bevor der Handel während der Londoner und New Yorker Sitzung durchgeführt wird. Breakout-Strategien haben in der Regel das Problem, dass sie falsche Signale geben, und der Daily Range Breakout ist da keine Ausnahme. Wie bei jeder Handelsstrategie ist es also auch beim Daily Range Breakout wichtig, das Risiko zu kontrollieren. Platzieren Sie Ihre Stop-Loss-Order bei Kaufpositionen knapp unter dem letzten Swing-Tief und bei Verkaufspositionen über dem letzten Swing-Hoch, um Ihr Risiko in Grenzen zu halten. Das wird unsere Strategie sein. Er steuert das Risiko durch eine Stop-Loss-Order, die über oder unter dem letzten Swing-Hoch bzw. -Tief platziert wird. Hier noch einmal ein Beispiel für die Stop-Loss-Logik.

STOP-LOSS-EINTRAG

Die Daily Range Breakout Strategie ist in mehrfacher Hinsicht vorteilhaft. Erstens ist es aufgrund seiner Einfachheit sowohl für Anfänger als auch für erfahrene Händler geeignet. Zweitens verwendet es definierte Niveaus, was die Händler davon abhält, zu viele diskretionäre Entscheidungen zu treffen. Die Art und Weise, wie diese Handelsmethode den Markt aufschlüsselt, ergibt ein klares Bild vor und nach jeder täglichen Sitzung. Am Morgen lässt sich die Handelsaktivität des Marktes in eine bestimmte „Bandbreite“ einteilen. Nach Schließung der Vormittagssitzung wird der „Durchbruch“ durch die obere oder untere Linie der Spanne zu einem möglichen Handelssignal für den nächsten Tag. Im nächsten Abschnitt werden wir unsere Handelsparameter genauer definieren, indem wir einen klaren Plan mit allen spezifischen Details erstellen.


Die Blaupause des Expert Advisors

Ausbruch aus dem oberen Bereich: Kaufbedingung

Wenn der Kurs die am Vortag festgelegte obere Spanne durchbricht, bedeutet dies einen Aufwärtsdurchbruch und deutet darauf hin, dass der Markt weiter steigen könnte. Dieser Ausbruch deutet auf ein starkes Kaufinteresse und das Potenzial für eine weitere Aufwärtsbewegung hin. Wir eröffnen eine Kaufposition, wenn der Schlusskurs des aktuellen Balkens über dem oberen Bereich liegt, um von der Dynamik zu profitieren, die in der Regel auf solche Ausbrüche folgt.

AUSBRUCH AUS DEM OBEREN BEREICH

Ausbruch aus dem unteren Bereich: Verkaufsbedingung

Umgekehrt bedeutet ein Durchbruch des Kurses unter die am Vortag ermittelte untere Spanne einen bärischen Ausbruch und deutet darauf hin, dass der Markt weiter fallen könnte. Dieser Ausbruch deutet auf starken Verkaufsdruck und das Potenzial für eine weitere Abwärtsbewegung hin. Wir eröffnen eine Verkaufsposition, wenn der Schlusskurs des aktuellen Kursbalkens unter dem unteren Niveau der Handelsspanne liegt, da wir nach dem Ausbruch eine weitere Kursschwäche erwarten.

AUSBRUCH AUS DEM UNTEREN BEREICH

Diese visuellen Darstellungen des Strategieentwurfs werden hilfreich sein, wenn wir diese Handelsbedingungen in MQL5 implementieren und als Referenz für die Codierung präziser Ein- und Ausstiegsregeln dienen.


Implementieren des Daily Range Breakout in MQL5

Nachdem wir alle Theorien über die Daily Range Breakout Handelsstrategie gelernt haben, wollen wir die Theorie automatisieren und einen Expert Advisor (EA) in MetaQuotes Language 5 (MQL5) für MetaTrader 5 erstellen.

Um einen Expert Advisor (EA) zu erstellen, klicken Sie auf Ihrem MetaTrader 5-Terminal auf die Registerkarte Tools und aktivieren Sie MetaQuotes Language Editor oder drücken Sie einfach F4 auf Ihrer Tastatur. Alternativ können Sie auch auf das IDE-Symbol (Integrated Development Environment) in der Symbolleiste klicken. Dadurch wird die MetaQuotes-Spracheditor-Umgebung geöffnet, die das Schreiben von Handelsrobotern, technischen Indikatoren, Skripten und Funktionsbibliotheken ermöglicht.

META-EDITOR ÖFFNEN

Sobald der MetaEditor geöffnet ist, navigieren Sie in der Symbolleiste zur Registerkarte „Datei“ und wählen Sie „Neue Datei“, oder drücken Sie einfach die Tastenkombination STRG + N, um ein neues Dokument zu erstellen. Alternativ könnten wir auch auf das Symbol New auf der Registerkarte Werkzeuge klicken. Daraufhin erscheint ein Popup-Fenster des MQL-Assistenten.

EA NEU ERSTELLEN

In dem sich öffnenden Assistenten markieren wir die Option Expert Advisor (Template bzw. Vorlage) und klicken auf Weiter (Next).

MQL WIZARD

Wir geben in den allgemeinen Eigenschaften des Expert Advisors unter dem Abschnitt Name den Dateinamen Ihres Experten an. Nicht vergessen, den Backslash vor dem Namen des EA verwenden, um einen Ordner anzugeben oder zu erstellen, wenn er nicht existiert. Hier haben wir zum Beispiel standardmäßig „Experts\“. Das bedeutet, dass unser EA im Ordner Experts erstellt wird und wir ihn dort finden können. Die anderen Abschnitte sind ziemlich einfach, aber Sie können dem Link am Ende des Assistenten folgen, um zu erfahren, wie der Prozess genau abläuft.

NEUER EA-NAME

Nachdem Sie den gewünschten Expert Advisor-Dateinamen eingegeben haben, klicken Sie auf Weiter, dann auf Weiter und schließlich auf Fertig stellen. Nachdem wir all dies getan haben, können wir nun unsere Strategie programmieren.

Zunächst definieren wir einige Metadaten über den Expert Advisor (EA). Dazu gehören der Name des EA, die Copyright-Informationen und ein Link zur MetaQuotes-Website. Wir geben auch die Version des EA an, die auf „1.00“ eingestellt ist.

//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

Beim Laden des Programms werden Informationen angezeigt, die dem unten gezeigten Bild entsprechen.

METADATEN-INFORMATIONEN

Zunächst binden wir eine Handelsinstanz ein, indem wir #include am Anfang des Quellcodes verwenden. Dadurch erhalten wir Zugriff auf die „CTrade-Klasse“, die wir zur Erstellung eines Handelsobjekts verwenden werden. Dies ist von entscheidender Bedeutung, da wir sie zur Eröffnung von Handelsgeschäften benötigen.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Der Präprozessor wird die Zeile #include <Trade/Trade.mqh> durch den Inhalt der Datei Trade.mqh ersetzen. Die spitzen Klammern zeigen an, dass die Datei Trade.mqh aus dem Standardverzeichnis entnommen wird (normalerweise ist es das Terminal-Installationsverzeichnis\MQL5\Include). Das aktuelle Verzeichnis wird bei der Suche nicht berücksichtigt. Die Zeile kann an beliebiger Stelle im Programm platziert werden, aber in der Regel werden alle Einschlüsse am Anfang des Quellcodes platziert, um den Code besser zu strukturieren und die Referenz zu erleichtern. Die Deklaration des Objekts obj_Trade der Klasse CTrade ermöglicht uns dank der MQL5-Entwickler einen einfachen Zugriff auf die in dieser Klasse enthaltenen Methoden.

CTRADE CLASS

Danach müssen wir mehrere wichtige Variablen deklarieren, um die Daten des Bereichsausbruchs zu speichern und zu verfolgen.

double maximum_price = -DBL_MAX;      //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;       //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time;  //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

Hier deklarieren wir mehrere wichtige Variablen, um wichtige Preisdaten zu verfolgen und Bereichsausbrüche in der Handelslogik zu behandeln. Zunächst initialisieren wir zwei Variablen vom Typ double, „maximum_price“ und „minimum_price“, die den höchsten und den niedrigsten Preis in einem bestimmten Zeitraum speichern. Der „maximum_price“ wird auf -DBL_MAX gesetzt, den kleinstmöglichen doppelten Wert, der sicherstellt, dass jeder angetroffene Preis höher ist und diesen Anfangswert ersetzt. In ähnlicher Weise setzen wir „minimum_price“ auf DBL_MAX, den größtmöglichen doppelten Wert, um sicherzustellen, dass jeder niedrigere Preis ihn als Mindestpreis ersetzt.

Wir deklarieren auch zwei datetime-Variablen, „maximum_time“ und „minimum_time“, um die genauen Zeiten zu speichern, zu denen die Höchst- und Mindestpreise auftreten. Diese werden uns später helfen, wenn wir uns auf die spezifischen Zeitpunkte beziehen müssen, zu denen diese Preisniveaus erreicht wurden.

Zusätzlich werden zwei Variablen vom Typ bool deklariert, um die Logik in Bezug auf Preisspannen und Ausbrüche zu handhaben. Die erste, „isHaveDailyRange_Prices“, wird auf „false“ initialisiert und dient als Flag, die anzeigt, ob die Preise der täglichen Spanne (d. h. das Maximum und Minimum) erfolgreich ermittelt wurden. Die zweite Option „isHaveRangeBreak“, die ebenfalls auf „false“ initialisiert ist, dient als Flag, die anzeigt, ob ein Ausbruch stattgefunden hat, d. h., ob sich der Kurs außerhalb der täglichen Spanne bewegt hat. Außerdem werden wir die Bereiche im Chart visuell darstellen. Wir brauchen also Namen für die Bereiche und können sie hier ebenfalls angeben.

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

Hier werden drei Präprozessoranweisungen definiert, die Präfixe für die Benennung verschiedener grafischer Objekte im Zusammenhang mit einem Handelsbereich erstellen. Wir verwenden die Direktive #define „RECTANGLE_PREFIX“ „RANGE RECTANGLE“, um eine konsistente Namenskonvention für Rechtecke, die den Handelsbereich repräsentieren, zu etablieren, was die Identifizierung und Verwaltung dieser Objekte innerhalb des Charts erleichtert. In ähnlicher Weise erzeugt #define „UPPER_LINE_PREFIX“ „UPPER LINE“ ein Präfix speziell für die obere Begrenzungslinie des Bereichs, während „LOWER_LINE_PREFIX“ „LOWER LINE“ denselben Zweck für die untere Begrenzungslinie erfüllt. Durch die Verwendung dieser Präfixe wird sichergestellt, dass alle grafischen Objekte, die sich auf den Bereich beziehen, systematisch benannt werden, was dazu beiträgt, die Klarheit und Organisation des Codes aufrechtzuerhalten, insbesondere wenn mehrere Objekte im Chart vorhanden sein können.

Nun können wir zur eigentlichen Logik der Code-Verarbeitung übergehen. Wir werden unsere Logik bei jedem Tick ausführen und daher direkt in die Ereignisbehandlung von OnTick eintauchen, der bei jedem Tick, der im Chart verarbeitet wird, aufgerufen und ausgeführt wird.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

}

Dies ist einfach die standardmäßige Ereignisbehandlung eines Tick-Ereignisses, den wir als Grundlage für unsere Steuerungslogik verwenden werden. Als Nächstes müssen wir einige Variablen deklarieren, die unsere Zeitbereichslogik enthalten.

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

Wir deklarieren drei statische Variablen zur Verwaltung der zeitbezogenen Funktionen. Der ersten Variablen „midnight“ wird der Wert zugewiesen, der von der Funktion iTime zurückgegeben wird, die die Uhrzeit von Mitternacht für das TagesChart des aktuellen Symbols abruft, das mit _Symbol bezeichnet wird und dessen Periode auf PERIOD_D1 eingestellt ist, um anzuzeigen, dass wir mit täglichen Candlesticks arbeiten wollen. 0 kennzeichnet den aktuellen Balken. Dadurch wird ein Basisbezugspunkt für die täglichen Berechnungen geschaffen.

Als Nächstes berechnen wir die Zeit für „sixAM“, indem wir sechs Stunden, dargestellt als 6 * 3600, wobei 3600 die Anzahl der Sekunden in einer Stunde ist (d.h. 1 Stunde multipliziert mit 60 Minuten multipliziert mit 60 Sekunden), zur Variable „midnight“ addieren. Dies ermöglicht es uns, einen Zeitpunkt für die tägliche Analyse nach der Markteröffnung festzulegen, was uns die Analyse der Kursentwicklung ab den frühen Morgenstunden des Handelstages erleichtert.

Schließlich legen wir die Variable „scanBarTime“ fest, um die Abfragezeit für den nächsten Takt nach „sixAM“ anzugeben. Wir erreichen dies, indem wir dynamisch einen zusätzlichen Balken zur aktuellen 6-Uhr-Scanzeit hinzufügen, sodass wir auch den 6-Uhr-Balken für den Scan berücksichtigen. Die 1 steht für die Anzahl der Balken, zu denen gesprungen werden soll, und die Funktion PeriodSeconds wandelt den aktuellen Zeitrahmen des Charts automatisch in Sekunden um. Wir könnten zum Beispiel ein 1-Stunden-Chart haben, was bedeutet, dass wir die 1 Stunde in Sekunden umwandeln und die Sekunden mit einem Balken multiplizieren, was typischerweise 3600 Sekunden ergibt, und diese dann zu 6 Uhr morgens addieren, womit wir bei 7 Uhr morgens landen. Insgesamt sind diese statischen Variablen entscheidend für die Umsetzung unserer zeitbasierten Logik innerhalb der Handelsstrategie.

Als Nächstes können wir auch Variablen deklarieren, um unsere gültigen Ausbruchsbereiche in der Zeit zu definieren. Wenn ein Ausbruch nach 7 Stunden oder einer bestimmten Zeit wie 13 Uhr geschieht, betrachten wir kein Signal als gültig und warten auf das Setup des nächsten Tages.

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+5) * 3600; //--- Set the end of valid breakout time to 11 AM

Hier deklarieren wir zwei zusätzliche statische Variablen, um das Zeitfenster für gültige Ausbruchsbedingungen innerhalb unserer Handelsstrategie zu definieren. Die erste Variable, „validBreakTime_start“, wird mit dem Wert von „scanBarTime“ initialisiert, den wir zuvor festgelegt haben. Damit ist der Beginn des gültigen Ausbruchszeitpunkts festgelegt, sodass wir uns ab dem nächsten Balken nach 6 Uhr morgens auf die Kursentwicklung konzentrieren können.

Die zweite Variable, „validBreakTime_end“, wird berechnet, indem (6 + 5) * 3600 zur Variablen „midnight“ addiert wird. Dieser Ausdruck gibt das Ende unseres gültigen Ausbruchszeitraums an, der 11 Uhr morgens entspricht. Durch die Festlegung dieses Zeitrahmens schaffen wir ein klares Fenster, in dem wir die Ausbruchsbedingungen bewerten und sicherstellen, dass unsere Handelsentscheidungen auf Kursbewegungen basieren, die innerhalb dieser definierten Spanne auftreten. Nach all dem sind wir bereit, mit unserer Logik zu beginnen. Das erste, was wir beachten müssen, ist, dass wir jeden Tag nach den Setups suchen wollen, also brauchen wir eine Logik, die einen neuen Tag identifiziert.

   if (isNewDay()){
        //---

   }

Wir verwenden eine if-Anweisung, um zu prüfen, ob ein neuer Tag vorhanden ist, und wenn ja, führen wir den Codeausschnitt darin aus. Wir verwenden eine nutzerdefinierte boolesche Funktion „isNewDay“, um zu prüfen, ob ein neuer Tag vorliegt. Die Logik ist wie folgt:

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}

Hier definieren wir die boolesche Funktion „isNewDay“, die dafür zuständig ist, festzustellen, ob ein neuer Tag in unserer Handelsstrategie begonnen hat. Wir initialisieren eine boolesche Variable „newDay“ auf „false“, die als Kennzeichen dafür dient, ob ein neuer Tag begonnen hat. Um das aktuelle Datum und die Uhrzeit zu erfassen, erstellen wir eine Struktur vom Typ MqlDateTime mit dem Namen „Str_DateTime“. Wir verwenden die Funktion TimeToStruct, um die aus der aktuellen Uhrzeit gewonnene aktuelle Zeit in ein strukturiertes Format umzuwandeln und die Struktur „Str_DateTime“ mit den relevanten Datums- und Zeitinformationen zu füllen.

Als Nächstes deklarieren wir eine statische Integer-Variable „prevDay“, die auf Null initialisiert wird und den Tag des letzten aufgezeichneten Datums speichert. Anschließend wird der aktuelle Tag aus der Struktur „Str_DateTime“ abgerufen und der Integer-Variablen „currDay“ zugewiesen.

Wir vergleichen „prevDay“ mit „currDay“. Wenn sie gleich sind, bedeutet dies, dass wir uns immer noch innerhalb desselben Tages befinden, und wir setzen „newDay“ auf „false“. Umgekehrt erkennen wir, dass ein neuer Tag begonnen hat, wenn „prevDay“ von „currDay“ abweicht. In diesem Fall wird mit der Funktion Print eine Meldung über den Übergang zu einem neuen Tag gedruckt, wobei die Variable „prevDay“ mit dem Wert von „currDay“ aktualisiert wird. Anschließend setzen wir das Flag „newDay“ auf „true“, um zu bestätigen, dass ein neuer Tag begonnen hat. Schließlich gibt die Funktion den Wert des „newDay“-Flags zurück, sodass wir diese Information in unserer Handelslogik verwenden können, um festzustellen, ob aufgrund des Beginns eines neuen Tages irgendwelche Maßnahmen ergriffen werden müssen.

In dieser Funktion setzen wir nun alles zurück, wenn ein neuer Tag für die Tagesberechnungen und die Zuordnung der Steuerlogik beginnt.

      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+5) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day

In der Funktion setzen wir verschiedene Variablen und Parameter zu Beginn eines neuen Handelstages zurück, um neue Berechnungen und Datenverfolgung vorzubereiten. Wir beginnen damit, die neue Mitternachtszeit für den aktuellen Tag mit der Funktion iTime zu ermitteln, die den Zeitstempel für den Beginn des aktuellen Tagesbalkens liefert. Anschließend aktualisieren wir die Variable „midnight“ mit diesem neuen Wert.

Als Nächstes berechnen wir die Zeit für 6 Uhr morgens neu, indem wir 6 Stunden, dargestellt als „6 * 3600“, zu der neu gesetzten Variable „midnight“ hinzufügen. Dies gibt uns einen Anhaltspunkt für den Beginn der Handelssitzung am Morgen. Anschließend setzen wir „scanBarTime“ auf einen Balken nach 6 Uhr morgens, indem wir die Dauer einer Periode addieren, die wir mit der Funktion PeriodSeconds erhalten, um sicherzustellen, dass unsere Berechnungen mit der aktuellen Chartperiode übereinstimmen.

Anschließend aktualisieren wir die gültigen Ausbruchszeitfenster, indem wir „validBreakTime_start“ auf die neu berechnete „scanBarTime“ setzen. Diese Anpassung ist der Ausgangspunkt für die Prüfung potenzieller Ausbrüche während des Handelstages. Außerdem setzen wir „validBreakTime_end“ auf 11 Uhr, indem wir es als „midnight + (6 + 5) * 3600“ berechnen, um sicherzustellen, dass wir einen klaren Endpunkt für unsere Breakout-Auswertung haben. Außerdem setzen wir die Werte von „maximum_price“ und „minimum_price“ zurück, um die Preisbewegungen für den neuen Tag zu verfolgen, indem wir „maximum_price“ auf -DBL_MAX (den niedrigstmöglichen Wert) und „minimum_price“ auf DBL_MAX (den höchstmöglichen Wert) initialisieren. Diese Rückstellung ermöglicht es uns, die höchsten und niedrigsten Preise im Laufe des Tages genau zu erfassen.

Schließlich setzen wir die booleschen Flags „isHaveDailyRange_Prices“ und „isHaveRangeBreak“ auf „false“, was bedeutet, dass wir für den neuen Tag noch keine Tagesspanne festgelegt oder einen Bereichsdurchbruch ermittelt haben. Dieser vollständige Reset bereitet unser System auf neue Berechnungen vor und stellt sicher, dass wir die Preisentwicklung im Laufe des Tages genau verfolgen. Jetzt können wir zur Logik des Bar-Scans übergehen. Wir müssen den Scan nicht bei jedem Tick durchführen, sondern nur, wenn ein neuer Balken erzeugt wurde. Daher benötigen wir eine weitere Kontrolllogik, um die Identifizierung neuer Balken zu handhaben. 

   if (isNewBar()){
        //---

   }

Hier verwenden wir immer noch eine if-Anweisung in Verbindung mit der Funktion „isNewBar“, um die Logik für die Erzeugung eines neuen Balkens anzuwenden. Der angepasste Funktionscode ist unten in einem Codeschnipsel dargestellt.

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

Zunächst wird eine statische Variable mit der Bezeichnung „prevBars“ deklariert, die die vorherige Anzahl der im Chart angezeigten Balken speichert. Das Schlüsselwort static stellt sicher, dass die Variable ihren Wert zwischen den Funktionsaufrufen beibehält, sodass wir Änderungen der Taktanzahl effektiv verfolgen können. Als Nächstes wird die aktuelle Anzahl der Balken im Chart mit der Funktion iBars ermittelt, wobei _Symbol für das Handelsinstrument steht und _Period den Zeitrahmen des Charts angibt. Diese Funktion gibt die Gesamtzahl der derzeit verfügbaren Balken für das angegebene Symbol und den angegebenen Zeitraum zurück.

Anschließend vergleichen wir die aktuelle Balkenanzahl, die in der Variablen „currBars“ gespeichert ist, mit der vorherigen Balkenanzahl „prevBars“. Wenn diese beiden Werte gleich sind, bedeutet dies, dass sich seit der letzten Überprüfung kein neuer Balken gebildet hat, sodass wir „false“ zurückgeben, um anzuzeigen, dass wir uns immer noch auf demselben Balken befinden. Wenn sich die Zählungen unterscheiden, bedeutet dies, dass ein neuer Balken erstellt wurde, was uns dazu veranlasst, „prevBars“ mit dem Wert von „currBars“ zu aktualisieren. Schließlich geben wir „true“ zurück, um zu signalisieren, dass sich tatsächlich ein neuer Balken gebildet hat. Als Nächstes müssen wir innerhalb der Funktion die Daten verarbeiten, wenn sich ein neuer Balken bildet, wobei wir uns insbesondere auf eine bestimmte Zeitbedingung für die Extraktion der Preisdaten konzentrieren.

      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price

         //--- 

      }

Zunächst deklarieren wir eine Variable „currentBarTime“ mit Hilfe der Funktion iTime, die die Zeit des aktuellen Balkens im Chart abruft. Auf diese Weise können wir feststellen, ob wir uns zu einem bestimmten Zeitpunkt des Tages befinden, an dem wir bestimmte Kursdaten verarbeiten wollen. Als Nächstes überprüfen wir zwei Bedingungen in der if-Anweisung. Zunächst wird überprüft, ob die Zeit des aktuellen Balkens mit der Zeit des Scan-Balkens übereinstimmt, d. h. mit der Zeit, die für die Analyse vorgesehen ist (in diesem Fall ist sie auf 6 Uhr morgens eingestellt). Zweitens überprüfen wir, ob die täglichen Preise noch nicht extrahiert wurden, indem wir sicherstellen, dass das Flag „isHaveDailyRange_Prices“ falsch ist. Wenn beide Bedingungen erfüllt sind, bedeutet dies, dass wir uns zum richtigen Zeitpunkt befinden und die Preisspannendaten extrahiert werden müssen.

Anschließend wird mit der Funktion Print eine Meldung protokolliert, die anzeigt, dass genügend Balkendaten vorhanden sind und der Extraktionsprozess beginnt. So lässt sich nachvollziehen, wann und warum der Prozess während der Ausführung ausgelöst wird. Wir berechnen nun die Gesamtzahl der Takte zwischen Mitternacht und 6 Uhr morgens, was für die Bestimmung der Preisspanne in diesem Zeitraum entscheidend ist. Die Funktion PeriodSeconds gibt die Dauer der einzelnen Balken an, und wir teilen die Zeitdifferenz zwischen „sixAM“ und „midnight“ durch diese Dauer, um die Gesamtzahl der Balken zu berechnen. Wir fügen 1 hinzu, um sicherzustellen, dass alle Balken in diesem Bereich enthalten sind.

Schließlich drucken wir die Gesamtzahl der zu überprüfenden Balken mit einem weiteren Aufruf von Print aus und deklarieren zwei Variablen: „highest_price_bar_index“ und „lowest_price_bar_index“. Wir initialisieren diese Variablen mit -1 und verwenden sie, um den Index der Balken zu speichern, die den höchsten bzw. niedrigsten Preis innerhalb des beobachteten Bereichs enthalten. Damit sind wir in der Lage, die Preisdaten aus diesen spezifischen Balken zu extrahieren. Wenn wir das Programm ausführen, erhalten wir die folgenden Ergebnisse.

BALKEN-SCAN-BESTÄTIGUNG

Wir sehen, dass wir, sobald die erforderliche Anzahl von Takten für die Betrachtung des Bereichs festgelegt wurde, den Status der Fertigstellung und die Anzahl der Takte innerhalb des zu betrachtenden Bereichs mitteilen. Nun können wir die Daten aus dem ermittelten Tagesbereich extrahieren und die Bereichsgrenzen festlegen.

         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }

Für die Datenextraktion durchlaufen wir eine Schleife durch alle Balken innerhalb eines bestimmten Zeitraums (von Mitternacht bis 6 Uhr morgens), um die höchsten und niedrigsten Preise zu ermitteln. Ziel ist es, die Höchst- und Tiefstpreise zu finden, die innerhalb dieser Spanne aufgetreten sind, und den Zeitpunkt festzuhalten, zu dem sie aufgetreten sind. Wir beginnen mit der Einrichtung einer for-Schleife mit der Anweisung „for (int i=1; i<=total_bars ; i++)“. Diese Anweisung bedeutet, dass die Schleife jeden Balken durchläuft, vom ersten (Index 1) bis zu „total_bars“, der zuvor berechnet wurde, um die Anzahl der Balken zwischen Mitternacht und 6 Uhr morgens darzustellen. Die Variable „i“ steht für den Index eines jeden Taktes in der Schleife.

Innerhalb der Schleife werden die Eröffnungs- und Schlusskurse für jeden Balken mit den nutzerdefinierten Funktionen „open“ bzw. „close“ abgerufen. Diese beiden Variablen - „open_i“ für den Eröffnungskurs und „close_i“ für den Schlusskurs - helfen uns bei der Analyse der Kursbewegung eines jeden Balkens. Bevor wir fortfahren, sollten wir uns vergegenwärtigen, dass diese nutzerdefinierten Funktionen nur Hilfsfunktionen sind, die wir an anderer Stelle im globalen Bereich definieren und direkt verwenden.

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

Als Nächstes verwenden wir eine ternäre Operation, um die höchsten und niedrigsten Preise für jeden Balken zu ermitteln. Die Anweisung „double highest_price_i = (open_i > close_i) ? open_i : close_i;“ prüft, ob der Eröffnungskurs größer ist als der Schlusskurs. Ist dies der Fall, wird der Eröffnungskurs als Höchstkurs für diesen Balken festgelegt. Andernfalls ist der Schlusskurs der höchste. In ähnlicher Weise vergleicht „double lowest_price_i = (open_i < close_i) ? open_i : close_i;“ den Eröffnungs- und den Schlusskurs, um den niedrigsten Preis für den Balken zu ermitteln.

Nach der Berechnung des Höchst- und Tiefstpreises für den aktuellen Balken werden diese mit dem gesamten Höchst- und Tiefstpreis für den gesamten Zeitraum bis zu diesem Zeitpunkt verglichen:

  • Wenn der höchste Preis für diesen ausgewählten Balken größer ist als der aufgezeichnete Höchstpreis, wird der Höchstpreis auf diesen neuen Wert aktualisiert. Wir speichern auch den Index dieses Balkens in „highest_price_bar_index“ und erfassen die Zeit dieses Balkens mit der Funktion „time“, die die mit dem i-ten Balken verbundene Zeit abruft. Auf diese Weise können wir nachvollziehen, wann der höchste Preis erzielt wurde.
  • Wenn der „niedrigste_Preis_i“ niedriger ist als der erfasste „minimum_price“, wird der „minimum_price“ auf diesen neuen Wert aktualisiert. Wir speichern auch den Index dieses Balkens in „lowest_price_bar_index“ und erfassen die Zeit dieses Balkens in „minimum_time“ mit Hilfe der Funktion „time“.

Dieses Verfahren stellt sicher, dass wir am Ende der Schleife die höchsten und niedrigsten Preise innerhalb der Zeitspanne von Mitternacht bis 6 Uhr morgens sowie die Zeiten, zu denen sie auftraten, ermittelt haben. Wir werden diese Werte später verwenden, um wichtige Kursniveaus für die Ausbruchsanalyse festzulegen. Um sicherzustellen, dass wir die Preisniveaus erhalten, können wir sie zu Bestätigungszwecken protokollieren.

         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);

Hier drucken wir nur die ermittelten Höchst- und Tiefstkurse zusammen mit ihren Bar-Indizes und der Uhrzeit zur Bestätigung aus. Wenn wir das Programm ausführen, erhalten wir die folgenden Daten:

PREISSTUFEN

Aus dem Bild können wir ersehen, dass unsere Höchstpreise beim 7. Balken liegen, dessen Daten aus dem Protokoll 0,6548 betragen, was dem Eröffnungskurs im Datenfenster entspricht. Die Uhrzeit ist Mitternacht, wie in der Zeit- und Datumsskala des Fadenkreuzes auf der x-Achse angezeigt. So können wir sicher sein, dass wir die Tagespreise haben und sie für weitere Analysen verwenden können. Wir brauchen die Analyse jedoch nicht mehr während des Tages durchzuführen, da wir die erforderlichen Daten bereits erfasst haben. So können wir unser boolesches Flag für die Variable für die Preise auf true setzen und auf den nächsten Tag warten, um die Preise erneut zu erfassen.

         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted

Sobald wir die Flag gesetzt haben, sind wir startklar. Auf dem Chart ist der Aufbau der Handelsspanne jedoch nicht sichtbar. Wir können also einen Mechanismus entwickeln, mit dem wir die Bereiche im Chart darstellen können. Um dies zu erreichen, müssen wir Funktionen erstellen, die wir wiederverwenden können. Die erste Funktion ist diejenige, die für die Erstellung von Rechtecken zuständig ist.

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}

Hier erstellen wir eine void Funktion „create_Rectangle“, die die Erstellung eines Rechteckobjekts auf einem Chart im MetaTrader übernimmt. Die Funktion benötigt sechs Parameter: „objName“ (der Name des Objekts), „time1“ und „price1“ (Koordinaten der ersten Ecke des Rechtecks), „time2“ und „price2“ (Koordinaten der gegenüberliegenden Ecke) und „clr“ (die Farbe des Rechtecks). In der Funktion wird zunächst geprüft, ob ein Objekt mit dem angegebenen Namen bereits im Chart vorhanden ist, indem die Funktion ObjectFind verwendet wird. Wird das Objekt nicht gefunden (d. h. es wird ein Wert kleiner als 0 zurückgegeben), wird mit der Erstellung des Rechtecks fortgefahren.

Anschließend rufen wir die Funktion ObjectCreate auf, um das Rechteck-Objekt zu erstellen. Dabei geben wir die erforderlichen Parameter an: die Chart-ID (für den aktuellen Chart auf 0 gesetzt), den Objektnamen, den Objekttyp (OBJ_RECTANGLE) und die Koordinaten (definiert durch „time1, price1“ und „time2, price2“).

Anschließend verwenden wir die Funktionen ObjectSetInteger und ObjectSetDouble, um die einzelnen Eigenschaften des Rechtecks festzulegen:

  • „ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)“ setzt die Zeit für die erste Ecke (Startpunkt) des Rechtecks.
  • „ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)“ setzt den Preis für die erste Ecke (Startpunkt) des Rechtecks.
  • „ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)“ setzt die Zeit für die zweite Ecke (Endpunkt) des Rechtecks.
  • „ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)“ setzt den Preis für die zweite Ecke (Endpunkt) des Rechtecks.

Außerdem aktivieren wir die Fülleigenschaft für das Rechteck mit der Methode OBJPROP_FILL, wodurch das Rechteck im Chart visuell ausgefüllt wird, anstatt nur einen Umriss zu bilden. Danach wird die Farbe des Rechtecks mit der Methode OBJPROP_COLOR festgelegt, wobei die angegebene Farbe („clr“), die der Funktion übergeben wurde, angewendet wird. Das Rechteck wird außerdem so konfiguriert, dass es vor anderen Objekten erscheint, indem die Eigenschaft OBJPROP_BACK deaktiviert wird. Schließlich rufen wir die Funktion ChartRedraw auf, um das Chart zu aktualisieren und sicherzustellen, dass das neu erstellte Rechteck sofort im Chart angezeigt wird. Die nächste Funktion, die wir definieren müssen, ist eine Funktion zur Erstellung von Linien im Chart, mit denen wir die Zeitspanne zwischen Anfang und Ende anzeigen können.

//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

Hier erstellen wir eine weitere ungültige Funktion „create_Line“ und übergeben ihr ebenfalls die erforderlichen Parameter. Die Funktion benötigt acht Parameter: „objName“ (Name des Linienobjekts), „time1“ und „price1“ (Koordinaten des Startpunkts), „time2“ und „price2“ (Koordinaten des Endpunkts), „width“ (die Dicke der Linie), „clr“ (die Farbe der Linie) und „text“ (die Beschreibung, die neben der Trendlinie angezeigt wird). Zunächst prüfen wir mit ObjectFind, ob die Trendlinie bereits im Chart existiert. Wenn das Trendline-Objekt mit dem angegebenen Namen nicht existiert (Rückgabewert kleiner als 0), wird mit der Erstellung der Linie fortgefahren.

Um die Trendlinie zu erstellen, verwenden wir die Funktion ObjectCreate, die den Objekttyp als OBJ_TREND definiert und die Start- („time1, price1“) und Endkoordinaten („time2, price2“) für die Trendlinie zuweist.

Anschließend verwenden wir ObjectSetInteger und ObjectSetDouble, um dem Anfangs- und Endpunkt der Linie Eigenschaften zuzuweisen:

  • „ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)“ setzt die Zeit des ersten Punktes.
  • „ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)“ setzt den Preis des ersten Punktes.
  • „ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)“ setzt die Zeit des zweiten Punktes.
  • „ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)“ setzt den Preis des zweiten Punktes.

Wir fahren fort, indem wir die Breite der Linie mit der Eigenschaft OBJPROP_WIDTH festlegen, die steuert, wie dick die Linie sein wird, gefolgt von der Einstellung der Farbe der Linie. Als Nächstes stellen wir sicher, dass die Linie vor anderen Objekten angezeigt wird, indem wir die Eigenschaft OBJPROP_BACK auf false setzen, was bedeutet, dass die Trendlinie nicht hinter anderen Chart-Elementen erscheint.

Um die Anzeige der Trendlinie zu verbessern, rufen wir die aktuelle Chart-Skala mit ChartGetInteger ab. Wenn wir die Skala erfolgreich erhalten haben, verwenden wir sie, um eine Schriftgröße für den beschreibenden Text festzulegen, der neben der Zeile angezeigt wird. Je nach Maßstab des Charts wird die Schriftgröße entsprechend angepasst, wobei die Standardeinstellung 11 ist. Als Nächstes definieren wir eine beschreibende Bezeichnung „Right Price“, die neben der Trendlinie platziert wird, und wir generieren einen Objektnamen für diese Bezeichnung, indem wir „txt“ an den ursprünglichen Objektnamen anhängen, wodurch „objNameDescr“ entsteht.

Anschließend erstellen wir das Textobjekt mit der Funktion ObjectCreate, platzieren es am Ende der Zeile („time2, price2“) und setzen verschiedene Eigenschaften:

  • „ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr)“ setzt die Textfarbe so, dass sie der Farbe der Trendlinie entspricht.
  • „ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize)“ setzt die Schriftgröße auf der Grundlage des zuvor berechneten Wertes.
  • „ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT)“ verankert den Text am linken Rand der Zeile.
  • „ObjectSetString(0, objNameDescr, OBJPROP_TEXT, ' ' + text)“ setzt den tatsächlichen Textinhalt auf den in der Funktion übergebenen Parameter „text“.
  • „ObjectSetString(0, objNameDescr, OBJPROP_FONT, 'Calibri')“ setzt die Schriftart des Textes auf „Calibri“, um die Lesbarkeit zu verbessern.

Zum Schluss aktualisieren wir das Chart, indem wir ChartRedraw aufrufen, um sicherzustellen, dass die neu erstellte Trendlinie und der dazugehörige Text korrekt im Chart angezeigt werden. Wir können dann diese Funktionen aufrufen und sie verwenden, um die Bereichsdetails abzubilden.

         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line

Wenn wir den Code kompilieren und das Programm ausführen, erhalten wir die folgenden Angaben.

ERSTE VISUELLE DARSTELLUNG

Wir können nun sehen, dass wir die Details der Spanne visuell auf dem Chart abbilden und darstellen, was ansprechender und einfacher ist, um die Preise zu referenzieren und zu bestätigen. Als Nächstes müssen wir nach Ausbrüchen suchen. Dies ist der Punkt, an dem wir bei jedem Tick Überprüfungen durchführen müssen, um Level-Breaks zu bestimmen und, wenn die Bedingungen erfüllt sind, die entsprechende Handelslogik zu initiieren. Für den Ausbruch auf der oberen Ebene gilt die folgende Logik.

   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
   }

Hier wird geprüft, ob der Schlusskurs des vorherigen Balkens den Tageshöchstkurs übersteigt, was auf einen Ausbruch nach oben hindeutet. Zunächst werden der Schlusskurs und die Uhrzeit des vorangegangenen Balkens mit den nutzerdefinierten Funktionen „close“ und „time“ ermittelt und in „barClose“ bzw. „barTime“ gespeichert. So können wir uns auf den Schlusskurs und die Uhrzeit des zu analysierenden Balkens beziehen.

Als Nächstes führen wir eine Reihe von Prüfungen durch, um festzustellen, ob ein Ausbruch stattgefunden hat. Wir prüfen, ob „barClose“ größer ist als „maximum_price“, um sicherzustellen, dass der Schlusskurs den höchsten für den Tag verzeichneten Kurs übersteigt. Wir überprüfen auch, ob die Preise der täglichen Spanne extrahiert wurden, indem wir das Flag „isHaveDailyRange_Prices“ verwenden, und bestätigen, dass zuvor kein Ausbruch erkannt wurde, indem wir das Flag „!isHaveRangeBreak“ verwenden. Außerdem stellen wir sicher, dass der Ausbruch innerhalb des gültigen Ausbruchsfensters erfolgt, indem wir prüfen, ob „barTime“ zwischen „validBreakTime_start“ und „validBreakTime_end“ liegt.

Wenn alle Bedingungen erfüllt sind, protokollieren wir das Ausbruchsereignis, indem wir eine Meldung ausgeben, dass der Schlusskurs die obere Spanne durchbrochen hat. Dann setzen wir „isHaveRangeBreak“ auf „true“, was bedeutet, dass ein Ausbruch erkannt wurde. Schließlich rufen wir die Funktion „drawBreakPoint“ auf, um diesen Ausbruch auf dem Chart visuell zu markieren. Die Funktion verwendet die Zeit, den Schlusskurs, die Markergröße, die Farbe und die Priorität des Balkens, um eine visuelle Darstellung des Ausbruchs anzuzeigen. Die Logik der Funktion ist ähnlich wie bei den vorherigen Funktionen.

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

Um nach Ausbrüchen im unteren Bereich zu suchen, verwenden wir eine ähnliche Logik wie bei der Suche nach Ausbrüchen im oberen Bereich.

   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
   }

Nach der Kompilierung erhalten wir folgendes Ergebnis.

AUSBRUCH DER UNTEREN EBENE

Das war ein Erfolg. Wir können sehen, dass bei einem Ausbruch aus dem unteren Level ein Breakpoint-Pfeil auf dem Chart angezeigt wird, der die Kerze, bei der der Ausbruch stattfindet, visuell festlegt. Lassen wir das Programm laufen und sehen wir auch den umgekehrten Ausbruch.

AUSBRUCH AUS DER OBEREN EBENE

Das war ein Erfolg. Wir können auch sehen, dass wir einen Ausbruch auf der oberen Ebene haben, wie erwartet. Das nächste, was wir tun müssen, ist, Positionen zu eröffnen, sobald diese Ausbrüche auftreten, und das wäre alles.

   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*2);
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*2);
   }

Mit dieser Logik können wir nun Positionen eröffnen. Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe.

AUSBRUCH NACH UNTEN HANDEN

Anhand des Bildes können wir feststellen, dass wir die Positionen richtig eröffnen, indem wir z. B. den Stop-Loss einer Verkaufsposition auf dem oberen Niveau und den Take-Profit 2 Mal die Spanne unterhalb des Einstiegspunktes platzieren. Im nächsten Abschnitt werden wir uns auf das Testen des Programms konzentrieren, um seine Leistung zu bewerten und die Parameter für optimale Ergebnisse fein abzustimmen.


Backtesting und Optimierung

Nach Abschluss der Implementierung besteht der nächste wichtige Schritt darin, den Expert Advisor (EA) gründlich zu testen, um seine Leistung zu bewerten und seine Parameter zu optimieren. Ein effektives Testen stellt sicher, dass sich die Strategie unter verschiedenen Marktbedingungen wie erwartet verhält, und minimiert das Risiko unvorhergesehener Probleme beim Handel. Hier werden wir den MetaTrader 5 Strategy Tester verwenden, um ein Backtesting und eine Optimierung durchzuführen, um die bestmöglichen Eingabewerte für unsere Strategie zu finden.

Für die Optimierung benötigen wir Eingaben im Bereich Einstellungen. Die Optimierung, die wir anwenden werden, berücksichtigt das Risiko-Ertrags-Verhältnis, die Gültigkeitsdauer des Breaks in Stunden und die Handelsrichtung, sobald ein Break eintritt. Das heißt, man könnte in Erwägung ziehen, bei einem Durchbruch des unteren Niveaus nicht zu verkaufen, sondern zu kaufen. So einfach ist das. Hier ist die Logik, die wir anwenden.

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

Hier definieren wir eine Enumeration und initialisieren einige Eingabevariablen, die wir zur Steuerung des Handelsverhaltens und der Strategieparameter verwenden werden. Zunächst deklarieren wir eine Enumeration namens „trade_direction“, die zwei mögliche Werte definiert: „Default_Trade_Directions“ und „Invert_Trade_Directions“. Ein Enum (Aufzählung) ist ein nutzerdefinierter Datentyp in MQL5, der es uns ermöglicht, Integer-Konstanten Namen zuzuweisen, wodurch der Code lesbarer und einfacher zu verwalten ist. In diesem Fall kann mit „trade_direction“ gesteuert werden, ob der Handel der Standard-Handelsrichtung folgt oder ob die Richtung auf der Grundlage bestimmter Bedingungen umgedreht wird.

Als Nächstes definieren wir drei Eingabevariablen, die es dem Nutzer ermöglichen, die Werte direkt in den Einstellungen des Expert Advisors zu ändern, ohne den Code selbst zu bearbeiten, aber sie sind nützlicher, wenn wir das Programm optimieren. Die erste Variable ist „r2r“, die standardmäßig auf 2 gesetzt ist und die wir verwenden werden, um einen Risiko-Ertrags-Aspekt der Strategie zu steuern. Das Schlüsselwort input zeigt an, dass diese Variable vom Nutzer von außen eingestellt werden kann. Die zweite Eingabe ist „hoursValidity“, initialisiert mit einem Standardwert von 5. Diese Variable steuert, wie lange Ausbruchsbedingungen oder -signale in Form von Stunden gültig bleiben.

Die dritte Eingabe schließlich ist „direction_of_trade“, die vom Typ „trade_direction“ ist (die Aufzählung, die wir zuvor definiert haben). Standardmäßig ist dieser Wert auf „Default_Trade_Directions“ eingestellt, aber der Nutzer kann ihn auf „Invert_Trade_Directions“ ändern, wenn er möchte, dass der Handel in die entgegengesetzte Richtung erfolgt. Diese Eingabe bietet Flexibilität bei der Entscheidung über die Handelsrichtung, ohne die Kernlogik des Expert Advisors zu verändern. In diesem Sinne müssen wir nur die entsprechenden statischen Parameter im Code ersetzen, um den dynamischen Aspekt hinzuzufügen.

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }

   //---

   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }

Wir haben nun die Änderungen hinzugefügt und die einzelnen Abschnitte zur Verdeutlichung hervorgehoben. Wenn wir das kompilieren, können wir jetzt die Eingaben im Abschnitt Eingaben wie unten finden, und wir können aus ihnen die Optimierung auswählen, um die besten Handelsparameter für das Programm zu finden.

EINGABEN DER OPTIMIERUNG

Auf dem Bild können wir sehen, dass wir die Eingaben für die Optimierung haben und wir können einfach auf die Schaltfläche „Start“ klicken, um die Optimierung zu beginnen. In diesem Fall haben wir nur einen Monat gewählt, um das Programm nicht zu sehr zu optimieren. Sobald diese abgeschlossen ist, legen wir die richtigen Einstellungen für das Programm fest und verwenden sie für den Backtest. Hier sind die erzielten Ergebnisse.

BACKTEST GIF

Das war ein Erfolg! Wir können daraus schließen, dass das Programm wie erwartet funktioniert hat. Der letzte Quellcodeausschnitt, der für die Erstellung und Implementierung der Daily Range Breakout-Strategie verantwortlich ist, lautet wie folgt:
//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

double maximum_price = -DBL_MAX;  //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;   //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time; //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialization code can be placed here if needed
   
   //---
   return(INIT_SUCCEEDED); //--- Return successful initialization
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   //--- Deinitialization code can be placed here if needed
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //--- 
   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }
   
   if (isNewBar()){
      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price
   
         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }
         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);
         
         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line
         
         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted
      }
   }
   
   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }
}

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}
//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}
//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

Backtest-Ergebnisse:

BACKTEST-ERGEBNISSE

Backtest-Grafik:

BACKTEST-GRAFIK

In dieser Testphase haben wir die Eingabeparameter optimiert und die Leistung der Strategie mit dem Strategietester überprüft. Die Anpassungen, die wir an den Eingabeparametern vorgenommen haben, verleihen der Handelsstrategie mehr Flexibilität. Wir haben bestätigt, dass die Strategie wie beabsichtigt funktioniert und günstige Ergebnisse erzielt, wenn wir sie backtesten und optimieren.


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass dieser Artikel einen Schritt-für-Schritt-Ansatz zur Erstellung eines Expert Advisors in MQL5 dargelegt hat, der mit dem täglichen Range Breakout handelt. Wir haben mit den wesentlichen Teilen begonnen, die dem EA die Fähigkeit verleihen, tägliche Preisspannen zu berechnen und die Ausbruchsniveaus festzulegen. Diese sind der Schlüssel zur Bestimmung der Zeitpunkte kurz vor und kurz nach dem Ausbruch des Kurses aus seiner Tagesspanne.

Darüber hinaus haben wir untersucht, wie man verschiedene MQL5-Funktionen implementiert, die die Marktbedingungen im Auge behalten, die notwendigen Preisvergleiche durchführen und dann die erforderlichen Maßnahmen ergreifen, um einen Handel genau dann auszuführen, wenn ein Ausbruch erfolgt. Um den Händlern zu helfen, die für diese Strategie erforderlichen Schlüsselniveaus schnell zu erkennen, haben wir dem Chart einige visuelle Hilfsmittel - Rechtecke und Trendlinien - hinzugefügt. Wir haben bei der Programmierung der Tools darauf geachtet, dass sie einigermaßen flexibel sind, da die Strategie eine gewisse Anpassungsfähigkeit der Eingabeparameter erfordert.

Haftungsausschluss: Die in diesem Artikel dargestellten Informationen dienen nur zu Bildungszwecken. Er soll Einblicke in die Erstellung eines Expert Advisors (EA) auf Basis der Daily Range Breakout Strategie geben und als Grundlage für die Entwicklung fortgeschrittener Systeme mit weiteren Optimierungen und Tests dienen. Die besprochenen Strategien und Methoden garantieren keine Handelsergebnisse, und die Verwendung dieser Inhalte erfolgt auf eigene Gefahr. Sorgen Sie immer für gründliche Tests und berücksichtigen Sie mögliche Marktbedingungen, bevor Sie eine automatisierte Handelslösung einsetzen.

Die Strategie wurde mit dem Strategy Tester von MetaTrader 5 getestet, der es uns ermöglichte, ihre Leistung zu bewerten und Anpassungen vorzunehmen, damit sie unter unseren Handelsbedingungen noch besser funktioniert. Viel Spaß beim Kodieren und ein erfolgreicher Handel!

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

MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 43): Reinforcement Learning mit SARSA MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 43): Reinforcement Learning mit SARSA
SARSA, eine Abkürzung für State-Action-Reward-State-Action, ist ein weiterer Algorithmus, der bei der Implementierung von Reinforcement Learning verwendet werden kann. Wie bei Q-Learning und DQN haben wir also untersucht, wie dies als unabhängiges Modell und nicht nur als Trainingsmechanismus in assistentengestützten Expert Advisors implementiert werden kann.
Erstellen eines Handelsadministrator-Panels in MQL5 Teil IV: Login-Sicherheitsschicht Erstellen eines Handelsadministrator-Panels in MQL5 Teil IV: Login-Sicherheitsschicht
Stellen Sie sich vor, ein bösartiger Akteur dringt in den Raum des Handelsadministrator ein und verschafft sich Zugang zu den Computern und dem Admin-Panel, über das Millionen von Händlern weltweit wertvolle Informationen erhalten. Ein solches Eindringen könnte katastrophale Folgen haben, z. B. das unbefugte Versenden irreführender Nachrichten oder zufällige Klicks auf Schaltflächen, die unbeabsichtigte Aktionen auslösen. In dieser Diskussion werden wir die Sicherheitsmaßnahmen in MQL5 und die neuen Sicherheitsfunktionen, die wir in unserem Admin-Panel zum Schutz vor diesen Bedrohungen implementiert haben, untersuchen. Durch die Verbesserung unserer Sicherheitsprotokolle wollen wir unsere Kommunikationskanäle schützen und das Vertrauen unserer weltweiten Handelsgemeinschaft erhalten. Weitere Informationen finden Sie in diesem Artikel.
Integration von MQL5 mit Datenverarbeitungspaketen (Teil 3): Verbesserte Datenvisualisierung Integration von MQL5 mit Datenverarbeitungspaketen (Teil 3): Verbesserte Datenvisualisierung
In diesem Artikel werden wir eine erweiterte Datenvisualisierung durchführen, indem wir über einfache Charts hinausgehen und Funktionen wie Interaktivität, geschichtete Daten und dynamische Elemente einbeziehen, die es Händlern ermöglichen, Trends, Muster und Korrelationen effektiver zu untersuchen.
MQL5 Handels-Toolkit (Teil 3): Entwicklung einer EX5-Bibliothek zur Verwaltung schwebenden Aufträge MQL5 Handels-Toolkit (Teil 3): Entwicklung einer EX5-Bibliothek zur Verwaltung schwebenden Aufträge
Lernen Sie, wie Sie eine umfassende EX5-Bibliothek für schwebende Aufträge in Ihrem MQL5-Code oder Ihren Projekten entwickeln und implementieren. Dieser Artikel zeigt Ihnen, wie Sie eine umfangreiche EX5-Bibliothek für die Verwaltung schwebender Aufträge erstellen können, und führt Sie durch den Import und die Implementierung dieser Bibliothek, indem er ein Handels-Panel oder eine grafische Nutzeroberfläche (GUI) erstellt. Das Expert Advisor-Order-Panel ermöglicht es den Nutzern, schwebende Aufträge, die mit einer bestimmten magischen Zahl verknüpft sind, direkt über die grafische Oberfläche im Chartfenster zu öffnen, zu überwachen und zu löschen.