Automatisieren von Handelsstrategien in MQL5 (Teil 45): Inverse Fair Value Gap (IFVG)
Einführung
In unserem letzten Artikel (Teil 44) haben wir das Erkennungssystem des Change of Character (CHoCH) in MetaQuotes Language 5 (MQL5) entwickelt. Mit diesem System wurden die Balken gescannt, um Hochs und Tiefs für die Trendbestimmung zu identifizieren und zu kennzeichnen. Es löste Handelsgeschäfte aus bei Durchbrüchen, die Umkehrungen signalisierten. Das System unterstützte die Modi „pro Balken“ und „pro Tick“ sowie die Visualisierung mit Symbolen, Kennzeichnungen, Bruchlinien und dynamischen Schriftarten. In Teil 45 entwickeln wir das System der Inversen Fair-Value-Gap (IFVG).
Dieses System identifiziert steigende oder fallende Fair-Value-Gaps (FVGs) auf den letzten Balken und wendet einen Filter für die Mindestgröße der Lücken an. Es verfolgt die Zustände als normal, abgeschwächt oder invertiert auf der Grundlage von Preisinteraktionen. Eine Abschwächung (mitigation) erfolgt bei Durchbrüchen auf der Gegenseite, ein Rücksetzer beim Wiedereinstieg und eine Umkehr des Schlusskurses auf der Gegenseite hinaus von innen. Das System ignoriert Überschneidungen und begrenzt die verfolgten FVGs. Es unterstützt einen, begrenzte oder unbegrenzte Anzahl von Handelsgeschäften pro FVG. Es kauft bei steigenden IFVGs oder verkauft bei fallenden. Die Zonen werden als farbige Rechtecke mit Kernzeichnungen für Staat und Handel sowie mit Symbolen zur Schadensbegrenzung dargestellt. Wir werden die folgenden Themen behandeln:
- Das Verständnis des Systems des Inverse Fair-Value-Gaps (IFVG)
- Implementation in MQL5
- Backtests
- Schlussfolgerung
Am Ende werden Sie eine funktionierende MQL5-Strategie zum Erkennen und Handeln von IFVGs mit Zustandsverfolgung, adaptiver Grafik und konfigurierbaren Modi haben – fangen wir an!
Das Verständnis des Systems des Inverse Fair-Value-Gaps (IFVG)
Die Kurslücke Fair-Value-Gap (FVG) ist ein Preisaktionskonzept, das Ungleichgewichte oder Lücken zwischen Kerzen darstellt, bei denen der Kauf- oder Verkaufsdruck eine unausgefüllte Lücke geschaffen hat. Diese Lücken werden oft als „bullish“, also Aufwärts-FVGs“ (Tief der späteren Kerze über dem Hoch der früheren Kerze), oder „bearish“, also Abwärts-FVGs“ (Hoch der späteren Kerze unter dem Tief der früheren Kerze), angesehen und fungieren als potenzielle Unterstützungs-/Widerstandszonen, die der Preis wieder auffüllen kann. Eine inverses Fair-Value-Lücke (IFVG) tritt auf, wenn ein abgeschwächtes FVG (der Kurs hat die Gegenseite durchbrochen) zurückverfolgt und dann invertiert wird, indem der Kurs von innen über die Gegenseite hinaus schließt, was eine Umkehr signalisiert: ein abgeschwächtes Aufwärts-FVG, das sich in ein fallendes umkehrt (der Kurs schließt nach dem Wiedereinstieg unter dem Tief), oder ein abgeschwächtes Abwärts-FVG, das sich in ein steigendes umkehrt (schließt über dem Hoch). Entwicklungskontrolle der Zustände – normal (anfängliche Lücke), abgeschwächt (Durchbruch auf der Gegenseite), zurückgegangen (Wiedereinstieg nach Abschwächung), invertiert (Schließen über die Gegenseite hinaus nach der Rückkehr) –, wobei die Inversion das wichtigste Handelssignal ist.
Bei einem abgeschwächten Abwärts-FVG (ursprünglich eine Abwärtslücke) wird ein Aufwärts-IFVG ausgelöst, wenn der Kurs nach der Abschwächung wieder eintritt und über dem Hoch schließt, wobei der Kauf mit einem Stop-Loss unterhalb des Tiefs und einem Take-Profit bei festen Punkten erfolgt. Umgekehrt wird bei einem abgeschwächten Aufwärts-FVG (ursprünglicher aufsteigend) ein Abwärts-IFVG bei einem Schlusskurs unter dem Tiefpunkt mit einem Stop-Loss über dem Hoch verkauft. Sehen wir uns unten die verschiedenen Möglichkeiten an, die wir haben.

