English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 36): Handel mit Angebot und Nachfrage mit Retest und Impulsmodell

Automatisieren von Handelsstrategien in MQL5 (Teil 36): Handel mit Angebot und Nachfrage mit Retest und Impulsmodell

MetaTrader 5Handel |
39 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel (Teil 35) haben wir das Handelssystem Breaker Block in MetaQuotes Language 5 (MQL5) entwickelt, das Konsolidierungsbereiche identifiziert, Ausbruchsblöcke mit Umkehrpunkten validiert und Retests mit anpassbaren Risikoparametern und visuellem Feedback gehandelt hat. In Teil 36 entwickeln wir ein Handelssystem auf Basis von Angebot und Nachfrage, das ein Retest- und Impulsmodell verwendet. Dieses Modell erkennt Angebots- und Nachfragezonen durch Konsolidierung, validiert sie mit impulsiven Bewegungen und führt Handelsgeschäfte bei Retests mit Trendbestätigung und dynamischen Chartvisualisierungen aus. Wir werden die folgenden Themen behandeln:

  1. Verständnis des Systems der Angebots- und Nachfragestrategie
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie eine funktionierende MQL5-Strategie für den Handel der Tests von Angebots- und Nachfragezonen, die Sie anpassen können - legen wir los!


Verständnis des Systems der Angebots- und Nachfragestrategie

Die Angebots- und Nachfragestrategie identifizieren wichtige Preiszonen, in denen bedeutende Käufe (Nachfrage) oder Verkäufe (Angebot) stattgefunden haben, typischerweise nach Phasen der Konsolidierung. Nachdem eine impulsive Kursbewegung die Gültigkeit einer Zone bestätigt hat, zielen Händler darauf ab, die Zone erneut zu testen. Sie können in Kaufgeschäfte einsteigen, wenn der Kurs in einem Abwärtstrend wieder in eine Nachfragezone eintritt, oder sie können in Erwartung einer Erholung an einer Angebotszone in einem Aufwärtstrend Verkaufstransaktionen einleiten. Durch das Festlegen von Risiko- und Ertragsniveaus können Händler aus Setups mit hoher Wahrscheinlichkeit Kapital schlagen. Sehen Sie sich unten die verschiedenen Möglichkeiten an, die wir haben.

Einrichtung der Angebotszone:

EINRICHTUNG DER ANGEBOTSZONE

Einrichtung der Nachfragezone:

EINRICHTUNG DER NACHFRAGEZONE

Unser Plan ist es, Konsolidierungsbereiche über eine bestimmte Anzahl von Balken zu erkennen, Zonen mit impulsiven Bewegungen anhand eines auf einem Multiplikator basierenden Schwellenwerts zu validieren und Handelseinträge mit optionalen Trendprüfungen zu bestätigen. Wir implementieren eine Logik zur Verfolgung des Zonenstatus, führen Handelsgeschäfte bei Retests mit anpassbaren Stop-Loss- und Take-Profit-Einstellungen aus und visualisieren die Zonen mit dynamischen Labels und Farben, um ein System für präzisen Angebots- und Nachfragehandel zu schaffen. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

SYSTEM VON ANGEBOT UND NACHFRAGE


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 das erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                         Supply and Demand EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

#include <Trade/Trade.mqh>                         //--- Include Trade library for position management
CTrade obj_Trade;                                  //--- Instantiate trade object for order operations

Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade/Trade.mqh>“ einbinden, die integrierte Funktionen für die Verwaltung von Handelsoperationen bietet. Anschließend initialisieren wir das Handelsobjekt „obj_Trade“ mit der Klasse CTrade, sodass der Expert Advisor Kauf- und Verkaufsaufträge programmatisch ausführen kann. So wird sichergestellt, dass die Ausführung von Handelsgeschäften effizient und ohne manuelle Eingriffe erfolgt. Dann können wir einige Enumerationen deklarieren, die eine Klassifizierung einiger Nutzereingaben ermöglichen.

//+------------------------------------------------------------------+
//| Enum for trading tested zones                                    |
//+------------------------------------------------------------------+
enum TradeTestedZonesMode {                        // Define modes for trading tested zones
   NoRetrade,                                      // Trade zones only once
   LimitedRetrade,                                 // Trade zones up to a maximum number of times
   UnlimitedRetrade                                // Trade zones as long as they are valid
};

//+------------------------------------------------------------------+
//| Enum for broken zones validation                                 |
//+------------------------------------------------------------------+
enum BrokenZonesMode {                             // Define modes for broken zones validation
   AllowBroken,                                    // Zones can be marked as broken
   NoBroken                                        // Zones remain testable regardless of price break
};

//+------------------------------------------------------------------+
//| Enum for zone size restriction                                   |
//+------------------------------------------------------------------+
enum ZoneSizeMode {                                // Define modes for zone size restrictions
   NoRestriction,                                  // No restriction on zone size
   EnforceLimits                                   // Enforce minimum and maximum zone points
};

//+------------------------------------------------------------------+
//| Enum for trend confirmation                                      |
//+------------------------------------------------------------------+
enum TrendConfirmationMode {                       // Define modes for trend confirmation
   NoConfirmation,                                 // No trend confirmation required
   ConfirmTrend                                    // Confirm trend before trading on tap
};

Wir deklarieren einige wichtige Enumerationen, um das Handelsverhalten und die Zonenvalidierung zu konfigurieren. Zunächst erstellen wir das Enum „TradeTestedZonesMode“ mit Optionen: „NoRetrade“ (einmaliger Handel mit Zonen), „LimitedRetrade“ (Handel bis zu einer bestimmten Grenze) und „UnlimitedRetrade“ (Handel während der Gültigkeitsdauer), die festlegen, wie oft Zonen gehandelt werden können. Dann definieren wir das Enum „BrokenZonesMode“ mit „AllowBroken“ (Zonen als gebrochen markieren, wenn der Preis sie durchbricht) und „NoBroken“ (Zonen bleiben testbar), die die Gültigkeit der Zonen nach Ausbrüchen bestimmen. Als Nächstes implementieren wir das Enum „ZoneSizeMode“ mit „NoRestriction“ (keine Größenbeschränkung) und „EnforceLimits“ (Beschränkung der Zonengröße innerhalb der Grenzen), um sicherzustellen, dass die Zonen die Größenkriterien erfüllen.

