English Русский 中文 Español 日本語 Português
Cross-Plattform Expert Advisor: Eigene Stopps, Breakeven und Trailing

Cross-Plattform Expert Advisor: Eigene Stopps, Breakeven und Trailing

MetaTrader 5Beispiele | 26 Oktober 2017, 08:17
1 255 0
Enrico Lambino
Enrico Lambino

Inhaltsverzeichnis

  1. Einführung
  2. Eigene Stopps
  3. Modifizieren der Stopps
  4. Breakeven
  5. Nachgezogene Stopps
  6. Nachgezogene TakeProfits
  7. Umsetzung
  8. CTrails (Container)
  9. Erweiterung von CTrail
  10. Beispiele

Einführung

Im vorherigen Artikel: "Cross-Platform Expert Advisor: Stopps" wurde gezeigt, wie die Klasse CStop in dem plattformübergreifenden Expert Advisor StopLoss und TakeProfit für eine bestimmten Position festlegt. Der gewünschte Abstand kann in Pips oder Points angegeben werden. Leider ist das bei vielen, existierenden Expert Advisor nicht immer der Fall. StopLoss und TakeProfit werden oft nicht aufgrund der Entfernung vom Eröffnungspreis berechnet. Vielmehr werden in diesen Fällen die StopLoss- und/oder TakeProfit in Form des Preises ausgedrückt, meist als Ergebnis einer anderen Berechnung. In diesem Artikel wird diskutiert, wie die Klasse CStop verwendet werden kann, um StopLoss' und TakeProfits in Bezug auf den Chartpreis zu berechnen. Der Artikel beschreibt auch, wie solche Stopps durch die Verwendung der in diesem Artikel vorgestellten Bibliothek geändert werden können. Darüber hinaus bietet sie auch eine neue Klasse, CTrail, die sehr ähnlich den nutzerdefinierte Stopps funktioniert. Im Gegensatz zu den eigenen Stopps wird CTrail jedoch in erster Linie verwendet, um das Nachziehen von Stopps für die Dauer einer Position zu ermöglichen.

Eigene Stopps

Im vorherigen Artikel wurde gezeigt, wie StopLoss und TakeProfit auf Basis der Points in einem plattformübergreifenden Expert Advisor umgesetzt werden kann. Dies ist jedoch nicht bei allen EAs der Fall. Es gibt Strategien, die eine dynamische Berechnung von StopLoss- und TakeProfit erfordern, die in der Regel auf Zeitreihendaten (OHLC) oder den Ergebnissen technischer Indikatoren basieren.

Dynamisch berechnete StopLoss' und TakeProfits können in CStop durch die beiden Methoden StopLossCustom und TakeProfitCustom festgelegt werden. Der folgende Codeausschnitt zeigt die beiden Methoden:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

Der Programmierer kann diese Methoden beliebig erweitern, um gewünschte StopLoss' oder TakeProfits für jede beliebige Instanz von CStop zu verwenden.

Die beiden oben genannten Methoden sind tatsächlich überladene Methoden der Klasse. Es folgen die alternativen Methoden, die Werte vom Typ Boolean zurückgeben:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

Beide Funktionen liefern true zurück, wenn ein bestimmtes Klassenmitglied (entweder m_stoploss oder m_takeprofit) Null ist. Ihr Zweck wird in Kürze erläutert.

CStop berechnet einen Stopp gemäß folgender Richtlinie. Der Stopp kann ein StopLoss oder ein TakeProfit sein, die in CStop in Form der Variablen m_stoploss und m_takeprofit existieren. Gehen Sie in den folgenden Schritten davon aus, dass es sich nur um den StopLoss (m_stoploss) handelte:

  1. Wenn m_stoploss Null ist, verwenden Sie StopLossCustom für die Berechnung des StopLoss
  2. Wenn m_stoploss ungleich Null ist, verwenden Sie m_stoploss für die Berechnung der tatsächlichen StopLoss' der Position in Bezug auf den Eröffnungspreis

Das Gleiche gilt für die Berechnung des TakeProfit mit der Methode TakeProfitCustom und der Variablen m_takeprofit.

Die tatsächliche Anwendung dieser vier Methoden gliedert sich in zwei Hauptbereiche. Für die Hauptstopps findet sich der Methodenaufruf innerhalb des Order-Managers selbst (COrderManager). Für die anderen Stopps ist er innerhalb jeder Instanz der Position (COrder).

Es gibt Fälle, in denen die wichtigsten Stopps an den Broker zusammen mit der ersten Handelsanfrage geschickt werden, und daher benötigt der Expert Advisor diese Informationen in dem Moment, in dem er mit der Bearbeitung der Handelsanfrage beginnt, und nicht erst, nachdem er ihn erfolgreich versendet hat. Dies gilt für brokerseitigen Stopps im Metatrader 4 und Metatrader 5 im 'hedging mode'. Für diese Modi müssen die Informationen für die StopLoss' und TakeProfits in der Handelsanfrage der Funktion OrderSend (MQL4, MQL5) aufgenommen werden, und diese Stopps gelten nur für die betreffenden Hauptposition (nicht global).

Innerhalb der Methode TradeOpen() des Order-Managers finden wir den Aufruf der beiden Methoden von CStop. Nachfolgend ist der Code der Version für MQL4 aufgeführt:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   int trades_total = TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return false;
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      ENUM_ORDER_TYPE ordertype=type;
      if(in_points)
         price=PriceCalculate(type);
      double sl=0,tp=0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      double lotsize=LotSizeCalculate(price,type,sl);
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker())
         {
            sl = 0;
            tp = 0;
         }
      }
      int ticket=(int)SendOrder(type,lotsize,price,sl,tp);
      if(ticket>0)
        {
         if(OrderSelect(ticket,SELECT_BY_TICKET))
         {
            COrder *order = m_orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),
(ENUM_ORDER_TYPE)::OrderType(),::OrderLots(),::OrderOpenPrice());            
            if (CheckPointer(order))
            {
               LatestOrder(GetPointer(order));
               return true;
            }   
         }         
        }
     }
   return false;
  }

