Automatisierung des Indikators Market Memory Zones: Bereiche, zu denen der Kurs voraussichtlich zurückkehrt
Inhaltsverzeichnis
- Einführung
- Überblick über die Automatisierung
- Die ersten Schritte
- Backtest-Ergebnisse
- Schlussfolgerung
Einführung
Im vorangegangenen Artikel haben wir den Indikator Market Memory Zones entwickelt, indem wir Schlüsselbereiche identifiziert haben, in denen der Kurs eine erkennbare Intention des Marktes zeigte – sei es durch Displacement-Bewegungen, strukturelle Übergänge oder Liquiditäts-Sweeps. Wir haben die rohen Kursbewegungen in strukturierte Zonen umgewandelt, die visuell darstellen, wo Ungleichgewichte, Kontrollwechsel oder Liquiditätsereignisse auftraten. Anstatt willkürliche Unterstützungs- und Widerstandszonen zu markieren, wurde der Indikator entwickelt, um Bereiche hervorzuheben, die durch konkretes Marktverhalten bestätigt werden: impulsive Ausweitungen jenseits der normalen Volatilität, eindeutige Change of Character (CHoCH)-Ereignisse sowie entscheidende Liquiditätsabgriffe, auf die eine Trendumkehr folgt.
Nun gehen wir den nächsten logischen Schritt: Wir wandeln diesen analytischen Rahmen in einen vollautomatischen Expert Advisor um, der in der Lage ist, Trades auszuführen. Anstatt Zonen als passive visuelle Anhaltspunkte zu betrachten, wandelt der Automatisierungsprozess sie in strukturierte Entscheidungspunkte um – er erkennt ihre Entstehung in Echtzeit, bewertet ihre Gültigkeit und führt Trades nur dann aus, wenn die Kursreaktion in der Zone die Marktintention bestätigt. Indem wir Bestätigungslogik, Verhaltensfilter und Risikomanagementregeln direkt in das System integrieren, gehen wir von der Beobachtung zur Ausführung über.
Überblick über die Automatisierung
Displacement-Zonen werden automatisch ermittelt, indem ungewöhnlich starke Impulskerzen erkannt werden, die auf Dringlichkeit und ein Ungleichgewicht am Markt hindeuten. Der EA identifiziert diese Zonen, wenn die Spanne einer Kerze die ATR deutlich übersteigt, der Kerzenkörper im Verhältnis zur Gesamtspanne groß ist, nur eine minimale Überlappung mit der vorherigen Kerze vorliegt und die Kerze nahe ihrem Extremwert schließt. Sobald dies erkannt wird, wird der gesamte Kerzenbereich zur Zone, und die Richtung des Schlusskurses bestimmt die Tendenz. Anstatt den Ausbruch selbst zu handeln, wartet der EA darauf, dass sich der Kurs von dieser Zone entfernt und später wieder dorthin zurückkehrt. Ein Kauf wird in einem bullischen Displacement ausgelöst, wenn der Kurs die Zone erneut erreicht und eine Bestätigung auf einem niedrigeren Zeitrahmen zeigt, wie etwa eine Zurückweisung oder einen Wechsel der Mikrostruktur, wobei Stopps außerhalb der Zone platziert und Kursziele auf nahegelegene Strukturhöchststände festgelegt werden. Das Verkaufsszenario spiegelt diese Logik für ein bärisches Displacement wider.

Strukturübergangszonen werden automatisch ermittelt, indem Brüche in der bisherigen Marktstruktur erkannt werden, die auf einen Kontrollwechsel zwischen Käufern und Verkäufern hindeuten. Der EA verfolgt Swing-Hochs und -Tiefs und erkennt einen Change of Character (CHoCH), wenn der Kurs entgegen dem vorherrschenden Trend ausbricht, beispielsweise wenn in einem Abwärtstrend ein Swing-Hoch durchbrochen wird. Die Zone wird als die letzte entgegengesetzte Kerze vor dem Strukturbruch definiert. Bei einem bullischen Trendwechsel wartet der EA darauf, dass der Kurs in diesen Bereich zurückfällt und die Stärke durch höhere Tiefststände oder bullisch schließende Kerzen bestätigt wird, bevor er eine Kaufposition eröffnet. Die Stopps werden jenseits des Übergangs-Swings gesetzt, und die Kursziele richten sich nach früheren Höchstständen oder Fortsetzungsniveaus. Das Verkaufs-Setup folgt derselben Logik in umgekehrter Richtung.

Liquiditäts-Sweep-Zonen werden automatisiert, indem Kerzen erkannt werden, die kurzzeitig über vorherige Höchststände oder unter vorherige Tiefststände steigen bzw. fallen und sich dann schnell wieder innerhalb der Spanne umkehren. Diese Kursbewegungen sind oft Ausdruck von Stop-Hunts, auf die eine starke Tendenz in eine bestimmte Richtung folgt. Der EA markiert die Spanne der Sweep-Kerze als Zone und wartet, bis sich der Kurs davon entfernt hat, bevor er dessen Rückkehr beobachtet. In einem Kaufszenario holt der Preis Liquidität unter einem Tief ab, dreht dann nach oben ab und kehrt später in diesen Bereich zurück; der EA wartet vor dem Einstieg auf eine Bestätigung, beispielsweise darauf, dass kein neues Tief erreicht wird, oder auf eine bullische Engulfing-Formation. Die Stopps werden jenseits des Sweep-Extrems gesetzt, und die Ziele richten sich nach der gegenläufigen Liquidität oder den Widerstandsniveaus. Das Verkaufsszenario spiegelt diese Logik wider, nachdem die Höchststände durchbrochen wurden.