Unser Plan ist es, FVGs auf aktuellen Balken mit einem Mindestlückenfilter zu erkennen, historische FVGs bei der Initialisierung zu laden, Zustände (normal/gemildert/invertiert) basierend auf Preisinteraktionen zu verfolgen/aktualisieren, Überlappungen zu ignorieren, verfolgte FVGs mit Bereinigung von abgelaufenen zu begrenzen, Handelsumkehrungen mit Käufen bei Aufwärts-IFVGs (orig. abwärts, invertiert) oder Verkäufen bei Abwärts-IFVGs (orig. aufwärts, invertiert), feste Handelsniveaus, Handelsmodi/-zahlen pro FVG, Trailing Stops und Visualisierung farbiger Rechtecke (normal/abgeschwächt/invertierte Schatten) mit Zustands-/Handelsbeschriftungen und Abschwächungssymbolen. 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.
//+------------------------------------------------------------------+ //| FVG Inverse.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 #define FVG_Prefix "IFVG REC " //--- FVG prefix // Normal FVGs #define CLR_UP clrGreen // Green for normal up (Bullish FVG) #define CLR_DOWN clrRed // Red for normal down (Bearish FVG) // Mitigated FVGs #define CLR_MIT_UP clrPurple // Purple for mitigated up (Mitigated Bullish FVG) #define CLR_MIT_DOWN clrOrange // Orange for mitigated down (Mitigated Bearish FVG) // Inverted FVGs #define CLR_INV_UP clrRed // Red for inverted up (Bearish IFVG) #define CLR_INV_DOWN clrGreen // Green for inverted down (Bullish IFVG) //+------------------------------------------------------------------+ //| Enums | //+------------------------------------------------------------------+ enum TradeMode { // Define trade mode enum TradeOnce, // Trade Once LimitedTrades, // Limited Trades UnlimitedTrades // Unlimited Trades }; enum FVGState { // Define FVG state enum Normal, // Normal Mitigated, // Mitigated Inverted // Inverted }; enum TrailingTypeEnum { // Define enum for trailing stop types Trailing_None = 0, // None Trailing_Points = 2 // By Points }; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input group "EA GENERAL SETTINGS" input double inpLot = 0.01; // Lotsize input int sl_pts = 300; // Stop Loss Points input int tp_pts = 300; // Take Profit Points input int minPts = 100; // Minimum Gap Size in Points input int FVG_Rec_Ext_Bars = 30; // FVG Extension Bars input bool prt = true; // Print Statements input long magic_number = 123456789; // Magic Number input bool ignoreOverlaps = true; // Ignore new FVGs that overlap existing ones input TradeMode tradeMode = TradeOnce; // Mode for trading FVGs input int maxTradesPerFVG = 2; // Maximum trades per FVG for LimitedTrades input int maxFVGs = 50; // Maximum FVGs to track in array input TrailingTypeEnum TrailingType = Trailing_None; // Trailing Stop Type input double Trailing_Stop_Pips = 30.0; // Trailing Stop in Pips (for Points type) input double Min_Profit_To_Trail_Pips = 50.0; // Min Profit to Start Trailing in Pips
Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade/Trade.mqh>“ einbinden, die die Klasse CTrade für die Verwaltung von Aufträgen und Positionen bereitstellt. Wir deklarieren „obj_Trade“ als eine globale Instanz von „CTrade“, um alle Handelsoperationen abzuwickeln. Wir definieren eine String-Konstante „FVG_Prefix“ als „IFVG REC“ für die Benennung von FVG-Rechteckobjekten. Wir legen Farbkonstanten für verschiedene FVG-Zustände und Richtungen fest: „CLR_UP“ in grün für normale Aufwärts-FVGs, „CLR_DOWN“ in rot für normale Abwärts-FVGs, „CLR_MIT_UP“ in lila für abgemilderte Aufwärts-FVGs, „CLR_MIT_DOWN“ in orange für abgeschwächte Aufwärts-FVGs, „CLR_INV_UP“ in rot für invertierte Abwärts-FVGs (orig. Aufwärts) und „CLR_INV_DOWN“ in grün für eine invertierte Aufwärts-FVGs. Sie können diese in die von Ihnen gewünschten Farben umwandeln; es handelt sich lediglich um willkürliche Farben, die wir für unsere Grafik mit weißem Hintergrund verwendet haben.
Dann erstellen wir drei Enumerationen für die Konfiguration. Die Enumeration „TradeMode“ bietet „TradeOnce“ zur Begrenzung auf einen Handel pro FVG, „LimitedTrades“ für eine nutzerdefinierte Höchstgrenze pro FVG und „UnlimitedTrades“ ohne Limit pro FVG. Dies ist wichtig, wenn Sie mit mehreren Positionen handeln wollen. Die Enumeration „FVGState“ definiert „Normal“ für anfängliche Lücken, „Mitigated“ nach Durchbrüchen auf der Gegenseite und „Inverted“ bei Inversionssignalen. Die Enumeration „TrailingTypeEnum“ bietet „Trailing_None“ zur Deaktivierung des Trailing und „Trailing_Points“ für punktbasierte Trailing-Stops.
Wir gruppieren die Eingabeparameter unter „EA GENERAL SETTINGS“ für den Eigenschaftsdialog. Dazu gehören „inpLot“ für die Losgröße, „sl_pts“ und „tp_pts“ für Stop-Loss- und Take-Profit-Abstände in Punkten, „minPts“ als Mindestlückengröße, um als FVG zu gelten, „FVG_Rec_Ext_Bars“ für die Anzahl der Balken, um die FVG-Rechtecke nach rechts zu verlängern, „prt“, um die Druckprotokollierung einzuschalten, „magic_number“ für die Handelsidentifikation, „ignoreOverlaps“ um neue FVGs, die bestehende überlappen, zu überspringen (wir dachten, dies wäre extrem wichtig für die visuelle Klarheit, aber Sie können es ignorieren), „tradeMode“ für die Verwendung des Enums für Handelslimits, „maxTradesPerFVG“ für das Limit im limitierten Modus, „maxFVGs“ für die Begrenzung der verfolgten FVGs im Array, „TrailingType“ mit seinem Enum, „Trailing_Stop_Pips“ für den Trailing-Abstand und „Min_Profit_To_Trail_Pips“ für die Gewinnschwelle, bevor das Trailing beginnt. Mit den vorhandenen Eingaben werden wir einige Strukturen und Hilfsfunktionen definieren, die uns bei der Verwaltung unserer Setups helfen.
//+------------------------------------------------------------------+ //| Structure for FVG zone information | //+------------------------------------------------------------------+ struct FVGZone { // Define FVG zone structure string name; //--- Zone name datetime startTime; //--- Start time datetime origEndTime; //--- Original end time datetime mitTime; //--- Mitigation time bool signal; //--- Signal flag bool inverted; //--- Inverted flag bool mit; //--- Mitigated flag bool ret; //--- Retraced flag bool origUp; //--- Original up flag int tradeCount; //--- Trade count FVGState state; //--- State bool newSignal; //--- New signal flag }; FVGZone fvgs[]; //--- FVG zones array //+------------------------------------------------------------------+ //| Get color based on state and direction | //+------------------------------------------------------------------+ color GetFVGColor(bool isUp, FVGState currentState) { if (currentState == Normal) return isUp ? CLR_UP : CLR_DOWN; //--- Return normal color if (currentState == Mitigated) return isUp ? CLR_MIT_UP : CLR_MIT_DOWN; //--- Return mitigated color if (currentState == Inverted) return isUp ? CLR_INV_UP : CLR_INV_DOWN; //--- Return inverted color return clrNONE; //--- Return none } //+------------------------------------------------------------------+ //| Print FVGs for debugging | //+------------------------------------------------------------------+ void PrintFVGs() { if (!prt) return; //--- Return if no print Print("Current FVGs count: ", ArraySize(fvgs)); //--- Print count for (int i = 0; i < ArraySize(fvgs); i++) { //--- Iterate FVGs Print("FVG ", i, ": ", fvgs[i].name, " state=", EnumToString(fvgs[i].state), " mit=", fvgs[i].mit, " ret=", fvgs[i].ret, " inverted=", fvgs[i].inverted, " tradeCount=", fvgs[i].tradeCount, " newSignal=", fvgs[i].newSignal, " endTime=", TimeToString(fvgs[i].origEndTime)); //--- Print details } }
Zunächst definieren wir die Struktur „FVGZone“, um alle relevanten Informationen für jede erkannte Fair-Value-Lücke zu speichern, einschließlich „name“ als String für den Objektidentifikator, „startTime“ und „origEndTime“ als Datumsangaben für die anfängliche Spanne der Lücke, „mitTime“ für den Zeitpunkt der Abschwächung, boolesche Flags wie „signal“ für Inversionsauslöser, „inverted“ für den Inversionsstatus, „mit“ für Mitigation, „ret“ für Retracement, „origUp“ um anzuzeigen, ob es sich ursprünglich um eine Aufwärtslücke handelte, „tradeCount“ als Ganzzahl, um den Handel mit diesem FVG zu verfolgen, „state“ unter Verwendung des State-Enums und „newSignal“ als Boolescher Wert für neue Inversionssignale. Wir deklarieren dann ein globales Array „fvgs[]“ vom Typ „FVGZone“, um alle aktiven FVGs zu speichern, sodass wir mehrere Lücken mit ihren Zuständen effizient verfolgen können.
Wir implementieren die Funktion „GetFVGColor“, um die geeignete Farbe für ein FVG-Rechteck auf der Grundlage seiner Richtung „isUp“ und „currentState“ zu bestimmen: Für „Normal“ geben wir „CLR_UP“ (grün) zurück, wenn es aufwärts geht, oder „CLR_DOWN“ (rot), wenn es abwärts geht; für „Mitigated“, „CLR_MIT_UP“ (violett) oder „CLR_MIT_DOWN“ (orange); für „Inverted“, „CLR_INV_UP“ (rot) oder „CLR_INV_DOWN“ (grün); ansonsten keine. Wir erstellen auch die Funktion „PrintFVGs“ für die Fehlersuche, die frühzeitig zurückkehrt, wenn „prt“ falsch ist, andernfalls den aktuellen Zählerstand aus ArraySize ausgibt und dann in Schleifen jeden Eintrag durchläuft, um Details wie Index, Name, Status über EnumToString, Flags für mit/ret/inverted, Trade Count, neues Signal und Endzeit mit der Funktion TimeToString auszugeben. Nun können wir die visuellen Funktionen für die Rechtecke und Beschriftungen definieren.
//+------------------------------------------------------------------+ //| Create Rectangle | //+------------------------------------------------------------------+ void CreateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) { ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2); //--- Create rectangle ObjectSetInteger(0, objName, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground datetime midTime = time1 + (time2 - time1) / 2; //--- Calc mid time double midPrice = (price1 + price2) / 2; //--- Calc mid price CreateLabel(objName, midTime, midPrice); //--- Create label ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Update Rectangle | //+------------------------------------------------------------------+ void UpdateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) { if (ObjectFind(0, objName) >= 0) { //--- Check exists ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1); //--- Set time1 ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1); //--- Set price1 ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2); //--- Set time2 ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2); //--- Set price2 ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set color datetime midTime = time1 + (time2 - time1) / 2; //--- Calc mid time double midPrice = (price1 + price2) / 2; //--- Calc mid price UpdateLabel(objName, midTime, midPrice); //--- Update label ChartRedraw(0); //--- Redraw chart } } //+------------------------------------------------------------------+ //| Create label | //+------------------------------------------------------------------+ void CreateLabel(string zoneName, datetime time, double price) { string lblName = zoneName + "_Label"; //--- Label name ObjectCreate(0, lblName, OBJ_TEXT, 0, time, price); //--- Create text ObjectSetInteger(0, lblName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor ObjectSetInteger(0, lblName, OBJPROP_COLOR, clrBlack); //--- Set color UpdateLabelText(lblName, zoneName); //--- Update text } //+------------------------------------------------------------------+ //| Update label position | //+------------------------------------------------------------------+ void UpdateLabel(string zoneName, datetime time, double price) { string lblName = zoneName + "_Label"; //--- Label name if (ObjectFind(0, lblName) >= 0) { //--- Check exists ObjectSetInteger(0, lblName, OBJPROP_TIME, 0, time); //--- Set time ObjectSetDouble(0, lblName, OBJPROP_PRICE, 0, price); //--- Set price UpdateLabelText(lblName, zoneName); //--- Update text } } //+------------------------------------------------------------------+ //| Update label text | //+------------------------------------------------------------------+ void UpdateLabelText(string lblName, string zoneName) { string text = ""; //--- Init text int tradeCnt = 0; //--- Init count FVGState state = Normal; //--- Init state bool origUp = false; //--- Init orig up for (int idx = 0; idx < ArraySize(fvgs); idx++) { //--- Iterate FVGs if (fvgs[idx].name == zoneName) { //--- Check match tradeCnt = fvgs[idx].tradeCount; //--- Get count state = fvgs[idx].state; //--- Get state origUp = fvgs[idx].origUp; //--- Get orig up break; //--- Break loop } } if (state == Normal) { //--- Check normal text = origUp ? "Bullish FVG" : "Bearish FVG"; //--- Set text } else if (state == Mitigated) { //--- Check mitigated text = origUp ? "Mitigated Bullish FVG" : "Mitigated Bearish FVG"; //--- Set text } else if (state == Inverted) { //--- Check inverted text = origUp ? "Bearish Inversed FVG" : "Bullish Inversed FVG"; //--- Set text } if (tradeCnt > 0) { //--- Check traded text += " (Traded " + IntegerToString(tradeCnt) + " times)"; //--- Add traded } ObjectSetString(0, lblName, OBJPROP_TEXT, text); //--- Set text } //+------------------------------------------------------------------+ //| Draw mitigation icon | //+------------------------------------------------------------------+ void DrawMitIcon(string fvgNAME, datetime mitTime, double fvgHigh, double fvgLow, bool isUp) { string iconName = fvgNAME + "_MitIcon"; //--- Icon name double iconPrice = isUp ? fvgLow : fvgHigh; //--- Icon price ObjectCreate(0, iconName, OBJ_ARROW, 0, mitTime, iconPrice); //--- Create arrow ObjectSetInteger(0, iconName, OBJPROP_ARROWCODE, 251); //--- Set code ObjectSetInteger(0, iconName, OBJPROP_COLOR, clrBlue); //--- Set color ObjectSetInteger(0, iconName, OBJPROP_ANCHOR, isUp ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor ChartRedraw(0); //--- Redraw chart }
Für die Visualisierung definieren wir zunächst die Funktion „CreateRec“, um ein neues Rechteck zu zeichnen, das eine FVG-Zone auf dem Chart darstellt. Wir erstellen das Rechteckobjekt mit ObjectCreate unter Verwendung von OBJ_RECTANGLE, das sich vom Zeitpunkt 1 zum Preis 1 bis zum Zeitpunkt 2 zum Preis 2 erstreckt. Wir aktivieren die Füllung, indem wir OBJPROP_FILL auf true setzen, wenden die angegebene Farbe über OBJPROP_COLOR an, positionieren sie im Vordergrund, indem wir OBJPROP_BACK auf false setzen, berechnen die Midpoint-Zeit als time1 plus die Hälfte der Dauer und den Midpoint-Preis als Durchschnitt von price1 und price2, rufen dann CreateLabel auf, um einen beschreibenden Text am Midpoint hinzuzufügen, und zeichnen schließlich das Chart mit der Funktion ChartRedraw neu. Wir implementieren die Funktion „UpdateRec“, um ein bestehendes FVG-Rechteck zu ändern, wenn sich sein Zustand oder seine Farbe ändert. Wenn das Objekt per ObjectFind vorhanden ist, aktualisieren wir seine Koordinaten, indem wir „OBJPROP_TIME“ und „OBJPROP_PRICE“ für beide Anker setzen, die neue Farbe anwenden, den Mittelpunkt der Zeit und des Preises wie zuvor neu berechnen, „UpdateLabel“ aufrufen, um den Text neu zu positionieren und zu aktualisieren, und das Chart neu zeichnen.
Als Nächstes erstellen wir die Funktion „CreateLabel“, um eine Textbeschriftung innerhalb des FVG-Rechtecks hinzuzufügen. Wir bilden den Labelnamen, indem wir „_Label“ an den Zonennamen anhängen, erstellen ein Objekt vom Typ OBJ_TEXT zur angegebenen Zeit und zum angegebenen Preis mit „ObjectCreate“, setzen seinen Anker mit OBJPROP_ANCHOR auf die Mitte, die Farbe auf Schwarz und rufen dann „UpdateLabelText“ auf, um den anfänglichen beschreibenden Text zu setzen. Wir definieren die Funktion „UpdateLabel“, um eine vorhandene Beschriftung zu verschieben, wenn sich das Rechteck anpasst. Wenn die Kennzeichnung vorhanden ist, aktualisieren wir seine Position und rufen dann „UpdateLabelText“ auf, um seinen Inhalt auf der Grundlage des aktuellen Zustands zu aktualisieren. Anschließend implementieren wir die Funktion „UpdateLabelText“, um die Zeichenfolge des Kennzeichens dynamisch zu erstellen und festzulegen. Wir initialisieren einen leeren Text und gehen dann in einer Schleife durch das Array „fvgs“, um die passende Zone anhand ihres Namens zu finden und ihre Handelsanzahl, ihren Status und ihre ursprüngliche Aufwärtsrichtung zu ermitteln.
Je nach Status wird der Text auf „Bullish FVG“ oder „Bearish FVG“ für normal, „Mitigated Bullish FVG“ oder „Mitigated Bearish FVG“ für abgemildert, „Bearish Inversed FVG“ oder „Bullish Inversed FVG“ für invertiert; wenn die Anzahl der Transaktionen 0 übersteigt, wird „(Traded X times)“ angehängt. Wir wenden diesen Text mit ObjectSetString und OBJPROP_TEXT auf die Kennzeichnung an. Schließlich erstellen wir die Funktion „DrawMitIcon“, um eine visuelle Markierung bei Mitigationsereignissen zu setzen. Wir bilden den Namen des Symbols, indem wir „_MitIcon“ an den FVG-Namen anhängen, erstellen einen OBJ_ARROW zur Mitigationszeit und zum entsprechenden Preis (niedrig für Aufwärtslücken, hoch für Abwärtslücken), setzen seinen Pfeilcode auf 251, die Farbe auf blau, den Anker nach oben für Aufwärts- oder unten für Abwärtslücken, basierend auf „isUp“, und zeichnen das Chart neu. Sie können den Pfeilcode nach Belieben ändern, basierend auf dem Schrifttyp MQL5 Wingdings wie unten.

