English
preview
Automatisierung von Handelsstrategien in MQL5 (Teil 47): Nick Rypock Trailing Reverse (NRTR) mit Hedging-Funktionen

Automatisierung von Handelsstrategien in MQL5 (Teil 47): Nick Rypock Trailing Reverse (NRTR) mit Hedging-Funktionen

MetaTrader 5Handel |
21 3
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel (Teil 46) haben wir ein Handelssystem mit dem Namen Liquidity Sweep on Break of Structure in MetaQuotes Language 5 (MQL5) erstellt, das Liquiditätszonen identifiziert, Strukturbrüche erkennt und Trades mit anpassbaren Risikoparametern und visuellen Indikatoren ausführt. In Teil 47 erstellen wir ein Nick Rypock Trailing Reverse (NRTR) System, das kanalbasierte Umkehrsignale für trendfolgende Einstiege nutzt, und wir integrieren Hedging-Funktionen, dynamische Trailing-Stops und Risikomanagementfunktionen. Wir werden die folgenden Themen behandeln:

  1. Untersuchung der Nick Rypock Trailing Reverse Strategie und ihrer Komponenten
  2. Implementierung in MQL5
  3. Backtesting
  4. Schlussfolgerung

Am Ende haben Sie ein funktionsfähiges MQL5-Programm für den NRTR-gesteuerten Umkehrhandel mit Hedging, das Sie anpassen können – los geht's!


Untersuchung der Nick Rypock Trailing Reverse Strategie und ihrer Komponenten

Die Nick Rypock Trailing Reverse-Strategie identifiziert potenzielle Trendumkehrungen, indem sie das Auftauchen dynamischer Unterstützungsniveaus in einem NRTR-Kanal verfolgt, der auf Average True Range-Berechnungen basiert, die mit einem benutzerdefinierten Faktor multipliziert werden, um adaptive obere und untere Kanalgrenzen zu bilden. Einstiegssignale treten auf, wenn ein neues Unterstützungsniveau auftaucht, wodurch eine Kaufposition am Beginn einer aufwärts gerichteten Unterstützung (Long-Kanal) oder eine Verkaufsposition am Beginn einer abwärts gerichteten Unterstützung (Short-Kanal) eingeleitet wird. Das System kann so aufkommende Trends verfolgen. Ist Hedging aktiviert, können gegensätzliche Positionen gleichzeitig gehalten werden. Die Risikokontrollen sind integriert und bieten eine automatische Berechnung der Lot-Größe, die proportional zum Kontostand, zum Kontoeigenkapital oder zur freien Marge ist, um das Risiko pro Handel zu begrenzen, kombiniert mit Festpunkt- oder ATR-basierten Stop-Loss- und Take-Profit-Einstellungen für volatilitätsabhängigen Schutz.

Unser Ansatz besteht darin, den NRTR-Kanalindikator zu laden, Unterstützungswechsel als Signale zu erkennen, Einstiegsregeln mit Positionslimits und Hedging durchzusetzen sowie Positionsgrößen und Stops anhand der Risikoparameter adaptiv zu berechnen. Ergänzend implementieren wir Trailing- und Routinen für virtuelle Positionsschließungen. So entsteht ein auf Umkehrsignale ausgerichtetes System, das Trendchancen mit diszipliniertem Risikomanagement verbindet. Kurz gesagt bietet dieses Framework einen an die Volatilität angepassten Handelsansatz, der sich auf Trailing-Reversals und anpassbare Schutzmaßnahmen konzentriert, um einen stabilen und konsistenten Systembetrieb sicherzustellen. Hier sehen Sie eine Visualisierung des Strategierahmens.

NRTR-FRAMEWORK


Implementierung in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner „Experts“, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                     NRTR - Nick Rypock Trailing Reverse - EA.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_RISK_BASE {
   RISK_BASE_EQUITY    = 1, // Equity
   RISK_BASE_BALANCE   = 2, // Balance
   RISK_BASE_FREEMARGIN = 3 // Free Margin
};

enum ENUM_RISK_DEFAULT_SIZE {
   RISK_DEFAULT_FIXED = 1, // Fixed
   RISK_DEFAULT_AUTO  = 2  // Auto
};

enum ENUM_MODE_SL {
   SL_FIXED = 0, // Fixed
   SL_AUTO  = 1  // Auto
};

enum ENUM_MODE_TP {
   TP_FIXED = 0, // Fixed
   TP_AUTO  = 1  // Auto
};

enum ENUM_TRAILING_TYPE {
   TRAILING_NONE          = 0, // None
   TRAILING_POINTS        = 1, // Points
   TRAILING_SUPPORT_LEVELS = 2 // Support Levels
};

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Risk Management Settings"
input ENUM_RISK_DEFAULT_SIZE RiskDefaultSize = RISK_DEFAULT_FIXED; // Default Risk Size
input double DefaultLotSize                  = 0.01;               // Default Lot Size
input ENUM_RISK_BASE RiskBase                = RISK_BASE_BALANCE;  // Risk Base
input int MaxRiskPerTrade                    = 5;                  // Max Risk Per Trade (%)
double MinLotSize                            = 0.01;               // Min Lot Size
double MaxLotSize                            = 100;                // Max Lot Size
input int MaxPositions                       = 1;                  // Max Positions
input bool HedgeMode                         = true;               // Hedge Mode
bool CloseOnReversalSignal                   = false;              // Close On Reversal Signal

input group "Stop-Loss and Take-Profit Settings"
input int DefaultStopLossPoints              = 300;                // Default Stop Loss (Points)
input int DefaultTakeProfitPoints            = 300;                // Default Take Profit (Points)
input bool CloseOnStopLossHit                = false;              // Close On Stop Loss Hit
input bool CloseOnTakeProfitHit              = false;              // Close On Take Profit Hit

input group "Additional Settings"
input int MagicNumber                        = 1234567890;         // Magic Number

input group "Trailing Stop Loss"
input ENUM_TRAILING_TYPE TrailingType        = TRAILING_POINTS;    // Trailing Type
input ushort TrailingFrequencySeconds        = 10;                 // Trailing Frequency (Seconds)
input ushort SignalCheckFrequencySeconds     = 10;                 // Signal Check Frequency (Seconds)
input ushort TrailingStopPoints              = 120;                // Trailing Stop (Points)
input ushort TrailingStepPoints              = 100;                // Trailing Step (Points)
input ushort BreakEvenPoints                 = 10;                 // Break Even (Points)
input ushort BreakEvenTriggerPoints          = 30;                 // Break Even Trigger (Points)

input group "NRTR Channel Settings"
input int NRTR_ATR_Period                    = 40;                 // NRTR ATR Period
input double NRTR_Multiplier                 = 2.0;                // NRTR Multiplier
input bool NRTR_Show_Price_Label             = true;               // Show Price Label

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
double CurrentStopLossPoints = DefaultStopLossPoints;              //--- Initialize current SL points
double CurrentTakeProfitPoints = DefaultTakeProfitPoints;          //--- Initialize current TP points
double CurrentTrailingStopPoints = TrailingStopPoints;             //--- Initialize current trailing stop points
double CurrentTrailingStepPoints = TrailingStepPoints;             //--- Initialize current trailing step points
bool PrintLog = false;                                             //--- Set print log flag
datetime LastTrailingTime = 0;                                     //--- Initialize last trailing time
ENUM_MODE_SL StopLossMode = SL_FIXED;                              //--- Set SL mode
ENUM_MODE_TP TakeProfitMode = TP_FIXED;                            //--- Set TP mode