Schließlich fügen wir das Enum „TrendConfirmationMode“ mit „NoConfirmation“ (keine Trendprüfung) und „ConfirmTrend“ (Trendbestätigung erforderlich) hinzu, um eine optionale trendbasierte Handelsfilterung zu ermöglichen. Dadurch wird das System eine flexible Konfiguration für den Zonenhandel und die Validierungsregeln erhalten. Wir können diese Enumerationen verwenden, um unsere Nutzereingaben zu erstellen.

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input double tradeLotSize = 0.01;                   // Trade size in lots
input bool   enableTrading = true;                  // Enable automated trading
input bool   enableTrailingStop = true;             // Enable trailing stop
input double trailingStopPoints = 30;               // Trailing stop points
input double minProfitToTrail = 50;                 // Minimum trailing points
input int    uniqueMagicNumber = 12345;             // Magic Number
input int    consolidationBars = 5;                 // Consolidation range bars
input double maxConsolidationSpread = 30;           // Maximum allowed spread in points for consolidation
input double stopLossDistance = 200;                // Stop loss in points
input double takeProfitDistance = 400;              // Take profit in points
input double minMoveAwayPoints = 50;                // Minimum points price must move away before zone is ready
input bool   deleteBrokenZonesFromChart = false;    // Delete broken zones from chart
input bool   deleteExpiredZonesFromChart = false;   // Delete expired zones from chart
input int    zoneExtensionBars = 150;               // Number of bars to extend zones to the right
input bool   enableImpulseValidation = true;        // Enable impulse move validation
input int    impulseCheckBars = 3;                  // Number of bars to check for impulsive move
input double impulseMultiplier = 1.0;               // Multiplier for impulsive threshold
input TradeTestedZonesMode tradeTestedMode = NoRetrade; // Mode for trading tested zones
input int    maxTradesPerZone = 2;                  // Maximum trades per zone for LimitedRetrade
input BrokenZonesMode brokenZoneMode = AllowBroken; // Mode for broken zones validation
input color  demandZoneColor = clrBlue;             // Color for untested demand zones
input color  supplyZoneColor = clrRed;              // Color for untested supply zones
input color  testedDemandZoneColor = clrBlueViolet; // Color for tested demand zones
input color  testedSupplyZoneColor = clrOrange;     // Color for tested supply zones
input color  brokenZoneColor = clrDarkGray;         // Color for broken zones
input color  labelTextColor = clrBlack;             // Color for text labels
input ZoneSizeMode zoneSizeRestriction = NoRestriction; // Zone size restriction mode
input double minZonePoints = 50;                    // Minimum zone size in points
input double maxZonePoints = 300;                   // Maximum zone size in points
input TrendConfirmationMode trendConfirmation = NoConfirmation; // Trend confirmation mode
input int    trendLookbackBars = 10;                // Number of bars for trend confirmation
input double minTrendPoints = 1;                    // Minimum points for trend confirmation

Hier legen wir die Konfigurations der Eingabeparameter für unser System fest, um sein Handels- und Visualisierungsverhalten zu definieren. Wir haben selbsterklärende Kommentare hinzugefügt, um alles einfach und unkompliziert zu gestalten. Da wir mehrere Angebots- und Nachfragezonen verwalten werden, müssen wir schließlich eine Struktur angeben, in der wir die Informationen der Zonen speichern, um die Verwaltung zu erleichtern.

//+------------------------------------------------------------------+
//| Structure for zone information                                   |
//+------------------------------------------------------------------+
struct SDZone {                                    //--- Define structure for supply/demand zones
   double   high;                                  //--- Store zone high price
   double   low;                                   //--- Store zone low price
   datetime startTime;                             //--- Store zone start time
   datetime endTime;                               //--- Store zone end time
   datetime breakoutTime;                          //--- Store breakout time
   bool     isDemand;                              //--- Indicate demand (true) or supply (false)
   bool     tested;                                //--- Track if zone was tested
   bool     broken;                                //--- Track if zone was broken
   bool     readyForTest;                          //--- Track if zone is ready for testing
   int      tradeCount;                            //--- Track number of trades on zone
   string   name;                                  //--- Store zone object name
};

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
SDZone zones[];                                    //--- Store active supply/demand zones
SDZone potentialZones[];                           //--- Store potential zones awaiting validation
int    maxZones = 50;                              //--- Set maximum number of zones to track

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(uniqueMagicNumber); //--- Set magic number for trade identification
   return(INIT_SUCCEEDED);                        //--- Return initialization success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, "SDZone_");                //--- Remove all zone objects from chart
   ChartRedraw(0);                                //--- Redraw chart to clear objects
}

Zunächst erstellen wir die Struktur „SDZone“, um Zonendetails zu speichern, einschließlich Höchst- und Tiefstpreisen, Start-, End- und Ausbruchszeiten, Flags für Nachfrage/Angebotstyp („isDemand“), den Teststatus („tested“), den Bruchstatus („broken“), die Testbereitschaft („readyForTest“), die Anzahl der Transaktionen („tradeCount“) und den Objektnamen („name“). Dann werden globale Variablen initialisiert: das Array „zones“ für die aktiven Angebots- und Nachfragezonen, das Array „potentialZones“ für die Zonen, die auf ihre Validierung warten, und das Array „maxZones“, das auf 50 gesetzt wird, um die verfolgten Zonen zu begrenzen. Sie können diesen Wert je nach Ihrem Zeitrahmen und Ihren Einstellungen erhöhen oder verringern; wir haben einfach einen willkürlichen Standardwert gewählt.

In OnInit rufen wir „SetExpertMagicNumber“ auf „obj_Trade“ mit „uniqueMagicNumber“ auf, um Handelsgeschäfte zu markieren und geben INIT_SUCCEEDED für eine erfolgreiche Initialisierung zurück. In der Funktion OnDeinit verwenden wir ObjectsDeleteAll, um alle Chartobjekte mit dem Präfix „SDZone_“ zu entfernen, da wir alle unsere Objekte mit diesem Präfix benennen werden, und rufen ChartRedraw auf, um das Chart zu aktualisieren und eine saubere Ressourcenbereinigung sicherzustellen. Wir können nun einige Hilfsfunktionen definieren, die uns bei der Erkennung und Verwaltung der Zonen helfen. Wir werden mit der Logik beginnen, die die Zonen erkennt, aber zuerst wollen wir eine Hilfsfunktion haben, die bei der Fehlersuche in den Zonen hilft.

//+------------------------------------------------------------------+
//| Print zones for debugging                                        |
//+------------------------------------------------------------------+
void PrintZones(SDZone &arr[]) {
   Print("Current zones count: ", ArraySize(arr)); //--- Log total number of zones
   for (int i = 0; i < ArraySize(arr); i++) {     //--- Iterate through zones
      Print("Zone ", i, ": ", arr[i].name, " endTime: ", TimeToString(arr[i].endTime)); //--- Log zone details
   }
}

Zur Überwachung der Zonenstatus entwickeln wir die Funktion „PrintZones“, die das Array „SDZone“ verwendet, die Gesamtzahl der Zonen mit Print mit ArraySize protokolliert und durchlaufen das Array, um den Index, den Namen und die Endzeit jeder Zone mit TimeToString zu protokollieren, damit eine klare Nachverfolgung möglich ist. Wir können nun die Kernlogik zur Erkennung der Zonen entwickeln.

