English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik

Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik

MetaTrader 5Handel |
135 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 22) haben wir ein Zone Recovery System für einen Trendhandel mit Envelopes in MetaQuotes Language 5 (MQL5) entwickelt, das den Relative Strength Index (RSI) und Envelopes-Indikatoren verwendet, um den Handel zu automatisieren und Verluste durch strukturierte Recovery-Zonen zu verwalten. In Teil 23 verfeinern wir diese Strategie, indem wir Trailing-Stops zur dynamischen Gewinnsicherung und ein System mehrerer Körbe (multi baskets) zur effizienten Verarbeitung mehrerer Handelssignale einbeziehen und damit die Anpassungsfähigkeit in volatilen Märkten verbessern. Wir werden die folgenden Themen behandeln:

  1. Verstehen des erweiterten Trailing-Stops und der Multi-Basket-Architektur
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie ein verfeinertes MQL5-Handelssystem mit fortschrittlichen Funktionen, das Sie testen und weiter anpassen können – legen wir los!


Verstehen des erweiterten Trailing-Stops und der Multi-Basket-Architektur

Die Zone Recovery Strategy, die wir verbessern, ist darauf ausgelegt, potenzielle Verluste in Gewinne zu verwandeln, indem wir Gegengeschäfte innerhalb einer bestimmten Preisspanne platzieren, wenn sich der Markt gegen uns bewegt. Wir verstärken sie jetzt mit zwei wichtigen Verbesserungen: Trailing-Stops und Multi-Basket-Trading. Trailing-Stops, also Stopps, die nachgezogen werden, sind notwendig, weil sie es uns ermöglichen, Gewinne zu sichern, wenn sich der Markt zu unseren Gunsten entwickelt, ohne den Handel zu früh zu schließen, was in trendigen Märkten, in denen die Preise erheblich steigen können, entscheidend ist. Der Handel mit mehreren Körben ist ebenso wichtig, da er uns ermöglicht, mehrere unabhängige Handelssignale gleichzeitig zu verwalten, wodurch wir mehr Chancen wahrnehmen können, während wir das Risiko auf verschiedene Handelsgruppen verteilen. Siehe unten.

TRAILING-STOP-ARCHITEKTUR

Wir werden diese Verbesserungen durch die Integration eines Trailing-Stop-Mechanismus erreichen, der das Stop-Loss-Niveau dynamisch an die Marktentwicklung anpasst, um Gewinne zu sichern und gleichzeitig den Handelsgeschäften einen Raum zu Wachsen zu geben. Für den Handel mit mehreren Körben werden wir ein System einführen, mit dem wir mehrere Handelsinstanzen mit jeweils eindeutiger Kennung handhaben können, sodass wir mehrere Zonenerholungszyklen gleichzeitig und ohne Überschneidungen verfolgen und verwalten können. Wir planen, diese Funktionen mit den bestehenden Indikatoren Relative Strength Indicator (RSI) und Envelopes zu kombinieren, um einen präzisen Einstieg in den Handel zu gewährleisten, während die Trailing-Stops und das Basket-System zusammenarbeiten, um den Gewinnschutz und die Handelskapazität zu optimieren, wodurch die Strategie robuster und an verschiedene Marktbedingungen anpassbar wird. Bleiben Sie dran, wenn wir diese Verbesserungen in die Tat umsetzen!


Implementation in MQL5

Um die Verbesserungen in MQL5 zu implementieren, fügen wir einige zusätzliche Nutzereingaben für die Trailing-Stop-Funktion hinzu und benennen das Maximum Cap Order Limit um, da wir es jetzt mit mehreren Recovery-Instanzen zu tun haben.

input group "======= EA GENERAL SETTINGS ======="
input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE;               // Lot Size Option
input double initialLotSize = 0.01;                                    // Initial Lot Size
input double riskPercentage = 1.0;                                     // Risk Percentage (%)
input int    riskPoints = 300;                                         // Risk Points
input int    baseMagicNumber = 123456789;                              // Base Magic Number
input int    maxInitialPositions = 1;                                  // Maximum Initial Positions (Baskets/Signals)
input double zoneTargetPoints = 600;                                   // Zone Target Points
input double zoneSizePoints = 300;                                     // Zone Size Points
input bool   enableInitialTrailing = true;                             // Enable Trailing Stop for Initial Positions
input int    trailingStopPoints = 50;                                  // Trailing Stop Points
input int    minProfitPoints = 50;                                     // Minimum Profit Points to Start Trailing

Wir beginnen mit der Verbesserung unseres Zone Recovery Systems für Envelopes Trend Trading in MQL5, indem wir die Eingabeparameter unter der Gruppe „EA GENERAL SETTINGS“ aktualisieren, um Trailing-Stops und den Handel mit mehreren Körben zu unterstützen. Wir nehmen vier wesentliche Änderungen an den Eingaben vor. Zunächst benennen wir „magicNumber“ in „baseMagicNumber“ um und setzen es auf 123456789, um als Ausgangspunkt für die Generierung eindeutiger magischer Zahlen für mehrere Handelskörbe (trade baskets) zu dienen und sicherzustellen, dass jeder Korb oder „basket“ für unser System mit mehreren Körben separat verfolgt wird. Zweitens ersetzen wir „maxOrders“ durch „maxInitialPositions“, das auf 1 gesetzt wird, um die Anzahl der anfänglichen Handelskörbe zu begrenzen, sodass wir mehrere Handelssignale effizient verwalten können.

Drittens fügen wir „enableInitialTrailing“ hinzu, einen booleschen Wert, der auf true gesetzt wird, um Trailing-Stops für Anfangspositionen zu aktivieren oder zu deaktivieren und so die Kontrolle über unsere neue Gewinnsicherungsfunktion zu ermöglichen. Viertens führen wir „trailingStopPoints“ mit einem Wert von 50 und „minProfitPoints“ mit einem Wert von 50 ein, die den Trailing-Stop-Abstand bzw. den zur Aktivierung erforderlichen Mindestgewinn definieren, um eine dynamische Gewinnsicherung zu implementieren. Diese Änderungen ermöglichen es unserem System, mit mehreren Handelskörben umzugehen und Gewinne wirksam zu schützen, und bilden die Grundlage für weitere Verbesserungen. Wir werden die Änderungen hervorheben, um sie leichter nachvollziehen zu können und Verwirrung zu vermeiden. Nach der Kompilierung ergibt sich die folgende Eingabemenge.

NEW INPUTS SET

Nachdem wir die Eingaben hinzugefügt haben, können wir nun die Klasse „MarketZoneTrader“ weiter deklarieren, sodass die Basisklasse auf sie zugreifen kann, da wir nun mehrere Handelsinstanzen behandeln wollen.

//--- Forward Declaration of MarketZoneTrader
class MarketZoneTrader;