double AtrMultiplierForStopLoss = 2;                               //--- Set ATR multiplier for SL
double AtrMultiplierForTakeProfit = 3;                             //--- Set ATR multiplier for TP

CTrade TradeObject;                                                //--- Declare trade object
CSymbolInfo SymbolInfo;                                            //--- Declare symbol info
int TrendIndicatorHandle = -1;                                     //--- Initialize trend handle
double CurrentTrendValue, PreviousTrendValue;                      //--- Declare trend values

double CurrentTrendDirection, PreviousTrendDirection;              //--- Declare trend directions
double CurrentUpSupport, PreviousUpSupport;                        //--- Declare up supports
double CurrentDownSupport, PreviousDownSupport;                    //--- Declare down supports
double CurrentNrtrAtr, PreviousNrtrAtr;                            //--- Declare NRTR ATR values

Wir beginnen die Implementierung, indem wir wesentliche MQL5-Bibliotheken mit „#include <Trade\Trade.mqh>“, „#include <Trade\PositionInfo.mqh>“ und „#include <Trade\SymbolInfo.mqh>“ einbinden, um auf Klassen für die Handhabung von Handelsoperationen, Positionsinformationen und Symboldetails zuzugreifen. Anschließend definieren wir mehrere Enumerationen, um die Benutzeroptionen systematisch zu kategorisieren. Die Enumeration „ENUM_RISK_BASE“ bietet Auswahlmöglichkeiten für Risikoberechnungen auf der Grundlage von Kontoeigenkapital, Kontostand oder freier Marge. Wir erstellen „ENUM_RISK_DEFAULT_SIZE“, um zwischen fester oder automatischer Lotgrößenberechnung zu wählen. Für Stop-Loss und Take-Profit richten wir „ENUM_MODE_SL“ und „ENUM_MODE_TP“ ein, mit Optionen für feste oder automatische Modi. Zusätzlich richten wir „ENUM_TRAILING_TYPE“ ein, um Trailing-Stop-Varianten zu definieren: keine, punktbasiert oder an Unterstützungsniveaus ausgerichtet.

Als Nächstes werden die Benutzereingaben in Gruppen eingeteilt, um die Benutzerfreundlichkeit zu verbessern. Im Bereich Risikomanagement sind „RiskDefaultSize“ für die Standardgröße, „DefaultLotSize“ als fester Volumenwert, „RiskBase“ für die Berechnungsgrundlage, „MaxRiskPerTrade“ als prozentuale Grenze, minimale und maximale Lot-Größen (definiert als Globals), „MaxPositions“ zur Begrenzung offener Trades, „HedgeMode“, um Hedging zu ermöglichen, und „CloseOnReversalSignal“ für den Ausstieg bei entgegengesetzten Signalen. Für Stop-Loss- und Take-Profit-Konfigurationen fügen wir „DefaultStopLossPoints“ und „DefaultTakeProfitPoints“ in Punkten hinzu, sowie boolesche Parameter für das Schließen bei Erreichen dieser Niveaus.

In zusätzlichen Einstellungen bieten wir eine „MagicNumber“ zur eindeutigen Identifizierung des Handels. Wir integrieren Trailing-Stop-Parameter wie „TrailingType“, Frequenzen in Sekunden für Trailing-Updates und Signalprüfungen, „TrailingStopPoints“, „TrailingStepPoints“, „BreakEvenPoints“ und „BreakEvenTriggerPoints“. Die NRTR-Kanalgruppe verfügt über „NRTR_ATR_Period“, „NRTR_Multiplier“ und „NRTR_Show_Price_Label“, um den Indikator direkt aus unserem Programm heraus anzupassen.