Mit diesen Funktionen können wir das anfänglichen Bereiche für die Einrichtung auf dem Chart erstellen, indem wir die verfügbaren Balken auf dem Chart verwenden, um das Styling des Indikators zu erstellen, sodass wir in der Lage sind, die Konstellationen bei der Initialisierung des Programms zu sehen. Wir werden die Logik ebenfalls in einer Funktion unterbringen.
//+------------------------------------------------------------------+ //| Process historical mitigation, retracement, signal for an FVG | //+------------------------------------------------------------------+ void ProcessHistoricalState(int idx) { string fvgNAME = fvgs[idx].name; //--- Get name datetime timeSTART = fvgs[idx].startTime; //--- Get start time datetime endTime = fvgs[idx].origEndTime; //--- Get end time double fvgLow = MathMin(ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 1)); //--- Calc low double fvgHigh = MathMax(ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 1)); //--- Calc high int fvgBar = iBarShift(_Symbol, _Period, timeSTART); //--- Get bar if (fvgBar < 0) return; //--- Return invalid bool isMit = false, isRet = false, isSig = false; //--- Init flags datetime mitTime = 0; //--- Init mit time int mitK = -1, sigK = -1; //--- Init indices for (int k = fvgBar - 1; k >= 0; k--) { //--- Iterate bars double barLow = iLow(_Symbol, _Period, k); //--- Get bar low double barHigh = iHigh(_Symbol, _Period, k); //--- Get bar high double barClose = iClose(_Symbol, _Period, k); //--- Get bar close if (!isMit) { //--- Check not mit bool breakFar = (fvgs[idx].origUp && barLow < fvgLow) || (!fvgs[idx].origUp && barHigh > fvgHigh); //--- Check break far if (breakFar) { //--- Break far isMit = true; //--- Set mit mitK = k; //--- Set mit k mitTime = iTime(_Symbol, _Period, k); //--- Set mit time if (prt) Print("Historical Mitigated: ", fvgNAME, " at bar ", k, " time=", TimeToString(mitTime)); //--- Log mitigated } } if (isMit && !isRet) { //--- Check mit and not ret bool inside = (barHigh > fvgLow && barLow < fvgHigh); //--- Check inside if (inside) { //--- Inside isRet = true; //--- Set ret if (prt) Print("Historical Retraced: ", fvgNAME, " at bar ", k); //--- Log retraced } } if (isMit && isRet && !isSig) { //--- Check mit ret not sig bool signal = (fvgs[idx].origUp && barClose < fvgLow) || (!fvgs[idx].origUp && barClose > fvgHigh); //--- Check signal if (signal) { //--- Signal if (k + 1 < iBars(_Symbol, _Period)) { //--- Check prev bar double prevClose = iClose(_Symbol, _Period, k + 1); //--- Get prev close bool prevInside = (prevClose > fvgLow && prevClose < fvgHigh); //--- Check prev inside if (prevInside) { //--- Prev inside isSig = true; //--- Set sig sigK = k; //--- Set sig k if (prt) Print("Historical Signal/Inverted: ", fvgNAME, " at bar ", k, " time=", TimeToString(iTime(_Symbol, _Period, k))); //--- Log signal } } } } } fvgs[idx].mit = isMit; //--- Set mit fvgs[idx].ret = isRet; //--- Set ret fvgs[idx].inverted = isSig; //--- Set inverted fvgs[idx].signal = isSig; //--- Set signal fvgs[idx].mitTime = mitTime; //--- Set mit time fvgs[idx].state = isSig ? Inverted : (isMit ? Mitigated : Normal); //--- Set state fvgs[idx].newSignal = false; //--- Set no new signal color currentClr = GetFVGColor(fvgs[idx].origUp, fvgs[idx].state); //--- Get color UpdateRec(fvgs[idx].name, fvgs[idx].startTime, fvgLow, fvgs[idx].origEndTime, fvgHigh, currentClr); //--- Update rec if (mitTime > 0) DrawMitIcon(fvgs[idx].name, mitTime, fvgHigh, fvgLow, fvgs[idx].origUp); //--- Draw mit icon }
Hier definieren wir die Funktion „ProcessHistoricalState“, um den Zustand eines bestimmten FVG im Array während der Initialisierung zu analysieren und zu aktualisieren, indem wir auf der Grundlage historischer Balken nach dem Start der Lücke auf Mitigation, Retracement und Inversion prüfen. Wir beginnen damit, den Namen, die Startzeit und die ursprüngliche Endzeit des FVG aus der Struktur am angegebenen Index abzurufen, und berechnen dann die Tiefst- und Höchstpreise mit MathMin und MathMax auf den Preisen des Rechteckobjekts über ObjectGetDouble mit OBJPROP_PRICE. Mit iBarShift wird der Balkenindex der Startzeit ermittelt und bei Ungültigkeit vorzeitig zurückgekehrt. Wir initialisieren die Flags für Mitigation, Retracement und Signal auf false, zusammen mit dem Abschwächungszeitpunkt und dem Balkenindex auf -1. Anschließend wird eine Rückwärtsschleife vom Balken vor dem FVG bis zum Balken 0 durchgeführt: Für jeden Balken werden die Tiefst-, Höchst- und Schlusskurse mit den Funktionen iLow, iHigh und iClose ermittelt. Falls noch nicht abgeschwächt, prüfen wir, ob ein Durchbruch auf der Gegenseite vorliegt – für ursprüngliche Aufwärtslücken (Aufwärts-FVG), wenn der Tiefststand des Balkens unter den Tiefststand des FVG fällt; für Abwärtslücken, wenn der Höchststand des Balkens den Höchststand des FVG übersteigt – und setzen das Flag für die Abschwächung, den Balkenindex, die Zeit über iTime und protokollieren, wenn „prt“ wahr ist.
Ist der Kurs abgeschwächt, aber nicht zurückgekehrt, wird geprüft, ob der Balken die FVG durchbrochen hat (Hoch über Tief und Tief unter Hoch), das Retracement-Flag gesetzt und protokolliert. Ist die Situation sowohl abgeschwächt als auch zurückgekehrt, aber ohne Signal, erkennen wir eine Inversion: für Aufwärtslücken, wenn der Schlusskurs des Balkens unter dem FVG-Tief liegt; für Abwärtslücken, über dem FVG-Hoch – dann überprüfen wir, ob der Schlusskurs des vorherigen Balkens innerhalb des FVG lag (über dem Tief und unter dem Hoch), um den Ausgang von innen zu bestätigen, und setzen das Signal-Flag, den Balken-Index und die Protokollierung.
Wir aktualisieren die Strukturfelder „mitigation“, „retracement“, „inverted“ und „signal“ auf die Flag-Werte, die Abschwächungszeit, den Status „Inverted“, wenn Signal vorliegt, „Mitigated“, wenn abgeschwächt, oder „Normal“, und setzen das neue Signal auf false zurück. Wir ermitteln die aktuelle Farbe mit „GetFVGColor“ auf der Grundlage der ursprünglichen Richtung und des ursprünglichen Zustands, aktualisieren das Rechteck mit „UpdateRec“ mit den möglicherweise angepassten Koordinaten und der neuen Farbe und zeichnen, falls eine Milderung erfolgt ist, das Milderungssymbol mit „DrawMitIcon“ zum Zeitpunkt der Milderung und zum entsprechenden Kantenpreis (niedrig für Lücken nach oben, hoch für Lücken nach unten). Wir können diese Funktion nun in der Initialisierungsfunktion verwenden, um unsere ersten Aufstellungen zu zeichnen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(magic_number); //--- Set magic number ObjectsDeleteAll(0, FVG_Prefix); //--- Delete FVG objects ArrayResize(fvgs, 0); //--- Reset array if (prt) Print("Initializing: Deleted all existing FVG objects and reset array."); //--- Log init int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars if (prt) Print("Total Visible Bars On Chart = ", visibleBars); //--- Log visible bars // Detect historical FVGs from older to newer for (int i = visibleBars - 3; i >= 0; i--) { //--- Iterate bars double low0 = iLow(_Symbol, _Period, i); //--- Get low0 double high2 = iHigh(_Symbol, _Period, i + 2); //--- Get high2 double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, _Digits); //--- Calc gap L0 H2 double high0 = iHigh(_Symbol, _Period, i); //--- Get high0 double low2 = iLow(_Symbol, _Period, i + 2); //--- Get low2 double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, _Digits); //--- Calc gap H0 L2 bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts; //--- Check up FVG bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts; //--- Check down FVG if (FVG_UP || FVG_DOWN) { //--- Check FVG datetime time1 = iTime(_Symbol, _Period, i + 1); //--- Get time1 double price1 = FVG_UP ? high2 : high0; //--- Set price1 double price2 = FVG_UP ? low0 : low2; //--- Set price2 double newLow = MathMin(price1, price2); //--- Calc new low double newHigh = MathMax(price1, price2); //--- Calc new high bool overlaps = false; //--- Init overlaps if (ignoreOverlaps) { //--- Check ignore overlaps for (int ex = 0; ex < ArraySize(fvgs); ex++) { //--- Iterate existing double exLow = ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0); //--- Get ex low double exHigh = ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1); //--- Get ex high exLow = MathMin(exLow, exHigh); //--- Min ex low exHigh = MathMax(exLow, exHigh); //--- Max ex high if (MathMax(newLow, exLow) < MathMin(newHigh, exHigh)) { //--- Check overlap overlaps = true; //--- Set overlaps if (prt) Print("Historical: Skipping overlapping FVG at ", TimeToString(time1)); //--- Log skip break; //--- Break loop } } } if (overlaps) continue; //--- Continue if overlaps string fvgNAME = FVG_Prefix + "(" + TimeToString(time1) + ")"; //--- FVG name color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN; //--- Set color CreateRec(fvgNAME, time1, price1, time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars, price2, fvgClr); //--- Create rec int size = ArraySize(fvgs); //--- Get size if (size >= maxFVGs) { //--- Check max if (prt) Print("Historical: Max FVGs reached, removing oldest."); //--- Log max ArrayRemove(fvgs, 0, 1); //--- Remove oldest PrintFVGs(); //--- Print FVGs } ArrayResize(fvgs, size + 1); //--- Resize array fvgs[size].name = fvgNAME; //--- Set name fvgs[size].startTime = time1; //--- Set start time fvgs[size].origEndTime = time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars; //--- Set end time fvgs[size].mitTime = 0; //--- Set mit time fvgs[size].signal = false; //--- Set signal fvgs[size].inverted = false; //--- Set inverted fvgs[size].mit = false; //--- Set mit fvgs[size].ret = false; //--- Set ret fvgs[size].origUp = FVG_UP; //--- Set orig up fvgs[size].tradeCount = 0; //--- Set trade count fvgs[size].state = Normal; //--- Set state fvgs[size].newSignal = false; //--- Set new signal if (prt) Print("Historical FVG created: ", fvgNAME, " origUp=", FVG_UP, " endTime=", TimeToString(fvgs[size].origEndTime)); //--- Log created PrintFVGs(); //--- Print FVGs } } // Process historical states for (int j = 0; j < ArraySize(fvgs); j++) { //--- Iterate FVGs ProcessHistoricalState(j); //--- Process state } PrintFVGs(); //--- Print FVGs return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { for (int i = 0; i < ArraySize(fvgs); i++) { //--- Iterate FVGs ObjectDelete(0, fvgs[i].name); //--- Delete name ObjectDelete(0, fvgs[i].name + "_Label"); //--- Delete label ObjectDelete(0, fvgs[i].name + "_MitIcon"); //--- Delete mit icon } ArrayResize(fvgs, 0); //--- Reset array ChartRedraw(0); //--- Redraw chart if (prt) Print("Deinit: Deleted all FVG objects and reset array."); //--- Log deinit }
In der Ereignisbehandlung von OnInit, das beim Programmstart aufgerufen wird, weisen wir zunächst „obj_Trade“ mit „SetExpertMagicNumber“ die Eingabe „magic_number“ zur Handelsidentifikation zu. Anschließend werden alle vorhandenen FVG-Rechtecke durch den Aufruf von ObjectsDeleteAll mit dem aktuellen Chart und „FVG_Prefix“ gelöscht, die Größe des Arrays „fvgs“ mit ArrayResize auf 0 zurückgesetzt und die Initialisierung protokolliert, wenn „prt“ wahr ist. Wir rufen die Anzahl der sichtbaren Balken auf dem Chart mit ChartGetInteger unter Verwendung von CHART_VISIBLE_BARS in „visibleBars“ ab und protokollieren sie, wenn „prt“.
Um historische FVGs von den ältesten bis zu den neuesten zu erkennen, führen wir eine Schleife von „visibleBars – 3“ bis hinunter zu 0: Für jeden Balken i berechnen wir potentielle Lücken, indem wir den Tiefstwert von i mit dem Höchstwert von i+2 vergleichen (normalisierte Lücke in Punkten in „gap_L0_H2“) und den Höchstwert von i mit dem Tiefstwert von i+2 („gap_H0_L2“), wobei „FVG_UP“ gesetzt wird, wenn low0 > high2 und gap > „minPts“ (Aufwärtslücke) oder „FVG_DOWN“, wenn low2 > high0 und gap > „minPts“ (Aufwärtslücke). Wir benötigen mindestens 3 vollständige Balken, um eine Lücke zu erkennen. Es ist besonders wichtig, dass Sie dies ein für alle Mal verstehen. Zur Verdeutlichung haben wir es für Sie wie folgt visualisiert.

