English Русский 中文 Español 日本語 Português
preview
Wie man jede Art von Trailing-Stop entwickelt und mit einem EA verbindet

Wie man jede Art von Trailing-Stop entwickelt und mit einem EA verbindet

MetaTrader 5Beispiele | 14 Oktober 2024, 09:40
783 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Einführung

In Fortsetzung des im vorigen Artikel begonnenen Themas über Trailing-Stops werden wir hier Trailing-Klassen für die bequeme Erstellung verschiedener Algorithmen für Trailing-StopLoss-Positionen betrachten. Auf der Grundlage der erstellten Klassen ist es möglich, einen beliebigen Algorithmus für die Verschiebung von Stop-Levels zu erstellen: durch Stop-Shift vom aktuellen Preis, durch Indikatoren, durch festgelegte StopLoss-Level-Werte, usw. Nach der Lektüre des Artikels werden wir in der Lage sein, beliebige Algorithmen zur Verschiebung von Stop-Positionen zu erstellen und mit beliebigen EAs zu verbinden. Gleichzeitig wird der Anschluss und die Verwendung von Schleppern bequem und übersichtlich.

Betrachten wir kurz den Algorithmus der Trailing-Stop-Operation. Einigen wir uns darauf, dass drei Betriebsbedingungen für jedes Trailing verwendet werden können:

  • trailing start — Anzahl der Punkte des Positionsgewinns, bei deren Erreichen ein Trailing-Stop ausgelöst wird;
  • trailing step — Anzahl der Punkte, um die sich der Preis in Richtung des Positionsgewinns bewegen sollte, um die nächste Verschiebung der Position StopLoss zu erreichen;
  • trailing distance — Abstand zum aktuellen Kurs, bei dem sich StopLoss befindet.

Diese drei Parameter können auf jede Art von Trailing angewendet werden. Jeder dieser Parameter kann in den Einstellungen für das Trailing vorhanden sein oder fehlen, wenn er nicht benötigt wird oder durch einen anderen Wert im Trailing-Algorithmus ersetzt wird. Ein Beispiel für das Ersetzen des Parameters „trailing distance“ kann der Indikatorwert sein, auf den der Stop-Loss der Position gesetzt ist. In diesem Fall wird bei Verwendung dieses Parameters der Stopp nicht auf den vom Indikator angezeigten Kurs gesetzt, sondern mit einem Abstand vom angezeigten Kurs um den Abstandswert in Punkten.

Im Allgemeinen sind die drei aufgeführten Parameter die am häufigsten verwendeten in verschiedenen Trailing-Formen, und wir werden sie bei der Erstellung von Trailing-Klassen berücksichtigen.

Wenn Sie eine Stop-Loss-Position auf den gewünschten Kurs verschieben, sollten Sie die folgenden Prüfungen durchführen:

  • Der StopLoss-Kurs sollte nicht näher am aktuellen Kurs liegen als der Wert des StopLevel-Levels des Symbols (SYMBOL_TRADE_STOPS_LEVEL — minimale Abweichung in Punkten vom aktuellen Schlusskurs für das Setzen von Stop-Orders);
  • Der StopLoss-Kurs sollte nicht mit dem bereits gesetzten Kurs übereinstimmen und bei einer Kaufposition höher und bei einer Verkaufsposition niedriger sein als das aktuelle Stop-Loss-Niveau;
  • Wenn der Trailing-Algorithmus die oben aufgeführten Parameter verwendet, müssen die durch diese Parameter festgelegten Bedingungen zusätzlich überprüft werden.

Es handelt sich um grundlegende Kontrollen. Jeder Trailing-Algorithmus funktioniert auf dieselbe Weise: Der für das Setzen des Stopps erforderliche Kurs wird eingegeben, alle notwendigen Einschränkungen werden überprüft und, wenn alle Prüfungen bestanden sind, wird die Stopp-Position auf das angegebene Niveau verschoben.

So kann ein einfaches Trailing funktionieren:

  1. Im Parameter „trailing start“ wird der Gewinn in Punkten angegeben, bei dessen Erreichen das Trailing gestartet werden soll, oder Null, wenn dieser Parameter nicht verwendet wird.
  2. Der Parameter „trailing step“ gibt an, nach wie vielen Gewinnpunkten der Stopp-Level dem Kurs folgend nach oben gezogen werden soll, oder Null, wenn dieser Parameter nicht verwendet wird.
  3. Der Parameter „trailing distance“ legt den Abstand zwischen dem aktuellen Kurs und dem Positionsstopp fest, an dem sich die Stopp-Stufe befinden soll, oder Null, wenn dieser Parameter nicht verwendet wird.
  4. Zusätzlich können wir ein Symbol und eine magische Anzahl von Positionen festlegen, die nachgezogen werden sollen, oder NULL bzw. -1, wenn wir die Positionen aller Symbole mit einer beliebigen magischen Zahl nachziehen wollen.

Wenn der Positionsgewinn die angegebene Anzahl von Punkten erreicht, wird das Trailing gestartet, und der Positionsstopp wird in der angegebenen Entfernung vom aktuellen Kurs gesetzt. Nachdem sich der Kurs um die angegebene Anzahl von Punkten in Richtung des Positionsgewinns bewegt hat (trailing step), wird der Positionsstopp erneut dem Kurs folgend verschoben, um den angegebenen Abstand zum Kurs einzuhalten. Dies geschieht so lange, bis der Kurs gegen die Position läuft. In diesem Fall bleibt der Stop auf dem bereits eingestellten Niveau. Sobald der Kurs diesen Wert erreicht, wird die Position durch StopLoss mit Gewinn geschlossen. Ein Trailing-Stop ermöglicht es uns also, den Gewinn zu sichern, indem wir eine Position schließen, wenn sich der Kurs umkehrt, und gleichzeitig dem Kurs erlauben, innerhalb des festgelegten Stop-Abstands zum Kurs zu „wandern“, indem wir den Stop allmählich nachziehen, wenn sich der Kurs in Richtung der Position bewegt.

Anstelle des Parameters „trailing distance“ kann auch ein Kurswert angegeben werden, der z.B. von einem Indikator stammt. In diesem Fall erhalten wir einen Indikator, der nachläuft. Wir können den Wert einer der vorangegangenen Kerzen übergeben (z.B. High, Low) - dann erhalten wir die „trailing“ Preise pro Kerze usw. Der grundlegende Algorithmus für die Nachverfolgung bleibt jedoch unverändert.