Schließlich deklarieren wir globale Variablen, um Schlüsselwerte wie aktuelle Punkte für Stops und Trailing, ein Logging-Flag, den letzten Trailing-Zeitstempel, Modi für Stop-Loss und Take-Profit, ATR-Multiplikatoren für dynamische Levels und Objektinstanzen von CTrade und CSymbolInfo zu initialisieren. Wir haben den Trendindikator-Handle zunächst auf -1 gesetzt, zusammen mit Doppelwerten für die Speicherung von aktuellen und früheren Trendwerten, Richtungen, Unterstützungsniveaus und NRTR-ATR-Werten. Als Nächstes werden wir den Indikator initialisieren. Wir strukturieren den Code konsequent in Funktionen, um ihn übersichtlich und modular zu halten.

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit() {
   if (!PreInitChecks()) return INIT_FAILED;                     //--- Perform pre-init checks
   SymbolInfo.Name(Symbol());                                    //--- Set symbol name
   if (!InitIndicatorHandles()) return INIT_FAILED;              //--- Initialize indicator handles
   InitTradeObject();                                            //--- Initialize trade object
   return INIT_SUCCEEDED;                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Perform pre-init checks                                          |
//+------------------------------------------------------------------+
bool PreInitChecks() {
   if (MaxLotSize < MinLotSize) {                                //--- Check lot sizes
      Print("MaxLotSize cannot be less than MinLotSize");        //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

//+------------------------------------------------------------------+
//| Initialize indicator handles                                     |
//+------------------------------------------------------------------+
bool InitIndicatorHandles() {
   TrendIndicatorHandle = iCustom(_Symbol, _Period, "Free Indicators\\NRTR Channel", NRTR_ATR_Period, NRTR_Multiplier, NRTR_Show_Price_Label); //--- Create NRTR handle
   if (TrendIndicatorHandle == INVALID_HANDLE) {                 //--- Check handle
      PrintFormat("Error creating NRTR handle - %d", GetLastError()); //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

//+------------------------------------------------------------------+
//| Initialize trade object                                          |
//+------------------------------------------------------------------+
void InitTradeObject() {
   TradeObject.SetExpertMagicNumber(MagicNumber);                //--- Set magic number
}

Im OnInit-Ereignishandler rufen wir zunächst „PreInitChecks“ auf, um die Anfangsbedingungen zu überprüfen, und geben INIT_FAILED zurück, wenn dies nicht gelingt, um sicherzustellen, dass das Programm nicht mit ungültigen Einstellungen fortfährt. Anschließend wird der Symbolname im Objekt „SymbolInfo“ unter Verwendung des aktuellen Symbols festgelegt. Als Nächstes rufen wir „InitIndicatorHandles“ auf, um den NRTR-Kanalindikator zu laden, und geben im Fehlerfall „INIT_FAILED“ zurück. Wir initialisieren das Handelsobjekt, indem wir „InitTradeObject“ aufrufen, und geben schließlich INIT_SUCCEEDED zurück, um die erfolgreiche Einrichtung zu bestätigen.

Die Funktion „PreInitChecks“ prüft, ob „MaxLotSize“ nicht kleiner als „MinLotSize“ ist, gibt eine Fehlermeldung aus und gibt im Falle eines Fehlers „false“ zurück, andernfalls „true“, damit die Initialisierung fortgesetzt werden kann. Innerhalb von „InitIndicatorHandles“ erstellen wir das „TrendIndicatorHandle“ mithilfe von iCustom mit dem Symbol, der Periode, dem Indikatorpfad und den Parametern wie „NRTR_ATR_Period“, „NRTR_Multiplier“ und „NRTR_Show_Price_Label“. Wenn das Handle INVALID_HANDLE ist, wird mit GetLastError ein Fehler ausgegeben und false zurückgegeben; andernfalls wird true zurückgegeben. Die Funktion „InitTradeObject“ konfiguriert das „TradeObject“, indem sie dessen MagicNumber zur Identifizierung programmspezifischer Trades auf „MagicNumber“ setzt. Es ist sehr wichtig, dass Sie das Programm ausführen, um sicherzustellen, dass der Indikator erfolgreich geladen wird, da er die Hauptquelle für unsere Signale ist.

INITIALES LADEN DES INDIKATORS IN DAS PROGRAMM

In unserem Fall wird der Indikator erfolgreich geladen. Nun können wir mit dem Auslesen der Indikatorpuffer fortfahren und unsere Signalerzeugungslogik definieren. Wir beginnen mit der Logik für den Datenabruf.

//+------------------------------------------------------------------+
//| Fetch indicator data                                             |
//+------------------------------------------------------------------+
bool FetchIndicatorData() {
   double atrBuffer[];                                           //--- Declare ATR buffer as dynamic
   double trendBuffer[];                                         //--- Declare trend buffer as dynamic
   double upSupportBuffer[];                                     //--- Declare up support buffer as dynamic
   double downSupportBuffer[];                                   //--- Declare down support buffer as dynamic
   ArrayResize(atrBuffer, 2);                                    //--- Resize to 2
   ArrayResize(trendBuffer, 2);                                  //--- Resize to 2
   ArrayResize(upSupportBuffer, 2);                              //--- Resize to 2
   ArrayResize(downSupportBuffer, 2);                            //--- Resize to 2
   ArraySetAsSeries(atrBuffer, true);                            //--- Set as series
   ArraySetAsSeries(trendBuffer, true);                          //--- Set as series
   ArraySetAsSeries(upSupportBuffer, true);                      //--- Set as series
   ArraySetAsSeries(downSupportBuffer, true);                    //--- Set as series
   int copyCount;                                                //--- Declare copy count
   bool dataReady = false;                                       //--- Set data ready flag
   int maxAttempts = 5;                                          //--- Set max attempts
   int delayMs = 200;                                            //--- Set delay ms
   int attempt = 0;                                              //--- Initialize attempt
   while (!dataReady && attempt < maxAttempts) {                 //--- Loop until ready
      dataReady = true;                                          //--- Assume ready
      copyCount = CopyBuffer(TrendIndicatorHandle, 5, 1, 2, atrBuffer); //--- Copy ATR
      if (copyCount < 2 || atrBuffer[0] == EMPTY_VALUE) {        //--- Check ATR copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set ATR values
         CurrentNrtrAtr = atrBuffer[0];                          //--- Set current ATR (recent)
         PreviousNrtrAtr = atrBuffer[1];                         //--- Set previous ATR (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 4, 1, 2, trendBuffer); //--- Copy trend
      if (copyCount < 2) {                                       //--- Check trend copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set trend directions
         CurrentTrendDirection = trendBuffer[0];                 //--- Set current direction (recent)
         PreviousTrendDirection = trendBuffer[1];                //--- Set previous direction (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 1, 1, 2, upSupportBuffer); //--- Copy up support
      if (copyCount < 2) {                                       //--- Check up copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set up supports
         CurrentUpSupport = upSupportBuffer[0];                  //--- Set current up (recent)
         PreviousUpSupport = upSupportBuffer[1];                 //--- Set previous up (older)
      }
      copyCount = CopyBuffer(TrendIndicatorHandle, 2, 1, 2, downSupportBuffer); //--- Copy down support
      if (copyCount < 2) {                                       //--- Check down copy
         dataReady = false;                                      //--- Set not ready
      } else {                                                   //--- Set down supports
         CurrentDownSupport = downSupportBuffer[0];              //--- Set current down (recent)
         PreviousDownSupport = downSupportBuffer[1];             //--- Set previous down (older)
      }
      if (dataReady) {                                           //--- Check ready
         CurrentTrendValue = (CurrentTrendDirection > 0) ? CurrentUpSupport : ((CurrentTrendDirection < 0) ? CurrentDownSupport : EMPTY_VALUE); //--- Set current trend value
         PreviousTrendValue = (PreviousTrendDirection > 0) ? PreviousUpSupport : ((PreviousTrendDirection < 0) ? PreviousDownSupport : EMPTY_VALUE); //--- Set previous trend value
         if (CurrentTrendValue == EMPTY_VALUE || PreviousTrendValue == EMPTY_VALUE) dataReady = false; //--- Check values
      }
      attempt++;                                                 //--- Increment attempt
      Sleep(delayMs);                                            //--- Delay
   }
   if (!dataReady) {                                             //--- Check final ready
      Print("Failed to fetch indicator data");                   //--- Print error
      return false;                                              //--- Return failure
   }
   return true;                                                  //--- Return success
}

Wir definieren die Funktion „FetchIndicatorData“, um Werte aus dem Puffer des NRTR-Kanalindikators zuverlässig abzurufen und zu speichern. Zunächst werden dynamische Arrays für „atrBuffer“, „trendBuffer“, „upSupportBuffer“ und „downSupportBuffer“ deklariert, deren Größe mit ArrayResize so angepasst wird, dass sie jeweils zwei Elemente enthalten, und die mit ArraySetAsSeries als Serien-Arrays festgelegt werden, sodass die neuesten Daten bei Index 0 liegen. Wir initialisieren Variablen für die Anzahl der Kopien, ein Datenbereitschaftsflag, eine maximale Anzahl von Wiederholungsversuchen von 5, eine Verzögerung von 200 Millisekunden und einen Versuchszähler, der bei 0 beginnt. In einer while-Schleife, die fortgesetzt wird, bis die Daten bereit sind oder die Versuche erschöpft sind, gehen wir von der Bereitschaft aus und verwenden dann CopyBuffer, um die Daten der letzten beiden Bars aus spezifischen Indikatorpuffern zu holen: Puffer 5 für ATR-Werte, Puffer 4 für Trendrichtungen, Puffer 1 für Aufwärtsunterstützungen und Puffer 2 für Abwärtsunterstützungen.

Wenn das Kopieren fehlschlägt (weniger als 2 Elemente) oder das letzte ATR EMPTY_VALUE ist, werden die Daten als nicht bereit markiert. Andernfalls weisen wir den globalen Variablen wie "CurrentNrtrAtr", "PreviousNrtrAtr", "CurrentTrendDirection", "PreviousTrendDirection", "CurrentUpSupport", "PreviousUpSupport", "CurrentDownSupport" und "PreviousDownSupport" aktuelle (neueste, Index 0) und vorherige (ältere, Index 1) Werte zu. Sobald alle Puffer erfolgreich kopiert wurden, berechnen wir „CurrentTrendValue“ als „CurrentUpSupport“, wenn die Richtung positiv ist, „CurrentDownSupport“, wenn sie negativ ist, oder „EMPTY_VALUE“, wenn sie nicht positiv ist, und ebenso für „PreviousTrendValue“. Wenn einer der beiden Trendwerte „EMPTY_VALUE“ ist, setzen wir die Bereitschaft zurück.

Wir erhöhen den Versuchszähler und pausieren mit Sleep, bevor wir es erneut versuchen. Wenn die Daten auch nach erneuten Versuchen nicht verfügbar sind, wird eine Fehlermeldung ausgegeben und false zurückgegeben; andernfalls wird true zurückgegeben, um den erfolgreichen Datenabruf zu bestätigen. Nachdem wir die Daten abgerufen haben, können wir sie für die Signalerzeugung auswerten. Zunächst werden wir einige Hilfsfunktionen wie folgt definieren.

//+------------------------------------------------------------------+
//| Count open positions                                             |
//+------------------------------------------------------------------+
int CountOpenPositions() {
   int count = 0;                                                //--- Initialize count
   int totalPositions = PositionsTotal();                        //--- Get total positions
   for (int i = 0; i < totalPositions; i++) {                    //--- Loop positions
      if (PositionGetSymbol(i) != Symbol()) continue;            //--- Skip wrong symbol
      if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic
      count++;                                                   //--- Increment count
   }
   return count;                                                 //--- Return count
}

//+------------------------------------------------------------------+
//| Count open positions by type                                     |
//+------------------------------------------------------------------+
int CountOpenPositionsByType(ENUM_POSITION_TYPE positionType) {
   int count = 0;                                                //--- Initialize count
   int totalPositions = PositionsTotal();                        //--- Get total positions
   for (int i = 0; i < totalPositions; i++) {                    //--- Loop positions
      if (PositionGetSymbol(i) != Symbol()) continue;            //--- Skip wrong symbol
      if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic
      if (PositionGetInteger(POSITION_TYPE) == positionType) count++; //--- Increment if match
   }
   return count;                                                 //--- Return count
}

//+------------------------------------------------------------------+
//| Open buy position                                                |
//+------------------------------------------------------------------+
bool OpenBuyPosition(string positionType) {
   double askPrice = SymbolInfoDouble(Symbol(), SYMBOL_ASK);               //--- Get ask price
   double stopLossPrice = CalculateStopLoss(ORDER_TYPE_BUY, askPrice);     //--- Calculate SL
   double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_BUY, askPrice); //--- Calculate TP
   double positionSize = CalculatePositionSize(stopLossPrice, askPrice);   //--- Calculate size
   string orderComment = positionType + " Buy";                            //--- Set comment
   if (!TradeObject.Buy(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open buy
      PrintFormat("Failed to open BUY: %d", TradeObject.ResultRetcode());  //--- Print error
      return false;                                                        //--- Return failure
   }
   PrintFormat("%s Buy Position Opened Successfully", positionType);       //--- Print success
   return true;                                                            //--- Return success
}

//+------------------------------------------------------------------+
//| Open sell position                                               |
//+------------------------------------------------------------------+
bool OpenSellPosition(string positionType) {
   double bidPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID);               //--- Get bid price
   double stopLossPrice = CalculateStopLoss(ORDER_TYPE_SELL, bidPrice);    //--- Calculate SL
   double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_SELL, bidPrice); //--- Calculate TP
   double positionSize = CalculatePositionSize(stopLossPrice, bidPrice);   //--- Calculate size
   string orderComment = positionType + " Sell";                           //--- Set comment
   if (!TradeObject.Sell(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open sell
      PrintFormat("Failed to open SELL: %d", TradeObject.ResultRetcode()); //--- Print error
      return false;                                                        //--- Return failure
   }
   PrintFormat("%s Sell Position Opened Successfully", positionType);      //--- Print success
   return true;                                                            //--- Return success
}

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void CloseAllBuyPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue; //--- Skip non-buy
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Buy Positions Closed");                                      //--- Print closed
}

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void CloseAllSellPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue; //--- Skip non-sell
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Sell Positions Closed");                                     //--- Print closed
}

//+------------------------------------------------------------------+
//| Close all positions                                              |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   int total = PositionsTotal();                                           //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                                  //--- Loop backward
      if (PositionGetSymbol(i) != Symbol()) continue;                      //--- Skip wrong symbol
      if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;     //--- Skip wrong magic
      TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET));      //--- Close position
   }
   Print("All Positions Closed");                                          //--- Print closed
}