//+------------------------------------------------------------------+
//| Detect supply and demand zones                                   |
//+------------------------------------------------------------------+
void DetectZones() {
   int startIndex = consolidationBars + 1;                 //--- Set start index for consolidation check
   if (iBars(_Symbol, _Period) < startIndex + 1) return;   //--- Exit if insufficient bars
   bool isConsolidated = true;                             //--- Assume consolidation
   double highPrice = iHigh(_Symbol, _Period, startIndex); //--- Initialize high price
   double lowPrice = iLow(_Symbol, _Period, startIndex);   //--- Initialize low price
   for (int i = startIndex - 1; i >= 2; i--) {             //--- Iterate through consolidation bars
      highPrice = MathMax(highPrice, iHigh(_Symbol, _Period, i)); //--- Update highest high
      lowPrice = MathMin(lowPrice, iLow(_Symbol, _Period, i)); //--- Update lowest low
      if (highPrice - lowPrice > maxConsolidationSpread * _Point) { //--- Check spread limit
         isConsolidated = false;                           //--- Mark as not consolidated
         break;                                            //--- Exit loop
      }
   }
   if (isConsolidated) {                                   //--- Confirm consolidation
      double closePrice = iClose(_Symbol, _Period, 1);     //--- Get last closed bar price
      double breakoutLow = iLow(_Symbol, _Period, 1);      //--- Get breakout bar low
      double breakoutHigh = iHigh(_Symbol, _Period, 1);    //--- Get breakout bar high
      bool isDemandZone = closePrice > highPrice && breakoutLow >= lowPrice; //--- Check demand zone
      bool isSupplyZone = closePrice < lowPrice && breakoutHigh <= highPrice; //--- Check supply zone
      if (isDemandZone || isSupplyZone) {                   //--- Validate zone type
         double zoneSize = (highPrice - lowPrice) / _Point; //--- Calculate zone size
         if (zoneSizeRestriction == EnforceLimits && (zoneSize < minZonePoints || zoneSize > maxZonePoints)) return; //--- Check size restrictions
         datetime lastClosedBarTime = iTime(_Symbol, _Period, 1); //--- Get last bar time
         bool overlaps = false;                             //--- Initialize overlap flag
         for (int j = 0; j < ArraySize(zones); j++) {       //--- Check existing zones
            if (lastClosedBarTime < zones[j].endTime) {     //--- Check time overlap
               double maxLow = MathMax(lowPrice, zones[j].low); //--- Find max low
               double minHigh = MathMin(highPrice, zones[j].high); //--- Find min high
               if (maxLow <= minHigh) {                     //--- Check price overlap
                  overlaps = true;                          //--- Mark as overlapping
                  break;                                    //--- Exit loop
               }
            }
         }
         bool duplicate = false;                        //--- Initialize duplicate flag
         for (int j = 0; j < ArraySize(zones); j++) {   //--- Check for duplicates
            if (lastClosedBarTime < zones[j].endTime) { //--- Check time
               if (MathAbs(zones[j].high - highPrice) < _Point && MathAbs(zones[j].low - lowPrice) < _Point) { //--- Check price match
                  duplicate = true;                     //--- Mark as duplicate
                  break;                                //--- Exit loop
               }
            }
         }
         if (overlaps || duplicate) return;             //--- Skip overlapping or duplicate zones
         if (enableImpulseValidation) {                 //--- Check impulse validation
            bool pot_overlaps = false;                  //--- Initialize potential overlap flag
            for (int j = 0; j < ArraySize(potentialZones); j++) { //--- Check potential zones
               if (lastClosedBarTime < potentialZones[j].endTime) { //--- Check time overlap
                  double maxLow = MathMax(lowPrice, potentialZones[j].low); //--- Find max low
                  double minHigh = MathMin(highPrice, potentialZones[j].high); //--- Find min high
                  if (maxLow <= minHigh) {              //--- Check price overlap
                     pot_overlaps = true;               //--- Mark as overlapping
                     break;                             //--- Exit loop
                  }
               }
            }
            bool pot_duplicate = false;           //--- Initialize potential duplicate flag
            for (int j = 0; j < ArraySize(potentialZones); j++) { //--- Check potential duplicates
               if (lastClosedBarTime < potentialZones[j].endTime) { //--- Check time
                  if (MathAbs(potentialZones[j].high - highPrice) < _Point && MathAbs(potentialZones[j].low - lowPrice) < _Point) { //--- Check price match
                     pot_duplicate = true;      //--- Mark as duplicate
                     break;                     //--- Exit loop
                  }
               }
            }
            if (pot_overlaps || pot_duplicate) return; //--- Skip overlapping or duplicate potential zones
            int potCount = ArraySize(potentialZones); //--- Get potential zones count
            ArrayResize(potentialZones, potCount + 1); //--- Resize potential zones array
            potentialZones[potCount].high = highPrice; //--- Set zone high
            potentialZones[potCount].low = lowPrice; //--- Set zone low
            potentialZones[potCount].startTime = iTime(_Symbol, _Period, startIndex); //--- Set start time
            potentialZones[potCount].endTime = TimeCurrent() + PeriodSeconds(_Period) * zoneExtensionBars; //--- Set end time
            potentialZones[potCount].breakoutTime = iTime(_Symbol, _Period, 1); //--- Set breakout time
            potentialZones[potCount].isDemand = isDemandZone; //--- Set zone type
            potentialZones[potCount].tested = false; //--- Set untested
            potentialZones[potCount].broken = false; //--- Set not broken
            potentialZones[potCount].readyForTest = false; //--- Set not ready
            potentialZones[potCount].tradeCount = 0; //--- Initialize trade count
            potentialZones[potCount].name = "PotentialZone_" + TimeToString(potentialZones[potCount].startTime, TIME_DATE|TIME_SECONDS); //--- Set zone name
            Print("Potential zone created: ", (isDemandZone ? "Demand" : "Supply"), " at ", lowPrice, " - ", highPrice, " endTime: ", TimeToString(potentialZones[potCount].endTime)); //--- Log potential zone
         } else {                                 //--- No impulse validation
            int zoneCount = ArraySize(zones);     //--- Get zones count
            if (zoneCount >= maxZones) {          //--- Check max zones limit
               ArrayRemove(zones, 0, 1);          //--- Remove oldest zone
               zoneCount--;                       //--- Decrease count
            }
            ArrayResize(zones, zoneCount + 1);    //--- Resize zones array
            zones[zoneCount].high = highPrice;    //--- Set zone high
            zones[zoneCount].low = lowPrice;      //--- Set zone low
            zones[zoneCount].startTime = iTime(_Symbol, _Period, startIndex); //--- Set start time
            zones[zoneCount].endTime = TimeCurrent() + PeriodSeconds(_Period) * zoneExtensionBars; //--- Set end time
            zones[zoneCount].breakoutTime = iTime(_Symbol, _Period, 1); //--- Set breakout time
            zones[zoneCount].isDemand = isDemandZone; //--- Set zone type
            zones[zoneCount].tested = false;      //--- Set untested
            zones[zoneCount].broken = false;      //--- Set not broken
            zones[zoneCount].readyForTest = false; //--- Set not ready
            zones[zoneCount].tradeCount = 0;      //--- Initialize trade count
            zones[zoneCount].name = "SDZone_" + TimeToString(zones[zoneCount].startTime, TIME_DATE|TIME_SECONDS); //--- Set zone name
            Print("Zone created: ", (isDemandZone ? "Demand" : "Supply"), " zone: ", zones[zoneCount].name, " at ", lowPrice, " - ", highPrice, " endTime: ", TimeToString(zones[zoneCount].endTime)); //--- Log zone creation
            PrintZones(zones);                    //--- Print zones for debugging
         }
      }
   }
}

