
Automatisieren von Handelsstrategien in MQL5 (Teil 22): Erstellen eines Zone Recovery Systems für den Trendhandel mit Envelopes
Einführung
In unserem letzten Artikel (Teil 21) haben wir eine Handelsstrategie auf Basis der Neuronalen Netzwerke untersucht, die mit adaptiven Lernraten erweitert wurde, um die Vorhersagegenauigkeit für Marktbewegungen in MetaQuotes Language 5 (MQL5) zu verbessern. In Teil 22 verlagern wir den Schwerpunkt auf die Entwicklung eines Zone Recovery System, das in eine Envelopes-Trendhandelsstrategie integriert ist und den Relative Strength Index (RSI) und die Envelopes-Indikatoren kombiniert, um den Handel zu automatisieren und Verluste effektiv zu verwalten. Wir werden die folgenden Themen behandeln:
- Verstehen der Architektur der Zone Recovery und dem Trend von Envelopes
- Implementation in MQL5
- Backtests
- Schlussfolgerung
Am Ende werden Sie ein robustes MQL5-Handelssystem haben, das für dynamische Marktbedingungen entwickelt wurde und zur Implementierung und zum Testen bereit ist - legen wir los!
Verstehen der Architektur der Zone Recovery und dem Trend von Envelopes
Die Zone Recovery ist eine intelligente Handelsstrategie, die uns dabei hilft, potenzielle Verluste in Gewinne umzuwandeln, indem wir zusätzliche Handelsgeschäfte platzieren, wenn sich der Markt gegen uns bewegt, mit dem Ziel, einen Gewinn oder ein ausgeglichenes Ergebnis zu erzielen. Stellen Sie sich vor, Sie kaufen ein Währungspaar in der Erwartung, dass es steigt, aber es fällt – dann greift die Zone Recovery ein, indem wir eine Preisspanne oder „Zone“ festlegen, in der wir gegenläufige Handelsgeschäfte platzieren, um Verluste auszugleichen, wenn der Kurs wieder steigt. Wir haben vor, ein automatisiertes System in MetaQuotes Language 5 (MQL5) zu entwickeln, das dieses Konzept für den Handel auf den Devisenmärkten bei geringem Risiko und maximiertem Gewinn nutzt.
Damit dies funktioniert, werden wir zwei technische Indikatoren verwenden, um die optimalen Zeitpunkte für den Einstieg in den Handel zu ermitteln. Ein Indikator prüft die Energie des Marktes und stellt sicher, dass wir nur handeln, wenn es einen starken Schub in eine Richtung gibt, und vermeidet schwache oder chaotische Signale. Der andere, Envelopes, zeichnet einen Kanal um den Durchschnittspreis des Marktes und zeigt uns an, wenn die Preise zu weit nach oben oder unten gehen, was einen wahrscheinlichen Moment für einen Rücksetzer signalisiert. Diese Indikatoren arbeiten zusammen, um chancenreiche Handelsgeschäfte zu finden, bei denen der Preis bereit ist, innerhalb eines Trends umzukehren.
Und so wollen wir vorgehen: Wir beginnen mit einem Handelsgeschäft, wenn unsere Indikatoren eine Umkehr signalisieren, z. B. wenn der Kurs mit starkem Momentum den Rand des Envelopes-Kanals erreicht. Wenn sich der Markt in die falsche Richtung bewegt, aktivieren wir die Zonenerholung, indem wir innerhalb unserer festgelegten Preiszone entgegengesetzte Handelsgeschäfte eröffnen, die sorgfältig ausgewählt werden, um Risiko und Erholung auszugleichen. Wir begrenzen die Anzahl der Handelsgeschäfte, damit das System diszipliniert bleibt und sich nicht verzettelt. Mit diesem Setup können wir Trendchancen nutzen und haben gleichzeitig ein Sicherheitsnetz für den Fall, dass die Dinge nicht so laufen wie geplant, das sowohl an wilde als auch an ruhige Märkte angepasst werden kann. Bleiben Sie dabei, wenn wir diesen Plan in die Tat umsetzen und ausprobieren! Siehe den nachstehenden Durchführungsplan.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen zum Navigator, suchen den Ordner Indikatoren, klicken auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald das Programm erstellt ist, beginnen wir in der Programmierumgebung mit der Deklaration einiger Eingabevariablen, die uns helfen werden, die Schlüsselwerte des Programms leicht zu kontrollieren.
//+------------------------------------------------------------------+ //| Envelopes Trend Bounce with Zone Recovery EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade/Trade.mqh> //--- Include trade library enum TradingLotSizeOptions { FIXED_LOTSIZE, UNFIXED_LOTSIZE }; //--- Define lot size options 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 magicNumber = 123456789; // Magic Number input int maxOrders = 1; // Maximum Initial Positions input double zoneTargetPoints = 600; // Zone Target Points input double zoneSizePoints = 300; // Zone Size Points input bool restrictMaxOrders = true; // Apply Maximum Orders Restriction
Hier legen wir den Grundstein für unser Zone Recovery System für Envelopes Trendhandel in MQL5, indem wir wesentliche Komponenten und nutzerkonfigurierbare Einstellungen einrichten. Wir beginnen mit der Einbindung der Bibliothek „<Trade/Trade.mqh>“, die die Klasse „CTrade“ für die Ausführung von Handelsoperationen wie das Öffnen und Schließen von Positionen enthält. Diese Einbeziehung ist von entscheidender Bedeutung, da sie unseren Expert Advisor (EA) mit den erforderlichen Instrumenten ausstattet, um nahtlos mit dem Markt zu interagieren, insbesondere bei der Einleitung von Aufträgen. Siehe unten, wie man die Datei öffnet.
Wir definieren dann die Enumeration „TradingLotSizeOptions“ mit zwei Werten: „FIXED_LOTSIZE“ und „UNFIXED_LOTSIZE“. Dadurch können wir den Nutzern die Wahl zwischen einer konstanten Losgröße und einer dynamisch an die Risikoparameter angepassten Losgröße bieten, was die Flexibilität bei der Handelsgröße für unterschiedliche Handelsstile erhöht. Als Nächstes konfigurieren wir die Eingabeparameter in der Gruppe „EA GENERAL SETTINGS“, die der Nutzer in der MetaTrader 5-Plattform anpassen kann.
Die Eingabe „lotOption“, die standardmäßig auf „UNFIXED_LOTSIZE“ eingestellt ist, legt fest, ob für den Handel eine feste oder risikobasierte Losgröße verwendet wird. Die „initialLotSize“ (0.01) legt die Lotgröße für feste Handelsgeschäfte fest, während „riskPercentage“ (1.0%) und „riskPoints“ (300) den Prozentsatz des Kontostandes und den Stop-Loss-Abstand für die dynamische Lot-Größe definieren. Diese Einstellungen steuern, wie viel Risiko wir pro Handel eingehen, um sicherzustellen, dass der EA mit der Risikotoleranz des Nutzers übereinstimmt.
Wir vergeben eine eindeutige „magicNumber“ (123456789), um die Handelsgeschäfte unserer EAs zu identifizieren, damit wir sie von anderen Handelsgeschäfte auf demselben Konto unterscheiden können. Die Eingaben „maxOrders“ (1) und „restrictMaxOrders“ (true) begrenzen die Anzahl der Anfangspositionen und verhindern, dass der EA zu viele Handelsgeschäfte auf einmal eröffnet. Schließlich legen „zoneTargetPoints“ (600) und „zoneSizePoints“ (300) das Gewinnziel und die Größe der Erholungszone in Punkten fest und definieren damit die Grenzen für unsere Zonenerholungsstrategie. Nach dem Kompilieren erhalten wir die folgende Ausgabe.
Nachdem die Eingänge geladen sind, können wir nun mit der Deklaration der Kernlogik für das gesamte System beginnen. Wir beginnen mit der Deklaration einiger Strukturen und Klassen, die wir verwenden werden, da wir einen objektorientierten Programmieransatz (OOP) anwenden wollen.
class MarketZoneTrader { private: //--- Trade State Definition enum TradeState { INACTIVE, RUNNING, TERMINATING }; //--- Define trade lifecycle states //--- Data Structures struct TradeMetrics { bool operationSuccess; //--- Track operation success double totalVolume; //--- Sum closed trade volumes double netProfitLoss; //--- Accumulate profit/loss }; struct ZoneBoundaries { double zoneHigh; //--- Upper recovery zone boundary double zoneLow; //--- Lower recovery zone boundary double zoneTargetHigh; //--- Upper profit target double zoneTargetLow; //--- Lower profit target }; struct TradeConfig { string marketSymbol; //--- Trading symbol double openPrice; //--- Position entry price double initialVolume; //--- Initial trade volume long tradeIdentifier; //--- Magic number string tradeLabel; //--- Trade comment ulong activeTickets[]; //--- Active position tickets ENUM_ORDER_TYPE direction; //--- Trade direction double zoneProfitSpan; //--- Profit target range double zoneRecoverySpan; //--- Recovery zone range double accumulatedBuyVolume; //--- Total buy volume double accumulatedSellVolume; //--- Total sell volume TradeState currentState; //--- Current trade state }; struct LossTracker { double tradeLossTracker; //--- Track cumulative profit/loss }; };
Hier definieren wir die Kernstruktur unseres Systems für den Envelopes-Trendhandel in MQL5, indem wir die Klasse „MarketZoneTrader“ implementieren, wobei wir uns auf den privaten Teil mit den Handelsstatus-Definitionen und Datenstrukturen konzentrieren. Diese Logik hilft bei der Organisation der kritischen Komponenten, die für die Verwaltung von Handelsgeschäften, die Verfolgung von Erholungszonen und die Leistungsüberwachung erforderlich sind. Wir beginnen mit der Definition der Klasse „MarketZoneTrader“, die als Rückgrat unseres Expert Advisors (EA) dient und die Logik für unsere Handelsstrategie kapselt.
In seinem privaten Bereich führen wir die Enumeration „TradeState“ mit drei Werten ein: „INACTIVE“, „RUNNING“, und „TERMINATING“. Anhand dieser Zustände können wir den Lebenszyklus unserer Handelsoperationen verfolgen und so sicherstellen, dass wir wissen, ob der EA im Leerlauf ist, aktiv Handelsgeschäfte verwaltet oder Positionen schließt. Dies ist entscheidend, um die Kontrolle über den Handelsprozess zu behalten, da es uns hilft, Aktionen wie die Eröffnung von Wiederherstellungsgeschäften oder die Schließung von Positionen zu koordinieren.
Als Nächstes erstellen wir die Struktur „TradeMetrics“, um wichtige Leistungsdaten für unsere Handelsgeschäfte zu speichern. Sie umfasst „operationSuccess“, um zu verfolgen, ob Handelsaktionen (wie das Schließen von Positionen) erfolgreich waren, „totalVolume“, um die Volumina der geschlossenen Handelsgeschäfte zu summieren, und „netProfitLoss“, um den Gewinn oder Verlust aus diesen Handelsgeschäften zu kumulieren. Diese Struktur hilft uns bei der Bewertung der Ergebnisse unserer Handelsaktionen und liefert ein klares Bild der Leistung während der Einziehung oder Schließung.
Anschließend definieren wir die Struktur „ZoneBoundaries“, die die Preisniveaus für unsere Zonenerholungsstrategie enthält. Die Variablen „zoneHigh“ und „zoneLow“ markieren die obere und untere Grenze der Erholungszone, in der wir Gegengeschäfte platzieren, um Verluste zu mindern. Die „zoneTargetHigh“ und „zoneTargetLow“ legen die Gewinnziele oberhalb und unterhalb der Zone fest und bestimmen, wann wir den Handel gewinnbringend beenden. Diese Grenzen sind für unsere Strategie von entscheidender Bedeutung, da sie den Zeitpunkt bestimmen, an dem wir Einziehungsmaßnahmen einleiten oder Positionen schließen. Hier sehen Sie, wie sie in der Visualisierung aussehen würden, damit Sie ein klares Bild davon haben, warum wir die Struktur brauchen.
In der Struktur „TradeConfig“ werden die Handelseinstellungen gespeichert. Es enthält „marketSymbol“ für das Währungspaar, „openPrice“ für den Einstiegskurs und „initialVolume“ für die Handelsgröße. Der „tradeIdentifier“ enthält unsere eindeutige magische Nummer, und „tradeLabel“ fügt einen Kommentar zur Identifizierung des Handelsgeschäfts hinzu. Das Array „activeTickets“ erfasst offene Positionstickets, während „direction“ angibt, ob es sich um einen Kauf oder Verkauf handelt. Wir fügen auch „zoneProfitSpan“ und „zoneRecoverySpan“ ein, um die Größe des Gewinnziels und der Erholungszone in Preiseinheiten zu definieren, sowie „accumulatedBuyVolume“ und „accumulatedSellVolume“, um die Gesamtvolumina für jeden Handelstyp zu überwachen. Die Variable „currentState“, die die Enumeration „TradeState“ verwendet, verfolgt den Handelsstatus und bindet alles zusammen.
Schließlich fügen wir die Struktur „LossTracker“ mit einer einzigen Variablen „tradeLossTracker“ hinzu, um den kumulierten Gewinn oder Verlust über die einzelnen Handelsgeschäfte hinweg zu überwachen. Dies hilft uns, die finanziellen Auswirkungen unserer Einziehungsmaßnahmen abzuschätzen, damit wir unsere Strategie anpassen können, falls die Verluste zu groß werden. Wir können dann einige Mitgliedsvariablen definieren, um die anderen, weniger wichtigen, aber notwendigen Handelsinformationen zu speichern.
//--- Member Variables TradeConfig m_tradeConfig; //--- Store trade configuration ZoneBoundaries m_zoneBounds; //--- Store zone boundaries LossTracker m_lossTracker; //--- Track profit/loss string m_lastError; //--- Store error message int m_errorStatus; //--- Store error code CTrade m_tradeExecutor; //--- Manage trade execution 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 TradingLotSizeOptions m_lotOption; //--- Lot size option double m_initialLotSize; //--- Fixed lot size double m_riskPercentage; //--- Risk percentage int m_riskPoints; //--- Risk points int m_maxOrders; //--- Maximum positions bool m_restrictMaxOrders; //--- Position restriction flag double m_zoneTargetPoints; //--- Profit target points double m_zoneSizePoints; //--- Recovery zone points
Im privaten Bereich der Klasse „MarketZoneTrader“ definieren wir wichtige Mitgliedsvariablen zur Verwaltung von Handelseinstellungen, Erholungszonen und Indikatordaten. Wir verwenden „m_tradeConfig“ (Struktur „TradeConfig“), um Handelsdetails wie Symbol und Richtung zu speichern, „m_zoneBounds“ (“ZoneBoundaries“-Struktur) für Erholungszonen und Gewinnzielpreise und „m_lossTracker“ (“LossTracker“-Struktur), um Gewinne oder Verluste zu verfolgen. Für die Fehlerbehandlung protokollieren „m_lastError“ (String) und „m_errorStatus“ (Integer) Probleme, während „m_tradeExecutor“ (Klasse „CTrade“) Handelsoperationen abwickelt.
Indikator-Handles - „m_handleRsi“, „m_handleEnvUpper“, „m_handleEnvLower“ - greifen auf RSI- und Daten von Envelopes zu, wobei die Arrays „m_rsiBuffer“, „m_envUpperBandBuffer“ und „m_envLowerBandBuffer“ ihre Werte speichern. Wir speichern Eingabeeinstellungen in „m_lotOption“ (“TradingLotSizeOptions“), „m_initialLotSize“, „m_riskPercentage“, „m_riskPoints““m_maxOrders“, „m_restrictMaxOrders“, „m_zoneTargetPoints“ und „m_zoneSizePoints“ zur Steuerung der Losgröße, der Positionslimits und der Zonengrößen. Diese Variablen bilden das Rückgrat für die Verwaltung von Handelsgeschäfte und Indikatoren und bereiten uns auf die kommende Handelslogik vor. Anschließend müssen wir einige Hilfsfunktionen definieren, die wir im Programm häufig verwenden werden.
//--- Error Handling void logError(string message, int code) { //--- Error Logging Start m_lastError = message; //--- Store error message m_errorStatus = code; //--- Store error code Print("Error: ", message); //--- Log error to Experts tab //--- Error Logging End } //--- Market Data Access double getMarketVolumeStep() { //--- Volume Step Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_VOLUME_STEP); //--- Retrieve broker's volume step //--- Volume Step Retrieval End } double getMarketAsk() { //--- Ask Price Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_ASK); //--- Retrieve ask price //--- Ask Price Retrieval End } double getMarketBid() { //--- Bid Price Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_BID); //--- Retrieve bid price //--- Bid Price Retrieval End }
Hier fügen wir wichtige Funktionen für die Fehlerbehandlung und den Zugriff auf Marktdaten hinzu. Die Funktion „logError“ speichert „message“ in „m_lastError“, „code“ in „m_errorStatus“ und protokolliert die Meldung über „Print“ in der Registerkarte „Experts“ zur Fehlersuche. Die Funktion „getMarketVolumeStep“ verwendet SymbolInfoDouble mit SYMBOL_VOLUME_STEP, um das Volumeninkrement des Brokers für „m_tradeConfig.marketSymbol“ zu ermitteln und gültige Handelsgrößen zu gewährleisten. Die Funktionen „getMarketAsk“ und „getMarketBid“ rufen Brief- und Geldkurse unter Verwendung von „SymbolInfoDouble“ mit SYMBOL_ASK bzw. „SYMBOL_BID“ ab, um eine genaue Preisfindung für den Handel zu ermöglichen.
Wir können nun die wichtigsten Funktionen für die Ausführung von Handelsgeschäften definieren. Beginnen wir mit denjenigen, die uns bei der Initialisierung, der Speicherung von Handelstickets für die Verfolgung und Überwachung von Vorgängen und der Schließung von Handelsgeschäften helfen, da dies die weniger komplexe Logik ist.
//--- Trade Initialization bool configureTrade(ulong ticket) { //--- Trade Configuration Start if (!PositionSelectByTicket(ticket)) { //--- Select position by ticket logError("Failed to select ticket " + IntegerToString(ticket), INIT_FAILED); //--- Log selection failure return false; //--- Return failure } m_tradeConfig.marketSymbol = PositionGetString(POSITION_SYMBOL); //--- Set symbol m_tradeConfig.tradeLabel = __FILE__; //--- Set trade comment m_tradeConfig.tradeIdentifier = PositionGetInteger(POSITION_MAGIC); //--- Set magic number m_tradeConfig.direction = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); //--- Set direction m_tradeConfig.openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set entry price m_tradeConfig.initialVolume = PositionGetDouble(POSITION_VOLUME); //--- Set initial volume m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number for executor return true; //--- Return success //--- Trade Configuration End } //--- Trade Ticket Management void storeTradeTicket(ulong ticket) { //--- Ticket Storage Start int ticketCount = ArraySize(m_tradeConfig.activeTickets); //--- Get ticket count ArrayResize(m_tradeConfig.activeTickets, ticketCount + 1); //--- Resize ticket array m_tradeConfig.activeTickets[ticketCount] = ticket; //--- Store ticket //--- Ticket Storage End } //--- Trade Execution ulong openMarketTrade(ENUM_ORDER_TYPE tradeDirection, double tradeVolume, double price) { //--- Trade Opening Start ulong ticket = 0; //--- Initialize ticket if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, tradeDirection, tradeVolume, price, 0, 0, m_tradeConfig.tradeLabel)) { //--- Open position ticket = m_tradeExecutor.ResultOrder(); //--- Get ticket } else { Print("Failed to open trade: Direction=", EnumToString(tradeDirection), ", Volume=", tradeVolume); //--- Log failure } return ticket; //--- Return ticket //--- Trade Opening End } //--- Trade Closure void closeActiveTrades(TradeMetrics &metrics) { //--- Trade Closure Start for (int i = ArraySize(m_tradeConfig.activeTickets) - 1; i >= 0; i--) { //--- Iterate tickets in reverse if (m_tradeConfig.activeTickets[i] > 0) { //--- Check valid ticket if (m_tradeExecutor.PositionClose(m_tradeConfig.activeTickets[i])) { //--- Close position m_tradeConfig.activeTickets[i] = 0; //--- Clear ticket metrics.totalVolume += m_tradeExecutor.ResultVolume(); //--- Accumulate volume if ((ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY) { //--- Check buy position metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (m_tradeExecutor.ResultPrice() - PositionGetDouble(POSITION_PRICE_OPEN)); //--- Calculate buy profit } else { //--- Handle sell position metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (PositionGetDouble(POSITION_PRICE_OPEN) - m_tradeExecutor.ResultPrice()); //--- Calculate sell profit } } else { metrics.operationSuccess = false; //--- Mark failure Print("Failed to close ticket: ", m_tradeConfig.activeTickets[i]); //--- Log failure } } } //--- Trade Closure End } //--- Bar Detection bool isNewBar() { //--- New Bar Detection Start static datetime previousTime = 0; //--- Store previous bar time datetime currentTime = iTime(m_tradeConfig.marketSymbol, Period(), 0); //--- Get current bar time bool result = (currentTime != previousTime); //--- Check for new bar previousTime = currentTime; //--- Update previous time return result; //--- Return new bar status //--- New Bar Detection End }
Hier tauchen wir in die Kernlogik unseres Programms ein und entwickeln Funktionen zum Einrichten von Handelsgeschäften, zum Verfolgen von Positionen, zum Ausführen von Aufträgen, zum Schließen von Handelsgeschäften und zum Festlegen des Zeitpunkts unserer Aktionen. Wir beginnen mit der Erstellung der Funktion „configureTrade“, um einen Handel für ein bestimmtes „Ticket“ vorzubereiten. Zunächst versuchen wir, die Position mit der Funktion PositionSelectByTicket auszuwählen. Wenn es nicht funktioniert, protokollieren wir das Problem mit „logError“ und beenden es mit false. Wenn dies gelingt, füllen wir „m_tradeConfig“ mit Details: Wir holen uns „marketSymbol“ mit der Funktion PositionGetString, setzen „tradeLabel“ auf __FILE__ und holen uns „tradeIdentifier“ und „direction“ von PositionGetInteger, wobei letzteres auf ENUM_ORDER_TYPE gecastet wird. Dann setzen wir „openPrice“ und „initialVolume“ mit PositionGetDouble und markieren „m_tradeExecutor“ mit „SetExpertMagicNumber“, um sicherzustellen, dass unser Handel einsatzbereit ist.
Als Nächstes erstellen wir die Funktion „storeTradeTicket“, um unsere offenen Positionen zu organisieren. Wir überprüfen die Größe von „m_tradeConfig.activeTickets“ mit der Funktion ArraySize, dehnen das Array mit der Funktion ArrayResize um einen Slot und setzen das neue „Ticket“ ein, sodass wir immer wissen, welche Handelsgeschäfte aktiv sind. Im nächsten Schritt erstellen wir die Funktion „openMarketTrade“, um den Handel auf dem Markt zu platzieren. Wir rufen „m_tradeExecutor.PositionOpen“ mit den Angaben „tradeDirection“, „tradeVolume“, „price“ und „m_tradeConfig“ auf. Wenn es klappt, weisen wir das „Ticket“ mit „ResultOrder“ zu; wenn nicht, protokollieren wir den Fehler mit „Print“ und halten unsere Handelsausführung fest.
Anschließend werden die Positionen mit der Funktion „closeActiveTrades“ geschlossen. Wir gehen in einer Schleife rückwärts durch „m_tradeConfig.activeTickets“ und schließen jedes gültige Ticket mit „m_tradeExecutor.PositionClose“. Wenn eine Schließung funktioniert, löschen wir das Ticket, fügen „ResultVolume“ zu „metrics.totalVolume“ hinzu und berechnen „metrics.netProfitLoss“ mithilfe der Funktionen „PositionGetInteger“ und „PositionGetDouble“, um die Handelsrichtung zu überprüfen. Wenn etwas fehlschlägt, kennzeichnen wir „metrics.operationSuccess“ als „false“ und protokollieren es mit Print, um sicherzustellen, dass wir jedes Ergebnis verfolgen.
Schließlich fügen wir die Funktion „isNewBar“ hinzu, um den Handel einmal pro Balken zu unterstützen und den Ressourcenverbrauch zu reduzieren. Wir holen die aktuelle Zeit des Balkens für „m_tradeConfig.marketSymbol“ mit der Funktion iTime, vergleichen sie mit „previousTime“ und aktualisieren „previousTime“, wenn sie sich unterscheidet, damit wir wissen, wann ein neuer Balken eintrifft, um auf Handelssignale zu prüfen. Schließlich benötigen wir eine Funktion zur Berechnung des Handelsvolumens und eine Funktion zur Eröffnung der Handelsgeschäfte.
//--- Lot Size Calculation double calculateLotSize(double riskPercent, int riskPips) { //--- Lot Size Calculation Start double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * riskPercent / 100; //--- Calculate risk amount double tickSize = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_SIZE); //--- Get tick size double tickValue = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_VALUE); //--- Get tick value if (tickSize == 0 || tickValue == 0) { //--- Validate tick data Print("Invalid tick size or value"); //--- Log invalid data return -1; //--- Return invalid lot } double lotValue = (riskPips * _Point) / tickSize * tickValue; //--- Calculate lot value if (lotValue == 0) { //--- Validate lot value Print("Invalid lot value"); //--- Log invalid lot return -1; //--- Return invalid lot } return NormalizeDouble(riskMoney / lotValue, 2); //--- Return normalized lot size //--- Lot Size Calculation End } //--- Order Execution int openOrder(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit) { //--- Order Opening Start int ticket; //--- Initialize ticket double openPrice; //--- Initialize open price if (orderType == ORDER_TYPE_BUY) { //--- Check buy order openPrice = NormalizeDouble(getMarketAsk(), Digits()); //--- Set buy price } else if (orderType == ORDER_TYPE_SELL) { //--- Check sell order openPrice = NormalizeDouble(getMarketBid(), Digits()); //--- Set sell price } else { Print("Invalid order type"); //--- Log invalid type return -1; //--- Return invalid ticket } double lotSize = 0; //--- Initialize lot size if (m_lotOption == FIXED_LOTSIZE) { //--- Check fixed lot lotSize = m_initialLotSize; //--- Use fixed lot size } else if (m_lotOption == UNFIXED_LOTSIZE) { //--- Check dynamic lot lotSize = calculateLotSize(m_riskPercentage, m_riskPoints); //--- Calculate risk-based lot } if (lotSize <= 0) { //--- Validate lot size Print("Invalid lot size: ", lotSize); //--- Log invalid lot return -1; //--- Return invalid ticket } if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, __FILE__)) { //--- Open position ticket = (int)m_tradeExecutor.ResultOrder(); //--- Get ticket Print("New trade opened: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log success } else { ticket = -1; //--- Set invalid ticket Print("Failed to open order: Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log failure } return ticket; //--- Return ticket //--- Order Opening End }
Wir beginnen mit der Funktion „calculateLotSize“, um die Handelsgröße auf der Grundlage der Risikoparameter zu bestimmen. Zunächst berechnen wir das „riskMoney“, indem wir einen Prozentsatz des Kontostands mit Hilfe von AccountInfoDouble mit ACCOUNT_BALANCE und „riskPercent“ ermitteln. Dann holen wir „tickSize“ und „tickValue“ für „m_tradeConfig.marketSymbol“ mit SymbolInfoDouble mit „SYMBOL_TRADE_TICK_SIZE“ und „SYMBOL_TRADE_TICK_VALUE“. Wenn einer der beiden Werte Null ist, wird ein Fehler mit „Print“ protokolliert und -1 zurückgegeben, um ungültige Berechnungen zu vermeiden. Wir berechnen den „lotValue“ mit „riskPips“, _Point, „tickSize“ und „tickValue“, und wenn er Null ist, protokollieren wir einen weiteren Fehler und geben -1 zurück. Schließlich geben wir die Losgröße mit NormalizeDouble auf zwei Dezimalstellen zurück, um sicherzustellen, dass sie den Anforderungen des Brokers entspricht.
Als Nächstes erstellen wir die Funktion „openOrder“ zur Platzierung von Handelsgeschäften. Wir initialisieren „ticket“ und „openPrice“, dann prüfen wir „orderType“. Für ORDER_TYPE_BUY setzen wir „openPrice“ mit „getMarketAsk“ und „NormalizeDouble“ mit Digits; für „ORDER_TYPE_SELL“ verwenden wir „getMarketBid“. Wenn „orderType“ ungültig ist, wird dies mit „Print“ protokolliert und -1 zurückgegeben. 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. Dann öffnen wir die Position mit „m_tradeExecutor.PositionOpen“ unter Verwendung von „m_tradeConfig.marketSymbol“, „orderType“, „lotSize“, „openPrice“ und „FILE“ als Kommentar. Bei Erfolg setzen wir „ticket“ auf „ResultOrder“ und protokollieren es mit „Print“; bei Misserfolg setzen wir „ticket“ auf -1 und protokollieren den Fehler. Schließlich geben wir den Wert des Tickets zurück.
Danach müssen wir die Systemwerte initialisieren. Wir können dies über eine eigene Funktion erreichen, aber um alles einfach zu halten, werden wir den Konstruktor verwenden. Es ist ratsam, den Konstruktor in einem Public Access Modifier zu definieren, damit er überall im Programm verfügbar ist. Definieren wir auch hier den Destruktor.
public: //--- Constructor MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, int maxOrds, bool restrictOrds, double targetPts, double sizePts) { //--- Constructor Start m_tradeConfig.currentState = INACTIVE; //--- Set initial state ArrayResize(m_tradeConfig.activeTickets, 0); //--- Initialize ticket array m_tradeConfig.zoneProfitSpan = targetPts * _Point; //--- Set profit target m_tradeConfig.zoneRecoverySpan = sizePts * _Point; //--- Set recovery zone m_lossTracker.tradeLossTracker = 0.0; //--- Initialize loss tracker m_lotOption = lotOpt; //--- Set lot size option m_initialLotSize = initLot; //--- Set initial lot m_riskPercentage = riskPct; //--- Set risk percentage m_riskPoints = riskPts; //--- Set risk points m_maxOrders = maxOrds; //--- Set max positions m_restrictMaxOrders = restrictOrds; //--- Set restriction flag m_zoneTargetPoints = targetPts; //--- Set target points m_zoneSizePoints = sizePts; //--- Set zone points m_tradeConfig.marketSymbol = _Symbol; //--- Set symbol m_tradeConfig.tradeIdentifier = magicNumber; //--- Set magic number //--- Constructor End } //--- Destructor ~MarketZoneTrader() { //--- Destructor Start cleanup(); //--- Release resources //--- Destructor End }
Wir fahren fort, indem wir den Konstruktor und den Destruktor für die Klasse „MarketZoneTrader“ in ihrem öffentlichen Abschnitt definieren. Wir beginnen mit dem Konstruktor „MarketZoneTrader“, der die Parameter „lotOpt“, „initLot“, „riskPct“, „riskPts“, „maxOrds“, „restrictOrds“, „targetPts“ und „sizePts“ enthält. Wir initialisieren die Handelsumgebung, indem wir „m_tradeConfig.currentState“ auf „INACTIVE“ setzen, um anzuzeigen, dass keine aktiven Handelsgeschäfte vorliegen. Als Nächstes wird das Array „m_tradeConfig.activeTickets“ mit ArrayResize auf Null gesetzt, um es für neue Tickets vorzubereiten. Wir berechnen „m_tradeConfig.zoneProfitSpan“ und „m_tradeConfig.zoneRecoverySpan“, indem wir „targetPts“ und „sizePts“ mit „_Point“ multiplizieren und die Größe des Gewinnziels und der Erholungszone in Preiseinheiten angeben. Wir setzen „m_lossTracker.tradeLossTracker“ auf 0,0 zurück, um mit der Verfolgung von Gewinnen oder Verlusten von Grund auf zu beginnen.
Dann weisen wir die Eingabeparameter den Mitgliedsvariablen zu: „m_lotOption“ auf „lotOpt“, „m_initialLotSize“ auf „initLot“, „m_riskPercentage“ auf „riskPct“, „m_riskPoints“ auf „riskPts“, „m_maxOrders“ zu „maxOrds“, „m_restrictMaxOrders“ zu „restrictOrds“, „m_zoneTargetPoints“ zu „targetPts“, und „m_zoneSizePoints“ zu „sizePts“. Wir setzen „m_tradeConfig.marketSymbol“ auf _Symbol, um das Symbol des aktuellen Charts zu handeln, und weisen „m_tradeConfig.tradeIdentifier“ auf „magicNumber“ für eine eindeutige Handelsidentifikation zu. Dieses Setup stellt sicher, dass unser EA die Nutzereinstellungen widerspiegelt und bereit für den Handel ist.
Als Nächstes definieren wir den Destruktor „~MarketZoneTrader“, um die Ressourcen aufzuräumen. Wir rufen die Funktion „cleanup“ auf, um alle zugewiesenen Ressourcen, wie z. B. Indikator-Handles, freizugeben und so sicherzustellen, dass der EA sauber und ohne Speicherlecks beendet wird. Es ist gut zu wissen, dass der Konstruktor und der Destruktor den gleichen Wortlaut des Klassennamens haben, nur dass der Destruktor ein Tilde (~) vor sich hat. Genau das. Hier ist die Funktion, um die Klasse zu zerstören, wenn sie nicht benötigt wird.
//--- Cleanup void cleanup() { //--- Cleanup Start IndicatorRelease(m_handleRsi); //--- Release RSI handle ArrayFree(m_rsiBuffer); //--- Free RSI buffer IndicatorRelease(m_handleEnvUpper); //--- Release upper Envelopes handle ArrayFree(m_envUpperBandBuffer); //--- Free upper Envelopes buffer IndicatorRelease(m_handleEnvLower); //--- Release lower Envelopes handle ArrayFree(m_envLowerBandBuffer); //--- Free lower Envelopes buffer //--- Cleanup End }
Wir verwenden einfach die Funktion IndicatorRelease, um die Indikator-Handles freizugeben, und die Funktion ArrayFree, um die Speicher-Arrays freizugeben. Da wir die Indikatoren berührt haben, sollten wir eine Initialisierungsfunktion definieren, die wir beim Start des Programms aufrufen.
//--- Getters TradeState getCurrentState() { //--- Get Current State Start return m_tradeConfig.currentState; //--- Return trade state //--- Get Current State End } double getZoneTargetHigh() { //--- Get Target High Start return m_zoneBounds.zoneTargetHigh; //--- Return profit target high //--- Get Target High End } double getZoneTargetLow() { //--- Get Target Low Start return m_zoneBounds.zoneTargetLow; //--- Return profit target low //--- Get Target Low End } double getZoneHigh() { //--- Get Zone High Start return m_zoneBounds.zoneHigh; //--- Return recovery zone high //--- Get Zone High End } double getZoneLow() { //--- Get Zone Low Start return m_zoneBounds.zoneLow; //--- Return recovery zone low //--- Get Zone Low End } //--- 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 beginnen wir mit der Erstellung einfacher Getter-Funktionen für den Zugriff auf wichtige Handelsdaten. Die Funktion „getCurrentState“ gibt „m_tradeConfig.currentState“ zurück, sodass wir überprüfen können, ob sich das System im Zustand „INACTIVE“, „RUNNING“ oder „TERMINATING“ befindet. Als Nächstes erstellen wir „getZoneTargetHigh“ und „getZoneTargetLow“, um „m_zoneBounds.zoneTargetHigh“ und „m_zoneBounds.zoneTargetLow“ abzurufen, die die Gewinnzielpreise für unsere Handelsgeschäfte liefern. Dann fügen wir „getZoneHigh“ und „getZoneLow“ hinzu, um „m_zoneBounds.zoneHigh“ und „m_zoneBounds.zoneLow“ abzurufen, wodurch wir die Grenzen der Erholungszone erhalten.
Weiter geht es mit der Funktion „Initialisieren“, mit der wir unseren Expert Advisor (EA) einrichten. Zunächst weisen wir „m_tradeConfig.tradeIdentifier“ dem „m_tradeExecutor“ zu und verwenden „SetExpertMagicNumber“, um unsere Handelsgeschäfte zu kennzeichnen. Dann prüfen wir mit „PositionsTotal“, ob es Positionen gibt, und gehen sie in einer Schleife durch, wobei wir jedes „Ticket“ mit „PositionGetTicket“ abrufen. Wenn PositionSelectByTicket erfolgreich ist und die Position mit „m_tradeConfig.marketSymbol“ und „m_tradeConfig.tradeIdentifier“ (über PositionGetString und „PositionGetInteger“) übereinstimmt, rufen wir „activateTrade“ auf, um sie zu verwalten, wobei Erfolg oder Misserfolg mit „Print“ protokolliert wird.
Als Nächstes richten wir unsere Indikatoren ein. Wir erstellen den RSI-Handle mit der Funktion iRSI für „m_tradeConfig.marketSymbol“ unter Verwendung einer 8-Perioden-Einstellung für den aktuellen Zeitrahmen und „PRICE_CLOSE“. Wenn „m_handleRsi“ INVALID_HANDLE ist, protokollieren wir den Fehler mit „Print“ und geben „INIT_FAILED“ zurück. Anschließend werden die Envelopes-Indikatoren initialisiert: „m_handleEnvUpper“ mit der Funktion „iEnvelopes“ unter Verwendung eines einfachen gleitenden Durchschnitts mit 150 Perioden und einer Abweichung von 0,1 sowie „PRICE_CLOSE“, und „m_handleEnvLower“ mit einer Periodenlänge von 95 und einer Abweichung von 1,4. Wenn eines der Handles „INVALID_HANDLE“ ist, wird der Fehler protokolliert und „INIT_FAILED“ zurückgegeben. Schließlich konfigurieren wir „m_rsiBuffer“, „m_envUpperBandBuffer“ und „m_envLowerBandBuffer“ als Zeitreihen-Arrays mit ArraySetAsSeries, protokollieren den Erfolg mit „Print“ und geben INIT_SUCCEEDED zurück. Wir können diese Funktion nun in OnInit aufrufen, aber zunächst benötigen wir eine Instanz der Klasse.
//--- Global Instance MarketZoneTrader *trader = NULL; //--- Declare trader instance
Hier richten wir die globale Instanz unseres Systems ein, indem wir einen Zeiger auf die Klasse „MarketZoneTrader“ deklarieren. Wir erstellen die Variable „trader“ als Zeiger auf „MarketZoneTrader“ und initialisieren sie mit „NULL“. Dieser Schritt stellt sicher, dass wir eine einzige, global zugängliche Instanz unseres Handelssystems haben, die wir im gesamten Expert Advisor (EA) verwenden können, um alle Handelsvorgänge zu verwalten, wie z. B. die Initialisierung von Handelsgeschäften, die Ausführung von Aufträgen und die Handhabung von Erholungszonen. Indem wir mit „NULL“ beginnen, bereiten wir den „Trader“ darauf vor, später ordnungsgemäß instanziiert zu werden, und verhindern so jeden vorzeitigen Zugriff, bevor der EA vollständig eingerichtet ist. Wir können nun die Funktion aufrufen.
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 }
In OnInit erstellen wir zunächst eine neue Instanz der Klasse „MarketZoneTrader“ und weisen ihr den globalen Zeiger „trader“ zu. Wir übergeben die nutzerdefinierten Eingabeparameter – „lotOption“, „initialLotSize“, „riskPercentage“, „riskPoints“, „maxOrders“, „restrictMaxOrders“, „zoneTargetPoints“ und „zoneSizePoints“ – an den Konstruktor, um das Handelssystem mit den gewünschten Einstellungen zu konfigurieren. Dann rufen wir die Funktion „initialize“ auf „trader“ auf, um den EA einzurichten, einschließlich der Handelskennzeichnung, der Überprüfung bestehender Positionen und der Initialisierung von Indikatoren, und geben das Ergebnis zurück, um zu signalisieren, ob die Einrichtung erfolgreich war. Diese Funktion stellt sicher, dass unser EA vollständig vorbereitet ist, um den Handel mit den angegebenen Konfigurationen zu starten. Nach dem Kompilieren erhalten wir die folgende Ausgabe.
Aus dem Bild ist ersichtlich, dass das Programm erfolgreich initialisiert wurde. Es gibt jedoch ein Problem, wenn wir versuchen, das Programm zu entfernen. Siehe unten.
Aus dem Bild ist ersichtlich, dass es ungelöschte Objekte gibt, die zu einem Speicherleck führen. Um dieses Problem zu lösen, müssen wir die Objektbereinigung durchführen. Um dies zu erreichen, verwenden wir die folgende Logik.
void OnDeinit(const int reason) { //--- EA Deinitialization Start if (trader != NULL) { //--- Check trader existence delete trader; //--- Delete trader trader = NULL; //--- Clear pointer Print("EA deinitialized"); //--- Log deinitialization } //--- EA Deinitialization End }
Um die Bereinigung durchzuführen, wird in OnDeinit zunächst geprüft, ob der Zeiger „trader“ nicht „NULL“ ist, um sicherzustellen, dass die „MarketZoneTrader“-Instanz existiert. Wenn dies der Fall ist, verwenden wir den Operator delete, um den für „trader“ zugewiesenen Speicher freizugeben und so Speicherlecks zu vermeiden. Dann setzen wir „trader“ auf „NULL“, um einen versehentlichen Zugriff auf den freigegebenen Speicher zu vermeiden. Schließlich protokollieren wir eine Meldung mit der Funktion „Print“, um zu bestätigen, dass der EA deinitialisiert wurde. Diese Funktion stellt sicher, dass unser EA sauber beendet wird und die Ressourcen ordnungsgemäß freigibt. Wir können nun mit der Definition der Hauptlogik fortfahren, um Signalauswertungen und die Verwaltung der eröffneten Handelsgeschäfte zu handhaben. Dafür brauchen wir Hilfsfunktionen.
//--- Position Management bool activateTrade(ulong ticket) { //--- Position Activation Start m_tradeConfig.currentState = INACTIVE; //--- Set state to inactive ArrayResize(m_tradeConfig.activeTickets, 0); //--- Clear tickets m_lossTracker.tradeLossTracker = 0.0; //--- Reset loss tracker if (!configureTrade(ticket)) { //--- Configure trade return false; //--- Return failure } storeTradeTicket(ticket); //--- Store ticket if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position m_zoneBounds.zoneHigh = m_tradeConfig.openPrice; //--- Set zone high m_zoneBounds.zoneLow = m_zoneBounds.zoneHigh - m_tradeConfig.zoneRecoverySpan; //--- Set zone low m_tradeConfig.accumulatedBuyVolume = m_tradeConfig.initialVolume; //--- Set buy volume m_tradeConfig.accumulatedSellVolume = 0.0; //--- Reset sell volume } else { //--- Handle sell position m_zoneBounds.zoneLow = m_tradeConfig.openPrice; //--- Set zone low m_zoneBounds.zoneHigh = m_zoneBounds.zoneLow + m_tradeConfig.zoneRecoverySpan; //--- Set zone high m_tradeConfig.accumulatedSellVolume = m_tradeConfig.initialVolume; //--- Set sell volume m_tradeConfig.accumulatedBuyVolume = 0.0; //--- Reset buy volume } m_zoneBounds.zoneTargetHigh = m_zoneBounds.zoneHigh + m_tradeConfig.zoneProfitSpan; //--- Set target high m_zoneBounds.zoneTargetLow = m_zoneBounds.zoneLow - m_tradeConfig.zoneProfitSpan; //--- Set target low m_tradeConfig.currentState = RUNNING; //--- Set state to running return true; //--- Return success //--- Position Activation End } //--- Tick Processing void processTick() { //--- Tick Processing Start double askPrice = NormalizeDouble(getMarketAsk(), Digits()); //--- Get ask price double bidPrice = NormalizeDouble(getMarketBid(), Digits()); //--- Get bid price if (!isNewBar()) return; //--- Exit if not new bar if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) { //--- Load RSI data Print("Error loading RSI data. Reverting."); //--- Log RSI failure return; //--- Exit } if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) { //--- Load upper Envelopes Print("Error loading upper envelopes data. Reverting."); //--- Log failure return; //--- Exit } if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) { //--- Load lower Envelopes Print("Error loading lower envelopes data. Reverting."); //--- Log failure return; //--- Exit } int ticket = 0; //--- Initialize ticket const int rsiOverbought = 70; //--- Set RSI overbought level const int rsiOversold = 30; //--- Set RSI oversold level if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) { //--- Check buy signal if (askPrice > m_envUpperBandBuffer[0]) { //--- Confirm price above upper Envelopes if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit ticket = openOrder(ORDER_TYPE_BUY, 0, 0); //--- Open buy order } } } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) { //--- Check sell signal if (bidPrice < m_envLowerBandBuffer[0]) { //--- Confirm price below lower Envelopes if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit ticket = openOrder(ORDER_TYPE_SELL, 0, 0); //--- Open sell order } } } if (ticket > 0) { //--- Check if trade opened if (activateTrade(ticket)) { //--- Activate position Print("New position activated: Ticket=", ticket); //--- Log activation } else { Print("Failed to activate new position: Ticket=", ticket); //--- Log failure } } //--- Tick Processing End }
Hier entwickeln wir unser Programm weiter, indem wir die Funktionen „activateTrade“ und „processTick“ innerhalb der Klasse „MarketZoneTrader“ implementieren, um Positionen zu verwalten und Marktticks zu verarbeiten. Wir beginnen mit der Funktion „activateTrade“, um einen Handel für ein bestimmtes „Ticket“ zu aktivieren. Zunächst setzen wir „m_tradeConfig.currentState“ auf „INACTIVE“ und löschen „m_tradeConfig.activeTickets“ mit der Funktion ArrayResize, um die Ticketliste zurückzusetzen. Wir setzen „m_lossTracker.tradeLossTracker“ auf 0,0 zurück und rufen dann „configureTrade“ mit „ticket“ auf. Wenn sie fehlschlägt, geben wir false zurück. Als Nächstes speichern wir das „Ticket“ mit „storeTradeTicket“. Bei einem Kaufgeschäft („m_tradeConfig.direction“ als ORDER_TYPE_BUY) setzen wir „m_zoneBounds.zoneHigh“ auf „m_tradeConfig.openPrice“, berechnen „m_zoneBounds.zoneLow“ durch Subtraktion von „m_tradeConfig.zoneRecoverySpan“, und aktualisieren Sie „m_tradeConfig.accumulatedBuyVolume“ auf „m_tradeConfig.initialVolume“, während Sie „m_tradeConfig.accumulatedSellVolume“ zurücksetzen.
Bei einem Verkaufsgeschäft setzen wir „m_zoneBounds.zoneLow“ auf „m_tradeConfig.openPrice“, fügen „m_tradeConfig.zoneRecoverySpan“ für „m_zoneBounds.zoneHigh“ hinzu und passen die Volumina entsprechend an. Dann setzen wir „m_zoneBounds.zoneTargetHigh“ und „m_zoneBounds.zoneTargetLow“ mit „m_tradeConfig.zoneProfitSpan“, ändern „m_tradeConfig.currentState“ auf „RUNNING“ und geben true zurück.
Als Nächstes erstellen wir die Funktion „processTick“, um Marktticks zu verarbeiten. Wir holen uns „askPrice“ und „bidPrice“ mit „getMarketAsk“ und „getMarketBid“, normalisiert mit NormalizeDouble und „Digits“. Wenn „isNewBar“ den Wert „false“ zurückgibt, wird das Programm beendet, um Ressourcen zu sparen. Wir laden Indikator-Daten mit CopyBuffer für „m_handleRsi“ in „m_rsiBuffer“, „m_handleEnvUpper“ in „m_envUpperBandBuffer“, und „m_handleEnvLower“ in „m_envLowerBandBuffer“, wobei Fehler mit „Print“ protokolliert und bei Fehlern beendet werden. Für Handelssignale setzen wir „rsiOverbought“ auf 70 und „rsiOversold“ auf 30.
Wenn „m_rsiBuffer“ eine überverkaufte Situation anzeigt und „askPrice“ „m_envUpperBandBuffer“ übersteigt, eröffnen wir eine Kauforder mit „openOrder“, wenn „m_restrictMaxOrders“ falsch ist oder PositionsTotal unter „m_maxOrders“ liegt. Bei einem überkauften Zustand, bei dem „bidPrice“ unter „m_envLowerBandBuffer“ liegt, eröffnen wir einen Verkaufsauftrag. Wenn ein gültiges „Ticket“ zurückgegeben wird, rufen wir „activateTrade“ auf und protokollieren das Ergebnis im Journal. Wir können nun die Funktion in OnTick ausführen, um die Signalauswertung und Positionsauslösung zu verarbeiten.
void OnTick() { //--- Tick Handling Start if (trader != NULL) { //--- Check trader existence trader.processTick(); //--- Process tick } //--- Tick Handling End }
In „OnTick“ überprüfen wir zunächst, ob der Zeiger „trader“, unsere Instanz der „MarketZoneTrader“-Klasse, nicht „NULL“ ist, um sicherzustellen, dass das Handelssystem initialisiert ist. Falls vorhanden, rufen wir die Funktion „processTick“ auf „trader“ auf, um jeden Markttick zu verarbeiten, Positionen zu bewerten, Indikatorsignale zu überprüfen und bei Bedarf Handelsgeschäfte auszuführen. Nach der Kompilierung erhalten wir folgendes Ergebnis.
Anhand des Bildes können wir erkennen, dass wir ein Signal identifiziert, bewertet und eine Kaufposition initiiert haben. Jetzt geht es darum, die offenen Positionen zu verwalten. Wir werden das aus Gründen der Modularität in Funktionen behandeln.
//--- Market Tick Evaluation void evaluateMarketTick() { //--- Tick Evaluation Start if (m_tradeConfig.currentState == INACTIVE) return; //--- Exit if inactive if (m_tradeConfig.currentState == TERMINATING) { //--- Check terminating state finalizePosition(); //--- Finalize position return; //--- Exit } }
Hier implementieren wir die Funktion „evaluateMarketTick“ innerhalb der Klasse „MarketZoneTrader“, um die Marktbedingungen für aktive Handelsgeschäfte zu bewerten. Wir beginnen mit der Überprüfung des „m_tradeConfig.currentState“, um festzustellen, ob er „INACTIVE“ ist. Wenn dies der Fall ist, wird die Transaktion sofort beendet, um unnötige Verarbeitung zu vermeiden, wenn keine Handelsgeschäfte aktiv sind. Als Nächstes wird geprüft, ob „m_tradeConfig.currentState“ „TERMINATING“ ist. Wenn dies der Fall ist, rufen wir die Funktion „finalizePosition“ auf, um alle offenen Positionen zu schließen und den Handelszyklus abzuschließen und dann zu beenden. Hier ist die Funktion zum Schließen von Handelsgeschäften.
//--- Position Finalization bool finalizePosition() { //--- Position Finalization Start m_tradeConfig.currentState = TERMINATING; //--- Set terminating state TradeMetrics metrics = {true, 0.0, 0.0}; //--- Initialize metrics closeActiveTrades(metrics); //--- Close all trades if (metrics.operationSuccess) { //--- Check success ArrayResize(m_tradeConfig.activeTickets, 0); //--- Clear tickets m_tradeConfig.currentState = INACTIVE; //--- Set inactive state Print("Position closed successfully"); //--- Log success } else { Print("Failed to close position"); //--- Log failure } return metrics.operationSuccess; //--- Return status //--- Position Finalization End }
Wir beginnen damit, „m_tradeConfig.currentState“ auf „TERMINATING“ zu setzen, um anzuzeigen, dass der Handelszyklus beendet ist. Dies trägt dazu bei, das Handelsmanagement vor einem Zirkel zu schützen, wenn wir dabei sind, die Handelsgeschäfte abzuschließen. Dann initialisieren wir eine Struktur „TradeMetrics“ mit dem Namen „metrics“, wobei „operationSuccess“ auf true, „totalVolume“ auf 0,0 und „netProfitLoss“ auf 0,0 gesetzt wird, um die Ergebnisse der Schließung zu verfolgen. Wir rufen „closeActiveTrades“ mit „metrics“ auf, um alle in „m_tradeConfig.activeTickets“ aufgeführten offenen Positionen zu schließen. Wenn „metrics.operationSuccess“ wahr bleibt, löschen wir „m_tradeConfig.activeTickets“ mit ArrayResize, um die Ticketliste zurückzusetzen, setzen „m_tradeConfig.currentState“ auf „INACTIVE“, um das System als inaktiv zu kennzeichnen, und protokollieren den Erfolg mit „Print“.
Wenn das Schließen fehlschlägt, protokollieren wir den Fehler mit „Print“. Schließlich geben wir „metrics.operationSuccess“ zurück, um anzuzeigen, ob der Prozess erfolgreich abgeschlossen wurde. Wenn wir die Handelsgeschäfte zu diesem Zeitpunkt nicht geschlossen haben, bedeutet dies, dass wir uns nicht im Prozess der Positionsschließung befinden, sodass wir mit der Bewertung fortfahren können, um zu sehen, ob der Preis die Erholungszonen oder Zielniveaus erreicht hat. Wir beginnen mit einem Kauf
double currentPrice; //--- Initialize price if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position currentPrice = getMarketBid(); //--- Get bid price if (currentPrice > m_zoneBounds.zoneTargetHigh) { //--- Check profit target Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice < m_zoneBounds.zoneLow) { //--- Check recovery trigger Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice); //--- Open sell recovery } }
Wir fahren fort mit der Implementierung von Logik in der Funktion „evaluateMarketTick“ der Klasse „MarketZoneTrader“, um Kaufpositionen zu behandeln. Wir beginnen mit der Deklaration von „currentPrice“, um den Marktpreis zu speichern. Wenn „m_tradeConfig.direction“ ORDER_TYPE_BUY ist, setzen wir „currentPrice“ mit Hilfe der Funktion „getMarketBid“, um den Geldkurs zu ermitteln, da dies der Kurs ist, zu dem wir eine Kaufposition schließen können. Als Nächstes wird geprüft, ob „currentPrice“ „m_zoneBounds.zoneTargetHigh“ übersteigt. Wenn dies der Fall ist, protokollieren wir die Schließung mit „Print“ und zeigen den Geldkurs und das Ziel an, rufen dann „finalizePosition“ auf, um den Handel zu schließen und mit „return“ zu beenden.
Wenn „currentPrice“ unter „m_zoneBounds.zoneLow“ fällt, protokollieren wir einen Recovery-Trigger mit „Print“ und rufen „triggerRecoveryTrade“ mit ORDER_TYPE_SELL und „currentPrice“ auf, um ein Verkaufsgeschäft zu eröffnen, um Verluste zu begrenzen. Diese Logik stellt sicher, dass wir gewinnbringende Kaufgeschäfte abschließen oder bei Verlusten die Wiederherstellung einleiten, damit unsere Strategie reaktionsfähig bleibt. Hier ist die Logik für die Funktion, die für die Eröffnung von Wiederherstellungsgeschäften verantwortlich ist.
//--- Recovery Trade Handling void triggerRecoveryTrade(ENUM_ORDER_TYPE tradeDirection, double price) { //--- Recovery Trade Start TradeMetrics metrics = {true, 0.0, 0.0}; //--- Initialize metrics closeActiveTrades(metrics); //--- Close existing trades for (int i = 0; i < 10 && !metrics.operationSuccess; i++) { //--- Retry closure Sleep(1000); //--- Wait 1 second metrics.operationSuccess = true; //--- Reset success flag closeActiveTrades(metrics); //--- Retry closure } m_lossTracker.tradeLossTracker += metrics.netProfitLoss; //--- Update loss tracker if (m_lossTracker.tradeLossTracker > 0 && metrics.operationSuccess) { //--- Check positive profit Print("Closing position due to positive profit: ", m_lossTracker.tradeLossTracker); //--- Log closure finalizePosition(); //--- Close position m_lossTracker.tradeLossTracker = 0.0; //--- Reset loss tracker return; //--- Exit } double tradeSize = determineRecoverySize(tradeDirection); //--- Calculate trade size ulong ticket = openMarketTrade(tradeDirection, tradeSize, price); //--- Open recovery trade if (ticket > 0) { //--- Check if trade opened storeTradeTicket(ticket); //--- Store ticket m_tradeConfig.direction = tradeDirection; //--- Update direction if (tradeDirection == ORDER_TYPE_BUY) m_tradeConfig.accumulatedBuyVolume += tradeSize; //--- Update buy volume else m_tradeConfig.accumulatedSellVolume += tradeSize; //--- Update sell volume Print("Recovery trade opened: Ticket=", ticket, ", Direction=", EnumToString(tradeDirection), ", Volume=", tradeSize); //--- Log recovery trade } //--- Recovery Trade End } //--- Recovery Size Calculation double determineRecoverySize(ENUM_ORDER_TYPE tradeDirection) { //--- Recovery Size Calculation Start double tradeSize = -m_lossTracker.tradeLossTracker / m_tradeConfig.zoneProfitSpan; //--- Calculate lot size tradeSize = MathCeil(tradeSize / getMarketVolumeStep()) * getMarketVolumeStep(); //--- Round to volume step return tradeSize; //--- Return trade size //--- Recovery Size Calculation End }
Um Fälle zu behandeln, in denen der Markt Recovery-Instanzen auslösen muss, beginnen wir mit der Funktion „triggerRecoveryTrade“, um Recovery-Trades zu behandeln, wenn sich eine Position gegen uns bewegt. Zunächst initialisieren wir eine Struktur „TradeMetrics“ mit dem Namen „metrics“, wobei „operationSuccess“ auf true, „totalVolume“ auf 0,0 und „netProfitLoss“ auf 0,0 gesetzt wird. Wir rufen „closeActiveTrades“ mit „metrics“ auf, um bestehende Positionen zu schließen. Wenn „metrics.operationSuccess“ falsch ist, versuchen wir es bis zu 10 Mal erneut, warten eine Sekunde mit Sleep und setzen „operationSuccess“ vor jedem Versuch zurück.
Wir aktualisieren „m_lossTracker.tradeLossTracker“ durch Hinzufügen von „metrics.netProfitLoss“. Wenn „m_lossTracker.tradeLossTracker“ positiv ist und „metrics.operationSuccess“ wahr ist, protokollieren wir die Schließung mit „Print“, rufen „finalizePosition“ auf, setzen „m_lossTracker.tradeLossTracker“ auf 0,0 zurück und beenden mit „return“. Andernfalls berechnen wir die Erholungsgröße „tradeSize“ mit „determineRecoverySize“ und „tradeDirection“ und eröffnen dann einen neuen Handel mit „openMarketTrade“ unter Verwendung von „tradeDirection“, „tradeSize“ und „price“.
Wenn das zurückgegebene „Ticket“ gültig ist, speichern wir es mit „storeTradeTicket“, aktualisieren „m_tradeConfig.direction“, passen „m_tradeConfig.accumulatedBuyVolume“ oder „m_tradeConfig.accumulatedSellVolume“ basierend auf „tradeDirection“, und protokollieren den Handel mit „Print“ unter Verwendung von EnumToString. Als Nächstes erstellen wir die Funktion „determineRecoverySize“, um die Losgröße für Recovery Handelsgeschäfte zu berechnen. Wir berechnen „tradeSize“, indem wir den negativen Wert von „m_lossTracker.tradeLossTracker“ durch „m_tradeConfig.zoneProfitSpan“ dividieren, um die Größe des Handels zur Deckung der Verluste zu bestimmen. Anschließend wird „tradeSize“ mit MathCeil und „getMarketVolumeStep“ auf den Volumenschritt des Brokers gerundet, um die Konformität sicherzustellen, und das Ergebnis zurückgegeben. Jetzt werden die Wiederherstellungsinstanzen behandelt, und wir können mit der Logik für die Behandlung der Verkaufszonen fortfahren. Die Logik ist genau das Gegenteil von Kaufen, also werden wir nicht viel Zeit darauf verwenden. Die endgültige vollständige Funktion wird wie folgt aussehen.
//--- Market Tick Evaluation void evaluateMarketTick() { //--- Tick Evaluation Start if (m_tradeConfig.currentState == INACTIVE) return; //--- Exit if inactive if (m_tradeConfig.currentState == TERMINATING) { //--- Check terminating state finalizePosition(); //--- Finalize position return; //--- Exit } double currentPrice; //--- Initialize price if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position currentPrice = getMarketBid(); //--- Get bid price if (currentPrice > m_zoneBounds.zoneTargetHigh) { //--- Check profit target Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice < m_zoneBounds.zoneLow) { //--- Check recovery trigger Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice); //--- Open sell recovery } } else if (m_tradeConfig.direction == ORDER_TYPE_SELL) { //--- Handle sell position currentPrice = getMarketAsk(); //--- Get ask price if (currentPrice < m_zoneBounds.zoneTargetLow) { //--- Check profit target Print("Closing position: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice > m_zoneBounds.zoneHigh) { //--- Check recovery trigger Print("Triggering recovery trade: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice); //--- Open buy recovery } } //--- Tick Evaluation End }
Die Funktion übernimmt nun alle Anweisungen für die Wiederherstellung. Nach der Kompilierung erhalten wir folgendes Ergebnis.
Aus dem Bild können wir ersehen, dass wir Positionen, die aufgrund von Abprallsignalen bei einem Trend ausgelöst werden, erfolgreich abwickeln. 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 ein robustes MQL5-Programm entwickelt, das ein Zone Recovery System für den Trendhandel mit Envelopes implementiert, das den Relative Strength Index (RSI) und die Envelopes-Indikatoren kombiniert, um Handelsgelegenheiten zu identifizieren und Verluste durch strukturierte Erholungszonen zu verwalten, wobei ein objektorientierter Programmieransatz (OOP) verwendet wird. Mit Komponenten wie der Klasse „MarketZoneTrader“, Strukturen wie „TradeConfig“ und „ZoneBoundaries“ und Funktionen wie „processTick“ und „triggerRecoveryTrade“ haben wir ein flexibles System geschaffen, das Sie durch Anpassung von Parametern wie „zoneTargetPoints“ oder „riskPercentage“ an verschiedene Marktbedingungen 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 den in diesem Artikel geschaffenen Grundlagen können Sie dieses System zur Zonenerholung verfeinern oder seine Logik anpassen, um neue Handelsstrategien zu entwickeln, die Ihren Fortschritt im algorithmischen Handel vorantreiben. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18720
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.





- 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.
Vielen Dank 🙏.
Sehr willkommen. Danke