//+------------------------------------------------------------------+
//| Calculate stop loss                                              |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE orderType, double entryPrice) {
   double stopLossPrice = 0;                                               //--- Initialize SL price
   if (StopLossMode == SL_FIXED) {                                         //--- Check fixed mode
      if (DefaultStopLossPoints == 0) return 0;                            //--- Return zero if none
      double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT);        //--- Get point value
      if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - DefaultStopLossPoints * pointValue; //--- Set buy SL
      if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + DefaultStopLossPoints * pointValue; //--- Set sell SL
   } else {                                                                //--- Handle auto mode
      if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set buy SL
      if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set sell SL
   }
   return NormalizeDouble(stopLossPrice, _Digits);                         //--- Normalize SL
}

//+------------------------------------------------------------------+
//| Calculate take profit                                            |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE orderType, double entryPrice) {
   double takeProfitPrice = 0;                                             //--- Initialize TP price
   if (TakeProfitMode == TP_FIXED) {                                       //--- Check fixed mode
      if (DefaultTakeProfitPoints == 0) return 0;                          //--- Return zero if none
      double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT);        //--- Get point value
      if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + DefaultTakeProfitPoints * pointValue; //--- Set buy TP
      if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - DefaultTakeProfitPoints * pointValue; //--- Set sell TP
   } else {                                                                //--- Handle auto mode
      if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set buy TP
      if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set sell TP
   }
   return NormalizeDouble(takeProfitPrice, _Digits);                       //--- Normalize TP
}

//+------------------------------------------------------------------+
//| Calculate position size                                          |
//+------------------------------------------------------------------+
double CalculatePositionSize(double stopLossPrice, double entryPrice) {
   double size = DefaultLotSize;                                          //--- Set default size
   if (RiskDefaultSize == RISK_DEFAULT_AUTO) {                            //--- Check auto risk
      if (stopLossPrice == 0) stopLossPrice = 200;                        //--- Set default SL if zero
      double riskBaseAmount = 0;                                          //--- Initialize base amount
      double tickValue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE); //--- Get tick value
      if (RiskBase == RISK_BASE_BALANCE) riskBaseAmount = AccountInfoDouble(ACCOUNT_BALANCE); //--- Set balance base
      else if (RiskBase == RISK_BASE_EQUITY) riskBaseAmount = AccountInfoDouble(ACCOUNT_EQUITY); //--- Set equity base
      else if (RiskBase == RISK_BASE_FREEMARGIN) riskBaseAmount = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Set free margin base
      double stopLossDistancePoints = MathAbs(entryPrice - stopLossPrice) / SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Compute distance
      size = (riskBaseAmount * MaxRiskPerTrade / 100) / (stopLossDistancePoints * tickValue); //--- Compute size
   }
   double lotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);       //--- Get lot step
   double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);         //--- Get max lot
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);         //--- Get min lot
   size = MathFloor(size / lotStep) * lotStep;                            //--- Step size
   if (size > MaxLotSize) size = MaxLotSize;                              //--- Clamp max size
   if (size > maxLot) size = maxLot;                                      //--- Clamp symbol max
   if (size < MinLotSize || size < minLot) size = 0;                      //--- Clamp min size
   return size;                                                           //--- Return size
}

Für die Hilfsfunktionen erstellen wir die Funktion „CountOpenPositions“, um alle offenen Positionen zu zählen, die dem aktuellen Symbol und der magischen Zahl entsprechen. Darin initialisieren wir einen Zähler, rufen die Gesamtpositionen mit PositionsTotal ab und führen eine Schleife von 0 bis zu dieser Summe durch, wobei wir PositionGetSymbol verwenden, um nicht übereinstimmende Symbole zu überspringen, und PositionGetInteger für POSITION_MAGIC, um nach „MagicNumber“ zu filtern, falls diese eingestellt ist, und den Zähler für gültige Positionen zu erhöhen, bevor wir ihn zurückgeben. In ähnlicher Weise zählt die Funktion „CountOpenPositionsByType“ Positionen eines bestimmten Typs, wie z. B. Kauf oder Verkauf, und folgt dabei der gleichen Schleifenstruktur, fügt aber eine Prüfung mit „PositionGetInteger“ für „POSITION_TYPE“ hinzu, um mit der Eingabe „positionType“ übereinzustimmen.