Hier führen wir eine Vorwärtsdeklaration der Klasse „MarketZoneTrader“ ein. Wir fügen sie vor der Definition der Klasse „BasketManager“ ein, die wir gleich nach dieser Klasse definieren werden, damit sie auf „MarketZoneTrader“ verweisen kann, ohne dass ihre vollständige Definition erforderlich ist. Diese Änderung ist notwendig, weil unser neues Multi-Basket-System, das von „BasketManager“ verwaltet wird, mehrere Instanzen von „MarketZoneTrader“ für verschiedene Handelskörbe erstellen und verwalten muss. Indem wir „MarketZoneTrader“ zuerst deklarieren, stellen wir sicher, dass der Compiler es erkennt, wenn es in der neuen Klasse verwendet wird, sodass unser System mehrere gleichzeitige Handelszyklen effizient unterstützen kann. Dann können wir die Managerklasse definieren.

//--- Basket Manager Class to Handle Multiple Traders
class BasketManager {
private:
   MarketZoneTrader* m_traders[];                                        //--- Array of trader instances
   int               m_handleRsi;                                        //--- RSI indicator handle
   int               m_handleEnvUpper;                                   //--- Upper Envelopes handle
   int               m_handleEnvLower;                                   //--- Lower Envelopes handle
   double            m_rsiBuffer[];                                     //--- RSI data buffer
   double            m_envUpperBandBuffer[];                            //--- Upper Envelopes buffer
   double            m_envLowerBandBuffer[];                            //--- Lower Envelopes buffer
   string            m_symbol;                                          //--- Trading symbol
   int               m_baseMagicNumber;                                 //--- Base magic number
   int               m_maxInitialPositions;                             //--- Maximum baskets (signals)

   //--- Initialize Indicators
   bool initializeIndicators() {
      m_handleRsi = iRSI(m_symbol, PERIOD_CURRENT, 8, PRICE_CLOSE);
      if (m_handleRsi == INVALID_HANDLE) {
         Print("Failed to initialize RSI indicator");
         return false;
      }
      m_handleEnvUpper = iEnvelopes(m_symbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1);
      if (m_handleEnvUpper == INVALID_HANDLE) {
         Print("Failed to initialize upper Envelopes indicator");
         return false;
      }
      m_handleEnvLower = iEnvelopes(m_symbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4);
      if (m_handleEnvLower == INVALID_HANDLE) {
         Print("Failed to initialize lower Envelopes indicator");
         return false;
      }
      ArraySetAsSeries(m_rsiBuffer, true);
      ArraySetAsSeries(m_envUpperBandBuffer, true);
      ArraySetAsSeries(m_envLowerBandBuffer, true);
      return true;
   }

}

Um die Verwaltung von Handelsgeschäften eines Korbs zu erleichtern, definieren wir die Klasse „BasketManager“ mit privaten Mitgliedern, um mehrere Instanzen der Klasse „MarketZoneTrader“ und Indikatordaten zu verwalten. Wir erstellen „m_traders“, ein Array von Zeigern vom Typ „MarketZoneTrader“, um einzelne Handelskörbe zu speichern, die jeweils einen separaten Zonenerholungszyklus repräsentieren. Diese Änderung ist von entscheidender Bedeutung, da sie es uns ermöglicht, mehrere Handelssignale gleichzeitig zu verwalten, im Gegensatz zu dem Single-Instance-Ansatz in der vorherigen Version. Wir deklarieren auch „m_handleRsi“, „m_handleEnvUpper“ und „m_handleEnvLower“, um Indikator-Handles zu halten, und „m_rsiBuffer“, „m_envUpperBandBuffer“ und „m_envLowerBandBuffer“, um die Daten des RSI und von Envelopes zu speichern, wobei die Indikatorenverwaltung von „MarketZoneTrader“ zu „BasketManager“ verlagert wird, um eine zentralisierte Kontrolle über die Körbe zu ermöglichen.

Zusätzlich fügen wir „m_symbol“ hinzu, um das Handelssymbol zu speichern, „m_baseMagicNumber“, um eindeutige magische Zahlen pro Korb zu generieren, und „m_maxInitialPositions“, um die Anzahl der aktiven Körbe zu begrenzen, die mit der neuen Eingabe „maxInitialPositions“ übereinstimmen. In der Funktion „initializeIndicators“ werden der RSI-Indikator mit iRSI mit einer Periodenlänge von 8 und den Indikator Envelopes mit iEnvelopes (Periodenlänge 150 und der Abweichung 0,1 und der Periodenlänge von 95 mit der Abweichung 1,4) eingerichtet, auf „INVALID_HANDLE“ geprüft und Fehler mit Print protokolliert. Wir konfigurieren „m_rsiBuffer“, „m_envUpperBandBuffer“ und „m_envLowerBandBuffer“ als Zeitserien-Arrays mit ArraySetAsSeries. Diese neue Klassenstruktur wird es uns ermöglichen, mehrere Handelskörbe effizient zu koordinieren und die Indikatordaten für eine konsistente Signalerzeugung über alle Körbe hinweg zu zentralisieren. Dann brauchen wir eine Logik zum Zählen aller einzelnen Korbpositionen, um die Verfolgung zu erleichtern, und zum Bereinigen der Körbe.

//--- Count Active Baskets
int countActiveBaskets() {
   int count = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() != MarketZoneTrader::INACTIVE) {
         count++;
      }
   }
   return count;
}

//--- Cleanup Terminated Baskets
void cleanupTerminatedBaskets() {
   int newSize = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() == MarketZoneTrader::INACTIVE) {
         delete m_traders[i];
         m_traders[i] = NULL;
      }
      if (m_traders[i] != NULL) newSize++;
   }
   MarketZoneTrader* temp[];
   ArrayResize(temp, newSize);
   int index = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         temp[index] = m_traders[i];
         index++;
      }
   }
   ArrayFree(m_traders);
   ArrayResize(m_traders, newSize);
   for (int i = 0; i < newSize; i++) {
      m_traders[i] = temp[i];
   }
   ArrayFree(temp);
}

Hier fügen wir der Klasse „BasketManager“ zwei neue Funktionen hinzu, „countActiveBaskets“ und „cleanupTerminatedBaskets“. Wir beginnen mit der Funktion „countActiveBaskets“, um die Anzahl der aktiven Handelskörbe zu ermitteln. Wir initialisieren eine Variable „count“ mit 0 und durchlaufen das Array „m_traders“ mit der Funktion ArraySize. Für jeden „m_traders“-Eintrag, der nicht null ist, wird geprüft, ob sein Status, der über „getCurrentState“ ermittelt wurde, nicht „MarketZoneTrader::INACTIVE“ ist. Wenn aktiv, erhöhen wir „count“. Wir geben „count“ zurück, um zu überwachen, wie viele Körbe derzeit laufen. Dies ist wichtig, um sicherzustellen, dass wir bei der Eröffnung neuer Körbe innerhalb des Limits „m_maxInitialPositions“ bleiben.