Wenn einer der beiden Typen gefunden wird, wird die Zeit von Takt i+1 in „time1“ übernommen, die Preise werden entsprechend gesetzt und die Tiefst- und Höchstwerte der Lücke mit den Funktionen MathMin und MathMax berechnet. Wenn „ignoreOverlaps“ wahr ist, überprüfen wir alle in „fvgs“ vorhandenen Preise, indem wir sie mit ObjectGetDouble abrufen und die Bereiche vergleichen – wenn sie sich überschneiden (Maximum der Tiefs < Minimum der Hochs), setzen wir „overlaps“ wahr und protokollieren „prt“ und fahren mit dem nächsten fort. Wenn es keine Überschneidung gibt, bilden wir den Namen als „FVG_Prefix + (TimeToString(time1))“, wählen die Farbe nach oben oder unten und rufen „CreateRec“ auf, um das um die „FVG_Rec_Ext_Bars“-Perioden erweiterte Rechteck zu zeichnen. Wir prüfen, ob die Array-Größe „maxFVGs“ erreicht, entfernen die ältesten mit ArrayRemove und protokollieren, wenn „prt“, dann verkleinern wir „fvgs“ um eins, füllen den neuen Eintrag mit Name, Zeiten, Flags alle false außer „origUp“ als „FVG_UP“, trade count 0, state „Normal“, new signal false, log creation if „prt“, und call „PrintFVGs“. Sie sehen, dass die Überschneidungen die visuelle Attraktivität beeinträchtigen könnten, haben aber kein Problem mit der Handelsaktivität.