Die ersten Schritte
//+------------------------------------------------------------------+ //| Auto MMZ.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include <Trade/Trade.mqh> #include <Math/Stat/Math.mqh> CTrade trade; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input ENUM_TIMEFRAMES HTF = PERIOD_H1; // Higher Timeframe input ENUM_TIMEFRAMES LTF = PERIOD_M5; // Lower Timeframe input ENUM_TIMEFRAMES ATF = PERIOD_M15; // Analysis Timeframe (for zones) input int SwingBars = 3; // Bars for swing detection // Displacement Zone Parameters input double Displacement_ATR_Multiplier = 1.8; // ATR multiplier for displacement input double Displacement_Body_Percent = 60.0; // Minimum body percentage input double Displacement_Overlap_Max = 30.0; // Maximum overlap with previous candle input double Displacement_Close_Extreme = 20.0; // Close near extreme percentage // Risk Management input double RiskPercent = 1.0; // Risk per trade (%) input int StopLoss_Pips = 150; // Stop Loss in pips input double RiskRewardRatio = 2.0; // Risk/Reward ratio input bool UseDynamicSL = true; // Use zone-based SL input bool UseStructureTP = true; // Use structure for TP // Trade Management input int MaxTradesPerDay = 10; // Maximum trades per day input bool AllowConsecutiveTrades = true; // Allow multiple trades in same direction input double TrailingStop_ATR = 1.5; // ATR multiple for trailing stop // Visualization input bool ShowZones = true; // Show zone rectangles input bool ShowArrows = true; // Show entry arrows input bool ShowLabels = true; // Show trade labels input bool DebugMode = true; // Show debug information //+------------------------------------------------------------------+ //| Zone Structure | //+------------------------------------------------------------------+ enum ZONE_TYPE { ZONE_DISPLACEMENT, ZONE_STRUCTURE_TRANSITION, ZONE_LIQUIDITY_SWEEP }; struct TradeZone { datetime time; double high; double low; double mid; ZONE_TYPE type; int direction; // 1 for bullish, -1 for bearish double atr; // ATR at time of zone formation datetime expiry; // Zone expiry time bool active; bool triggered; string symbol; ENUM_TIMEFRAMES tf; // Constructor to initialize with default values TradeZone() { Reset(); } // Reset method void Reset() { time = 0; high = 0; low = 0; mid = 0; type = ZONE_DISPLACEMENT; direction = 0; atr = 0; expiry = 0; active = false; triggered = false; symbol = _Symbol; tf = PERIOD_CURRENT; } };
Zu Beginn legen wir die Grundlage für den Expert Advisor, indem wir die Handels- und Statistikbibliotheken importieren, das CTrade-Objekt für die Orderausführung initialisieren und alle konfigurierbaren Eingabewerte definieren, die die Analyse mehrerer Zeitrahmen, die Regeln zur Erkennung von Displacement-Bewegungen, das Risikomanagement, das Handelsmanagement und das Visualisierungsverhalten steuern. Anhand dieser Eingabewerte kann das System Zonen in höheren, niedrigeren und dem Analyse-Zeitrahmen erkennen, die Stärke von Kursbewegungen anhand von ATR-basierten Bedingungen bewerten und das Risiko durch prozentuale Positionsgrößen, Stop-Loss-Logik und Risiko-Ertrags-Verhältnisse steuern.
Anschließend definieren wir einen Enumerationstyp ZONE_TYPE, um Displacement-, Strukturübergangs- und Liquiditäts-Sweep-Zonen zu klassifizieren, gefolgt von einem strukturierten TradeZone-Datenmodell, in dem alle relevanten Zoneninformationen wie Preisgrenzen, Richtung, ATR zum Zeitpunkt der Bildung, Ablaufzeitpunkt, Aktivierungsstatus und Metadaten gespeichert werden. Der Konstruktor und die Methode Reset() stellen sicher, dass jede Zone aus einem sauberen, kontrollierten Zustand startet, und bieten so einen strukturierten und skalierbaren Rahmen für die Verwaltung mehrerer dynamischer Markt-Gedächtniszonen innerhalb des EAs.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ SwingPoint htf_lastHigh, htf_prevHigh; SwingPoint htf_lastLow, htf_prevLow; SwingPoint atf_lastHigh, atf_prevHigh; SwingPoint atf_lastLow, atf_prevLow; TradeZone zones[100]; int zoneCount = 0; TradeInfo activeTrades[10]; int activeTradeCount = 0; datetime lastTradeDay = 0; int tradesToday = 0; double currentATR = 0; int atrHandle = -1; MqlDateTime today; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize ATR indicator atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14); if(atrHandle == INVALID_HANDLE) { Print("Failed to create ATR indicator"); return INIT_FAILED; } // Initialize swing points htf_lastHigh.Reset(); htf_prevHigh.Reset(); htf_lastLow.Reset(); htf_prevLow.Reset(); atf_lastHigh.Reset(); atf_prevHigh.Reset(); atf_lastLow.Reset(); atf_prevLow.Reset(); // Initialize zones array for(int i = 0; i < ArraySize(zones); i++) zones[i].Reset(); zoneCount = 0; // Initialize active trades array for(int i = 0; i < ArraySize(activeTrades); i++) activeTrades[i].Reset(); activeTradeCount = 0; // Initialize trade tracking lastTradeDay = 0; tradesToday = 0; // Set magic number for trades trade.SetExpertMagicNumber(12345); Print("EA Initialized. Zone Types: Displacement, Structure Transition, Liquidity Sweep"); return INIT_SUCCEEDED; }
In diesem Abschnitt legen wir den zentralen globalen Zustand des Expert Advisors fest, indem wir Tracker für Swing-Punkte sowohl für höhere als auch für den Analyse-Zeitrahmen deklarieren, Arrays zur Speicherung von bis zu 100 erkannten Handelszonen und 10 aktiven Trades sowie Variablen zur Verwaltung der täglichen Handelslimits und der ATR-basierten Volatilitätsverfolgung. In der Funktion OnInit() initialisieren wir das ATR-Indikator-Handle für die Volatilitätsberechnungen, setzen alle Swing-Strukturen zurück, um saubere Startwerte zu gewährleisten, löschen und bereiten die Arrays für die Zonen und aktiven Trades vor und setzen die Tageshandelszähler zurück. Insgesamt gewährleistet diese Konfiguration, dass das System in einem kontrollierten, geordneten Zustand startet, mit ordnungsgemäß initialisierten Datenstrukturen, die für die Echtzeit-Zonenerkennung und die Handelsausführung bereit sind.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle); ObjectsDeleteAll(0, "ZONE_"); ObjectsDeleteAll(0, "TRADE_"); ChartRedraw(); Print("EA Deinitialized"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update current ATR UpdateATR(); // Check for new bars on analysis timeframe static datetime lastAtfBar = 0; datetime currentAtfBar = iTime(_Symbol, ATF, 0); if(currentAtfBar != lastAtfBar) { lastAtfBar = currentAtfBar; if(DebugMode) Print("New ATF bar: ", TimeToString(currentAtfBar)); // Update market structure UpdateMarketStructure(); // Detect new zones DetectDisplacementZones(); DetectStructureTransitionZones(); DetectLiquiditySweepZones(); // Clean up expired zones CleanupZones(); } // Check zone entries on every tick CheckZoneEntries(); // Manage open positions ManageOpenTrades(); // Update visualization if(ShowZones) UpdateVisualization(); } //+------------------------------------------------------------------+ //| Update Market Structure | //+------------------------------------------------------------------+ void UpdateMarketStructure() { // Update HTF structure UpdateSwingPoints(HTF, htf_lastHigh, htf_prevHigh, htf_lastLow, htf_prevLow); // Update Analysis TF structure UpdateSwingPoints(ATF, atf_lastHigh, atf_prevHigh, atf_lastLow, atf_prevLow); if(DebugMode && atf_lastHigh.price > 0 && atf_lastLow.price > 0) { Print("Market Structure - ATF High: ", atf_lastHigh.price, " Low: ", atf_lastLow.price, " ATR: ", currentATR); } }
Die Funktion OnDeinit() sorgt für ein ordnungsgemäßes Beenden des Expert Advisors, indem sie das Handle des ATR-Indikators freigibt, um Systemressourcen freizugeben, und alle Chart-Objekte löscht, die mit Zonen und Trades in Verbindung stehen, wobei sie deren Präfixe verwendet. Anschließend wird das Chart aktualisiert, um verbleibende grafische Artefakte zu entfernen, und es wird eine Bestätigungsmeldung protokolliert. Dadurch wird sichergestellt, dass beim Entfernen oder Neustarten des EAs keine übrig gebliebenen Indikatoren, Zeichnungen oder Speicherzuweisungen zukünftige Ausführungen beeinträchtigen, wodurch die Stabilität gewahrt und Ressourcenlecks verhindert werden.
Die Funktion OnTick() bildet das operative Kernstück des EAs und koordiniert die Erkennung, Ausführung und Verwaltungslogik in Echtzeit. Bei jedem Tick aktualisiert das System die aktuelle ATR und prüft, ob sich auf dem Analyse-Zeitrahmen eine neue Kerze gebildet hat, bevor es die Marktstruktur neu berechnet und nach neuen Zonen für Displacement, Structure Transition und Liquidity Sweep sucht. Abgelaufene Zonen werden systematisch bereinigt, während die Einstiegsbedingungen bei jedem Tick kontinuierlich ausgewertet werden, um eine zeitnahe Handelsausführung zu gewährleisten. Offene Positionen werden aktiv verwaltet, und die visuellen Elemente werden aktualisiert, sofern diese Funktion aktiviert ist. Die Funktion UpdateMarketStructure() unterstützt diesen Arbeitsablauf, indem sie die Swing-Punkte sowohl in höheren als auch in Analyse-Zeitrahmen aktualisiert und so sicherstellt, dass der EA stets an den sich verändernden strukturellen Kontext und die Volatilitätsbedingungen angepasst bleibt.
//+------------------------------------------------------------------+ //| Detect Displacement Zones | //+------------------------------------------------------------------+ void DetectDisplacementZones() { MqlRates rates[]; ArraySetAsSeries(rates, true); int copied = CopyRates(_Symbol, ATF, 0, 5, rates); if(copied < 2) return; // Check latest candle for displacement MqlRates candle = rates[0]; MqlRates prevCandle = rates[1]; // Calculate candle metrics double range = candle.high - candle.low; if(range <= 0) return; double body = MathAbs(candle.close - candle.open); double bodyPercent = (body / range) * 100; // Calculate overlap with previous candle double overlapHigh = MathMin(candle.high, prevCandle.high); double overlapLow = MathMax(candle.low, prevCandle.low); double overlap = (overlapHigh > overlapLow) ? overlapHigh - overlapLow : 0; double overlapPercent = (overlap / range) * 100; // Check close near extreme (for bullish or bearish) double closeToHigh = candle.high - candle.close; double closeToLow = candle.close - candle.low; double closePercent; if(candle.close > candle.open) // Bullish candle closePercent = (closeToHigh / range) * 100; else // Bearish candle closePercent = (closeToLow / range) * 100; // Debug information if(DebugMode) { Print("Checking displacement - Range: ", range, " ATR*Mult: ", currentATR * Displacement_ATR_Multiplier, " Body%: ", bodyPercent, " Req: ", Displacement_Body_Percent, " Overlap%: ", overlapPercent, " Max: ", Displacement_Overlap_Max, " Close%: ", closePercent, " Max: ", Displacement_Close_Extreme); } // Check displacement conditions bool isDisplacement = (range >= currentATR * Displacement_ATR_Multiplier) && (bodyPercent >= Displacement_Body_Percent) && (overlapPercent <= Displacement_Overlap_Max) && (closePercent <= Displacement_Close_Extreme); if(isDisplacement) { // Check if similar zone already exists (within last 5 bars) bool zoneExists = false; for(int i = 0; i < zoneCount; i++) { if(zones[i].active && MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5) { zoneExists = true; break; } } if(!zoneExists) { // Create displacement zone TradeZone zone; zone.time = candle.time; zone.high = candle.high; zone.low = candle.low; zone.mid = (zone.high + zone.low) / 2.0; zone.type = ZONE_DISPLACEMENT; zone.direction = (candle.close > candle.open) ? 1 : -1; zone.atr = currentATR; zone.expiry = zone.time + (24 * 60 * 60); // 24 hour expiry zone.active = true; zone.triggered = false; zone.symbol = _Symbol; zone.tf = ATF; // Add to zones array int index = zoneCount % 100; zones[index] = zone; zoneCount++; Print("Displacement Zone Detected: ", (zone.direction == 1) ? "BULLISH" : "BEARISH", " | High: ", zone.high, " Low: ", zone.low, " | Time: ", TimeToString(zone.time)); } } } //+------------------------------------------------------------------+ //| Detect Structure Transition Zones (CHoCH) | //+------------------------------------------------------------------+ void DetectStructureTransitionZones() { // Check for bullish CHoCH (downtrend to uptrend) if(atf_prevHigh.price > 0 && atf_lastHigh.price > atf_prevHigh.price && atf_prevLow.price > 0 && atf_lastLow.price > atf_prevLow.price) { // Bullish transition - look for last bearish candle before break MqlRates rates[]; ArraySetAsSeries(rates, true); int startBar = MathMax(0, atf_lastHigh.barIndex - 10); int copied = CopyRates(_Symbol, ATF, startBar, 20, rates); if(DebugMode) Print("Checking Bullish CHoCH - PrevHigh: ", atf_prevHigh.price, " LastHigh: ", atf_lastHigh.price); for(int i = 0; i < copied; i++) { if(rates[i].close < rates[i].open) // Bearish candle { CreateStructureTransitionZone(rates[i], 1); break; } } } // Check for bearish CHoCH (uptrend to downtrend) if(atf_prevLow.price > 0 && atf_lastLow.price < atf_prevLow.price && atf_prevHigh.price > 0 && atf_lastHigh.price < atf_prevHigh.price) { // Bearish transition - look for last bullish candle before break MqlRates rates[]; ArraySetAsSeries(rates, true); int startBar = MathMax(0, atf_lastLow.barIndex - 10); int copied = CopyRates(_Symbol, ATF, startBar, 20, rates); if(DebugMode) Print("Checking Bearish CHoCH - PrevLow: ", atf_prevLow.price, " LastLow: ", atf_lastLow.price); for(int i = 0; i < copied; i++) { if(rates[i].close > rates[i].open) // Bullish candle { CreateStructureTransitionZone(rates[i], -1); break; } } } }
Die Funktion DetectDisplacementZones() analysiert die jüngste Kerze im Analysezeitrahmen und prüft anhand strenger quantitativer Filter, ob es sich um eine echte Displacement-Bewegung handelt. Es berechnet die Gesamtspanne der Kerze, den prozentualen Anteil der Kerzenkörpergröße, die Überlappung mit der vorherigen Kerze und wie nah der Schlusskurs am Extremwert der Kerze liegt. Diese Kennzahlen werden mit ATR-basierten und prozentualen Schwellenwerten verglichen, um eine starke Marktintention mit geringer Überlappung und hohem Momentum zu bestätigen. Wenn alle Displacement-Bedingungen erfüllt sind und noch keine ähnliche Zone aus der jüngsten Vergangenheit existiert, erstellt die Funktion ein neues TradeZone-Objekt, weist diesem Preisgrenzen, Mittelpunkt, Richtung, ATR-Kontext, Ablaufzeit und Aktivierungsstatus zu und speichert es anschließend im zones-Array. Dadurch wird sichergestellt, dass nur statistisch signifikante impulsartige Kursbewegungen als potenzielle Rückkehrzonen erfasst werden.
Die Funktion DetectStructureTransitionZones() konzentriert sich auf die Identifizierung von Change of Character (CHoCH)-Ereignissen durch die Analyse von Veränderungen der Swing-Struktur auf dem Analyse-Zeitrahmen. Ein bullischer Übergang wird erkannt, wenn sich nach einem vorangegangenen Abwärtstrend sowohl höhere Hochs als auch höhere Tiefs bilden, während ein bärischer Übergang festgestellt wird, wenn niedrigere Tiefs und niedrigere Hochs einen vorangegangenen Aufwärtstrend durchbrechen. Sobald sich ein Strukturwechsel bestätigt hat, sucht die Funktion rückwärts nach der letzten entgegengesetzten Kerze vor dem Durchbruch – bei Übergängen von bärisch zu bullisch ist dies eine bärische Kerze, bei Übergängen von bullisch zu bärisch eine bullische Kerze – und nutzt diese, um mithilfe einer speziellen Erstellungsfunktion eine Strukturübergangszone zu erstellen.
//+------------------------------------------------------------------+ //| Detect Liquidity Sweep Zones | //+------------------------------------------------------------------+ void DetectLiquiditySweepZones() { MqlRates rates[]; ArraySetAsSeries(rates, true); int copied = CopyRates(_Symbol, ATF, 0, 5, rates); if(copied < 3) return; // Check for buy-side liquidity sweep (sweep of lows) if(atf_lastLow.price > 0 && rates[1].low < atf_lastLow.price) { // Check if there's a bullish reversal after the sweep bool bullishReversal = rates[1].close > rates[1].open || (rates[0].close > rates[0].open && rates[0].close > rates[1].high); if(bullishReversal) { if(DebugMode) Print("Buy-side Liquidity Sweep detected - Swept Low: ", atf_lastLow.price, " Sweep Candle Low: ", rates[1].low); CreateLiquiditySweepZone(rates[1], 1); // Bullish reversal } } // Check for sell-side liquidity sweep (sweep of highs) if(atf_lastHigh.price > 0 && rates[1].high > atf_lastHigh.price) { // Check if there's a bearish reversal after the sweep bool bearishReversal = rates[1].close < rates[1].open || (rates[0].close < rates[0].open && rates[0].close < rates[1].low); if(bearishReversal) { if(DebugMode) Print("Sell-side Liquidity Sweep detected - Swept High: ", atf_lastHigh.price, " Sweep Candle High: ", rates[1].high); CreateLiquiditySweepZone(rates[1], -1); // Bearish reversal } } } //+------------------------------------------------------------------+ //| Create Structure Transition Zone | //+------------------------------------------------------------------+ void CreateStructureTransitionZone(MqlRates &candle, int direction) { // Check if similar zone already exists (within last 5 bars) bool zoneExists = false; for(int i = 0; i < zoneCount; i++) { if(zones[i].active && zones[i].type == ZONE_STRUCTURE_TRANSITION && MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5) { zoneExists = true; break; } } if(!zoneExists) { TradeZone zone; zone.time = candle.time; zone.high = candle.high; zone.low = candle.low; zone.mid = (zone.high + zone.low) / 2.0; zone.type = ZONE_STRUCTURE_TRANSITION; zone.direction = direction; zone.atr = currentATR; zone.expiry = zone.time + (12 * 60 * 60); // 12 hour expiry zone.active = true; zone.triggered = false; zone.symbol = _Symbol; zone.tf = ATF; // Add to zones array int index = zoneCount % 100; zones[index] = zone; zoneCount++; Print("Structure Transition Zone Detected: ", (zone.direction == 1) ? "BULLISH (CHoCH)" : "BEARISH (CHoCH)", " | High: ", zone.high, " Low: ", zone.low, " | Time: ", TimeToString(zone.time)); } }
Die Funktion DetectLiquiditySweepZones() automatisiert die Erkennung von Stop-Hunts, indem sie die jüngsten Kerzen nach Kursbewegungen absucht, die über frühere Swing-Hochs oder -Tiefs hinausgehen. Sie prüft, ob der Kurs vorübergehend ein bekanntes Swing-Niveau durchbricht, und bestätigt anschließend, dass eine Trendumkehr folgt – entweder noch innerhalb derselben Kerze oder in der nächsten. Wird ein vorheriges Tief durchbrochen und folgt darauf eine bullische Bestätigung, entsteht eine Liquiditäts-Sweep auf der Kaufseite; wird hingegen ein vorheriges Hoch durchbrochen und folgt darauf eine bärische Bestätigung, bildet sich eine Liquiditäts-Sweep auf der Verkaufsseite. Debug-Ausgaben sorgen für Transparenz während der Erkennung.
Die Funktion CreateStructureTransitionZone() regelt die Erstellung einer CHoCH-basierten Zone, sobald ein Strukturbruch identifiziert wurde. Bevor eine neue Zone erstellt wird, wird zunächst geprüft, ob innerhalb eines kürzlichen Zeitfensters bereits eine ähnliche aktive Strukturübergangszone existiert, um Doppelungen zu vermeiden. Wird keine entsprechende Zone aus der jüngeren Vergangenheit gefunden, initialisiert das System ein neues TradeZone-Objekt anhand der Kerze, die dem strukturellen Bruch vorausging, und legt dabei die Kursgrenzen, den Mittelpunkt, die Richtung (bullisch oder bärisch), den ATR-Kontext sowie eine Laufzeit von 12 Stunden fest. Die Zone wird anschließend mithilfe eines rollierenden Indexmechanismus im Zonen-Array gespeichert, zur Überwachung aktiviert und auf dem Terminal protokolliert, wodurch sichergestellt wird, dass jeder strukturelle Übergang sauber erfasst und innerhalb des Speicherrahmens der EA verwaltet wird.
//+------------------------------------------------------------------+ //| Check Zone Entries | //+------------------------------------------------------------------+ void CheckZoneEntries() { // Reset daily trade count if new day TimeToStruct(TimeCurrent(), today); if(lastTradeDay != today.day) { tradesToday = 0; lastTradeDay = today.day; if(DebugMode) Print("New day started. Trade counter reset."); } // Check if we can take more trades today if(MaxTradesPerDay > 0 && tradesToday >= MaxTradesPerDay) { if(tradesToday == MaxTradesPerDay) Print("Maximum daily trades (", MaxTradesPerDay, ") reached for today."); return; } double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); for(int i = 0; i < zoneCount; i++) { if(!zones[i].active || zones[i].triggered) continue; // Check if zone is expired if(TimeCurrent() > zones[i].expiry) { zones[i].active = false; continue; } // Check BUY entries (bullish zones) if(zones[i].direction == 1) { // Check if price is returning to zone (using Ask for buys) if(currentAsk >= zones[i].low && currentAsk <= zones[i].high) { // Get LTF confirmation if(CheckLTFConfirmation(true)) { Print("BUY Signal: Price in ", ZoneTypeToString(zones[i].type), " zone | Entry: ", currentAsk, " Zone: [", zones[i].low, "-", zones[i].high, "]"); ExecuteZoneTrade(true, zones[i], currentAsk); zones[i].triggered = true; tradesToday++; break; // Take only one trade per tick } } } // Check SELL entries (bearish zones) else if(zones[i].direction == -1) { // Check if price is returning to zone (using Bid for sells) if(currentBid >= zones[i].low && currentBid <= zones[i].high) { // Get LTF confirmation if(CheckLTFConfirmation(false)) { Print("SELL Signal: Price in ", ZoneTypeToString(zones[i].type), " zone | Entry: ", currentBid, " Zone: [", zones[i].low, "-", zones[i].high, "]"); ExecuteZoneTrade(false, zones[i], currentBid); zones[i].triggered = true; tradesToday++; break; // Take only one trade per tick } } } } } //+------------------------------------------------------------------+ //| Check LTF Confirmation | //+------------------------------------------------------------------+ bool CheckLTFConfirmation(bool forBuy) { MqlRates ltfRates[]; ArraySetAsSeries(ltfRates, true); int copied = CopyRates(_Symbol, LTF, 0, 5, ltfRates); if(copied < 3) return false; // For BUY confirmation if(forBuy) { // Simple confirmation: bullish candle closing above midpoint if(ltfRates[0].close > ltfRates[0].open && ltfRates[0].close > ((ltfRates[0].high + ltfRates[0].low) / 2)) { return true; } // Alternative: bullish engulfing if(ltfRates[0].close > ltfRates[0].open && ltfRates[0].close > ltfRates[1].high && ltfRates[0].open < ltfRates[1].low) { return true; } } // For SELL confirmation else { // Simple confirmation: bearish candle closing below midpoint if(ltfRates[0].close < ltfRates[0].open && ltfRates[0].close < ((ltfRates[0].high + ltfRates[0].low) / 2)) { return true; } // Alternative: bearish engulfing if(ltfRates[0].close < ltfRates[0].open && ltfRates[0].close < ltfRates[1].low && ltfRates[0].open > ltfRates[1].high) { return true; } } return false; }
Hier implementieren wir die zentrale Ausführungs-Engine, die entscheidet, wann aus einer erkannten ‚Market Memory Zone‘ ein konkreter Trade wird. Die Funktion beginnt damit, die täglichen Handelslimits zu verwalten, den Zähler zu Beginn eines neuen Handelstags zurückzusetzen und weitere Einstiege zu verhindern, sobald die maximale Anzahl an Trades erreicht ist. Anschließend ruft es die aktuellen Geld- und Briefkurse ab und durchläuft alle aktiven, noch nicht ausgelösten Zonen. Für jede Zone werden zunächst die Ablaufbedingungen überprüft, um sicherzustellen, dass veraltete Zonen deaktiviert werden. Kehrt der Kurs innerhalb der Grenzen einer gültigen Zone zurück, prüft der EA, ob es sich um ein bullisches oder bärisches Setup handelt, und fordert anschließend eine Bestätigung auf einem niedrigeren Zeitrahmen an, bevor er einen Trade ausführt. Sobald ein Handel platziert wurde, wird die Zone als ausgelöst markiert, um doppelte Einstiege zu verhindern, und die tägliche Handelsanzahl wird erhöht, wodurch eine disziplinierte Ausführungssteuerung gewährleistet wird.
Die Funktion CheckLTFConfirmation() führt vor dem Absenden einer Order eine Verhaltensprüfung durch und dient dabei als Verfeinerungsfilter, um blinden Handel allein auf Basis eines Levels zu vermeiden. Bei Kaufsignalen bestätigt es entweder eine starke bullische Kerze, die über ihrem Mittelpunkt schließt, oder ein bullisches Engulfing-Muster; bei Verkaufssignalen sucht es nach den bärischen Entsprechungen. Indem der EA Momentum-Signale aus dem niedrigeren Zeitrahmen verlangt, stellt er sicher, dass Einstiegspunkte auf Reaktionen und Tendenzen basieren und nicht auf bloßen Preiskontakten innerhalb einer Zone.
//+------------------------------------------------------------------+ //| Execute Zone Trade | //+------------------------------------------------------------------+ void ExecuteZoneTrade(bool isBuy, TradeZone &zone, double entryPrice) { // Check if position already exists for this symbol if(PositionSelect(_Symbol)) { if(!AllowConsecutiveTrades) { Print("Position already exists and AllowConsecutiveTrades is false, skipping trade"); return; } } ENUM_ORDER_TYPE orderType = isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; // Calculate stop loss double stopLoss = CalculateZoneStopLoss(isBuy, zone, entryPrice); // Calculate take profit double takeProfit = CalculateZoneTakeProfit(isBuy, zone, entryPrice, stopLoss); // Calculate lot size double volume = CalculateLotSize(entryPrice, stopLoss); if(volume <= 0) { Print("Invalid lot size calculated: ", volume, ". Trade cancelled."); return; } // Execute trade string comment = StringFormat("%s_%s_%s", (isBuy ? "BUY" : "SELL"), ZoneTypeToString(zone.type), TimeToString(TimeCurrent(), TIME_MINUTES)); bool success = trade.PositionOpen(_Symbol, orderType, volume, entryPrice, stopLoss, takeProfit, comment); if(success) { ulong ticket = trade.ResultOrder(); Print("Trade Executed Successfully: ", comment, " | Ticket: ", ticket, " | Entry: ", entryPrice, " | SL: ", stopLoss, " | TP: ", takeProfit, " | Lots: ", volume); // Store trade info if(activeTradeCount < ArraySize(activeTrades)) { activeTrades[activeTradeCount].entryTime = TimeCurrent(); activeTrades[activeTradeCount].entryPrice = entryPrice; activeTrades[activeTradeCount].stopLoss = stopLoss; activeTrades[activeTradeCount].takeProfit = takeProfit; activeTrades[activeTradeCount].direction = isBuy ? 1 : -1; activeTrades[activeTradeCount].zone = zone; activeTrades[activeTradeCount].ticket = ticket; activeTradeCount++; } } else { Print("Trade failed: Error Code=", trade.ResultRetcode(), " Description=", trade.ResultRetcodeDescription()); } } //+------------------------------------------------------------------+ //| Calculate Zone Stop Loss | //+------------------------------------------------------------------+ double CalculateZoneStopLoss(bool isBuy, TradeZone &zone, double entryPrice) { double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(UseDynamicSL) { // Zone-based stop loss with buffer double buffer = 50 * point; // 50 pip buffer if(isBuy) { double sl = zone.low - buffer; // Ensure SL is below entry if(sl >= entryPrice) sl = entryPrice - (100 * point); return NormalizeDouble(sl, _Digits); } else { double sl = zone.high + buffer; // Ensure SL is above entry if(sl <= entryPrice) sl = entryPrice + (100 * point); return NormalizeDouble(sl, _Digits); } } else { // Fixed pip stop loss double slDistance = StopLoss_Pips * 10 * point; if(isBuy) return NormalizeDouble(entryPrice - slDistance, _Digits); else return NormalizeDouble(entryPrice + slDistance, _Digits); } } //+------------------------------------------------------------------+ //| Calculate Zone Take Profit | //+------------------------------------------------------------------+ double CalculateZoneTakeProfit(bool isBuy, TradeZone &zone, double entry, double sl) { double risk = MathAbs(entry - sl); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(UseStructureTP) { if(isBuy) { // Take profit at next structure high double target = entry + (risk * RiskRewardRatio); if(atf_lastHigh.price > entry) target = atf_lastHigh.price; else if(htf_lastHigh.price > entry) target = htf_lastHigh.price; // Ensure minimum distance double minDistance = 50 * point; if(target - entry < minDistance) target = entry + (risk * RiskRewardRatio); return NormalizeDouble(target, _Digits); } else { // Take profit at next structure low double target = entry - (risk * RiskRewardRatio); if(atf_lastLow.price < entry) target = atf_lastLow.price; else if(htf_lastLow.price < entry) target = htf_lastLow.price; // Ensure minimum distance double minDistance = 50 * point; if(entry - target < minDistance) target = entry - (risk * RiskRewardRatio); return NormalizeDouble(target, _Digits); } } else { // Fixed risk/reward ratio if(isBuy) return NormalizeDouble(entry + (risk * RiskRewardRatio), _Digits); else return NormalizeDouble(entry - (risk * RiskRewardRatio), _Digits); } }
Die Funktion ExecuteZoneTrade() verwaltet den gesamten Prozess der Ausführung eines Trades, sobald ein gültiger Zoneneintritt bestätigt wurde. Zunächst wird geprüft, ob für das Wertpapier bereits eine Position besteht, und die Einstellung AllowConsecutiveTrades wird berücksichtigt, um ein unerwünschtes Aufstocken von Positionen zu verhindern. Die Funktion ermittelt anschließend die Orderart (Kauf oder Verkauf), berechnet anhand einer speziellen, auf Zonen basierenden Logik einen Stop-Loss und einen Take-Profit und ermittelt auf der Grundlage von Risikoparametern die entsprechende Lotgröße. Ist das berechnete Volumen gültig, sendet es den Trade über PositionOpen() und fügt einen strukturierten Kommentar hinzu, der die Richtung, den Zonentyp und den Zeitstempel enthält. Bei erfolgreicher Ausführung werden die Handelsdetails zur späteren Verwaltung und Nachverfolgung im Array activeTrades gespeichert; sollte die Order fehlschlagen, protokolliert das System den Rückgabecode und die Beschreibung des Brokers zu Debugging-Zwecken.
Die Stop-Loss- und Take-Profit-Berechnungen sind so ausgelegt, dass sie mit dem Market Memory-Konzept im Einklang stehen. Wenn UseDynamicSL aktiviert ist, wird der Stop-Loss mit einem Sicherheitspuffer außerhalb der Zonengrenze platziert, wodurch eine logische Ungültigkeitserklärung gewährleistet wird, falls der Kurs die Zone vollständig durchbricht; andernfalls wird ein fester, auf Pips basierender Stop angewendet. Der Take-Profit kann dynamisch auf die jüngsten strukturellen Hochs oder Tiefs aus der Analyse oder einem höheren Zeitrahmen ausgerichtet werden, wenn UseStructureTP aktiviert ist, wobei jedoch eine Mindestabstandsanforderung eingehalten wird, um zu enge Ziele zu vermeiden. Ist das strukturbasierte Targeting deaktiviert, wird stattdessen ein festgelegtes Risiko-Ertrags-Verhältnis angewendet.
//+------------------------------------------------------------------+ //| Update Visualization | //+------------------------------------------------------------------+ void UpdateVisualization() { ObjectsDeleteAll(0, "ZONE_"); ObjectsDeleteAll(0, "TRADE_"); // Draw zones int activeZoneCount = 0; for(int i = 0; i < zoneCount; i++) { if(zones[i].active) { DrawZone(zones[i], i); activeZoneCount++; } } // Draw structure levels if(ShowLabels) { if(atf_lastHigh.price > 0) CreateHorizontalLine("ATF_High", atf_lastHigh.price, clrRed, STYLE_DASH); if(atf_lastLow.price > 0) CreateHorizontalLine("ATF_Low", atf_lastLow.price, clrBlue, STYLE_DASH); if(htf_lastHigh.price > 0) CreateHorizontalLine("HTF_High", htf_lastHigh.price, clrDarkRed, STYLE_DOT); if(htf_lastLow.price > 0) CreateHorizontalLine("HTF_Low", htf_lastLow.price, clrDarkBlue, STYLE_DOT); } // Show zone count in corner if(ShowLabels) { string labelText = "Active Zones: " + IntegerToString(activeZoneCount); CreateLabel("ZoneCount", labelText, 10, 180, clrWhite); } } //+------------------------------------------------------------------+ //| Draw Zone | //+------------------------------------------------------------------+ void DrawZone(TradeZone &zone, int index) { string name = "ZONE_" + IntegerToString(index); // Calculate zone width (4 bars forward) datetime endTime = zone.time + (PeriodSeconds(zone.tf) * 4); // Draw rectangle if(ObjectCreate(0, name, OBJ_RECTANGLE, 0, zone.time, zone.high, endTime, zone.low)) { // Set color based on zone type and direction color zoneColor; int zoneStyle = STYLE_SOLID; switch(zone.type) { case ZONE_DISPLACEMENT: zoneColor = (zone.direction == 1) ? clrLimeGreen : clrIndianRed; zoneStyle = STYLE_SOLID; break; case ZONE_STRUCTURE_TRANSITION: zoneColor = (zone.direction == 1) ? clrDodgerBlue : clrOrangeRed; zoneStyle = STYLE_DASH; break; case ZONE_LIQUIDITY_SWEEP: zoneColor = (zone.direction == 1) ? clrGold : clrMagenta; zoneStyle = STYLE_DOT; break; default: zoneColor = clrGray; } ObjectSetInteger(0, name, OBJPROP_COLOR, zoneColor); ObjectSetInteger(0, name, OBJPROP_STYLE, zoneStyle); ObjectSetInteger(0, name, OBJPROP_WIDTH, 2); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_FILL, true); ObjectSetInteger(0, name, OBJPROP_ZORDER, 0); // Add label if(ShowLabels) { string labelName = name + "_LABEL"; if(ObjectCreate(0, labelName, OBJ_TEXT, 0, zone.time, zone.high)) { string typeText; switch(zone.type) { case ZONE_DISPLACEMENT: typeText = "DISP"; break; case ZONE_STRUCTURE_TRANSITION: typeText = "CHoCH"; break; case ZONE_LIQUIDITY_SWEEP: typeText = "LIQ"; break; default: typeText = "UNK"; } string directionText = (zone.direction == 1) ? "BULL" : "BEAR"; ObjectSetString(0, labelName, OBJPROP_TEXT, typeText + " " + directionText); ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, labelName, OBJPROP_BACK, true); } } if(DebugMode && ShowZones) Print("Drawing zone: ", name, " Type: ", ZoneTypeToString(zone.type), " Dir: ", zone.direction, " High: ", zone.high, " Low: ", zone.low); } }
Die Funktion UpdateVisualization() verwaltet alle grafischen Elemente im Chart, um sicherzustellen, dass die Darstellung den aktuellen internen Zustand des EAs korrekt widerspiegelt. Zunächst werden zuvor gezeichnete Bereiche und Handelsobjekte gelöscht, um Doppelungen oder veraltete Darstellungen zu vermeiden. Anschließend durchläuft das Programm alle gespeicherten Zonen und zeichnet nur diejenigen neu, die weiterhin aktiv sind, wodurch der Chart mit der aktuellen Market-Memory-Logik synchronisiert bleibt. Ist die Beschriftungsanzeige aktiviert, zeichnet die Funktion zudem horizontale Linien ein, die die jüngsten Swing-Hochs und -Tiefs sowohl aus der Analyse als auch aus höheren Zeitrahmen darstellen und so einen klaren strukturellen Kontext liefern.
Die Funktion DrawZone() übernimmt die detaillierte Darstellung jeder einzelnen Zone. Es erstellt ein nach vorne gerichtetes Rechteck, das sich aus dem Startzeitpunkt und den Preisgrenzen der Zone ergibt, und dehnt es über mehrere Kerzen in die Zukunft aus, um ihren Einflussbereich zu veranschaulichen. Jedem Zonentyp – Displacement, Structure Transition (CHoCH) oder Liquidity Sweep – ist eine eigene Farbe und ein eigener Linienstil zugeordnet, wobei bullische und bärische Richtungen optisch voneinander unterschieden werden. Optionale Textbeschriftungen geben den Zonentyp und die Richtung an (z. B. DISP BULL oder CHoCH BEAR), sodass die Interpretation auf einen Blick möglich ist.
Backtest-Ergebnisse
Das Backtesting wurde auf dem H1-Zeitrahmen über einen Testzeitraum von 1. Juni 2025 bis 14. August 2025 mit den Standardeinstellungen ausgewertet:


Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir das Konzept der Market Memory Zones von einem visuellen Analysewerkzeug in eine vollautomatische Handelsmaschine umgewandelt haben, indem wir dessen strukturelle Logik direkt in den Expert Advisor integriert haben. Wir haben eine systematische Erkennung von Displacement Candles, Strukturübergängen (CHoCH) und Liquiditäts-Sweeps implementiert und diese Zonen anschließend in einem kontrollierten Ringpuffer gespeichert, um die langfristige Stabilität während des Backtestings und der Live-Ausführung zu gewährleisten. Jede Zone enthält nun Kontextdaten wie Richtung, ATR-Bedingungen, Ablaufzeitpunkt und Strukturverweise, sodass der EA das Kursverhalten in Echtzeit überwachen kann. Automatisierte Einstiegslogik, Bestätigung auf einem niedrigeren Zeitrahmen, dynamische Stop-Loss-Platzierung, strukturbasierte Take-Profit-Ziele und risikobasierte Positionsgrößenbestimmung vollenden den Übergang von der diskretionären Interpretation zur regelgesteuerten Ausführung.
Zusammenfassend lässt sich sagen, dass die Automatisierung von Market Memory Zones emotionale Verzerrungen beseitigt und gleichzeitig die strukturelle Intelligenz hinter der Strategie bewahrt. Das System verfolgt kontinuierlich, wohin der Kurs voraussichtlich zurückkehren wird, überprüft Reaktionen anhand von Bestätigungen aus mehreren Zeitrahmen und führt Trades mit vordefiniertem Risikomanagement aus – wodurch der gesamte Prozess konsistent, skalierbar und überprüfbar wird. Durch die Umwandlung des Preisgedächtnisses in strukturierte, programmierbare Logik gewinnen Händler an Klarheit und Wiederholbarkeit und erhalten die Möglichkeit, die Performance über verschiedene Instrumente und Zeiträume hinweg zu optimieren.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21255
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.
Vom Anfänger zum Experten: Automatisierung von Intraday-Strategien
MQL5 Trading Tools (Teil 20): Grafiken auf Zeichenflächen mit statistischer Korrelations- und linearer Regressionsanalyse
MQL5-Handelswerkzeuge (Teil 21): Hinzufügen eines Cyberpunk-Theme zu Regressionsdiagrammen
Vom Neuling zum Experten: Erweiterung einer Liquiditätsstrategie mit Trendfiltern
- 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.