Nachfolgend ist der Code für die MQL5 Version dargestellt:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   bool ret=false;
   double lotsize=0.0;
   int trades_total =TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      if(in_points)
         price=PriceCalculate(type);
      double sl=0.0,tp=0.0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      lotsize=LotSizeCalculate(price,type,m_main_stop==NULL?0:m_main_stop.StopLossCalculate(symbol,type,price));  
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker() || !IsHedging())
         {
            sl = 0;
            tp = 0;
         }
      }    
      ret=SendOrder(type,lotsize,price,sl,tp);
      if(ret)
      {
         COrder *order = m_orders.NewOrder((int)m_trade.ResultOrder(),m_trade.RequestSymbol(),(int)m_trade.RequestMagic(),
m_trade.RequestType(),m_trade.ResultVolume(),m_trade.ResultPrice());
         if (CheckPointer(order))
         {
            LatestOrder(GetPointer(order));
            return true;
         }
      }         
     }
   return ret;
  }

Obwohl TradeOpen eine geteilte Implementierung ist, können wir einen gemeinsamen Nenner in Bezug auf die Berechnung der Hauptstopps finden. Zunächst werden mit den Hauptstopps die StopLoss' und TakeProfits (bezeichnet als sl bzw. tp ) berechnet. Dies geschieht unabhängig davon, ob die StopLoss' und TakeProfits in der eigentlichen anfänglichen Handelsanfrage benötigt werden oder nicht, da die Informationen auch für die Berechnung der Lotgröße benötigt werden können (Geldmanagement).

Nach der Berechnung der Lotgröße ist es wichtig, dass die Informationen in bestimmten Fällen auf Null zurückgesetzt werden. In MQL4 werden die Variablen sl und tp auf Null gesetzt, wenn der Hauptstopp kein brokerseitiger Stopp ist:

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker())
   {
      sl = 0;
      tp = 0;
   }
}

In MQL5 versuchen wir, die Verwendung von StopLoss und TakeProfit im 'netting mode' zu vermeiden, da diese sich immer auf die gesamte Position beziehen würden, nicht nur auf das gewünschte Volumen des Handelsauftrages. So werden die Variablen auf Null zurückgesetzt, wenn es sich bei dem Stopp nicht um einen brokerseitigen Stopp handelt ODER die Plattform sich im 'netting mode' befindet:

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker() || !IsHedging())
   {
      sl = 0;
      tp = 0;
   }
}  

Wie im vorherigen Artikel beschrieben (siehe Cross-Plattform Expert Advisor: Stopps), würde der Expert Advisor die brokerseitigen Stopps im 'netting mode' in Pending-Order umwandeln, so dass es keine Notwendigkeit mehr gibt, StopLoss und TakeProfit im ersten Handelsauftrag der Position in diesem Modus zu senden.

Für Positionen mit mehreren StopLoss' und TakeProfits werden diese verarbeitet, sobald die Hauptposition erfolgreich eröffnet wurde Die Position muss zuerst eröffnet werden, da diese Stopps sinnlos wären, wenn die Hauptposition im Markt gar nicht existiert. Das Erstellen dieser anderen Stopp geschieht innerhalb der Initialisierung der Instanz von COrder, die über die Methode CreateStops() von COrder verfügbar ist (aus COrderBase, da MQL4 und MQL5 die Implementierung gemeinsam nutzen):

void COrderBase::CreateStops(CStops *stops)
  {
   if(!CheckPointer(stops))
      return;
   if(stops.Total()>0)
     {
      for(int i=0;i<stops.Total();i++)
        {
         CStop *stop=stops.At(i);
         if(CheckPointer(stop)==POINTER_INVALID)
            continue;
         m_order_stops.NewOrderStop(GetPointer(this),stop);
        }
     }
  }

Wie im obigen Code ersichtlich, wird für jede Instanz von CStop, die der Expert Advisor findet, eine Instanz von COrderStop angelegt. Diese zusätzlichen Stopps entstehen jedoch erst, nachdem die Hauptposition erfolgreich im Markt eröffnet wurde.

Modifizieren der Stopps

CStop verfügt über eine eigene eigene Instanz des CTradeManager und damit über CExpertTradeX (eine Erweiterung von CExpertTrade aus der Standardbibliothek von MQL5). Diese Instanz ist unabhängig von der Instanz des Order Managers (COrderManager), die ausschließlich für die Erfassung der Hauptpositionen verwendet wird. Die Änderung der Stopps wird daher nicht durch den COrderManager vorgenommen, sondern nur durch CStop selbst. Da die Änderung der Stopps jedoch auf der Grundlage des Auftrags vor der Eröffnung erfolgen soll, muss die Anforderung nach einer Änderung der Stopps aus der Position selbst stammen, modifiziert werden soll, d. h. innerhalb der Instanz COrder, die die Position repräsentiert.

Die Überwachung der Stopps beginnt mit der Methode CheckStops() von COrder, deren Code unten dargestellt ist:

void COrderBase::CheckStops(void)
  {
   m_order_stops.Check(m_volume);
  }

Hier ruft er nur die Methode Check() eines seiner Klassenmitglieder auf, die eine Instanz von COrderStops ist. Wie wir uns vom vorherigen Artikel erinnern, ist COrderStop ein Container für Pointer auf die Instanzen von COrderStop. COrderStop ist dagegen eine Repräsentation von CStop in einer aktuellen Instanz von COrder.

Schauen wir uns jetzt die Methode Check() von COrderStops an. Die Methode ist im folgenden Codeausschnitt gezeigt:

COrderStopsBase::Check(double &volume)
  {
   if(!Active())
      return;
   for(int i=0;i<Total();i++)
     {
      COrderStop *order_stop=(COrderStop *)At(i);
      if(CheckPointer(order_stop))
        {
         if(order_stop.IsClosed())
            continue;
         order_stop.CheckTrailing();
         order_stop.Update();
         order_stop.Check(volume);
         if(!CheckNewTicket(order_stop))
            return;
        }
     }
  }