Sie sehen, dass die sich überlappenden Instanzen optisch nicht sehr ansprechend sind. Nachdem wir alle historischen FVGs erkannt haben, durchlaufen wir eine Schleife durch „fvgs“ und rufen „ProcessHistoricalState“ für jeden Index auf, um die Anfangszustände auf der Grundlage vergangener Preisaktionen festzulegen, rufen dann erneut „PrintFVGs“ auf und geben INIT_SUCCEEDED zurück. In der Funktion OnDeinit durchlaufen wir schließlich eine Schleife durch „fvgs“, löschen jedes Rechteck, jede Beschriftung (mit dem Suffix „_Label“) und jedes Mitigations-Symbol („_MitIcon“) mit ObjectDelete, ändern die Größe von „fvgs“ auf 0, zeichnen das Chart neu und protokollieren das Deinit if „prt“. Wenn wir kompilieren, erhalten wir folgendes Ergebnis.

Aus der Visualisierung geht hervor, dass wir alle FVGs beim Laden scannen, abbilden und aktualisieren. Jetzt müssen wir die Erkennung fortsetzen und die Aktualisierungen für neue Balken einrichten. Beginnen wir mit der Erkennungslogik.
//+------------------------------------------------------------------+ //| Detect new FVGs on recent bars | //+------------------------------------------------------------------+ void DetectFVGs() { for (int i = 3; i >= 1; i--) { //--- Iterate recent bars double low0 = iLow(_Symbol, _Period, i); //--- Get low0 double high2 = iHigh(_Symbol, _Period, i + 2); //--- Get high2 double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, _Digits); //--- Calc gap L0 H2 double high0 = iHigh(_Symbol, _Period, i); //--- Get high0 double low2 = iLow(_Symbol, _Period, i + 2); //--- Get low2 double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, _Digits); //--- Calc gap H0 L2 bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts; //--- Check up FVG bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts; //--- Check down FVG if (FVG_UP || FVG_DOWN) { //--- Check FVG datetime time1 = iTime(_Symbol, _Period, i + 1); //--- Get time1 double price1 = FVG_UP ? high2 : high0; //--- Set price1 double price2 = FVG_UP ? low0 : low2; //--- Set price2 double newLow = MathMin(price1, price2); //--- Calc new low double newHigh = MathMax(price1, price2); //--- Calc new high bool overlaps = false; //--- Init overlaps if (ignoreOverlaps) { //--- Check ignore overlaps for (int ex = 0; ex < ArraySize(fvgs); ex++) { //--- Iterate existing double exLow = MathMin(ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1)); //--- Calc ex low double exHigh = MathMax(ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1)); //--- Calc ex high if (MathMax(newLow, exLow) < MathMin(newHigh, exHigh)) { //--- Check overlap overlaps = true; //--- Set overlaps if (prt) Print("Detect: Skipping overlapping FVG at ", TimeToString(time1)); //--- Log skip break; //--- Break loop } } } if (overlaps) continue; //--- Continue if overlaps string fvgNAME = FVG_Prefix + "(" + TimeToString(time1) + ")"; //--- FVG name if (ObjectFind(0, fvgNAME) >= 0) continue; //--- Skip duplicate color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN; //--- Set color datetime endTime = time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars; //--- Calc end time CreateRec(fvgNAME, time1, price1, endTime, price2, fvgClr); //--- Create rec int size = ArraySize(fvgs); //--- Get size if (size >= maxFVGs) { //--- Check max if (prt) Print("Detect: Max FVGs reached, removing oldest."); //--- Log max ArrayRemove(fvgs, 0, 1); //--- Remove oldest PrintFVGs(); //--- Print FVGs } ArrayResize(fvgs, size + 1); //--- Resize array fvgs[size].name = fvgNAME; //--- Set name fvgs[size].startTime = time1; //--- Set start time fvgs[size].origEndTime = endTime; //--- Set end time fvgs[size].mitTime = 0; //--- Set mit time fvgs[size].signal = false; //--- Set signal fvgs[size].inverted = false; //--- Set inverted fvgs[size].mit = false; //--- Set mit fvgs[size].ret = false; //--- Set ret fvgs[size].origUp = FVG_UP; //--- Set orig up fvgs[size].tradeCount = 0; //--- Set trade count fvgs[size].state = Normal; //--- Set state fvgs[size].newSignal = false; //--- Set new signal if (prt) Print("New FVG added to storage: ", fvgNAME, " origUp=", FVG_UP, " endTime=", TimeToString(endTime)); //--- Log added PrintFVGs(); //--- Print FVGs } } }
Wir definieren die Funktion „DetectFVGs“ so, dass sie bei jedem neuen Balken die letzten Balken auf neue Fair-Value-Lücken untersucht und sie unserem Tracking-System hinzufügt, wenn sie die Kriterien erfüllen. Wir machen eine Schleife von Index 3 bis 1, um die letzten abgeschlossenen Balken zu prüfen (das ist jetzt nicht neu): Für jedes i holen wir das Tief von i mit iLow in „low0“, das Hoch von i+2 in „high2“, und berechnen den normalisierten Abstand in Punkten in „gap_L0_H2“; ähnlich für das Hoch von i in „high0“, das Tief von i+2 in „low2“, und „gap_H0_L2“. Wir setzen „FVG_UP“ true, wenn „low0 > high2“ und Gap über „minPts“ (Aufwärtslücke), oder „FVG_DOWN“, wenn „low2 > high0“ und Gap > „minPts“ (Abwärtslücke).
Wenn einer von beiden erkannt wird, wird die Zeit von i+1 in „time1“ eingetragen, „price1“ auf „high2“ für Aufwärtsbewegungen oder „high0“ für Abwärtsbewegungen gesetzt, „price2“ auf „low0“ oder „low2“, und die Tiefst- und Höchstwerte der Lücke werden berechnet. Wenn „ignoreOverlaps“ wahr ist, überprüfen wir alle in „fvgs“ vorhandenen Preise, indem wir sie mit ObjectGetDouble abrufen und MathMin/MathMax verwenden, um Bereiche zu erhalten – wenn sich ein neues Gap mit einem überschneidet (max lows < min highs), setzen wir „overlaps“ auf wahr, protokollieren die Lücke, wenn „prt“, und fahren fort. Wenn es keine Überschneidungen und kein doppeltes Objekt per ObjectFind gibt, bilden wir den Namen als „FVG_Prefix + (TimeToString(time1))“, wählen die Farbe nach oben (grün) oder unten (rot), berechnen „endTime“ als „time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars“, und rufen „CreateRec“ auf, um das Rechteck zu zeichnen. Wir verwalten dann das Array „fvgs“: Wenn die Größe „maxFVGs“ erreicht ist, entfernen wir das älteste mit ArrayRemove und protokollieren, wenn „prt“, dann verkleinern wir es um eins mit ArrayResize, füllen den neuen Eintrag mit Name, Zeiten, Abschwächungszeit 0, alle Flags false außer „origUp“ als „FVG_UP“, Handelszähler auf 0, state = „Normal“, newsignal auf false, protokollieren, wenn „prt“ und rufen „PrintFVGs“ auf. Dadurch werden nur gültige, sich nicht überschneidende neue FVGs, die sich nach rechts erstrecken, erkannt und gespeichert. Wir können diese Funktion in der Tick-Ereignishandhabung aufrufen, um die schwere Arbeit wie unten beschrieben zu erledigen.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static datetime lastBarTime = 0; //--- Last bar time datetime curBarTime = iTime(_Symbol, _Period, 0); //--- Current bar time bool newBar = (curBarTime != lastBarTime); //--- Check new bar if (!newBar) return; //--- Return if not new lastBarTime = curBarTime; //--- Update last time DetectFVGs(); //--- Detect FVGs }
In der Ereignisbehandlung von OnTick, das bei jedem Preis-Tick ausgeführt wird, um Echtzeit-Aktualisierungen zu verarbeiten, verwenden wir die statische Variable „lastBarTime“, um die Eröffnungszeit des vorherigen Balkens zu verfolgen, holen die Zeit des aktuellen Balkens mit iTime bei Shift 0 in „curBarTime“ und setzen „newBar“ auf true, wenn sie sich unterscheiden, was anzeigt, dass ein neuer Balken entstanden ist. Wenn es sich nicht um einen neuen Balken handelt, kehren wir vorzeitig zurück, um eine überflüssige Bearbeitung zu vermeiden. Andernfalls aktualisieren wir „lastBarTime“ auf „curBarTime“ und rufen „DetectFVGs“ auf, um nach neuen Lücken in den letzten Balken zu suchen. Wir kommen zu folgendem Ergebnis.