Um einen Trailing-Stop-Algorithmus zu erstellen, muss also zunächst ein einfacher Trailing-Algorithmus erstellt werden, der es uns ermöglicht, die erforderlichen Preise für StopLoss-Positionen an ihn zu übergeben, und der die StopLoss-Position auf dieses Niveau verschiebt, wobei alle erforderlichen Prüfungen durchgeführt werden.

Im vorangegangenen Artikel haben wir uns bereits mit den enthaltenen Merkmalen befasst, wo es ein einfaches Trailing gab, sowie mit dem Trailing verschiedener Indikatoren. Dieser Ansatz ermöglicht es uns, einen Trailing-Stop mit dem EA zu verbinden, um die Positionen des aktuellen Symbols zu verfolgen. Aber es hat auch seine Grenzen: Wir müssen einen Indikator im EA erstellen und sein Handle für jeden nachgezogenen Indikator an die Trailing-Funktion senden. Die Trailing-Funktionen selbst können nur mit den Positionen des aktuellen Symbols arbeiten.

Wenn wir Trailing-Klassen verwenden, können wir mehrere Instanzen einer Trailing-Klasse mit unterschiedlichen Einstellungen erstellen, und dann können alle auf diese Weise erstellten Trails gleichzeitig in einem EA nach einem bestimmten, vom Programmierer festgelegten Algorithmus arbeiten. Mit anderen Worten, wir erstellen ein nutzerdefiniertes Trailing, das nach seinem eigenen Algorithmus in einer Reihe von Code-Strings für jeden Satz von Parametern arbeiten soll. Jedes erstellte Trailing kann im EA unter bestimmten Bedingungen gestartet werden, wodurch komplexe Algorithmen für Trailing-StopLoss-Positionen entstehen.

Selbst wenn wir das Trailing zweimal identisch für verschiedene Symbole erstellen, wird für jedes Symbol eine eigenes Trailing erstellt, die entsprechend den Daten des Symbols arbeitet, die bei der Erstellung der Nachverfolgung angegeben wurden. Dies vereinfacht die Verwendung von Trailing in Ihren Programmen erheblich: Wir müssen nur ein Trailing mit den erforderlichen Parametern erstellen und es in den EA-Handlern aufrufen. Fairerweise sei darauf hingewiesen, dass dieser Ansatz bei einigen Algorithmen möglicherweise nicht optimal ist, da jede Art des Trailing eine eigene Suche nach vorhandenen Positionen im Terminal auslöst. Wenn wir einen wirklich universellen Algorithmus entwickeln, der für jedes Trailing dieselbe Liste von Terminalpositionen verwendet, kann es sich herausstellen, dass die Kosten für seinen Entwurf und seine Entwicklung nicht mit seiner Nachfrage vergleichbar sind. Außerdem würde dieses Thema den Rahmen dieses Artikels sprengen.

Zunächst müssen wir also die Grundlagen schaffen: eine einfache Trailing-Stop-Klasse, die den Stop-Loss von Positionen auf einen bestimmten Preis verschiebt und dabei alle notwendigen Prüfungen durchführt.

Im vorangegangenen Artikel wurde dieses einfache Basis-Trailing als TrailingStopByValue() bezeichnet. Hier nennen wir es SimpleTrailing. Es ist vollständig in nutzerdefinierten Programmen zu verwenden.

Wie im vorangegangenen Artikel werden wir alle Trailing-Klassen in einer Datei unterbringen. Dann wird diese Datei einfach mit dem EA verbunden. Normalerweise werden Include-Dateien im Ordner \MQL5\Include\ oder in dessen Unterordnern gespeichert.


Basis der Trailing-Klasse

Erstellen Sie im Terminalordner \MQL5\Include\ den neuen Unterordner Trailings\, der die neue Klassendatei namens Trailings.mqh enthält:



Der Klassenname sollte CSimpleTrailing lauten, der Name der erstellten Datei ist Trailings.mqh, während die Klasse des Basisobjekts der CObject Standard Library als Basisklasse dienen sollte:


Nachdem der MQL-Assistent seine Arbeit beendet hat, erhalten wir die folgende Klassenvorlage in der Datei \MQL5\Include\Trailings\Trailings.mqh:

//+------------------------------------------------------------------+
//|                                                    Trailings.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
class CSimpleTrailing : public CObject
  {
private:

public:
                     CSimpleTrailing();
                    ~CSimpleTrailing();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::~CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+


Fügen wir die Datei des Basisobjekts der Standardbibliothek in die erstellte Datei ein:

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

#include <Object.mqh>

//+------------------------------------------------------------------+
//| Class of the position StopLoss simple trailing                   |
//+------------------------------------------------------------------+
class CSimpleTrailing : public CObject

Warum ist es notwendig, alle erstellten Nachfolgeklassen von dem Basisobjekt der Standardbibliothek zu erben?

Auf diese Weise lassen sich aus diesen Klassen leicht Sammlungen erstellen, die in Listen abgelegt werden können, in denen nach den gewünschten Objekten gesucht werden kann, usw. Mit anderen Worten, wenn wir nur zukünftige Klassenobjekte als Instanzen in EA-Dateien verwenden wollen, ist es nicht notwendig, von CObject abzuleiten. Wenn wir aber aus diesen Klassen vollwertige Sammlungen machen und alle Möglichkeiten der Standardbibliothek nutzen wollen, dann sollten diese Klassen von der Basisklasse CObject abgeleitet werden:

Die Klasse CObject ist die Basisklasse für den Aufbau der MQL5-Standardbibliothek und bietet allen ihren Nachkommen die Fähigkeit, ein Element einer verknüpften Liste zu sein.
Darüber hinaus wird eine Reihe virtueller Methoden für die weitere Implementierung in nachgeordneten Klassen definiert.

Wir deklarieren drei Methoden im privaten Bereich der Klasse:

class CSimpleTrailing : public CObject
  {
private:
//--- check the criteria for modifying the StopLoss position and return the flag
   bool              CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick);

//--- modify StopLoss of a position by its ticket
   bool              ModifySL(const ulong ticket, const double stop_loss);

//--- return StopLevel in points
   int               StopLevel(void);

protected: 

Die Funktion CheckCriterion() gibt ein Flag zurück, das besagt, dass alle erforderlichen Prüfungen für die Änderung der StopLoss-Position erfolgreich bestanden wurden (oder nicht).

Die Methode ModifySL() ändert den Stop Loss der Position um den angegebenen Wert.

Die Methode StopLevel() gibt den Wert der Eigenschaft StopLevel des Symbols zurück.

Im vorherigen Artikel waren alle diese Methoden separate Funktionen. Jetzt werden sie als Teil der Basisklasse für Anhänger funktionieren, von der wir dann andere Klassen für andere Anhänger erben werden.

Deklarieren Sie alle Variablen, die für das Funktionieren der Klasse erforderlich sind, im geschützten Bereich der Klasse:

protected: 
   string            m_symbol;                        // trading symbol
   long              m_magic;                         // EA ID
   double            m_point;                         // Symbol Point
   int               m_digits;                        // Symbol digits
   int               m_offset;                        // stop distance from price
   int               m_trail_start;                   // profit in points for launching trailing
   uint              m_trail_step;                    // trailing step
   uint              m_spread_mlt;                    // spread multiplier for returning StopLevel value
   bool              m_active;                        // trailing activity flag

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:

Von allen hier angegebenen Variablen ist nur der „Spread-Multiplikator“ erklärungsbedürftig. Der Wert des Symbols StopLevel ist der Abstand zum aktuellen Kurs, unterhalb dessen ein Stop Loss nicht platziert werden kann. Wenn der StopLevel-Wert auf dem Server auf Null gesetzt ist, bedeutet das nicht, dass es keinen StopLevel gibt. Stattdessen bedeutet dies, dass der Stopp-Level variabel ist und normalerweise zwei Spreads entspricht. Oder manchmal drei. Es hängt alles von den Servereinstellungen ab. Daher können wir diesen Multiplikator hier selbst festlegen. Wenn für den StopLevel-Wert eine doppelte Spanne verwendet wird, wird der Wert in der Variablen m_spread_mlt auf zwei gesetzt (Standard). Wenn es sich um einen dreifachen Aufstrich handelt, dann drei usw.

Die virtuelle Methode GetStopLossValue() berechnet und liefert den StopLoss-Kurs für eine Position. Bei den verschiedenen Arten von Trailing-Stops wird das Stop-Loss-Niveau, auf dem der Positionsstopp platziert werden sollte, unterschiedlich berechnet. Aus diesem Grund wird diese Methode als virtuell deklariert und sollte in jeder geerbten Trailing-Klasse überschrieben werden, wenn die Stop-Loss-Berechnung in ihnen von dem abweicht, was in dieser Klassenmethode geschrieben wird.

Im öffentlichen Teil der Klasse schreiben wir die Methoden zum Setzen und Zurückgeben der Werte für die im geschützten Teil der Klasse deklarierten Variablen sowie die Klassenkonstruktoren und einen Destruktor:

public:
//--- set trailing parameters
   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }
   void              SetMagicNumber(const long magic)       { this.m_magic = magic;       }
   void              SetStopLossOffset(const int offset)    { this.m_offset = offset;     }
   void              SetTrailingStart(const int start)      { this.m_trail_start = start; }
   void              SetTrailingStep(const uint step)       { this.m_trail_step = step;   }
   void              SetSpreadMultiplier(const uint value)  { this.m_spread_mlt = value;  }
   void              SetActive(const bool flag)             { this.m_active = flag;       }

//--- return trailing parameters
   string            Symbol(void)                     const { return this.m_symbol;       }
   long              MagicNumber(void)                const { return this.m_magic;        }
   int               StopLossOffset(void)             const { return this.m_offset;       }
   int               TrailingStart(void)              const { return this.m_trail_start;  }
   uint              TrailingStep(void)               const { return this.m_trail_step;   }
   uint              SpreadMultiplier(void)           const { return this.m_spread_mlt;   }
   bool              IsActive(void)                   const { return this.m_active;       }

//--- launch trailing with StopLoss offset from the price
   bool              Run(void);

//--- constructors
                     CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()),
                                         m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {}
                     CSimpleTrailing(const string symbol, const long magic,
                                     const int trailing_start, const uint trailing_step, const int offset);
//--- destructor
                    ~CSimpleTrailing() {}
  };


Die Klasse wird zwei Konstruktoren haben:

  • Der erste (Standard-)Konstruktor verwendet das aktuelle Symbol, die magische Zahl wird auf -1 gesetzt (für alle Positionen des aktuellen Symbols, ohne Ausnahme), während die Trailing-Parameter auf Null gesetzt werden.
  • Der zweite Konstruktor wird parametrisch sein, d.h. seine formalen Parameter übergeben dem Konstruktor einen Symbolnamen, eine magische Positionsnummer und drei nachgestellte Parameter, Start, Step und Shift.

Betrachten wir nun die Implementierung des parametrischen Konstruktors:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic,
                                 const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2)
  {
   this.SetSymbol(symbol);
   this.m_magic      = magic;
   this.m_trail_start= trail_start;
   this.m_trail_step = trail_step;
   this.m_offset     = offset;
  }

Im Gegensatz zum Standardkonstruktor, bei dem die Initialisierungszeichenfolge alle Variablen auf den aktuellen Symbolwert und Nullwerte für die nachgestellten Parameter setzt, erhält der Konstruktor hier die auf die entsprechenden Variablen gesetzten Werte. Die Methode SetSymbol() setzt Werte für mehrere Variablen auf einmal:

   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }

In beiden Konstruktoren wird der Spread-Multiplikator auf 2 gesetzt. Falls erforderlich, kann er mit der Methode SetSpreadMultiplier() geändert werden.


Die virtuelle Methode, die das StopLoss-Niveau der ausgewählten Position berechnet und zurückgibt:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(tick.bid - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(tick.ask + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }

Da es sich um ein einfaches Trailing handelt, wird der Stop-Loss der Position immer in einem bestimmten Abstand zum aktuellen Kurs gehalten.

Die berechneten StopLoss-Werte der Positionen werden von der Methode zurückgegeben und dann in der Methode CheckCriterion() überprüft:

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false'
   if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0)
      return false;
      
//--- trailing variables
   double trailing_step = this.m_trail_step * this.m_point;                      // convert the trailing step into price
   double stop_level    = this.StopLevel() * this.m_point;                       // convert the symbol StopLevel 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) / this.m_point);              // calculate the position profit in points
         if(tick.bid - stop_level > value_sl                                     // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) 
            && pos_sl + trailing_step < value_sl                                 // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // 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) / this.m_point);              // calculate the position profit in points
         if(tick.ask + stop_level < value_sl                                     // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained)
            && (pos_sl - trailing_step > value_sl || pos_sl == 0)                // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // 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;
     }
     
//--- conditions are not met - return 'false'
   return false;
  }