Hier implementieren wir die Logik der Zonenerkennung für unser System. In der Funktion „DetectZones“ setzen wir „startIndex“ auf „consolidationBars + 1“ und verlassen die Funktion, wenn nicht genügend Balken vorhanden sind, über die Funktion iBars. Wir gehen von einer Konsolidierung aus („isConsolidated“ true), initialisieren „highPrice“ und „lowPrice“ mit iHigh und iLow bei „startIndex“ und iterieren rückwärts durch die Balken, aktualisieren MathMax und MathMin und setzen „isConsolidated“ auf false, wenn der Bereich „maxConsolidationSpread * Point“ überschreitet. Bei einer Konsolidierung prüfen wir den Schlusskurs („iClose“), das Tief („iLow“) und das Hoch („iHigh“) des letzten Balkens, um die Nachfrage- („closePrice > highPrice“ und „breakoutLow >= lowPrice“) oder Angebotszonen („closePrice < lowPrice“ und „breakoutHigh <= highPrice“) zu identifizieren.

Für gültige Zonen überprüfen wir Größenbeschränkungen mit „zoneSizeRestriction” und „minZonePoints”/„maxZonePoints”, suchen mit „MathMax” und „MathMin” nach Überschneidungen oder Duplikaten in „zones” und „potentialZones” und wenn „enableImpulseValidation“ wahr ist, fügen wir mit ArrayResize zu „potentialZones“ hinzu, setzen Felder wie „high“, „low“, „startTime“ ( iTime), „endTime“ („TimeCurrent + zoneExtensionBars“) und „name“ („PotentialZone“) und protokollieren mit „Print“; andernfalls fügen wir direkt zu „zones“ hinzu, entfernen das älteste Element, wenn „maxZones“ erreicht ist, und protokollieren mit „Print“ und „PrintZones“ zur Fehlerbehebung, damit wir den Überblick über unsere Zonen behalten und so die Kernlogik für die Erkennung und Speicherung von Angebots- und Nachfragzonen erstellen. Wir können dies in OnInit ausführen, um die Zonen zu erkennen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime lastBarTime = 0;                      //--- Store last processed bar time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   bool isNewBar = (currentBarTime != lastBarTime);      //--- Check for new bar
   if (isNewBar) {                                       //--- Process new bar
      lastBarTime = currentBarTime;                      //--- Update last bar time
      DetectZones();                                     //--- Detect new zones
   }
}

In OnTick werden neue Balken verfolgt, indem die Zeit des aktuellen Balkens von iTime (für das Symbol und die Periode bei Shift 0) mit einer statischen „lastBarTime“ verglichen, „isNewBar“ auf true gesetzt und „lastBarTime“ aktualisiert wird, falls sie unterschiedlich ist. Wenn ein neuer Balken erkannt wird, rufen wir unsere Funktion „DetectZones“ auf, um neue Angebots- und Nachfragezonen auf der Grundlage von Konsolidierungsmustern zu ermitteln. Jetzt können wir die Zonen wie unten dargestellt erkennen.

ERMITTELTE POTENZIELLE ZONEN

Da wir nun potenzielle Angebots- und Nachfragezonen erkennen können, müssen wir sie nur noch durch die Aufwärts- oder Abwärtsbewegungen der Rallye bestätigen, die wir als Impulsbewegungen bezeichnen. Zur Modularisierung können wir die gesamte Logik in einer Funktion unterbringen.

//+------------------------------------------------------------------+
//| Validate potential zones based on impulsive move                 |
//+------------------------------------------------------------------+
void ValidatePotentialZones() {
   datetime lastClosedBarTime = iTime(_Symbol, _Period, 1);   //--- Get last closed bar time
   for (int p = ArraySize(potentialZones) - 1; p >= 0; p--) { //--- Iterate potential zones backward
      if (lastClosedBarTime >= potentialZones[p].endTime) {   //--- Check for expired zone
         Print("Potential zone expired and removed from array: ", potentialZones[p].name, " endTime: ", TimeToString(potentialZones[p].endTime)); //--- Log expiration
         ArrayRemove(potentialZones, p, 1);                   //--- Remove expired zone
         continue;                                            //--- Skip to next
      }
      if (TimeCurrent() > potentialZones[p].breakoutTime + impulseCheckBars * PeriodSeconds(_Period)) { //--- Check impulse window
         bool isImpulsive = false;                            //--- Initialize impulsive flag
         int breakoutShift = iBarShift(_Symbol, _Period, potentialZones[p].breakoutTime, false); //--- Get breakout bar shift
         double range = potentialZones[p].high - potentialZones[p].low; //--- Calculate zone range
         double threshold = range * impulseMultiplier;        //--- Calculate impulse threshold
         for (int shift = 1; shift <= impulseCheckBars; shift++) { //--- Check bars after breakout
            if (shift + breakoutShift >= iBars(_Symbol, _Period)) continue; //--- Skip out-of-bounds
            double cl = iClose(_Symbol, _Period, shift);      //--- Get close price
            if (potentialZones[p].isDemand) {                 //--- Check demand zone
               if (cl >= potentialZones[p].high + threshold) { //--- Check bullish impulse
                  isImpulsive = true;                         //--- Set impulsive flag
                  break;                                      //--- Exit loop
               }
            } else {                                          //--- Check supply zone
               if (cl <= potentialZones[p].low - threshold) { //--- Check bearish impulse
                  isImpulsive = true;                         //--- Set impulsive flag
                  break;                                      //--- Exit loop
               }
            }
         }
         if (isImpulsive) {                                  //--- Process impulsive zone
            double zoneSize = (potentialZones[p].high - potentialZones[p].low) / _Point; //--- Calculate zone size
            if (zoneSizeRestriction == EnforceLimits && (zoneSize < minZonePoints || zoneSize > maxZonePoints)) { //--- Check size limits
               ArrayRemove(potentialZones, p, 1);            //--- Remove invalid zone
               continue;                                     //--- Skip to next
            }
            bool overlaps = false;                           //--- Initialize overlap flag
            for (int j = 0; j < ArraySize(zones); j++) {     //--- Check existing zones
               if (lastClosedBarTime < zones[j].endTime) {   //--- Check time overlap
                  double maxLow = MathMax(potentialZones[p].low, zones[j].low); //--- Find max low
                  double minHigh = MathMin(potentialZones[p].high, zones[j].high); //--- Find min high
                  if (maxLow <= minHigh) {                   //--- Check price overlap
                     overlaps = true;                        //--- Mark as overlapping
                     break;                                  //--- Exit loop
                  }
               }
            }
            bool duplicate = false;                          //--- Initialize duplicate flag
            for (int j = 0; j < ArraySize(zones); j++) {     //--- Check for duplicates
               if (lastClosedBarTime < zones[j].endTime) {   //--- Check time
                  if (MathAbs(zones[j].high - potentialZones[p].high) < _Point && MathAbs(zones[j].low - potentialZones[p].low) < _Point) { //--- Check price match
                     duplicate = true;                       //--- Mark as duplicate
                     break;                                  //--- Exit loop
                  }
               }
            }
            if (overlaps || duplicate) {                     //--- Check overlap or duplicate
               Print("Validated zone overlaps or duplicates, discarded: ", potentialZones[p].low, " - ", potentialZones[p].high); //--- Log discard
               ArrayRemove(potentialZones, p, 1);            //--- Remove zone
               continue;                                     //--- Skip to next
            }
            int zoneCount = ArraySize(zones);                //--- Get zones count
            if (zoneCount >= maxZones) {                     //--- Check max zones limit
               ArrayRemove(zones, 0, 1);                     //--- Remove oldest zone
               zoneCount--;                                  //--- Decrease count
            }
            ArrayResize(zones, zoneCount + 1);               //--- Resize zones array
            zones[zoneCount] = potentialZones[p];            //--- Copy potential zone
            zones[zoneCount].name = "SDZone_" + TimeToString(zones[zoneCount].startTime, TIME_DATE|TIME_SECONDS); //--- Set zone name
            zones[zoneCount].endTime = TimeCurrent() + PeriodSeconds(_Period) * zoneExtensionBars; //--- Update end time
            Print("Zone validated: ", (zones[zoneCount].isDemand ? "Demand" : "Supply"), " zone: ", zones[zoneCount].name, " at ", zones[zoneCount].low, " - ", zones[zoneCount].high, " endTime: ", TimeToString(zones[zoneCount].endTime)); //--- Log validation
            ArrayRemove(potentialZones, p, 1);               //--- Remove validated zone
            PrintZones(zones);                               //--- Print zones for debugging
         } else {                                            //--- Zone not impulsive
            Print("Potential zone not impulsive, discarded: ", potentialZones[p].low, " - ", potentialZones[p].high); //--- Log discard
            ArrayRemove(potentialZones, p, 1);               //--- Remove non-impulsive zone
         }
      }
   }
}