Für die Eröffnung von Trades definieren wir „OpenBuyPosition“, die die Zeichenkette „positionType“ zur Kommentierung benötigt. Wir holen uns den Ask über SymbolInfoDouble mit „SYMBOL_ASK“, berechnen Stop-Loss und Take-Profit mit speziellen Funktionen, bestimmen die Positionsgröße und bilden einen Kommentar als „positionType + " Buy'". Wir versuchen, den Kauf mit „TradeObject.Buy“ zu eröffnen, wobei wir den Fehler mit dem Retcode ausdrucken, wenn er nicht erfolgreich war, und ansonsten das Ergebnis zurückgeben. Die „OpenSellPosition“ spiegelt dies für Verkäufe wider, wobei der Bid aus SYMBOL_BID verwendet wird, die Stopps entsprechend berechnet werden und „TradeObject.Sell“ mit einem „Sell“-Kommentar ausgeführt wird.

Um Schließungen zu verwalten, führt „CloseAllBuyPositions“ eine Rückwärtsschleife von „PositionsTotal“ minus 1 bis 0 für eine sichere Löschung durch, überspringt nicht übereinstimmende Symbole, Nicht-Kauf-Typen über „POSITION_TYPE_BUY“ und falsche Magic Number, schließt gültige mit „TradeObject.PositionClose“ auf dem Ticket von „POSITION_TICKET“ und druckt eine Bestätigung. „CloseAllSellPositions“ macht das Gleiche für Verkäufe und prüft POSITION_TYPE_SELL. „CloseAllPositions“ schließt alles, was mit Symbol und Magic Number übereinstimmt, ohne Typfilterung, und gibt das Ergebnis aus.

In „CalculateStopLoss“ berechnen wir den Preis auf der Grundlage der Auftragsart und der Eingabe. Für den festen Modus im „StopLossMode“, wenn die Punkte null sind, geben wir null zurück; andernfalls erhalten wir den Punktwert und subtrahieren oder addieren Punkte mal Punkt für Kauf oder Verkauf. Im Automodus erfolgt die Anpassung durch Subtraktion oder Addition von „PreviousNrtrAtr“ mal „AtrMultiplierForStopLoss“ und Normalisierung auf Ziffern. „CalculateTakeProfit“ folgt diesem Beispiel und verwendet „TakeProfitMode“ und „AtrMultiplierForTakeProfit“ für automatische Anpassungen. „CalculatePositionSize“ schließlich beginnt mit einem Standard-Lot, setzt aber bei automatischem Risiko einen Standardabstand, wenn der Stop-Loss gleich null ist, bestimmt den Risikobasisbetrag, erhält den Tick-Wert, berechnet den Abstand in Punkten und berechnet die Größe als Risikobetrag über (Abstand mal Tick-Wert). Anschließend runden wir den Wert mit MathFloor auf die nächste Lot-Stufe auf, begrenzen ihn auf den Bereich zwischen dem minimalen und maximalen Lot, einschließlich der Symbolgrenzen, und geben die Größe zurück. Wir können diese Funktionen nun bequem nutzen, um Signale zu prüfen und Positionen zu eröffnen.

//+------------------------------------------------------------------+
//| Check for entry signals                                          |
//+------------------------------------------------------------------+
void CheckForEntrySignals() {
   bool buySignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE);      //--- Check buy signal (start of long support)
   bool sellSignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Check sell signal (start of short support)
   if (buySignal) {                                                                               //--- Handle buy signal
      string currUpStr = DoubleToString(CurrentUpSupport, _Digits);
      string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits);
      PrintFormat("Buy Signal Detected: Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print buy signal
      if ((CountOpenPositionsByType(POSITION_TYPE_BUY) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) {     //--- Check open buys
         OpenBuyPosition("Initial Signal");                                                       //--- Open buy
      } else {                                                                                    //--- Handle rejected
         Print("Buy Trade Rejected: Maximum positions reached or hedge mode restricts");          //--- Print rejected
      }
   }
   if (sellSignal) {                                                                              //--- Handle sell signal
      string currDownStr = DoubleToString(CurrentDownSupport, _Digits);
      string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits);
      PrintFormat("Sell Signal Detected: Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print sell signal
      if ((CountOpenPositionsByType(POSITION_TYPE_SELL) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) {          //--- Check open sells
         OpenSellPosition("Initial Signal");                                                      //--- Open sell
      } else {                                                                                    //--- Handle rejected
         Print("Sell Trade Rejected: Maximum positions reached or hedge mode restricts");         //--- Print rejected
      }
   }
}

Hier implementieren wir die Funktion „CheckForEntrySignals“, um potenzielle Einstiege auf der Grundlage der NRTR-Kanalübergänge zu erkennen und entsprechend zu handeln. In der Funktion wird ein Kaufsignal als wahr definiert, wenn „CurrentUpSupport“ nicht EMPTY_VALUE ist, „PreviousUpSupport“ aber schon, was auf den Beginn eines langen Unterstützungsniveaus hinweist. In ähnlicher Weise wird ein Verkaufssignal ausgelöst, wenn „CurrentDownSupport“ gültig ist, während „PreviousDownSupport“ leer ist, was den Beginn einer kurzen Unterstützung markiert.

Wird ein Kaufsignal erkannt, werden die Unterstützungswerte in Zeichenketten umgewandelt (wobei „EMPTY_VALUE“ als „EMPTY“ behandelt wird), es wird eine formatierte Meldung über die Erkennung aus Puffer 1 gedruckt, und die Bedingungen werden überprüft: Wenn die Absicherung aktiviert ist und keine Käufe offen sind, oder wenn die Gesamtpositionen unter „MaxPositions“ liegen, rufen wir „OpenBuyPosition“ mit „Initial Signal“ auf; andernfalls drucken wir eine Ablehnungsmeldung. Bei einem Verkaufssignal gehen wir nach demselben Muster vor: Formatierung und Druck der Details aus Puffer 2, Überprüfung, ob bei aktiviertem Hedging noch keine Short-Position geöffnet ist oder ob das Positionslimit noch nicht erreicht wurde. Wir können diese Funktion nun im Tick-Event-Handler aufrufen, um die Ergebnisse zu erhalten.

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick() {
   ProcessEachTick();                                            //--- Process tick
}

//+------------------------------------------------------------------+
//| Process each tick                                                |
//+------------------------------------------------------------------+
void ProcessEachTick() {
   if (!FetchIndicatorData()) return;                            //--- Fetch indicator data

   static datetime lastBarTime = WRONG_VALUE;                    //--- Initialize last bar time
   datetime currentBarTime = iTime(Symbol(), Period(), 0);       //--- Get current bar time
   static int newBarTicks = 0;                                   //--- Initialize new bar ticks
   if (currentBarTime == lastBarTime) {                          //--- Check same bar
      newBarTicks++;                                             //--- Increment ticks
      if (newBarTicks > 1) return;                               //--- Skip if more than 1
   } else {                                                      //--- New bar
      newBarTicks = 0;                                           //--- Reset ticks
      lastBarTime = currentBarTime;                              //--- Update last time
   }
   CheckForEntrySignals();                                       //--- Check entry signals
}

Wir behandeln eingehende Ticks im OnTick-Ereignishandler, indem wir einfach „ProcessEachTick“ aufrufen, um die Logik für jede Preisaktualisierung zu zentralisieren. In „ProcessEachTick“ versuchen wir zunächst, mit „FetchIndicatorData“ frische Indikatordaten abzurufen, und brechen vorzeitig ab, wenn dies nicht gelingt, um sicherzustellen, dass die Entscheidungen auf gültigen Informationen beruhen. Um die Verarbeitung zu optimieren und sich auf neue Bars zu konzentrieren, verwenden wir eine statische „lastBarTime“, die auf WRONG_VALUE initialisiert ist, und holen die Eröffnungszeit der aktuellen Bar über iTime für das Symbol und den Zeitraum. Ein statisches „newBarTicks“ beginnt bei 0, um Ticks innerhalb derselben Bar zu verfolgen.

Wenn die aktuelle Zeit der Bar mit „lastBarTime“ übereinstimmt, was auf dieselbe Bar hindeutet, erhöhen wir „newBarTicks“ und kehren ohne weitere Maßnahmen zurück, wenn der Wert 1 übersteigt, um unnötige Überprüfungen bei nachfolgenden Ticks zu vermeiden. Andernfalls setzen wir bei einer neuen Bar „newBarTicks“ auf 0 zurück, aktualisieren „lastBarTime“ und fahren fort. Anschließend rufen wir „CheckForEntrySignals“ auf, um auf der Grundlage der aktualisierten Daten neue Trade-Einstiege zu bewerten und möglicherweise auszuführen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

SIGNALERZEUGUNG UND -HANDEL

Aus dem Bild geht hervor, dass Signale erkannt und Orders unmittelbar bei Signalerzeugung ausgeführt werden. Was jetzt noch übrig bleibt, ist die Handhabung der Exit-Strategie, bei der wir bei entgegengesetzten Signalen aussteigen und virtuelle Positionsschließungen behandeln.

//+------------------------------------------------------------------+
//| Check for exit signals                                           |
//+------------------------------------------------------------------+
void CheckForExitSignals() {
   bool exitBuySignal = false;                                   //--- Initialize buy exit
   bool exitSellSignal = false;                                  //--- Initialize sell exit
   if (CloseOnReversalSignal) {                                  //--- Check reversal close
      exitBuySignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Set buy exit (start of short)
      exitSellSignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE); //--- Set sell exit (start of long)
   }
   if (exitBuySignal) {                                          //--- Handle buy exit
      string currDownStr = DoubleToString(CurrentDownSupport, _Digits);
      string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits);
      PrintFormat("Exit Buy Signal Detected (Reversal): Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print buy exit
      CloseAllBuyPositions();                                    //--- Close buys
   }
   if (exitSellSignal) {                                         //--- Handle sell exit
      string currUpStr = DoubleToString(CurrentUpSupport, _Digits);
      string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits);
      PrintFormat("Exit Sell Signal Detected (Reversal): Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print sell exit
      CloseAllSellPositions();                                   //--- Close sells
   }
}