Wie wir aus dem Code ersehen können, führt sie mindestens fünf Funktionen mit der jeweiligen Instanz von COrderStop aus:

  1. Sie prüft, ob eine bestimmte Instanz von COrderStop bereits geschlossen ist (Methode IsClosed())
  2. Sie aktualisiert den Stopp anhand der ihr zugeordneten Instanz, falls vorhanden, eines TrailingStopp (Methode CheckTrailing())
  3. Sie aktualisiert den Stopp (Methode Update())
  4. Sie prüft, ob der Markt einen bestimmten Stopp erreicht hat (Methode Check())
  5. Sie prüft, ob es für die Position ein neues Ticket existiert

Von diesen Aufgaben ist die zweite Aufgabe die, die sich auf das Nachziehen der StopLoss' oder TakeProfits bezieht. Die erste Aufgabe dient nur dazu, um zu sehen, ob weitere Maßnahmen wegen der Stopps erforderlich sind (wenn die Stopps geschlossen wurden, muss sie der Expert Advisor nicht mehr überprüfen). Die dritte Methode wird verwendet, wenn ein Stopp von außerhalb des Expert Advisor verändert wurde (z. B. Ziehen der Stopplinie bei virtuellen Stopps). Die vierte Aufgabe wird verwendet, um zu sehen, ob der Stopp (durch Nachziehen aktualisiert oder nicht) vom Markt erreicht wurde. Die fünfte Aufgabe wird nur im Metatrader 4 verwendet, da nur auf dieser Plattform bei teilweiser Schließung einer Position sich deren Ticketnummer ändert. Metatrader 5 macht in diesem Fall eine einfachere Implementierung möglich, da es eine klare Abfolge der Positionsentwicklung bewahrt.

Die Instanz von COrderStop stellt den Stopp einer Position im Sinne von CStop dar. Jedes Nachziehen eines Stopps führt also letztlich zur Änderung einer Instanz dieses Klassenobjekts. Der folgende Code zeigt die Methode CheckTrailing():

bool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }
Aus diesem Code geht hervor, dass er immer noch der allgemeinen Richtlinie entspricht, einen bestimmten Stopp nicht zu ändern, wenn diese bereits geschlossen ist. Wenn ein Auftrag mit einem Stopp zulässig ist, wird die Methode CheckTrailing() der Instanz CStop aufgerufen, die er repräsentiert. Betrachten wir nun die Methode CheckTrailing() von CStop:
double CStopBase::CheckTrailing(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!CheckPointer(m_trails))
      return 0;
   return m_trails.Check(symbol,type,entry_price,price,mode);
  }

Hier ruft CStop die Check-Methode eines seiner Klassenmitglieder, m_trails, auf. m_trails ist einfach ein Container der Pointer auf die Objekte für das Nachziehen oder die nachzuziehenden Stopps: Der Code der Methode ist unten dargestellt:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

An diesem Punkt genügt es, zu verstehen, dass der Container CTrails durch seine eigenen Instanzen von CTrail iteriert und einen Endwert zurück gibt. Dieser Endwert bezieht sich auf den Preis des ausgewählten Symbols und ist somit vom Typ double. Dies ist der neue Wert der StopLoss' oder TakeProfits nach einem erfolgreichem Nachziehen. Gehen wir nun zurück zur Methode CheckTrailing() von COrderStop, denn von dieser Methode aus würde der eigentliche Aufruf zur Änderung des Stopps erfolgen:

bool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }

Der Rückgabewert dieser Methode ist vom Typ Boolean. Es ist das Ergebnis der Änderung des Stopps durch die Methode Modify() von COrderStop (sie gibt bei Erfolg true zurück). Bevor er jedoch den Änderungsauftrag absendet, prüft er mit den Methoden IsStopLossValid() und IsTakeProfitValid(), ob StopLoss und TakeProfit gültig sind. Wenn der vorgeschlagene Wert nicht gültig ist, wird er auf Null zurückgesetzt:

bool COrderStopBase::IsStopLossValid(const double stoploss) const
  {
   return stoploss!=StopLoss();
  }

bool COrderStopBase::IsTakeProfitValid(const double takeprofit) const
  {
   return takeprofit!=TakeProfit();
  }

Im Code oben sollte der vorgeschlagene Wert sowohl für StopLoss als auch für TakeProfit nicht dem aktuellen Wert entsprechen.

Nach der Auswertung von StopLoss und TakeProfit wird die Methode() Modify von COrderStop aufgerufen, die unten aufgeführt wird:

bool COrderStopBase::Modify(const double stoploss,const double takeprofit)
  {
   bool stoploss_modified=false,takeprofit_modified=false;
   if(stoploss>0 && takeprofit>0)
     {
      if(ModifyStops(stoploss,takeprofit))
        {
         stoploss_modified=true;
         takeprofit_modified=true;
        }
     }
   else if(stoploss>0 && takeprofit==0)
      stoploss_modified=ModifyStopLoss(stoploss);
   else if(takeprofit>0 && stoploss==0)
      takeprofit_modified=ModifyTakeProfit(takeprofit);
   return stoploss_modified || takeprofit_modified;
  }

An diesem Punkt werden drei Arten von Operationen durchgeführt, jeweils abhängig von den Werten der StopLoss' und TakeProfits. Normalerweise kann die Änderung in einer Aktion durchgeführt werden, wie bei den brokerseitigen Stopps, aber das ist nicht immer der Fall. Stopps auf der Basis von Pending-Orders sowie virtuelle Stopps müssen jeweils einzeln für StopLoss und TakeProfit abgearbeitet werden. Den Code zur Änderung beider Werte finden Sie auf den folgenden Methoden:

bool COrderStopBase::ModifyStops(const double stoploss,const double takeprofit)
  {
   return ModifyStopLoss(stoploss) && ModifyTakeProfit(takeprofit);
  }


Die Methode ModifyStops() ruft einfach die beiden anderen Methoden auf. Die geteilte Implementierung beginnt an dieser Stelle, basierend auf zwei Faktoren: (1) Typ des verwendeten Compilers (MQL4 oder MQL5) und (2) die Art des Stopps (brokerseitig, 'pending' oder virtuell). Ist der der Stopp brokerseitiger, dann würde es zu einem Handelsauftrag führen, der die Hauptposition modifiziert. Ist der Stopp eine Pending-Order, müsste der Expert Advisor den Eröffnungspreis der Pending-Order verschieben. Ist der Stopp virtueller, so muss der Expert Advisor lediglich seine internen Daten des Stopps aktualisieren.