Wenn der an die Methode übergebene StopLoss-Wert alle Kriterien aller Filter erfüllt, gibt die Methode „true“ zurück und der StopLoss der Position wird mit der Methode ModifySL() geändert:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if the EA stop flag is set, report this in the journal and return 'false'
   if(::IsStopped())
     {
      Print("The Expert Advisor is stopped, trading is disabled");
      return false;
     }
//--- 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, this.m_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;
  }


Um den Stop-Level zu erhalten, verwenden wir die Methode StopLevel():

//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int CSimpleTrailing::StopLevel(void)
  {
   int spread     = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD);
   int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
   return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level);
  }

Da das Stoppniveau kein konstanter Wert ist und sich dynamisch ändern kann, werden bei jedem Aufruf der Methode die erforderlichen Werte aus den Symboleigenschaften abgefragt. Wenn der StopLevel eines Symbols auf Null gesetzt ist, verwenden wir den Wert der Symbolspanne multipliziert mit 2. Dies ist der Standardwert.

Wir haben alle drei oben genannten Methoden bereits im vorherigen Artikel als separate Funktionen implementiert. Jetzt sind diese Methoden im privaten Bereich der Klasse versteckt und führen ihre Funktionen aus, ohne dass sie von außen zugänglich sind.

Die wichtigste Trailing-Methode ist die Methode Run(), die den Zyklus der Auswahl von Positionen und der Verschiebung ihrer Stop-Levels auf die berechneten Werte startet:

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- if disabled, leave
   if(!this.m_active)
      return false;
//--- trailing variables
   MqlTick tick = {};                           // price structure
   bool    res  = true;                         // result of modification of all positions
//--- check the correctness of the data by symbol
   if(this.m_point==0)
     {
      //--- let's try to get the data again
      ::ResetLastError();
      this.SetSymbol(this.m_symbol);
      if(this.m_point==0)
        {
         ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError());
         return false;
        }
     }
     
//--- 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);
      
      //--- if the position does not match the filter by symbol and magic number, leave
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      //--- if failed to get the prices, move on
      if(!::SymbolInfoTick(this.m_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);
      
      //--- get the calculated StopLoss level
      double value_sl = this.GetStopLossValue(pos_type, tick);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable
      if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
         res &=this.ModifySL(pos_ticket, value_sl);
     }
//--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter
   return res;
  }

Die Methode geht die Positionen in der Liste der aktiven Positionen des Terminals durch, sortiert sie nach Symbol und magischer Zahl und verschiebt die Stopps der Positionen auf den berechneten Abstand zum aktuellen Kurs. Das Ergebnis der StopLoss-Änderung jeder Position wird der Variablen res hinzugefügt. Am Ende des Zyklus der Änderung der Stop-Levels aller Positionen setzen wir entweder true (wenn die Stop-Levels aller Positionen, die dem Symbol/Magic Filter entsprechen, erfolgreich geändert wurden), oder, wenn mindestens eine Position nicht erfolgreich geändert wurde, enthält das Flag den Wert false. Dies dient der zusätzlichen Kontrolle über die Änderung der Stopps von außen.

Verbinden wir nun diese Klasse mit dem EA, um ihre Funktionalität zu testen.

Um den Test durchzuführen, verwenden wir die Datei Moving Average.mq5 aus dem Terminal-Ordner \MQL5\Experts\Examples\ und speichern sie als MovingAverageWithSimpleTrail.mq5.

Wir binden die Trailing-Klassendatei in den EA ein, geben zusätzliche Parameter in die Einstellungen ein und erstellen eine Instanz einer einfachen Trailing-Klasse:

//+------------------------------------------------------------------+
//|                                 MovingAverageWithSimpleTrail.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 <Trade\Trade.mqh>
#include <Trailings\Trailings.mqh>

input group  " - Moving Averages Expert Parameters -"
input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift

input group  " - Simple Trailing Parameters -"
input int   InpTrailingStart  =  10;      // Trailing start
input int   InpTrailingStep   =  20;      // Trailing step in points
input int   InpTrailingOffset =  30;      // Trailing offset in points
input bool  InpUseTrailing    =  true;    // Use Trailing Stop

//---
int    ExtHandle=0;
bool   ExtHedging=false;
CTrade ExtTrade;

CSimpleTrailing ExtTrailing;              // simple trailing class instance

#define MA_MAGIC 1234501
//+------------------------------------------------------------------+


In OnInit() setzen wir die Parameter für das Trailing:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- prepare trade class to control positions if hedging mode is active
   ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   ExtTrade.SetExpertMagicNumber(MA_MAGIC);
   ExtTrade.SetMarginMode();
   ExtTrade.SetTypeFillingBySymbol(Symbol());
//--- Moving Average indicator
   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }
     
//--- set trailing parameters
   ExtTrailing.SetActive(InpUseTrailing);
   ExtTrailing.SetSymbol(Symbol());
   ExtTrailing.SetMagicNumber(MA_MAGIC);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
//--- ok
   return(INIT_SUCCEEDED);
  }


In OnTick() wird das Trailing gestartet:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
      
//--- launch trailing
   ExtTrailing.Run();
  }


Dies ist alles, was zusätzlich in die EA-Datei eingegeben werden muss. Wir kompilieren sie und lassen sie im Strategietester auf EURUSD M15 laufen, ein einziger Test für das letzte Jahr. Alle Ticks ohne Ausführungsverzögerung.

Die Parameter lauten wie folgt (Trailing ist deaktiviert):


Führen wir den Test mit deaktiviertem Trailing durch und sehen wir uns das Ergebnis an:



Nun aktivieren wir die Verwendung des Trailings:


und führen Sie genau denselben Test durch.

Das Testergebnis lautet wie folgt:



Das Ergebnis ist etwas besser. Es ist klar, dass das Trailing die Statistik bei diesen Einstellungen leicht verbessert.

Nun können wir auf der Grundlage der erstellten einfachen Trailing beliebige andere Formen von Trailing vornehmen.

Implementieren wir mehrere Indikator-Trailing-Klassen, darunter Parabolic SAR und verschiedene gleitende Durchschnitte aus der Liste der Trendfolge-Indikatoren im Client-Terminal präsentiert.


Trailing-Klassen mit Indikatoren

Fahren wir fort, den Code in derselben Datei zu schreiben. Im Falle von indikatorbasierten Trailing müssen wir den erforderlichen Indikator erstellen. Zu diesem Zweck müssen wir ein Symbol und einen Zeitrahmen angeben, die für die Erstellung einer Datenreihe verwendet werden. Außerdem müssen wir das Handle des erstellten Indikators in einer Variablen speichern, um auf den Indikator zugreifen zu können. Der Empfang von Daten per Handle hängt nicht vom Indikatortyp ab — die Daten erhalten wir von CopyBuffer(), während der Indikator per Handle angegeben wird.