//+------------------------------------------------------------------+
//| Close on virtual take profit                                     |
//+------------------------------------------------------------------+
void CloseOnVirtualTakeProfit(ulong ticket) {
   if (!PositionSelectByTicket(ticket)) return;                  //--- Select position
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double virtualTakeProfit = 0;                                 //--- Initialize virtual TP
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);          //--- Get ask
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);          //--- Get bid
   if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
      virtualTakeProfit = entryPrice + DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy TP
      if (virtualTakeProfit <= bid && CloseOnTakeProfitHit) {    //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed
      }
   } else {                                                      //--- Handle sell
      virtualTakeProfit = entryPrice - DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell TP
      if (virtualTakeProfit >= ask && CloseOnTakeProfitHit) {    //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed
      }
   }
}

//+------------------------------------------------------------------+
//| Close on virtual stop loss                                       |
//+------------------------------------------------------------------+
void CloseOnVirtualStopLoss(ulong ticket) {
   if (!PositionSelectByTicket(ticket)) return;                  //--- Select position
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double virtualStopLoss = 0;                                   //--- Initialize virtual SL
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);          //--- Get ask
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);          //--- Get bid
   if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
      virtualStopLoss = entryPrice - DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy SL
      if (virtualStopLoss >= ask && CloseOnStopLossHit) {        //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed
      }
   } else {                                                      //--- Handle sell
      virtualStopLoss = entryPrice + DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell SL
      if (virtualStopLoss <= bid && CloseOnStopLossHit) {        //--- Check hit
         TradeObject.PositionClose(ticket);                      //--- Close position
         PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed
      }
   }
}

//+------------------------------------------------------------------+
//| Handle virtual closures                                          |
//+------------------------------------------------------------------+
void HandleVirtualClosures() {
   int total = PositionsTotal();                                 //--- Get total positions
   for (int i = total - 1; i >= 0; i--) {                        //--- Loop backward
      if (PositionGetSymbol(i) == "") continue;                  //--- Skip invalid
      ulong ticket = PositionGetInteger(POSITION_TICKET);        //--- Get ticket
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong symbol/magic
      if (CloseOnStopLossHit) CloseOnVirtualStopLoss(ticket);    //--- Check virtual SL
      if (CloseOnTakeProfitHit) CloseOnVirtualTakeProfit(ticket); //--- Check virtual TP
   }
}

Zunächst entwickeln wir die Funktion „CheckForExitSignals“, um auf Bedingungen zu achten, die das Schließen bestehender Positionen rechtfertigen. Hier initialisieren wir die booleschen Flaggen für Kauf- und Verkaufsausgänge auf false. Wenn „CloseOnReversalSignal“ aktiviert ist, setzen wir „exitBuySignal“ auf true, wenn wir den Beginn einer Short-Unterstützung erkennen (aktuelle Abwärtsunterstützung gültig, vorherige leer), und „exitSellSignal“ für das Einsetzen einer Long-Unterstützung. Wenn ein Ausstiegssignal für Long-Positionen auftritt, formatieren wir Strings für die Down-Support-Werte (wobei EMPTY_VALUE als „EMPTY“ behandelt wird), drucken eine ausführliche Meldung aus Puffer 2 und rufen „CloseAllBuyPositions“ auf, um alle Käufe zu beenden. In ähnlicher Weise wird bei einem Exit-Verkaufssignal aus Puffer 1 gedruckt und die Funktion „CloseAllSellPositions“ aufgerufen.

Die Funktion „CloseOnVirtualTakeProfit“ verwaltet programmatische Take-Profit-Schließungen für ein bestimmtes Ticket. Wir wählen die Position mit PositionSelectByTicket aus, wobei wir vorzeitig aussteigen, wenn sie nicht erfolgreich ist, und rufen dann den Einstiegskurs ab. Für Käufe berechnen wir „virtualTakeProfit“, indem wir „DefaultTakeProfitPoints“ mit dem Punktwert zum Eröffnungskurs addieren; für Verkäufe subtrahieren wir ihn. Wir holen den Bid- und Ask-Kurs, und wenn das virtuelle Niveau erreicht ist (TP <= Bid für Kauf, >= Ask für Verkauf) und „CloseOnTakeProfitHit“ wahr ist, schließen wir die Position über „TradeObject.PositionClose“ und drucken eine Bestätigung mit dem Ticket- und Trefferpreis.

