Automatisieren von Handelsstrategien in MQL5 (Teil 46): Liquidity Sweep on Break of Structure (BoS)
Einführung
In unserem vorangegangenen Artikel (Teil 45) haben wir das System von Inverse Fair Value Gap (IFVG) in MetaQuotes Language 5 (MQL5) entwickelt, das Kurslücken mit einer Mindestgrößenfilterung erkennt, Zustände als normal/gemildert/invertiert verfolgt, Überschneidungen ignoriert, Inversionen mit festen Stop-Levels, Handelsmodi und Trailing-Stops handelt und Rechtecke mit Kennzeichnungen/Symbolen visualisiert. In Teil 46 entwickeln wir das System der Liquiditätsbereinigung (Liquidity-Sweeps) bei einem Strukturbruch (BoS).
Dieses System erkennt Schwankungen über eine definierte Zeitspanne, kennzeichnet sie als Schwankungen, um BoS, den Strukturbruch, zu identifizieren (HH in Aufwärtstrends, LL in Abwärtstrends), erkennt Bereinigungen, wenn der Kurs über die Umkehrpunkte hinausschießt, aber bei innerhalb der dem Trend entsprechenden Kerzen schließt, kauft bei der Bereinigung einer Sell-Side-Liquidität (SSL) in einem Aufwärts-BoS oder verkauft bei Buy Side-Liquidität (BSL) in Abwärtstrends mit dynamischen Stop-Levels, maximalen Handelsvolumina, schließt Gegenpositionen und visualisiert mit Symbolen/Kennzeichnungen, Rechtecken, gestrichelten Linien und Pfeilen sowie dynamischen Schriftarten. Wir werden die folgenden Themen behandeln: Wir werden die folgenden Themen behandeln:
- Verständnis der Strategie der Liquiditätsbereinigung bei einem Strukturbruch (BoS)
- Implementation in MQL5
- Backtests
- Schlussfolgerung
Am Ende verfügen Sie über eine funktionsfähige MQL5-Strategie für den Handel mit BoS-Liquiditäts-Sweeps, komplett mit visuellen Darstellungen und Risikokontrollen – fangen wir an!
Verständnis der Strategie der Liquiditätsbereinigung bei einem Strukturbruch (BoS)
Die Liquiditätsbereinigung bei einem Strukturbruch (BoS) ist eine Preisaktionsstrategie, die Trendidentifikation durch Umkehrpunkte mit der Erkennung von manipulativen Bereinigungen über diese Punkte hinaus kombiniert, um Liquidität vor einer Umkehr abzufangen. Wir scannen die umliegenden Balken, um hohe Umkehrpunkte (höher als die linken/rechten Nachbarn) und tiefe (niedriger) zu finden, und kennzeichnen sie relativ zu den Prioritäten: HH (höheres Hoch) oder LH (niedrigeres Hoch) für Höchstwerte, HL (höheres Tief) oder LL (niedrigeres Tief) für Tiefstwerte. BoS tritt bei HH in Aufwärtstrends (Aufwärts-Fortsetzung) oder LL in Abwärtstrends (fallend) auf und signalisiert einen Strukturbruch; eine Bereinigung tritt auf, wenn der Preis über den Umkehrpunkt hinausgeht (SSL unter dem Tiefpunkt in einem Aufwärtstrend, BSL über dem Hochpunkt in einem Abwärtstrend), aber innerhalb einer Richtungskerze schließt, was auf ein Einfangen der Stops vor der eigentlichen Bewegung hinweist.
Unser Plan ist es, Schwankungen über die Eingabelänge zu erkennen, HH/HL/LH/LL zu markieren, um den BoS-Trend festzulegen, Bereinigungen auf dem BoS mit Docht außerhalb des Umkehrpunkte zu erkennen und innerhalb der Trend-Kerze zu schließen, Käufe bei einem SSL und einem Aufwärts-BoS oder Verkäufe bei einem BSL und einem Abwärts-BoS mit dynamischen Handelsniveaus, maximalen Handelslimits, entgegengesetzten Schließungen zu handeln und mit Icons/Labels auf Umkehrpunkte, Rechtecken der Bereinigungen, gestrichelten Linien auf BoS-Ausbrüchen, Pfeilen auf Eingängen sowie dynamischen Schriftarten auf Skalenänderungen zu visualisieren. Liquidity-Sweep kann für jede Konstellation verwendet werden; wir haben uns für die Strategie des Strukturbruchs entschieden, weil sie einfach ist, aber sie kann auch für jede andere Konstellation verwendet werden, wie z.B. für Ungleichgewichte. 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.
//+------------------------------------------------------------------+ //| BOS Liquidity Sweep 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> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CTrade obj_Trade; //--- Trade object //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input group "EA GENERAL SETTINGS" input int SwingLength = 5; // Swing Length in Bars (left/right check) input double LotSize = 0.01; // Fixed lot size input double SL_Buffer_Pips = 10.0; // SL buffer in pips below/above sweep input double RiskRewardRatio = 2.0; // Take profit multiplier (e.g., 2:1 RR) input int MaxTrades = 1; // Max open trades input long MagicNumber = 12345; // Unique magic number input group "VISUALIZATION SETTINGS" input color clr_Bullish = clrBlue; // Bullish Color (HH/HL) input color clr_Bearish = clrRed; // Bearish Color (LL/LH) input color clr_SSL_Rect = clrLightBlue; // SSL Sweep Rectangle Color input color clr_BSL_Rect = clrLightCoral; // BSL Sweep Rectangle Color input color clr_SSL_Line = clrBlue; // SSL Sweep Line Color input color clr_BSL_Line = clrRed; // BSL Sweep Line Color input color clr_BullBOS = clrGreen; // Bullish BOS Line Color input color clr_BearBOS = clrMaroon; // Bearish BOS Line Color input int LineWidth = 2; // Line Width input bool PrintLogs = true; // Print Statements //+------------------------------------------------------------------+ //| Global Variables Continued | //+------------------------------------------------------------------+ static double current_swing_high = -1.0, current_swing_low = -1.0; //--- Current swing high and low static datetime swing_high_time = 0, swing_low_time = 0; //--- Swing high and low times int MarketTrend = 0; //--- Market trend (1: Bullish BOS, -1: Bearish BOS, 0: Neutral) int OpenTrades = 0; //--- Open trades count int current_font_size = 10; //--- Current font size int object_code = 174; //--- Wingdings arrow code for swings int buy_arrow_code = 233; //--- Wingdings up arrow for buy int sell_arrow_code = 234; //--- Wingdings down arrow for sell string ObjPrefix = "BOSLiqSweep_"; //--- Object prefix
Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade/Trade.mqh>“ einbinden, die die Klasse CTrade für die Auftrags- und Positionsverwaltung bereitstellt. Wir deklarieren „obj_Trade“ als eine globale Instanz von „CTrade“, um Handelsoperationen abzuwickeln. Wir gruppieren die Eingabeparameter unter „EA GENERAL SETTINGS“ für den Eigenschaftsdialog: „SwingLength“, um die Anzahl der Balken für Links-/Rechts-Prüfungen der Umkehrpunkte festzulegen, „LotSize“ für feste Lots, „SL_Buffer_Pips“ als Puffer unterhalb/oberhalb der Bereinigung für Stop-Loss, „RiskRewardRatio“ für Take-Profit-Multiples, „MaxTrades“, um offene Positionen zu begrenzen, und „MagicNumber“ für die Handelsidentifikation. Unter „VISUALISIERUNGSEINSTELLUNGEN“ haben wir Farben wie „clr_Bullish“ für HH/HL (blau), „clr_Bearish“ für LL/LH (rot), „clr_SSL_Rect“ für SSL-Rechtecke (hellblau), „clr_BSL_Rect“ für BSL (hellkoralle), „clr_SSL_Line“ für SSL-Linien (blau), „clr_BSL_Line“ für BSL (rot), „clr_BullBoS“ für Aufwärts-BoS (grün), „clr_BearBoS“ für Abwärts-BoS (kastanienbraun), „LineWidth“ für die Linienstärke und „PrintLogs“ zum Umschalten der Protokollierung.