Hier erstellen wir eine Funktion zur Implementierung der Validierungslogik für potenzielle Angebots- und Nachfragezonen. In der Funktion „ValidatePotentialZones“ iterieren wir rückwärts durch die „potentialZones“, prüfen, ob die Zeit des letzten geschlossenen Balkens (iTime bei Shift 1) die „endTime“ einer Zone überschreitet, entfernen abgelaufene Zonen mit ArrayRemove und protokollieren die Aktion. Für Zonen innerhalb des Impulsfensters („TimeCurrent > breakoutTime + impulseCheckBars * PeriodSeconds“) berechnen wir den Zonenbereich („high - low“) und die Impulsschwelle („range * impulseMultiplier“), dann prüfen wir die Balken nach dem Ausbruch (iBarShift) auf einen Schlusskurs (iClose), der den Schwellenwert „high plus“ für Nachfragezonen überschreitet oder den Schwellenwert „low minus“ für Angebotszonen unterschreitet, und setzen „isImpulsive“, wenn dies der Fall ist.

Wenn die Zone impulsiv ist, überprüfen wir die Zonengröße anhand von „minZonePoints“ und „maxZonePoints“, wenn „zoneSizeRestriction“ auf „EnforceLimits“ eingestellt ist, prüfen mit MathMax und MathMin auf Überschneidungen oder Duplikate in „zones“ und verschieben die Zone, wenn sie gültig ist, mit ArrayResize in „zones“, aktualisieren ihren Namen auf „SDZone_“ und Endzeit, Protokollierung mit den Funktionen „Print“ und „PrintZones“, dann entfernen wir es aus „potentialZones“; nicht-impulsive Zonen werden mit ArrayRemove entfernt und protokolliert, wodurch ein System zur Validierung von Zonen auf der Grundlage von impulsiven Bewegungen und zur Gewährleistung eindeutiger, gültiger Zonen geschaffen wird. Wenn Sie die Funktion im Tick-Ereignishandler aufrufen, sollten Sie etwas erhalten, das das Folgende darstellt.

VALIDIERUNG VON ANGEBOTS- UND NACHFRAGEZONEN

Nachdem wir nun die Zonen validiert haben, können wir die Zonen im Chartverwalten und visualisieren, um sie leichter zu verfolgen.

//+------------------------------------------------------------------+
//| Update and draw zones                                            |
//+------------------------------------------------------------------+
void UpdateZones() {
   datetime lastClosedBarTime = iTime(_Symbol, _Period, 1); //--- Get last closed bar time
   for (int i = ArraySize(zones) - 1; i >= 0; i--) { //--- Iterate zones backward
      if (lastClosedBarTime >= zones[i].endTime) { //--- Check for expired zone
         Print("Zone expired and removed from array: ", zones[i].name, " endTime: ", TimeToString(zones[i].endTime)); //--- Log expiration
         if (deleteExpiredZonesFromChart) {    //--- Check if deleting expired
            ObjectDelete(0, zones[i].name);    //--- Delete zone rectangle
            ObjectDelete(0, zones[i].name + "Label"); //--- Delete zone label
         }
         ArrayRemove(zones, i, 1);             //--- Remove expired zone
         continue;                             //--- Skip to next
      }
      bool wasReady = zones[i].readyForTest;   //--- Store previous ready status
      if (!zones[i].readyForTest) {            //--- Check if not ready
         double currentClose = iClose(_Symbol, _Period, 1); //--- Get current close
         double zoneLevel = zones[i].isDemand ? zones[i].high : zones[i].low; //--- Get zone level
         double distance = zones[i].isDemand ? (currentClose - zoneLevel) : (zoneLevel - currentClose); //--- Calculate distance
         if (distance > minMoveAwayPoints * _Point) { //--- Check move away distance
            zones[i].readyForTest = true;      //--- Set ready for test
         }
      }
      if (!wasReady && zones[i].readyForTest) { //--- Check if newly ready
         Print("Zone ready for test: ", zones[i].name); //--- Log ready status
      }
      if (brokenZoneMode == AllowBroken && !zones[i].tested) { //--- Check if breakable
         double currentClose = iClose(_Symbol, _Period, 1); //--- Get current close
         bool wasBroken = zones[i].broken;     //--- Store previous broken status
         if (zones[i].isDemand) {              //--- Check demand zone
            if (currentClose < zones[i].low) { //--- Check if broken
               zones[i].broken = true;         //--- Mark as broken
            }
         } else {                              //--- Check supply zone
            if (currentClose > zones[i].high) { //--- Check if broken
               zones[i].broken = true;         //--- Mark as broken
            }
         }
         if (!wasBroken && zones[i].broken) {  //--- Check if newly broken
            Print("Zone broken in UpdateZones: ", zones[i].name); //--- Log broken zone
            ObjectSetInteger(0, zones[i].name, OBJPROP_COLOR, brokenZoneColor); //--- Update zone color
            string labelName = zones[i].name + "Label"; //--- Get label name
            string labelText = zones[i].isDemand ? "Demand Zone (Broken)" : "Supply Zone (Broken)"; //--- Set broken label
            ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); //--- Update label text
            if (deleteBrokenZonesFromChart) {  //--- Check if deleting broken
               ObjectDelete(0, zones[i].name); //--- Delete zone rectangle
               ObjectDelete(0, labelName);     //--- Delete zone label
            }
         }
      }
      if (ObjectFind(0, zones[i].name) >= 0 || (!zones[i].broken || !deleteBrokenZonesFromChart)) { //--- Check if drawable
         color zoneColor;                        //--- Initialize zone color
         if (zones[i].tested) {                  //--- Check if tested
            zoneColor = zones[i].isDemand ? testedDemandZoneColor : testedSupplyZoneColor; //--- Set tested color
         } else if (zones[i].broken) {           //--- Check if broken
            zoneColor = brokenZoneColor;         //--- Set broken color
         } else {                                //--- Untested zone
            zoneColor = zones[i].isDemand ? demandZoneColor : supplyZoneColor; //--- Set untested color
         }
         ObjectCreate(0, zones[i].name, OBJ_RECTANGLE, 0, zones[i].startTime, zones[i].high, zones[i].endTime, zones[i].low); //--- Create zone rectangle
         ObjectSetInteger(0, zones[i].name, OBJPROP_COLOR, zoneColor); //--- Set zone color
         ObjectSetInteger(0, zones[i].name, OBJPROP_FILL, true); //--- Enable fill
         ObjectSetInteger(0, zones[i].name, OBJPROP_BACK, true); //--- Set to background
         string labelName = zones[i].name + "Label"; //--- Generate label name
         string labelText = zones[i].isDemand ? "Demand Zone" : "Supply Zone"; //--- Set base label
         if (zones[i].tested) labelText += " (Tested)"; //--- Append tested status
         else if (zones[i].broken) labelText += " (Broken)"; //--- Append broken status
         datetime labelTime = zones[i].startTime + (zones[i].endTime - zones[i].startTime) / 2; //--- Calculate label time
         double labelPrice = (zones[i].high + zones[i].low) / 2; //--- Calculate label price
         ObjectCreate(0, labelName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create label
         ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); //--- Set label text
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, labelTextColor); //--- Set label color
         ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor
      }
   }
   ChartRedraw(0);                                //--- Redraw chart
}

