
Automatisieren von Handelsstrategien in MQL5 (Teil 12): Umsetzung der Strategie der Mitigation Order Blocks (MOB)
Einführung
In unserem letzten Artikel (Teil 11) haben wir ein Multi-Level-Raster-Handelssystem in MetaQuotes Language 5 (MQL5) entwickelt, um von Marktschwankungen zu profitieren. In Teil 12 konzentrieren wir uns nun auf die Umsetzung der Strategie Mitigation Order Blocks (MOB), ein Smart Money Konzept, das wichtige Preiszonen identifiziert, in denen institutionelle Aufträge vor großen Marktbewegungen abgeschwächt (mitigated) werden. Wir werden die folgenden Themen behandeln:
Am Ende dieses Artikels werden Sie ein vollautomatisches Mitigation Orderblock-Handelssystem haben, das für den Handel bereit ist. Fangen wir an!
Blaupause der Strategie
Zur Umsetzung der Strategie Mitigation Order Block werden wir ein automatisiertes System entwickeln, das Handelsgeschäfte auf der Basis von Order Block Mitigation Events erkennt, validiert und ausführt. Die Strategie wird sich darauf konzentrieren, institutionelle Preiszonen zu identifizieren, in denen die Liquidität vor der Trendfortsetzung absorbiert wird. Unser System enthält präzise Bedingungen für den Einstieg, die Platzierung von Stop-Loss und das Handelsmanagement, um Effizienz und Genauigkeit zu gewährleisten. Wir werden die Entwicklung wie folgt strukturieren:
- Identifizierung von Orderblöcken - Das System scannt die historische Preisentwicklung, um Auf- und Abwärts-Orderblöcke zu erkennen, und filtert schwache Zonen auf der Grundlage von Volatilität, Liquiditätsengpässen und Preisungleichgewichten heraus.
- Mitigation Validation - Wir programmieren Bedingungen, die ein gültiges Mitigation-Ereignis bestätigen und sicherstellen, dass der Preis den Orderblock erneut besucht und mit Ablehnungssignalen wie Dochten oder Momentum Shifts reagiert.
- Bestätigung der Marktstruktur - Der EA analysiert Trends in einem größeren Zeitrahmen und Liquiditätssweeps, um sicherzustellen, dass die identifizierten Abhilfemaßnahmen mit dem breiteren Marktfluss übereinstimmen.
- Regeln für die Handelsausführung - Sobald die Mitigation bestätigt ist, definiert das System präzise Einstiegspunkte, berechnet dynamisch Stop-Loss-Levels auf der Grundlage der Orderblockstruktur und setzt Take-Profit-Ziele auf der Basis von Risiko-Rendite-Parametern.
- Risiko- und Geldmanagement - Die Strategie wird Positionsgrößen, Schutz vor Drawdowns und Ausstiegsstrategien integrieren, um das Handelsrisiko effektiv zu verwalten.
Kurz gesagt, hier eine allgemeine Darstellung der Strategie.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen Sie zum Navigator, suchen Sie den Ordner Indikatoren, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Mitigation Order Blocks Strategy" #property strict //--- Include the trade library for managing positions #include <Trade/Trade.mqh> CTrade obj_Trade;
Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade/Trade.mqh>“ einbinden, die integrierte Funktionen für die Verwaltung von Handelsoperationen bietet. Anschließend initialisieren wir das Handelsobjekt „obj_Trade“ mit der Klasse „CTrade“, sodass der Expert Advisor Kauf- und Verkaufsaufträge programmatisch ausführen kann. So wird sichergestellt, dass die Ausführung von Handelsgeschäften effizient und ohne manuelle Eingriffe erfolgt. Dann können wir einige Eingaben machen, sodass der Nutzer das Verhalten über die Nutzer-Schnittstelle (UI) ändern und steuern kann.
//+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input double tradeLotSize = 0.01; // Trade size for each position input bool enableTrading = true; // Toggle to allow or disable trading input bool enableTrailingStop = true; // Toggle to enable or disable trailing stop input double trailingStopPoints = 30; // Distance in points for trailing stop input double minProfitToTrail = 50; // Minimum profit in points before trailing starts (not used yet) input int uniqueMagicNumber = 12345; // Unique identifier for EA trades input int consolidationBars = 7; // Number of bars to check for consolidation input double maxConsolidationSpread = 50; // Maximum allowed spread in points for consolidation input int barsToWaitAfterBreakout = 3; // Bars to wait after breakout before checking impulse input double impulseMultiplier = 1.0; // Multiplier for detecting impulsive moves input double stopLossDistance = 1500; // Stop loss distance in points input double takeProfitDistance = 1500; // Take profit distance in points input color bullishOrderBlockColor = clrGreen; // Color for bullish order blocks input color bearishOrderBlockColor = clrRed; // Color for bearish order blocks input color mitigatedOrderBlockColor = clrGray; // Color for mitigated order blocks input color labelTextColor = clrBlack; // Color for text labels
Hier definieren wir die Eingabeparameter, um das Verhalten des Programms zu konfigurieren: „tradeLotSize“ legt die Positionsgröße fest, während „enableTrading“ und „enableTrailingStop“ die Ausführung und den Trailing-Stop steuern, wobei „trailingStopPoints“ und „minProfitToTrail“ die Stop-Logik verfeinern. „uniqueMagicNumber“ identifiziert Handelsgeschäfte, und Konsolidierung wird mit „consolidationBars“ und „maxConsolidationSpread“ erkannt. Ausbrüche werden mit „barsToWaitAfterBreakout“ und „impulseMultiplier“ bestätigt. Mit „stopLossDistance“ und „takeProfitDistance“ wird das Risiko verwaltet, während „bullishOrderBlockColor“, „bearishOrderBlockColor“, „mitigatedOrderBlockColor“ und „labelTextColor“ die Chartdarstellung regeln.
Zum Schluss müssen wir noch einige globale Variablen definieren, die wir für die gesamte Systemsteuerung verwenden werden.
//--- Struct to store price and index for highs and lows struct PriceAndIndex { double price; // Price value int index; // Bar index where this price occurs }; //--- Global variables for tracking market state PriceAndIndex rangeHighestHigh = {0, 0}; // Highest high in the consolidation range PriceAndIndex rangeLowestLow = {0, 0}; // Lowest low in the consolidation range bool isBreakoutDetected = false; // Flag for when a breakout occurs double lastImpulseLow = 0.0; // Low price after breakout for impulse check double lastImpulseHigh = 0.0; // High price after breakout for impulse check int breakoutBarNumber = -1; // Bar index where breakout happened datetime breakoutTimestamp = 0; // Time of the breakout string orderBlockNames[]; // Array of order block object names datetime orderBlockEndTimes[]; // Array of order block end times bool orderBlockMitigatedStatus[]; // Array tracking if order blocks are mitigated bool isBullishImpulse = false; // Flag for bullish impulsive move bool isBearishImpulse = false; // Flag for bearish impulsive move #define OB_Prefix "OB REC " // Prefix for order block object names
Zunächst definieren wir die Struktur „PriceAndIndex“, die einen „Preis“-Wert und den „Index“ des Balkens speichert, in dem dieser Preis auftritt. Diese Struktur hilft, bestimmte Preispunkte innerhalb einer definierten Spanne zu verfolgen. Die globalen Variablen verwalten wichtige Aspekte der Marktstruktur und der Ausbruchserkennung: „rangeHighestHigh“ und „rangeLowestLow“ speichern den höchsten bzw. den niedrigsten Preis im Konsolidierungsbereich und helfen so, die Grenzen potenzieller Orderblöcke zu definieren. „isBreakoutDetected“ dient als Flag, die anzeigt, wann ein Ausbruch stattgefunden hat, während „lastImpulseLow“ und „lastImpulseHigh“ den ersten Tiefst- und Höchststand nach einem Ausbruch speichern und zur Bestätigung impulsiver Bewegungen verwendet werden.
Mit „breakoutBarNumber“ wird der Bar-Index aufgezeichnet, bei dem der Ausbruch stattfand, und mit „breakoutTimestamp“ wird die genaue Zeit des Ausbruchs gespeichert. Die Arrays „orderBlockNames“, „orderBlockEndTimes“ und „orderBlockMitigatedStatus“ dienen der Identifizierung, Lebensdauer und Nachverfolgung von Auftragsblöcken. Die booleschen Flags „isBullishImpulse“ und „isBearishImpulse“ bestimmen, ob die Ausbruchsbewegung als Aufwärts- oder oder Abwärts-Impuls einzustufen ist. Schließlich ist „OB_Prefix“ ein vordefinierter String-Präfix, der durch das Makro #define definiert ist und bei der Benennung von Auftragsblockobjekten verwendet wird, um die Konsistenz der grafischen Darstellung zu gewährleisten. Mit den Variablen sind wir bereit, die Programmlogik zu beginnen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the magic number for the trade object to identify EA trades obj_Trade.SetExpertMagicNumber(uniqueMagicNumber); return(INIT_SUCCEEDED); }
Hier initialisieren wir den Expert Advisor in OnInit. Wir setzen die magische Zahl des Experten mit der Methode „SetExpertMagicNumber“ und stellen damit sicher, dass alle von unserem EA ausgeführten Handelsgeschäfte eindeutig gekennzeichnet sind, um Konflikte mit anderen Trades zu vermeiden. Dieser Schritt ist wichtig, um nur die von unserer Strategie eröffneten Handelsgeschäfte zu verfolgen und zu verwalten. Sobald die Initialisierung abgeschlossen ist, geben wir INIT_SUCCEEDED zurück und bestätigen damit, dass unser Programm einsatzbereit ist. Wir können dann zur Ereignisbehandlung durch OnTick zu unserer Hauptsteuerungslogik übergehen.
//+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check for a new bar to process logic only once per bar static bool isNewBar = false; int currentBarCount = iBars(_Symbol, _Period); static int previousBarCount = currentBarCount; if (previousBarCount == currentBarCount) { isNewBar = false; } else if (previousBarCount != currentBarCount) { isNewBar = true; previousBarCount = currentBarCount; } //--- Exit if not a new bar to avoid redundant processing if (!isNewBar) return; //--- }
Um sicherzustellen, dass die Daten bei jedem Balken und nicht bei jedem Tick verarbeitet werden, verwenden wir in der Funktion OnTick, die bei jedem neu empfangenen Tick ausgeführt wird, die Funktion iBars, um die Gesamtzahl der Balken im Chart zu ermitteln und sie in „currentBarCount“ zu speichern. Wir vergleichen ihn dann mit „previousBarCount“, und wenn sie gleich sind, bleibt „isNewBar“ falsch, um eine redundante Verarbeitung zu vermeiden. Wenn ein neuer Balken erkannt wird, aktualisieren wir „previousBarCount“ und setzen „isNewBar“ auf true, sodass die Strategielogik ausgeführt werden kann. Wenn „isNewBar“ falsch ist, kehren wir vorzeitig zurück, um die Leistung zu optimieren, indem wir unnötige Berechnungen auslassen. Wenn es sich um einen neuen Balken handelt, suchen wir weiter nach Konsolidierungen.
//--- Define the starting bar index for consolidation checks int startBarIndex = 1; //--- Check for consolidation or extend the existing range if (!isBreakoutDetected) { if (rangeHighestHigh.price == 0 && rangeLowestLow.price == 0) { //--- Check if bars are in a tight consolidation range bool isConsolidated = true; for (int i = startBarIndex; i < startBarIndex + consolidationBars - 1; i++) { if (MathAbs(high(i) - high(i + 1)) > maxConsolidationSpread * Point()) { isConsolidated = false; break; } if (MathAbs(low(i) - low(i + 1)) > maxConsolidationSpread * Point()) { isConsolidated = false; break; } } if (isConsolidated) { //--- Find the highest high in the consolidation range rangeHighestHigh.price = high(startBarIndex); rangeHighestHigh.index = startBarIndex; for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { if (high(i) > rangeHighestHigh.price) { rangeHighestHigh.price = high(i); rangeHighestHigh.index = i; } } //--- Find the lowest low in the consolidation range rangeLowestLow.price = low(startBarIndex); rangeLowestLow.index = startBarIndex; for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { if (low(i) < rangeLowestLow.price) { rangeLowestLow.price = low(i); rangeLowestLow.index = i; } } //--- Log the established consolidation range Print("Consolidation range established: Highest High = ", rangeHighestHigh.price, " at index ", rangeHighestHigh.index, " and Lowest Low = ", rangeLowestLow.price, " at index ", rangeLowestLow.index); } } else { //--- Check if the current bar extends the existing range double currentHigh = high(1); double currentLow = low(1); if (currentHigh <= rangeHighestHigh.price && currentLow >= rangeLowestLow.price) { Print("Range extended: High = ", currentHigh, ", Low = ", currentLow); } else { Print("No extension: Bar outside range."); } } }
Hier wird der Konsolidierungsbereich durch die Analyse der jüngsten Kursbewegungen definiert und festgelegt. Wir beginnen, indem wir „startBarIndex“ auf 1 setzen und damit den Startpunkt für unsere Konsolidierungsprüfungen festlegen. Wenn wir noch keinen Ausbruch festgestellt haben, was durch „isBreakoutDetected“ angezeigt wird, bewerten wir, ob sich der Markt in einer engen Konsolidierungsphase befindet. Wir iterieren durch die letzte „consolidationBars“-Anzahl von Balken und verwenden die Funktion MathAbs, um die absoluten Differenzen zwischen aufeinanderfolgenden Hochs und Tiefs zu messen. Bleiben alle Differenzen innerhalb von „maxConsolidationSpread“, bestätigen wir die Konsolidierung.
Sobald eine Konsolidierung festgestellt wird, bestimmen wir das höchste Hoch und das tiefste Tief innerhalb der Spanne. Wir initialisieren „rangeHighestHigh“ und „rangeLowestLow“ mit dem Hoch und dem Tief von „startBarIndex“ und iterieren dann durch den Bereich, um diese Werte immer dann zu aktualisieren, wenn wir einen neuen Höchst- oder Tiefstwert finden. Diese Werte definieren unsere Konsolidierungsgrenzen.
Wenn der Konsolidierungsbereich bereits festgelegt ist, prüfen wir, ob der aktuelle Balken den bestehenden Bereich erweitert. Wir rufen „currentHigh“ und „currentLow“ mit den Funktionen „high“ und „low“ ab und vergleichen sie mit „rangeHighestHigh.price“ und „rangeLowestLow.price“. Bleibt der Kurs innerhalb der Spanne, drucken wir mit der Funktion Print eine Meldung über die Erweiterung der Spanne aus. Andernfalls drucken wir, dass keine Ausdehnung stattgefunden hat, was auf ein mögliches Ausbruchsszenario hindeutet. Die nutzerdefinierten Preisfunktionen sind wie folgt.
//+------------------------------------------------------------------+ //| Price data accessors | //+------------------------------------------------------------------+ double high(int index) { return iHigh(_Symbol, _Period, index); } //--- Get high price of a bar double low(int index) { return iLow(_Symbol, _Period, index); } //--- Get low price of a bar double open(int index) { return iOpen(_Symbol, _Period, index); } //--- Get open price of a bar double close(int index) { return iClose(_Symbol, _Period, index); } //--- Get close price of a bar datetime time(int index) { return iTime(_Symbol, _Period, index); } //--- Get time of a bar
Diese nutzerdefinierten Funktionen helfen uns beim Abrufen von Preisdaten. Die Funktion „high“ verwendet iHigh, um das Hoch eines Balkens bei einem bestimmten „Index“ zurückzugeben, während die Funktion „low“ iLow aufruft, um das entsprechende Tief zu erhalten. Die Funktion „open“ holt den Eröffnungskurs über iOpen und die Funktion „close“ den Schlusskurs über iClose. Außerdem verwendet die Funktion „time“ iTime, um den Zeitstempel des angegebenen Balkens zurückzugeben. Wenn wir das Programm ausführen, erhalten wir folgendes Ergebnis.
Aus dem Bild ist ersichtlich, dass wir, sobald die Preisspanne festgelegt ist und der Preis innerhalb der Spanne rotiert, diese erweitern, bis wir eine Spanne durchbrechen. Jetzt müssen wir also einen Ausbruch aus dem bestätigten Kursrückstand erkennen. Dies geschieht nach der folgenden Logik.
//--- Detect a breakout from the consolidation range if (rangeHighestHigh.price > 0 && rangeLowestLow.price > 0) { double currentClosePrice = close(1); if (currentClosePrice > rangeHighestHigh.price) { Print("Upward breakout at ", currentClosePrice, " > ", rangeHighestHigh.price); isBreakoutDetected = true; } else if (currentClosePrice < rangeLowestLow.price) { Print("Downward breakout at ", currentClosePrice, " < ", rangeLowestLow.price); isBreakoutDetected = true; } } //--- Reset state after a breakout is detected if (isBreakoutDetected) { Print("Breakout detected. Resetting for the next range."); breakoutBarNumber = 1; breakoutTimestamp = TimeCurrent(); lastImpulseHigh = rangeHighestHigh.price; lastImpulseLow = rangeLowestLow.price; isBreakoutDetected = false; rangeHighestHigh.price = 0; rangeHighestHigh.index = 0; rangeLowestLow.price = 0; rangeLowestLow.index = 0; }
Um Ausbrüche aus einer zuvor identifizierten Konsolidierungsspanne zu erkennen und zu behandeln, überprüfen wir zunächst, ob die Werte „rangeHighestHigh.price“ und „rangeLowestLow.price“ gültig sind, um sicherzustellen, dass eine Konsolidierungsspanne festgelegt wurde. Anschließend vergleichen wir den „currentClosePrice“, den wir mit der Funktion „close“ erhalten, mit den Bereichsgrenzen. Wenn der Schlusskurs den „rangeHighestHigh.price“ überschreitet, erkennen wir einen Ausbruch nach oben, protokollieren das Ereignis und setzen „isBreakoutDetected“ auf true. Ähnlich verhält es sich, wenn der Schlusskurs unter den „rangeLowestLow.price“ fällt: Wir erkennen einen Ausbruch nach unten und kennzeichnen ihn entsprechend.
Sobald ein Ausbruch bestätigt ist, setzen wir die notwendigen Zustandsvariablen zurück, um die Verfolgung einer neuen Konsolidierungsphase vorzubereiten. Wir protokollieren das Auftreten des Ausbruchs und speichern die „breakoutBarNumber“ als 1, was den ersten Balken der Ausbruchssequenz markiert. Der „breakoutTimestamp“ wird mit TimeCurrent aufgezeichnet, um den genauen Zeitpunkt des Ausbruchs festzuhalten. Zusätzlich speichern wir „lastImpulseHigh“ und „lastImpulseLow“, um das Kursverhalten nach einem Ausbruch zu verfolgen. Abschließend setzen wir „isBreakoutDetected“ auf false zurück und löschen die vorherige Konsolidierungsspanne, indem wir rangeHighestHigh.price“ und rangeLowestLow.price“ auf 0 setzen, um sicherzustellen, dass das System bereit ist, die nächste Handelsmöglichkeit zu erkennen.
Wenn es bestätigte Ausbrüche gibt, warten wir ab und verifizieren sie durch impulsive Bewegungen, um sie dann auf dem Chart darzustellen.
//--- Check for impulsive movement after breakout and create order blocks if (breakoutBarNumber >= 0 && TimeCurrent() > breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds()) { double impulseRange = lastImpulseHigh - lastImpulseLow; double impulseThresholdPrice = impulseRange * impulseMultiplier; isBullishImpulse = false; isBearishImpulse = false; for (int i = 1; i <= barsToWaitAfterBreakout; i++) { double closePrice = close(i); if (closePrice >= lastImpulseHigh + impulseThresholdPrice) { isBullishImpulse = true; Print("Impulsive upward move: ", closePrice, " >= ", lastImpulseHigh + impulseThresholdPrice); break; } else if (closePrice <= lastImpulseLow - impulseThresholdPrice) { isBearishImpulse = true; Print("Impulsive downward move: ", closePrice, " <= ", lastImpulseLow - impulseThresholdPrice); break; } } if (!isBullishImpulse && !isBearishImpulse) { Print("No impulsive movement detected."); } //--- }
Hier analysieren wir die Kursentwicklung nach einem Ausbruch, um festzustellen, ob eine impulsive Bewegung stattgefunden hat, was für die Identifizierung gültiger Orderblöcke entscheidend ist. Zunächst wird geprüft, ob „breakoutBarNumber“ gültig ist und ob die aktuelle Zeit, die über TimeCurrent abgerufen wird, den „breakoutTimestamp“ plus „barsToWaitAfterBreakout“ multipliziert mit PeriodSeconds überschritten hat, um sicherzustellen, dass eine ausreichende Wartezeit verstrichen ist. Anschließend berechnen wir „impulseRange“ als die Differenz zwischen „lastImpulseHigh“ und „lastImpulseLow“, die die Kursschwankung nach dem Ausbruch darstellt. Auf dieser Grundlage berechnen wir „impulseThresholdPrice“, indem wir „impulseRange“ mit „impulseMultiplier“ multiplizieren, um die für eine impulsive Bewegung erforderliche Mindestpreisausdehnung zu definieren.
Als Nächstes initialisieren wir „isBullishImpulse“ und „isBearishImpulse“ als „false“ und bereiten so die Auswertung der Kursbewegung über die letzten „barsToWaitAfterBreakout“-Balken vor. Diese Balken werden in einer for-Schleife durchlaufen, wobei der Schlusskurs mit der Funktion „close“ ermittelt wird. Wenn „closePrice“ größer oder gleich „lastImpulseHigh + impulseThresholdPrice“ ist, erkennen wir eine impulsive Aufwärts-Bewegung, setzen „isBullishImpulse“ auf true und protokollieren das Ereignis. Wenn „closePrice“ kleiner oder gleich „lastImpulseLow - impulseThresholdPrice“ ist, identifizieren wir eine impulsive Abwärtsbewegung, setzen „isBearishImpulse“ auf true und protokollieren das. Wenn keine der beiden Bedingungen erfüllt ist, wird die Meldung ausgegeben, dass keine impulsive Bewegung festgestellt wurde. Diese Logik stellt sicher, dass nur starke Ausbruchsfortsetzungen als gültige Auftragsblöcke für die weitere Verarbeitung in Frage kommen. Um sie zu visualisieren, verwenden wir die folgende Logik.
bool isOrderBlockValid = isBearishImpulse || isBullishImpulse; if (isOrderBlockValid) { datetime blockStartTime = iTime(_Symbol, _Period, consolidationBars + barsToWaitAfterBreakout + 1); double blockTopPrice = lastImpulseHigh; int visibleBarsOnChart = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); datetime blockEndTime = blockStartTime + (visibleBarsOnChart / 1) * PeriodSeconds(); double blockBottomPrice = lastImpulseLow; string orderBlockName = OB_Prefix + "(" + TimeToString(blockStartTime) + ")"; color orderBlockColor = isBullishImpulse ? bullishOrderBlockColor : bearishOrderBlockColor; string orderBlockLabel = isBullishImpulse ? "Bullish OB" : "Bearish OB"; if (ObjectFind(0, orderBlockName) < 0) { //--- Create a rectangle for the order block ObjectCreate(0, orderBlockName, OBJ_RECTANGLE, 0, blockStartTime, blockTopPrice, blockEndTime, blockBottomPrice); ObjectSetInteger(0, orderBlockName, OBJPROP_TIME, 0, blockStartTime); ObjectSetDouble(0, orderBlockName, OBJPROP_PRICE, 0, blockTopPrice); ObjectSetInteger(0, orderBlockName, OBJPROP_TIME, 1, blockEndTime); ObjectSetDouble(0, orderBlockName, OBJPROP_PRICE, 1, blockBottomPrice); ObjectSetInteger(0, orderBlockName, OBJPROP_FILL, true); ObjectSetInteger(0, orderBlockName, OBJPROP_COLOR, orderBlockColor); ObjectSetInteger(0, orderBlockName, OBJPROP_BACK, false); //--- Add a text label in the middle of the order block with dynamic font size datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; double labelPrice = (blockTopPrice + blockBottomPrice) / 2; string labelObjectName = orderBlockName + orderBlockLabel; if (ObjectFind(0, labelObjectName) < 0) { ObjectCreate(0, labelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); ObjectSetString(0, labelObjectName, OBJPROP_TEXT, orderBlockLabel); ObjectSetInteger(0, labelObjectName, OBJPROP_COLOR, labelTextColor); ObjectSetInteger(0, labelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); ObjectSetInteger(0, labelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); } ChartRedraw(0); //--- Store the order block details in arrays ArrayResize(orderBlockNames, ArraySize(orderBlockNames) + 1); orderBlockNames[ArraySize(orderBlockNames) - 1] = orderBlockName; ArrayResize(orderBlockEndTimes, ArraySize(orderBlockEndTimes) + 1); orderBlockEndTimes[ArraySize(orderBlockEndTimes) - 1] = blockEndTime; ArrayResize(orderBlockMitigatedStatus, ArraySize(orderBlockMitigatedStatus) + 1); orderBlockMitigatedStatus[ArraySize(orderBlockMitigatedStatus) - 1] = false; Print("Order Block created: ", orderBlockName); } }
Hier bestimmen wir, ob eine Auftragssperre aufgrund der Erkennung einer impulsiven Bewegung erstellt werden sollte. Zunächst wird „isOrderBlockValid“ ausgewertet, indem geprüft wird, ob entweder „isBearishImpulse“ oder „isBullishImpulse“ wahr ist. Falls gültig, definieren wir die wichtigsten Parameter für den Orderblock: „blockStartTime“ wird mit Hilfe der iTime-Funktion ermittelt, um den Bar bei „consolidationBars + barsToWaitAfterBreakout + 1“ zu referenzieren und sicherzustellen, dass er mit der identifizierten Struktur übereinstimmt. „blockTopPrice“ wird auf „lastImpulseHigh“ und „blockBottomPrice“ auf „lastImpulseLow“ gesetzt, wodurch die Preisspanne des Auftragsblocks festgelegt wird. Wir verwenden die Funktion ChartGetInteger, um „visibleBarsOnChart“ zu bestimmen und „blockEndTime“ dynamisch auf der Grundlage von PeriodSeconds zu berechnen, um sicherzustellen, dass das Rechteck innerhalb des aktuellen Bereichs des Charts sichtbar bleibt.
Der Name des Auftragsblocks wird mit „OB_Prefix“ und der Funktion TimeToString konstruiert, um den Zeitstempel für die Eindeutigkeit einzuschließen. Die Farbe und die Bezeichnung werden danach bestimmt, ob der Impuls auf- oder abwärts ist, indem „bullishOrderBlockColor“ oder „bearishOrderBlockColor“ ausgewählt und die entsprechende Bezeichnung zugewiesen wird.
Anschließend wird mit ObjectFind geprüft, ob der Auftragsblock vorhanden ist. Wenn er nicht existiert, zeichnen wir mit der Funktion ObjectCreate ein Rechteck (OBJ_RECTANGLE), das den Auftragsblock darstellt, und setzen seine Zeit- und Preisgrenzen mit ObjectSetInteger und ObjectSetDouble. Das Rechteck wird gefüllt (OBJPROP_FILL), mit Farbe versehen (OBJPROP_COLOR) und im Vordergrund gezeichnet (OBJPROP_BACK = false).
Als Nächstes erstellen wir zur besseren Visualisierung ein Etikett innerhalb des Auftragsblocks. Die Zeit des Etiketts („labelTime“) wird auf den Mittelpunkt von „blockStartTime“ und „blockEndTime“ gesetzt, während der „labelPrice“ als Mittelpunkt von „blockTopPrice“ und „blockBottomPrice“ berechnet wird. Wir generieren einen eindeutigen Etikettennamen, indem wir „orderBlockLabel“ an „orderBlockName“ anhängen. Wenn die Beschriftung nicht vorhanden ist, wird mit „ObjectCreate“ ein Textobjekt (OBJ_TEXT) erstellt, wobei der Textinhalt (OBJPROP_TEXT), die Farbe (OBJPROP_COLOR), die Schriftgröße (OBJPROP_FONTSIZE) und die Zentrierung mit (OBJPROP_ANCHOR = ANCHOR_CENTER) festgelegt werden. Die Funktion ChartRedraw sorgt dafür, dass die neu erstellten Elemente sofort erscheinen. Da die Schriftgröße je nach Maßstab des Charts eine wichtige Rolle spielt, wird sie dynamisch wie folgt berechnet.
//--- Calculate dynamic font size based on chart scale (0 = zoomed out, 5 = zoomed in) int chartScale = (int)ChartGetInteger(0, CHART_SCALE); // Scale ranges from 0 to 5 int dynamicFontSize = 8 + (chartScale * 2); // Font size: 8 (min) to 18 (max)
Schließlich speichern wir die Details der Auftragsblocks in Arrays: „orderBlockNames“ (speichert Objektnamen), „orderBlockEndTimes“ (speichert Ablaufzeiten) und „orderBlockMitigatedStatus“ (verfolgt, ob der Auftragsblock abgeschwächt wurde). Wir passen die Größe jedes Arrays mit der Funktion ArrayResize dynamisch an, um neue Einträge unterzubringen und sicherzustellen, dass unsere Auftragsblockverwaltung flexibel bleibt. Eine Bestätigungsmeldung wird gedruckt, um die erfolgreiche Erstellung eines Auftragsblocks anzuzeigen. Zum Schluss müssen wir nur noch die Variablen für das Breakout-Tracking zurücksetzen.
//--- Reset breakout tracking variables breakoutBarNumber = -1; breakoutTimestamp = 0; lastImpulseHigh = 0; lastImpulseLow = 0; isBullishImpulse = false; isBearishImpulse = false;
Nach dem Kompilieren und Ausführen des Programms erhalten wir das folgende Ergebnis.
Aus dem Bild ist ersichtlich, dass wir Orderblöcke, die aus den impulsiven Ausbruchsbewegungen resultieren, bestätigt und beschriftet haben. Jetzt müssen wir nur noch fortfahren, die abgeschwächten Orderblöcke durch fortgesetzte Verwaltung der Setups innerhalb der Chartgrenzen zu validieren.
//--- Process existing order blocks for mitigation and trading for (int j = ArraySize(orderBlockNames) - 1; j >= 0; j--) { string currentOrderBlockName = orderBlockNames[j]; bool doesOrderBlockExist = false; //--- Retrieve order block properties double orderBlockHigh = ObjectGetDouble(0, currentOrderBlockName, OBJPROP_PRICE, 0); double orderBlockLow = ObjectGetDouble(0, currentOrderBlockName, OBJPROP_PRICE, 1); datetime orderBlockStartTime = (datetime)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_TIME, 0); datetime orderBlockEndTime = (datetime)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_TIME, 1); color orderBlockCurrentColor = (color)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_COLOR); //--- Check if the order block is still valid (not expired) if (time(1) < orderBlockEndTime) { doesOrderBlockExist = true; } //--- }
Die „orderBlockNames“ werden in umgekehrter Reihenfolge durchlaufen, wobei jeder Auftragsblock auf Entschärfung und Handel geprüft wird. „currentOrderBlockName“ speichert den Namen des geprüften Blocks. Wir verwenden ObjectGetDouble und ObjectGetInteger, um „orderBlockHigh“, „orderBlockLow“, „orderBlockStartTime“, „orderBlockEndTime“ und „orderBlockCurrentColor“ abzurufen, um eine präzise Handhabung der Eigenschaften jedes Auftragsblocks sicherzustellen.
Um zu überprüfen, ob der Auftragsblock noch gültig ist, vergleichen wir „time(1)“ (abgerufen mit der Funktion „time“) mit „orderBlockEndTime“. Wenn der aktuelle Zeitpunkt innerhalb der Lebensdauer des Auftragsblocks liegt, wird „doesOrderBlockExist“ auf true gesetzt, um zu bestätigen, dass der Auftragsblock für die weitere Verarbeitung aktiv bleibt. Wenn dies der Fall ist, verarbeiten wir sie und handeln sie.
//--- Get current market prices double currentAskPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); double currentBidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Check for mitigation and execute trades if trading is enabled if (enableTrading && orderBlockCurrentColor == bullishOrderBlockColor && close(1) < orderBlockLow && !orderBlockMitigatedStatus[j]) { //--- Sell trade when price breaks below a bullish order block double entryPrice = currentBidPrice; double stopLossPrice = entryPrice + stopLossDistance * _Point; double takeProfitPrice = entryPrice - takeProfitDistance * _Point; obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); orderBlockMitigatedStatus[j] = true; ObjectSetInteger(0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor); string blockDescription = "Bullish Order Block"; string textObjectName = currentOrderBlockName + blockDescription; if (ObjectFind(0, textObjectName) >= 0) { ObjectSetString(0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription); } Print("Sell trade entered upon mitigation of bullish OB: ", currentOrderBlockName); } else if (enableTrading && orderBlockCurrentColor == bearishOrderBlockColor && close(1) > orderBlockHigh && !orderBlockMitigatedStatus[j]) { //--- Buy trade when price breaks above a bearish order block double entryPrice = currentAskPrice; double stopLossPrice = entryPrice - stopLossDistance * _Point; double takeProfitPrice = entryPrice + takeProfitDistance * _Point; obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); orderBlockMitigatedStatus[j] = true; ObjectSetInteger(0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor); string blockDescription = "Bearish Order Block"; string textObjectName = currentOrderBlockName + blockDescription; if (ObjectFind(0, textObjectName) >= 0) { ObjectSetString(0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription); } Print("Buy trade entered upon mitigation of bearish OB: ", currentOrderBlockName); }
Wir beginnen mit der Abfrage der aktuellen Marktpreise mit der Funktion SymbolInfoDouble und stellen sicher, dass sowohl „currentAskPrice“ als auch „currentBidPrice“ mit _Digits auf die entsprechende Anzahl von Dezimalstellen normalisiert sind. Dies garantiert Präzision bei der Platzierung von Handelsgeschäften. Als Nächstes wird geprüft, ob „enableTrading“ aktiv ist und ob eine Bedingung für die Abschwächung einer Auftragssperre erfüllt wurde. Eine Abschwächung tritt ein, wenn ein Preis einen Auftragsblock durchbricht, was auf eine Störung in seiner Haltestruktur hinweist.
Bei Aufwärts-Orderblöcken wird überprüft, ob der Schlusskurs des vorangegangenen Balkens (der mit der Funktion „close“ ermittelt wurde) unter orderBlockLow“ gefallen ist, und sichergestellt, dass dieser Orderblock nicht bereits abgeschwächt wurde (orderBlockMitigatedStatus[j] == false“). Wenn diese Bedingungen erfüllt sind, platzieren wir ein Verkaufsgeschäft mit Hilfe der Funktion „Sell“ des Objekts „obj_Trade“. Der Handel wird zum „currentBidPrice“ ausgeführt, wobei ein Stop-Loss („stopLossPrice“) um „stopLossDistance * _Point“ über dem Einstiegskurs und ein Take-Profit („takeProfitPrice“) um „takeProfitDistance * _Point“ unter dem Einstiegskurs gesetzt wird.
Sobald der Handel ausgeführt ist, wird der Auftragsblock als mitigiert markiert, indem „orderBlockMitigatedStatus[j]“ auf true aktualisiert wird, und seine Farbe wird mithilfe von ObjectSetInteger geändert, um seinen abgeschwächten Status anzuzeigen. Wenn für diesen Auftragsblock ein Text vorhanden ist (geprüft mit ObjectFind), aktualisieren wir sie mit ObjectSetString, um „Mitigated Bullish Order Block“ anzuzeigen. Ein Print protokolliert die Handelsausführung zur Nachverfolgung und Fehlersuche.
Bei Abwärts-Orderblöcken ist der Prozess ähnlich. Wir prüfen, ob der „close“ über „orderBlockHigh“ gestiegen ist, was auf einen Bruch des Abwärts-Orderblocks hindeutet. Wenn die Bedingungen erfüllt sind, wird ein Kaufgeschäft über die Funktion „Kaufen“ getätigt, wobei der „currentAskPrice“ als Einstiegskurs verwendet wird. Der „stopLossPrice“ liegt unter dem Einstiegskurs und der „takeProfitPrice“ darüber, um ein angemessenes Risikomanagement zu gewährleisten. Nach der Platzierung des Kaufgeschäfts aktualisieren wir „orderBlockMitigatedStatus[j]“, ändern die Farbe des Orderblocks mithilfe von ObjectSetInteger und ändern den Text (falls gefunden), um „Mitigated Bearish Order Block“ anzuzeigen. Schließlich protokolliert „Print“ die Ausführung des Kaufs zu Überwachungszwecken. Das haben wir erreicht.
Sobald die Blöcke außerhalb der Grenzen liegen, werden sie aus den Speicherbereichen entfernt.
//--- Remove expired order blocks from arrays if (!doesOrderBlockExist) { bool removedName = ArrayRemove(orderBlockNames, j, 1); bool removedTime = ArrayRemove(orderBlockEndTimes, j, 1); bool removedStatus = ArrayRemove(orderBlockMitigatedStatus, j, 1); if (removedName && removedTime && removedStatus) { Print("Success removing OB DATA from arrays at index ", j); } }
Wenn der Auftragsblock nicht mehr existiert, entfernen wir seinen Namen, seine Endzeit und seinen abgeschwächten Status aus den entsprechenden Arrays mit Hilfe der Funktion ArrayRemove. Wenn alle Entfernungen erfolgreich waren, wird die Aktion mit Print protokolliert, um die Bereinigung zu bestätigen. Hier ist ein Beispiel für eine Bereinigungsbestätigung.
Aus dem Bild geht hervor, dass wir die Blöcke erfolgreich bereinigt haben. Jetzt müssen wir nur noch die Logik für den Trailing-Stop hinzufügen, und dafür verwenden wir eine Funktion, um alles zu kapseln.
//+------------------------------------------------------------------+ //| Trailing stop function | //+------------------------------------------------------------------+ void applyTrailingStop(double trailingPoints, CTrade &trade_object, int magicNo = 0) { //--- Calculate trailing stop levels based on current market prices double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits); double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits); //--- Loop through all open positions for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) { //--- Adjust stop loss for buy positions if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) && (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP)); } //--- Adjust stop loss for sell positions else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) && (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP)); } } } } }
Hier definieren wir die Funktion „applyTrailingStop“, um die Stop-Loss für aktive Positionen dynamisch anzupassen. Wir beginnen mit der Berechnung von „buyStopLoss“ und „sellStopLoss“ unter Verwendung der aktuellen Geld-/Briefkurse und der angegebenen „trailingPoints“. Als Nächstes durchlaufen wir alle offenen Positionen und filtern sie nach Symbol und magischer Zahl (falls vorhanden). Wenn eine Kaufposition ein gültiges Stop-Loss-Niveau über ihrem Einstiegskurs hat und dieses entweder den aktuellen Stop-Loss übersteigt oder nicht gesetzt ist, wird sie aktualisiert. In ähnlicher Weise stellen wir bei Verkaufspositionen sicher, dass der neue Stop-Loss unter dem Einstiegskurs liegt, bevor er geändert wird.
Anschließend rufen wir die Funktion OnTick auf, um sie bei jedem Tick und nicht bei jedem Balken zu verarbeiten, um den Preis in Echtzeit zu überprüfen.
//--- Apply trailing stop to open positions if enabled if (enableTrailingStop) { applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber); }
Nach dem Kompilieren und Ausführen des Programms erhalten wir folgendes Ergebnis.
Aus der Visualisierung ist ersichtlich, dass das Programm alle Einstiegsbedingungen identifiziert und überprüft und bei Bestätigung die jeweilige Position mit den entsprechenden Einstiegsparametern eröffnet und somit unser Ziel erreicht. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest haben wir folgende Ergebnisse.
Backtest-Grafik:
Bericht des Backtest:
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir die Mitigation Order Blocks (MOB) Strategie erfolgreich in MQL5 implementiert haben, was eine präzise Erkennung, Visualisierung und einen automatisierten Handel auf der Grundlage von Smart Money Konzepten ermöglicht. Durch die Integration von Breakout-Validierung, Erkennung impulsiver Bewegungen und abschwächungsbasierter Handelsausführung identifiziert und verarbeitet unser System effektiv Orderblöcke und passt sich dabei der Marktdynamik an. Darüber hinaus haben wir Trailing-Stops und Risikomanagement-Mechanismen eingebaut, um die Handelsperformance zu optimieren und die Robustheit zu erhöhen.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Marktbedingungen können unvorhersehbar sein. Ordnungsgemäße Backtests und ein Risikomanagement sind vor dem Live-Einsatz unerlässlich.
Durch den Einsatz dieser Techniken können Sie Ihre algorithmischen Handelsstrategien verfeinern und die Effizienz des auftragsblockbasierten Handels verbessern. Testen, optimieren und passen Sie Ihren Ansatz weiter an, um langfristig erfolgreich zu sein. Viel Glück!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17547
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.





- 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.
Danke Allan, schön zusammengestellt, ich mag die Visuals und den Farbwechsel bei Mitigated und deine Handhabung der Arrays. Danke fürs Teilen
Vielen Dank für das freundliche Feedback. Gern geschehen.
Haben Sie den Artikel überhaupt gelesen? Denn wir sind uns sicher, dass der Artikel so ziemlich alle Ihre Fragen beantwortet.