Als Nächstes erstellen wir die Funktion „cleanupTerminatedBaskets“, um inaktive Körbe zu entfernen und den Speicher zu optimieren. Zuerst zählen wir die nicht-null Einträge in „m_traders“, indem wir das Array in einer Schleife durchlaufen. Wenn „trader“ nicht null ist und sein „getCurrentState“ „MarketZoneTrader::INACTIVE“ zurückgibt, verwenden wir „delete“, um seinen Speicher freizugeben und den Eintrag auf „NULL“ zu setzen. Wir verfolgen die Anzahl der verbleibenden „trader“, die nicht null sind, in „newSize“. Dann erstellen wir ein temporäres Array „temp“, ändern seine Größe mit ArrayResize auf „newSize“ und kopieren „trader“, die nicht-null sind, von „m_traders“ nach „temp“ mit einem „index“-Zähler. Wir löschen „m_traders“ mit „ArrayFree“, ändern die Größe auf „newSize“ und übertragen die „trader“ zurück von „temp“. Schließlich geben wir „temp“ mit ArrayFree frei. Diese Bereinigung gewährleistet, dass wir abgebrochene Körbe entfernen und unser System effizient und bereit für neue Handelsgeschäfte bleibt. Anschließend gehen wir zum Modifikator für den öffentlichen Zugriff über, wo wir die Art und Weise ändern, wie wir den Konstruktor und Destruktor beim Initialisieren und Zerstören der Klassenmitglieder und -elemente handhaben.

public:
   BasketManager(string symbol, int baseMagic, int maxInitPos) {
      m_symbol = symbol;
      m_baseMagicNumber = baseMagic;
      m_maxInitialPositions = maxInitPos;
      ArrayResize(m_traders, 0);
      m_handleRsi = INVALID_HANDLE;
      m_handleEnvUpper = INVALID_HANDLE;
      m_handleEnvLower = INVALID_HANDLE;
   }

   ~BasketManager() {
      for (int i = 0; i < ArraySize(m_traders); i++) {
         if (m_traders[i] != NULL) delete m_traders[i];
      }
      ArrayFree(m_traders);
      cleanupIndicators();
   }

Wir beginnen mit dem Konstruktor „BasketManager“, der „symbol“, „baseMagic“ und „maxInitPos“ als Parameter benötigt. Diese weisen wir „m_symbol“, „m_baseMagicNumber“ und „m_maxInitialPositions“ zu, um das Handelssymbol, die magische Basiszahl zur eindeutigen Identifizierung des Korbs und die maximale Anzahl der aktiven Körbe festzulegen. Wir initialisieren das Array „m_traders“ mit der Funktion ArrayResize auf die Größe Null und setzen die Indikator-Handles „m_handleRsi“, „m_handleEnvUpper“ und „m_handleEnvLower“ auf „INVALID_HANDLE“, um die spätere Einrichtung des Indikators vorzubereiten. Dieser Konstruktor ist entscheidend für die Konfiguration des Mehrkorbsystems.

Als Nächstes erstellen wir den Destruktor „~BasketManager“, um die Ressourcen aufzuräumen. Standardmäßig haben Destruktoren das Tilde-Zeichen (~) als Präfix, nur zur Erinnerung. Wir durchlaufen das Array „m_traders“ mit ArraySize und löschen alle Instanzen von „MarketZoneTrader“, die nicht null sind, mit delete, um ihren Speicher freizugeben. Anschließend leeren wir das Array „m_traders“ mit ArrayFree und rufen „cleanupIndicators“ auf, um Indikator-Handles und Puffer freizugeben. Dadurch wird sichergestellt, dass unser System sauber heruntergefahren wird und keine Speicherlecks entstehen, wenn der EA anhält. In der vorherigen Version mussten wir die Löschlogik in OnDeinit direkt hinzufügen, nachdem wir festgestellt hatten, dass es ein Speicherleck gab, aber hier können wir sie frühzeitig hinzufügen, da wir bereits wissen, dass wir uns um Speicherlecks kümmern müssen. Dann müssen wir die Initialisierungslogik so ändern, dass sie bestehende Positionen in die jeweiligen Körbe laden kann. Hier ist die Logik, mit der wir das erreichen.

bool initialize() {
   if (!initializeIndicators()) return false;
   //--- Load existing positions into baskets
   int totalPositions = PositionsTotal();
   for (int i = 0; i < totalPositions; i++) {
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket)) {
         if (PositionGetString(POSITION_SYMBOL) == m_symbol) {
            long magic = PositionGetInteger(POSITION_MAGIC);
            if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) {
               //--- Check if basket already exists for this magic
               bool exists = false;
               for (int j = 0; j < ArraySize(m_traders); j++) {
                  if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) {
                     exists = true;
                     break;
                  }
               }
               if (!exists && countActiveBaskets() < m_maxInitialPositions) {
                  createNewBasket(magic, ticket);
               }
            }
         }
      }
   }
   Print("BasketManager initialized with ", ArraySize(m_traders), " existing baskets");
   return true;
}

/*
//--- PREVIOUS INITIALIZATION
int initialize() {
   //--- Initialization Start
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number
   int totalPositions = PositionsTotal();                           //--- Get total positions
   
   for (int i = 0; i < totalPositions; i++) {                       //--- Iterate positions
      ulong ticket = PositionGetTicket(i);                          //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                         //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic
            if (activateTrade(ticket)) {                            //--- Activate position
               Print("Existing position activated: Ticket=", ticket); //--- Log activation
            } else {
               Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure
            }
         }
      }
   }
   
   m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI
   if (m_handleRsi == INVALID_HANDLE) {                             //--- Check RSI
      Print("Failed to initialize RSI indicator");                  //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes
   if (m_handleEnvUpper == INVALID_HANDLE) {                        //--- Check upper Envelopes
      Print("Failed to initialize upper Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes
   if (m_handleEnvLower == INVALID_HANDLE) {                        //--- Check lower Envelopes
      Print("Failed to initialize lower Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   ArraySetAsSeries(m_rsiBuffer, true);                             //--- Set RSI buffer
   ArraySetAsSeries(m_envUpperBandBuffer, true);                    //--- Set upper Envelopes buffer
   ArraySetAsSeries(m_envLowerBandBuffer, true);                    //--- Set lower Envelopes buffer
   
   Print("EA initialized successfully");                            //--- Log success
   return INIT_SUCCEEDED;                                           //--- Return success
   //--- Initialization End
}
*/

Hier implementieren wir die aktualisierte Funktion „initialize“ in der Klasse „BasketManager“, um unsere Verbesserung des Multi-Basket-Handels zu unterstützen, indem wir Indikatoren initialisieren und bestehende Positionen in separate Baskets laden. Wir beginnen mit dem Aufruf von „initializeIndicators“, um die Indikatoren RSI und Envelopes einzurichten, und geben false zurück, wenn der Aufruf fehlschlägt, um sicherzustellen, dass unser System über die erforderlichen Marktdaten verfügt. Im Gegensatz zur Vorgängerversion, in der wir die Einrichtung der Indikatoren direkt in der Funktion „Initialisieren“ des „MarketZoneTrader“ vorgenommen haben, haben wir dies nun im „BasketManager“ zentralisiert, um die Indikatordaten für mehrere Körbe gemeinsam zu nutzen. Als Nächstes wird mit der Funktion PositionsTotal geprüft, ob Positionen vorhanden sind, und jede Position wird in einer Schleife durchlaufen, um ihr „ticket“ mit der Funktion PositionGetTicket abzurufen.