Ebenso behandelt „CloseOnVirtualStopLoss“ virtuelle Stop-Losses. Nach der Auswahl der Position und dem Einstieg berechnen wir „virtualStopLoss“, indem wir Punkte für Käufe abziehen oder für Verkäufe hinzufügen. Unter Verwendung von Ask und Bid, wenn ein Treffer vorliegt (>= Ask für Buy SL, <= Bid für Sell) und „CloseOnStopLossHit“ aktiviert ist, schließen wir und drucken Details. In „HandleVirtualClosures“ wird eine Rückwärtsschleife über alle Positionen von „PositionsTotal“ minus 1 bis 0 zur sicheren Iteration durchgeführt. Wenn das Symbol ungültig ist, wird es übersprungen; wenn das Symbol oder die Magic Number nicht übereinstimmen, wird das Ticket geholt und fortgesetzt. Wenn „CloseOnStopLossHit“ gesetzt ist, rufen wir „CloseOnVirtualStopLoss“ auf; wenn „CloseOnTakeProfitHit“ gesetzt ist, rufen wir „CloseOnVirtualTakeProfit“ auf, um virtuelle Checks auf offene Trades anzuwenden. Wir müssen auch Trailing-Typen behandeln, wenn sie aktiviert sind, entweder durch Punkte oder durch die Unterstützungsebenen.

//+------------------------------------------------------------------+
//| Adjust to break even                                             |
//+------------------------------------------------------------------+
bool AdjustToBreakEven(ulong ticket) {
   if (TrailingType == TRAILING_NONE) return false;              //--- Check trailing type
   if (!PositionSelectByTicket(ticket)) return false;            //--- Select position
   MqlTradeRequest request = {};                                 //--- Declare request
   MqlTradeResult result = {};                                   //--- Declare result
   request.action = TRADE_ACTION_SLTP;                           //--- Set action SLTP
   request.position = ticket;                                    //--- Set position ticket
   long positionType = PositionGetInteger(POSITION_TYPE);        //--- Get type
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);   //--- Get entry price
   double currentStopLoss = PositionGetDouble(POSITION_SL);      //--- Get current SL
   double currentTakeProfit = PositionGetDouble(POSITION_TP);    //--- Get current TP
   string symbol = PositionGetString(POSITION_SYMBOL);           //--- Get symbol
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);        //--- Get point
   double currentPrice = (positionType == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID) : SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Get current price
   double currentProfitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - entryPrice) / point : (entryPrice - currentPrice) / point; //--- Compute profit points
   double newStopLoss = 0;                                       //--- Initialize new SL
   if (currentProfitPoints <= 0) return false;                   //--- Skip if no profit

   switch (TrailingType) {                                       //--- Switch trailing type
   case TRAILING_POINTS: {                                       //--- Handle points
      if (currentProfitPoints >= BreakEvenTriggerPoints) {       //--- Check BE trigger
         double bePoints = BreakEvenPoints;                      //--- Set BE points
         newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(entryPrice + (bePoints * point), _Digits) : NormalizeDouble(entryPrice - (bePoints * point), _Digits); //--- Compute new SL
         if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Breakeven SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      if (currentProfitPoints >= CurrentTrailingStopPoints) {       //--- Check trailing trigger
         double trailPoints = currentProfitPoints - CurrentTrailingStepPoints; //--- Compute trail points
         newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(currentPrice - (CurrentTrailingStopPoints * point), _Digits) : NormalizeDouble(currentPrice + (CurrentTrailingStopPoints * point), _Digits); //--- Compute new SL
         if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Trailing SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Current Profit = %.0f points)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      break;
   }

   case TRAILING_SUPPORT_LEVELS: {                                 //--- Handle support levels
      double currentSupport = (positionType == POSITION_TYPE_BUY) ? CurrentUpSupport : CurrentDownSupport; //--- Get current support
      double previousSupport = (positionType == POSITION_TYPE_BUY) ? PreviousUpSupport : PreviousDownSupport; //--- Get previous support
      if (currentSupport == EMPTY_VALUE) return false;             //--- Skip if invalid support
      if (currentProfitPoints >= BreakEvenTriggerPoints) {         //--- Check BE trigger
         newStopLoss = currentSupport;                             //--- Set new SL to support
         bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry
         if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("Breakeven SL Adjusted to Support Level for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted
                  return true;                                      //--- Return success
               }
            }
         }
      }
      if ((positionType == POSITION_TYPE_BUY && currentSupport > previousSupport) || (positionType == POSITION_TYPE_SELL && currentSupport < previousSupport)) { //--- Check support moved
         newStopLoss = currentSupport;                              //--- Set new SL to support
         bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry
         if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable
            if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different
               request.sl = newStopLoss;                            //--- Set SL
               request.tp = currentTakeProfit;                      //--- Set TP
               if (OrderSend(request, result)) {                    //--- Send order
                  PrintFormat("SL Trailed to Support Level for %s Position: Ticket %llu, New SL = %.5f (Support Moved from %.5f to %.5f)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, previousSupport, currentSupport); //--- Print trailed
                  return true;                                      //--- Return success
               }
            }
         }
      }
      break;
   }

   default:
      break;
   }
   return false;                                                    //--- Return no adjustment
}

//+------------------------------------------------------------------+
//| Adjust all stop losses                                           |
//+------------------------------------------------------------------+
void AdjustAllStopLosses() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) {                //--- Loop backward
      if (PositionGetSymbol(i) == "") continue;                     //--- Skip invalid
      ulong ticket = PositionGetInteger(POSITION_TICKET);           //--- Get ticket
      AdjustToBreakEven(ticket);                                    //--- Adjust to BE
   }
}

Hier richten wir die Funktion „AdjustToBreakEven“ ein, um Trailing- und Break-even-Anpassungen für eine bestimmte Position zu verwalten, die durch ihr Ticket identifiziert wird. Wenn der „TrailingType“ „TRAILING_NONE“ ist, oder wenn wir die Position nicht mit PositionSelectByTicket auswählen können, geben wir sofort false zurück. Wir bereiten die Strukturen MqlTradeRequest und MqlTradeResult vor, setzen die Request-Action auf TRADE_ACTION_SLTP, um Stop-Loss und Take-Profit zu ändern, und weisen das Positionsticket zu. Wir rufen den Positionstyp, den Einstiegskurs, den aktuellen Stop-Loss, den Take-Profit, das Symbol und den Punktwert ab. Der aktuelle Preis ist bei Long-Positionen der Bid-Kurs und bei Short-Positionen der Ask-Kurs, und wir berechnen „currentProfitPoints“ als die Differenz zum Einstieg, normalisiert nach Punkten. Wenn es keinen Gewinn gibt, wird der Vorgang übersprungen und false zurückgegeben.

Mit einem Schalter auf „TrailingType“ für „TRAILING_POINTS“ prüfen wir zunächst, ob der Gewinn den „BreakEvenTriggerPoints“ entspricht; wenn dies der Fall ist, berechnen wir einen neuen Stop-Loss beim Einstieg plus oder minus „BreakEvenPoints“ mal Punkt für Käufe oder Verkäufe, normalisiert. Wir prüfen, ob dies den aktuellen Stop verbessert (höher für Käufe, niedriger für Verkäufe) und setzen dann Stop-Loss und Take-Profit, senden mit OrderSend und drucken den Erfolg, wenn er akzeptiert wird, und geben true zurück. Wenn der Gewinn „CurrentTrailingStopPoints“ übersteigt, wird ein Trailing-Stop auf der Grundlage des aktuellen Kurses minus oder plus „CurrentTrailingStopPoints“ mal Punkt berechnet, die Verbesserung und die Differenz geprüft, die Änderung gesendet, bei Erfolg gedruckt und true zurückgegeben.