Wir fahren fort mit der Implementierung der Zonenverwaltung und der Visualisierungslogik für das System. In der Funktion „UpdateZones“ iterieren wir rückwärts durch die „Zonen“ und prüfen, ob die Zeit des letzten geschlossenen Balkens (iTime bei Shift 1) die „endTime“ einer Zone überschreitet, entfernen abgelaufene Zonen mit ArrayRemove, löschen ihre Chart-Objekte (OBJ_RECTANGLE und „Label“), wenn „deleteExpiredZonesFromChart“ wahr ist, und protokollieren, um sicherzustellen, dass wir uns mit einer abgelaufenen Zone nicht mehr beschäftigen. Für nicht-bereite Zonen berechnen wir den Abstand zwischen dem aktuellen Schlusskurs (iClose) und dem Höchst- (Nachfrage) bzw. Tiefstkurs (Angebot) der Zone und markieren „readyForTest“ als wahr, wenn er „minMoveAwayPoints * _Point“ übersteigt, und protokollieren, wenn er neu bereit ist.

Wenn „brokenZoneMode“ „AllowBroken“ ist und die Zone ungetestet ist, markieren wir sie als gebrochen, wenn sich der Schlusskurs unter das Tief (Nachfrage) oder über das Hoch (Angebot) bewegt, und aktualisieren wir die Farbe auf „brokenZoneColor“ mit ObjectSetInteger und die Kennzeichnung auf „Demand/Supply Zone (Broken)“ mit ObjectSetString, löschen die Objekte, wenn „deleteBrokenZonesFromChart“ wahr ist, und die protokollieren die Instanz. Für zeichenbare Zonen (existierende oder nicht gebrochene mit „deleteBrokenZonesFromChart“ false), setzen wir Farben („demandZoneColor“, „supplyZoneColor“, „testedDemandZoneColor“, „testedSupplyZoneColor“ oder „brokenZoneColor“) basierend auf dem Status, zeichnen Rechtecke mit ObjectCreate („OBJ_RECTANGLE“) unter Verwendung von „startTime“, „high“, „endTime“ und „low“ Rechtecke zeichnen und mit „ObjectCreate“ (OBJ_TEXT) unter Verwendung von „labelTextColor“ zentrierte Beschriftungen hinzufügen, dann das Chart mit der Funktion ChartRedraw neu zeichnen und so die Zonenstatus aktualisieren und dynamisch darstellen. Wir können diese Funktion nun in der Ereignisbehandlung der Ticks aufrufen, und wenn wir das tun, erhalten wir das folgende Ergebnis.

VERWALTETE UND VISUALISIERTE ZONEN

Nun, da wir die Zonen verwalten und auf dem Chart visualisieren können, müssen wir sie nur noch verfolgen und auf der Grundlage der erfüllten Handelsbedingungen handeln. Wir werden eine Funktion erstellen, die die gültigen Zonen in einer Schleife durchläuft und die Handelsbedingungen überprüft.