Alle Aktionen zum Abrufen von Daten aus dem Indikator sollten in den indikatorbasierten Trailing.Klassen gesetzt werden. In diesem Fall müssen wir alle diese Aktionen in jeder Klasse der verschiedenen Indikatoren für die Trailing-Phase festlegen. Dies ist möglich, aber nicht optimal. Es ist besser, eine Basisklasse von indikatorbasierten Trailing zu erstellen, die Methoden für den Erhalt von Daten von einem Indikator per Handle implementiert und einige Variablen angibt, die für jeden Indikator gleich sind (Symbol, Zeitrahmen, Datenzeitreihenindex und Indikator-Handle). Die Trailing-Klassen selbst werden von der Basisklasse abgeleitet. Dann wird die Klassenstruktur organisiert. Die Basisklasse ermöglicht den Zugriff auf die Indikatordaten per Handle, während die geerbte Klasse die Erstellung des gewünschten Indikators und die Handhabung seiner Daten beinhaltet.

Fahren wir mit dem Schreiben von Code in der Datei Trailings.mqh fort. Implementieren wir eine Basis-Klasse für das indikatorbasierten Trailing:

//+------------------------------------------------------------------+
//| Base class of indicator-based trailings                          |
//+------------------------------------------------------------------+
class CTrailingByInd : public CSimpleTrailing
  {
protected:
   ENUM_TIMEFRAMES   m_timeframe;            // indicator timeframe
   int               m_handle;               // indicator handle
   uint              m_data_index;           // indicator data bar
   string            m_timeframe_descr;      // timeframe description

//--- return indicator data
   double            GetDataInd(void)              const { return this.GetDataInd(this.m_data_index); }
   
//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- set parameters
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)
                       {
                        this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                        this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7);
                       }
   void              SetDataIndex(const uint index)                  { this.m_data_index=index;       }
                       
//--- return parameters
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_timeframe;       }
   uint              DataIndex(void)                           const { return this.m_data_index;      }
   string            TimeframeDescription(void)                const { return this.m_timeframe_descr; }
     
//--- return indicator data from the specified timeseries index
   double            GetDataInd(const int index) const;
                      
//--- constructors
                     CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), 
                                            m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {}
                     
                     CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                    CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE)
                       {
                        this.SetTimeframe(timeframe);
                       }
//--- destructor
                    ~CTrailingByInd(void)
                       {
                        if(this.m_handle!=INVALID_HANDLE)
                          {
                           ::IndicatorRelease(this.m_handle);
                           this.m_handle=INVALID_HANDLE;
                          }
                       }
  };

Die Klasse wird von der einfachen Trailing-Klasse abgeleitet, sodass die gesamte Funktionalität dieser Klasse sowie die Methoden für den Zugriff auf Indikatordaten per Handle in ihr verfügbar sind.

Im geschützten Teil der Klasse werden Klassenvariablen deklariert, eine Methode zum Empfang von Daten vom Indikator per Handle und eine virtuelle Methode zur Berechnung der StopLoss-Levels, die die gleichnamige Methode der übergeordneten Klasse außer Kraft setzt.

Der öffentliche Teil der Klasse enthält die Methoden zum Setzen und Abrufen der Indikatoreigenschaften, die Konstruktoren und den Destruktor.

Eine magische Zahl mit dem Wert -1 (alle Positionen) und Null-Parameter für das Trailing werden in der Initialisierungszeichenfolge des Standardkonstruktors an die übergeordnete Klasse übergeben. Für die Berechnung des Indikators wird der aktuelle Chart-Zeitrahmen verwendet.

Im parametrischen Konstruktor werden das Symbol und der Zeitrahmen des Charts für die Berechnung des Indikators sowie alle Parameter für das Trailing in den formalen Parametern des Konstruktors übergeben.
Im Destruktor der Klasse wird der Berechnungsteil des Indikators freigegeben, während der Wert von INVALID_HANDLE in der Variablen, die den Handle speichert, gesetzt wird.


Die Methode, die Indikatordaten aus dem angegebenen Zeitreihenindex zurückgibt:

//+------------------------------------------------------------------+
//| Return indicator data from the specified timeseries index        |
//+------------------------------------------------------------------+
double CTrailingByInd::GetDataInd(const int index) const
  {
//--- if the handle is invalid, report this and return "empty value"
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- get the value of the indicator buffer by the specified index
   double array[1];
   ::ResetLastError();
   if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1)
     {
      ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError());
      return EMPTY_VALUE;
     }
//--- return the received value
   return array[0];
  }

Wir verwenden CopyBuffer(), um den Wert eines einzelnen Indikatorbalkens mit dem angegebenen Index abzurufen und ihn bei erfolgreichem Erhalt zurückzugeben. Im Falle eines Fehlers wird eine Meldung im Journal angezeigt und ein leerer Wert (EMPTY_VALUE) zurückgegeben.

Die Funktion CopyBuffer() arbeitet mit den Daten eines beliebigen Indikators über dessen Handle. Somit ist diese Methode universell und wird ohne Änderungen in jeder Klasse von indikatorbasierten Nachverfolgungen verwendet.


Die virtuelle Methode, die das StopLoss-Niveau der ausgewählten Position berechnet und zurückgibt:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- get the indicator value as a level for StopLoss
   double data=this.GetDataInd();
       
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid);
      case POSITION_TYPE_SELL :  return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask);
      default                 :  return 0;
     }
  }

Bei dieser Methode, d. h. in der übergeordneten Klasse, berechnen wir einfach das Stop-Loss-Niveau, das vom Preis abgezogen wird. In derselben Klasse der Methode müssen Daten vom Indikator empfangen und als StopLoss-Level übergeben werden. Wenn die Daten nicht vom Indikator empfangen wurden, gibt die Methode den aktuellen Preis zurück. Dies verhindert, dass wir einen Stop-Loss für eine Position setzen können, da die StopLevel-Ebene es uns nicht erlaubt, ein Stop-Level für den aktuellen Preis zu setzen.

Infolgedessen implementiert diese Klasse den Zugriff auf die Indikatordaten durch ihren Handle und die Berechnung des Niveaus für die StopLoss-Position durch den Indikatorwert, und alle ihre Methoden werden in den untergeordneten Klassen verfügbar sein.

Nun können wir auf der Grundlage der erstellten Klasse indikatorbasierte Trailing-Klassen implementieren.