Wenn PositionSelectByTicket erfolgreich ist und das Symbol der Position mit „m_symbol“ (über PositionGetString) übereinstimmt, wird überprüft, ob die mit „PositionGetInteger“ ermittelte magische Zahl im Bereich von „m_baseMagicNumber“ bis „m_baseMagicNumber + m_maxInitialPositions“ liegt. Anschließend wird geprüft, ob für diese magische Zahl bereits ein Korb existiert, indem „m_traders“ in einer Schleife durchlaufen und „getMagicNumber“ bei Einträgen aufgerufen wird, die nicht null sind. Wenn kein Korb existiert und „countActiveBaskets“ unter „m_maxInitialPositions“ liegt, rufen wir „createNewBasket“ mit der magischen Zahl und „ticket“ auf, um die Position in einen neuen Korb zu laden. Schließlich protokollieren wir die Anzahl der initialisierten Körbe mit „Print“ unter Verwendung von ArraySize von „m_traders“ und geben true zurück. Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis:

INITIALISIERUNG DER KÖRBE

Wir können nun zur Verarbeitung von Ticks übergehen, wobei wir die bestehenden Körbe bei jedem Tick verarbeiten und neue Körbe erstellen müssen, wenn neue Signale in der Funktion „processTick“ bestätigt werden, im Gegensatz zur vorherigen Version, in der wir nur auf der Grundlage von bestätigten Signalen Handelsgeschäfte einleiten mussten.

void processTick() {
   //--- Process existing baskets
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         m_traders[i].processTick(m_rsiBuffer, m_envUpperBandBuffer, m_envLowerBandBuffer);
      }
   }
   cleanupTerminatedBaskets();

   //--- Check for new signals on new bar
   if (!isNewBar()) return;

   if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) {
      Print("Error loading RSI data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) {
      Print("Error loading upper envelopes data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) {
      Print("Error loading lower envelopes data. Reverting.");
      return;
   }

   const int rsiOverbought = 70;
   const int rsiOversold = 30;
   int ticket = -1;
   ENUM_ORDER_TYPE signalType = (ENUM_ORDER_TYPE)-1;

   double askPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_ASK), Digits());
   double bidPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_BID), Digits());

   if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) {
      if (askPrice > m_envUpperBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_BUY;
         }
      }
   } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) {
      if (bidPrice < m_envLowerBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_SELL;
         }
      }
   }

   if (signalType != (ENUM_ORDER_TYPE)-1) {
      //--- Create new basket with unique magic number
      int newMagic = m_baseMagicNumber + ArraySize(m_traders);
      if (newMagic < m_baseMagicNumber + m_maxInitialPositions) {
         MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, newMagic);
         ticket = newTrader.openInitialOrder(signalType); //--- Open INITIAL position
         if (ticket > 0 && newTrader.activateTrade(ticket)) {
            int size = ArraySize(m_traders);
            ArrayResize(m_traders, size + 1);
            m_traders[size] = newTrader;
            Print("New basket created: Magic=", newMagic, ", Ticket=", ticket, ", Type=", EnumToString(signalType));
         } else {
            delete newTrader;
            Print("Failed to create new basket: Ticket=", ticket);
         }
      } else {
         Print("Maximum initial positions (baskets) reached: ", m_maxInitialPositions);
      }
   }
}

In der Funktion beginnen wir mit einer Schleife durch das Array „m_traders“ unter Verwendung der Funktion ArraySize und rufen für jede Instanz von „MarketZoneTrader“, die nicht null ist, die Funktion „processTick“ auf, wobei wir „m_rsiBuffer“, „m_envUpperBandBuffer“ und „m_envLowerBandBuffer“ übergeben, um die Logik der einzelnen Körbe zu behandeln. Dies unterscheidet sich von der vorherigen Version, bei der „processTick“ direkt einen einzelnen Handelszyklus verwaltete. Anschließend rufen wir „cleanupTerminatedBaskets“ auf, um inaktive Körbe zu entfernen und eine effiziente Ressourcennutzung zu gewährleisten. Als Nächstes prüfen wir mit „isNewBar“ nur bei einem neuen Balken auf neue Handelssignale und beenden den Vorgang, wenn er falsch ist, um Ressourcen zu sparen.

Wir laden die Indikatordaten mit CopyBuffer für „m_handleRsi“, „m_handleEnvUpper“ und „m_handleEnvLower“ in ihre jeweiligen Puffer, protokollieren Fehler mit „Print“ und beenden, wenn einer fehlschlägt, im Gegensatz zur vorherigen Version, wo dies im „MarketZoneTrader“ geschah. Wir setzen „rsiOverbought“ auf 70 und „rsiOversold“ auf 30, und initialisieren „ticket“ und „signalType“. Wir holen „askPrice“ und „bidPrice“ mit SymbolInfoDouble mit „SYMBOL_ASK“ und SYMBOL_BID, normalisiert mit der Funktion NormalizeDouble.

Für ein Kaufsignal, wenn „m_rsiBuffer“ auf überverkaufte Bedingungen hinweist und „askPrice“ über „m_envUpperBandBuffer“ liegt, setzen wir „signalType“ auf ORDER_TYPE_BUY, wenn „countActiveBaskets“ unter „m_maxInitialPositions“ liegt. Für ein Verkaufssignal, wenn „m_rsiBuffer“ überkaufte Bedingungen anzeigt und „bidPrice“ unter „m_envLowerBandBuffer“ liegt, setzen wir „signalType“ auf ORDER_TYPE_SELL. Wenn ein gültiger „signalType“ existiert, erstellen wir eine einzigartige magische Zahl mit „m_baseMagicNumber“ plus „ArraySize(m_traders)“, und wenn innerhalb von „m_maxInitialPositions“, instanziieren wir einen neuen „MarketZoneTrader“ mit Eingabeparametern und der neuen magischen Zahl.

Wir rufen „openInitialOrder“ mit „signalType“ auf, und wenn das zurückgegebene „ticket“ gültig ist und „activateTrade“ erfolgreich ist, fügen wir den neuen „trader“ mit ArrayResize zu „m_traders“ hinzu und protokollieren den Erfolg mit „Print“ und der Funktion EnumToString. Andernfalls löschen wir „trader“ und protokollieren den Fehler bzw. vermerken, wenn das Korblimit erreicht ist. Sobald die neuen Handelsgeschäfte eröffnet sind, müssen wir neue Körbe für sie erstellen. Hier ist die Logik, mit der wir das erreichen.