COrderStop hat als eine der Mitglieder kein Objekt der Position (oder einen Zeiger darauf), so dass es nicht in der Lage ist, seine eigenen Stopps zu ändern. Sie muss sich weiterhin auf die Instanz von CStop, durch die ihre eigenen Stopps geändert werden. Jede Änderung eines Stopps führt daher letztendlich dazu, dass eine bestimmte Methode in CStop aufgerufen wird.

Breakeven

Ein Breakeven, oder Gewinnschwelle, ist ein Preis, an dem der Gewinn den Kosten entspricht. Im Handel ist der Ertrag der tatsächliche Gewinn des Handels, während die Kosten der Spread bzw. die Provision sind. Dies ist der Preis, an dem eine Position ohne Gewinn oder Verlust geschlossen werden kann. Für Market Maker oder Broker, die keine Provisionen verlangen, ist der Breakeven in der Regel der Eröffnungspreis der Position.

Ein Expert Advisor sollte eine Position in der Regel mit Gewinn beenden, wenn er den StopLoss in Höhe des Eröffnungspreises erfolgreich gesetzt hat. Bei Erreichen dieses Zustandes wird das maximale Risiko der Position gleich Null. Allerdings kann die Position noch immer den Markt durch andere Mittel verlassen, wie Ausstiegssignale auf Basis des Gewinns.

Der Breakeven einer Position eines Expert Advisor wird durch mindestens zwei Restriktionen begrenzt:

  1. Die Dynamik des StopLoss' der Position
  2. Den Mindestabstand des Brokers

Der StopLoss einer Position sollte immer unterhalb des aktuellen Preises für Kaufpositionen und oberhalb des Preises für Verkaufsposition liegen. Aufgrund dieser Einschränkung kann ein Expert Advisor die StopLoss' nur dann auf den Preis für Breakeven verschieben, wenn sich die Positionen im Zustand eines nicht realisierten Gewinns befindet.

Der Mindestabstand des Brokers gilt weiterhin für die Änderung des StopLoss' einer Position, nicht nur bei deren Eröffnung. So ist es oft nicht möglich, Breakeven einer Position unmittelbar nach ihrer Eröffnung zu setzen. Der Markt muss sich erst deutlich zu Gunsten der Position entwickeln, bevor der Expert Advisor ihren StopLoss in Richtung von Breakeven verschieben kann.

Angesichts dieser Einschränkungen müssen wir bei der Realisation des Breakeven mindestens zwei Faktoren berücksichtigen:

  1. Der Aktivierungspreis
  2. Der neue StopLoss bei der Aktivierung

Der Aktivierungspreis oder Triggerpreis ist der Preis, den der Markt erreichen muss, damit ein Expert Advisor den StopLoss einer Position auf Breakeven verschieben kann. Dieser Aktivierungspreis muss jeweils auf der Basis der einzelnen Positionen ermittelt werden, da von unterschiedlichen Eröffnungspreisen auszugehen ist.

Nach der Aktivierung wird der Expert Advisor veranlasst, den StopLoss der Position auf Breakeven zu verschieben. Normalerweise ist dies der Einstiegspreis der fraglichen Position, aber das ist nicht immer der Fall. Für Broker, die Provisionen verlangen, liegt Breakeven irgendwo oberhalb des Eröffnungspreises für Kaufposition und irgendwo unterhalb des Eröffnungspreises für Verkaufspositionen.

Diese Werte werden in der Regel unter Berücksichtigung weiterer Werte, wie z. B. den aktuellen Marktpreis und den Eröffnungspreis der Position berechnet.

Das folgende Bild zeigt ein Diagramm zur Berechnung des Breakeven in der oben beschriebenen Weise. Basierend auf diesem Ablaufdiagramm werden die drei Werte der Aktivierung, Deaktivierung und des neue Stopp vorab berechnet. Ist der aktuelle Preis größer oder gleich dem erforderlichen Mindestabstand (Setzen von StopLoss auf Breakeven), wird der berechnete neue Stopp als vorläufiges neues Stopp für die Position verwendet. Wenn nicht, dann wäre die Ausgabe gleich Null. Im nächste Schritt wäre zu prüfen, ob der neue Stopp innerhalb des aktuellen Stopps liegt, der immer true zurückgeben sollte, wenn die vorherige Bedingung erfüllt ist, und somit den berechneten Stopp als Endergebnis zurückliefern würde.


Breakeven


Nachgezogene Stopps

Ein TrailingStopp (ein oder mehrmals nachgezogene Stopps) kann als Sonderfall des Breakeven angesehen werden. Während Breakeven in der Regel nur einmal angewendet wird, kann ein TrailingStopp beliebig oft angewendet werden. Nach der Aktivierung wird der Stopp in der Regel während der Restlaufzeit der Position nachgezogen. Mindestens drei Faktoren müssen bei der Entwicklung eines TrailingStopps berücksichtigt werden:

  1. Der Aktivierungspreis
  2. Der neue StopLoss bei der Aktivierung
  3. Die Häufigkeit des Nachziehens

Ein TrailingStopp teilt sich die ersten beiden Variablen mit dem Breakeven. Im Gegensatz zum Breakeven kann es jedoch vorkommen, dass der Aktivierungspreis eines TrailingStopps ein Preis ist, an dem die Position noch einen nicht realisierten Verlust ausweist oder "out of the money" ist. Es ist möglich, dass der StopLoss einer Position modifiziert werden kann, während diese Position verliert, und so ist es auch möglich, einen StopLoss nachzuziehen. In diesem Fall führt der tatsächliche StopLoss der Position noch immer zu Verlusten, wenn der der Markt ihn auslöst, da er noch nicht Breakeven erreicht hat oder ihn sogar überschritten hat.

Der dritte Faktor ist die Häufigkeit, mit der der StopLoss nachgezogen wird oder die Konsequenz aus der "Schrittweite". Diese bestimmt den Abstand (in Points oder Pips) des aktuellen Preises des StopLoss' zum nächsten, nach dem ersten Nachzug des StopLoss. Dies macht es ziemlich ähnlich der ersten Variable, dem Aktivierungspreis. Im Gegensatz zum Aktivierungspreis ändert sich jedoch der berechnete Preis aus dieser dritten Variablen nach jeden Nachziehen.