Nachdem die erste Erkennung erfolgt ist, können wir mit der Aktualisierung der Einstellungen fortfahren. Wir verwenden die folgende Logik.
//+------------------------------------------------------------------+ //| Update states for all FVGs | //+------------------------------------------------------------------+ void UpdateFVGs() { double prevClose = iClose(_Symbol, _Period, 1); //--- Get prev close double prevLow = iLow(_Symbol, _Period, 1); //--- Get prev low double prevHigh = iHigh(_Symbol, _Period, 1); //--- Get prev high double bar2Close = iClose(_Symbol, _Period, 2); //--- Get bar2 close datetime curBarTime = iTime(_Symbol, _Period, 1); //--- Get prev bar time bool removed = false; //--- Init removed for (int j = ArraySize(fvgs) - 1; j >= 0; j--) { //--- Iterate reverse if (ObjectFind(0, fvgs[j].name) < 0) { //--- Check no object if (prt) Print("Update: Removed non-existent FVG from storage: ", fvgs[j].name); //--- Log removed ArrayRemove(fvgs, j, 1); //--- Remove from array removed = true; //--- Set removed continue; //--- Continue } double fvgLow = MathMin(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc low double fvgHigh = MathMax(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc high if (!fvgs[j].mit) { //--- Check not mit bool breakFar = (fvgs[j].origUp && prevLow < fvgLow) || (!fvgs[j].origUp && prevHigh > fvgHigh); //--- Check break far if (breakFar) { //--- Break far fvgs[j].mit = true; //--- Set mit fvgs[j].mitTime = curBarTime; //--- Set mit time fvgs[j].state = Mitigated; //--- Set state if (prt) Print("Mitigated FVG: ", fvgs[j].name, " at time=", TimeToString(curBarTime)); //--- Log mitigated color mitClr = GetFVGColor(fvgs[j].origUp, fvgs[j].state); //--- Get color UpdateRec(fvgs[j].name, fvgs[j].startTime, fvgLow, fvgs[j].origEndTime, fvgHigh, mitClr); //--- Update rec DrawMitIcon(fvgs[j].name, curBarTime, fvgHigh, fvgLow, fvgs[j].origUp); //--- Draw icon } } if (fvgs[j].mit && !fvgs[j].ret) { //--- Check mit not ret bool inside = (prevHigh > fvgLow && prevLow < fvgHigh); //--- Check inside if (inside) { //--- Inside fvgs[j].ret = true; //--- Set ret if (prt) Print("Retraced into FVG: ", fvgs[j].name); //--- Log retraced } } if (fvgs[j].mit && fvgs[j].ret) { //--- Check mit ret bool signal = (fvgs[j].origUp && prevClose < fvgLow) || (!fvgs[j].origUp && prevClose > fvgHigh); //--- Check signal bool prevInside = (bar2Close > fvgLow && bar2Close < fvgHigh); //--- Check prev inside if (signal && curBarTime != fvgs[j].mitTime && prevInside) { //--- Check signal conditions fvgs[j].newSignal = true; //--- Set new signal if (!fvgs[j].inverted) { //--- Check not inverted fvgs[j].inverted = true; //--- Set inverted fvgs[j].state = Inverted; //--- Set state if (prt) Print("Signal/Inverted FVG: ", fvgs[j].name, " at time=", TimeToString(curBarTime)); //--- Log signal color sigClr = GetFVGColor(fvgs[j].origUp, fvgs[j].state); //--- Get color UpdateRec(fvgs[j].name, fvgs[j].startTime, fvgLow, fvgs[j].origEndTime, fvgHigh, sigClr); //--- Update rec } } } } if (removed) PrintFVGs(); //--- Print if removed }
Hier definieren wir die Funktion „UpdateFVGs“, um die Zustände aller verfolgten Fair-Value-Lücken bei jedem neuen Balken zu aktualisieren, wobei die Daten des vorherigen Balkens verwendet werden, um Mitigation, Retracement und Inversion in Echtzeit zu erkennen. Wir beginnen damit, dass wir den Schluss des vorherigen Balkens mit iClose bei Shift 1 in „prevClose“ abrufen, sein Tief mit iLow in „prevLow“, sein Hoch mit iHigh in „prevHigh“, den Schluss des vorangegangenen Balkens in „bar2Close“ bei Shift 2 und die Zeit des vorangegangenen Balkens mit „iTime“ bei Shift 1 in „curBarTime“. Wir initialisieren das Flag „removed“ mit „false“ und gehen dann in einer Schleife rückwärts durch das Array „fvgs“, um Einträge bei Bedarf sicher zu entfernen. Für jedes FVG bei Index j, wenn das Rechteck-Objekt per „ObjectFind“ fehlt und negativ zurückkommt, protokollieren wir das Entfernen; wenn „prt“ = true, löschen den Eintrag mit ArrayRemove, setzen „removed“ auf true und fahren mit dem nächsten fort. Andernfalls berechnen wir den aktuellen Höchst- und Tiefstwert aus den Preisen des Objekts mit „MathMin“ und „MathMax“ auf ObjectGetDouble mit OBJPROP_PRICE für die Anker 0 und 1.
Falls noch nicht abgeschwächt, wird auf einen Durchbruch auf der Gegenseite geprüft: für ursprüngliche Aufwärtslücken, wenn „prevLow“ unter das FVG-Tief fällt; für Abwärtslücken, wenn „prevHigh“ das FVG-Hoch überschreitet – Setzen das Flag der Abschwächung auf „true“, „mitTime“ auf „curBarTime“, Status auf „Mitigated“, protokollieren, wenn „prt“, rufen die neue Farbe mit „GetFVGColor“ ab, aktualisieren das Rechteck über „UpdateRec“ mit den angepassten Koordinaten und der Farbe und Zeichnen des Abschwächungssymbols mit „DrawMitIcon“ an „curBarTime“ und dem äußersten Rand (oben für Abwärtslücken, unten für Aufwärtslücken). Wenn eine Abschwächung erfolgt, aber kein Retracement, wird überprüft, ob der vorherige Balken das FVG überschneidet (Hoch über Tief und Tief unter Hoch), wobei das Retracement-Flag auf true gesetzt und protokolliert wird.
Wenn die Situation sowohl abgeschwächt als auch zurückgekehrt ist, erkennen wir eine Umkehrung: bei Aufwärtslücken, wenn „prevClose“ unter dem FVG-Tief liegt; bei Abwärtslücken, wenn es über dem FVG-Hoch liegt – außerdem stellen wir sicher, dass es sich nicht um den Mitigation Balken selbst handelt und dass „bar2Close“ innerhalb des FVG lag (über dem Tief und unter dem Hoch), um einen Ausstieg von innen zu bestätigen. Wir setzen „newSignal“ true, und wenn nicht bereits invertiert, setzen wir „inverted“ auf true, „state“ auf „Inverted“, protokollieren, wählen die Farbe der Inversion und aktualisieren das Rechteck. Wenn etwas entfernt worden ist, rufen wir „PrintFVGs“ zur Fehlersuche auf. Dadurch bleiben die Zustände aller FVGs auf dem neuesten Stand, was genaue Umkehrsignale für den Handel ermöglicht, während verwaiste Objekte elegant behandelt werden. Wenn wir die Funktion aufrufen, erhalten wir das folgende Ergebnis.