Dann kommen wir zu den weiteren globalen Variablen: statische „current_swing_high“ und „current_swing_low“, initialisiert auf -1.0 für die Verfolgung der letzten Schwankungen, statische „swing_high_time“ und „swing_low_time“ für ihre Zeitstempel, „MarketTrend“ als 1 für Aufwärts-BoS, -1 für Abwärts-BoS, 0 für neutral, „OpenTrades“ zum Zählen der aktuellen Positionen, „current_font_size“ mit 10 für dynamischen Text, „object_code“ mit 174 für das Symbol aus Wingdings, „buy_arrow_code“ mit 233 für Kaufpfeile, „sell_arrow_code“ mit 234 für Verkaufspfeile und „ObjPrefix“ mit „BoSLiqSweep_“ für die Benennung der Objekte. Nun können wir die Programmlogik initialisieren. Wir wollen die Objekte, die wir bei der Initialisierung erstellen, löschen, um das bestehende Durcheinander zu beseitigen. Wir werden zunächst einige Hilfsfunktionen definieren.
//+------------------------------------------------------------------+ //| Update font sizes | //+------------------------------------------------------------------+ void UpdateFontSizes() { long scale = 0; //--- Init scale if (ChartGetInteger(0, CHART_SCALE, 0, scale)) { //--- Get scale current_font_size = (int)(7 + scale * 0.7); //--- Calculate font size if (current_font_size < 6) current_font_size = 6; //--- Set minimum font size if (current_font_size > 15) current_font_size = 15; //--- Set maximum font size for (int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--) { //--- Iterate objects reverse string name = ObjectName(0, i, -1, -1); //--- Get object name long type = ObjectGetInteger(0, name, OBJPROP_TYPE); //--- Get object type if (type == OBJ_TEXT) { //--- Check text type ObjectSetInteger(0, name, OBJPROP_FONTSIZE, current_font_size); //--- Set font size } } ChartRedraw(0); //--- Redraw chart } } //+------------------------------------------------------------------+ //| Delete objects by prefix | //+------------------------------------------------------------------+ void DeleteObjectsByPrefix(string prefix) { int total = ObjectsTotal(0, 0, -1); //--- Get total objects for (int i = total - 1; i >= 0; i--) { //--- Iterate reverse string name = ObjectName(0, i, 0, -1); //--- Get name if (StringFind(name, prefix) == 0) { //--- Check prefix ObjectDelete(0, name); //--- Delete object } } } //+------------------------------------------------------------------+ //| Count open trades | //+------------------------------------------------------------------+ int CountOpenTrades() { int count = 0; //--- Init count for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate reverse ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket)) { //--- Select position if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol and magic count++; //--- Increment count } } } return count; //--- Return count }
Hier implementieren wir die Funktion „UpdateFontSizes“, um die Größe der Textobjekte im Chart dynamisch an die aktuelle Zoomstufe anzupassen und so die Lesbarkeit zu gewährleisten. Wir initialisieren „scale“ auf 0 und rufen den Skalenwert des Charts mit ChartGetInteger unter Verwendung von CHART_SCALE ab. Bei Erfolg wird „current_font_size“ als 7 plus 70 % der Skala berechnet, sodass der Wert zwischen 6 und 15 liegt. Anschließend werden alle Objekte im Chart in einer Schleife rückwärts durchlaufen, wobei ObjectsTotal -1 für alle Fenster und Typen angibt und jeder Name über ObjectName und jeder Typ mit ObjectGetInteger und OBJPROP_TYPE abgerufen wird. Bei OBJ_TEXT-Objekten wird die Schriftgröße mit ObjectSetInteger und OBJPROP_FONTSIZE aktualisiert und das Chart dann neu gezeichnet.
Wir definieren die Funktion „DeleteObjectsByPrefix“, um alle Chartobjekte zu entfernen, die mit einem bestimmten Präfix übereinstimmen, der zum Aufräumen verwendet wird. Wir ermitteln die Gesamtzahl der Objekte im Hauptdiagramm und alle Typen und führen dann eine Rückwärtsschleife durch: für jedes Objekt holen wir den Namen, prüfen, ob es mit dem Präfix beginnt, indem wir StringFind verwenden, das 0 zurückgibt, und löschen es über ObjectDelete, wenn es übereinstimmt. Wir erstellen die Funktion „CountOpenTrades“, um die aktuellen offenen Positionen zu zählen, die zu diesem Programm gehören. Wir initialisieren „count“ auf 0, durchlaufen eine Schleife rückwärts durch PositionsTotal, rufen jedes Ticket mit PositionGetTicket ab und wählen die Position mit der Funktion PositionSelectByTicket aus. Wenn das Symbol mit „PositionGetString“ und „POSITION_SYMBOL“ und die magische Zahl mit „PositionGetInteger“ und „POSITION_MAGIC“ übereinstimmen erhöhen wir „count“, bevor wir es zurückgeben. Wir werden einige weitere Hilfsfunktionen für die Visualisierung definieren.
//+------------------------------------------------------------------+ //| Draw swing point with label | //+------------------------------------------------------------------+ void DrawSwingPoint(string objName, datetime time, double price, int arrCode, color clr, int direction, string label) { UpdateFontSizes(); //--- Update font sizes objName = ObjPrefix + label + TimeToString(time); //--- Set obj name if (ObjectFind(0, objName) < 0) { //--- Check no object string iconName = objName + "_icon"; //--- Icon name ObjectCreate(0, iconName, OBJ_TEXT, 0, time, price); //--- Create icon ObjectSetString(0, iconName, OBJPROP_FONT, "Wingdings"); //--- Set font ObjectSetInteger(0, iconName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size ObjectSetString(0, iconName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text ObjectSetInteger(0, iconName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, iconName, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor string txtName = objName + "_txt"; //--- Text name ObjectCreate(0, txtName, OBJ_TEXT, 0, time, price); //--- Create text ObjectSetString(0, txtName, OBJPROP_FONT, "Arial"); //--- Set font ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor ObjectSetString(0, txtName, OBJPROP_TEXT, label); //--- Set text } ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Draw sweep rectangle (no text) | //+------------------------------------------------------------------+ void DrawSweepRectangle(string objName, datetime time, double level, double extremum, color clr, bool is_ssl) { UpdateFontSizes(); //--- Update font sizes objName = ObjPrefix + objName + TimeToString(time, TIME_SECONDS); //--- Set obj name if (ObjectFind(0, objName) < 0) { //--- Check no object double top = MathMax(level, extremum); //--- Calc top double bottom = MathMin(level, extremum); //--- Calc bottom datetime end_time = time + PeriodSeconds(_Period); //--- Calc end time ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time, top, end_time, bottom); //--- Create rectangle ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, objName, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(0, objName, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID); //--- Set style // No text inside rectangle to reduce clutter } ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Draw horizontal dashed break level | //+------------------------------------------------------------------+ void DrawBreakLevel(string objName, datetime time1, double price, datetime time2, double price2, color clr, int direction, string label) { UpdateFontSizes(); //--- Update font sizes objName = ObjPrefix + objName + label + TimeToString(time2, TIME_SECONDS); //--- Set obj name if (ObjectFind(0, objName) < 0) { //--- Check no object ObjectCreate(0, objName, OBJ_TREND, 0, time1, price, time2, price); //--- Create trend line ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, objName, OBJPROP_WIDTH, LineWidth); //--- Set width ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH); //--- Set style ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, false); //--- Set no ray right string txt = label + " Sweep"; //--- Set text string txtName = objName + "_txt"; //--- Text name ObjectCreate(0, txtName, OBJ_TEXT, 0, time2, price); //--- Create text ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size if (direction > 0) { //--- Check positive ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER); //--- Set anchor ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt); //--- Set text } else { //--- Negative ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); //--- Set anchor ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt); //--- Set text } } ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Draw entry arrow with Wingdings | //+------------------------------------------------------------------+ void DrawEntryArrow(datetime time, double price, bool is_buy) { UpdateFontSizes(); //--- Update font sizes string objName = ObjPrefix + "Entry_" + TimeToString(time, TIME_SECONDS); //--- Set obj name if (ObjectFind(0, objName) < 0) { //--- Check no object int arrCode = is_buy ? buy_arrow_code : sell_arrow_code; //--- Set arrow code color arrow_color = is_buy ? clrBlue : clrRed; //--- Set color ObjectCreate(0, objName, OBJ_TEXT, 0, time, price); //--- Create text ObjectSetString(0, objName, OBJPROP_FONT, "Wingdings"); //--- Set font ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size ObjectSetString(0, objName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, arrow_color); //--- Set color ObjectSetInteger(0, objName, OBJPROP_ANCHOR, is_buy ? ANCHOR_UPPER : ANCHOR_LOWER); //--- Set anchor } ChartRedraw(0); //--- Redraw chart }
Zunächst definieren wir die Funktion „DrawSwingPoint“, um erkannte Umkehrpunkte im Chart mit einem Symbol und einer Kennzeichnung zu visualisieren. Wir rufen „UpdateFontSizes“ auf, um die aktuelle Schriftgröße zu gewährleisten, und bilden einen eindeutigen Objektnamen, indem wir „ObjPrefix“, die Bezeichnung und die Zeitangabe kombinieren. Wenn kein Objekt per ObjectFind existiert, erstellen wir ein Wingdings-Icon als OBJ_TEXT mit dem Suffix „_icon“ bei Zeit und Preis, setzen die Schriftart auf Wingdings, die Größe auf „current_font_size“, den Text auf das Zeichen aus „arrCode“ via CharToString, die Farbe auf „clr“ und den Anker rechts. Dann erstellen wir die Textbeschriftung mit dem Suffix „_txt“ als ein weiteres „OBJ_TEXT“, verwenden die Schriftart Arial, dieselbe Farbe und Größe, setzen den Anker links und den Text auf „label“. Wir zeichnen das Chart mit der Funktion ChartRedraw neu.
Dann implementieren wir die Funktion „DrawSweepRectangle“, um ein gefülltes Rechteck zu zeichnen, das den Sweep-Bereich ohne internen Text hervorhebt, um Unordnung zu vermeiden. Wir rufen „UpdateFontSizes“ auf, bilden den Namen mit „ObjPrefix“ und die Zeit mit Sekunden als Zeichenkette. Wenn kein Objekt vorhanden ist, berechnen wir oben als Maximum von Level und Extremum, unten als Minimum, die Endzeit als Zeit plus eine Balkenperiode, erstellen OBJ_RECTANGLE, das die Zeit oben bis zum Ende unten überspannt, setzen die Farbe auf „clr“, back true, fill true, solid style, und zeichnen das Chart neu. Wir erstellen die Funktion „DrawBreakLevel“, um BoS-Unterbrechungen mit einer horizontalen gestrichelten Linie und Text zu markieren. Wir rufen „UpdateFontSizes“, Formularname mit „ObjPrefix“, Label und Zeit 2 Sekunden String. Wenn kein Objekt vorhanden ist, erstellen wir OBJ_TREND vom Zeitpunkt 1 zum Preis zum Zeitpunkt 2 zum Preis (horizontal), setzen die Farbe auf „clr“, die Breite auf „LineWidth“, den Stil auf Strichform, nicht als Strahl nach rechts. Wir fügen den Text als „OBJ_TEXT“ zum Zeitpunkt 2 und den Preis mit dem Suffix „_txt“ hinzu, die Farbe setzen wir auf „clr“, die Größe auf „current_font_size“, den Anker nach rechts-oben, wenn die Richtung positiv ist, oder rechts-unten, wenn negativ.
Schließlich definieren wir die Funktion „DrawEntryArrow“, um einen Wingdings-Pfeil zu platzieren, der Handelseinträge anzeigt. Wir rufen „UpdateFontSizes“ auf, formen den Namen mit „ObjPrefix + "Entry_" + Zeitstring mit Sekunden“. Wenn kein Objekt vorhanden ist, wählen wir „arrCode“ als „buy_arrow_code“ für Käufe oder „sell_arrow_code“ für Verkäufe, Farbe als blau für Käufe oder rot für Verkäufe, erstellen „OBJ_TEXT“ zu Zeit und Preis, setzen die Schriftart auf „Wingdings“, die Größe auf „current_font_size“, den Text auf das Zeichen aus „arrCode“, die Farbe und den Anker oben für „buys“ oder unten für „sells“, und zeichnen das Chart neu. Wir können nun mit der Initialisierung fortfahren, um die Eingabevariablen zu bereinigen und zu überprüfen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number for trade object if (SwingLength < 1 || LotSize <= 0 || SL_Buffer_Pips < 0 || RiskRewardRatio < 1.0 || MaxTrades < 1) { //--- Check invalid inputs Print("Invalid input parameters."); //--- Log invalid parameters return(INIT_PARAMETERS_INCORRECT); //--- Return incorrect parameters } DeleteObjectsByPrefix(ObjPrefix); //--- Delete objects by prefix UpdateFontSizes(); //--- Update font sizes Print("EA Initialized Successfully."); //--- Log initialization success return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { DeleteObjectsByPrefix(ObjPrefix); //--- Delete objects by prefix Print("EA Deinitialized."); //--- Log deinitialization }
In der Ereignisbehandlung von OnInit, die ausgeführt wird, wenn das Programm an einen Chart angehängt oder geladen wird, setzen wir die Eingabe „MagicNumber“ auf „obj_Trade“ mit „SetExpertMagicNumber“, um unsere Trades zu identifizieren. Anschließend werden die wichtigsten Eingaben überprüft: Wenn „SwingLength“ kleiner als 1 ist, „LotSize“ nicht positiv ist, „SL_Buffer_Pips“ negativ ist, „RiskRewardRatio“ unter 1,0 liegt oder „MaxTrades“ kleiner als 1 ist, wird „Invalid input parameters“ mit Print protokolliert und INIT_PARAMETERS_INCORRECT zurückgegeben. Andernfalls rufen wir „DeleteObjectsByPrefix“ auf, um das vorhandene Bildmaterial zu löschen, „UpdateFontSizes“, um die anfängliche Textgröße festzulegen, protokollieren „EA Initialized Successfully“ und geben INIT_SUCCEEDED zurück. In der Ereignisbehandlung von OnDeinit, der aufgerufen wird, wenn das Programm beendet oder das Terminal geschlossen wird, rufen wir „DeleteObjectsByPrefix“ auf, um alle Chartobjekte, die unserem Präfix entsprechen, zu entfernen und so ein sauberes Ende ohne visuelle Überreste zu gewährleisten. Wir können nun mit der nächsten Logik in der Ereignisbehandlung von OnTick fortfahren, um die ganze schwere Arbeit zu erledigen. Wir werden mit der Erkennung von Schwankungen und Brüchen beginnen.
//+------------------------------------------------------------------+ //| Detect swings and BOS | //+------------------------------------------------------------------+ void DetectSwingsAndBOS() { int curr_bar = SwingLength; //--- Set current bar bool isSwingHigh = true, isSwingLow = true; //--- Init swing flags for (int j = 1; j <= SwingLength; j++) { //--- Iterate length int right_index = curr_bar - j; //--- Calc right index (newer) int left_index = curr_bar + j; //--- Calc left index (older) if (iHigh(_Symbol, _Period, curr_bar) <= iHigh(_Symbol, _Period, right_index) || iHigh(_Symbol, _Period, curr_bar) < iHigh(_Symbol, _Period, left_index)) { //--- Check not high isSwingHigh = false; //--- Set not high } if (iLow(_Symbol, _Period, curr_bar) >= iLow(_Symbol, _Period, right_index) || iLow(_Symbol, _Period, curr_bar) > iLow(_Symbol, _Period, left_index)) { //--- Check not low isSwingLow = false; //--- Set not low } } if (isSwingHigh) { //--- Check swing high double new_high = iHigh(_Symbol, _Period, curr_bar); //--- Get new high string label = "H"; //--- Init label color clr = clr_Bullish; //--- Set color if (current_swing_high > 0) { //--- Check existing high if (new_high > current_swing_high) { //--- Check higher label = "HH"; //--- Set HH MarketTrend = 1; //--- Set bullish trend if (PrintLogs) Print("Bullish BOS Detected"); //--- Log bullish BOS datetime break_time = FindBreakTime(swing_high_time, current_swing_high, true); //--- Find break time if (break_time > 0) DrawBreakLevel("Bull_BOS_", swing_high_time, current_swing_high, break_time, current_swing_high, clr_BullBOS, -1, "Bullish BOS"); //--- Draw BOS } else { //--- Lower label = "LH"; //--- Set LH clr = clr_Bearish; //--- Set bearish color } } if (PrintLogs) Print("SWING HIGH @ BAR INDEX ", curr_bar, " of High: ", new_high, " Label: ", label); //--- Log high DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_high, object_code, clr, -1, label); //--- Draw high point current_swing_high = new_high; //--- Update high swing_high_time = iTime(_Symbol, _Period, curr_bar); //--- Update high time } if (isSwingLow) { //--- Check swing low double new_low = iLow(_Symbol, _Period, curr_bar); //--- Get new low string label = "L"; //--- Init label color clr = clr_Bearish; //--- Set color if (current_swing_low > 0) { //--- Check existing low if (new_low < current_swing_low) { //--- Check lower label = "LL"; //--- Set LL MarketTrend = -1; //--- Set bearish trend if (PrintLogs) Print("Bearish BOS Detected"); //--- Log bearish BOS datetime break_time = FindBreakTime(swing_low_time, current_swing_low, false); //--- Find break time if (break_time > 0) DrawBreakLevel("Bear_BOS_", swing_low_time, current_swing_low, break_time, current_swing_low, clr_BearBOS, 1, "Bearish BOS"); //--- Draw BOS } else { //--- Higher label = "HL"; //--- Set HL clr = clr_Bullish; //--- Set bullish color } } if (PrintLogs) Print("SWING LOW @ BAR INDEX ", curr_bar, " of Low: ", new_low, " Label: ", label); //--- Log low DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_low, object_code, clr, 1, label); //--- Draw low point current_swing_low = new_low; //--- Update low swing_low_time = iTime(_Symbol, _Period, curr_bar); //--- Update low time } } //+------------------------------------------------------------------+ //| Find break candle time (based on close) | //+------------------------------------------------------------------+ datetime FindBreakTime(datetime prev_time, double prev_level, bool is_high_break) { int prev_shift = iBarShift(_Symbol, _Period, prev_time); //--- Get prev shift if (prev_shift < 0) return 0; //--- Return invalid for (int i = prev_shift - 1; i >= 0; i--) { //--- Iterate reverse if (is_high_break) { //--- Check high break if (iClose(_Symbol, _Period, i) > prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break } else { //--- Low break if (iClose(_Symbol, _Period, i) < prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break } } return 0; //--- Return no break }
Um die Erkennungslogik unterzubringen, definieren wir die Funktion „DetectSwingsAndBoS“, um Umkehrpunkte zu identifizieren und Strukturbrüche bei jedem neuen Balken zu erkennen, wobei die Trendrichtung und das Bildmaterial entsprechend aktualisiert werden. Wir setzen „curr_bar“ auf „SwingLength“ als Zielbalken zum Scannen, initialisieren „isSwingHigh“ und „isSwingLow“ auf true. Dann wird eine Schleife von 1 bis „SwingLength“ durchlaufen: Für jedes j wird „right_index“ als „curr_bar - j“ (jüngere Balken) und „left_index“ als „curr_bar + j“ (ältere Balken) berechnet, wobei überprüft wird, ob das Hoch des aktuellen Balkens nicht streng über dem rechten und linken Hoch liegt.Wenn diese Bedingung nicht erfüllt ist, wird „isSwingHigh“ auf „false“ gesetzt. Ähnlich verhält es sich mit den Tiefs, wobei „isSwingLow“ auf „false“ gesetzt wird, wenn sie nicht streng genommen niedriger sind.
Wenn „isSwingHigh“ wahr bleibt, erfassen wir das Hoch in „new_high“, initialisieren das Label als „H“ und die Farbe als „clr_Bullish“. Wenn ein vorheriger Wert von „current_swing_high“ vorhanden ist, vergleichen wir ihn: Ist er höher, kennzeichnen wir ihn mit „HH“, setzen „MarketTrend“ auf 1 (aufwärts) und protokollieren „Bullish BoS Detected“, sofern „PrintLogs“ auf „true“ gesetzt ist; ermitteln wir die Durchbruchzeit mit „FindBreakTime“ unter Verwendung von „swing_high_time“, „current_swing_high“ und „true“ für einen Durchbruch nach oben, und falls gültig, rufen wir „DrawBreakLevel“ mit dem Präfix „Bull_BoS_“, Zeiten, Level, „clr_BullBoS“, Richtung -1 und dem Text „Bullish BoS“ auf.
Ist er niedriger, kennzeichnen wir ihn mit „LH“ und der Farbe „clr_Bearish“. Wir protokollieren den Umkehrpunkt mit „PrintLogs“, rufen „DrawSwingPoint“ mit time string, time, price, „object_code“, color, direction -1, label auf, aktualisieren „current_swing_high“ und „swing_high_time“. Wir spiegeln für den tiefen Umkehrpunkt. Wir implementieren die Funktion „FindBreakTime“, um den ersten Balken-Schlusskurs über ein vorheriges Niveau des Umkehrpunkts hinaus zu finden, ermitteln die Verschiebung von „prev_time“ mit „iBarShift“ und geben 0 zurück, wenn sie ungültig ist, und führen eine Rückwärtsschleife von prev_shift -1 bis 0 durch: für den Ausbruch nach oben, wenn der Schlusskurs über „prev_level“ liegt, geben wir die Zeit dieses Balkens mit iTime zurück; für den Ausbruch nach unten, wenn der Schlusskurs darunter liegt. Es wird 0 zurückgegeben, wenn kein Ausbruch gefunden wird. Wir verwenden diese Funktion nun, um die Erkennung pro Balken durchzuführen, indem wir sie wie folgt aufrufen.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static bool isNewBar = false; //--- New bar flag int currBars = iBars(_Symbol, _Period); //--- Get current bars static int prevBars = currBars; //--- Previous bars if (prevBars == currBars) { //--- Check same bars isNewBar = false; //--- Set not new bar } else if (prevBars != currBars) { //--- Check new bars isNewBar = true; //--- Set new bar prevBars = currBars; //--- Update previous bars } if (!isNewBar) return; //--- Return if not new bar OpenTrades = CountOpenTrades(); //--- Count open trades if (OpenTrades >= MaxTrades) return; //--- Return if max trades reached DetectSwingsAndBOS(); //--- Detect swings and BOS }
Hier verwenden wir in OnTick, das bei jedem Preis-Tick ausgeführt wird, um die Kernlogik zu verwalten, ein statisches „isNewBar“-Flag und „prevBars“, um Balkenänderungen zu erkennen: Wir holen alle Balken mit iBars in „currBars“, vergleichen ihn mit „prevBars“ – ist er unverändert, weisen wir „isNewBar“ false zu, ist er erhöht true und aktualisieren „prevBars“. Wenn es sich nicht um einen neuen Balken handelt, kehren wir früher zurück. Andernfalls aktualisieren wir „OpenTrades“ durch den Aufruf von „CountOpenTrades“ und kehren bei Erreichen oder Überschreiten von „MaxTrades“ zurück, um neue Einträge zu verhindern. Dann rufen wir „DetectSwingsAndBoS“ auf, um nach Schwankungen und Strukturbrüchen zu suchen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:
Konstellation der Abwärtsbereinigung (Bearish Sweep Setup):

Konstellation der Aufwärtsbereinigung (Bullish Sweep Setup):

Nachdem die Erkennung abgeschlossen ist, müssen wir nun mit den Liquidity-Sweeps handeln. Aus Gründen der Modularität werden wir die Logik in einer Funktion unterbringen.
//+------------------------------------------------------------------+ //| Detect and trade sweep on BOS | //+------------------------------------------------------------------+ void DetectAndTradeSweepOnBOS() { if (MarketTrend == 0) return; //--- Return if neutral double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask // Bullish BOS + SSL Sweep for Buy if (MarketTrend == 1 && current_swing_low > 0.0 && iLow(_Symbol, _Period, 1) < current_swing_low && iClose(_Symbol, _Period, 1) > current_swing_low && iClose(_Symbol, _Period, 1) > iOpen(_Symbol, _Period, 1)) { //--- Check bullish sweep if (PrintLogs) Print("Bullish BOS + SSL Sweep Detected"); //--- Log sweep double sweep_low = iLow(_Symbol, _Period, 1); //--- Get sweep low datetime sweep_time = iTime(_Symbol, _Period, 1); //--- Get sweep time DrawSweepRectangle("SSL_Rect_", sweep_time, current_swing_low, sweep_low, clr_SSL_Rect, true); //--- Draw SSL rect DrawBreakLevel("SSL_Line_", swing_low_time, current_swing_low, sweep_time, current_swing_low, clr_SSL_Line, 1, "SSL"); //--- Draw SSL line CloseOpposite(true); //--- Close opposite double sl = NormalizeDouble(sweep_low - SL_Buffer_Pips * _Point, _Digits); //--- Calc SL double entry = ask; //--- Set entry double risk = entry - sl; //--- Calc risk double tp = NormalizeDouble(entry + risk * RiskRewardRatio, _Digits); //--- Calc TP obj_Trade.Buy(LotSize, _Symbol, entry, sl, tp, "BOS SSL Buy"); //--- Open buy if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success DrawEntryArrow(sweep_time, iLow(_Symbol,_Period, 1), true); //--- Draw buy arrow MarketTrend = 0; //--- Reset trend } else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure } // Bearish BOS + BSL Sweep for Sell if (MarketTrend == -1 && current_swing_high > 0.0 && iHigh(_Symbol, _Period, 1) > current_swing_high && iClose(_Symbol, _Period, 1) < current_swing_high && iClose(_Symbol, _Period, 1) < iOpen(_Symbol, _Period, 1)) { //--- Check bearish sweep if (PrintLogs) Print("Bearish BOS + BSL Sweep Detected"); //--- Log sweep double sweep_high = iHigh(_Symbol, _Period, 1); //--- Get sweep high datetime sweep_time = iTime(_Symbol, _Period, 1); //--- Get sweep time DrawSweepRectangle("BSL_Rect_", sweep_time, current_swing_high, sweep_high, clr_BSL_Rect, false); //--- Draw BSL rect DrawBreakLevel("BSL_Line_", swing_high_time, current_swing_high, sweep_time, current_swing_high, clr_BSL_Line, -1, "BSL"); //--- Draw BSL line CloseOpposite(false); //--- Close opposite double sl = NormalizeDouble(sweep_high + SL_Buffer_Pips * _Point, _Digits); //--- Calc SL double entry = bid; //--- Set entry double risk = sl - entry; //--- Calc risk double tp = NormalizeDouble(entry - risk * RiskRewardRatio, _Digits); //--- Calc TP obj_Trade.Sell(LotSize, _Symbol, entry, sl, tp, "BOS BSL Sell"); //--- Open sell if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success DrawEntryArrow(sweep_time, iHigh(_Symbol,_Period,1), false); //--- Draw sell arrow MarketTrend = 0; //--- Reset trend } else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure } }
Hier definieren wir die Funktion „DetectAndTradeSweepOnBoS“, um Liquidity-Sweeps nach einem Strukturbruch zu erkennen und dementsprechend Handelsgeschäfte auszuführen, während die Darstellung aktualisiert und bestehende Positionen verwaltet werden. Wir kehren zunächst vorzeitig zurück, wenn „MarketTrend“ 0 ist, was auf neutrale Bedingungen ohne aktive BoS hinweist. Die aktuellen Geld- und Briefkurse werden über SymbolInfoDouble mit SYMBOL_BID und SYMBOL_ASK abgefragt. Bei einem Aufwärts-BoS („MarketTrend == 1“) mit einem gültigen „current_swing_low“ über 0.0, prüfen wir, ob es sich um einen SSL-Sweep handelt: Wenn das Tief des vorherigen Balkens („iLow“ bei Shift 1) unter das „current_swing_low“ fiel, sein Schlusskurs („iClose“ bei 1) jedoch darüber und über dem Eröffnungskurs (iOpen bei 1) endete, bestätigt dies eine Aufwärtskerze, die Verkäufe beendet. Wenn erkannt, wird, wenn „PrintLogs“ true ist, „Bullish BoS + SSL Sweep Detected“ protokolliert, es werden der Bereinigungstiefpunkt und die Zeit mit iLow und „iTime“ bei Index 1 erfasst und es wird „DrawSweepRectangle“ aufgerufen mit Präfix „SSL_Rect_“, Zeit, „current_swing_low“, Sweep low, „clr_SSL_Rect“ und true für SSL.
Wir spiegeln die Logik für Abwärts-BoS („MarketTrend == -1“) mit einem gültigen „current_swing_high“ wider: Wir prüfen, ob das vorherige Hoch über dem „current_swing_high“ lag, aber unter der Eröffnung schloss, was eine Abwärtskerze bestätigt. Wenn ja, protokollieren wir „Bearish BoS + BSL Sweep Detected“, erfassen das Bereinigungshoch und die Zeit, zeichnen ein Rechteck mit „BSL_Rect_“, „clr_BSL_Rect“, false für BSL, und dem Ausbruchsniveau mit „BSL_Line_“, „clr_BSL_Line“, Richtung -1, Bezeichnung „BSL“. Wir rufen „CloseOpposite“ mit „false“ auf, um Kaufpositionen zu schließen; setzen den Stop-Loss über das Sweep-Hoch plus Puffer; der Eröffnungspreis ist der Geldkurs; das Risiko entspricht dem Stop-Loss minus Eröffnungspreis; der Take-Profit entspricht dem Eröffnungspreis minus Risiko mal Ratio; wir verkaufen mit „obj_Trade.Sell“ und fügen den Kommentar „BOS BSL Sell“ hinzu. Bei Erfolg wird ein Pfeil mit false für Verkauf gezeichnet und der Trend zurückgesetzt; andernfalls wird ein Fehler protokolliert. Danach rufen wir die Funktion einfach über die Tick-Funktion auf und erhalten folgendes Ergebnis.

Anhand der Visualisierung können wir sehen, dass wir die Konstellationen der Lquidity-Sweeps erkennen, handeln und verwalten und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.
Backtest-Grafik:

Bericht des Backtests:

Schlussfolgerung
Abschließend haben wir ein System mit dem Liquidity Sweep on Break of Structure (BoS) in MQL5 entwickelt. Es erkennt Schwankungen über die Eingabelänge und markiert die Umkehrpunkte, um den Trend festzulegen. Es zeigt Bereinigungen an, wenn es einen Docht jenseits eines Umkehrpunkts und einen Schlusskurs innerhalb der Richtungskerze gibt. Das System kauft bei einem Sell-Side-Liquidity (SSL) und einem Aufwärts-BoS, oder verkauft bei einem Buy-Side-Liquidity (BSL) und einem Abwärts-BoS. Es verwendet dynamische Handelsstufen, ein maximales Handelslimit und schließt entgegengesetzte Positionen. Die Visualisierung umfasst Symbole für Umkehrpunkte, gestrichelte Linien für Unterbrechungen, gefüllte Rechtecke für Umkehrpunkte, Pfeile für Eingaben und adaptive Schriftgrößen zum Skalieren.
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 Strategie der Liquiditätsbereinigung, des Liquidity-Sweeps, und dem Strukturbruch können Sie manipulative Dochte eines BoS erkennen. Sie sind für den Handel mit Umkehr-Konstellationen gerüstet und bereit für weitere Optimierungen auf Ihrer Handelsreise. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20569
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.
Adaptive Smart Money Architektur (ASMA): Verschmelzung von SMC-Logik und Marktstimmung für dynamische Strategie-Wechsel
Reine Implementierung der RSA-Verschlüsselung in MQL5
ARIMA-Prognose-Indikator in MQL5
Die Grenzen des maschinellen Lernens überwinden (Teil 9): Korrelationsbasierte Lernen von Merkmalen im selbstüberwachten Finanzwesen
- 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.
Die angehängte Datei funktioniert nicht auf mt5
Hallo Allan, herzlichen Glückwunsch zum neuen Jahr. Vielen Dank für den Artikel, es hilft mir auf meinem Weg, um herauszufinden, wie man meine Multi Timeframe BOS EA Code, indem Sie Ihre Schritte und die Eingabe von alles, wie ich gehe entlang statt nur kopieren einfügen. Ich habe jetzt jedoch in einen Fehler bei der "Detect Swings und BOS" Abschnitt des Codes laufen, beim Kompilieren erhalte ich eine "undeclared Bezeichner" Fehler für die "FindBreakTime" Zeile des Codes. Wie haben Sie es geschafft, dass es mit "FindBreakTime" funktioniert, da es so aussieht, als ob ich lieber etwas wie "SymbolInfoSessionsTrade" verwenden sollte?
Ich danke Ihnen.
Hallo Allan, herzlichen Glückwunsch zum neuen Jahr. Vielen Dank für den Artikel, es hilft mir auf meinem Weg, um herauszufinden, wie man meine Multi Timeframe BOS EA Code, indem Sie Ihre Schritte und die Eingabe von alles, wie ich gehe entlang statt nur kopieren einfügen. Ich habe jetzt jedoch in einen Fehler bei der "Detect Swings und BOS" Abschnitt des Codes laufen, beim Kompilieren erhalte ich eine "undeclared Bezeichner" Fehler für die "FindBreakTime" Zeile des Codes. Wie haben Sie es mit "FindBreakTime" zum Funktionieren gebracht, da es so aussieht, als ob ich lieber etwas wie "SymbolInfoSessionsTrade" verwenden sollte?
Ich danke Ihnen.