Automatisieren von Handelsstrategien in MQL5 (Teil 42): Sitzungsbasiertes System des Opening Range Breakout (ORB)
Einführung
In unserem vorangegangenen Artikel (Teil 41) haben wir ein Handelssystem auf Basis der Kerzenbereichstheorie (CRT) entwickelt, das die Phasen Akkumulation, Manipulation und Distribution AMD in MetaQuotes Language 5 (MQL5) beinhaltet, das Akkumulationsbereiche auf einem bestimmten Zeitrahmen identifiziert, Durchbrüche mit Filterung der Manipulationstiefe erkennt und Umkehrungen durch Schlusskurse der Balken für Eröffnungsgeschäfte in der Distributionsphase bestätigt. In Teil 42 entwickeln wir ein vollständig anpassbares sitzungsbasiertes System für den Opening Range Breakout (ORB).
Mit diesem System können wir die Startzeit und die Dauer jeder Sitzung in Minuten festlegen. Er erfasst automatisch den wahren Höchst- und Tiefststand während dieses Zeitraums in einem ausgewählten Zeitrahmen. Er erkennt Ausbrüche mit einer optionalen Bestätigung des Abschlusses mehrerer Bars, um Fehlsignale zu reduzieren. Das System führt die Handelsgeschäfte nur in Ausbruchsrichtung aus. Stop-Loss- und Take-Profit-Niveaus können dynamisch (basierend auf der Größe der Spanne) oder statisch sein. Trailing Stops können nach Erreichen einer Gewinnschwelle eingesetzt werden. Positionslimits werden pro Richtung durchgesetzt. Wir werden die folgenden Themen behandeln:
- Verständnis der Strategie des Opening Range Breakout (ORB)
- Implementierung in MQL5
- Backtesting
- Schlussfolgerung
Am Ende werden Sie ein MQL5-Programm haben, das in der Lage ist, saubere Eröffnungsspannenausbrüche in jeder Marktsitzung zu handeln – London, New York, Asien oder sogar nutzerdefinierte Eröffnungen – und bereit für weitere Anpassungen ist. Fangen wir an!
Verständnis der Strategie des Opening Range Breakout (ORB)
Der Opening Range Breakout (ORB) ist eine klassische Intraday-Momentum-Strategie, die von der anfänglichen Richtungsausrichtung zu Beginn einer Handelssitzung profitiert. Wir definieren „opening range“, die Eröffnungsspanne, als das Hoch und das Tief, die sich in den ersten Minuten (in der Regel 5-60 Minuten) nach der Markteröffnung bilden, und warten dann darauf, dass der Kurs entscheidend über das Hoch der Spanne (Aufwärtsausbruch) oder unter das Tief der Spanne (Abwärtsausbruch) ausbricht, und steigen in die Richtung des Ausbruchs ein. Die Prämisse ist einfach, aber wirkungsvoll: Die Eröffnungsspanne stellt oft den Kampf zwischen Käufern und Verkäufern dar, während der Markt die Nachrichten und den Auftragsfluss der Nacht verdaut. Ein sauberer Ausbruch signalisiert, dass eine Seite die Kontrolle gewonnen hat, was häufig zu einer anhaltenden Richtungsbewegung führt. Das System ist im Allgemeinen einfach. Sehen wir uns unten die verschiedenen Möglichkeiten an, die wir haben.