Ein vierter Faktor ist ein Deaktivierungspreis mancher TrailingStopps. Ist dieser Punkt einer Position erreicht, würde der Expert Advisor das Nachziehen des StopLoss' beenden. Diese wird ebenfalls individuell je Position ausgewertet. 

Die folgende Abbildung zeigt das Ablaufdiagramm für das Nachziehen des StopLoss' einer Position. Dies unterscheidet sich nicht wesentlich vom vorherigen Diagramm (Breakeven). Ist der Preis höher als die Anfangsphase des Nachziehens, so ist es sehr wahrscheinlich, dass der Stopp bereits über der Anfangsphase liegt. Andernfalls würde er immer noch die Anfangsphase des Nachziehens als Wert des neuen Stopps für die aktuellen Situation verwenden. Liegt der Stopp noch nicht über dem Aktivierungspreis, so folgt er einfach dem weiteren Verfahren zur Berechnung des Breakeven (der Deaktivierungspreis gilt nicht für den Breakeven).


Nachgezogene Stopps


Nachgezogene TakeProfits

Das 'Trailing' oder das Nachziehen der Stopps bezieht sich in der Regel auf das Nachziehen der StopLoss. Es ist aber auch möglich, die TakeProfits einer Position wiederholt zu verschieben. In diesem Fall wird der TakeProfit angepasst, wenn sich der Preis dem Preis des TakeProfit annähert (das Gegenteil vom Nachziehen des StopLoss'). Theoretisch wird ein TakeProfit, der verschoben wird, niemals erreicht. Dies ist jedoch in bestimmten Situationen wie plötzlichen Preisspitzen und -sprüngen möglich - jeder Zustand, in dem sich der Markt schneller bewegt als es dem Expert Advisor möglich ist darauf zu reagieren.

Damit das Verschieben des TakeProfits sinnvoll ist, müssen einige Dinge erfüllt werden:

  1. Der Aktivierungspreis sollte zwischen Eröffnungspreis und aktuellem TakeProfit liegen
  2. Der nächste Preis, der die nächste Stufe auslöst, sollte innerhalb des aktuellen TakeProfits liegen
  3. Idealerweise sollte nicht so häufig nachgezogen werden

Liegt der Aktivierungspreis jenseits des aktuellen TakeProfits, würde der Markt den TakeProfit vor dem Aktivierungspreis erreichen. Und sobald der TakeProfit erreicht ist, wird die Position geschlossen, lange bevor überhaupt ein Nachziehen des TakeProfits überhaupt beginnen kann. Bei Kaufpositionen sollte der Aktivierungspreis unter dem TakeProfit liegen. Bei Verkaufsposition sollte der Aktivierungspreis höher sein als der TakeProfit. Gleiches gilt für die zweite Bedingung, nur dass sie nach der Erstaktivierung für weitere Schritte gilt.

Idealerweise sollte nicht so häufig nachgezogen werden Dies bedeutet, dass der Abstand zwischen den einzelnen TakeProfits (Schrittweite) groß genug sein sollte. Je häufiger TakeProfit nachgezogen werden muss, desto geringer sind die Chancen, dass der Markt TakeProfit erreicht, da TakeProfit bei jedem Nachzug vom aktuellen Preis weg geschoben wird.

Umsetzung

CTrailBase, die Basisklasse von CTrail, ist im Code unten dargestellt:

class CTrailBase : public CObject
  {
protected:
   bool              m_active;
   ENUM_TRAIL_TARGET m_target;
   double            m_trail;
   double            m_start;
   double            m_end;
   double            m_step;
   CSymbolManager   *m_symbol_man;
   CSymbolInfo      *m_symbol;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CTrailBase(void);
                    ~CTrailBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAIL;}
   //--- Initialisierung                    
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void) const;
   //--- Abfragen und Ändern    
   bool              Active(void) const;
   void              Active(const bool);
   double            End(void) const;
   void              End(const double);
   void              Set(const double,const double,const double,const double);
   double            Start(void) const;
   void              Start(const double);
   double            Step(void) const;
   void              Step(const double);
   double            Trail(void) const;
   void              Trail(const double);
   int               TrailTarget(void) const;
   void              TrailTarget(const ENUM_TRAIL_TARGET);
   //--- Prüfen
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
protected:
   //--- Preisberechnung
   virtual double    ActivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    DeactivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    Price(const ENUM_ORDER_TYPE);
   virtual bool      Refresh(const string);
  };

Die Methode Set() erlaubt es uns, die Einstellungen einer Instanz von CTrails zu konfigurieren. Sie funktioniert gleich wie der übliche Klassenkonstruktor. Bei Bedarf kann man auch einen benutzerdefinierten Konstruktor deklarieren, der diese Methode aufruft:

void CTrailBase::Set(const double trail,const double st,const double step=1,const double end=0)
  {
   m_trail=trail;
   m_start=st;
   m_end=end;
   m_step=step;
  }

CTrail verwendet für seine Berechnungen auf Marktpreise. Es hat also eine Instanz des Symbolmanagers (CSymbolManager) als eines seiner Klassenmitglieder. Das Symbol muss vor den weiteren Berechnungen aktualisiert werden.

bool CTrailBase::Refresh(const string symbol)
  {
   if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),symbol)!=0)
      m_symbol=m_symbol_man.Get(symbol);
   return CheckPointer(m_symbol);
  }

Der Aktivierungspreis ist der Preis, der das erste Nachziehen der StopLoss' oder TakeProfits der Instanz von CTrail auslösen würde. Da die Anfangs-, Schritt- und Endparameter in Points ausgedrückt sind, muss die Klasse den Aktivierungspreis in Form der Preise berechnen. Die übrigen Preise werden nach der gleichen Methode berechnet:

double CTrailBase::ActivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return entry_price+m_start*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return entry_price-m_start*m_symbol.Point();
   return 0;
  }