private:
   void createNewBasket(long magic, ulong ticket) {
      MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, magic);
      if (newTrader.activateTrade(ticket)) {
         int size = ArraySize(m_traders);
         ArrayResize(m_traders, size + 1);
         m_traders[size] = newTrader;
         Print("Existing position loaded into basket: Magic=", magic, ", Ticket=", ticket);
      } else {
         delete newTrader;
         Print("Failed to load existing position into basket: Ticket=", ticket);
      }
   }

Wir implementieren die Funktion „createNewBasket“ im privaten Bereich der Klasse „BasketManager“, eine neue Ergänzung zur Unterstützung unserer Verbesserung des Multi-Basket-Handels durch die Erstellung und Verwaltung neuer Handelskörbe für bestehende Positionen. Wir beginnen mit der Erstellung einer neuen „MarketZoneTrader“-Instanz mit dem Namen „newTrader“, wobei wir die Eingabeparameter „lotOption“, „initialLotSize“, „riskPercentage“, „riskPoints“, „zoneTargetPoints“, „zoneSizePoints“ und der angegebenen „magischen“ Zahl, um einen einzigartigen Handelskorb zu konfigurieren. Erinnern Sie sich, dass wir diese Nutzereingabe in der vorherigen Version in der Initialisierungsphase hatten, weil wir nur eine Instanz der Zone brauchten, sodass sie für alle neuen Positionen galt, aber in diesem Fall organisieren wir sie in neuen Klasseninstanzen. Hier ist der Code dafür, um einen schnelleren Vergleich zu ermöglichen.

//--- PREVIOUS VERSION OF NEW CLASS INSTANCE
//--- Global Instance
MarketZoneTrader *trader = NULL;                                        //--- Declare trader instance

int OnInit() {
   //--- EA Initialization Start
   trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance
   return trader.initialize();                                           //--- Initialize EA
   //--- EA Initialization End
}

Wir rufen dann „activateTrade“ auf „newTrader“ mit dem angegebenen „ticket“ auf, um die bestehende Position in den Korb zu laden. Bei Erfolg ermitteln wir die aktuelle Größe des Arrays „m_traders“ mit ArraySize, erweitern es mit ArrayResize um eins und fügen „newTrader“ in den neuen Slot ein. Wir protokollieren den Erfolg mit „Print“, einschließlich der Werte für „magic“ und „ticket“. Wenn „activateTrade“ fehlschlägt, löschen wir „newTrader“, um Speicher freizugeben und protokollieren den Fehler mit „Print“. Die Funktion ermöglicht es uns nun, bestehende Positionen in separaten Körben zu organisieren, ein Hauptmerkmal unseres Multi-Basket-Systems, im Gegensatz zum Single-Instance-Ansatz in der vorherigen Version. Diese Klasse wird es uns nun ermöglichen, die Handelskörbe effektiv zu verwalten. Gehen wir nun dazu über, die Basisklasse so zu modifizieren, dass sie die neuen Funktionen „Multiple Baskets“ und „Trailing-Stop“ enthalten kann. Beginnen wir mit seinen Mitgliedern.

//--- Modified MarketZoneTrader Class
class MarketZoneTrader {
private:
   enum TradeState { INACTIVE, RUNNING, TERMINATING };

   struct TradeMetrics {
      bool   operationSuccess;
      double totalVolume;
      double netProfitLoss;
   };

   struct ZoneBoundaries {
      double zoneHigh;
      double zoneLow;
      double zoneTargetHigh;
      double zoneTargetLow;
   };

   struct TradeConfig {
      string         marketSymbol;
      double         openPrice;
      double         initialVolume;
      long           tradeIdentifier;
      string         initialTradeLabel;  //--- Label for initial positions
      string         recoveryTradeLabel; //--- Label for recovery positions
      ulong          activeTickets[];
      ENUM_ORDER_TYPE direction;
      double         zoneProfitSpan;
      double         zoneRecoverySpan;
      double         accumulatedBuyVolume;
      double         accumulatedSellVolume;
      TradeState     currentState;
      bool           hasRecoveryTrades;  //--- Flag to track recovery trades
      double         trailingStopLevel;  //--- Virtual trailing stop level
   };

   struct LossTracker {
      double tradeLossTracker;
   };

   TradeConfig           m_tradeConfig;
   ZoneBoundaries        m_zoneBounds;
   LossTracker           m_lossTracker;
   string                m_lastError;
   int                   m_errorStatus;
   CTrade                m_tradeExecutor;
   TradingLotSizeOptions m_lotOption;
   double                m_initialLotSize;
   double                m_riskPercentage;
   int                   m_riskPoints;
   double                m_zoneTargetPoints;
   double                m_zoneSizePoints;
}

Hier erweitern wir unser Programm, indem wir die Klasse „MarketZoneTrader“, insbesondere ihren privaten Teil, modifizieren, um neue Funktionen zur Unterstützung von Trailing-Stops und eine verbesserte Handelskennzeichnung einzubauen. Wir behalten die Kernstruktur bei, führen aber wichtige Änderungen an der „TradeConfig“ -Struktur ein, um sie mit unserer erweiterten Strategie in Einklang zu bringen. Die Enumeration „TradeState“ mit den Zuständen „INACTIVE“, „RUNNING“ und „TERMINATING“ sowie die Strukturen „TradeMetrics“, „ZoneBoundaries“ und „LossTracker“ bleiben gegenüber der Vorgängerversion unverändert, da sie weiterhin Handelszustände, Leistungsmetriken, Zonengrenzen und Verlustverfolgung verwalten.

In der Struktur „TradeConfig“ fügen wir zwei neue String-Variablen hinzu: „initialTradeLabel“ und „recoveryTradeLabel“. Diese Etiketten ermöglichen es uns, Erst- und Wiederbeschaffungsgeschäfte getrennt zu kennzeichnen, was die Identifizierung und Verfolgung von Handelsgeschäften innerhalb jedes Korbes verbessert, was besonders nützlich für die Verwaltung mehrerer Körbe in unserem neuen System ist. Wir führen auch „hasRecoveryTrades“ ein, ein Boolescher Wert, der anzeigt, ob ein Korb Erholungsgeschäfte enthält, was für die angemessene Aktivierung oder Deaktivierung von Trailing-Stops entscheidend ist. Zusätzlich fügen wir „trailingStopLevel“ hinzu, ein Double, um das virtuelle Trailing-Stop-Level für jeden Korb zu speichern, was eine dynamische Gewinnsicherung für die ersten Handelsgeschäfte ermöglicht.

Von den Mitgliedsvariablen behalten wir „m_tradeConfig“, „m_zoneBounds“, „m_lossTracker“, „m_lastError“, „m_errorStatus“, „m_tradeExecutor“, „m_lotOption“, „m_initialLotSize“, „m_riskPercentage“, „m_riskPoints“, „m_zoneTargetPoints“ und „m_zoneSizePoints“ wie bisher, aber ihre Rollen unterstützen jetzt die neue Trailing-Stop- und Multi-Basket-Funktionalität innerhalb jeder „MarketZoneTrader“-Instanz. Insbesondere entfernen wir die indikatorbezogenen Variablen wie „m_handleRsi“ und „m_rsiBuffer“ aus der Klasse, da diese nun zentral von der Klasse „BasketManager“ verwaltet werden, was die Konzentration jedes Händlers auf einzelne Korboperationen vereinfacht. Im Konstruktor und Destruktor müssen wir einige Variablen geringfügig ändern, damit sie die neuen Funktionen verarbeiten können.

