English Русский 日本語 Português
preview
Hinzufügen von Trailing-Stop mit Parabolic SAR

Hinzufügen von Trailing-Stop mit Parabolic SAR

MetaTrader 5Beispiele | 16 September 2024, 10:42
177 0
Artyom Trishkin
Artyom Trishkin

Inhalt



Einführung

Der Trailing-Stop ist den meisten Händlern gut bekannt. Diese Funktion ist in den MetaTrader 5 integriert und passt das StopLoss-Niveau automatisch an und hält es in einem bestimmten Abstand zum aktuellen Kurs:

Aktivieren von Trailing-Stop in MetaTrader 5


Der Trailing-Stop ist eine automatische Verschiebung der StopLoss-Position hinter den Kurs, die es ermöglicht, den Schutz-Stop ständig in einem gewissen Abstand zum Kurs zu halten. Dieser Ansatz ermöglicht es dem Händler, einen Teil des aufgelaufenen Gewinns zu schützen, ohne die Position vorzeitig zu verlassen. Jedes Mal, wenn sich der Marktpreis vom Eröffnungskurs der Position entfernt, zieht der Trailing-Stop den StopLoss automatisch nach, wobei der festgelegte Abstand zum aktuellen Kurs beibehalten wird. Nähert sich der Kurs jedoch dem Eröffnungskurs, bleibt der StopLoss auf demselben Niveau. Dies bietet Schutz vor Verlusten aufgrund möglicher Marktschwankungen.

Wenn Sie jedoch eine speziellere Version des Trailing-Stop benötigen, können Sie jederzeit eine Funktion in MQL5 entwickeln, um die Möglichkeiten des Standardtools zu erweitern.

Es gibt eine Programmfunktion, der der gewünschte Kurs übergeben wird, um das StopLoss-Niveau zu setzen. Das Programm prüft einige Verbotsfaktoren, wie z.B. das StopLevel-Niveau - der Abstand, in dessen Nähe keine Stops gesetzt werden können, oder das FreezeLevel-Niveau - der „Einfrierabstand“, innerhalb dessen eine Position oder ein schwebender Auftrag nicht geändert werden kann. Mit anderen Worten: Wenn sich der Kurs dem Stop-Level der Position in einem Abstand genähert hat, der näher als das FreezeLevel-Level ist, wird erwartet, dass der Stop-Auftrag ausgelöst wird, und eine Änderung ist nicht zulässig. Trailing-Stops haben auch einige individuelle Parametereinstellungen, die ebenfalls überprüft werden, bevor das Stop-Loss-Niveau auf den angegebenen Preis verschoben wird, zum Beispiel das Symbol und die Magie der Position. Alle diese Kriterien werden unmittelbar vor dem Verschieben der StopLoss-Position auf das angegebene Niveau überprüft.

Die verschiedenen Trailing-Typen verfügen über unterschiedliche Algorithmen zur Berechnung des StopLoss-Kurses einer Position, die an die Trailing-Funktion übergeben und dann von der Trailing-Stop-Funktion verwendet werden.

Parabolic SAR-Indikator dient perfekt als „Zeiger“ für die von StopLoss benötigten Niveaus.



Der parabolische SAR-Indikator (Stop and Reverse) ist ein beliebtes Instrument der technischen Analyse, um die Momente des möglichen Endes und der Umkehr des aktuellen Trends zu bestimmen. Dieser Indikator wurde von Welles Wilder entwickelt und wird häufig für automatische Trailing-Stop-Losses verwendet. Hier sind die wichtigsten Gründe, warum der Parabolic SAR-Indikator für die Nachverfolgung eines schützenden Stopps attraktiv ist:

  1. Leichte Interpretation: Der parabolische SAR ist leicht zu interpretieren, da er auf dem Chart als Punkte dargestellt wird, die über oder unter dem Kurs liegen. Wenn die Punkte unter den Preisen liegen, ist es ein Kaufsignal, wenn die Punkte über dem Preis liegen, ist es ein Verkaufssignal.

  2. Automatische Preisverfolgung: Der Hauptvorteil des Parabolic SAR ist seine Fähigkeit, sich automatisch an Preisänderungen anzupassen und sich im Laufe der Zeit zu bewegen. Dies macht ihn zu einem perfekten Instrument für das Setzen von Trailing-Stops, da er den Gewinn schützt, indem er den Stop-Loss näher an den aktuellen Kurs zieht, wenn sich der Trend bewegt.

  3. Schutz des Gewinns: Wenn sich der Kurs auf den Gewinn der offenen Position zubewegt, zieht der Parabolic SAR das Stop-Loss-Niveau nach oben, was dazu beiträgt, einen Teil des aufgelaufenen Gewinns vor einer möglichen Trendumkehr zu schützen.

  4. Ausstiegssignale: Neben der Trailing-Stop-Funktion kann der Parabolic SAR auch als Signal zum Schließen einer Position dienen, wenn die Punkte des Indikators den Kurs kreuzen. Dies kann weitere Verluste verhindern, wenn sich der Trend schnell ändert.

  5. Einfache Einrichtung: Die Parameter des Parabolic SAR (Schritt und Maximum) können leicht an die jeweilige Marktvolatilität oder Handelsstrategie angepasst werden. Dies macht ihn zu einem vielseitigen Instrument für eine Vielzahl von Handelsbedingungen.

  6. Geeignet für alle Zeiträume: Der Indikator kann in verschiedenen Zeitrahmen effektiv eingesetzt werden und eignet sich daher sowohl für langfristige Investoren als auch für Händler, die in kurzen Zeitabständen arbeiten.