Wir können sehen, dass wir die Situation aktualisieren, wenn die Preise mit ihnen interagieren. Was jetzt noch bleibt, ist der Handel bei einer Inversion, und das wird alles sein. Hier ist die Logik, die wir implementiert haben, um dies in einer Funktion zu erreichen und die Modularisierung beizubehalten.
//+------------------------------------------------------------------+ //| Trade on FVGs with signals | //+------------------------------------------------------------------+ void TradeOnFVGs() { double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid for (int j = 0; j < ArraySize(fvgs); j++) { //--- Iterate FVGs if (!fvgs[j].newSignal || fvgs[j].mitTime == 0) continue; //--- Skip no signal or no mit if (tradeMode == TradeOnce && fvgs[j].tradeCount >= 1) { //--- Check once and traded fvgs[j].newSignal = false; //--- Reset signal continue; //--- Continue } if (tradeMode == LimitedTrades && fvgs[j].tradeCount >= maxTradesPerFVG) { //--- Check limited and max fvgs[j].newSignal = false; //--- Reset signal continue; //--- Continue } double fvgLow = MathMin(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc low double fvgHigh = MathMax(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc high if (!fvgs[j].origUp) { //--- Check orig down: Bullish IFVG, Buy if (prt) Print("BULLISH IFVG TRADE SIGNAL For ", fvgs[j].name, " at ", Bid); //--- Log buy signal double SL_Buy = NormalizeDouble(fvgLow - sl_pts * _Point, _Digits); //--- Calc buy SL double TP_Buy = NormalizeDouble(Ask + tp_pts * _Point, _Digits); //--- Calc buy TP obj_Trade.Buy(inpLot, _Symbol, Ask, SL_Buy, TP_Buy, "IFVG Buy"); //--- Open buy } else { //--- Orig up: Bearish IFVG, Sell if (prt) Print("BEARISH IFVG TRADE SIGNAL For ", fvgs[j].name, " at ", Ask); //--- Log sell signal double SL_Sell = NormalizeDouble(fvgHigh + sl_pts * _Point, _Digits); //--- Calc sell SL double TP_Sell = NormalizeDouble(Bid - tp_pts * _Point, _Digits); //--- Calc sell TP obj_Trade.Sell(inpLot, _Symbol, Bid, SL_Sell, TP_Sell, "IFVG Sell"); //--- Open sell } fvgs[j].tradeCount++; //--- Increment count fvgs[j].newSignal = false; //--- Reset signal fvgs[j].ret = false; //--- Reset ret if (prt) Print("Trade executed on ", fvgs[j].name, ", tradeCount now=", fvgs[j].tradeCount); //--- Log executed double midPrice = (fvgLow + fvgHigh) / 2; //--- Calc mid price datetime midTime = fvgs[j].startTime + (fvgs[j].origEndTime - fvgs[j].startTime) / 2; //--- Calc mid time UpdateLabel(fvgs[j].name, midTime, midPrice); //--- Update label } } //+------------------------------------------------------------------+ //| Cleanup expired FVGs from array (keep on chart) | //+------------------------------------------------------------------+ void CleanupExpiredFVGs(datetime curBarTime) { bool removed = false; //--- Init removed for (int j = ArraySize(fvgs) - 1; j >= 0; j--) { //--- Iterate reverse if (curBarTime > fvgs[j].origEndTime) { //--- Check expired if (prt) Print("Expired FVG removed from storage (kept on chart): ", fvgs[j].name, " endTime=", TimeToString(fvgs[j].origEndTime)); //--- Log expired ArrayRemove(fvgs, j, 1); //--- Remove from array removed = true; //--- Set removed } } if (removed) PrintFVGs(); //--- Print if removed }
Zunächst definieren wir die Funktion „TradeOnFVGs“, um Handelsgeschäfte auf frische Inversionssignale von den FVGs auszuführen, wobei die konfigurierten Handelsmodi und Limits beachtet werden. Zunächst wird der aktuelle Briefkurs mit SymbolInfoDouble unter Verwendung von SYMBOL_ASK abgerufen und in „Ask“ auf die Dezimalstellen normalisiert; dasselbe gilt für den Geldkurs mit SYMBOL_BID in „Bid“. Anschließend wird das Array „fvgs“ in einer Schleife durchlaufen: Jeder Eintrag wird übersprungen, wenn kein neues Signal vorliegt oder die Abschwächungszeit Null ist. Wir prüfen die Handelsmodi – bei „TradeOnce“, wenn die Anzahl der Trades 1 oder mehr beträgt, oder bei „LimitedTrades“, wenn sie bei oder über „maxTradesPerFVG“ liegt, setzen wir das neue Signal-Flag zurück und fahren fort.
Für gültige Signale berechnen wir den Tiefst- und Höchststand des FVG aus den Preisen des Rechtecks mit MathMin und MathMax der Funktion ObjectGetDouble. Wenn kein ursprünglicher Aufwärtstrend vorliegt (Abwärts-FVG, invertierte Aufwärtsbewegung), protokollieren wir das Kaufsignal, wenn „prt“ true ist, setzen Stop-Loss unter Low minus „sl_pts * _Point“ normalisiert, Take-Profit über Ask plus „tp_pts * _Point“ normalisiert, und eröffnen einen Buy mit „obj_Trade.Buy“ unter Verwendung von „inpLot“, Symbol, „Ask“, berechneten Niveaus und Kommentar „IFVG Buy“. Dieselbe Logik gilt für eine Abwärtsbewegung. Wir erhöhen den Handelszähler, setzen neue Signal- und Retracement-Flags zurück, protokollieren die Ausführung mit dem aktuellen Zählerstand, wenn „prt“, berechnen den mittleren Preis und die Zeit und rufen „UpdateLabel“ auf, um die Position der Kennzeichnung zu aktualisieren.
Schließlich implementieren wir die Funktion „CleanupExpiredFVGs“, um veraltete FVGs aus dem Array zu entfernen, während ihre Darstellung im Chart beibehalten wird, aufgerufen mit der Zeit des vorherigen Balkens. Wir initialisieren das Flag „removed“ auf „false“ und durchlaufen dann eine Schleife rückwärts durch „fvgs“: Wenn die angegebene Zeit die ursprüngliche Endzeit überschreitet, protokollieren wir den Ablauf, entfernen den Eintrag mit ArrayRemove und setzen „removed“ auf true. Wenn etwas entfernt wurde, rufen wir „PrintFVGs“ zur Fehlersuche auf. Dies ist sehr wichtig, um sicherzustellen, dass wir nur die für uns wichtigen Konfigurationen verfolgen. Wenn wir diese aufrufen und ausführen, erhalten wir das folgende Ergebnis.