Die Berechnung des Deaktivierungspreises erfolgt ebenfalls in gleicher Art und Weise, diesmal jedoch mit Hilfe der Klassenvariablen m_end.

double CTrailBase::DeactivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return m_end==0?0:entry_price+m_end*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return m_end==0?0:entry_price-m_end*m_symbol.Point();
   return 0;
  }

Ist der Deaktivierungspreis auf Null gesetzt, ist diese Funktion nicht aktiv. Es wird solange nachgezogen, bis die Hauptposition geschlossen wird.

Die Methode Price() berechnet den neuen Wert der StopLoss' oder TakeProfits, wenn es die aktuellen Bedingungen erlauben, die Stopps nachzuziehen:

double CTrailBase::Price(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
     }
   return 0;
  }

Kommen wir nun zur Methode Check(). Die jeweilige Instanz von CTrail kann entweder einen StopLoss oder ein TakeProfit bewegen. Deshalb muss CTrail nur erkennen können, ob es den StopLoss oder den TakeProfit der Position verändern soll. Dies wird durch die Enumeration ENUM_TRAIL_TARGET erreicht. Die Deklaration dieser Aufzählung findet sich unter MQLx\Common\Enum\ENUM_TRAIL_TARGET.mqh. Der Code ist unten dargestellt:

enum ENUM_TRAIL_TARGET
  {
   TRAIL_TARGET_STOPLOSS,
   TRAIL_TARGET_TAKEPROFIT
  };

Die Methode Check() der Klasse ist im folgenden Code dargestellt. Im Gegensatz zu den anderen bisher in dieser Klasse diskutierten Methoden ist diese Methode eine 'public' Methode. Diese Methode wird aufgerufen, wenn der TrailingStopp auf eine Aktualisierung überprüft werden soll.

double CTrailBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   if(m_start==0 || m_trail==0)
      return 0;
   double next_stop=0.0,activation=0.0,deactivation=0.0,new_price=0.0,point=m_symbol.Point();
   activation=ActivationPrice(type,entry_price);
   deactivation=DeactivationPrice(type,entry_price);
   new_price=Price(type);
   if(type==ORDER_TYPE_BUY)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price>=activation-m_trail*point) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop>=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && ( activation==0.0 || price>=activation) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
     }
   if(type==ORDER_TYPE_SELL)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price<=activation+m_trail*point) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && (activation==0.0 || price<=activation) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
     }
   return 0;
  }

Von hier aus sehen wir, dass die Berechnung unterschiedlich ist zwischen dem StopLoss und dem TakeProfit. Für den StopLoss würden wir uns wünschen, dass jedes Nachziehen ihn näher an den Marktpreis rückt. Für den TakeProfit würden wir das Entgegengesetzte wünschen - ihn in einem bestimmten Abstand (in Points oder Pips) weg vom gegenwärtigen Marktpreis bei jedem Nachziehen drücken.

CTrails (Container)

CTrail hätte auch einen Container namens CTrails. Seine Definition ist im folgenden Code dargestellt:

class CTrailsBase : public CArrayObj
  {
protected:
   bool              m_active;
   CEventAggregator *m_event_man;
   CStop            *m_stop;
public:
                     CTrailsBase(void);
                    ~CTrailsBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAILS;}
   //--- Initialisierung
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CStop    *GetContainer(void);
   virtual void      SetContainer(CStop*stop);
   virtual bool      Validate(void) const;
   //--- Abfragen und Ändern
   bool              Active(void) const;
   void              Active(const bool activate);
   //--- Prüfen
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

Der Container müsste eine Schnittstelle zwischen CStop und den von ihm referenzierten Objekten von CTrail haben. Er müsste auch eine eigene Methode Check() haben:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

CTrails, ähnlich wie andere in dieser Artikelserie verwendete Container, ist von CArrayObj abgeleitet, das entwickelt wurde, um mehrere Pointer auf Instanzen von CObject und seinen abgeleiteten Klassen zu speichern. CTrails kann dann mehr als eine Instanz von CTrail speichern. Wenn in CTrails mehrere Zeiger auf Instanzen von CTrail vorhanden sind, ruft die Methode Check(), wenn aufgerufen, die Methode Check() aller Instanzen von CTrail auf. Als Endergebnis wird jedoch nur der Wert zurückgegeben, der dem aktuellen Marktpreis am nächsten kommt. Dieses Verhalten kann durch Erweiterungen in CTrails geändert werden.

Die Klassen CTrail und CTrails wurden auf reine Berechnung reduziert. Das bedeutet, dass alle Methoden in der Basisklasse (CTrailBase) kodiert sind und keine sprachspezifische Implementierungen benötigen. Beim Aufruf zur Überprüfung des Status des TrailingStopps (von CStop) wird der neue Wert des Stopps abgerufen, und CStop modifiziert den Stopp der Position entsprechend.

Erweiterung von CTrail

Die Klasse CTrail funktioniert nicht nur bei TrailingStopps und Breakeven. Sie kann verwendet werden, um fast jede mögliche Entwicklung der Stopps einer Position im Laufe der Zeit zu bestimmen. Der Prozess ist sehr ähnlich der Art und Weise, wie die in diesem Artikel besprochenen, nutzerdefinierte Stopps implementiert sind. Die Änderung wird jedoch durch eine Erweiterung der Methoden von CTrail erreicht, um die Stopps nach der Positionseröffnung nachzuziehen.

Beispiele

Beispiel #1: Nutzerdefinierte Stopps

Unser erstes Beispiel für diesen Artikel verwendet nutzerdefinierte StopLoss und TakeProfit für die Position, die der Expert Advisor festlegen wird. Wir erweitern den Expert Advisor des dritten Beispiels aus dem vorherigen Artikel (siehe Cross-Platform Expert Advisor: Stopps), um diesen Expert Advisor zu erstellen. Die nutzerdefinierten Stopps werden auf der Grundlage des 'high' und 'low' der vorherigen Kerze berechnet. Als zusätzliche Absicherung wird eine minimaler Abstand für die Stopps hinzugefügt: Wären die berechneten StopLoss' oder TakeProfits weniger als 200 Punkte vom Eröffnungspreis entfernt, setzen wir die StopLoss' oder TakeProfits stattdessen auf 200 Points weg vom Eröffnungspreis. Dies kann dazu beitragen, dass unser Handelsauftrag vom Broker akzeptiert wird.