Trailing-Klassen mit dem Parabolic SAR

Fahren wir mit dem Schreiben von Code in der gleichen Datei \MQL5\Include\Trailings\Trailings.mqh fort.

Wie alle anderen Klassen das indikatorbasierten Trailing sollte auch die Trailing-Klasse des Parabolic SAR von der Basisklasse des indikatorbasierten Trailings abgeleitet werden:

//+------------------------------------------------------------------+
//| Parabolic SAR position StopLoss trailing class                   |
//+------------------------------------------------------------------+
class CTrailingBySAR : public CTrailingByInd
  {
private:
   double            m_sar_step;             // Parabolic SAR Step parameter
   double            m_sar_max;              // Parabolic SAR Maximum parameter

public:
//--- set Parabolic SAR parameters
   void              SetSARStep(const double step)                   { this.m_sar_step=(step<0.0001 ? 0.0001 : step);   }
   void              SetSARMaximum(const double max)                 { this.m_sar_max =(max <0.0001 ? 0.0001 : max);    }
   
//--- return Parabolic SAR parameters
   double            SARStep(void)                             const { return this.m_sar_step;                          }
   double            SARMaximum(void)                          const { return this.m_sar_max;                           }
   
//--- create Parabolic SAR indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum);

//--- constructors
                     CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2);
                       }
                     
                     CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const double sar_step, const double sar_maximum,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingBySAR(){}
  };

Im privaten Bereich der Klasse deklarieren wir die Variablen, in denen die Werte der Parameter des Parabolic SAR-Indikators gespeichert werden.

Der öffentliche Teil enthält die Methoden zum Setzen und Zurückgeben der Werte von Indikatoreigenschaften. Wir haben die Methode zur Erstellung eines Indikators und zwei Konstruktoren - den Standard- und den parametrischen Konstruktor - angegeben.

Im Konstruktor wird standardmäßig ein Indikator für das aktuelle Symbol und die aktuelle Chart-Periode erstellt, während die Werte der Parameter für das Trailing auf Null gesetzt werden.

Im parametrischen Konstruktor werden alle Parameter über die Eingänge des Konstruktors übergeben, und dann wird ein Indikator auf der Grundlage der an den Konstruktor übergebenen Parameter erstellt:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const double sar_step, const double sar_maximum,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, sar_step, sar_maximum);
  }


Die Methode Initialize() erstellt den Parabolic SAR-Indikator und gibt das Ergebnis seiner Erstellung zurück:

//+------------------------------------------------------------------+
//| create Parabolic SAR indicator and return the result             |
//+------------------------------------------------------------------+
bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetSARStep(sar_step);
   this.SetSARMaximum(sar_maximum);
   ::ResetLastError();
   this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }


Testen wir die erstellte Parabolic SAR-basierte Trailing-Klasse.

Um den Test durchzuführen, verwenden wir den EA aus der Standardlieferung \MQL5\Experts\Advisors\ExpertMACD.mq5, speichern ihn als ExpertMACDWithTrailingBySAR.mq5 und fügen die erforderlichen Code-Strings hinzu.

Wir binden die Datei mit den Trailing-Klasse ein, fügen die neue Eingänge hinzu und deklarieren die Instanz der Trailing-Klasse:

//+------------------------------------------------------------------+
//|                                                   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 <Trailings\Trailings.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;

input group  " - Trailing By SAR 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 int               InpTrailingStart  =  0;                // Trailing Start
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingBySAR ExtTrailing;


In OnInit() initialisieren wir das Parabolic-Trailing:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR))
     {
      ExtTrailing.SetMagicNumber(Expert_MagicNumber);
      ExtTrailing.SetTrailingStart(InpTrailingStart);
      ExtTrailing.SetTrailingStep(InpTrailingStep);
      ExtTrailing.SetStopLossOffset(InpTrailingOffset);
      ExtTrailing.SetActive(InpUseTrailing);
     }
   
//--- Initializing expert


In OnTick() lassen wir das Trailing arbeiten:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   ExtTrailing.Run();
  }


Kompilieren wir den EA und führen ihn im Tester mit Standardparametern aus:



Wie wir sehen können, werden die Stop-Levels der Positionen korrekt von den Werten des ersten Parabolic SAR nachgezogen.

Erstellen wir nun Trailing-Klassen für verschiedene gleitende Durchschnitte, die im Client-Terminal angezeigt werden.


Trailing-Klassen mit Gleitendem Durchschnitt

Diese Klassen unterscheiden sich von der Trailing-Klasse des Parabolic SAR nur durch die Menge der Eingaben und durch die Tatsache, dass die Methode Initialize() einen der Klasse entsprechenden Indikator erzeugt.

Auf dieser Grundlage werden wir nur eine Klasse betrachten: die Trailing-Klasse auf der Grundlage des adaptiven gleitenden Durchschnitt (AMA):

//+------------------------------------------------------------------+
//| Adaptive Moving Average position StopLoss trailing class         |
//+------------------------------------------------------------------+
class CTrailingByAMA : public CTrailingByInd
  {
private:
   int               m_period;               // Period AMA parameter
   int               m_fast_ema;             // Fast EMA Period parameter
   int               m_slow_ema;             // Slow EMA Period parameter
   int               m_shift;                // Shift AMA parameter
   ENUM_APPLIED_PRICE m_price;               // Applied Price AMA parameter

public:
//--- set AMA parameters
   void              SetPeriod(const uint period)                    { this.m_period=int(period<1 ? 9 : period);     }
   void              SetFastEMAPeriod(const uint period)             { this.m_fast_ema=int(period<1 ? 2 : period);   }
   void              SetSlowEMAPeriod(const uint period)             { this.m_slow_ema=int(period<1 ? 30 : period);  }
   void              SetShift(const int shift)                       { this.m_shift = shift;                         }
   void              SetPrice(const ENUM_APPLIED_PRICE price)        { this.m_price = price;                         }
   
//--- return AMA parameters
   int               Period(void)                              const { return this.m_period;                         }
   int               FastEMAPeriod(void)                       const { return this.m_fast_ema;                       }
   int               SlowEMAPeriod(void)                       const { return this.m_slow_ema;                       }
   int               Shift(void)                               const { return this.m_shift;                          }
   ENUM_APPLIED_PRICE Price(void)                              const { return this.m_price;                          }
   
//--- create AMA indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price);
   
//--- constructors
                     CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE);
                       }
                     
                     CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingByAMA(){}
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price);
  }
