
Automatisieren von Handelsstrategien in MQL5 (Teil 7): Aufbau eines Raster-Handel EA mit dynamischer Losgrößen-Skalierung
Einführung
Im vorherigen Artikel (Teil 6) haben wir ein automatisiertes Order Block Detection System in MetaQuotes Language 5 (MQL5) entwickelt. In Teil 7 konzentrieren wir uns nun auf den Raster-Handel, eine Strategie, bei der in festen Preisintervallen gehandelt wird, kombiniert mit einer dynamischen Los-Skalierung zur Optimierung von Risiko und Ertrag. Dieser Ansatz passt die Positionsgröße an die Marktbedingungen an und zielt darauf ab, die Rentabilität und das Risikomanagement zu verbessern. Wir werden das Thema behandeln:
Am Ende werden Sie ein voll funktionsfähiges Raster-Handelsprogramm (Grid Trading) mit dynamischer Los-Skalierung haben, das Sie testen und optimieren können. Fangen wir an!
Blaupause der Strategie
Der Raster-Handel ist ein systematischer Ansatz, bei dem Kauf- und Verkaufsaufträge in vorher festgelegten Preisintervallen platziert werden, sodass Händler von Marktschwankungen profitieren können, ohne genaue Trendvorhersagen treffen zu müssen. Diese Strategie profitiert von der Marktvolatilität, indem sie innerhalb einer definierten Preisspanne kontinuierlich Handelsgeschäfte eröffnet und schließt. Um die Leistung zu verbessern, werden wir eine dynamische Losgrößenanpassung integrieren, die die Positionsgrößen auf der Grundlage von vordefinierten Bedingungen wie Kontostand, Volatilität oder früheren Handelsergebnissen anpasst. Unser Raster-Handel System wird mit den folgenden Schlüsselkomponenten arbeiten:
- Rasterstruktur - Wir definieren die Abstände zwischen den Aufträgen.
- Einstiegs- und Ausführungsregeln - Wir legen fest, wann wir Rastergeschäfte auf der Grundlage fester Abstände mit einer Indikatorstrategie, die den gleitenden Durchschnitt verwendet, eröffnen.
- Dynamische Losgrößenanpassung - Wir werden einen adaptiven Mechanismus für die Losgrößenanpassung implementieren, der die Positionsgrößen auf der Grundlage der Marktbedingungen oder vordefinierter Risikoparameter anpasst.
- Handelsmanagement - Wir werden Stop-Loss-, Take-Profit- und optionale Breakeven-Mechanismen einbauen, um das Risiko effektiv zu verwalten.
- Ausstiegsstrategie - Wir entwickeln eine Logik zum Schließen von Positionen auf der Grundlage von Gewinnzielen, Risikolimits oder Trendumkehrungen.
Kurz und bündig: Hier ist der gesamte Strategieplan visualisiert, um das Verständnis zu erleichtern.
Durch die Kombination eines strukturierten Rastersystems mit einer adaptiven Losgröße erstellen wir einen EA, der die Rendite maximiert und gleichzeitig das Risiko effektiv steuert. Als Nächstes werden wir diese Konzepte in MQL5 umsetzen.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen Sie zum Navigator, suchen Sie den Ordner Indikatoren, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald es erstellt ist, müssen wir in der Programmierumgebung einige globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Grid Strategy" #property strict #include <Trade/Trade.mqh> //--- Include trading library CTrade obj_Trade; //--- Trading object instance //--- Closure Mode Enumeration and Inputs enum ClosureMode { CLOSE_BY_PROFIT, //--- Use total profit (in currency) to close positions CLOSE_BY_POINTS //--- Use a points threshold from breakeven to close positions }; input group "General EA Inputs" input ClosureMode closureMode = CLOSE_BY_POINTS; //Select closure mode double breakevenPoints = 50 * _Point; //--- Points offset to add/subtract to/from breakeven //--- Global Variables double TakeProfit; //--- Current take profit level double initialLotsize = 0.1; //--- Initial lot size for the first trade double takeProfitPts = 200 * _Point; //--- Take profit distance in points double profitTotal_inCurrency = 100; //--- Profit target (in currency) to close positions double gridSize; //--- Price level at which grid orders are triggered double gridSize_Spacing = 500 * _Point; //--- Grid spacing in points double LotSize; //--- Current lot size (increased with grid orders) bool isTradeAllowed = true; //--- Flag to allow trade on a new bar int totalBars = 0; //--- Count of bars seen so far int handle; //--- Handle for the Moving Average indicator double maData[]; //--- Array for Moving Average data
Hier binden wir die Bibliothek „Trade/Trade.mqh“ mit #include ein und instanziieren das Objekt „obj_Trade“, um unsere Handelsgeschäfte zu bearbeiten. Wir definieren die Enumeration „ClosureMode“ mit Optionen für das Schließen von Positionen und richten Nutzereingaben wie „closureMode“ und „breakevenPoints“ ein. Als Nächstes deklarieren wir Variablen zur Verwaltung unserer Take-Profit-Levels, der anfänglichen Losgröße, des Rasterabstands und der dynamischen Losgröße sowie Flags und Zähler für die Handelssteuerung und die Daten des gleitenden Durchschnittsindikators. Anschließend müssen wir die Prototypen für unsere Schlüsselfunktionen deklarieren, die das Programm wie folgt strukturieren werden.
//--- Function Prototypes void CheckAndCloseProfitTargets(); //--- Closes all positions if total profit meets target void ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions) void ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions) void UpdateMovingAverage(); //--- Updates MA indicator data from its buffer bool IsNewBar(); //--- Checks if a new bar has formed double CalculateWeightedBreakevenPrice(); //--- Calculates the weighted average entry price for positions void CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold void CloseAllPositions(); //--- Closes all open positions
Für die Funktionen werden wir „CheckAndCloseProfitTargets“ implementieren, um die Gesamtrentabilität zu überwachen und Positionen zu schließen, sobald unser Ziel erreicht ist, und „ExecuteInitialTrade“, um die Strategie mit den ersten Auftrag eines KAUFS oder VERKAUFS zu starten. „ManageGridPositions“ fügt zusätzliche Aufträge in festgelegten Rasterintervallen hinzu, wenn sich der Markt bewegt, während „UpdateMovingAverage“ sicherstellt, dass unsere Indikatordaten für die Entscheidungsfindung aktuell sind. „IsNewBar“ erkennt neue Balken, um zu verhindern, dass mehrere Handelsgeschäfte auf derselben Kerze getätigt werden, „CalculateWeightedBreakevenPrice“ berechnet den durchschnittlichen Einstiegspreis über alle Positionen hinweg, und „CheckBreakevenClose“ verwendet diese Informationen, um Handelsgeschäfte zu beenden, wenn günstige Bedingungen erfüllt sind. Schließlich werden mit „CloseAllPositions“ alle offenen Handelsgeschäfte bei Bedarf systematisch geschlossen.
Nachdem wir all diese Einstellungen im „globalen Bereich“ vorgenommen haben, können wir mit der Programminitialisierung durch „OnInit“ fortfahren.
//+------------------------------------------------------------------+ //--- Expert initialization function //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close) handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE); if (handle == INVALID_HANDLE){ Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!"); return (INIT_FAILED); } ArraySetAsSeries(maData, true); //--- Ensure MA data array is in series order return(INIT_SUCCEEDED); }
Hier initialisieren wir das Programm, indem wir unseren Indikator für den gleitenden Durchschnitt mit der Funktion iMA mit einer Periodenlänge von 21, dem Typ SMA und PRICE_CLOSE zur Erfassung der Schlusskurse einrichten. Wir prüfen, ob der Indikator-Handle gültig ist - wenn nicht (INVALID_HANDLE), geben wir eine Fehlermeldung aus und geben INIT_FAILED zurück, um die Ausführung des Programms zu beenden. Schließlich rufen wir die Funktion ArraySetAsSeries für das Array „maData“ auf, um sicherzustellen, dass die gleitenden Durchschnittsdaten in der richtigen Reihenfolge angeordnet sind, bevor wir INIT_SUCCEEDED zurückgeben, um die erfolgreiche Initialisierung zu bestätigen. Nach der korrekten Initialisierung können wir mit OnTick fortfahren, um die Logik für die Eröffnung und Verwaltung der Positionen zu erstellen.
//+------------------------------------------------------------------+ //--- Expert tick function //+------------------------------------------------------------------+ void OnTick(){ //--- Allow new trade signals on a new bar if(IsNewBar()) isTradeAllowed = true; //--- Update the Moving Average data UpdateMovingAverage(); }
Da wir nicht bei jedem Tick, sondern bei jedem Balken auf Handelsgeschäfte prüfen wollen, rufen wir die Funktion „IsNewBar“ auf und setzen damit die Variable „isTradeAllowed“ auf true, wenn sich ein neuer Balken bildet. Anschließend rufen wir die Funktion auf, die für die Ermittlung der gleitenden Durchschnittswerte zuständig ist. Die Definitionen der Funktion lauten wie folgt.
//+-------------------------------------------------------------------+ //--- Function: UpdateMovingAverage //--- Description: Copies the latest data from the MA indicator buffer. //+-------------------------------------------------------------------+ void UpdateMovingAverage(){ if(CopyBuffer(handle, 0, 1, 3, maData) < 0) Print("Error: Unable to update Moving Average data."); } //+-------------------------------------------------------------------+ //--- Function: IsNewBar //--- Description: Checks if a new bar has been formed. //+-------------------------------------------------------------------+ bool IsNewBar(){ int bars = iBars(_Symbol, _Period); if(bars > totalBars){ totalBars = bars; return true; } return false; }
Hier implementieren wir „UpdateMovingAverage“, um unsere Indikatordaten zu aktualisieren, indem wir die neuesten Werte aus dem Puffer des gleitenden Durchschnitts mit der Funktion CopyBuffer kopieren. Wenn dieser Funktionsaufruf fehlschlägt, wird eine Fehlermeldung ausgegeben, um uns darauf hinzuweisen, dass die Aktualisierung nicht erfolgreich war. In der Funktion „IsNewBar“ prüfen wir, ob sich ein neuer Balken gebildet hat, indem wir die aktuelle Anzahl der Balken, die wir über die Funktion iBars erhalten haben, mit unserer gespeicherten „totalBars“-Anzahl vergleichen; wenn sich die Anzahl erhöht hat, aktualisieren wir „totalBars“ und geben „true“ zurück, was anzeigt, dass ein neuer Balken für Handelsentscheidungen zur Verfügung steht. Anschließend fahren wir mit der Tick-Funktion fort, um Handelsgeschäfte auf der Grundlage der abgerufenen Indikatorwerte auszuführen.
//--- Reset lot size if no positions are open if(PositionsTotal() == 0) LotSize = initialLotsize; //--- Retrieve recent bar prices for trade signal logic double low1 = iLow(_Symbol, _Period, 1); double low2 = iLow(_Symbol, _Period, 2); double high1 = iHigh(_Symbol, _Period, 1); double high2 = iHigh(_Symbol, _Period, 2); //--- Get current Ask and Bid prices (normalized) double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- If no positions are open and trading is allowed, check for an initial trade signal if(PositionsTotal() == 0 && isTradeAllowed){ ExecuteInitialTrade(ask, bid); }
Hier wird zunächst mit der Funktion PositionsTotal geprüft, ob keine Positionen offen sind, und wenn ja, wird die „LotSize“ auf „initialLotsize“ zurückgesetzt. Als Nächstes rufen wir die jüngsten Balkenpreise ab, indem wir iLow und iHigh aufrufen, um die Höchst- und Tiefstwerte der beiden vorangegangenen Balken zu erfassen, die uns bei der Bildung unserer Handelssignale helfen werden. Anschließend werden die aktuellen Geld- und Briefkurse mit SymbolInfoDouble ermittelt und mit NormalizeDouble normalisiert, um die Genauigkeit zu gewährleisten. Wenn schließlich der Handel erlaubt ist (wie durch „isTradeAllowed“ angezeigt) und derzeit keine Positionen offen sind, rufen wir die Funktion „ExecuteInitialTrade“ mit dem „Ask“- und „Bid“-Kurs auf, um unseren ersten Handel einzuleiten. Die Funktion ist wie folgt definiert.
//+---------------------------------------------------------------------------+ //--- Function: ExecuteInitialTrade //--- Description: Executes the initial BUY or SELL trade based on MA criteria. //--- (These are considered "initial positions.") //+---------------------------------------------------------------------------+ void ExecuteInitialTrade(double ask, double bid){ //--- BUY Signal: previous bar's low above MA and bar before that below MA if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){ gridSize = ask - gridSize_Spacing; //--- Set grid trigger below current ask TakeProfit = ask + takeProfitPts; //--- Set TP for BUY if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy")) Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize); else Print("Initial BUY order failed at ", ask); isTradeAllowed = false; } //--- SELL Signal: previous bar's high below MA and bar before that above MA else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){ gridSize = bid + gridSize_Spacing; //--- Set grid trigger above current bid TakeProfit = bid - takeProfitPts; //--- Set TP for SELL if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell")) Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize); else Print("Initial SELL order failed at ", bid); isTradeAllowed = false; } }
Hier implementieren wir die Funktion „ExecuteInitialTrade“, um einen ersten Handel auf der Grundlage der Werte von „maData“ zu eröffnen. Mit der Funktion iLow werden die Tiefstkurse der beiden vorangegangenen Balken und mit der Funktion iHigh die Höchstkurse ermittelt. Für ein KAUF-Signal prüfen wir, ob das Tief des vorherigen Balkens über „maData“ liegt, während der vorherige Balken darunter lag. Wenn diese Bedingung erfüllt ist, setzen wir „gridSize“ unter den aktuellen „ask“, indem wir „gridSize_Spacing“ verwenden, um das nächste Raster-Level zu bestimmen, berechnen „TakeProfit“, indem wir „takeProfitPts“ zum „ask“ addieren, und führen einen KAUF mit der Methode „obj_Trade.Buy“ aus.
Für ein VERKAUFS-Signal prüfen wir, ob das Hoch des vorherigen Balkens unter „maData“ liegt, während der vorherige Balken darüber lag. Wenn „true“, setzen wir „gridSize“ über das „bid“, bestimmen „TakeProfit“ durch Subtraktion von „takeProfitPts“ vom „bid“ und versuchen, einen VERKAUF mit „obj_Trade.Sell“ auszuführen. Sobald ein Handel ausgeführt wurde, setzen wir „isTradeAllowed“ auf false, um weitere Eingaben zu verhindern, bis weitere Bedingungen erfüllt sind. Hier ist das Ergebnis.
Aus dem Bild können wir ersehen, dass die bestätigten Handelsgeschäfte ausgeführt werden. Nun müssen wir zur Verwaltung der Handelsgeschäfte übergehen, indem wir die Rasterpositionen öffnen.
//--- If positions exist, manage grid orders if(PositionsTotal() > 0){ ManageGridPositions(ask, bid); }
Mit der Funktion PositionsTotal prüfen wir, ob es offene Positionen gibt. Wenn die Anzahl der Positionen größer als Null ist, rufen wir die Funktion „ManageGridPositions“ auf, um zusätzliche Raster-Handelsgeschäfte zu behandeln. Die Funktion nimmt „ask“ und „bid“ als Parameter, um die geeigneten Preisniveaus für die Platzierung neuer Netzaufträge auf der Grundlage der Marktbewegung zu bestimmen. Der Codeausschnitt der Funktion ist wie folgt.
//+------------------------------------------------------------------------+ //--- Function: ManageGridPositions //--- Description: When an initial position exists, grid orders are added //--- if the market moves to the grid level. (These orders are //--- considered "grid positions.") The lot size is doubled //--- with each grid order. //+------------------------------------------------------------------------+ void ManageGridPositions(double ask, double bid){ for(int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ int positionType = (int)PositionGetInteger(POSITION_TYPE); //--- Grid management for BUY positions if(positionType == POSITION_TYPE_BUY){ if(ask <= gridSize){ LotSize *= 2; //--- Increase lot size for grid order if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY")) Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize); else Print("Grid BUY order failed at ", ask); gridSize = ask - gridSize_Spacing; //--- Update grid trigger } } //--- Grid management for SELL positions else if(positionType == POSITION_TYPE_SELL){ if(bid >= gridSize){ LotSize *= 2; //--- Increase lot size for grid order if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL")) Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize); else Print("Grid SELL order failed at ", bid); gridSize = bid + gridSize_Spacing; //--- Update grid trigger } } } } }
Wir implementieren die Funktion „ManageGridPositions“ zur Verwaltung von Rasteraufträgen. Wir durchlaufen alle offenen Positionen in umgekehrter Reihenfolge mit einer for-Schleife und rufen das Ticket für jede Position mit der Funktion PositionGetTicket ab. Dann wählen wir die Position mit PositionSelectByTicket aus und bestimmen mit PositionGetInteger mit dem Parameter POSITION_TYPE, ob es sich um einen KAUF oder VERKAUF handelt. Wenn es sich bei der Position um einen KAUF handelt, prüfen wir, ob der Marktpreis „ask“ „gridSize“ erreicht oder unterschritten hat. Wenn ja, verdoppeln wir „LotSize“ und führen den Auftrag für einen neuen Raster-KAUF mit der Funktion „obj_Trade.Buy“ aus. Wenn die Bestellung erfolgreich war, wird eine Bestätigungsmeldung gedruckt, andernfalls eine Fehlermeldung. Anschließend wird „gridSize“ auf die nächsttiefere Rasterebene aktualisiert.
Wenn es sich bei der Position um einen VERKAUF handelt, wird geprüft, ob „bid“ die „gridSize“ erreicht oder überschritten hat. Wenn ja, verdoppeln wir „LotSize“ und platzieren eine neue VERKAUFS-Order mit „obj_Trade.Sell“. Der Raster-Trigger „gridSize“ wird dann auf die nächsthöhere Ebene aktualisiert. Nachdem wir die Rasterpositionen geöffnet haben, müssen wir die Positionen verfolgen und verwalten, indem wir sie schließen, sobald wir die unten definierten Werte erreichen.
//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT) if(closureMode == CLOSE_BY_PROFIT) CheckAndCloseProfitTargets();
Wenn „closureMode“ auf „CLOSE_BY_PROFIT“ eingestellt ist, rufen wir die Funktion „CheckAndCloseProfitTargets“ auf, um zu prüfen, ob der Gesamtgewinn das vordefinierte Ziel erreicht hat und schließen alle Positionen entsprechend. Die Funktionsdeklaration lautet wie folgt.
//+----------------------------------------------------------------------------+ //--- Function: CheckAndCloseProfitTargets //--- Description: Closes all positions if the combined profit meets or exceeds //--- the user-defined profit target. //+----------------------------------------------------------------------------+ void CheckAndCloseProfitTargets(){ if(PositionsTotal() > 1){ double totalProfit = 0; for(int i = PositionsTotal()-1; i >= 0; i--){ ulong tkt = PositionGetTicket(i); if(PositionSelectByTicket(tkt)) totalProfit += PositionGetDouble(POSITION_PROFIT); } if(totalProfit >= profitTotal_inCurrency){ Print("Profit target reached (", totalProfit, "). Closing all positions."); CloseAllPositions(); } } }
Um sicherzustellen, dass alle Positionen geschlossen werden, wenn der kumulierte Gesamtgewinn das vordefinierte Gewinnziel erreicht oder übersteigt, prüfen wir zunächst mithilfe von PositionsTotal, ob es mehr als eine offene Position gibt. Wir initialisieren „totalProfit“, um den kombinierten Gewinn aller Positionen zu verfolgen. Anschließend werden alle offenen Positionen in einer Schleife durchlaufen, wobei das Ticket jeder Position mit PositionGetTicket abgerufen und mit PositionSelectByTicket ausgewählt wird. Für jede ausgewählte Position wird der Gewinn mittels PositionGetDouble mit dem Parameter POSITION_PROFIT ermittelt und zu „totalProfit“ addiert. Wenn „totalProfit“ den Wert von „profitTotal_inCurrency“ erreicht oder übersteigt, drucken wir eine Nachricht aus, die anzeigt, dass das Gewinnziel erreicht wurde, und rufen die Funktion „CloseAllPositions“ auf, deren Definition wie folgt lautet, um alle offenen Handelsgeschäfte zu schließen.
//+------------------------------------------------------------------+ //--- Function: CloseAllPositions //--- Description: Iterates through and closes all open positions. //+------------------------------------------------------------------+ void CloseAllPositions(){ for(int i = PositionsTotal()-1; i >= 0; i--){ ulong posTkt = PositionGetTicket(i); if(PositionSelectByTicket(posTkt)){ if(obj_Trade.PositionClose(posTkt)) Print("Closed position ticket: ", posTkt); else Print("Failed to close position ticket: ", posTkt); } } }
Die Funktion iteriert einfach über alle offenen Positionen und wird für jede ausgewählte Position mit der Methode „obj_Trade.PositionClose“ geschlossen. Schließlich definieren wir die Logik zur Schließung der Positionen bei Erreichen der Gewinnschwelle.
//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1) CheckBreakevenClose(ask, bid);
Wenn „closureMode“ auf „CLOSE_BY_POINTS“ eingestellt ist und es mehr als eine offene Position gibt, rufen wir die Funktion „CheckBreakevenClose“ mit den Parametern „ask“ und „bid“ auf, um festzustellen, ob der Preis die Break-Even-Schwelle erreicht hat, sodass Positionen auf der Grundlage von vordefinierten Punkten ab Break-Even geschlossen werden können. Im Folgenden wird die Funktion definiert.
//+----------------------------------------------------------------------------+ //--- Function: CalculateWeightedBreakevenPrice //--- Description: Calculates the weighted average entry price (breakeven) //--- of all open positions (assumed to be in the same direction). //+----------------------------------------------------------------------------+ double CalculateWeightedBreakevenPrice(){ double totalCost = 0; double totalVolume = 0; int posType = -1; //--- Determine the type from the first position for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (int)PositionGetInteger(POSITION_TYPE); break; } } //--- Sum the cost and volume for positions matching the type for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ if(PositionGetInteger(POSITION_TYPE) == posType){ double price = PositionGetDouble(POSITION_PRICE_OPEN); double volume = PositionGetDouble(POSITION_VOLUME); totalCost += price * volume; totalVolume += volume; } } } if(totalVolume > 0) return(totalCost / totalVolume); else return(0); } //+-----------------------------------------------------------------------------+ //--- Function: CheckBreakevenClose //--- Description: When using CLOSE_BY_POINTS and multiple positions exist, //--- calculates the weighted breakeven price and checks if the //--- current price has moved the specified points in a profitable //--- direction relative to breakeven. If so, closes all positions. //+-----------------------------------------------------------------------------+ void CheckBreakevenClose(double ask, double bid){ //--- Ensure we have more than one position (grid positions) if(PositionsTotal() <= 1) return; double weightedBreakeven = CalculateWeightedBreakevenPrice(); int posType = -1; //--- Determine the trade type from one of the positions for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (int)PositionGetInteger(POSITION_TYPE); break; } } if(posType == -1) return; //--- For BUY positions, profit when Bid >= breakeven + threshold if(posType == POSITION_TYPE_BUY){ if(bid >= weightedBreakeven + breakevenPoints){ Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints); CloseAllPositions(); } } //--- For SELL positions, profit when Ask <= breakeven - threshold else if(posType == POSITION_TYPE_SELL){ if(ask <= weightedBreakeven - breakevenPoints){ Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints); CloseAllPositions(); } } }
Hier berechnen wir den Breakeven-Preis für alle offenen Positionen und stellen fest, ob der Marktpreis eine bestimmte Distanz darüber hinaus gegangen ist, um die Positionen mit Gewinn zu schließen. In „CalculateWeightedBreakevenPrice“ berechnen wir den gewichteten Breakeven-Preis, indem wir die Gesamtkosten aller offenen Positionen unter Verwendung von POSITION_PRICE_OPEN summieren und mit „POSITION_VOLUME“ gewichten. Zunächst wird mit POSITION_TYPE die Positionsart (KAUF oder VERKAUF) der ersten offenen Position ermittelt. Wir gehen dann in einer Schleife durch alle Positionen und summieren die Gesamtkosten und das Volumen für Positionen, die dem identifizierten Typ entsprechen. Wenn das Gesamtvolumen größer als Null ist, wird der gewichtete Breakeven-Preis ermittelt, indem die Gesamtkosten durch das Gesamtvolumen geteilt werden. Andernfalls geben wir Null zurück.
In „CheckBreakevenClose“ bestätigen wir zunächst mit der Funktion PositionsTotal, dass es mehrere offene Positionen gibt. Anschließend wird der gewichtete Breakeven-Preis durch den Aufruf von „CalculateWeightedBreakevenPrice“ ermittelt. Wir ermitteln die Position, indem wir eine Position auswählen und POSITION_TYPE abfragen. Wenn der Typ ungültig ist, wird die Funktion beendet. Bei KAUF-Positionen wird geprüft, ob der Geldkurs den „gewichteten Breakeven“ plus die „BreakevenPoints“ erreicht oder überschritten hat. Wenn ja, drucken wir eine Meldung und rufen „CloseAllPositions“ auf. Bei VERKAUFS-Positionen wird geprüft, ob der „Ask“-Kurs unter „weightedBreakeven“ minus „breakevenPoints“ gefallen ist. Wenn diese Bedingung erfüllt ist, drucken wir auch eine Meldung aus und rufen die Funktion „CloseAllPositions“ auf, um die Gewinne zu sichern. Nach dem Kompilieren und Ausführen des Programms erhalten wir das folgende Ergebnis.
Aus der Visualisierung ist ersichtlich, dass die Positionen über das Raster-System eröffnet und verwaltet und bei Erreichen der definierten Closure-Levels geschlossen werden, womit unser Ziel, ein Raster-System mit dynamischer Losgröße zu schaffen, erreicht ist. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest haben wir folgende Ergebnisse.
Backtest-Grafik:
Backtest-Bericht:
Schlussfolgerung
Abschließend haben wir den Prozess der Entwicklung eines MQL5 Expert Advisors (EA) demonstriert, der eine dynamische Raster-Handelsstrategie verwendet. Durch die Kombination von Schlüsselelementen wie der Platzierung von Rasteraufträgen, der dynamischen Skalierung von Lots und einem gezielten Gewinn- und Breakeven-Management haben wir ein System geschaffen, das sich an Marktschwankungen anpasst und darauf abzielt, das Risiko-Ertrags-Verhältnis zu optimieren und sich von ungünstigen Kursbewegungen zu erholen.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel birgt ein erhebliches finanzielles Risiko, und das Marktverhalten kann sehr unvorhersehbar sein. Die vorgestellten Strategien bieten zwar einen strukturierten Ansatz für den Raster-Handel, sind aber keine Garantie für künftige Rentabilität. Rigorose Backtests und Risikomanagement sind vor dem Live-Handel unerlässlich.
Durch die Anwendung dieser Techniken können Sie Ihre Raster-Handelssysteme verfeinern, Ihre Marktanalyse verbessern und Ihre algorithmischen Handelsstrategien optimieren. Viel Glück auf Ihrer Handelsreise!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17190
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.
zum Beispiel.....für das Kaufsignal haben Sie iLow und nicht Low1 variabel verwendet
ist nur für meine Studie, danke!!!
diese vier Zeilen können kommentiert werden
diese vier Zeilen können kommentiert werden
Sicher. Ist jetzt alles in Ordnung?
ist ok.....best EA .
Die 4 Zeilen sind verwirrend für einen Neuling
ist ok.....beste EA .
Die 4 Zeilen sind verwirrend für einen Neuling
Okay