Zuerst deklarieren wir eine Kindklasse von CStop. In diesem Beispiel wird die abgeleitete Klasse CCustomStop aufgerufen. Die Definition ist nachfolgend aufgeführt:

class CCustomStop : public CStop
  {
   public:
                     CCustomStop(const string);
                    ~CCustomStop(void);
   virtual double    StopLossCustom(const string,const ENUM_ORDER_TYPE,const double);
   virtual double    TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double);
  };

Hier werden wir die Methoden StopLossCustom() und TakeProfitCustom() erweitern. Der folgende Code zeigt die Methode StopLossCustom():

double CCustomStop::StopLossCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price+200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price-200*m_symbol.Point();
        }
     }
   return val;
  }

Zunächst berechnen wir mit den Funktionen CopyLow() und CopyHigh() die Stopps in Abhängigkeit von der Richtung der Position (Kauf oder Verkauf). Nachdem wir den Anfangswert erhalten haben, ermitteln wir dann den absoluten Wert der Differenz zum Eröffnungspreis in Points. Ist der Abstand kleiner als das Minimum, setzen wir stattdessen den Abstand auf das Minimum.

Die Methode TakeProfitCustom() geht ähnlich vor, wie unten dargestellt:

double CCustomStop::TakeProfitCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return 0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price+200*m_symbol.Point();
        }
     }
   return val;
  }

Die Klassenmitglieder von CStop, die für die zugewiesenen Werte StopLoss und TakeProfit in Points (m_stoploss bzw. m_takeprofit) verantwortlich sind, werden standardmäßig mit Null initialisiert. Daher brauchen wir nur die folgenden Zeilen innerhalb der Funktion OnInit() kommentieren:

//main.StopLoss(stop_loss);
//main.TakeProfit(take_profit);

Nur wenn die Werte (in Points) von StopLoss und TakeProfit eines Stopps auf Null gesetzt werden, wird die Instanz von CStop die nutzerdefinierte Berechnung verwenden.

Beispiel #2: Breakeven

Für die Breakeven modifizieren wir auch die Headerdatei des dritten Beispiels aus dem vorherigen Artikel (wie in Beispiel 1). Aber dieses Mal werden wir keine Klassenobjekte erweitern. Vielmehr werden wir nur eine Instanz von CTrail sowie eine Instanz von CTrails deklarieren:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

Die Letzte Zeile ist wichtig. Wir müssten eine Instanz von CTrails einer bestehenden Instanz von CStop hinzufügen. Auf diese Weise würde sich auch diese spezielle Instanz von CStop so verhalten.

Das dritte Argument der Methode Set() von CTrail ist der Schrittweite. Der Standardwert ist 1 (1 Point). Da wir nur Breakeven verwenden, geben wir ihr einen Wert von 0.

Der obige Code erfordert die Variablen breakeven_start, d. h. den Aktivierungspreis (in Points) vom Eröffnungspreis und breakeven_value, d. h. den neuen Abstand des StopLoss' (in Points) vom Aktivierungspreis. Wir deklarieren die Eingabeparameter für diese beiden:

input int breakeven_value = 200;
input int breakeven_start = 200;

Bei dieser Einstellung würde, sobald sich der Markt um mindestens 200 Punkte zu Gunsten der Position entwickelt, der StopLoss um 200 Punkte vom Aktivierungspreis verschoben. 200 - 200 = 0, und so wäre der neu berechnete StopLoss der Eröffnungspreis der Position selbst.

Beispiel #3: TrailingStopp

Fahren wir nun mit der Implementierung eines TrailingStopp in einem EA fort. Dieses Beispiel ist dem vorherigen sehr ähnlich. Wir erinnern uns, der folgende Code wurde OnInit() hinzugefügt:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

Der Vorgang ist der selbe, wenn wir stattdessen einen TrailingStopp verwenden:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(trail_value,trail_start,trail_step);
trails.Add(trail);   
main.Add(trails);

Diesmal verwendet Set() von CTrail drei Parameter. Die ersten beiden sind identisch mit denen des vorherigen Beispiels. Der dritte Parameter ist die Schrittweite, also die Häufigkeit des Nachziehens nach der Aktivierung. Im vorherigen Beispiel wurde dieser Parameter standardmäßig 0 zugewiesen, nach der ersten Aktivierung wurde also nicht weiter nachgezogen. Die Eingabeparameter trail_value, trail_start und trail_step werden dann im EA deklariert.

Beispiel #4: Nutzerdefiniertes Nachziehen

In diesem Beispiel, implementieren wir nun ein nutzerdefiniertes Nachziehen. Wir erweitern den Expert Advisor, der im ersten Beispiel dieses Artikels verwendet wird. Erinnern wir uns, für den Expert Advisor des ersten Beispiels deklarierten wir einen eigenen Stopp, der StopLoss und TakeProfit auf Basis von 'high' und 'low' der letzten Kerze ermittelte. In diesem Beispiel werden wir das erweitern, aber nicht nur, um StopLoss und TakeProfit bei der Eröffnung festzulegen. Wir möchten auch, dass der StopLoss der Position den Marktpreis auf Basis von 'high' oder 'low' der letzten Kerze verfolgen würde. Zur Sicherheit verwenden wir für jede Schritt des Nachziehens einen Mindestabstand.

Um den Expert Advisor zu erstellen, erweitern wir zuerst CTrail. Wir nennen die abgeleitete Klasse CCustomTrail. Die Definition ist nachfolgend aufgeführt:

class CCustomTrail : public CTrail
  {
public:
                     CCustomTrail(void);
                    ~CCustomTrail(void);
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

Jetzt erweitern wir die Methode Check(). Beachten Sie, dass es nicht notwendig ist das Ziel des Nachziehens zu überprüfen. Wir ziehen den StopLoss nach, und das ist das Standard für CTrail:

double CCustomTrail::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = m_symbol.Ask()-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=m_symbol.Bid()+200*m_symbol.Point();
        }
     }
   if((type==ORDER_TYPE_BUY && val<=price+10*m_symbol.Point()) || (type==ORDER_TYPE_SELL && val>=price-10*m_symbol.Point()))
      val = 0;
   return val;
  }
