Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik
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:
- Verstehen des erweiterten Trailing-Stops und der Multi-Basket-Architektur
- Implementation in MQL5
- Backtests
- 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.

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.

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:

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.

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.

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.

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:

Bericht des Backtest:

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem
Selbstoptimierende Expert Advisors in MQL5 (Teil 8): Analyse mehrerer Strategien (3) – Gewichtetes Abstimmungsverhalten
Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte
Vom Neuling zum Experten: Animierte Nachrichten-Schlagzeile mit MQL5 (IV) – Markteinsichten durch lokal verfügbare KI-Modelle
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Sir, keine anfänglichen Verkaufstransaktionen werden eröffnet.
Hängt das mit der Handelslogik zusammen?