Die Verwendung des Parabolic SAR für einen Trailing-Stop ist besonders nützlich in Märkten, die sich im Trend befinden, da dieses Instrument dazu beiträgt, Gewinne zu maximieren, während eine Position offen gehalten werden kann, solange der Trend anhält. Beachten Sie jedoch, dass die Verwendung des Parabolic SAR in Phasen geringer Volatilität zu einer vorzeitigen Schließung von Positionen führen kann, da sich die Position der Indikatorpunkte häufig ändert.


Trailing Parabolic SAR

Schauen wir uns das Strukturdiagramm eines jeden Schleppers an.

In der Regel besteht der Code für den Trailing-Stop aus mehreren autarken Blöcken, die von der allgemeinen Struktur getrennt und als Funktionen gestaltet werden können:

  1. Block zur Berechnung des erforderlichen StopLoss-Niveaus; der erhaltene Wert wird an den Trailing-StopLoss-Block übergeben.
  2. Der Block des Trailing-StopLoss umfasst:
    1. den Filterblock
    2. den Block zum Setzen von StopLoss auf den erhaltenen Wert vom Block zur Berechnung des StopLoss-Niveaus, er umfasst
      1. den Block von Filtern für die Einhaltung der Serverbedingungen bezüglich der Symbolebenen sowie der Bedingungen für die Verschiebung von StopLoss.
      2. den Block zum Ändern des StopLoss.

Der Block zur Berechnung des erforderlichen StopLoss-Levels — hier ist er: der Parabolic SAR. Sein Wert, in der Regel vom Balken 1, wird bei jedem Tick an den Trailing-StopLoss-Block gesendet, wo die Eigenschaften jeder ausgewählten Position den Filterblock in einer Schleife durch die Liste der offenen Positionen durchlaufen - in der Regel nach Symbol und MagicNumber. Wenn die Filter nach Symbol & Magic bestanden worden sind, wird das erforderliche StopLoss-Niveau zusätzlich gefiltert, um die Bedingungen für das Server-StopLevel, den Trailing-Schritt, den Wert des erforderlichen StopLoss im Verhältnis zu seiner vorherigen Position und das Kriterium für den Beginn des Trailing durch den Positionsgewinn in Punkten zu erfüllen. Wenn diese Filter ebenfalls übergeben werden, wird der StopLoss der Position geändert und auf ein neues Niveau gesetzt.

Wir haben also bereits einen Block für die Berechnung des Stop-Loss-Niveaus - den Parabolic SAR-Indikator. Dies bedeutet, dass es nur notwendig ist, einen Block von sich verschiebenden StopLoss-Levels von Positionen zu erstellen, die durch das aktuelle Symbol und die EA-ID (Magic Number) ausgewählt wurden. Wenn der MagicNumber auf -1 gesetzt wird, wird jede Position, die auf dem Chartsymbol geöffnet ist, nachgezogen. Wenn eine MagicNumber angegeben wird, werden nur Positionen mit der entsprechenden MagicNumber nachgezogen. Die Trailing-Funktion wird nur gestartet, wenn ein neuer Balken oder eine neue Position eröffnet wird. Geben wir ein Beispiel für einen EA.

Im EA erstellen wir den Parabolic SAR-Indikator mit den in den EA-Einstellungen angegebenen Parametern. Die Indikatorwerte eines bestimmten Balkens (standardmäßig des ersten Balkens) werden an die Trailing-Stop-Funktion übergeben, die ihrerseits alle notwendigen Berechnungen durchführt, um die StopLoss-Ebenen der Positionen zu verschieben. Es ist notwendig, die StopLevel-Ebene des Symbols zu berücksichtigen, in deren Nähe keine Stops gesetzt werden können. Das aktuelle StopLoss-Niveau wird ebenfalls überprüft, und wenn es gleich oder höher (bei Käufen) bzw. niedriger (bei Verkäufen) ist als das an die Funktion übergebene Niveau, dann muss der Stop nicht verschoben werden.

Alle diese Prüfungen werden von einer speziellen Funktion zur Überprüfung der Kriterien für die Änderung der Stop-Loss-Position durchgeführt. Nachdem alle erforderlichen Prüfungen abgeschlossen sind, wird das Stop mit Hilfe der Funktion zur Änderung von Stopps auf eine neue Ebene verschoben.

Wir erstellen im Terminal-Ordner \MQL5\Experts\ eine neue EA-Datei namens TrailingBySAR_01.mq5.

Im zweiten Schritt des Assistenten zur Erstellung einer neuen EA-Datei überprüfen wir den OnTradeTransaction()-Handler im neuen Fenster:


Die Ereignisbehandlung durch OnTradeTransaction() ist erforderlich, um das Trailing im Moment der Eröffnung einer neuen Position zu starten.

Sie wird im Falle des Ereignisses TradeTransaction aufgerufen, das auch die Eröffnung einer neuen Position beinhaltet.

Erfahren Sie mehr über Handelsgeschäfte und Ereignisse im Artikel „Womit soll man bei der Erstellung eines Handelsroboters für die Moskauer Börse MOEX anfangen“.

Wir werden die folgenden Eingaben und globalen Variablen in die erstellte EA-Datei einfügen:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_01.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum

//--- global variables
int      ExtHandleSAR =INVALID_HANDLE// Parabolic SAR handle
double   ExtStepSAR   =0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;                 // Parabolic SAR maximum

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 

  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {

  }


In OnInit() des EA setzen wir die richtigen Werte für die in den Indikatoreinstellungen eingegebenen Parameter, erstellen den Indikator und holen uns sein Handle:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);

//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }
//--- successful
   return(INIT_SUCCEEDED);
  }

Wenn der Indikator erfolgreich erstellt wurde, wird sein Handle erstellt, mit dem wir weiter die Werte vom Parabolic SAR erhalten können. Tritt bei der Erstellung eines Indikators ein Fehler auf, wird im Journal eine Fehlermeldung mit den Daten des zu erstellenden Indikators angezeigt. Um den Zeitrahmen zu beschreiben, auf dem der Indikator basiert, verwenden wir die Funktion, die den Zeitrahmen als Text zurückgibt:

//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }

Der Teil des Textes der Enumeration wird aus der Zeitrahmenkonstante entnommen, um eine Zeichenkette zu erhalten, die nur den Namen des Zeitrahmens enthält.
Zum Beispiel erhalten wir die Zeichenkette „PERIOD_H1“ aus der Konstante PERIOD_H1, die nur „H1“ aus der Zeichenkette zurückgibt.

Um die Eröffnung eines neuen Balkens zu überprüfen, müssen wir die Eröffnungszeit des aktuellen Balkens mit der zuvor gespeicherten vergleichen. Wenn die zu prüfenden Werte nicht gleich sind, wird ein neuer Balken geöffnet.
Da es sich hier nicht um einen Indikator handelt (für den es bereits ein vordefiniertes Array mit der Zeitreihe des aktuellen Symbols/der aktuellen Periode gibt), sondern um einen EA, müssen wir eine Funktion erstellen, um die Eröffnungszeit des Balkens zu ermitteln:

//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

Wir übergeben der Funktion den Index des Balkens, dessen Öffnungszeit wir abfragen wollen, kopieren die erforderlichen Daten in das Array und geben die empfangenen Daten aus dem Array zurück. Da wir die Zeit von nur einen Balken benötigen, definieren wir das Array mit der Dimension 1.

Mit dieser Funktion können wir nun eine Funktion implementieren, die das Flag für das Öffnen eines neuen Balkens zurückgibt:

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }

Wir vergleichen die vorherige Öffnungszeit des Nullbalkens mit der aktuellen, die wir mit der Funktion TimeOpenBar() erhalten haben. Wenn die verglichenen Werte nicht gleich sind, wird die neue Zeit für die nächste Prüfung gespeichert und das Flag für eine echte, neue Balkenöffnung zurückgegeben. Wenn ein Fehler auftritt oder wenn die verglichenen Werte gleich sind, wird false zurückgegeben — es gibt keinen neuen Balken.

Um Daten vom Parabolic SAR zu empfangen und Werte an die Trailing-Funktion zu senden, schreiben wir eine Funktion zum Empfang von Daten über den Indikator-Handle:

//+------------------------------------------------------------------+
//| Return Parabolic SAR data from the specified timeseries index    |
//+------------------------------------------------------------------+
double GetSARData(const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(ExtHandleSAR, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Hier ist alles genauso wie in der Funktion zur Ermittlung der Öffnungszeit der Bar: Wir holen uns den Wert in einem Array mit einer Dimension von 1 entsprechend dem an die Funktion übergebenen Index und geben bei erfolgreichem Empfang den Wert aus dem Array zurück. Im Falle eines Fehlers wird EMPTY_VALUE zurückgegeben.

Um StopLoss einer Position zu setzen, müssen wir überprüfen, ob der Stop-Abstand zum Kurs nicht innerhalb der Grenzen liegt, die durch das Symbol StopLevel festgelegt wurden. Wenn der StopLoss-Kurs näher am Kurs liegt, als es der StopLevel-Abstand zulässt, wird die Stop-Position aufgrund des Fehlers „ungültige Stops“ nicht gesetzt. Um solche Fehler zu vermeiden, müssen wir diesen Abstand überprüfen, bevor wir die Anschlagposition festlegen. Es gibt eine weitere Ebene - das „Einfrierabstand“ (FreezeLevel), das den Abstand zwischen dem Preis und dem Positionsstopp (StopLoss oder TakeProfit) angibt, innerhalb dessen die Stopp-Levels nicht verändert werden können, da sie wahrscheinlich ausgelöst werden. In den allermeisten Fällen werden diese Ebenen jedoch nicht mehr verwendet, und wir werden sie hier nicht überprüfen.

Was die StopLevel-Ebenen betrifft, so gibt es eine Nuance: Wenn die Ebene auf 0 gesetzt ist, bedeutet dies nicht, dass sie nicht vorhanden ist. Dies bedeutet, dass die Werte dieser Ebene fließend sind. Meistens entsprechen sie zwei Spreads. Oder manchmal drei. Hier müssen wir Werte auswählen, da sie von den Servereinstellungen abhängen. Zu diesem Zweck wird ein nutzerdefinierter Parameter in die Funktion aufgenommen, um den StopLevel-Wert zu erhalten. Der Multiplikator, mit dem die Spread des Symbols multipliziert werden soll, um den StopLevel-Wert zu erhalten, wird an die Funktion übergeben, wenn StopLevel auf Null gesetzt ist. Ist der Wert von StopLevel ungleich Null, gibt die Funktion einfach diesen Wert zurück::

//+------------------------------------------------------------------+
//| Return StopLevel value of the current symbol in points           |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }


Implementieren wir die Hauptfunktion für das Trailing:

//+------------------------------------------------------------------+
//| Trailing stop function by StopLoss price value                   |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
      
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }

Die Logik ist einfach: In einer Schleife durch die Liste der offenen Positionen im Terminal wird jede aufeinanderfolgende Position anhand ihres Tickets ausgewählt, es wird überprüft, ob das Symbol und die MagicNumber der Position mit dem für die Auswahl der Positionen festgelegten Filter übereinstimmen, und es werden die Bedingungen für die Verschiebung des StopLoss-Levels überprüft. Wenn die Bedingungen geeignet sind, ändern wir den Stop-Wert.

Die Funktion zur Überprüfung der Kriterien für die Änderung der Stop-Werte:

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }

Die Bedingungen sind einfach:

  1. Wenn das Niveau der Stops und das Niveau, auf das die Stops verschoben werden soll, gleich sind, ist keine Änderung erforderlich - es wird false zurückgegeben,
  2. Wenn das Stop-Level näher am Kurs liegt, als es StopLevel erlaubt, ist eine Änderung inkl. einer Fehlermeldung nicht erlaubt, es wird false zurückgegeben.
  3. Wenn der Kurs sich nach der letzten Änderung noch nicht weit genug wegbewegt hat, ist es zu früh für eine Änderung, da die Schrittweite des Trailing nicht eingehalten werden kann, und es wird false zurückgegeben,
  4. Wenn der Kurs den angegebenen Gewinn in Punkten noch nicht erreicht hat, ist es zu früh für eine Änderung, es wird false zurückgegeben.

Diese einfachen Regeln sind die Grundlage für jedes Trailing. Wenn die Kriterien erfüllt sind, muss der Stop geändert werden. Ist dies nicht der Fall, werden die Bedingungen beim nächsten Aufruf der Funktion überprüft.

Schreiben wir die Funktion, um den StopLoss-Kurs einer Position gemäß ihres Tickets zu ändern:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};

//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

Wir wählen eine Position auf der Grundlage des an die Funktion übergebenen Tickets aus, füllen die erforderlichen Felder der Anforderungsstruktur aus und senden einen Handelsauftrag an den Server. Im Falle eines Fehlers wird eine Meldung mit einem Fehlercode im Journal angezeigt und false zurückgegeben. Weitere Informationen über Handelsoperationen finden Sie in der MQL5-Dokumentation.

Implementieren wir die Funktion zum Nachziehen einer Stop-Position auf der Basis von Parabolic SAR-Werten:

//+------------------------------------------------------------------+
//| StopLoss trailing function using the Parabolic SAR indicator     |
//+------------------------------------------------------------------+
void TrailingStopBySAR(const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- get the Parabolic SAR value from the first bar of the timeseries
   double sar=GetSARData(SAR_DATA_INDEX);
   
//--- if failed to obtain data, leave
   if(sar==EMPTY_VALUE)
      return;
      
//--- call the trailing function with the StopLoss price obtained from Parabolic SAR 
   TrailingStopByValue(sar, magic, trailing_step_pt, trailing_start_pt);
  }

Wir ermitteln zunächst den Indikatorwert vom Balken 1. Wenn der Wert nicht erreicht wird, verlassen wir die Funktion. Wenn der Wert vom Parabolic SAR erfolgreich empfangen wurde, senden wir ihn an die Stop-Trailing-Funktion nach Wert.

Nun legen wir für das zu erstellende Trailing den Parabolic SAR in dem EA ein.

In OnTick():

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR();
  }

Bei jeder Eröffnung eines neuen Balkens wird die Trailing-Funktion für den Parabolic SAR mit Standardwerten aufgerufen - alle auf dem Symbol eröffneten Positionen werden nachgezogen, unabhängig von ihrer MagicNumber. Das Stopp-Niveau der Positionen wird ab dem Zeitpunkt ihrer Eröffnung genau entsprechend den Indikatorwerten nachgezogen, ohne dass ein Trailing-Schritt erfolgt und ohne Berücksichtigung des Gewinns der Positionen in Punkten.

In OnTradeTransaction():

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR();
  }

Damit das Trailing funktioniert, wenn eine Position eröffnet wird, wird die Trailing-Funktion im Handler aufgerufen, sofern ein neues Handelsgeschäft in die Liste der Deals im Terminal aufgenommen wurde. Ohne diesen Handler wird das Trailing nur ausgelöst, wenn ein neuer Balken geöffnet wird.

Wir haben eine grundlegende Trailing-Stop-Funktion und einen Trailing-EA auf der Grundlage des Parabolic SAR-Indikators erstellt. Wir können den EA kompilieren und, indem wir ihn auf dem Chart ausführen, eine Position eröffnen und das Trailing durch den Parabolic SAR-Wert verwalten. Die EA-Datei ist unten angefügt.


Verwendung der Standardbibliothek CTrade

Die MQL5-Standardbibliothek wurde entwickelt, um den Endnutzern die Entwicklung von Programmen zu erleichtern. Die Bibliothek bietet bequemen Zugang zu den internen Funktionen von MQL5.

Nutzen wir diese Gelegenheit und ersetzen die Änderungsfunktion der StopLoss durch die Methode PositionModify() der Klasse CTrade.

Schauen wir uns den Inhalt der Positionsänderungsmethode an:

//+------------------------------------------------------------------+
//| Modify specified opened position                                 |
//+------------------------------------------------------------------+
bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp)
  {
//--- check stopped
   if(IsStopped(__FUNCTION__))
      return(false);
//--- check position existence
   if(!PositionSelectByTicket(ticket))
      return(false);
//--- clean
   ClearStructures();
//--- setting request
   m_request.action  =TRADE_ACTION_SLTP;
   m_request.position=ticket;
   m_request.symbol  =PositionGetString(POSITION_SYMBOL);
   m_request.magic   =m_magic;
   m_request.sl      =sl;
   m_request.tp      =tp;
//--- action and return the result
   return(OrderSend(m_request,m_result));
  }

und vergleichen sie mit der Funktion zur Änderung der Halteposition, die im obigen EA geschrieben wurde:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

Es gibt keinen besonderen Unterschied. In der Methode der Handelsklasse wird gleich zu Beginn geprüft, ob der EA aus dem Chart entfernt werden kann. Die Funktion enthält keine solche Prüfung.
Anstelle der Standardfunktion IsStopped() verwenden wir die gleichnamige Handelsklasse mit dem formalen Parameter:

//+------------------------------------------------------------------+
//| Checks forced shutdown of MQL5-program                           |
//+------------------------------------------------------------------+
bool CTrade::IsStopped(const string function)
  {
   if(!::IsStopped())
      return(false);
//--- MQL5 program is stopped
   PrintFormat("%s: MQL5 program is stopped. Trading is disabled",function);
   m_result.retcode=TRADE_RETCODE_CLIENT_DISABLES_AT;
   return(true);
  }