Wie wir sehen können, ist die Berechnung ziemlich ähnlich wie in den Methoden in CCustomStop. Im letzten Teilen des Codes, prüfen wir, ob der vorgeschlagene neue StopLoss 10 Punkte (1 Pips) jenseits dem vorherigen liegt. Damit soll verhindert werden, dass der StopLoss der Position auf Basis des aktuellen 'high' oder 'low' schwankt. Anstatt auf der Grundlage von 'high'/'low' wird der StopLoss immer jenseits des Wertes liegen, der ersetzt wird, (höher für Kaufpositionen und niedriger für Verkaufspositionen).

Schlussfolgerung

In diesem Artikel haben wir gezeigt, wie nutzerdefinierte Stopps in einem plattformübergreifenden Expert Advisor eingerichtet werden können. Anstatt StopLoss und TakeProfit in Pips und Points zu setzen, führen die vorgestellten Methoden eine Art und Weise ein, wie die genannten Werte über der Preise des Charts ermittelt werden können. Der Artikel hat auch eine Weise demonstriert, in der StopLoss' und TakeProfits im Laufe der Zeit geändert werden können.

Die Programme dieses Artikels

#
Name
Typ
Beschreibung
1.
breakeven_ha_ma.mqh
Header-Datei
Header-Datei, des ersten Beispiels
2.
breakeven_ha_ma.mq4 Expert Advisor
Quellcode, verwendet in der Version für MQL4 des ersten Beispiels
3. breakeven_ha_ma.mq5 Expert Advisor Quellcode, verwendet in der Version für MQL5 des ersten Beispiels
4. trail_ha_ma.mqh Header-Datei
Header-Datei, des zweiten Beispiels
5. trail_ha_ma.mq4 Expert Advisor
Quellcode, verwendet in der Version für MQL5 des zweiten Beispiels
6. trail_ha_ma.mq5 Expert Advisor
Quellcode, verwendet in der Version für MQL5 des zweiten Beispiels
7. custom_stop_ha_ma.mqh Header-Datei Header-Datei, des dritten Beispiels
8. custom_stop_ha_ma.mq4 Expert Advisor Quellcode, verwendet in der Version für MQL4 des dritten Beispiels
9. custom_stop_ha_ma.mq5 Expert Advisor Quellcode, verwendet in der Version für MQL5 des dritten Beispiels
10. custom_trail_ha_ma.mqh
Header-Datei Header-Datei, des vierten Beispiels
11. custom_trail_ha_ma.mq4 Expert Advisor Quellcode, verwendet in der Version für MQL4 des vierten Beispiels
12.
custom_trail_ha_ma.mq5 Expert Advisor Quellcode, verwendet in der Version für MQL5 des vierten Beispiels

Die Dateien der Klassen dieses Artikels

 # Name
Typ
 Beschreibung
1.
MQLx\Base\Stop\StopBase.mqh Header-Datei CStop (Basisklasse)
2.
MQLx\MQL4\Stop\Stop.mqh Header-Datei CStop (MQL4 Version)
3.
MQLx\MQL5\Stop\Stop.mqh Header-Datei CStop (MQL5 Version)
4. MQLx\Base\Trail\TrailBase.mqh  Header-Datei CTrail (Basisklasse)
5. MQLx\MQL4\Trail\Trail.mqh  Header-Datei CTrail (MQL4 Version)
6. MQLx\MQL5\Trail\Trail.mqh  Header-Datei CTrail (MQL5 Version)
7. MQLx\Base\Trail\TrailsBase.mqh  Header-Datei CTrails (base class)
8. MQLx\MQL4\Trail\Trails.mqh  Header-Datei CTrails (MQL4 Version)
9. MQLx\MQL5\Trail\Trails.mqh  Header-Datei CTrails (MQL5 Version)

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

Beigefügte Dateien |
MQL5.zip (741.35 KB)
Automatische Suche nach Divergenzen und Konvergenzen Automatische Suche nach Divergenzen und Konvergenzen
Der Artikel behandelt alle Arten von Divergenzen: einfach, versteckt, erweitert, dreifache, vierfache, Konvergenzen, sowie Divergenzen der Klassen A, B und C. Es wurde ein universeller Indikator für deren Ermittlung und Darstellung auf dem Chart entwickelt.
Universeller Expert Advisor: CUnIndicator und das Arbeiten mit Pending Orders (Teil 9) Universeller Expert Advisor: CUnIndicator und das Arbeiten mit Pending Orders (Teil 9)
Der Artikel beschreibt das Arbeiten mit Indikatoren anhand der universellen Klasse CUnIndicator. Darüber hinaus wurden im Artikel neue Arbeitsmethoden mit Pending Orders betrachtet. Bitte beachten Sie, dass die Struktur des CStrategy Projektes wesentlich verändert wurde. Jetzt sind alle seine Dateien in einem einheitlichen Verzeichnis für die Bequemlichkeit der Nutzer abgelegt.
Cross-Plattform Expert Advisor: Die Klassen CExpertAdvisor und CExpertAdvisors Classes Cross-Plattform Expert Advisor: Die Klassen CExpertAdvisor und CExpertAdvisors Classes
In diesem Artikel geht es in erster Linie um die Klassen CExpertAdvisor und CExpertAdvisors, die als Container für alle anderen in dieser Artikelserie beschriebenen Komponenten im Hinblick auf einen plattformübergreifende Expert Advisor dienen.
Grafische Interfaces XI: Integration der graphischen Standardbibliothek (build 16) Grafische Interfaces XI: Integration der graphischen Standardbibliothek (build 16)
Eine neue Version der Grafikbibliothek zum Erstellen wissenschaftlicher Diagramme (die Klasse CGraphic) wurde vor Kurzen veröffentlicht. Mit dieser Aktualisierung der weiterentwickelten Bibliothek, um grafische Interfaces zu erstellen, wird eine Version mit neuem Steuerelemente zur Erstellung von Diagrammen eingeführt. Jetzt ist es noch einfacher, Daten verschiedener Typen zu visualisieren.