Unser Plan ist es, ein vollständig sitzungsflexibles ORB-System zu entwickeln, das mit jedem Instrument und jeder Handelssitzung (New York, London, Asien oder sogar nutzerdefinierte Eröffnungen) funktioniert. Wir werden den Nutzern die Möglichkeit geben, die genaue Startzeit festzulegen. Zum Beispiel 09:30 Uhr für die NYSE oder 08:00 Uhr für London. Die Nutzer können auch die Dauer des Bereichs in Minuten festlegen. Das System berechnet automatisch den wahren Höchst- und Tiefststand für den ausgewählten Zeitrahmen innerhalb dieses Fensters. Bei Bedarf können Nutzer mehrere Bestätigungen mit den Schlusskursen der Balken aktivieren, um einen Ausbruch zu validieren.
Der Algorithmus führt nur ein Handelsgeschäft pro Richtung und Sitzung aus. Wir werden zwei Berechnungsarten für Stop-Loss und Take-Profit anbieten: dynamisch (basierend auf der Größe der Handelsspanne) und statisch, mit anpassbaren Risiko-Ertrags-Verhältnissen. Es werden auch punktebasierte Trailing-Stops verfügbar sein, die bei Erreichen einer Mindestgewinnschwelle aktiviert werden. Darüber hinaus bietet das Tool eine umfangreiche Visualisierung auf dem Chart. Dazu gehören ausgefüllte Bereichsrechtecke, vertikale Markierungen für Sitzungsbeginn und -ende, anhaltende Höchst-/Tiefstwerte und Eröffnungspfeile.
Die Visualisierung ist in unserem Fall ebenso wichtig, wie Sie vielleicht schon bemerkt haben, um Klarheit zu schaffen. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner Experts, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| ORB Opening Range Breakout EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Enums | //+------------------------------------------------------------------+ enum SLTP_Method { // Define SL/TP method enum Dynamic_Method = 0, // Dynamic based on range size Static_Method = 1 // Static based on fixed points }; enum TrailingTypeEnum { // Define trailing type enum Trailing_None = 0, // None Trailing_Points = 1 // By Points }; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input ENUM_TIMEFRAMES RangeTF = PERIOD_M5; // Timeframe for Opening Range Calculation input int RangeDurationMinutes = 30; // Duration of Opening Range in Minutes input string SessionStartTime = "09:00"; // Session Start Time (HH:MM) input double TradeVolume = 0.01; // Trade Volume Size input double RR_Ratio = 2.0; // Risk to Reward Ratio input SLTP_Method SLTP_Approach = Dynamic_Method; // SL/TP Calculation Method input int SL_Points = 50; // SL Points (for Static Method) input TrailingTypeEnum TrailingType = Trailing_None; // Trailing Stop Type input double Trailing_Stop_Points = 20.0; // Trailing Stop in Points input double Min_Profit_To_Trail_Points = 30.0; // Min Profit to Start Trailing in Points input int UniqueID = 987654321; // Unique Trade Identifier input int MaxPositionsDir = 1; // Max Positions per Direction input bool UseBreakoutFilter = true; // Use Breakout Confirmation Filter input int ConfirmBars = 1; // Bars to Confirm Breakout on Close (0 to disable)
Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade\Trade.mqh>“ einbinden, die die Klasse CTrade und die für die Auftragsausführung und das Positionsmanagement erforderlichen Funktionen bereitstellt. Anschließend definieren wir zwei Enumerationen, um die Nutzeroptionen übersichtlich zu gestalten. Die Enumeration „SLTP_Method“ bietet „Dynamic_Method“ für Stop-Loss- und Take-Profit-Niveaus, die auf der tatsächlichen Größe der Eröffnungsspanne basieren, und „Static_Method“ für Berechnungen, die auf festen Punkten basieren. In ähnlicher Weise bietet die Enumeration „TrailingTypeEnum“ „Trailing_None“, um das Trailing zu deaktivieren, und „Trailing_Points“, um das Trailing um eine nutzerdefinierte Anzahl von Punkten zu aktivieren, sobald eine Mindestgewinnschwelle erreicht ist.
Als Nächstes deklarieren wir Eingabeparameter, die das Programm direkt über das Eigenschaftsfenster in hohem Maße konfigurierbar machen. Dazu gehören „RangeTF“ zur Auswahl des Zeitrahmens, der für die Berechnung des Höchst- und Tiefstwertes der Eröffnungsspanne verwendet wird, „RangeDurationMinutes“, um festzulegen, wie viele Minuten nach Sitzungsbeginn die Eröffnungsspanne bilden, „SessionStartTime“ als String im Format „HH:MM“, um festzulegen, wann jede neue Sitzung beginnt (z. B., „09:30“ für die Eröffnung der NYSE, „08:00“ für London usw.), „TradeVolume“ für die Losgröße und der Rest ist selbsterklärend. Wir haben Kommentare zur Verdeutlichung hinzugefügt. Diese Eingaben gewährleisten, dass das System ohne Code-Änderungen perfekt an jeden Markt oder jede Sitzung angepasst werden kann. Als Nächstes brauchen wir die Definition der globalen Variablen.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CTrade obj_Trade; //--- Trade object datetime sessionStart = 0; //--- Session start time datetime rangeEndTime = 0; //--- Range end time double rangeHigh = 0.0; //--- Range high double rangeLow = 0.0; //--- Range low bool rangeDefined = false; //--- Range defined flag bool breakoutHigh = false; //--- Breakout high flag bool breakoutLow = false; //--- Breakout low flag double breakoutPrice = 0.0; //--- Breakout price string highLevelObj = "ORB_HighLevel"; //--- High level object name string lowLevelObj = "ORB_LowLevel"; //--- Low level object name string highTextObj = "ORB_High_Text"; //--- High text object string lowTextObj = "ORB_Low_Text"; //--- Low text object bool tradedLong = false; //--- Traded long flag bool tradedShort = false; //--- Traded short flag datetime lastConfirmTime = 0; //--- Last confirm time
Wir fahren fort, indem wir eine Reihe von globalen Variablen deklarieren, die den Zustand des Programms während jeder Handelssitzung aufrechterhalten und die ordnungsgemäße Verfolgung der Logik des Ausbruchs aus dem Eröffnungsbereich sicherstellen. Wir instanziieren „obj_Trade“ als Instanz der Klasse CTrade, um alle Auftragsausführungen und Positionsänderungen zu verarbeiten. Zu den Zeitvariablen gehören „sessionStart“, um den genauen Zeitpunkt des Beginns einer neuen Sitzung festzuhalten, und „rangeEndTime“, um zu markieren, wann der Eröffnungszeitraum des Bereichs zu Ende ist. Wir verfolgen die Grenzen der Eröffnungsspanne mit „rangeHigh“ (initialisiert auf 0,0) und „rangeLow“, während „rangeDefined“ als boolesches Flag dient, das anzeigt, ob die Spanne der aktuellen Sitzung vollständig festgelegt wurde. Der Rest ist ganz einfach. Danach müssen wir das System initialisieren, indem wir einfach die magische Zahl setzen.
//+------------------------------------------------------------------+ //| EA Start Function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(UniqueID); //--- Set magic number return(INIT_SUCCEEDED); //--- Return success }
In der Ereignisbehandlung von OnInit, die automatisch ausgeführt wird, wenn das Programm zum ersten Mal geladen oder an einen Chart angehängt wird, weisen wir dem Objekt „obj_Trade“ die nutzerdefinierte „UniqueID“ als magische Zahl zu, indem wir „obj_Trade.SetExpertMagicNumber(UniqueID)“ aufrufen. Dadurch wird sichergestellt, dass jedes vom Programm eröffnete Handelsgeschäft diese eindeutige Kennung trägt, was eine präzise Filterung und Verwaltung ermöglicht, selbst wenn mehrere Programme oder manuelle Handelsgeschäfte auf demselben Konto aktiv sind. Abschließend geben wir INIT_SUCCEEDED zurück, um der Plattform zu bestätigen, dass die Initialisierung ohne Probleme abgeschlossen wurde und das Programm einsatzbereit ist. Wir werden einige Hilfsfunktionen definieren, die wir für die Visualisierung verwenden werden, wenn wir die Logik fertig haben, wie unten beschrieben.
//+------------------------------------------------------------------+ //| Render Horizontal Level | //+------------------------------------------------------------------+ void RenderLevel(string objName, double levelVal, color levelClr, string levelDesc) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_HLINE, 0, 0, levelVal); //--- Create hline ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, levelClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_STYLE, STYLE_DOT); //--- Set style ObjectSetString(ChartID(), objName, OBJPROP_TOOLTIP, levelDesc); //--- Set tooltip ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Render Vertical Line | //+------------------------------------------------------------------+ void RenderVLine(string objName, datetime timeVal, color lineClr, string desc) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_VLINE, 0, timeVal, 0); //--- Create vline ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, lineClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_STYLE, STYLE_DOT); //--- Set style ObjectSetInteger(ChartID(), objName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(ChartID(), objName, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(ChartID(), objName, OBJPROP_RAY, true); //--- Set ray ObjectSetInteger(ChartID(), objName, OBJPROP_HIDDEN, true); //--- Set hidden ObjectSetString(ChartID(), objName, OBJPROP_TOOLTIP, desc); //--- Set tooltip ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Render Text Label | //+------------------------------------------------------------------+ void RenderText(string objName, datetime timeVal, double priceVal, string textStr, color textClr, int anchorVal) { ObjectDelete(ChartID(), objName); //--- Delete object ObjectCreate(ChartID(), objName, OBJ_TEXT, 0, timeVal, priceVal); //--- Create text ObjectSetString(ChartID(), objName, OBJPROP_TEXT, textStr); //--- Set text ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, textClr); //--- Set color ObjectSetInteger(ChartID(), objName, OBJPROP_ANCHOR, anchorVal); //--- Set anchor ObjectSetInteger(ChartID(), objName, OBJPROP_FONTSIZE, 10); //--- Set fontsize ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Draw Entry Arrow | //+------------------------------------------------------------------+ void DrawEntryArrow(datetime timeVal, double priceVal, bool isBuy) { string markerName = "EntryMarker_" + IntegerToString(timeVal); //--- Marker name ObjectCreate(ChartID(), markerName, OBJ_ARROW, 0, timeVal, priceVal); //--- Create arrow int arrowCode = isBuy ? 233 : 234; //--- Arrow code color arrowClr = isBuy ? clrBlue : clrRed; //--- Arrow color int anchor = isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP; //--- Anchor ObjectSetInteger(ChartID(), markerName, OBJPROP_ARROWCODE, arrowCode); //--- Set code ObjectSetInteger(ChartID(), markerName, OBJPROP_COLOR, arrowClr); //--- Set color ObjectSetInteger(ChartID(), markerName, OBJPROP_ANCHOR, anchor); //--- Set anchor ChartRedraw(ChartID()); //--- Redraw chart } //+------------------------------------------------------------------+ //| Count Active Positions by Type | //+------------------------------------------------------------------+ int ActivePositions(ENUM_POSITION_TYPE posType) { int total = 0; //--- Init total for (int pos = PositionsTotal() - 1; pos >= 0; pos--) { //--- Iterate positions if (PositionGetSymbol(pos) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID && PositionGetInteger(POSITION_TYPE) == posType) { //--- Check position total++; //--- Increment total } } return total; //--- Return total }
Hier erstellen wir mehrere Hilfsfunktionen für die Chartvisualisierung und das Positionsmanagement und stellen sicher, dass die Eröffnungsspanne und die Handelssignale klar angezeigt werden, während der Code sauber organisiert bleibt. Die Funktion „RenderLevel“ zeichnet oder aktualisiert persistente horizontale Linien für den Bereich high und low. Es löscht alle vorhandenen Objekte mit dem angegebenen Namen, erstellt ein neues OBJ_HLINE auf dem angegebenen Preisniveau, legt seine Farbe fest (grün für oben, rot für unten), wendet einen gepunkteten Stil an, fügt einen beschreibenden Tooltip hinzu und zeichnet das Chart neu, damit es sofort sichtbar ist.
In ähnlicher Weise setzt die Funktion „RenderVLine“ vertikale Linien, um den Beginn und das Ende der Sitzung zu markieren. Sie entfernt frühere Instanzen, erstellt eine OBJ_VLINE zum angegebenen Zeitpunkt, konfiguriert sie mit blauer Farbe, gepunktetem Stil, Breite 1, Hintergrundplatzierung, Strahlenerweiterung nach rechts, aus der Objektliste ausgeblendet, einem Tooltip, der die genaue Zeit anzeigt, und löst ein Neuzeichnen des Charts mithilfe der Funktion ChartRedraw aus. Die Funktion „RenderText“ fügt anpassbare Textbeschriftungen hinzu, z. B. Start-/Endzeiten oder die Anmerkungen „ORB High“ bzw. „ORB Low“. Sie löscht vorhandene Textobjekte, erstellt einen OBJ_TEXT an den angegebenen Zeit- und Preiskoordinaten, setzt den Textinhalt und andere Eigenschaften.
Für Handelseröffnungen implementieren wir „DrawEntryArrow“, das eine visuelle Markierung direkt auf dem Chart im Moment der Ausführung platziert. Es wird ein eindeutiger Namen unter Verwendung der aktuellen Uhrzeit generiert, ein OBJ_ARROW erstellt, das Wingdings-Symbol 233 (Pfeil nach oben) für Käufe oder 234 (Pfeil nach unten) für Verkäufe ausgewählt, mit blau für Kauf oder rot für Verkauf, es wird korrekt am unteren oder oberen Rand verankert und das Chart neu gezeichnet. Für die Pfeilcodes verfügt MQL5 über spezielle Schriftarten (siehe unten), die Sie nach Belieben wechseln können.