public:
   MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, double targetPts, double sizePts, long magic) {
      m_tradeConfig.currentState = INACTIVE;
      ArrayResize(m_tradeConfig.activeTickets, 0);
      m_tradeConfig.zoneProfitSpan = targetPts * _Point;
      m_tradeConfig.zoneRecoverySpan = sizePts * _Point;
      m_lossTracker.tradeLossTracker = 0.0;
      m_lotOption = lotOpt;
      m_initialLotSize = initLot;
      m_riskPercentage = riskPct;
      m_riskPoints = riskPts;
      m_zoneTargetPoints = targetPts;
      m_zoneSizePoints = sizePts;
      m_tradeConfig.marketSymbol = _Symbol;
      m_tradeConfig.tradeIdentifier = magic;
      m_tradeConfig.initialTradeLabel = "EA_INITIAL_" + IntegerToString(magic); //--- Label for initial positions
      m_tradeConfig.recoveryTradeLabel = "EA_RECOVERY_" + IntegerToString(magic); //--- Label for recovery positions
      m_tradeConfig.hasRecoveryTrades = false; //--- Initialize recovery flag
      m_tradeConfig.trailingStopLevel = 0.0; //--- Initialize trailing stop
      m_tradeExecutor.SetExpertMagicNumber(magic);
   }

   ~MarketZoneTrader() {
      ArrayFree(m_tradeConfig.activeTickets);
   }

Wir beginnen mit dem Konstruktor „MarketZoneTrader“, der jetzt einen zusätzlichen „magic“-Parameter akzeptiert, um eine eindeutige magische Zahl für jeden Handelskorb zuzuweisen, im Gegensatz zur vorherigen Version, die eine feste magische Zahl verwendete. Zur Unterstützung einer verbesserten Handelskennzeichnung fügen wir „m_tradeConfig.initialTradeLabel“ als „EA_INITIAL“ plus „magic“ (über IntegerToString) und „m_tradeConfig.recoveryTradeLabel“ als „EA_RECOVERY“ plus „magic“ hinzu, was eine eindeutige Identifizierung von Initial- und Recovery-Handelsgeschäfte innerhalb eines Korbs ermöglicht. Wir initialisieren „m_tradeConfig.hasRecoveryTrades“ auf false, um den Recovery-Trade-Status zu verfolgen, und setzen „m_tradeConfig.trailingStopLevel“ auf 0,0 für den virtuellen Trailing-Stop, beides neue Funktionen. Schließlich konfigurieren wir „m_tradeExecutor“ mit „SetExpertMagicNumber“ unter Verwendung von „magic“. Wir haben die wichtigsten Änderungen zur schnellen Identifizierung hervorgehoben.

Als Nächstes vereinfachen wir den Destruktor „~MarketZoneTrader“ im Vergleich zur vorherigen Version, die „cleanup“ genannt wurde. Wir löschen jetzt nur noch „m_tradeConfig.activeTickets“ mit ArrayFree, da die Bereinigung der Indikatoren von „BasketManager“ übernommen wird, wodurch sich der Anwendungsbereich des Destruktors auf basket-spezifische Ressourcen konzentriert. Wir können dann die Funktion, die für die Aktivierung von Handelsgeschäften verantwortlich ist, so aktualisieren, dass sie das Trailing-Stop-Niveau und den Erholungszustand für die ersten Handelsgeschäfte initialisieren kann.

bool activateTrade(ulong ticket) {

   m_tradeConfig.hasRecoveryTrades = false;
   m_tradeConfig.trailingStopLevel = 0.0;
   
   //--- THE REST OF THE LOGIC REMAINS
   
   return true;
}

Hier fügen wir lediglich die Logik hinzu, um das Trailing-Stop-Level des ersten Handelsgeschäfts auf 0 und den Recovery-Status auf false zu setzen, um anzuzeigen, dass es sich um die erste Position im Basket handelt. Schließlich können wir eine Funktion hinzufügen, um die Ausgangsposition zu öffnen.

int openInitialOrder(ENUM_ORDER_TYPE orderType) {
   //--- Open INITIAL position based on signal
   int ticket;
   double openPrice;
   if (orderType == ORDER_TYPE_BUY) {
      openPrice = NormalizeDouble(getMarketAsk(), Digits());
   } else if (orderType == ORDER_TYPE_SELL) {
      openPrice = NormalizeDouble(getMarketBid(), Digits());
   } else {
      Print("Invalid order type [Magic=", m_tradeConfig.tradeIdentifier, "]");
      return -1;
   }
   double lotSize = 0;
   if (m_lotOption == FIXED_LOTSIZE) {
      lotSize = m_initialLotSize;
   } else if (m_lotOption == UNFIXED_LOTSIZE) {
      lotSize = calculateLotSize(m_riskPercentage, m_riskPoints);
   }
   if (lotSize <= 0) {
      Print("Invalid lot size [Magic=", m_tradeConfig.tradeIdentifier, "]: ", lotSize);
      return -1;
   }
   if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, m_tradeConfig.initialTradeLabel)) {
      ticket = (int)m_tradeExecutor.ResultOrder();
      Print("INITIAL trade opened [Magic=", m_tradeConfig.tradeIdentifier, "]: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize);
   } else {
      ticket = -1;
      Print("Failed to open INITIAL order [Magic=", m_tradeConfig.tradeIdentifier, "]: Type=", EnumToString(orderType), ", Volume=", lotSize);
   }
   return ticket;
}

Wir implementieren eine neue Funktion „openInitialOrder“ im öffentlichen Bereich der Klasse „MarketZoneTrader“, um unsere Multi-Basket- und verbesserten Trade-Labeling-Erweiterungen zu unterstützen, indem wir Anfangspositionen für einen bestimmten Handelskorb mit eindeutiger Kennzeichnung eröffnen. Wir beginnen mit der Initialisierung von „ticket“ und „openPrice“. Für „orderType“, das auf ORDER_TYPE_BUY gesetzt ist, setzen wir „openPrice“ mit „getMarketAsk“ und normalisieren es mit NormalizeDouble und „Digits“. Für „ORDER_TYPE_SELL“ verwenden wir „getMarketBid“. Wenn „orderType“ ungültig ist, protokollieren wir einen Fehler mit „Print“, einschließlich „m_tradeConfig.tradeIdentifier“, und geben -1 zurück.