//+------------------------------------------------------------------+
//| Trade on zones                                                   |
//+------------------------------------------------------------------+
void TradeOnZones(bool isNewBar) {
   static datetime lastTradeCheck = 0;                   //--- Store last trade check time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   if (!isNewBar || lastTradeCheck == currentBarTime) return; //--- Exit if not new bar or checked
   lastTradeCheck = currentBarTime;                      //--- Update last trade check
   double currentBid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get current bid
   double currentAsk = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get current ask
   for (int i = 0; i < ArraySize(zones); i++) {          //--- Iterate through zones
      if (zones[i].broken) continue;                     //--- Skip broken zones
      if (tradeTestedMode == NoRetrade && zones[i].tested) continue; //--- Skip tested zones
      if (tradeTestedMode == LimitedRetrade && zones[i].tested && zones[i].tradeCount >= maxTradesPerZone) continue; //--- Skip max trades
      if (!zones[i].readyForTest) continue;              //--- Skip not ready zones
      double prevHigh = iHigh(_Symbol, _Period, 1);      //--- Get previous high
      double prevLow = iLow(_Symbol, _Period, 1);        //--- Get previous low
      double prevClose = iClose(_Symbol, _Period, 1);    //--- Get previous close
      bool tapped = false;                               //--- Initialize tap flag
      bool overlap = (prevLow <= zones[i].high && prevHigh >= zones[i].low); //--- Check candle overlap
      if (zones[i].isDemand) {                           //--- Check demand zone
         if (overlap && prevClose > zones[i].high) {     //--- Confirm demand tap
            tapped = true;                               //--- Set tapped flag
         }
      } else {                                           //--- Check supply zone
         if (overlap && prevClose < zones[i].low) {      //--- Confirm supply tap
            tapped = true;                               //--- Set tapped flag
         }
      }
      if (tapped) {                                      //--- Process tapped zone
         bool trendConfirmed = (trendConfirmation == NoConfirmation); //--- Assume no trend confirmation
         if (trendConfirmation == ConfirmTrend) {        //--- Check trend confirmation
            int oldShift = 2 + trendLookbackBars - 1;    //--- Calculate lookback shift
            if (oldShift >= iBars(_Symbol, _Period)) continue; //--- Skip if insufficient bars
            double oldClose = iClose(_Symbol, _Period, oldShift); //--- Get old close
            double recentClose = iClose(_Symbol, _Period, 2); //--- Get recent close
            double minChange = minTrendPoints * _Point; //--- Calculate min trend change
            if (zones[i].isDemand) {                    //--- Check demand trend
               trendConfirmed = (oldClose > recentClose + minChange); //--- Confirm downtrend
            } else {                                    //--- Check supply trend
               trendConfirmed = (oldClose < recentClose - minChange); //--- Confirm uptrend
            }
         }
         if (!trendConfirmed) continue;                 //--- Skip if trend not confirmed
         bool wasTested = zones[i].tested;              //--- Store previous tested status
         if (zones[i].isDemand) {                       //--- Handle demand trade
            double entryPrice = currentAsk;             //--- Set entry at ask
            double stopLossPrice = NormalizeDouble(zones[i].low - stopLossDistance * _Point, _Digits); //--- Set stop loss
            double takeProfitPrice = NormalizeDouble(entryPrice + takeProfitDistance * _Point, _Digits); //--- Set take profit
            obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice, "Buy at Demand Zone"); //--- Execute buy trade
            Print("Buy trade entered at Demand Zone: ", zones[i].name); //--- Log buy trade
         } else {                                       //--- Handle supply trade
            double entryPrice = currentBid;             //--- Set entry at bid
            double stopLossPrice = NormalizeDouble(zones[i].high + stopLossDistance * _Point, _Digits); //--- Set stop loss
            double takeProfitPrice = NormalizeDouble(entryPrice - takeProfitDistance * _Point, _Digits); //--- Set take profit
            obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice, "Sell at Supply Zone"); //--- Execute sell trade
            Print("Sell trade entered at Supply Zone: ", zones[i].name); //--- Log sell trade
         }
         zones[i].tested = true;                        //--- Mark zone as tested
         zones[i].tradeCount++;                         //--- Increment trade count
         if (!wasTested && zones[i].tested) {           //--- Check if newly tested
            Print("Zone tested: ", zones[i].name, ", Trade count: ", zones[i].tradeCount); //--- Log tested zone
         }
         color zoneColor = zones[i].isDemand ? testedDemandZoneColor : testedSupplyZoneColor; //--- Set tested color
         ObjectSetInteger(0, zones[i].name, OBJPROP_COLOR, zoneColor); //--- Update zone color
         string labelName = zones[i].name + "Label";                   //--- Get label name
         string labelText = zones[i].isDemand ? "Demand Zone (Tested)" : "Supply Zone (Tested)"; //--- Set tested label
         ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);       //--- Update label text
      }
   }
   ChartRedraw(0);                                       //--- Redraw chart
}

Um die Handelslogik für Zonenwiederholungen oder Taps zu implementieren, erstellen wir die Funktion „TradeOnZones“. Zunächst werden neue Balken mit einem statischen „lastTradeCheck“ verfolgt und verlassen die Funktion, wenn sie nicht neu sind oder bereits geprüft wurden, wobei „lastTradeCheck“ mit iTime aktualisiert wird, wenn dies der Fall ist, und die Geld- und Briefkurse (Bid, Ask) werden mit den Funktionen SymbolInfoDouble und NormalizeDouble normalisiert. Wir iterieren durch „zones“, überspringen die gebrochenen, über-getesteten (basierend auf „tradeTestedMode“ und „maxTradesPerZone“) oder nicht vorbereiteten Zonen und prüfen dann das Hoch (iHigh), das Tief („iLow“) und den Schlusskurs (iClose) des vorherigen Balkens auf Überschneidung mit der Zone. Bei Nachfragezonen („isDemand“) bestätigen wir eine Berührung, wenn „overlap“ wahr ist und „prevClose > high“, bei Angebot, wenn „overlap“ und „prevClose < low“, und setzen „tapped“ entsprechend.

Bei Berührung bestätigen wir den Trend, wenn „trendConfirmation“ „ConfirmTrend“ ist, indem wir alte und aktuelle Schlusskurse („iClose“) über „trendLookbackBars“ mit „minTrendPoints * _Point“ vergleichen und überspringen, wenn nicht bestätigt. Für gültige Berührungen führen wir Handelsgeschäfte aus: für die Nachfrage kaufen wir zu Ask mit Stop Loss unter „Low“ um „stopLossDistance * _Point“ und Take Profit über Entry um „takeProfitDistance * _Point“ mit „obj_Trade.Buy“, Protokollierung mit Print; für das Angebot verkaufen wir zu Bid mit Stop Loss über „High“ und Take Profit unter Entry, mit „obj_Trade.Sell“. Wir markieren die Zone als „getestet“, erhöhen „tradeCount“, protokollieren, ob neu getestet wurde, aktualisieren die Zonenfarbe mit ObjectSetInteger auf „testedDemandZoneColor“ oder „testedSupplyZoneColor“ und aktualisieren den Beschriftungstext mit der Funktion ObjectSetString auf „Demand/Supply Zone (Tested)“. Zum Schluss wird das Chart neu gezeichnet. Wenn wir die Funktion aufrufen, erhalten wir das folgende Ergebnis.

BESTÄTIGTE SIGNALE

Aus dem Bild können wir ersehen, dass wir das Berühren der Zone erkennen und die Anzahl der Handelsgeschäfte oder Berührungen im Wesentlichen für diese Zone speichern, sodass wir sie bei Bedarf auf andere Berührungen übertragen können. Jetzt muss nur noch ein Trailing-Stop hinzugefügt werden, um die Gewinne zu maximieren. Wir werden auch dafür eine Funktion machen.

//+------------------------------------------------------------------+
//| Apply trailing stop to open positions                            |
//+------------------------------------------------------------------+
void ApplyTrailingStop() {     
   double point = _Point;                               //--- Get point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {    //--- Iterate through positions
      if (PositionGetTicket(i) > 0) {                   //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == uniqueMagicNumber) { //--- Verify symbol and magic
            double sl = PositionGetDouble(POSITION_SL); //--- Get current stop loss
            double tp = PositionGetDouble(POSITION_TP); //--- Get current take profit
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingStopPoints * point, _Digits); //--- Calculate new stop loss
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > minProfitToTrail * point) { //--- Check trailing condition
                  obj_Trade.PositionModify(PositionGetInteger(POSITION_TICKET), newSL, tp); //--- Update stop loss
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingStopPoints * point, _Digits); //--- Calculate new stop loss
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > minProfitToTrail * point) { //--- Check trailing condition
                  obj_Trade.PositionModify(PositionGetInteger(POSITION_TICKET), newSL, tp); //--- Update stop loss
               }
            }
         }
      }
   }
}

Hier implementieren wir die Trailing-Stop-Logik, um offene Positionen dynamisch zu verwalten. In der Funktion „ApplyTrailingStop“ rufen wir den Punktwert mit _Point ab und iterieren rückwärts durch die offenen Positionen mit PositionsTotal, wobei wir das Ticket jeder Position mit PositionGetTicket, das Symbol mit PositionGetString und die magische Zahl mit PositionGetInteger mit der magischen Zahl abgleichen.