//+------------------------------------------------------------------+
//| create AMA indicator and return the result                       |
//+------------------------------------------------------------------+
bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetPeriod(period);
   this.SetFastEMAPeriod(fast_ema);
   this.SetSlowEMAPeriod(slow_ema);
   this.SetShift(shift);
   this.SetPrice(price);
   ::ResetLastError();
   this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema,
                    ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }

Die Klasse ist absolut identisch mit der oben erwähnten Parabolic SAR-basierten Trailing-Klasse. Jede MA-basierte Trailing-Klasse muss über einen eigenen Satz von Variablen zur Speicherung von Indikatorparametern sowie über Methoden zum Setzen und Zurückgeben der Werte, die diesen Variablen entsprechen, verfügen. Alle MA-basierten Trailing-Klassen sind identisch. Sie können sie studieren, indem Sie sich die Codes in der unten angehängten Datei Trailings.mqh ansehen. Sie können die MA-basierte Trailing-Klasse testen, indem Sie den Test-EA aus der Datei ExpertMACDWithTrailingByMA.mq5 starten, die ebenfalls unten angehängt ist.

Wir haben Klassen von einfachen Trailing und Trailings erstellt, die auf Standardindikatoren basieren, die im Terminal bereitgestellt werden und geeignet sind, Niveaus für die Platzierung von StopLoss-Positionen anzuzeigen.

Dies sind jedoch nicht alle Trailing-Typen, die mit den vorgestellten Klassen erstellt werden können. Es gibt Trailings, die die Stop-Levels der Positionen auf bestimmte Kursniveaus verschieben, die für Kauf- und Verkaufs-Positionen getrennt festgelegt werden. Ein Beispiel für ein solches Trailing kann ein Trailing sein, das auf Hoch/Tief-Kerzen oder beispielsweise auf dem Fraktalindikator basiert.

Um ein solches Trailing zu implementieren, sollten wir in der Lage sein, für jeden Positionstyp ein separates Niveau für die Festlegung eines Stop-Loss festzulegen. Lassen Sie uns eine solche Klasse erstellen.


Trailing-Klassen mit angegebenen StopLoss-Ebenen

Um ein Trailing auf Basis der angegebenen Werte zu erstellen, müssen lediglich Variablen anstelle von Indikatorparametern definiert werden, um Stop-Loss-Werte für Kauf- und Verkaufs-Positionen festzulegen. Und anstatt Daten aus dem Indikator zu erhalten, müssen wir nur die Werte für StopLoss-Positionen aus den in diesen Variablen angegebenen Werten berechnen. Die Werte sind beim Aufruf der abschließenden Run()-Methode direkt aus dem Steuerprogramm in die Variablen zu setzen.

Lassen Sie uns die folgende Klasse implementieren:

//+------------------------------------------------------------------+
//| Trailing class based on a specified value                        |
//+------------------------------------------------------------------+
class CTrailingByValue : public CSimpleTrailing
  {
protected:
   double            m_value_sl_long;     // StopLoss level for long positions
   double            m_value_sl_short;    // StopLoss level for short positions

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- return StopLoss level for (2) long and (2) short positions
   double            StopLossValueLong(void)    const { return this.m_value_sl_long;   }
   double            StopLossValueShort(void)   const { return this.m_value_sl_short;  }

//--- launch trailing with the specified StopLoss offset from the price
   bool              Run(const double value_sl_long, double value_sl_short);

//--- constructors
                     CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {}
                     
                     CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                      CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {}
//--- destructor
                    ~CTrailingByValue(void){}
  };

Es ist klar, dass die Klasse fast identisch mit allen Klassen von indikatorbasierten Trailing-Stops ist. Allerdings gibt es hier keine Initialize()-Methode, da kein Indikator erstellt werden muss.

In der virtuellen Methode, die das StopLoss-Niveau der ausgewählten Position berechnet und zurückgibt, werden die Werte für StopLoss aus den festgelegten Niveaus der Stop-Levels für Kauf- und Verkaufs-Positionen berechnet:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(this.m_value_sl_long  - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(this.m_value_sl_short + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }


Stop-Loss-Werte für Kauf- und Verkaufs-Positionen werden der Klasse über die formalen Parameter der Methode Run() übergeben:

//+------------------------------------------------------------------+
//| Launch trailing with the specified StopLoss offset from the price|
//+------------------------------------------------------------------+
bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short)
  {
   this.m_value_sl_long =value_sl_long;
   this.m_value_sl_short=value_sl_short;
   return CSimpleTrailing::Run();
  }

Zunächst werden die in den Formalparametern übergebenen Werte auf die Mitgliedsvariablen der Klasse gesetzt, und dann wird die Methode Run() der Elternklasse aufgerufen. Die neu definierte virtuelle Methode GetStopLossValue() wird von der übergeordneten Klasse aufgerufen, während die Stopp-Levels der Position auf die in ihr berechneten Werte gesetzt werden.


Verbinden eines Trailing-Stops mit einem EA

Wir haben oben bereits besprochen, wie man ein Trailing mit einem EA verbindet, wenn man indikatorbasierte Trails testet. Betrachten wir nun, wie eine Verbindung hergestellt und ein Trailing auf der Grundlage der übergebenen Werte, d. h. des Kerzenhochs und -tiefs, gestartet werden kann.

Fügen wir dem EA aus der Standardauslieferung \MQL5\Experts\Advisors\ExpertMACD.mq5 das Trailing hinzu. Wir speichern ihn als ExpertMACDWithTrailingByValue.mq5 und nehmen die notwendigen Verbesserungen vor.

Wir fügen die Datei mit den Trailing-Klassen in den EA ein, fügen die Trailing-Setup-Eingänge hinzu und deklarieren die wertbasierte Trailing-Klasseninstanz:

//+------------------------------------------------------------------+
//|                                                   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 <Trailings\Trailings.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;

input group  " - Trailing By Value Parameters -"
input ENUM_TIMEFRAMES   InpTimeframe      =  PERIOD_CURRENT;   // Data Rates Timeframe
input uint              InpDataRatesIndex =  2;                // Data Rates Index for StopLoss
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingStart  =  0;                // Trailing Start
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingByValue ExtTrailing;

Der eingegebene Datenkurs-Zeitrahmen ist der Zeitrahmen des Charts, aus der die Höchstkurse für die Stop-Levels der Verkaufspositionen und die Tiefstkurse für die Stop-Levels der Kaufpositionen genommen werden.