Wir bestimmen „lotSize“ auf der Grundlage von „m_lotOption“: für „FIXED_LOTSIZE“ verwenden wir „m_initialLotSize“; für „UNFIXED_LOTSIZE“ rufen wir „calculateLotSize“ mit „m_riskPercentage“ und „m_riskPoints“ auf. Wenn „lotSize“ ungültig ist, protokollieren wir den Fehler mit „Print“ und geben -1 zurück. Wir eröffnen dann die Position mit „m_tradeExecutor.PositionOpen“ mit „m_tradeConfig.marketSymbol“, „orderType“, „lotSize“, „openPrice“ und „m_tradeConfig.initialTradeLabel“ zur eindeutigen Kennzeichnung der ersten Handelsgeschäfts. Bei Erfolg setzen wir „ticket“ mit „ResultOrder“ und protokollieren den Handel mit „Print“, einschließlich „m_tradeConfig.tradeIdentifier“ und der Funktion EnumToString. Bei einem Fehlschlag setzen wir „ticket“ auf -1 und protokollieren den Fehler. Schließlich geben wir das „Ticket“ zurück. Im Gegensatz zur „openOrder“-Funktion der Vorgängerversion verwendet diese Funktion das neue „initialTradeLabel“ und konzentriert sich ausschließlich auf Anfangspositionen, was unserem Mehrkorbsystem entspricht. Nach der Kompilierung erhalten wir das folgende Ergebnis.

ANFANGSKORB

Aus dem Bild können wir ersehen, dass wir den ursprünglichen Handel öffnen und eine neue Korbinstanz für ihn erstellen können. Wir brauchen jetzt eine Trailing-Logik, damit wir die Trailing-Stop-Funktion für die Positionen verwalten können.

void evaluateMarketTick() {
   if (m_tradeConfig.currentState == INACTIVE) return;
   if (m_tradeConfig.currentState == TERMINATING) {
      finalizePosition();
      return;
   }
   double currentPrice;
   double profitPoints = 0.0;

   //--- Handle BUY initial position
   if (m_tradeConfig.direction == ORDER_TYPE_BUY) {
      currentPrice = getMarketBid();
      profitPoints = (currentPrice - m_tradeConfig.openPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice - trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop > m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice <= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " <= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice > m_zoneBounds.zoneTargetHigh) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh);
         finalizePosition();
         return;
      } else if (currentPrice < m_zoneBounds.zoneLow) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow);
         triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);
      }
   }
   //--- Handle SELL initial position
   else if (m_tradeConfig.direction == ORDER_TYPE_SELL) {
      currentPrice = getMarketAsk();
      profitPoints = (m_tradeConfig.openPrice - currentPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice + trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop < m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice >= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " >= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice < m_zoneBounds.zoneTargetLow) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow);
         finalizePosition();
         return;
      } else if (currentPrice > m_zoneBounds.zoneHigh) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh);
         triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice);
      }
   }
}

Hier erweitern wir das Programm, indem wir die Funktion „evaluateMarketTick“ aktualisieren, um eine Trailing-Stop-Logik einzubauen, während wir die bestehende Zonenerholungslogik beibehalten. Wir beginnen mit der Überprüfung, ob „m_tradeConfig.currentState“ „INACTIVE“ oder „TERMINATING“ ist, beenden oder rufen „finalizePosition“ wie zuvor auf. Für eine Kaufposition („m_tradeConfig.direction“ als ORDER_TYPE_BUY) erhalten wir „currentPrice“ mit „getMarketBid“ und berechnen „profitPoints“ als Differenz zwischen „currentPrice“ und „m_tradeConfig.openPrice“ geteilt durch „_Point“. Die neue Trailing-Stop-Logik prüft, ob „enableInitialTrailing“ wahr ist, „m_tradeConfig.hasRecoveryTrades“ falsch ist und „profitPoints“ „minProfitPoints“ erreicht oder überschreitet. Wenn ja, berechnen wir „newTrailingStop“, indem wir „trailingStopPoints“ mal „_Point“ von „currentPrice“ abziehen. Wenn „profitPoints“ auch „minProfitPoints“ plus „trailingStopPoints“ übersteigt und „m_tradeConfig.trailingStopLevel“ entweder 0,0 oder kleiner als „newTrailingStop“ ist, aktualisieren wir „m_tradeConfig.trailingStopLevel“ und protokollieren es mit „Print“.

Wenn „m_tradeConfig.trailingStopLevel“ gesetzt ist und „currentPrice“ darunter fällt, protokollieren wir den Trigger und rufen „finalizePosition“ auf, um den Handel zu schließen. Die Logik der Zonenerholung bleibt unverändert, d.h. die Position wird geschlossen, wenn „currentPrice“ den Wert „m_zoneBounds.zoneTargetHigh“ übersteigt, oder es wird ein Erholungsverkaufsgeschäft mit „triggerRecoveryTrade“ ausgelöst, wenn der Wert unter „m_zoneBounds.zoneLow“ fällt.

Für eine Verkaufsposition („m_tradeConfig.direction“ als ORDER_TYPE_SELL), holen wir „currentPrice“ mit „getMarketAsk“ und berechnen „profitPoints“ umgekehrt. Die Trailing-Stop-Logik spiegelt den Kauffall wider, indem „newTrailingStop“ durch Hinzufügen von „trailingStopPoints“ mal _Point zu „currentPrice“ gesetzt wird, „m_tradeConfig.trailingStopLevel“ aktualisiert wird, wenn die Bedingungen erfüllt sind, und die Position geschlossen wird, wenn „currentPrice“ diesen Wert überschreitet. Die Logik der Zonenerholung schließt die Position, wenn „currentPrice“ unter „m_zoneBounds.zoneTargetLow“ liegt, oder löst einen Buy-Recovery-Trade aus, wenn er über „m_zoneBounds.zoneHigh“ liegt. Wir verzichten auf einen physischen Trailing-Stop, weil wir die volle Kontrolle über das System haben wollen. Auf diese Weise sind wir in der Lage, alle Instanzen zu überwachen und zu verwalten. Hier ist die Ausgabe nach der Ausführung des Programms für die Trailing-Stop-Funktion.

TRAILING-STOP-INSTANZ

Aus dem Bild können wir ersehen, dass wir die Position nachziehen und schließen können, wenn der Kurs auf das Trailing-Niveau zurückfällt. Schließlich erstellen wir einfach eine Instanz des Basket-Managers und verwenden ihn dann für die globale Verwaltung.

//--- Global Instance
BasketManager *manager = NULL;