Für Kaufpositionen (POSITION_TYPE_BUY) berechnen wir einen neuen Stop Loss als Geldkurs (SymbolInfoDouble mit SYMBOL_BID) minus „trailingStopPoints * point“, normalisiert mit NormalizeDouble, und aktualisieren ihn mit „obj_Trade.PositionModify“, wenn er höher ist als der aktuelle Stop Loss („PositionGetDouble(POSITION_SL)“) und der Gewinn „minProfitToTrail * Punkt“ übersteigt. Bei Verkaufspositionen berechnen wir den neuen Stop-Loss mit Briefkurs ("SYMBOL_ASK") plus „trailingStopPoints * point" und aktualisieren ihn, wenn er unter dem aktuellen Stop-Loss liegt und der Gewinn den Schwellenwert überschreitet. Wir können es jetzt einfach bei jedem Tick aufrufen, um die Verwaltung wie folgt durchzuführen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (enableTrailingStop) {                      //--- Check if trailing stop enabled
      ApplyTrailingStop();                        //--- Apply trailing stop to positions
   }
   static datetime lastBarTime = 0;               //--- Store last processed bar time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   bool isNewBar = (currentBarTime != lastBarTime); //--- Check for new bar
   if (isNewBar) {                                //--- Process new bar
      lastBarTime = currentBarTime;               //--- Update last bar time
      DetectZones();                              //--- Detect new zones
      ValidatePotentialZones();                   //--- Validate potential zones
      UpdateZones();                              //--- Update existing zones
   }
   if (enableTrading) {                           //--- Check if trading enabled
      TradeOnZones(isNewBar);                     //--- Execute trades on zones
   }
}

Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis.

TRAILING-STOP AKTIVIERT

Aus dem Bild können wir ersehen, dass der Trailing-Stop vollständig aktiviert ist, wenn sich der Kurs zu unseren Gunsten entwickelt. Hier ist ein einheitlicher Test für beide Zonen im vergangenen Monat.

EINHEITLICHTER TEST VON ANGEBOT UND NACHFRAGE GIF

Anhand der Visualisierung können wir sehen, dass das Programm alle Einstiegsbedingungen identifiziert und überprüft und bei Bestätigung die entsprechende Position mit den entsprechenden Einstiegsparametern öffnet und somit unser Ziel erreicht. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.


Backtests

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtest:

BERICHT


Schlussfolgerung

Abschließend haben wir ein Angebots- und Nachfragehandelssystem in MQL5 erstellt, um Angebots- und Nachfragezonen durch Konsolidierung zu erkennen, sie mit impulsiven Bewegungen zu validieren und mit Trendbestätigung und anpassbaren Risikoeinstellungen zu handeln. Das System visualisiert die Zonen mit dynamischen Beschriftungen und Farben, wobei Trailing Stops für ein effektives Risikomanagement integriert sind.

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 Angebots- und Nachfragestrategie sind Sie für den Handel mit Retest-Gelegenheiten gerüstet und können Ihre Handelsreise weiter optimieren. Viel Spaß beim Handeln!

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19674

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
jiyanazad
jiyanazad | 27 Nov. 2025 in 20:59
Hallo,
ich habe es ohne Fehler kompiliert. Leider sieht man weder im Backtest noch im Chart etwas. Weder werden Angebots- & Nachfragezonen eingezeichnet, noch werden Signale erkannt oder Trades ausgeführt. Das Debug und das Journal sind auch leer. Hat jemand das Programm zum Laufen gebracht? Kann die Automatik überprüfen, ob der Code vollständig und aktuell ist?
bzaranyika
bzaranyika | 4 Dez. 2025 in 19:04
Arbeitete in einem Zeitrahmen von 1m
Allan Munene Mutiiria
Allan Munene Mutiiria | 5 Dez. 2025 in 07:52
bzaranyika #:
Arbeitet in einem Zeitrahmen von 1m
Vielen Dank für die freundliche Rückmeldung
Olowogbade Sunday
Olowogbade Sunday | 6 Jan. 2026 in 23:44
Danke für diese großartige Hilfe

Aber es funktioniert nicht auf einige Notierungen, ist es ein Fehler auf meinem Ende.
Wenn ja, pls was und was sind die Kriterien für den Code auf jede Währung oder handelbaren Vermögenswert zu arbeiten
Entwicklung des Price Action Analysis Toolkit (Teil 44): Aufbau eines VWMA Crossover Signal EA in MQL5 Entwicklung des Price Action Analysis Toolkit (Teil 44): Aufbau eines VWMA Crossover Signal EA in MQL5
In diesem Artikel wird ein VWMA-Crossover-Signal für den MetaTrader 5 vorgestellt, das Händlern helfen soll, potenzielle Aufwärts- und Abwärtsbewegungen zu erkennen, indem es Preisbewegungen mit dem Handelsvolumen kombiniert. Der EA generiert klare Kauf- und Verkaufssignale direkt auf dem Chart, verfügt über ein informatives Panel und lässt sich vollständig an den Nutzer anpassen, was ihn zu einer praktischen Ergänzung Ihrer Handelsstrategie macht.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 82): Verwendung von TRIX- und WPR-Mustern mit DQN-Verstärkungslernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 82): Verwendung von TRIX- und WPR-Mustern mit DQN-Verstärkungslernen
Im letzten Artikel haben wir die Paarung von Ichimoku und ADX im Rahmen des Inferenzlernens untersucht. In diesem Beitrag greifen wir das Verstärkungslernen in Verbindung mit einem Indikatorpaar auf, das wir zuletzt in „Teil 68“ betrachtet haben. Der TRIX und Williams Percent Range. Unser Algorithmus für diese Überprüfung wird die Quantilregression DQN sein. Wie üblich stellen wir dies als nutzerdefinierte Signalklasse vor, die für die Implementierung mit dem MQL5-Assistenten entwickelt wurde.
Bivariate Copulae in MQL5 (Teil 1): Implementierung von Gauß- und Studentische t-Copulae für die Modellierung von Abhängigkeiten Bivariate Copulae in MQL5 (Teil 1): Implementierung von Gauß- und Studentische t-Copulae für die Modellierung von Abhängigkeiten
Dies ist der erste Teil einer Artikelserie, in der die Implementierung von bivariaten Copulae in MQL5 vorgestellt wird. Dieser Artikel enthält Code zur Implementierung der Gauß‘schen und Studentischen t-Copulae. Außerdem werden die Grundlagen der statistischen Copulae und verwandte Themen behandelt. Der Code basiert auf dem Python-Paket Arbitragelab von Hudson und Thames.
Schnellhandel meistern: Überwindung der Umsetzungslähmung Schnellhandel meistern: Überwindung der Umsetzungslähmung
Der Indikator UT BOT ATR Trailing ist ein persönlicher und anpassbarer Indikator, der sehr effektiv für Händler ist, die gerne schnelle Entscheidungen treffen und Geld aus Preisunterschieden machen, die als kurzfristiger Handel bezeichnet werden (Scalper), und sich auch als wichtig und sehr effektiv für langfristige Händler (positionelle Händler) erweist.