Schließlich definieren wir die Funktion „ActivePositions“, um sicher zu zählen, wie viele offene Positionen für einen bestimmten Typ (Kauf oder Verkauf) existieren, die zu diesem Programm gehören. Sie durchläuft alle Positionen in einer Schleife rückwärts, prüft auf ein übereinstimmendes Symbol, eine magische Zahl über „UniqueID“ und den Positionstyp mit POSITION_TYPE_BUY oder „POSITION_TYPE_SELL“ und gibt dann die genaue Anzahl zurück. Damit können wir nun mit der Umsetzung der Strategie beginnen, indem wir zunächst die täglichen Bereiche definieren.
//+------------------------------------------------------------------+ //| Tick Processing Function | //+------------------------------------------------------------------+ void OnTick() { datetime currentTime = TimeCurrent(); //--- Get current time MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert to struct // Determine if a new session has started string currentTimeStr = StringFormat("%02d:%02d", timeStruct.hour, timeStruct.min); //--- Format time string if (currentTimeStr == SessionStartTime && sessionStart != currentTime - (timeStruct.hour * 3600 + timeStruct.min * 60 + timeStruct.sec)) { //--- Check new session sessionStart = currentTime - timeStruct.sec; //--- Align to minute start rangeEndTime = sessionStart + RangeDurationMinutes * 60; //--- Calc end time rangeHigh = 0.0; //--- Reset high rangeLow = DBL_MAX; //--- Reset low rangeDefined = false; //--- Reset defined breakoutHigh = false; //--- Reset high breakout breakoutLow = false; //--- Reset low breakout tradedLong = false; //--- Reset long traded tradedShort = false; //--- Reset short traded lastConfirmTime = 0; //--- Reset confirm time // Clean previous visuals for current levels ObjectDelete(ChartID(), highLevelObj); //--- Delete high level ObjectDelete(ChartID(), lowLevelObj); //--- Delete low level ObjectDelete(ChartID(), highTextObj); //--- Delete high text ObjectDelete(ChartID(), lowTextObj); //--- Delete low text } if (sessionStart == 0) return; //--- Return if no session double currBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask // Define the opening range if (currentTime < rangeEndTime) { //--- Check within range rangeHigh = MathMax(rangeHigh, iHigh(_Symbol, RangeTF, 0)); //--- Update high rangeLow = MathMin(rangeLow, iLow(_Symbol, RangeTF, 0)); //--- Update low } else if (!rangeDefined) { //--- Check not defined rangeDefined = true; //--- Set defined // Draw the opening range rectangle string rectObj = "ORB_Rectangle_" + IntegerToString(sessionStart); //--- Rect name ObjectCreate(ChartID(), rectObj, OBJ_RECTANGLE, 0, sessionStart, rangeHigh, rangeEndTime, rangeLow); //--- Create rect ObjectSetInteger(ChartID(), rectObj, OBJPROP_COLOR, clrLightBlue); //--- Set color ObjectSetInteger(ChartID(), rectObj, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(ChartID(), rectObj, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(ChartID(), rectObj, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ChartRedraw(ChartID()); //--- Redraw chart // Add vertical lines for start and end string startVLineObj = "ORB_StartVLine_" + IntegerToString(sessionStart); //--- Start vline name RenderVLine(startVLineObj, sessionStart, clrBlue, "ORB Start at " + TimeToString(sessionStart, TIME_MINUTES)); //--- Render start vline string endVLineObj = "ORB_EndVLine_" + IntegerToString(sessionStart); //--- End vline name RenderVLine(endVLineObj, rangeEndTime, clrBlue, "ORB End at " + TimeToString(rangeEndTime, TIME_MINUTES)); //--- Render end vline // Add time text labels for start and end double textOffset = (rangeHigh - rangeLow) * 0.05; //--- Calc offset string startTimeTextObj = "ORB_StartTime_Text_" + IntegerToString(sessionStart); //--- Start text name RenderText(startTimeTextObj, sessionStart, rangeLow - textOffset, TimeToString(sessionStart, TIME_MINUTES), clrBlue, ANCHOR_UPPER); //--- Render start text string endTimeTextObj = "ORB_EndTime_Text_" + IntegerToString(sessionStart); //--- End text name RenderText(endTimeTextObj, rangeEndTime, rangeLow - textOffset, TimeToString(rangeEndTime, TIME_MINUTES), clrBlue, ANCHOR_UPPER); //--- Render end text // Render high and low levels RenderLevel(highLevelObj, rangeHigh, clrGreen, "ORB High"); //--- Render high level RenderLevel(lowLevelObj, rangeLow, clrRed, "ORB Low"); //--- Render low level // Add text labels RenderText(highTextObj, rangeEndTime, rangeHigh, "ORB High", clrGreen, ANCHOR_RIGHT_LOWER); //--- Render high text RenderText(lowTextObj, rangeEndTime, rangeLow, "ORB Low", clrRed, ANCHOR_RIGHT_UPPER); //--- Render low text } }
In der Ereignisbehandlung von OnTick kopieren wir zunächst die aktuelle Zeit des Servers mit TimeCurrent in „currentTime“ und konvertieren sie über TimeToStruct in eine MqlDateTime-Struktur, um auf einzelne Komponenten wie Stunde und Minute zuzugreifen. Dann formatieren wir die aktuelle Zeit als „HH:MM“-String mit StringFormat und speichern sie in „currentTimeStr“. So sieht die Struktur aus.
struct MqlDateTime { int year; // Year int mon; // Month int day; // Day int hour; // Hour int min; // Minutes int sec; // Seconds int day_of_week; // Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) int day_of_year; // Day number of the year (January 1st is assigned the number value of zero) };
Die Aufteilung in eine Struktur hilft uns, die spezifischen Komponenten leicht zu finden. Um den genauen Beginn einer neuen Handelssitzung zu ermitteln, vergleichen wir diese Zeichenfolge mit der nutzerdefinierten „SessionStartTime“. Die zusätzliche Bedingung stellt sicher, dass wir nur einmal pro Tag auslösen, indem wir prüfen, ob „sessionStart“ nicht bereits mit dem aktuellen Tag übereinstimmt, der auf diese Minute ausgerichtet ist (wobei Sekunden abgezogen werden, um zu normalisieren). Wenn eine neue Sitzung beginnt, richten wir „sessionStart“ genau auf den Beginn dieser Minute aus, indem wir die verbleibenden Sekunden abziehen, berechnen „rangeEndTime“ durch Addition von „RangeDurationMinutes“ × 60 Sekunden, setzen „rangeHigh“ auf 0,0 und „rangeLow“ auf den maximalen Double-Wert zurück, um korrekte anfängliche Aktualisierungen zu gewährleisten, löschen alle Flags („rangeDefined“, „breakoutHigh“, „breakoutLow“, „tradedLong“, „tradedShort“, „lastConfirmTime“) und löschen die persistenten Hoch-/Tief-Werte sowie die Textobjekte aus der vorherigen Sitzung, um einen sauberen Neuanfang zu ermöglichen. Wenn noch keine aktive Sitzung erkannt wurde („sessionStart“ == 0), kehren wir einfach zurück, um unnötige Verarbeitung zu vermeiden. Andernfalls werden die aktuellen Geld- und Briefkurse mit der Funktion SymbolInfoDouble abgefragt.
Während der Periode der Bildung des Eröffnungsbereichs (solange „currentTime“ < „rangeEndTime“) werden die Bereichsgrenzen kontinuierlich aktualisiert, indem „rangeHigh“ über „iHigh“ auf das Maximum seines aktuellen Wertes oder das Hoch des letzten Balkens auf „RangeTF“ bei Shift 0 und „rangeLow“ über die Funktion iLow auf das Minimum seines aktuellen Wertes oder das Tief des letzten Balkens setzen. Sobald der Bereich endet und der Bereich noch nicht abgeschlossen ist („!rangeDefined“), setzen wir „rangeDefined“ auf true und fahren fort, den abgeschlossenen Eröffnungsbereich zu visualisieren.
Wir zeichnen ein gefülltes hellblaues Rechteck, das sich von „sessionStart“ bei „rangeHigh“ bis „rangeEndTime“ bei „rangeLow“ erstreckt und einen eindeutigen Namen verwendet, der auf dem Zeitstempel der Sitzung basiert, mit einem einfarbigen Stil und einer Hintergrundplatzierung. Vertikale blaue gepunktete Linien werden über „RenderVLine“ mit beschreibenden Tooltips zu den Start- und Endzeiten hinzugefügt. Die Zeitbeschriftungen werden mit „RenderText“ direkt unter dem Bereich platziert, wobei ein kleiner Abstand von 5 % der Bereichsgröße berechnet und in Blau nach oben verankert wird. Schließlich werden die dauerhaften horizontalen Niveaus mit „RenderLevel“ (grün für oben, rot für unten) und den entsprechenden Textbeschriftungen gerendert, die auf der rechten Seite der Endzeit des Bereichs verankert sind, um sicherzustellen, dass wir immer die genauen Ausbruchsniveaus sehen, auch Stunden oder Tage später. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Sobald wir aus einem der Bereiche ausbrechen, bestimmen wir die Richtung und damit die Art des Ausbruchs-Setups und eröffnen den Handel. Ein Kinderspiel. Hier ist die Logik, mit der wir das erreichen.
if (!rangeDefined) return; //--- Return if not defined // Detect breakout bool justBreached = false; //--- Init just breached if (currAsk > rangeHigh && !breakoutHigh) { //--- Check high breakout breakoutHigh = true; //--- Set high breakout justBreached = true; //--- Set just breached breakoutPrice = currAsk; //--- Set breakout price } else if (currBid < rangeLow && !breakoutLow) { //--- Check low breakout breakoutLow = true; //--- Set low breakout justBreached = true; //--- Set just breached breakoutPrice = currBid; //--- Set breakout price } if ((breakoutHigh || breakoutLow) && !(tradedLong || tradedShort)) { //--- Check breakout and not traded // Confirm breakout with bar closures if enabled bool confirmed = false; //--- Init confirmed if (ConfirmBars == 0) { //--- Check no confirm confirmed = true; //--- Set confirmed } else { //--- Else datetime currConfirmTime = iTime(_Symbol, RangeTF, 0); //--- Get confirm time if (currConfirmTime != lastConfirmTime) { //--- Check new confirm lastConfirmTime = currConfirmTime; //--- Update last confirm int confirmCount = 0; //--- Init count for (int i = 1; i <= ConfirmBars; i++) { //--- Iterate bars double closePrice = iClose(_Symbol, RangeTF, i); //--- Get close if (breakoutHigh && closePrice > rangeHigh) confirmCount++; //--- Check high if (breakoutLow && closePrice < rangeLow) confirmCount++; //--- Check low } if (confirmCount >= ConfirmBars) confirmed = true; //--- Set confirmed } } if (confirmed && UseBreakoutFilter) { //--- Check confirmed and filter // Additional filter logic if needed, but for now assume confirmed } if (confirmed) { //--- Check confirmed double sl = 0.0, tp = 0.0; //--- Init SL TP if (breakoutHigh && ActivePositions(POSITION_TYPE_BUY) < MaxPositionsDir && !tradedLong) { //--- Check long entry if (SLTP_Approach == Dynamic_Method) { //--- Check dynamic double rangeSize = rangeHigh - rangeLow; //--- Calc range size sl = NormalizeDouble(rangeLow, _Digits); //--- Set SL tp = NormalizeDouble(currAsk + rangeSize * RR_Ratio, _Digits); //--- Set TP } else { //--- Static sl = NormalizeDouble(currAsk - SL_Points * _Point, _Digits); //--- Set SL tp = NormalizeDouble(currAsk + (SL_Points * _Point) * RR_Ratio, _Digits); //--- Set TP } if (obj_Trade.Buy(TradeVolume, _Symbol, currAsk, sl, tp, "ORB Long Breakout")) { //--- Open buy if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success Print("Long Breakout: Entry at ", DoubleToString(currAsk, _Digits), " SL at ", DoubleToString(sl, _Digits), " TP at ", DoubleToString(tp, _Digits)); //--- Log entry DrawEntryArrow(currentTime, currBid, true); //--- Draw arrow tradedLong = true; //--- Set long traded } } } else if (breakoutLow && ActivePositions(POSITION_TYPE_SELL) < MaxPositionsDir && !tradedShort) { //--- Check short entry if (SLTP_Approach == Dynamic_Method) { //--- Check dynamic double rangeSize = rangeHigh - rangeLow; //--- Calc range size sl = NormalizeDouble(rangeHigh, _Digits); //--- Set SL tp = NormalizeDouble(currBid - rangeSize * RR_Ratio, _Digits); //--- Set TP } else { //--- Static sl = NormalizeDouble(currBid + SL_Points * _Point, _Digits); //--- Set SL tp = NormalizeDouble(currBid - (SL_Points * _Point) * RR_Ratio, _Digits); //--- Set TP } if (obj_Trade.Sell(TradeVolume, _Symbol, currBid, sl, tp, "ORB Short Breakout")) { //--- Open sell if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success Print("Short Breakout: Entry at ", DoubleToString(currBid, _Digits), " SL at ", DoubleToString(sl, _Digits), " TP at ", DoubleToString(tp, _Digits)); //--- Log entry DrawEntryArrow(currentTime, currAsk, false); //--- Draw arrow tradedShort = true; //--- Set short traded } } } } }
Sobald der Eröffnungsbereich vollständig definiert ist, kehren wir sofort zurück, wenn „rangeDefined“ immer noch falsch ist, um sicherzustellen, dass keine Ausbruchslogik vorzeitig ausgeführt wird. Anschließend überwachen wir einen Ausbruch: Wenn der aktuelle Briefkurs „rangeHigh“ übersteigt und „breakoutHigh“ noch nicht gesetzt ist, markieren wir einen Ausbruch nach oben, indem wir „breakoutHigh“ auf „true“ setzen, den genauen Ausbruchskurs in „breakoutPrice“ aufzeichnen und vermerken, dass ein neuer Durchbruch stattgefunden hat. Dasselbe tun wir bei einem niedrigen Ausbruch. Wenn mindestens eine Ausbruchsrichtung aktiv ist und in dieser Sitzung weder ein Kauf noch ein Verkauf getätigt wurde („!tradedLong && !tradedShort“), gehen wir zur Bestätigungsphase über. Ist „ConfirmBars“ gleich Null, gilt der Ausbruch sofort als bestätigt. Andernfalls wird bei jedem neuen Balken des Range-Zeitrahmens (der durch den Vergleich der iTime bei Shift 0 mit der „lastConfirmTime“ ermittelt wird) gezählt, wie viele der vorangegangenen „ConfirmBars“-Balken entschieden außerhalb des Bereichs geschlossen haben – über „rangeHigh“ für aufwärts oder unter „rangeLow“ für abwärts. Erst wenn die erforderliche Anzahl von bestätigenden Schlusskursen erreicht ist, setzen wir „bestätigt“ auf true.
Wir beachten die Eingabe „UseBreakoutFilter“ (obwohl sie derzeit nur die Bestätigung weiterleitet – Raum für zukünftige Erweiterungen, wenn Sie weitere Filter hinzufügen möchten). Sobald dies bestätigt ist, berechnen wir die Stop-Loss- und Take-Profit-Niveaus entsprechend der gewählten Methode. Wenn bei einem Ausbruch nach oben die Kaufpositionen unter „MaxPositionsDir“ liegen und noch keine Kaufpositionen gehandelt wurden, verwenden wir den dynamischen Modus, um den Stop-Loss genau am Tiefpunkt des Bereichs (normalisiert) zu platzieren und den Take-Profit bei der aktuellen Nachfrage plus dem vollen Umfang des Bereichs multipliziert mit „RR_Ratio“. Im statischen Modus platzieren wir einen Stop-Loss bei einem festen „SL_Points“ unter dem Briefkurs und nehmen den Gewinn im gleichen Abstand mal dem obigen Multiplikator. Dann führen wir die Kauforder über „obj_Trade.Buy“ aus und protokollieren bei Erfolg (TRADE_RETCODE_DONE) die Details der Eingabe in der Registerkarte Experten, zeichnen mit „DrawEntryArrow“ einen blauen Aufwärtspfeil an der Eingabe und setzen „tradedLong“ auf true, um weitere Kaufpositionen in dieser Sitzung auszuschließen. Die Abwärtsseite spiegelt dies genau. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus dem Bild können wir ersehen, dass wir Handelsgeschäfte eröffnen, sobald wir einen Ausbruch erkennen. Jetzt müssen wir nur noch die offenen Positionen verwalten, indem wir einen Trailing-Stop anwenden, sobald sich der Markt zu unseren Gunsten entwickelt, wenn wir das möchten.
//+------------------------------------------------------------------+ //| Apply Points Trailing Stop | //+------------------------------------------------------------------+ void ApplyPointsTrailing() { double point = _Point; //--- Get point for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate positions if (PositionGetTicket(i) > 0) { //--- Check ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID) { //--- Check symbol magic double sl = PositionGetDouble(POSITION_SL); //--- Get SL double tp = PositionGetDouble(POSITION_TP); //--- Get TP double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get ticket if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Points * point, _Digits); //--- Calc new SL if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Points * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Points * point, _Digits); //--- Calc new SL if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Points * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } } } } } //--- Call the function per tick in the "OnTick" event handler if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing ApplyPointsTrailing(); //--- Apply trailing }
Hier implementieren wir die Funktion „ApplyPointsTrailing“, um den Stop-Loss dynamisch nachzuziehen, wenn der Modus „Trailing_Points“ ausgewählt ist, und so Gewinne zu sichern, wenn sich der Markt zu unseren Gunsten bewegt. Die Funktion beginnt mit der Speicherung des Punktwerts des Symbols in „point“ mit _Point. Dann wird rückwärts durch alle offenen Positionen iteriert, um alle Änderungen ohne Indexkonflikte sicher zu verarbeiten. Für jedes gültige Ticket überprüfen wir, ob die Position zum aktuellen Symbol gehört und unsere magische Nummer „UniqueID“ trägt. Wir rufen den aktuellen Stop-Loss, Take-Profit, den offenen Preis und die Ticketnummer ab.
Bei Kaufpositionen berechnen wir einen potenziellen neuen Stop-Loss, indem wir „Trailing_Stop_Points“ × point vom aktuellen Geldkurs (normalisiert auf die Ziffern des Symbols) abziehen. Wir wenden die Änderung nur an, wenn dieses neue Niveau höher ist als der bestehende Stop-Loss (Verschärfung) und der unrealisierte Gewinn den Punkt „Min_Profit_To_Trail_Points“ × point überschreitet, um sicherzustellen, dass wir nur nach einem sinnvollen Puffer nachziehen. Die Position wird über „obj_Trade.PositionModify“ aktualisiert, wobei der ursprüngliche Take-Profit erhalten bleibt. Die Logik für Verkaufspositionen spiegelt dies genau. Schließlich wird am Ende von OnTick geprüft, ob das Trailing aktiviert ist („TrailingType == Trailing_Points“) und ob es offene Positionen gibt. Wenn dies der Fall ist, rufen wir bei jedem Tick sofort „ApplyPointsTrailing“ auf und bieten so ohne Verzögerung eine Gewinnabsicherung in Echtzeit. Schließlich müssen wir unsere Visualisierungsobjekte löschen, wenn wir das Programm aus dem Chart entfernen.
//+------------------------------------------------------------------+ //| EA Stop Function | //+------------------------------------------------------------------+ void OnDeinit(const int code) { ObjectDelete(ChartID(), highLevelObj); //--- Delete high level ObjectDelete(ChartID(), lowLevelObj); //--- Delete low level ObjectDelete(ChartID(), highTextObj); //--- Delete high text ObjectDelete(ChartID(), lowTextObj); //--- Delete low text // Clean dynamic objects ObjectsDeleteAll(ChartID(), "ORB_Rectangle_", OBJ_RECTANGLE); //--- Delete rectangles ObjectsDeleteAll(ChartID(), "ORB_StartVLine_", OBJ_VLINE); //--- Delete start vlines ObjectsDeleteAll(ChartID(), "ORB_EndVLine_", OBJ_VLINE); //--- Delete end vlines ObjectsDeleteAll(ChartID(), "ORB_StartTime_Text_", OBJ_TEXT); //--- Delete start texts ObjectsDeleteAll(ChartID(), "ORB_EndTime_Text_", OBJ_TEXT); //--- Delete end texts ObjectsDeleteAll(ChartID(), "EntryMarker_", OBJ_ARROW); //--- Delete entry markers }
In der Funktion OnDeinit, die ausgeführt wird, wenn das Programm aus dem Chart entfernt oder das Terminal heruntergefahren wird, löschen wir zunächst die vier persistenten Objekte mit ihrem Namen: die hohen und niedrigen horizontalen Ebenen über „highLevelObj“ und „lowLevelObj“ und ihre Textbeschriftungen mit „highTextObj“ und „lowTextObj“. Anschließend verwenden wir ObjectsDeleteAll, um jedes dynamisch erstellte Objekt aus der aktuellen und allen vorherigen Sitzungen zu entfernen. Diese vollständige Bereinigung verhindert die Ansammlung von Objekten in mehreren Sitzungen oder beim Neuladen von Charts. Nach dem Kompilieren erhalten wir das folgende Ergebnis, wenn der Trailing-Stop aktiviert ist.

Anhand der Visualisierung können wir sehen, dass wir die Bereiche definieren, Positionen eröffnen und sie verwalten, indem wir bei Bedarf Trailing Stops anwenden und so unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtest
Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.
Backtest-Grafik:

Bericht des Backtests:

Schlussfolgerung
Zusammenfassend haben wir das sitzungsbasierte System Opening Range Breakout (ORB) in MQL5 entwickelt, das nutzerdefinierte Sitzungsstartzeiten und Eröffnungsbereichsdauern in Minuten erlaubt, automatisch das korrekte Hoch und Tief auf einem ausgewählten Zeitrahmen bestimmt, Ausbrüche mit optionaler Bestätigung durch die Schlusskurse mehrerer Balken erkennt und Handelsgeschäfte nur in der Ausbruchsrichtung ausführt.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.
Mit dieser sitzungsbasierten Strategie des Opening Range Breakout sind Sie für den Handel mit Intraday-Breakout-Setups in jeder beliebigen Marktsitzung gerüstet und können Ihre Handelsreise weiter optimieren. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20339
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die „Griechen“ in Black-Scholes automatisieren: Fortgeschrittenes Scalping und Mikrostrukturhandel
Marktpositionierungskodex für den VGT mit Kendall’schen Tau und Distanzkorrelation
MetaTrader 5 Machine Learning Blueprint (Teil 6): Entwicklung eines produktionsgerechten Caching-Systems
Die Grenzen des maschinellen Lernens überwinden (Teil 8): Nichtparametrische Strategieauswahl
- 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.
Hallo. Danke für die freundliche Rückmeldung. Sie können die Zeit in Ihrem Code definieren, indem Sie die lokale Zeit anstelle der Zeit des Brokers verwenden, oder Sie können Ihre Zeit definieren. Siehe Beispiel.
Lokale Zeit:
TimeLocal()Sie können auch die direkte Zeit gemäß den Einstellungen Ihres Computers über den Handelsserver verwenden:
TimeTradeServer()GMT-Zeit:
TimeGMT()Sie können auch Ihr eigenes Datum und Ihre eigene Uhrzeit wie unten angegeben definieren:
Es liegt ganz bei Ihnen, den besten Ansatz zu wählen. Vielen Dank!
Ihr Problem ist nicht klar genug. Sie können aber die umfangreiche Bibliothek Local Timezones and Local Session Hours oder das einfachere TimeServerDaylightSavings ausprobieren. Ohne Zeitanpassungen können Sie Ihre Strategie nicht zuverlässig an einer Historie testen, die in der Regel von Sommerzeit- und Zeitzonenumstellungen betroffen ist. Oder Sie möchten vielleicht den Daylight (DST) Schedule des Brokers ermitteln, um Zeitzonenänderungen online zu erkennen.
Leider bietet die integrierte MQL5-API keine fertige und aussagekräftige Lösung.