Der Datenkursindex für die StopLoss-Eingabe ist ein Balkenindex auf dem Chart mit dem Datenkurs-Zeitrahmen, aus dem die Hochs und Tiefs für die Festlegung der StopLoss-Position genommen werden sollen.


In OnInit() des EAs initialisieren wir die Parameter für das Trailing:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   ExtTrailing.SetMagicNumber(Expert_MagicNumber);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
   ExtTrailing.SetActive(InpUseTrailing);
   
//--- Initializing expert

Nur die Stopp-Levels der Positionen, deren magische Zahl mit der des EA übereinstimmt, werden nachgezogen. Mit anderen Worten, wir verfolgen nur die vom EA eröffneten Positionen.


In OnTick() des EAs holen wir uns die Daten des Balkens mit dem in der Input-Variablen InpDataRatesIndex angegebenen Index und starten das Trailing mit den StopLoss-Preisen (Bar Hoch und Tief des Balkens) für Kauf- und Verkaufs-Positionen:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   MqlRates rates[1]={};
   if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates))
      ExtTrailing.Run(rates[0].low,rates[0].high);
  }

Das ist alles, was wir tun müssen, um Trailing mit dem EA zu verbinden. Wie Sie vielleicht schon bemerkt haben, besteht der einzige Unterschied bei der Verbindung verschiedener Traings mit EA in der Deklaration von Instanzen verschiedener Trailing-Typen. Alle anderen Schritte zum Verbinden von Spuren sind nahezu identisch und sollten keine Fragen oder Zweifel aufwerfen.

Kompilieren wir den EA und führen ihn im visuellen Testmodus mit den Standardeinstellungen aus:

Wir können sehen, wie die Stop-Positionen auf die Hoch- und Tiefstwerte der Kerzen gesetzt werden, die ihren Index in der Zeitreihe 2 haben.

Inwieweit ein solches Trailing in einem Handelssystem für einen Gewinn sorgen kann, ist Gegenstand eines unabhängigen Tests.

Darüber hinaus kann jeder einen individuellen Trailing-Stop nach seinem eigenen Algorithmus erstellen, indem er die in diesem Artikel vorgestellten Klassen verwendet. Hier gibt es viele Möglichkeiten der Forschung, die nahezu unbegrenzt sind.


Schlussfolgerung

In diesem Artikel haben wir Klassen verschiedener Trailing-Stops erstellt, die es uns ermöglichen, einen Trailing-Stop in jeden EA einzubauen. Die erstellten Klassen sind auch ein gutes Instrumentarium für die Implementierung von Trailing-Stops mit nutzerdefinierten Algorithmen.

In den Beispielen wurde nur eine Möglichkeit der Arbeit mit Klassenobjekten behandelt - die Erstellung einer Instanz eines nachgeordneten Klassenobjekts. Auf diese Weise kann im Programm vorab festgelegt werden, welche Arten von Trailing erforderlich sind und welche Parameter das Trailing haben soll. Für jedes Trailing wird ein Objekt im globalen Bereich erstellt. Dieser Ansatz hat seine Berechtigung, denn er ist einfach und klar. Um jedoch eine dynamische Erstellung von Trailing-Objekten mit dem Operator „new“ direkt während der Programmausführung zu ermöglichen, ist es besser, die Möglichkeiten der Standardbibliothek zur Erstellung von verknüpften Objektlisten zu nutzen. Für diese Zwecke werden alle Trailing-Klassen vom Basisobjekt CObject der Standardbibliothek abgeleitet. Dieser Ansatz kann in den Kommentaren erörtert werden, da er den Rahmen des vorliegenden Themas sprengt.

Alle in diesem Artikel vorgestellten Klassen können in Ihren eigenen Entwicklungen „so wie sie sind“ verwendet werden, oder Sie können sie an Ihre Bedürfnisse und Aufgaben anpassen.


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

Сode Lock Algorithmus (CLA) Сode Lock Algorithmus (CLA)
In diesem Artikel werden wir Zahlenschlösser (Code Locks) neu überdenken und sie von Sicherheitsmechanismen in Werkzeuge zur Lösung komplexer Optimierungsprobleme verwandeln. Entdecken Sie die Welt der Zahlenschlösser, die nicht als einfache Sicherheitsvorrichtungen betrachtet werden, sondern als Inspiration für einen neuen Ansatz zur Optimierung. Wir werden eine ganze Population von Zahlenschlössern (Locks) erstellen, wobei jedes Schloss eine einzigartige Lösung für das Problem darstellt. Wir werden dann einen Algorithmus entwickeln, der diese Schlösser „knackt“ und optimale Lösungen in einer Vielzahl von Bereichen findet, vom maschinellen Lernen bis zur Entwicklung von Handelssystemen.
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 11): Automatisieren der Optimierung (erste Schritte) Entwicklung eines Expertenberaters für mehrere Währungen (Teil 11): Automatisieren der Optimierung (erste Schritte)
Um einen guten EA zu erhalten, müssen wir mehrere gute Parametersätze von Handelsstrategie-Instanzen für ihn auswählen. Dies kann manuell erfolgen, indem die Optimierung für verschiedene Symbole durchgeführt und dann die besten Ergebnisse ausgewählt werden. Aber es ist besser, diese Arbeit an das Programm zu delegieren und sich produktiveren Tätigkeiten zu widmen.
Aufbau des Kerzenmodells Trend-Constraint (Teil 8): Entwicklung eines Expert Advisors (I) Aufbau des Kerzenmodells Trend-Constraint (Teil 8): Entwicklung eines Expert Advisors (I)
In dieser Diskussion werden wir unseren ersten Expert Advisor in MQL5 erstellen, der auf dem Indikator basiert, den wir im vorherigen Artikel erstellt haben. Wir werden alle Funktionen abdecken, die erforderlich sind, um den Prozess zu automatisieren, einschließlich des Risikomanagements. Dies wird den Nutzern in hohem Maße zugute kommen, wenn sie von der manuellen Ausführung von Geschäften zu automatisierten Systemen übergehen.
Kometenschweif-Algorithmus (CTA) Kometenschweif-Algorithmus (CTA)
In diesem Artikel befassen wir uns mit der Optimierungsalgorithmus nach dem Kometenschweif (Comet Tail Optimization Algorithm, CTA), der sich von einzigartigen Weltraumobjekten inspirieren lässt - von Kometen und ihren beeindruckenden Schweifen, die sich bei der Annäherung an die Sonne bilden. Der Algorithmus basiert auf dem Konzept der Bewegung von Kometen und ihren Schweifen und ist darauf ausgelegt, optimale Lösungen für Optimierungsprobleme zu finden.