Da wir auf die generierten Signale reagieren und Positionen eröffnen können, müssen wir die Handelsgeschäfte nur noch mit einem Trailing-Stop verwalten. Hier ist die Logik, mit der wir das erreichen.
//+------------------------------------------------------------------+ //| Apply Points Trailing Stop | //+------------------------------------------------------------------+ void ApplyPointsTrailing() { double point = _Point; //--- Get point value for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate positions reverse if (PositionGetTicket(i) > 0) { //--- Check valid ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { //--- Check symbol and magic double sl = PositionGetDouble(POSITION_SL); //--- Get SL double tp = PositionGetDouble(POSITION_TP); //--- Get TP double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get ticket if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calc new SL if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calc new SL if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check conditions obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position } } } } } } //--- Call the function in the tick event handler per tick if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing ApplyPointsTrailing(); //--- Apply trailing }
Hier definieren wir die Funktion „ApplyPointsTrailing“, um punktbasierte Trailing-Stops zu implementieren, wenn sie ausgewählt werden, und passen die Stop-Loss-Niveaus in Echtzeit an, wenn sich der Preis profitabel bewegt. Wir beginnen damit, dass wir den Punktwert des Symbols „point“ mit _Point zuweisen. Anschließend durchlaufen wir mit PositionsTotal eine Rückwärtsschleife durch alle offenen Positionen, um Indexprobleme bei Änderungen zu vermeiden, und überprüfen die Gültigkeit jedes Tickets mit der Funktion PositionGetTicket. Für Positionen, die mit unserem Symbol und der „magic_number“ übereinstimmen, rufen wir den Stop-Loss mit PositionGetDouble und POSITION_SL, den Take-Profit mit „POSITION_TP“, den offenen Preis mit „POSITION_PRICE_OPEN“ und das Ticket mit „POSITION_TICKET“ ab. Für Kaufpositionen (POSITION_TYPE_BUY) berechnen wir einen neuen Stop-Loss als aktuelles Bid minus „Trailing_Stop_Pips * point“, normalisiert auf die Dezimalstellen – wenn dieser enger ist als der bestehende Stop-Loss und der unrealisierte Gewinn „Min_Profit_To_Trail_Pips * point“ übersteigt, aktualisieren wir die Position mit „obj_Trade.PositionModify“, wobei der Take-Profit unverändert bleibt. Wir wenden eine ähnliche Logik für Verkaufspositionen an: neuer Stop-Loss als Briefkurs plus Trailing-Distanz, die nur geändert wird, wenn sie sich verengt und der Gewinn die Schwelle erreicht.
Innerhalb von „OnTick“, wenn „TrailingType“ „Trailing_Points“ ist und Positionen per „PositionsTotal“ existieren, rufen wir „ApplyPointsTrailing“ bei jedem Tick auf, um rechtzeitige Anpassungen sicherzustellen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Anhand der Visualisierung erkennen wir die Konstellationen der inversen Fair-Value-Lücken, um sie zu kontrollieren und zu handeln und so unsere Ziele zu verwirklichen. Bleibt nur noch der Backtest des Programms, und der wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.
Backtest-Grafik:

Bericht des Backtests:

Schlussfolgerung
Zusammenfassend haben wir das System Inverse Fair-Value-Gap (IFVG) in MQL5 entwickelt, das steigende/fallende Fair-Value-Gaps (FVGs) auf aktuellen Balken mit einem Filter für die Mindestgröße der Lücken erkennt, Zustände als normal/gemildert/invertiert basierend auf Preisinteraktionen verfolgt, Überschneidungen ignoriert, während es verfolgte FVGs begrenzt, und historische FVGs bei der Initialisierung mit Echtzeit-Updates und abgelaufener Bereinigung lädt. Das System unterstützt einmalige/begrenzte/unbegrenzte Handelsgeschäfte pro Konstellation, eröffnet Käufe bei Aufwärts-IFVGs oder Verkäufe bei Abwärts-IFVGs mit festen Handelsniveaus, Positionslimits, Trailing Stops und visualisiert farbige Rechtecke mit Status-/Handels-Kennzeichnungen und Abschwächungssymbolen.
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 Inverse Fair-Value-Gap-Strategie, die State-Tracking- und Inversionssignale bietet, sind Sie für den Handel mit Gap-Ungleichgewichten 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/20361
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Grenzen des maschinellen Lernens überwinden (Teil 9): Korrelationsbasierte Lernen von Merkmalen im selbstüberwachten Finanzwesen
Klassische Strategien neu interpretieren (Teil 19): Tiefes Eintauchen in das Kreuzen von gleitenden Durchschnitten
Reine Implementierung der RSA-Verschlüsselung in MQL5
Der MQL5 Standard Library Explorer (Teil 5): Experte für mehrere Signale
- 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.