
Entwicklung des Price Action Analysis Toolkit (Teil 27): Liquidity Sweep With MA Filter Tool
Inhalt
Einführung
Die großen institutionellen Händler beeinflussen die Märkte nicht zufällig. Ihre Strategie besteht häufig darin, die Kurse über bekannte Unterstützungs- oder Widerstandsniveaus hinaus zu treiben und absichtlich kleinere Aufträge wie Stop-Losses und schwebende Aufträge auszulösen. Diese kurze Bewegung, die als Liquidity Sweep bezeichnet wird, ermöglicht es ihnen, große Positionen zu günstigeren Preisen zu erwerben oder zu veräußern, ohne der Marktrichtung unmittelbar entgegenzuwirken.
Für viele Kleinanleger können sich diese Bewegungen wie trügerische Fallen anfühlen. Der Kurs kann unter ein bekanntes Tief fallen und Stop-Loss-Kurse auslösen, um dann schnell wieder zu steigen. Umgekehrt könnte er über den Widerstand ausbrechen, was Stopps auslösen würde, und dann eine scharfe Gegenbewegung einleiten. Sobald die großen Akteure diesen Liquiditätszufluss absorbiert haben, nimmt der Markt häufig seinen vorherigen Trend mit neuer Stärke wieder auf. Wenn Händler diese Liquidity Sweep frühzeitig erkennen, können sie vermeiden, vorzeitig ausgestoppt zu werden, und sich stattdessen so positionieren, dass sie dem institutionellen Fluss folgen können, anstatt auf ihre vorübergehenden Erschütterungen zu reagieren.
In diesem Artikel werden wir einen MQL5 Expert Advisor entwickeln, der dazu dient, einen Liquidity Sweep zu erkennen, während er sich entwickelt. Der EA beginnt mit der Analyse von Kerzen, die unterhalb oder oberhalb früherer Umkehrpunkte ausbrechen und dann wieder innerhalb der Spanne schließen, was auf eine mögliche Liquiditätsabsorption hindeutet. Es enthält optionale Filter, wie z. B. Farbänderungen der Kerzen oder Bestätigungen des gleitenden Durchschnitts, um sicherzustellen, dass die Signale mit Ihrer Marktausrichtung übereinstimmen. Wenn ein gültiges Muster erkannt wird, markiert es der EA visuell auf dem Chart mit Pfeilen oder Kennzeichnungen und generiert einen Alarm.
Am Ende des Tutorials verfügen Sie über einen umfassenden, klaren und unkomplizierten Expert Advisor, der einen Liquidity Sweep sofort aufzeigt. Sie erfahren, wie der EA diese spezifischen Marktmuster erkennt, wie er Filter einsetzt, um Fehlsignale zu reduzieren, und wie er Ihnen hilft, sich an den Aktionen des Smart Money zu orientieren. Mit diesem Werkzeug in Ihrem Arsenal können Sie institutionelle Bewegungen antizipieren, bevor sie Ihren Stop-Loss einfangen, was Ihnen in jedem Handelsumfeld einen strategischen Vorteil verschafft.
Die Strategie verstehen
Ein Liquidity Sweep kann auf zwei Arten entstehen, entweder als Aufwärts-Sweep (ein falscher Durchbruch unter die Unterstützung) oder als Abwärts-Sweep (ein falscher Durchbruch über den Widerstand). Die zentrale Erkennungslogik des EA befindet sich in der Funktion DetectLiquiditySweep. Nachfolgend finden Sie eine schrittweise Aufschlüsselung der Unterscheidung zwischen den beiden, gefolgt von dem genauen MQL5-Codeausschnitt, der die booleschen Prüfungen durchführt.
Sobald eine neue Kerze geschlossen wird, ruft der EA
DetectLiquiditySweep(1);
Das Bestehen von shift=1 bedeutet:
- Index 1 (Shift) bezieht sich auf den gerade geschlossenen Balken (die „aktuelle“ Kerze, die wir testen wollen).
- Index 2 (Shift + 1) bezieht sich auf den Balken direkt davor (die „vorherige“ Kerze).
- Innerhalb von DetectLiquiditySweep erfassen diese Linien Open/High/Low/Close für beide Balken:
double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
- (o, h, l, c) sind der Eröffnungswert, der Höchstwert, der Tiefstwert und der Schlusswert des neuen Balkens.
- (o1, h1, l1, c1) sind die gleichen Werte für den unmittelbar vorangegangenen Balken.
Der EA erlaubt zwei leicht unterschiedliche Definitionen, die über den Eingang SignalStrict gesteuert werden:
1. LessStrict (Standard)
Aufwärts-Sweep:
- Der neue Balken muss höher schließen als er eröffnet wurde: c > o.
- Sein Tiefstwert muss unter den Tiefstwert des vorherigen Balkens fallen: l < l1.
- Sein Schlusskurs muss ebenfalls über dem Eröffnungswert des vorherigen Balkens liegen: c > o1. (Dadurch wird verhindert, dass ein reiner Doji oder eine winzige falsche Kerze den Ausschlag gibt).
- Der vorherige Balken war kein Doji: c1 != o1.
- Der neue Balken muss tiefer schließen als er eröffnet wurde: c < o.
- Sein Hoch muss über dem Hoch des vorherigen Balkens liegen: h > h1.
- Der Schlusskurs muss ebenfalls unter dem Eröffnungswert des vorherigen Balkens liegen: c < o1.
- Der vorherige Balken war kein Doji: c1 != o1.
2. Strict
Aufwärts-Sweep:
Genau dasselbe wie LessStrict, außer dass Schritt 2 von „low < previous low“ dahingehend verschärft wird, dass auch der Schlusskurs höher als der vorherige Höchstwert sein muss (c > h1). Mit anderen Worten: Der neue Balken muss unter das vorherige Hoch eintauchen und dieses auch wieder überholen, bevor er schließt.
Abwärts-Sweep:
Ebenso muss der neue Balken unter dem vorherigen Tief (c < l1) schließen, nachdem er über das vorherige Hoch (h > h1) notiert hat, anstatt nur über dem vorherigen Hoch zu liegen.
Diese booleschen Tests befinden sich in dem folgenden Codeblock
//--- Liquidity sweep logic (LessStrict vs Strict) bool bullSweep, bearSweep; if (SignalStrict == LessStrict) { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > o1 && // 3) Close above previous open c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < o1 && // 3) Close below previous open c1 != o1); // 4) Previous bar was not a doji } else // Strict { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > h1 && // 3) Close above previous high (stricter) c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < l1 && // 3) Close below previous low (stricter) c1 != o1); // 4) Previous bar was not a doji }
- Im LessStrict-Modus ist für Schritt 3 nur c > o1 (für aufwärts) oder c < o1 (für abwärts) erforderlich.
- Im strikten Modus ändert sich Schritt 3 in c > h1 (aufwärts) oder c < l1 (abwärts).
Wenn der Nutzer ColorChangeOnly = true gesetzt hat, verlangt der EA, dass der neue Balken die entgegengesetzte Farbe des vorherigen Balkens hat. Konkret:
bool bullCC = (c > o && c1 < o1); // New bar bullish, old bar bearish bool bearCC = (c < o && c1 > o1); // New bar bearish, old bar bullish if (ColorChangeOnly) { bullSweep &= bullCC; // Only a bullish sweep if also a bull‐after‐bear color flip bearSweep &= bearCC; // Only a bearish sweep if also a bear‐after‐bull color flip }
Wenn ColorChangeOnly falsch ist, haben diese beiden Linien keine Auswirkung und sowohl bullSweep als auch bearSweep bleiben so, wie sie durch die vorherigen Preistests bestimmt wurden.
Sobald bullSweep oder bearSweep wahr ist (nach den Preis- und optionalen Farbtests), kann der EA auf der Grundlage eines gleitenden Durchschnitts weiter filtern. Dies wird durch UseMAFilter und PriceAboveMA gesteuert. Im Grunde genommen:
- Berechne einen einzelnen MA-Wert bei Indexverschiebung (der gerade geschlossene Balken), entweder über das integrierte Handle für iMA() (SMA, EMA, LWMA oder RMA) oder eine nutzerdefinierte Funktion (CalcVWMA oder CalcHMA).
bool cond = PriceAboveMA ? (c > maValue) : (c < maValue);
- Wenn PriceAboveMA == true ist, behält der EA den bullSweep nur bei, wenn c > maValue ist und setzt zwangsweise bearSweep = false.
- Wenn PriceAboveMA == false ist, behält der EA den bearSweep nur bei, wenn c < maValue ist und setzt zwangsweise bullSweep = false.
if (UseMAFilter) { double maValue = 0.0; if (MAType == VWMA) maValue = CalcVWMA(shift); else if (MAType == HMA) maValue = CalcHMA(shift); else { double buf[]; if (CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // not enough MA data yet maValue = buf[0]; } // Keep only bullish sweeps if price > MA, or only bearish if price < MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
Zusammenfassung
Aufwärts-Sweep:
- Die Kerze schließt höher als sie eröffnet hat, fällt unter das vorherige Tief und schließt über der vorherigen Eröffnung (LessStrict) oder dem vorherigen Hoch (Strict).
- Gegebenenfalls muss es sich um einen Farbwechsel zwischen Auf- und Abwärts handeln.
- Optional muss der Schlusskurs oberhalb des gleitenden Durchschnitts liegen.
- Wenn alle Tests erfolgreich sind, platziert der EA einen grünen Aufwärtspfeil (OBJ_ARROW_UP) einige Punkte unter dem Tief dieser Kerze.
Abb. 1. Aufwärts-Sweep
Abwärts-Sweep:
- Die Kerze schließt tiefer, als sie eröffnet hat, über dem vorherigen Hoch und schließt unter der vorherigen Eröffnung (LessStrict) oder dem vorherigen Tief (Strict).
- Optional muss es sich um einen Abwärts-Farbwechsel nach einem Aufwärts-Farbwechsel handeln.
- Optional muss der Schlusskurs unter dem gleitenden Durchschnitt liegen.
- Wenn alle Prüfungen erfolgreich sind, platziert der EA einen roten Abwärtspfeil (OBJ_ARROW_DOWN) einige Punkte über dem Hoch dieser Kerze.
Abb. 2. Abwärts-Sweep
Code-Aufschlüsselung
Der EA „Liquidity Sweep with MA filter“ wurde entwickelt, um falsche Ausbrüche - oft als Liquidity Sweeps bezeichnet - auf jedem Chart im MetaTrader 5 zu identifizieren und visuell zu markieren. Im Kern achtet der EA auf Balken, die sich unter (oder über) den Umkehrpunkt Tief (oder Hoch) des vorherigen Balkens schieben und dann in einer Weise schließen, die auf gebundene Aufträge schließen lässt. Durch die Kombination dieser Erkennungslogik mit einem optionalen Filter für gleitende Durchschnitte können Händler die Signale verfeinern, um sie an den breiteren Trendkontext anzupassen. In den folgenden Abschnitten werden wir jede Hauptkomponente des EA durchgehen und in einer zusammenhängenden, schrittweisen Erzählung erklären, was jeder Abschnitt tut und warum er wichtig ist.
Zu Beginn gibt die EA-Datei ihre Metadaten an - Titel, Copyright, Autorenlink und Version - unmittelbar gefolgt von #property strict. Diese Linien sind nicht nur dekorativ: Sie dienen zwei Zwecken. Erstens identifizieren sie das Skript für jeden, der mehrere EAs durchsucht, und machen deutlich, wer der Autor ist und wo man mehr von seiner Arbeit findet. Zweitens erzwingt der Compiler durch die Angabe von #property strict eine strengere Syntax- und Typüberprüfung, wodurch häufige Fehler (wie nicht deklarierte Variablen oder Typinkongruenzen) erkannt werden, bevor der Code überhaupt ausgeführt wird. Diese Praxis klarer Metadaten und strenger Kompilierung spiegelt einen professionellen Standard wider, der sowohl Wartbarkeit als auch Zuverlässigkeit gewährleistet.
//+------------------------------------------------------------------+ //| Liquidity Sweep with MA filter| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/en/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict
Als Nächstes enthält der EA die Bibliothek Trade.mqh. Auch wenn diese spezielle Version nicht automatisch echte Handelsgeschäfte platziert, ist die Einbindung von <Trade\Trade.mqh> zukunftsweisend: Sie macht die leistungsstarke Klasse CTrade für alle zukünftigen Erweiterungen verfügbar, die Positionen öffnen, ändern oder schließen könnten. In einem Tutorial-Kontext signalisiert es den Lesern auch, dass sich dieser EA leicht zu einem vollautomatischen Strategieausführer entwickeln könnte - einfach durch den Aufruf von trade.Buy(...) oder trade.Sell(...) innerhalb der Erkennungslogik. Indem der Autor diese Einbeziehung von vornherein aufzeigt, regt er den Leser dazu an, sowohl über die Analyse als auch über die Ausführung nachzudenken.
#include <Trade\Trade.mqh>
Nach der Einbindung der Bibliothek definiert der EA eine Reihe von Eingabeparametern, die im Eingabedialog von MetaTrader sichtbar werden, wenn der EA mit einem Chart verbunden ist. Diese Eingänge lassen sich in drei logische Gruppen einteilen. In der ersten Gruppe, den Einstellungen für den gleitenden Durchschnitt, kann der Nutzer einen MA-Filter ein- oder ausschalten (UseMAFilter), auswählen, ob dieser MA im Chart angezeigt werden soll (ShowMA), die MA-Periode auswählen (MALength) und zwischen sechs MA-Typen wählen (SMA, EMA, LWMA, VWMA, RMA oder HMA). Die boolsche Variable PriceAboveMA legt dann fest, ob der EA verlangt, dass der Kurs über (für Aufwärts-Sweeps) oder unter (für Abwärts-Sweeps) dem MA liegt.
In der Praxis stellt dieser Filter sicher, dass die Händler nur Liquidity Sweep in Richtung des vorherrschenden Trends vornehmen und so die Zahl der Fehlalarme reduziert wird, wenn der Markt gegen sie gerichtet ist.
//--- Inputs: Moving Average Filter input bool UseMAFilter = false; // Enable Moving Average Filter input bool ShowMA = false; // Show MA on chart input int MALength = 20; // MA period (must be >=1) enum MA_Type {SMA=0, EMA, LWMA, VWMA, RMA, HMA}; input MA_Type MAType = SMA; // Moving Average type input bool PriceAboveMA = true; // Filter: price above MA?
Die zweite Gruppe von Eingaben steuert, wie rigide der EA einen Sweep definiert. Eine Enumeration SignalStrict bietet zwei Modi: „LessStrict“ verlangt nur, dass das Tief des neuen Balkens (bei Aufwärtssignalen) unter dem vorherigen Tief liegt (und umgekehrt bei Aufwärtssignalen), während „Strict“ einen zusätzlichen Bruch des Hochs (oder Tiefs) des vorherigen Balkens verlangt. Durch das Aufdecken dieses Flags lernen die Leser, wie man die Empfindlichkeit kalibriert: Einige bevorzugen eine lockere Interpretation, die mehr Signale erzeugt, während andere einen konservativeren Ansatz bevorzugen, der auf einen klaren Verstoß wartet.
Ein zusätzlicher boolescher Wert, ColorChangeOnly, verschärft die Bedingungen weiter, indem er darauf besteht, dass die Farbe des neuen Balkens (auf- vs. abwärts) der Farbe des vorangegangenen Balkens entgegengesetzt ist. In vielen Handelsphilosophien signalisiert ein Farbwechsel eine Verschiebung im Momentum, sodass diese Option für diejenigen interessant ist, die nur Umkehrungen markieren möchten, die mit einem sichtbaren Kerzenwechsel zusammenfallen.
//--- Inputs: Sweep Definition Strictness enum Strictness {LessStrict=0, Strict}; input Strictness SignalStrict = LessStrict; // Signal strictness input bool ColorChangeOnly = false; // Only on color-change candles
Die dritte Gruppe betrifft visuelle Elemente auf dem Chart . Die Enumeration LabelType erlaubt keine Bezeichnung, eine kurze, aus zwei Buchstaben bestehende Bezeichnung (“BS“ für Bull Sweep, „SS“ für Sell Sweep) oder eine Volltextbezeichnung (“Bull Sweep“ oder „Bear Sweep“). Parallel dazu können Händler mit PlotArrow einen Pfeil anstelle von Text (oder zusätzlich zu diesem) zeichnen. Um Unordnung zu vermeiden und sicherzustellen, dass sich die Kennzeichnung nicht mit den Kerzen überschneiden, verschiebt die Eingabe ArrowOffsetPoints jedes Pfeil- oder Textobjekt um eine bestimmte Anzahl von Punkten über, für Abwärts-, oder unter, für Aufwärtskerzen.
Schließlich bieten zwei Eingaben für Farben - BullishColor und BearishColor - die vollständige Kontrolle über den Farbton dieser Marker, sodass der Nutzer sie an sein Chart-Darstellung anpassen kann. Durch die Trennung der Anzeigeoptionen von der Erkennungslogik bleibt der EA flexibel: Händler können ihn je nach ihren persönlichen Vorlieben oder den Anforderungen an die Systemleistung im „Silent Alert“-Modus (keine Chart-Objekte) oder im vollständigen „Chart Annotation“-Modus konfigurieren.
//--- Inputs: Label/Arrow & Color Customization enum LabelType {None=0, Short, Full}; input LabelType LblType = Full; // Label type (None/Short/Full) input bool PlotArrow = true; // Draw arrow on signal input int ArrowOffsetPoints = 10; // Offset (in points) above/below candles input color BullishColor = clrLime; // Color for bullish signals input color BearishColor = clrRed; // Color for bearish signals
Unmittelbar nach dem Eingabeabschnitt deklariert der EA zwei globale Variablen. Der erste, datetime lastBarTime, speichert den Zeitstempel des zuletzt verarbeiteten Balkens. Diese Variable ist von zentraler Bedeutung: Bei jedem Tick vergleicht der EA die Startzeit des aktuellen Balkens (iTime(Symbol(),Period(),0)) mit lastBarTime. Wenn sie sich unterscheiden, bedeutet dies, dass sich eine neue Kerze gebildet hat, und erst dann führt der EA seine Erkennungsroutine aus. Ohne diese Prüfung würde die Logik bei jedem Tick eines sich bildenden Balkens ablaufen, was zu mehreren Signalen auf einer Kerze führen würde und was allgemein als „Repainting“ bezeichnet wird.
Die zweite globale Variable, int MAHandle, enthält den Indikator-Handle, der von iMA(...) zurückgegeben wird, wenn ein eingebauter MA erstellt wird. Durch die Initialisierung auf INVALID_HANDLE kann der Code später testen, ob ein gültiges Handle tatsächlich existiert, bevor er versucht, von diesem zu lesen.
//--- Globals datetime lastBarTime = 0; // Timestamp of the last processed bar int MAHandle = INVALID_HANDLE; // Handle for built‐in MA indicator
Die Funktion OnInit() ist die Setup-Routine des EA. Wenn der EA gestartet wird oder wenn jemand eine Eingabe ändert, ruft die Plattform OnInit() auf. Die erste Aufgabe besteht darin, MALength zu validieren. Da MQL5 jede Eingabe nach der Kompilierung als Konstante behandelt, können wir nicht einfach einen sicheren Wert neu zuweisen; stattdessen prüft der Code if(MALength < 1), gibt eine Fehlermeldung aus und gibt INIT_FAILED zurück, wenn der Nutzer einen ungültigen Punkt angegeben hat. Diese defensive Programmierung stellt sicher, dass der EA niemals versucht, einen MA über Null- oder negative Balken zu berechnen, was andernfalls Laufzeitfehler oder unsinnige Ergebnisse verursachen würde. Als Nächstes wird lastBarTime auf die Startzeit des aktuellen Balkens gesetzt, damit die Tick-Handler-Logik nicht sofort bei dem Balken ausgelöst wird, bei dem sie angehängt wurde.
int OnInit() { // Validate MALength (cannot assign to an input directly) if(MALength < 1) { Print("ERROR: MALength must be at least 1. Current value = ", MALength); return(INIT_FAILED); } // Initialize timing so we only run when a new bar closes lastBarTime = iTime(Symbol(), Period(), 0); // … (rest of OnInit follows) … return(INIT_SUCCEEDED); }
Im weiteren Verlauf von OnInit() bestimmt der EA, ob ein integriertes MA-Handle verwendet werden muss. Wenn der Nutzer einen der vier Standard-MA-Typen (SMA, EMA, LWMA oder RMA) ausgewählt und entweder UseMAFilter oder ShowMA aktiviert hat, ruft der Code iMA(Symbol(), Period(), MALength, 0, (ENUM_MA_METHOD)MAType, PRICE_CLOSE) auf, um ein Indikator-Handle zu erstellen. Wenn dieser Aufruf fehlschlägt, gibt der EA einen Diagnosefehler aus und bricht die Initialisierung ab. Bei Erfolg wird dann geprüft, ob (ShowMA == true) und, falls ja, ChartIndicatorAdd(0, 0, MAHandle) aufgerufen, um den MA auf dem Chart darzustellen. Am Ende der Initialisierung bestätigt eine Meldung - „Liquidity Sweep EA erfolgreich initialisiert“ - dass alle Voraussetzungen (gültiger Zeitraum, MA-Handle) gegeben sind und der EA bereit ist, zu laufen.
// Create MA handle if using a built-in MA (SMA, EMA, LWMA, RMA) if((MAType != VWMA && MAType != HMA) && (UseMAFilter || ShowMA)) { ENUM_MA_METHOD method = (ENUM_MA_METHOD)MAType; MAHandle = iMA(Symbol(), Period(), MALength, 0, method, PRICE_CLOSE); if(MAHandle == INVALID_HANDLE) { Print("Failed to create MA handle (type=", EnumToString(MAType), ", length=", MALength, ")"); return(INIT_FAILED); } if(ShowMA) ChartIndicatorAdd(0, 0, MAHandle); }
Wenn der Nutzer den EA entfernt oder den MetaTrader schließt, wird die Funktion OnDeinit() ausgeführt. Seine einzige Aufgabe ist das Housekeeping: Wenn MAHandle gesetzt wurde (d.h. nicht INVALID_HANDLE), ruft er IndicatorRelease(MAHandle) auf, um den Speicher freizugeben und alle verbleibenden Referenzen zu entfernen. Auf diese Weise vermeidet der EA Ressourcenlecks, eine wichtige Best Practice, wenn Indikator-Handles zur Laufzeit erstellt werden. Auch wenn der moderne MetaTrader einen Teil davon automatisch verwaltet, verhindert das explizite Freigeben von Handles die Aufblähung des Charts bei lang laufenden Sitzungen oder wenn der EA zur Parameteroptimierung häufig neu verbunden wird.
void OnDeinit(const int reason) { if(MAHandle != INVALID_HANDLE) IndicatorRelease(MAHandle); }
Die Funktion OnTick() ist der Herzschlag des EA, der MetaTrader ruft sie bei jedem eingehenden Preis-Tick auf. Da wir jedoch nur einmal pro abgeschlossener Kerze auf Liquidity Sweep prüfen wollen, ruft OnTick() zunächst die Zeit des letzten Balkens über iTime(Symbol(),Period(),0) ab und vergleicht sie mit lastBarTime. Wenn sie übereinstimmen, bedeutet dies, dass sich der EA immer noch innerhalb desselben Balkens befindet, sodass nichts passiert.
Nur wenn eine neue Kerze auftaucht (d.h. iTime(...) != lastBarTime), ruft der Code DetectLiquiditySweep(1) auf, wobei eine Verschiebung von 1 übergeben wird, um die soeben geschlossene Kerze mit ihrer Vorgängerin zu vergleichen. Unmittelbar danach wird lastBarTime auf den neuen Wert aktualisiert, um sicherzustellen, dass der EA erst nach dem Schließen des nächsten Balkens wieder erkannt wird. Dieser disziplinierte Ansatz garantiert ein - und nur ein - Signal pro Balken, eliminiert Rauschen und verhindert mehrere Alarme auf einer einzigen Kerze.
void OnTick() { // Retrieve the current bar’s start time datetime current = iTime(Symbol(), Period(), 0); // If the bar start time changed, call the detection routine once if(current != lastBarTime) { DetectLiquiditySweep(1); lastBarTime = current; } }
Das Arbeitspferd, DetectLiquiditySweep(int shift), implementiert die Logik, die diesen EA von einfacheren Mustermarkern unterscheidet. Erstens wird sichergestellt, dass genügend historische Daten für die Berechnung von nutzerdefinierten, gleitenden Durchschnitten vorhanden sind. Durch die Berechnung von requiredBars = shift + MALength und die Überprüfung von if(Bars(Symbol(),Period()) <= requiredBars) return; verhindert der Code, dass die Indizierung außerhalb des Bereichs erfolgt. Wenn z. B. nur 15 Balken auf dem Chart vorhanden sind, der Nutzer aber MALength=20 eingestellt hat, wird der EA keine MA-Berechnung oder Musterprüfung versuchen, da ihm eine ausreichende Historie fehlt. Diese Schutzklausel demonstriert eine sorgfältige Fehlervermeidung, die für robuste EAs unerlässlich ist.
void DetectLiquiditySweep(int shift) { // Ensure there are at least (shift + MALength) bars of history int requiredBars = shift + MALength; if(Bars(Symbol(), Period()) <= requiredBars) { // Not enough bars to compute custom MA or compare prices return; } // … (next steps in the function) … }
Sobald die historische Prüfung bestanden ist, liest DetectLiquiditySweep acht Kurswerte - Open, High, Low und Close - sowohl für den „aktuellen“ abgeschlossenen Balken (Index = Shift) als auch für den „vorherigen“ Balken (Index = Shift + 1). Diese Werte werden in acht separaten Werten vom Typ double gespeichert: o, c, h, l für den aktuellen Balken und o1, c1, h1, l1 für den vorherigen Balken. Anhand dieser Daten kann die EA beurteilen, ob ein Liquidity Sweep stattgefunden hat.
Wichtig ist, dass der Code auch zwei boolesche Flags - bullCC (Aufwärts-Farbwechsel) und bearCC (Abwärts-Farbwechsel) - berechnet, indem er einfach prüft, ob der Schlusskurs des aktuellen Balkens über dem Eröffnungswert liegt, während der Schlusskurs des vorherigen Balkens unter dem Eröffnungswert lag, und umgekehrt. Diese Flags kommen später ins Spiel, wenn ColorChangeOnly aktiviert ist, um sicherzustellen, dass der EA nur Sweeps markiert, die mit einer Umkehrung der Kerzenfarbe zusammenfallen.
//--- Bar data for the current completed candle (index = shift) double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); //--- Bar data for the prior candle (index = shift + 1) double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
Im nächsten Abschnitt wird die Variante „LessStrict“ umgesetzt. Suchdefinitionen „Strict“. Wenn der Nutzer LessStrict ausgewählt hat, wird ein Aufwärts-Sweep signalisiert, wenn der aktuelle Balken über seiner eigenen Eröffnung schließt (c > o), sein Tief unter den Tief des vorherigen Balkens fällt (l < l1), er über der Eröffnung des vorherigen Balkens schließt (c > o1) und der vorherige Balken kein Doji war (c1 != o1). Ein Abwärts-Sweep ist symmetrisch: aktueller Schlusskurs < aktuelle Eröffnung, aktueller Höchststand > vorheriger Höchststand, aktueller Schlusskurs < vorherige Eröffnung und vorheriger Balken ist kein Doji.
Wenn der Nutzer stattdessen Strict wählt, verschärft der EA die Bedingungen, indem er verlangt, dass der aktuelle Schlusskurs über das Hoch des vorherigen Balkens (für aufwärts) oder unter das Tief des vorherigen Balkens (für abwärts) bricht. Diese Strict-Variante reduziert „falsche“ Whistleblower-Signale bei flachen Rücksetzern und erzwingt eine definitivere Verletzung von Unterstützung oder Widerstand. Indem er beide Modi offenlegt, zeigt der EA dem Leser, wie kleine Änderungen in der Logik die Signalfrequenz und -qualität erheblich verändern können.
// Compute color-change flags bool bullCC = (c > o && c1 < o1); bool bearCC = (c < o && c1 > o1); // Liquidity sweep flags (LessStrict or Strict) bool bullSweep, bearSweep; if(SignalStrict == LessStrict) { bullSweep = (c > o && l < l1 && c > o1 && c1 != o1); bearSweep = (c < o && h > h1 && c < o1 && c1 != o1); } else // Strict { bullSweep = (c > o && l < l1 && c > h1 && c1 != o1); bearSweep = (c < o && h > h1 && c < l1 && c1 != o1); }
Nach der Überprüfung der Rohdaten wendet der EA optional den Farbänderungsfilter an. Wenn ColorChangeOnly true ist, wird bullSweep &= bullCC; bearSweep &= bearCC; ausgeführt. In der Tat wird jeder Sweep, der nicht mit einem Farbwechsel der Kerzen zusammenfällt, sofort verworfen. Viele Händler betrachten einen Farbwechsel (z. B. eine rote Kerze, die auf eine grüne Kerze folgt) als Bestätigung für ein Umkehrmomentum. Der EA verringert so die Chance, einen Sweep zu markieren, der keine sichtbares Abprallen aufweist. Diese einfache bitweise UND-Verknüpfung verbindet auf elegante Weise zwei komplementäre Signale - Preisstruktur und Kerzenpsychologie - in einem einzigen Filter.
// If only color-change sweeps are desired, AND‐combine with the raw sweep flags if(ColorChangeOnly) { bullSweep &= bullCC; // must also be a bullish color reversal bearSweep &= bearCC; // must also be a bearish color reversal }
Mit den rohen Sweep-Bedingungen und dem optionalen Farbfilter untersucht der EA dann den Moving Average-Filter, wenn UseMAFilter wahr ist. Er deklariert double maValue = 0.0; und fährt fort, den MA auf eine der drei Arten zu berechnen. Wenn der Nutzer „VWMA“ ausgewählt hat, wird das nutzerdefinierte Hilfsmittel CalcVWMA(shift) aufgerufen. Wenn „HMA“ gewählt wurde, wird CalcHMA(shift) aufgerufen. Andernfalls wird ein integrierter MA-Typ (SMA, EMA, LWMA oder RMA) angenommen und CopyBuffer(MAHandle, 0, shift, 1, buf) ausgeführt, um einen MA-Wert abzurufen. Wenn CopyBuffer nicht genau ein Ergebnis liefert, kehrt der EA einfach zurück und überspringt alle weitere Berechnungen für diesen Balken.
Sobald maValue bekannt ist, testet eine boolesche Bedingung PriceAboveMA? (c > maValue) : (c < maValue). Wenn PriceAboveMA true ist, behält der EA nur Aufwärts-Sweeps bei, die über dem MA schließen (und verhindert Abwärts-Sweeps, indem er bearSweep = false setzt), während, wenn PriceAboveMA false ist, nur Abwärts- Sweeps überleben, die unter dem MA schließen. Dieser Filter stellt sicher, dass Liquidity Sweep im Kontext des Trends betrachtet werden: Aufwärtsgerichtete Ausschläge sind nur von Bedeutung, wenn der Kurs über dem MA liegt, und abwärtsgerichtete Ausschläge nur, wenn der Kurs darunter liegt.
if(UseMAFilter) { double maValue = 0.0; // Compute MA value at the same 'shift' if(MAType == VWMA) maValue = CalcVWMA(shift); else if(MAType == HMA) maValue = CalcHMA(shift); else { // Built‐in MA handle (SMA, EMA, LWMA, RMA) double buf[]; if(CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // no valid MA data available maValue = buf[0]; } // Only allow bullish sweeps above MA or bearish sweeps below MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
Nachdem alle Filter angewendet wurden, verwendet DetectLiquiditySweep zwei if-Blöcke, um if(bullSweep) und if(bearSweep) zu überprüfen. Wenn bullSweep wahr ist, wird DrawSignal(shift, true) aufgerufen, um eine Aufwärts-Markierung auf dem Chart zu platzieren, und eine Meldung wie „Bullish sweep detected at [time], price=[close]“ in das Expertenprotokoll geschrieben. Wenn bearSweep true ist, wird dasselbe mit DrawSignal(shift, false) und einem entsprechenden Ausdruck gemacht.
In beiden Fällen ruft der Code schließlich Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period())); auf, wenn einer der beiden Alerts ausgelöst wurde, wodurch eine Warnmeldung auf dem Bildschirm erscheint. Diese Trennung zwischen „Zeichnen auf dem Chart“ und „Senden eines Alarms“ ermöglicht es jedem Händler zu entscheiden, ob er nur akustische/Popup-Benachrichtigungen oder auch einen dauerhaften visuellen Hinweis auf dem Chart selbst wünscht.
// If a bullish sweep remains true after all filters, draw and log it if(bullSweep) { DrawSignal(shift, true); PrintFormat("Bullish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // If a bearish sweep remains true after all filters, draw and log it if(bearSweep) { DrawSignal(shift, false); PrintFormat("Bearish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // In either case, fire a pop‐up alert if(bullSweep || bearSweep) { Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period())); }
Die Funktion DrawSignal(int shift, bool bullish) ist ausschließlich für die Platzierung von Chart-Objekten zuständig. Er liest zunächst den Zeitstempel des Balkens t = iTime(Symbol(),Period(),shift) und berechnet einen Anzeigepreis, indem er entweder das Tief minus ArrowOffsetPoints * _Point (für einen Aufwärts-Pfeil) oder das Hoch plus denselben Offset (für einen Abwärts-Pfeil) nimmt. Durch die Wahl von _Point wird sichergestellt, dass der Offset in tatsächlichen Kursinkrementen gemessen wird, unabhängig davon, ob es sich bei dem Instrument z. B. um EURUSD (0,0001 Inkremente) oder USDJPY (0,01 Inkremente) handelt. Anschließend wird ein eindeutiger Objektname mittels StringFormat("LS_%I64u", (long)t) zusammengestellt. Da es sich bei (long)t um eine 64-Bit-Ganzzahl handelt, die den genauen Öffnungszeitpunkt des Balkens widerspiegelt, gibt es nie zwei Balken mit demselben Objektnamen.
Bevor irgendetwas gezeichnet wird, ruft es ObjectFind(0, name) auf und löscht alle vorher existierenden Objekte mit diesem Namen - so wird ein Durcheinander vermieden, wenn der EA wieder angehängt wird oder wenn eine Aktualisierung des s die gleiche Erkennung wiederholt. Wenn PlotArrow true ist, zeichnet ObjectCreate(...) einen farbigen Pfeil (für aufwärts oder abwärts) am berechneten Preis. Wenn PlotArrow falsch ist, aber LblType nicht None ist, zeichnet die Funktion stattdessen eine Kurz- oder Volltextbeschriftung (z. B. „BS“ vs. „Bull Sweep“). Durch die Isolierung des gesamten Chart-Objekt-Codes in einer Routine hält der EA seine Erkennungslogik von den visuellen Anmerkungen getrennt, was ein Markenzeichen für sauberes, modulares Design ist.
void DrawSignal(int shift, bool bullish) { // Get the exact bar start time for this sweep datetime t = iTime(Symbol(), Period(), shift); // Determine the on‐chart Y‐coordinate: offset a few points above/below the candle double price = bullish ? (iLow(Symbol(), Period(), shift) - ArrowOffsetPoints * _Point) : (iHigh(Symbol(), Period(), shift) + ArrowOffsetPoints * _Point); // Compose a unique name using the 64-bit timestamp string name = StringFormat("LS_%I64u", (long)t); // If an object with that name already exists, delete it first if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); // (Next lines will choose arrow vs. text drawing) }
Unter der Haube befinden sich zwei Hilfsfunktionen, die nicht standardmäßige gleitende Durchschnitte berechnen. CalcVWMA(int shift) implementiert einen typischen volumengewichteten gleitenden Durchschnitt. Sie initialisiert zwei Aggregatvariablen - Zähler und Nenner - auf 0,0. Dann wird für jeden Balkenindex von shift bis shift + MALength - 1 der Schlusskurs price = iClose(...) und das Tick-Volumen vol = iVolume(...) (gespeichert als 64-bit long) gelesen. Indem vol bei der Berechnung von Zähler += Preis * (double)vol und Nenner += (double)vol explizit in double umgewandelt wird, vermeidet der Code Warnungen über eine versehentliche Umwandlung von ganzen in reelle Zahlen.
Nach Beendigung der Schleife wird Zähler / Nenner zurückgegeben, wenn der Nenner > 0,0 ist; andernfalls wird sicher 0,0 zurückgegeben, um eine Division durch Null zu vermeiden. Somit ist jeder VWMA-Punkt genau die Summe von (Preis × Volumen) über die letzten Balken der MALength, geteilt durch die Summe des Volumens - genau das, was Händler von einem volumengewichteten Durchschnitt erwarten.
double CalcVWMA(int shift) { double numerator = 0.0; double denominator = 0.0; // Loop over 'MALength' bars, starting at 'shift' for(int i = shift; i < shift + MALength; i++) { double price = iClose(Symbol(), Period(), i); long vol = iVolume(Symbol(), Period(), i); // Accumulate (price × volume) and sum of volume numerator += price * (double)vol; denominator += (double)vol; } // Return VWMA = sum(price×volume) / sum(volume), or 0 if volume = 0 return (denominator > 0.0) ? (numerator / denominator) : 0.0; }
CalcHMA(int shift) schließlich berechnet den gleitenden Hull-Durchschnitt, einen zweistufigen gewichteten Durchschnitt, der die Verzögerung verringern soll. Zunächst wird half = MALength / 2 gesetzt und eine Schleife von i = shift bis i < shift + half durchlaufen, wobei jedem Balken eine absteigende Gewichtung von half bis 1 zugewiesen wird. Er akkumuliert eine gewichtete Summe w1 und eine gewichtete Gesamtsumme sw1 und dividiert schließlich w1/sw1, um einen gewichteten MA für die halbe Periode zu erhalten. Zweitens wird eine Schleife von i = shift bis i < shift + MALength durchlaufen, wobei Gewichte von MALength bis hinunter zu 1 zugewiesen werden, w2 und sw2 akkumuliert werden und w2/sw2 berechnet wird, um einen gewichteten MA für eine ganze Periode zu erhalten.
Der endgültige HMA-Wert ist 2 * w1 - w2. Durch die Verdoppelung des MA der halben Periode und den Abzug des MA der ganzen Periode wird die Reaktionsfähigkeit des gleitenden Durchschnitts auf die jüngsten Preisänderungen verstärkt, was viele Händler für dynamische Märkte als vorteilhaft ansehen. Wie bei VWMA schützt der EA vor unzureichender Historie, indem er CalcHMA nur dann aufruft, wenn Bars(Symbol(),Period()) > shift + MALength.
//+------------------------------------------------------------------+ //| Compute Hull Moving Average (HMA) | //+------------------------------------------------------------------+ double CalcHMA(int shift) { int half = MALength / 2; double w1 = 0.0, sw1 = 0.0; double w2 = 0.0, sw2 = 0.0; // 1) Weighted MA over half period for(int i = shift; i < shift + half; i++) { double p = iClose(Symbol(), Period(), i); int weight = half - (i - shift); w1 += p * weight; sw1 += weight; } w1 = (sw1 > 0.0) ? (w1 / sw1) : 0.0; // 2) Weighted MA over full period for(int i = shift; i < shift + MALength; i++) { double p = iClose(Symbol(), Period(), i); int weight = MALength - (i - shift); w2 += p * weight; sw2 += weight; } w2 = (sw2 > 0.0) ? (w2 / sw2) : 0.0; // 3) Final HMA value = 2 * (MA over half) – (MA over full) return 2.0 * w1 - w2; }
Tests und Ergebnisse
Um die Leistung unseres Tools zu bewerten und zu verstehen, haben wir umfassende Tests durchgeführt. Dabei wurde das Tool mit historischen Daten im Rahmen von Backtests getestet und seine unmittelbare Leistung unter Live-Marktbedingungen bewertet. Im Folgenden werden die Ergebnisse dieser Tests veranschaulicht.
Abb. 3. Live-Test auf GBPUSD
Dieses Chart veranschaulicht eindrucksvoll die Fähigkeit des Tools, Liquidity Sweep zu erkennen. Es zeigt eine deutliche Abwärtsbewegung gefolgt von einer Aufwärtsbewegung, was auf das Potenzial des Tools hinweist, Marktumkehrungen und institutionelle Aktivitäten genau zu erkennen. Dieses Chart veranschaulicht eindrucksvoll die Fähigkeit des Tools, Liquidity Sweep zu erkennen. Es zeigt eine deutliche Abwärtsbewegung gefolgt von einer Aufwärtsbewegung, was auf das Potenzial des Tools hinweist, Marktumkehrungen und institutionelle Aktivitäten genau zu erkennen.
Abb. 4. Live-Tests Schritt Index
Das obige Ergebnis zeigt die Effektivität des Tools bei der Identifizierung von Liquidity Sweep und der Erfassung von wichtigen Marktumschwüngen mit klaren visuellen Markierungen, die bei der strategischen Entscheidungsfindung helfen können. Dieses Ergebnis zeigt die Wirksamkeit des Tools bei der Identifizierung von Liquidity Sweep und der Erfassung von wichtigen Marktumschwüngen mit klaren visuellen Markierungen, die bei der strategischen Entscheidungsfindung helfen können.
Abb. 5. Backtest mit EURUSD
Dieser Backtest zeigt, dass das Tool in der Lage ist, das Muster des Liquidity Sweep in historischen Marktdaten genau zu erkennen. Durch die Analyse vergangener Kursbewegungen hebt es die Fälle hervor, in denen der Kurs vorübergehend Unterstützungs- oder Widerstandsniveaus durchbrochen hat, bevor er sich wieder umkehrt, was auf potenzielle institutionelle Aktivitäten hinweist. Diese identifizierten Punkte können als wertvolle Anhaltspunkte für die Vorwegnahme künftiger Marktumkehrungen oder Trendfortsetzungen dienen. Das Chart bestätigt die Wirksamkeit des Erkennungsmechanismus und schafft Vertrauen in seine Anwendung für Live-Handelsszenarien.
Schlussfolgerung
Dieser EA identifiziert Liquidity Sweep sowohl in historischen Backtests als auch in Live-Simulationen und markiert jeden gültigen Ausschlag mit einem Pfeil genau dort, wo der Preis einen vorherigen hohen oder tiefen Umkehrpunkt durchstoßen und dann wieder erreicht hat. Über mehrere MA-Filtereinstellungen hinweg stimmten die Signale gut mit der nachfolgenden Trendfortsetzung überein, was eine hohe Zuverlässigkeit unter realen Marktbedingungen zeigt. Unabhängig davon, ob Sie die frühere Leistung des EA überprüfen oder ihn im Live-Betrieb beobachten, hat sich seine Fähigkeit, institutionelle „Stop Hunts“ abzufangen, stets bewährt. Nutzen Sie diese Erkenntnisse, um die Sweep-Erkennung in Ihre eigene Strategie zu integrieren, denn Sie wissen, dass der Code seine Genauigkeit und Reaktionsfähigkeit sowohl in Replay- als auch in Echtzeit-Umgebungen unter Beweis gestellt hat.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18379
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.