int OnInit() {
   manager = new BasketManager(_Symbol, baseMagicNumber, maxInitialPositions);
   if (!manager.initialize()) {
      delete manager;
      manager = NULL;
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) {
   if (manager != NULL) {
      delete manager;
      manager = NULL;
      Print("EA deinitialized");
   }
}

void OnTick() {
   if (manager != NULL) {
      manager.processTick();
   }
}

Wir aktualisieren die globale Instanz und die Event-Handler, um die neue Klasse „BasketManager“ zu verwenden, die die vorherige Version der Klasse „MarketZoneTrader“ ersetzt, um unsere Verbesserung des Multi-Basket-Handels durch die Zentralisierung der Verwaltung mehrerer Handelskörbe zu unterstützen. Wir beginnen mit der Deklaration des globalen Zeigers „Manager“ auf die Klasse „BasketManager“, der mit „NULL“ initialisiert wird, anstelle des früheren Zeigers „trader“ auf „MarketZoneTrader“. Diese Änderung ist von entscheidender Bedeutung, da sie es uns ermöglicht, mehrere Handelskörbe über einen einzigen Manager zu verwalten, im Gegensatz zum Single-Instance-Ansatz in der vorherigen Version.

In OnInit erstellen wir eine neue Instanz von „BasketManager“ für „manager“ und übergeben „_Symbol“, „baseMagicNumber“ und „maxInitialPositions“, um sie für den aktuellen Chart, die eindeutige Basket-Identifikation und die maximale Anzahl der Körbe zu konfigurieren. Wir rufen „manager.initialize“ auf, um Indikatoren einzurichten und bestehende Positionen zu laden, und wenn dies fehlschlägt, löschen wir „manager“, setzen ihn auf „NULL“ und geben INIT_FAILED zurück. Bei Erfolg wird „INIT_SUCCEEDED“ zurückgegeben.

In „OnDeinit“ prüfen wir, ob „manager“ nicht „NULL“ ist, löschen ihn dann mit „delete“, setzen ihn auf „NULL“ und protokollieren die Deinitialisierung mit „Print“. Im OnTick prüfen wir, ob „manager“ nicht „NULL“ ist, und rufen „manager.processTick“ auf, um Marktticks über alle Körbe hinweg zu verarbeiten, und ersetzen damit den vorherigen Aufruf von „trader.processTick“. Dadurch wird die Tick-Verarbeitung für mehrere Körbe zentralisiert, was die Fähigkeit des Systems verbessert, gleichzeitige Handelssignale zu verwalten. Nach der Kompilierung erhalten wir folgendes Ergebnis.

ENDGÜLTIGE HANDEL

Aus dem Bild ist ersichtlich, dass wir separate Signalkörbe erstellen und verwalten können, wobei die verschiedenen Bezeichnungen aus der angegebenen magischen Zahl gebildet werden. 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 unser Zone Recovery System für Envelopes Trend Trading in MQL5 durch die Einführung von Trailing-Stops und einem Multi-Basket-Handelssystem erweitert, das auf der Grundlage von Teil 22 mit neuen Komponenten wie der Klasse „BasketManager“ und aktualisierten Funktionen von „MarketZoneTrader“ aufbaut. Diese Verbesserungen bieten einen flexibleren und robusteren Handelsrahmen, den Sie durch die Anpassung von Parametern wie „trailingStopPoints“ oder „maxInitialPositions“ weiter anpassen können.

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 unerlässlich, bevor Sie dieses Programm auf den Live-Märkten einsetzen.

Mit diesen Erweiterungen können Sie dieses System verfeinern oder seine Architektur anpassen, um neue Strategien zu entwickeln und Ihren algorithmischen Handel voranzutreiben. Viel Spaß beim Handeln!

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

Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Tyrone Chan
Tyrone Chan | 31 Aug. 2025 in 07:20

Sir, keine anfänglichen Verkaufstransaktionen werden eröffnet.

Hängt das mit der Handelslogik zusammen?

Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem
In dieser Diskussion werden wir weitere Fortschritte bei der Integration einer verfeinerten Logik zur Ereigniswarnung für die vom „News Headline EA“ angezeigten wirtschaftlichen Kalenderereignisse untersuchen. Diese Verbesserung ist von entscheidender Bedeutung, da sie sicherstellt, dass die Nutzer rechtzeitig vor wichtigen Ereignissen benachrichtigt werden. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.
Selbstoptimierende Expert Advisors in MQL5 (Teil 8): Analyse mehrerer Strategien (3) – Gewichtetes Abstimmungsverhalten Selbstoptimierende Expert Advisors in MQL5 (Teil 8): Analyse mehrerer Strategien (3) – Gewichtetes Abstimmungsverhalten
In diesem Artikel wird untersucht, wie die Bestimmung der optimalen Anzahl von Strategien in einem Ensemble eine komplexe Aufgabe sein kann, die durch den Einsatz des genetischen Optimierers von MetaTrader 5 leichter zu lösen ist. Die MQL5 Cloud wird auch als Schlüsselressource zur Beschleunigung von Backtests und Optimierung eingesetzt. Alles in allem schafft unsere Diskussion hier die Grundlage für die Entwicklung statistischer Modelle zur Bewertung und Verbesserung von Handelsstrategien auf der Grundlage unserer ersten Ensemble-Ergebnisse.
Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte
Dieser Artikel beschreibt den Aufbau einer Strategie des Kreuzens zweier gleitender Durchschnitte, die Signale aus einem höheren Zeitrahmen (D1) verwendet, um Einstiege auf einem niedrigeren Zeitrahmen (M15) zu steuern, wobei die Stop-Loss-Niveaus aus einem Zeitrahmen mit mittlerem Risiko (H4) berechnet werden. Es werden Systemkonstanten, nutzerdefinierte Enumerationen und Logik für trendfolgende und zum Mittelwert rückkehrende Modi eingeführt, wobei der Schwerpunkt auf Modularität und künftige Optimierung mithilfe eines genetischen Algorithmus liegt. Der Ansatz ermöglicht flexible Einstiegs- und Ausstiegsbedingungen und zielt darauf ab, die Signalverzögerung zu verringern und das Handels-Timing zu verbessern, indem Einstiegsmöglichkeiten im unteren Zeitrahmen mit Trends im oberen Zeitrahmen abgestimmt werden.
Vom Neuling zum Experten: Animierte Nachrichten-Schlagzeile mit MQL5 (IV) – Markteinsichten durch lokal verfügbare KI-Modelle Vom Neuling zum Experten: Animierte Nachrichten-Schlagzeile mit MQL5 (IV) – Markteinsichten durch lokal verfügbare KI-Modelle
In der heutigen Diskussion untersuchen wir, wie man Open-Source-KI-Modelle selbst hosten und zur Gewinnung von Markteinblicken nutzen kann. Dies ist Teil unserer laufenden Bemühungen, den News Headline EA zu erweitern, indem wir einen AI Info-Streifen einführen, die ihn in ein Multi-Integrations-Assistenz-Tool verwandelt. Der aktualisierte EA zielt darauf ab, Händler durch Kalenderereignisse, aktuelle Finanznachrichten, technische Indikatoren und jetzt auch durch KI-generierte Marktperspektiven auf dem Laufenden zu halten - und bietet so zeitnahe, vielfältige und intelligente Unterstützung für Handelsentscheidungen. Seien Sie dabei, wenn wir praktische Integrationsstrategien erforschen und untersuchen, wie MQL5 mit externen Ressourcen zusammenarbeiten kann, um ein leistungsstarkes und intelligentes Arbeitsterminal für den Handel aufzubauen.