Für „TRAILING_SUPPORT_LEVELS“ werden die aktuelle und die vorherige Unterstützung auf der Grundlage des Typs ermittelt, wobei übersprungen wird, wenn die aktuelle Unterstützung leer ist. Wenn der Gewinn den Breakeven erreicht, setzen wir einen neuen Stopp auf die aktuelle Unterstützung, stellen sicher, dass er profitabel ist (am oder über dem Einstiegswert), prüfen die Verbesserung und senden und drucken bei Erfolg, wenn dies anders ist. Wenn sich die Unterstützung günstig entwickelt hat (höher für Käufe, niedriger für Verkäufe), aktualisieren wir auf die aktuelle Unterstützung, bestätigen profitabel und verbessert, senden die Anfrage, falls anders, drucken die Trail-Details und geben true zurück. Standardmäßig brechen wir ohne Aktion ab und geben false zurück, wenn keine Anpassung stattgefunden hat. Die Funktion „AdjustAllStopLosses“ iteriert rückwärts über alle Positionen von PositionsTotal minus 1 bis 0, überspringt dabei ungültige Symbole, ruft jedes Ticket auf und ruft „AdjustToBreakEven“ auf, um Anpassungen auf alle offenen Geschäfte anzuwenden. Wir können nun alle diese Funktionen in der Verwaltungsfunktion aufrufen, um alle Tick-Ereignisse zu behandeln. Die endgültige Funktion sieht wie folgt aus:

//+------------------------------------------------------------------+
//| Process each tick                                                |
//+------------------------------------------------------------------+
void ProcessEachTick() {
   if (!FetchIndicatorData()) return;                            //--- Fetch indicator data
   int positionCount = CountOpenPositions();                     //--- Count positions
   if (positionCount > 0) {                                      //--- Check positions exist
      CheckForExitSignals();                                     //--- Check exit signals
   }
   static datetime lastBarTime = WRONG_VALUE;                    //--- Initialize last bar time
   datetime currentBarTime = iTime(Symbol(), Period(), 0);       //--- Get current bar time
   static int newBarTicks = 0;                                   //--- Initialize new bar ticks
   if (currentBarTime == lastBarTime) {                          //--- Check same bar
      newBarTicks++;                                             //--- Increment ticks
      if (newBarTicks > 1) return;                               //--- Skip if more than 1
   } else {                                                      //--- New bar
      newBarTicks = 0;                                           //--- Reset ticks
      lastBarTime = currentBarTime;                              //--- Update last time
   }
   CheckForEntrySignals();                                       //--- Check entry signals
   datetime currentTime = TimeCurrent();                         //--- Get current time
   if (currentTime - LastTrailingTime >= TrailingFrequencySeconds) { //--- Check trailing time
      AdjustAllStopLosses();                                     //--- Adjust SLs
      LastTrailingTime = currentTime;                            //--- Update trailing time
   }
   HandleVirtualClosures();                                      //--- Handle virtual closures
}

Abschließend werden diese Funktionen bedarfsgerecht aufgerufen, und nach der Kompilierung erhalten wir das folgende Ergebnis.

NRTR TRAILING

Anhand des Bildes können wir erkennen, dass wir die NRTR-Signale erkennen, handeln und verwalten und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.


Backtesting

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtests:

BERICHT


Schlussfolgerung

Abschließend haben wir ein Nick Rypock Trailing Reverse (NRTR) Handelssystem in MQL5 entwickelt, das Umkehrsignale über dynamische Kanäle erkennt, trendfolgende Einstiege mit Absicherung für Käufe und Verkäufe unterstützt und integrierte Positionslimits zur kontrollierten Risikobegrenzung hat. Wir haben Risikofunktionen hinzugefügt, wie z. B. die automatische Berechnung der Lot-Größe auf der Grundlage des Kontoeigenkapitals, des Kontostands oder der freien Marge sowie feste oder ATR-angepasste Stop-Loss- und Take-Profit-Levels.

Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.

Mit dieser NRTR-Umkehrstrategie sind Sie für die Verfolgung von Trendchancen mit Hedging- und Trailing-Absicherungen gerüstet und bereit für weitere Verfeinerungen auf Ihrem Handelsweg. Viel Spaß beim Handeln!

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

Letzte Kommentare | Zur Diskussion im Händlerforum (3)
peterssozi2004
peterssozi2004 | 30 Jan. 2026 in 21:34
Kann ich diesen Roboter bekommen?
Jay4rty
Jay4rty | 31 Jan. 2026 in 06:50
Interessiert es Sie auch, ob es verfügbar ist?
hemantto
hemantto | 31 Jan. 2026 in 08:09
Hallo Sir, aber diese EA nicht in der Lage, Backtes in m5 Xau/usd? pls mir helfen, wie kann Backtest smoth .
Vom Einsteiger zum Experten: Erstellung eines Liquiditätszonenindikators Vom Einsteiger zum Experten: Erstellung eines Liquiditätszonenindikators
Das Ausmaß der Liquiditätszonen und das Ausmaß der Ausbruchsbewegung sind Schlüsselvariablen, die die Wahrscheinlichkeit eines Retests erheblich beeinflussen. In diesem Beitrag zeigen wir den vollständigen Entwicklungsprozess eines Indikators, der diese Verhältnisse berücksichtigt.
Entwicklung eines dynamischen Multi-Pair-EA (Teil 6): Adaptive Spread-Sensitivität für hochfrequente Symbolwechsel Entwicklung eines dynamischen Multi-Pair-EA (Teil 6): Adaptive Spread-Sensitivität für hochfrequente Symbolwechsel
In diesem Teil werden wir uns auf die Entwicklung einer intelligenten Ausführungsschicht konzentrieren, die die Spread-Bedingungen in Echtzeit über mehrere Symbole hinweg kontinuierlich überwacht und auswertet. Der EA passt seine Symbolauswahl dynamisch an, indem er den Handel auf der Grundlage der Spread-Effizienz und nicht nach festen Regeln aktiviert oder deaktiviert. Dieser Ansatz ermöglicht es Hochfrequenz-Multi-Pair-Systemen, kostengünstige Symbole zu priorisieren.
Graphentheorie: Einsatz von Breadth-First Search (BFS) im Trading Graphentheorie: Einsatz von Breadth-First Search (BFS) im Trading
Breadth First Search (BFS) verwendet Level-Order-Traversierung, um die Marktstruktur als einen gerichteten Graphen von Swings zu modellieren, der sich im Zeitverlauf entwickelt. Durch die schichtweise Analyse historischer Bars oder Sitzungen priorisiert BFS das jüngste Kursverhalten und berücksichtigt gleichzeitig die historische Marktprägung.
Vom Einsteiger zum Experten: Statistische Validierung von Angebots- und Nachfragezonen Vom Einsteiger zum Experten: Statistische Validierung von Angebots- und Nachfragezonen
Heute decken wir die oft übersehene statistische Grundlage hinter den Handelsstrategien für Angebot und Nachfrage auf. Durch die Kombination von MQL5 mit Python über einen Jupyter-Notebook-Workflow führen wir eine strukturierte, datengesteuerte Untersuchung durch, die darauf abzielt, visuelle Marktannahmen in messbare Erkenntnisse zu verwandeln. Dieser Artikel behandelt den gesamten Forschungsprozess, einschließlich der Datenerfassung, der Python-basierten statistischen Analyse, des Algorithmusentwurfs, der Tests und der endgültigen Schlussfolgerungen. Um die Methodik und die Ergebnisse im Detail nachzuvollziehen, lesen Sie den vollständigen Artikel.