Wenn IsStopped() true zurückgibt, wird zunächst eine Meldung im Journal angezeigt, dass der EA aus dem Chart entfernt wurde, und dann wird das EA-Stop-Flag zurückgegeben.

Die verbleibenden Unterschiede in der Handelsfunktion und der Klassenmethode sind unbedeutend - sie sind gleich, nur anders implementiert. In der Klassenmethode werden die im Klassenkopf deklarierten Strukturen gelöscht, aber in der Funktion sind diese Strukturen lokal und werden beim Funktionsaufruf deklariert und alle Felder mit Nullen initialisiert. Wenn in der Funktion ein Fehler beim Senden einer Handelsanfrage auftritt, wird sofort eine Meldung mit einem Fehlercode angezeigt. Danach wird das Ergebnis zurückgegeben, während der Aufruf der Funktion OrderSend() einfach in der Methode der Handelsklasse zurückgegeben wird.

Nehmen wir nun Änderungen an dem bereits implementierten EA vor und speichern ihn unter dem neuen Namen TrailingBySAR_02.mq5. Testen wir den Pfad im Strategietester, indem wir Positionen auf der Grundlage der Werte des Parabolic SAR-Indikators öffnen und gleichzeitig die StopLoss-Level der geöffneten Positionen auf der Grundlage der Werte desselben Indikators verschieben.

Wir binden die Handelsklassendatei in den EA ein, deklarieren die MagicNumber des EA in den Eingaben und die Handelsklasseninstanz im globalen Bereich. Außerdem weisen wir in OnInit() dem Objekt der Handelsklasse die MagicNumber aus den Eingaben zu:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_02.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com" 
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include <Trade\Trade.mqh>    // replace trading functions with Standard Library methods

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input ulong             InpMagic          =  123;              // Magic Number

//--- global variables
int      ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle
double   ExtStepSAR=0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;              // Parabolic SAR maximum
CTrade   ExtTrade;                     // trading operations class instance

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the magic number to the trading class object
   ExtTrade.SetExpertMagicNumber(InpMagic);

//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);
   
//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }   
//--- successful
   return(INIT_SUCCEEDED);
  }


In OnTick() des EA fügen wie den Code für die Eröffnung von Positionen gemäß dem Parabolic SAR im Strategietester hinzu. Außerdem fügen wir die MagicNumber des EA so hinzu, dass sie an die abschließende Funktion übergeben wird:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
   if(MQLInfoInteger(MQL_TESTER))
     {
      //--- get Parabolic SAR data from bars 1 and 2
      double sar1=GetSARData(SAR_DATA_INDEX);
      double sar2=GetSARData(SAR_DATA_INDEX+1);
      
      //--- if the price structure is filled out and Parabolic SAR data is obtained
      MqlTick tick={};
      if(SymbolInfoTick(Symbol(), tick) && sar1!=EMPTY_VALUE && sar2!=EMPTY_VALUE)
        {
         //--- if Parabolic SAR on bar 1 is below Bid price, while on bar 2 it is above Bid price, open a long position
         if(sar1<tick.bid && sar2>tick.bid)
            ExtTrade.Buy(0.1);
         //--- if Parabolic SAR on bar 1 is above Ask, while on bar 2 it is below Ask price, open a long position
         if(sar1>tick.ask && sar2<tick.ask)
            ExtTrade.Sell(0.1);
        }
     }
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR(InpMagic);
  }


In OnTradeTransaction() ergänzen wir die Übergabe der MagicNumber an die Trailing-Funktion:

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR(InpMagic);
  }

Wenn wir die MagicNumber im EA verwenden, um Positionen im Tester zu öffnen und deren Stopps nachzuziehen, können wir die Filterfunktion anhand der MagicNumber überprüfen.

In der universellen Funktion des Trailing ersetzen wir den Aufruf der Modifikationsfunktion durch den Aufruf der Methode der Handelsklasse:

//+------------------------------------------------------------------+
//| Universal trailing stop function by StopLoss price value         |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ExtTrade.PositionModify(pos_ticket, value_sl, PositionGetDouble(POSITION_TP));
     }
  }

Um nun ein Stop-Level zu ändern, rufen wir nicht eine zuvor implementierte Funktion auf, sondern die Methode PositionModify() der Handelsklasse CTrade. Der Unterschied zwischen diesen Aufrufen liegt in einem Parameter. Wenn die Funktion geschrieben wurde, um nur den StopLoss-Kurs einer Position zu ändern, dann musste bei der Angabe der Parameter der Ticketwert der zu ändernden Position und das neue Niveau ihres Stop-Levels an die Funktion übergeben werden. Nun müssen drei Parameter an die Methode der Handelsklasse übergeben werden - neben dem Positionsticket und dem Wert des StopLoss-Levels müssen wir auch das TakeProfit-Level angeben. Da die Position bereits ausgewählt wurde und ihr TakeProfit-Wert nicht geändert werden muss, übergeben wir den TakeProfit-Wert ohne Änderungen direkt aus den Eigenschaften der ausgewählten Position an die Methode.

Die zuvor implementierte Funktion ModifySL() wurde nun aus dem EA-Code entfernt.

Kompilieren wir den EA und lassen ihn im Strategietester auf einem beliebigen Symbol und einem beliebigen Chart-Zeitrahmen im Modus „Jeder Tick“ laufen:


Wie wir sehen können, werden die Stopps der Positionen korrekt durch den Wert des ersten Balkens des Parabolic SAR-Indikators nachgezogen.

Die EA-Datei ist unten angefügt.


Ein funktionsfähiges Trailing im EA „in ein paar Zeilen“

Doch trotz der Leichtigkeit, mit der das Trailing erstellt und in einem EA verwendet werden kann, möchten wir nicht jedes Mal alle Funktionen, die für seinen Betrieb erforderlich sind, in jedem neuen EA aufschreiben müssen, sondern einfach alles im Stil „Plug & Play“ erledigen.
Dies ist in MQL5 möglich. Dazu müssen wir die Plug-in-Datei nur einmal schreiben und sie dann einfach mit dem gewünschten EA verbinden und an die Stelle schreiben, an der wir die Schleppfunktionen aufrufen müssen.

Verschieben wir alle erstellten Trailing-Funktionen in eine neue Datei. Wir erstellen eine neue Plug-in-Datei TrailingsFunc.mqh in dem Ordner, der den EA enthält. Es ist ratsam, alle diese Dateien in einem gemeinsamen Ordner für alle eingebundenen Dateien \MQL5\Include\ oder in einem Unterordner innerhalb dieses Verzeichnisses zu speichern. Für diesen Test reicht es jedoch aus, eine Datei direkt im Ordner mit dem EA zu erstellen und sie von dort aus einzubinden.

Drücken Sie Strg+N im Editor und wählen Sie eine neue einzubindende Datei aus:


Geben Sie im nächsten Fenster des Assistenten den Namen der TrailingFunc-Datei ein.
Standardmäßig enthält die Zeichenfolge für die Eingabe des Dateinamens bereits das Stammverzeichnis für eingeschlossene Dateien - Include. Mit anderen Worten, die Datei wird in diesem Ordner erstellt.
Dann können wir sie einfach in den gewünschten Ordner mit dem EA verschieben oder manuell den Pfad zum gewünschten Ordner in die Zeichenkette des Dateinamens integrieren (anstelle von Include\, geben Sie Experts\ ein und dann den Pfad zum Ordner mit den Test-EAs, falls einer verwendet wird, gefolgt vom Namen der erstellten Datei TrailingFunc):


Wir klicken auf Fertig (Finish) stellen und erstellen eine leere Datei:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+


Nun müssen wir alle zuvor erstellten Funktionen aus den Test-EAs hierher übertragen:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Simple trailing by value                                         |
//+------------------------------------------------------------------+
void SimpleTrailingByValue(const double value_sl, const long magic=-1, 
                           const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- price structure
   MqlTick tick={};
   
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }
//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }
//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest    request={};
   MqlTradeResult     result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }
//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

Die Funktion zum Empfang von Daten aus dem Parabolic-SAR-Indikator wurde in den EA implementiert. Die Daten in dieser Funktion wurden mit dem angegebenen Indikatorhandle ermittelt. Das bedeutet, dass nicht nur der Parabolic SAR als ein solcher Indikator fungieren kann, sondern jeder andere Indikator, der sich für die Verwendung als Preis für die Festlegung von StopLoss-Positionen eignet.

Daher wird diese Funktion hier in eine allgemeine Funktion für den Empfang von Daten aus Indikatoren per Handle umbenannt:

//+------------------------------------------------------------------+
//| Return indicator data by handle                                  |
//| from the specified timeseries index                              |
//+------------------------------------------------------------------+
double GetIndData(const int handle_ind, const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(handle_ind, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Dementsprechend gibt es jetzt eine Trailing-Funktion, die auf den von der obigen Funktion erhaltenen Indikatordaten basiert:

//+------------------------------------------------------------------+
//| Trailing by indicator data specified by handle                   |
//+------------------------------------------------------------------+
void TrailingByDataInd(const int handle_ind, const int index=1, const long magic=-1, 
                       const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- get the Parabolic SAR value from the specified timeseries index
   double data=GetIndData(handle_ind, index);
   
//--- if failed to obtain data, leave
   if(data==EMPTY_VALUE)
      return;
      
//--- call the simple trailing function with the StopLoss price obtained from Parabolic SAR 
   SimpleTrailingByValue(data, magic, trailing_step_pt, trailing_start_pt, trailing_offset_pt);
  }

In der Funktion holen wir zunächst Daten vom Indikator über das angegebene Handle vom angegebenen Bar-Index ab und rufen dann die Trailing-Funktion über den Wert auf, wobei wir den vom Indikator erhaltenen StopLoss-Wert an sie übergeben.

Auf diese Weise können wir die Stop-Levels der Positionen anhand der Daten eines beliebigen, für diesen Zweck geeigneten Indikators verfolgen.

Wir schreiben die folgende Funktion in die Datei, um die Erstellung des Parabolic SAR-Indikators nicht in den EA-Code aufzunehmen:

//+------------------------------------------------------------------+
//| Create and return the Parabolic SAR handle                       |
//+------------------------------------------------------------------+
int CreateSAR(const string symbol_name, const ENUM_TIMEFRAMES timeframe, const double step_sar=0.02, const double max_sar=0.2)
  {
//--- set the indicator parameters within acceptable limits
   double step=(step_sar<0.0001 ? 0.0001 : step_sar);
   double max =(max_sar <0.0001 ? 0.0001 : max_sar);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ResetLastError();
   int handle=iSAR(symbol, period, step, max);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  symbol, TimeframeDescription(period), step, max, GetLastError());
     } 
//--- return the result of creating the indicator handle
   return handle;
  }

Im Wesentlichen handelt es sich bei der Funktion CreateSAR() um einen Code, der aus OnInit() des Test-EAs TrailingBySAR_01.mq5 stammt. Dieser Ansatz ermöglicht es uns, diese Funktion einfach aufzurufen, ohne Eingabevariablenkorrekturstrings für den Indikator im EA schreiben und sein Handle erstellen zu müssen.

Weiter unten im Code befinden sich ähnliche Funktionen zur Erstellung verschiedener gleitender Durchschnitte, z. B. die Funktion zur Erstellung des adaptiven gleitenden Durchschnitts:

//+------------------------------------------------------------------+
//| Create and return Adaptive Moving Average handle                 |
//+------------------------------------------------------------------+
int CreateAMA(const string symbol_name, const ENUM_TIMEFRAMES timeframe,
              const int ama_period=9, const int fast_ema_period=2, const int slow_ema_period=30, const int shift=0, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
//--- set the indicator parameters within acceptable limits
   int ma_period=(ama_period<1 ? 9 : ama_period);
   int fast_ema=(fast_ema_period<1 ? 2 : fast_ema_period);
   int slow_ema=(slow_ema_period<1 ? 30 : slow_ema_period);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ::ResetLastError();
   int handle=::iAMA(symbol, period, ma_period, fast_ema, slow_ema, shift, price);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    symbol, TimeframeDescription(period), ma_period, fast_ema, slow_ema,
                    ::StringSubstr(::EnumToString(price),6), ::GetLastError());
     }
//--- return the result of creating the indicator handle
   return handle;
  }

Alle anderen Funktionen sind ähnlich wie die oben beschriebenen. Es ist nicht notwendig, sie hier zu berücksichtigen. Sie finden sie in der unten angehängten Datei TrailingsFunc.mqh.

Diese Funktionen wurden entwickelt, um schnell gleitende Durchschnitte zu erstellen, sodass ihre Daten anstelle von Parabolic SAR-Daten bei der Durchführung unserer eigenen Studien zur Erstellung verschiedener Arten von Trailing verwendet werden können.


Um die implementierten Funktionen zu testen, erstellen wir den Test-EA TrailingBySAR_03.mq5 und binden die neu erstellte Datei TrailingsFunc.mqh darin ein.
Im globalen Bereich deklarieren wir die Variable zum Speichern des Handles des erstellten Indikators und weisen dieser Variable in OnInit() das Ergebnis der Erstellung des ParabolicSAR-Indikators zu:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_03.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include "TrailingsFunc.mqh"

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input long              InpMagic          =  123;              // Expert Magic Number

//--- global variables
int   ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create Parabolic SAR handle
   ExtHandleSAR=CreateSAR(Symbol(), InpTimeframeSAR, InpStepSAR, InpMaximumSAR);
   
//--- if there is an error creating the indicator, exit OnInit with an error
   if(ExtHandleSAR==INVALID_HANDLE)
      return(INIT_FAILED);

//--- successful
   return(INIT_SUCCEEDED);
  }


Jetzt müssen wir nur noch das Trailing entsprechend den Daten des erstellten Indikators in OnTick() und OnTradeTransaction() neu starten, indem wir die in den EA-Einstellungen angegebene MagicNumber der Position an die Trailing-Funktion übergeben:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }

Der erstellte EA ist ein Trailing-Stop, der auf den Daten des Parabolic SAR Indikators basiert. Er verfolgt die Stop-Level der Positionen, die auf dem Symbol geöffnet sind, auf dem dieser EA gestartet wurde und deren MagicNumber mit der in den EA-Einstellungen festgelegten übereinstimmt.


Verbinden des Trailing mit dem EA

Zum Schluss verbinden wir den Trailing by Parabolic SAR mit dem Standard-ExpertMACD-EA, der sich in \MQL5\Experts\Advisors\ExpertMACD.mq5 befindet.

Wir speichern die Datei unter dem Namen ExpertMACDPSAR.mq5 und nehmen Änderungen vor, um das Trailing einzuschließen.

Im globalen Bereich fügen wir die Datei mit der Schleppfunktion ein, ergänzen die Eingaben für das Trailing und deklarieren eine Variable zum Speichern von des Handles, das von Parabolic SAR erstellt wird:

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include "TrailingsFunc.mqh"

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

//--- inputs for trail
input group  " - PSAR Trailing Parameters -"
input bool   InpUseTrail       =  true;      // Trailing is Enabled
input double InpSARStep        =  0.02;      // Trailing SAR Step
input double InpSARMaximum     =  0.2;       // Trailing SAR Maximum
input int    InpTrailingStart  =  0;         // Trailing start
input int    InpTrailingStep   =  0;         // Trailing step in points
input int    InpTrailingOffset =  0;         // Trailing offset in points
//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
int     ExtHandleSAR;
//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+


In OnInit() erstellen wir einen Indikator und weisen dessen Handle der Variable zu:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trail
   if(InpUseTrail)
     {
      ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT,InpSARStep,InpSARMaximum);
      if(ExtHandleSAR==INVALID_HANDLE)
         return(INIT_FAILED);
     }
   
//--- Initializing expert
//...
//...


In OnDeinit() löschen wir das Handle und geben den Berechnungsteil des Indikators frei:

//+------------------------------------------------------------------+
//| Deinitialization function of the expert advisor                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
   IndicatorRelease(ExtHandleSAR);
  }


In OnTick() und OnTrade() starten wir den Indikator für das Trailing durch Parabolic SAR:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }
//+------------------------------------------------------------------+
//| Function-event handler "trade"                                   |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
   ExtExpert.OnTrade();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

Dies ist alles, was der EA-Datei hinzugefügt werden muss, um einen vollwertigen Trailing für Parabolic SAR hinzuzufügen.

Hier können wir im selben EA ein Handle für einen anderen Indikator, z. B. einen gleitenden Durchschnitt, erstellen und dessen Handle an die Trailing-Funktion übergeben. In diesem Fall erhalten wir ein Trailing auf der Grundlage des gleitenden Durchschnitts. Wir können die von verschiedenen Indikatoren erhaltenen Werte kombinieren und die berechneten Gesamtwerte an die Trailing-Funktion senden, oder in verschiedenen Marktsituationen Stops mit verschiedenen Algorithmen nachziehen, indem wir den erforderlichen berechneten Wert der Positionsstopps an die Trailing-Funktion übergeben. Es gibt viel Raum für Experimente.

Es ist wichtig, daran zu denken, dass die in der Datei TrailingFunc.mqh vorgestellten Funktionen das Erstellen erlauben von

  1. nutzerdefinierten, nicht handelsbezogene Trailing-EAs, die auf verschiedenen Algorithmen oder Indikatorwerten basieren,
  2. verschiedene Trailing-Stops in bestehende Trading EAs einbauen, die entweder nach Indikatorwerten oder nach eigenen Algorithmen arbeiten.

Es gibt jedoch auch Einschränkungen: Wir können nicht gleichzeitig unterschiedliche Werte für Kauf- und Verkaufs-Positionen an die Trailing-Funktion übergeben, und wir können auch keine auf einem anderen Symbol eröffneten Positionen ein Trailing machen. Es gibt auch einige Unannehmlichkeiten bei der Verbindung von Trailing - es ist notwendig, Indikatoren im EA zu erstellen und ihre Handles in Variablen zu speichern. Wir können alles, was oben beschrieben wurde, sowie einige andere Dinge loswerden, indem wir Trailingklassen erstellen. Darauf werde ich im nächsten Artikel eingehen.

Führen wir den erstellten EA in einem einzigen Durchgang im Tester mit den eingestellten Parametern aus.

Wählen Sie die folgenden Einstellungen:

  • Symbol: EURUSD,
  • Zeitrahmen: M15,
  • Testen Sie das letzte Jahr bei jedem Tick ohne Ausführungsverzögerung.

Eingabeeinstellungen:


Nach einem Test in einem bestimmten Intervall mit ausgeschaltetem Trailing erhalten wir die folgenden Statistiken:



Starten wir nun den EA und aktivieren das Trailing in den Einstellungen:


Es ist klar, dass nach dem Trailing das Chart ein wenig glatter wurde. Ich lade die Leserinnen und Leser ein, verschiedene Lösungen für die Prüfung des Trailings selbst auszuprobieren.

Alle Dateien sind dem Artikel zum Selbststudium und für Tests beigefügt.


Schlussfolgerung

Wir haben gelernt, wie man schnell einen Trailing-Stop erstellt und mit EAs verbindet. Alles, was wir tun müssen, um einen Trailing-Stop in jeden EA zu integrieren, ist:

  1. legen Sie die Include-Datei TrailingsFunc.mqh in den EA-Ordner,
  2. fügen Sie diese Datei in die EA-Datei ein, indem Sie den Befehl #include „TrailingsFunc.mqh“ verwenden,
  3. tragen Sie die Erstellung des ParabolicSAR-Indikators in OnInit() des EAs ein: ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT);
  4. fügen Sie in OnTick() und (falls erforderlich) OnTrade() oder OnTradeTransaction() des EAs einen nachfolgenden Aufruf hinzu: TrailingByDataInd(ExtHandleSAR);
  5. geben Sie den Berechnungsteil des ParabolicSAR-Indikators mit IndicatorRelease(ExtHandleSAR) in EAs OnDeinit() frei.


Jetzt hat dieser EA einen vollwertigen Trailing-Stop eingebaut, um seine Positionen zu verwalten. Wir können das Trailing nicht nur mit dem Parabolic SAR Indikator, sondern auch mit jedem anderen Indikator durchführen. Wir können auch nutzerdefinierte Algorithmen für die Berechnung von Stop-Loss-Niveaus erstellen.

Wir können mehrere Trails mit unterschiedlichen Parametern in einem EA verwenden und je nach Marktsituation zwischen ihnen wechseln. Im nächsten Artikel werde ich mich mit Klassen für das Trailing befassen, die frei von den Nachteilen der einfachen Funktionen sind.



Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/14782

Beigefügte Dateien |
ExpertMACDPSAR.mq5 (14.04 KB)
Neuronale Netze leicht gemacht (Teil 87): Zeitreihen-Patching Neuronale Netze leicht gemacht (Teil 87): Zeitreihen-Patching
Die Vorhersage spielt eine wichtige Rolle in der Zeitreihenanalyse. Im neuen Artikel werden wir über die Vorteile des Zeitreihen-Patchings sprechen.
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 9): Sammeln von Optimierungsergebnissen für einzelne Handelsstrategie-Instanzen Entwicklung eines Expertenberaters für mehrere Währungen (Teil 9): Sammeln von Optimierungsergebnissen für einzelne Handelsstrategie-Instanzen
Schauen wir uns die wichtigsten Phasen der EA-Entwicklung an. Eine der ersten Aufgaben besteht darin, eine einzelne Instanz der entwickelten Handelsstrategie zu optimieren. Versuchen wir, alle notwendigen Informationen über die Testergebnisse während der Optimierung an einem Ort zu sammeln.
Kolmogorov-Smirnov-Test bei zwei Stichproben als Indikator für die Nicht-Stationarität von Zeitreihen Kolmogorov-Smirnov-Test bei zwei Stichproben als Indikator für die Nicht-Stationarität von Zeitreihen
Der Artikel befasst sich mit einem der bekanntesten nichtparametrischen Homogenitätstests – dem Kolmogorov-Smirnov-Test mit zwei Stichproben. Es werden sowohl Modelldaten als auch reale Kurse analysiert. Der Artikel enthält auch ein Beispiel für die Konstruktion eines Nicht-Stationaritätsindikators (iSmirnovDistance).
Neuronale Netze leicht gemacht (Teil 86): U-förmiger Transformator Neuronale Netze leicht gemacht (Teil 86): U-förmiger Transformator
Wir untersuchen weiterhin Algorithmen für die Zeitreihenprognose. In diesem Artikel werden wir eine andere Methode besprechen: den U-förmigen Transformator.