English Русский 中文 Español 日本語 Português
Verwendung von Limit-Orders anstelle von Take-Profit, ohne den ursprünglichen Code des EA zu ändern.

Verwendung von Limit-Orders anstelle von Take-Profit, ohne den ursprünglichen Code des EA zu ändern.

MetaTrader 5Beispiele | 10 Dezember 2018, 07:35
1 796 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Inhalt

Einführung

In verschiedenen Foren kritisieren die Nutzer den MetaTrader 5 für seine Marktperformance bei Take-Profits. Solche Beiträge finden Sie auch in dem Forum dieser Website. Anwender schreiben über die negativen Auswirkungen einer Abweichung auf das Finanzergebnis während der Take-Profit Ausführung. Alternativ schlagen einige vor, Limit-Orders als Ersatz der standardmäßigen Take-Profits zu verwenden.

Andererseits ermöglicht die Verwendung von Limit-Orders im Gegensatz zum standardmäßigen Take-Profit den Händlern, einen Algorithmus für die teilweise und schrittweise Schließung von Positionen aufzubauen, da für eine Limit-Order ein anderes Volumen als das der Position angeben werden kann. In diesem Artikel möchte ich eine mögliche Alternative vorstellen, um damit die normalen Take-Profits zu ersetzen.

1. Allgemeine Aspekte

Ich glaube, es hat keinen Sinn, darüber zu streiten, was besser ist — die integrierten Take-Profits oder die Limit-Orders, die sie ersetzen. Jeder Händler sollte dieses Problem auf der Grundlage seiner strategischen Grundsätze und Anforderungen lösen. Dieser Artikel bietet lediglich eine der möglichen Lösungen.

Bevor wir das System der Limit-Orders entwickeln, lassen Sie uns einige Aspekte betrachten, die wir beim Design des Algorithmus beachten sollten.

Die Hauptsache, an die wir denken sollten, ist, dass Take-Profit eine Order ist, die eine Position schließt. Das mag selbstverständlich erscheinen, aber jeder ist es gewohnt, dass diese Aufgabe vom Terminal und vom System übernommen wird. Da wir uns entschieden haben, das System bei der Festlegung eines Take-Profit zu ersetzen, sollten wir die volle Verantwortung für seine Wartung übernehmen.

Wovon genau rede ich? Eine Position kann nicht nur durch Take-Profit, sondern auch durch Stop-Loss und nach Ermessen des Händlers geschlossen werden (oft unter Einbeziehung einiger EAs, um zum Marktpreis zu schließen). Das bedeutet, dass unser System das Vorhandensein der begleiteten Position auf dem Markt kontrolliert und eine Limit-Order sofort entfernen sollte, falls die Position aus irgendeinem Grund geschlossen wurde. Andernfalls könnte eine unerwünschte Position geöffnet werden, was zu viel größeren Verlusten führt als der Schlupf bei der Aktivierung eines standardmäßigen Take-Profits.

Außerdem kann eine Position teilweise geschlossen oder auch (auf Netting-Konten) vergrößert werden. Daher ist es wichtig, nicht nur die Verfügbarkeit einer Position, sondern auch ihr Volumen zu kontrollieren. Wenn sich das Volumen einer Position ändert, sollte sofort eine Limit-Order ersetzt werden.

Ein weiterer Aspekt betrifft den Betrieb des Absicherungssystems. Dieses System führt eine getrennte Verrechnung von Positionen durch und ermöglicht das gleichzeitige Öffnen mehrerer Positionen desselben Symbols. Das bedeutet, dass die Aktivierung einer Limit-Order die bestehende Position nicht schließt. Stattdessen öffnet sie eine Neue. Nachdem also eine Limit-Order ausgelöst wurde, müssen wir das Schließen mit einer entgegengesetzten Position durchführen.

Ein weiteres mögliches Problem ist der Take-Profit von Pending-Orders. In diesem Fall sollten wir darauf achten, dass kein Take-Profit ausgelöst solange die Pending-Order als Pending-Order existiert. Auf den ersten Blick ist es möglich, Stop-Limit-Orders zu verwenden. So können wir beispielsweise gleichzeitig eine Sell-Stop-Order und eine Buy-Stop-Limit-Order platzieren. Aber das System erlaubt es uns nicht, eine ähnliche Operation mit einer Sell-Limit-Order durchzuführen. Dies wirft die Frage auf, wie die Aktivierung einer Pending-Order mit der nachfolgenden Festlegung eines Limit-Order als Take-Profit verfolgt werden kann. Die Verfolgung der Aktivierung einer Pending-Order innerhalb des Programms und das Setzen einer Pending-Order ohne ein Take-Profit birgt wiederum das Risiko einer unkontrollierbaren Positionsöffnung. Infolgedessen kann der Preis das Gewinnniveau erreichen und umkehren. Mangelnde Kontrolle durch das Programm erlaubt es nicht, die Position zu schließen, was schließlich zu einem Verlust führt.

Meine persönliche Lösung ist es, Pending-Orders zu setzen und gleichzeitig einen standardmäßigen Take-Profit festzulegen. Nachdem dann eine Position eröffnet wurde, wird deren Take-Profit durch eine Limit-Order ersetzt, indem die Limit-Order platziert und das Feld Take-Gewinn auf Null gesetzt wird. Diese Option sichert uns ab gegen den Verlust der Kontrolle über die Situation. Wenn das Programm die Verbindung zum Server verliert, wird der Order Take-Profit vom System aktiviert. In diesem Fall sind mögliche Verluste durch einen negativen Schlupf geringer als Verluste durch mangelnde Kontrolle.

Ein weiteres Problem ist die Änderung eines zuvor festgelegten Take-Profit. Häufig, wenn verschiedene Strategien verwendet werden, muss der Take-Profit einer offenen Position verfolgt und anpasst werden. Wir haben hier zwei Möglichkeiten.

  1. Wenn wir Änderungen am Code eines solchen EA vornehmen, dann ersetzen wir einfach den OrderSend-Funktionsaufruf durch den Aufruf der Methode unserer Klasse, bei der wir bereits das Vorhandensein der zuvor gesetzten Limit-Order überprüfen und ob sie der neuen Ebene entspricht, um nicht alle möglichen Optionen zur Änderung des Take-Profit im Code zu finden. Ändern Sie gegebenenfalls die zuvor erteilte Order oder ignorieren Sie den Befehl, wenn die zuvor erteilte Limit-Order den neuen Anforderungen entspricht.
  2. Wir verwenden einen gekauften EA und haben keinen Zugriff auf seinen Code; unser Programm eröffnet keine Position, sondern ersetzt nur den Take-Profit. In diesem Fall ist die Wahrscheinlichkeit hoch, dass für eine Position, für die wir bereits eine Limit-Orders gesetzt haben, ein Take-Profit gesetzt wird. Das bedeutet, dass wir bestehende Limit-Orders auf ihre Relevanz hin überprüfen und anpassen sollten, während wir das Feld Take-Profit auf Null setzen.

Außerdem sollten wir die Mindestabstände (freezing) für die Einstellung der Pending-Order vom aktuellen Preis und die Entfernung der von einem Broker festgelegten Werte kontrollieren. Und wenn erstere auch für die Einstellung eines Systems gilt, kann der Abstand zum 'Einfrieren' beim Schließen der nachgeführten Position in der Nähe einer festgelegten Limit-Order nach hinten losgehen, was deren Entfernung oder Änderung unmöglich macht. Daher sollte ein solches Risiko nicht nur beim Aufbau eines Systems, sondern auch bei der Nutzung berücksichtigt werden, da es nicht vom Algorithmus des Systems abhängt.

2. Grundsätze für die Umsetzung des Links "Position - Limit-Order".

Wie ich bereits erwähnt habe, ist es notwendig, den Zustand einer Position zu verfolgen und nach einem passenden Limit zu suchen. Mal sehen, wie wir das umsetzen können. Zunächst müssen wir feststellen, zu welchem Zeitpunkt wir diese Kontrolle durchführen müssen, um das Terminal nicht zu überlasten.

Theoretisch kann eine Position zu jedem Zeitpunkt der Handelszeit geändert werden. Dies geschieht jedoch nicht allzu oft, während aber die Überprüfung bei jedem Tick die vom EA durchgeführten Operationen deutlich erhöht. Hier können wir die Ereignisbehandlung nutzen. Gemäß der MQL5-Dokumentation wird ein Handelsereignis generiert, wenn eine Handelsoperation auf einem Trade-Server durchgeführt wurde. Als Ergebnis dieses Ereignisses wird die Funktion OnTrade gestartet. Somit ermöglicht diese Funktion das Starten der Überprüfung der Übereinstimmung zwischen offenen Positionen und platzierten Limit-Orders als Take-Profit. So verpassen wir keine Änderungen und müssen nicht ständig bei jedem neuen Tick alles überprüfen.

Als nächstes kommt die Frage der Identifikation. Auf den ersten Blick ist alles einfach. Wir sollten eigentlich nur Limit-Orders und offene Positionen überprüfen. Wir wollen jedoch einen universellen Algorithmus entwickeln, der auf verschiedenen Arten von Konten und mit unterschiedlichen Strategien gut funktioniert. Berücksichtigen wir aber auch, dass innerhalb einer Strategie Limit-Orders verwendet werden können. Deshalb müssen wir Limit-Take-Gewinne zuweisen. Ich biete an, Kommentare zu verwenden, um sie zu identifizieren. Da unsere Limit-Orders als Ersatz für einen Take-Profit verwendet werden, werden wir am Anfang des Order-Kommentars "TP" hinzufügen, um sie zu identifizieren. Als Nächstes fügen wir eine Stufungsnummer hinzu, falls eine mehrstufige Positionsschließung angewendet wird. Das wäre genug für das Netting-System, aber vergessen wir nicht das Absicherungssystem mit der Möglichkeit, mehrere Positionen auf einem Konto zu eröffnen. Daher sollten wir die entsprechende Positions-ID zum Kommentar des Limit-Take-Profit hinzufügen.

3. Anlegen eine Klasse für Limit-Take-Profits

Fassen wir das Ganze zusammen. Die Funktionalität unserer Klasse lässt sich in zwei logische Prozesse unterteilen:

  1. Änderungen beim Senden von Handelsaufträgen an den Server vornehmen.
  2. Überwachung und Korrektur offener Positionen und Platzierung von Limit-Orders.

Um die Bedienung zu erleichtern, entwerfen wir unseren Algorithmus mit der Klasse CLimitTakeProfit und machen alle Funktionen statisch. Dies ermöglicht es uns, Klassenmethoden zu verwenden, ohne ihre Instanz im Programmcode zu deklarieren.

class CLimitTakeProfit : public CObject
  {
private:
   static CSymbolInfo       c_Symbol;
   static CArrayLong        i_TakeProfit; //fixes Take-Profit
   static CArrayDouble      d_TakeProfit; //Prozentsatz, um bei Take-Profit zu schließen
   
public:
                     CLimitTakeProfit();
                    ~CLimitTakeProfit();
//---
   static void       Magic(int value)  {  i_Magic=value; }
   static int        Magic(void)       {  return i_Magic;}
//---
   static void       OnlyOneSymbol(bool value)  {  b_OnlyOneSymbol=value;  }
   static bool       OnlyOneSymbol(void)        {  return b_OnlyOneSymbol; }
//---
   static bool       OrderSend(const MqlTradeRequest &request, MqlTradeResult &result);
   static bool       OnTrade(void);
   static bool       AddTakeProfit(uint point, double percent);
   static bool       DeleteTakeProfit(uint point);
   
protected:
   static int        i_Magic;          //Magicnummer für die Kontrolle
   static bool       b_OnlyOneSymbol;  //Nur Positionen des einen Symbols werden kontrolliert
//---
   static bool       SetTakeProfits(ulong position_ticket, double new_tp=0);
   static bool       SetTakeProfits(string symbol, double new_tp=0);
   static bool       CheckLimitOrder(MqlTradeRequest &request);
   static void       CheckLimitOrder(void);
   static bool       CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0);
   static double     GetLimitOrderPriceByComment(string comment);
  };

Die Methoden Magic, OnlyOneSymbol, AddTakeProfit und DeleteTakeProfit sind diejenigen, die die Klassenoperation konfigurieren. Magic — die Magicnummern, die für die Kontrolle der Positionen (Hedge-Konten) verwendet werden. Ist sie -1, arbeitet die Klasse mit allen Positionen. OnlyOneSymbol weist die Klasse an, nur mit Positionen des Symbols zu arbeiten, auf dessen Chart der EA gestartet wurde. Mit den Methoden AddTakeProfit und DeteleTakeProfit können feste Take-Profit-Level mit Angabe des zu schließenden Volumens als Prozentsatz des Anfangsbestandsvolumens hinzugefügt und gelöscht werden.

Nutzer können diese Methoden anwenden, wenn sie wollen, aber sie sind optional. Standardmäßig arbeitet die Methode mit allen Magicnummern und Symbolen, ohne feste Take-Profits zu setzen. Eine Limit-Order wird nur anstelle eines in der Position angegebenen Take-Profit gesetzt.

3.1. Änderungen beim Versenden der Handelsaufträge

Die Methode OrderSend überwacht die von dem EA gesendeten Aufträge. Der Name und die Form des Methodenaufrufs ähneln der Standardfunktion zum Senden von Aufträgen an MQL5. Dies vereinfacht die Einbettung des Algorithmus in den Code des zuvor geschriebenen EA, indem die Standardfunktion durch unsere Methode ersetzt wird.

Wir haben bereits ein Problem mit dem Ersatz von Take-Profit für Pending-Orders beschrieben. Aus diesem Grund werden wir nur in diesem Block einen Take-Profit für Marktorders ersetzen können. Vergessen wir jedoch nicht, dass die Annahme eines Auftrages durch den Server nicht unbedingt bedeutet, dass sie ausgeführt wird. Außerdem erhalten wir nach dem Absenden des Auftrags die Ticketnummer, aber nicht die Positions-ID. Daher werden wir Take-Profit im Überwachungsblock ersetzen. Hier werden wir nur den Moment verfolgen, in dem der zuvor festgelegte Take-Profit geändert wird.

Überprüfen Sie am Anfang des Methodencodes, ob die gesendete Anforderung den für die Verwendung des Algorithmus' eingestellten Filtern entspricht. Darüber hinaus sollten wir die Art eines Geschäfts überprüfen. Sie sollte der Anforderung der Position zur Änderung des Stoppniveaus entsprechen. Vergessen Sie auch nicht zu überprüfen, ob ein Take-Profit im Auftrag existiert. Wenn die Anforderung mindestens eine der Anforderungen nicht erfüllt, wird sie sofort unverändert an den Server gesendet.

Nach Prüfung der Anforderungen wird der Auftrag an die Methode SetTakeProfit weitergeleitet, wo Limit-Orders erteilt werden. Beachten Sie, dass die Klasse zwei Methoden zum Arbeiten mit einem Positionsticket und einem Symbol bietet. Die Zweite ist eher auf Netting-Konten anwendbar, wenn die Anforderung kein Positionsticket enthält. Wenn die Methode erfolgreich ist, setzen Sie das Feld Take-Profit in der Anforderung auf Null.

Da die Anforderung sowohl Take-Profit als auch Stop-Loss ändern kann, überprüfen Sie, ob Stop-Loss und Take-Profit, die in der Position eingestellt sind, angemessen sind. Senden Sie bei Bedarf eine Anforderung an den Server und verlassen Sie die Funktion. Der vollständige Methodencode wird unten angezeigt.

bool CLimitTakeProfit::OrderSend(MqlTradeRequest &request,MqlTradeResult &result)
  {
   if((b_OnlyOneSymbol && request.symbol!=_Symbol) ||
      (i_Magic>=0 && request.magic!=i_Magic) || !(request.action==TRADE_ACTION_SLTP && request.tp>0))
      return(::OrderSend(request,result));
//---
   if(((request.position>0 && SetTakeProfits(request.position,request.tp)) ||
       (request.position<=0 && SetTakeProfits(request.symbol,request.tp))) && request.tp>0)
      request.tp=0;
   if((request.position>0 && PositionSelectByTicket(request.position)) ||
      (request.position<=0 && PositionSelect(request.symbol)))
     {
      if(PositionGetDouble(POSITION_SL)!=request.sl || PositionGetDouble(POSITION_TP)!=request.tp)
         return(::OrderSend(request,result)); 
     }
//---
   return true;
  }

Nun, lassen Sie uns die Methode SetTakeProfit im Detail analysieren. Überprüfen Sie zu Beginn der Methode, ob die angegebene Position vorhanden ist und ob wir mit dem Positionssymbol arbeiten. Aktualisieren Sie anschließend die Daten des Positionsinstruments. Berechnen Sie anschließend die nächstgelegenen Preise, bei denen die Limit-Order zulässig sind. Im Falle eines Fehlers verlassen Sie die Methode mit dem Ergebnis "false".

bool CLimitTakeProfit::SetTakeProfits(ulong position_ticket, double new_tp=0)
  {
   if(!PositionSelectByTicket(position_ticket) || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
      return false;
   if(!c_Symbol.Name(PositionGetString(POSITION_SYMBOL)) || !c_Symbol.Select() || !c_Symbol.Refresh() || !c_Symbol.RefreshRates())
      return false;
//---
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

Bereiten Sie anschließend die Strukturen für das Versenden einer Handelsaufforderung zur Platzierung einer Limit-Order vor. Berechnen Sie den Take-Profit, der in der Position platziert oder angegeben ist. Es sollten nur feste Take-Profits verwendet werden, die die berechnete Abstände nicht überschreiten.

   MqlTradeRequest tp_request={0};
   MqlTradeResult tp_result={0};
   tp_request.action =  TRADE_ACTION_PENDING;
   tp_request.magic  =  PositionGetInteger(POSITION_MAGIC);
   tp_request.type_filling =  ORDER_FILLING_RETURN;
   tp_request.position=position_ticket;
   tp_request.symbol=c_Symbol.Name();
   int total=i_TakeProfit.Total();
   double tp_price=(new_tp>0 ? new_tp : PositionGetDouble(POSITION_TP));
   if(tp_price<=0)
      tp_price=GetLimitOrderPriceByComment("TPP_"+IntegerToString(position_ticket));
   double open_price=PositionGetDouble(POSITION_PRICE_OPEN);
   int tp_int=(tp_price>0 ? (int)NormalizeDouble(MathAbs(open_price-tp_price)/c_Symbol.Point(),0) : INT_MAX);
   double position_volume=PositionGetDouble(POSITION_VOLUME);
   double closed=0;
   double closed_perc=0;
   double fix_closed_per=0;

Als Nächstes ordnen Sie die Schleife für die Überprüfung und Platzierung von festen Take-Profits an. Setzen Sie zunächst den Auftragskommentar (das Kodierungsprinzip wurde oben besprochen). Dann prüfen wir, ob der festgelegte Take-Profit den in der Position oder Auftrag angegebenen Wert übersteigt. Ist dies der Fall, gehen Sie zum nächsten Take-Profit. Achten Sie auch darauf, dass das Volumen der zuvor eingestellten Limit-Orders nicht mit dem Positionsvolumen übersteigt. Ist das der Fall, verlassen Sie die Schleife.

   for(int i=0;i<total;i++)
     {
      tp_request.comment="TP"+IntegerToString(i)+"_"+IntegerToString(position_ticket);
      if(i_TakeProfit.At(i)<tp_int && d_TakeProfit.At(i)>0)
        {
         if(closed>=position_volume || fix_closed_perc>=100)
            break;

Der nächste Schritt besteht darin, die fehlenden Elemente der Struktur der Handelsaufforderung zu füllen. Dazu berechnen wir das Volumen der neuen Limit-Order und geben die Art der Order und den Preis der Eröffnung an.

//---
         double lot=position_volume*MathMin(d_TakeProfit.At(i),100-closed)/(100-fix_closed_perc);
         lot=MathMin(position_volume-closed,lot);
         lot=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((lot-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
         lot=NormalizeDouble(lot,2);
         tp_request.volume=lot;
         switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_BUY:
              tp_request.type=ORDER_TYPE_SELL_LIMIT;
              tp_request.price=c_Symbol.NormalizePrice(open_price+i_TakeProfit.At(i)*c_Symbol.Point());
              break;
            case POSITION_TYPE_SELL:
              tp_request.type=ORDER_TYPE_BUY_STOP;
              tp_request.price=c_Symbol.NormalizePrice(open_price-i_TakeProfit.At(i)*c_Symbol.Point());
              break;
           }

Überprüfen Sie nach dem Ausfüllen der Handelsaufforderung, ob zuvor eine Limit-Order mit den gleichen Parametern gesetzt wurde. Verwenden Sie dazu die Methode CheckLimitOrder (der Verfahrensalgorithmus wird unten besprochen), indem Sie die ausgefüllte Anforderungsstruktur an sie übergeben. Wenn die Order noch nicht festgelegt wurde, addieren Sie das Set-Order-Volumen zur Summe der Set-Volumina für die Position. Dies ist notwendig, um sicherzustellen, dass Position und Limit-Ordervolumen übereinstimmen.

         if(CheckLimitOrder(tp_request))
           {
            if(tp_request.volume>=0)
              {
               closed+=tp_request.volume;
               closed_perc=closed/position_volume*100;
              }
            else
              {
               fix_closed_per-=tp_request.volume/(position_volume-tp_request.volume)*100;
              }
            continue;
           }

Wenn der Auftrag noch nicht erteilt wurde, passen Sie ihren Kurs an die Anforderungen des Brokers in Bezug auf den aktuellen Kurs an und senden Sie den Auftrag an den Server. Wenn der Auftrag erfolgreich gesendet wurde, wird das Volumen der erteilten Order zur Summe der zuvor eingestellten Volumina für die Position addiert.

         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }

Verwenden Sie nach Abschluss der Schleife den gleichen Algorithmus, um eine Limit-Order für das fehlende Volumen zu dem in einer Änderungsanforderung (oder in einer Position) angegebenen Preis zu platzieren. Wenn das Volumen kleiner als das minimal zulässige ist, wird die Funktion mit dem Ergebnis "false" verlassen.

   if(tp_price>0 && position_volume>closed)
     {
      tp_request.price=tp_price;
      tp_request.comment="TPP_"+IntegerToString(position_ticket);
      tp_request.volume=position_volume-closed;
      if(tp_request.volume<c_Symbol.LotsMin())
         return false;
      tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
      tp_request.volume=NormalizeDouble(tp_request.volume,2);
//---
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           tp_request.type=ORDER_TYPE_SELL_LIMIT;
           break;
         case POSITION_TYPE_SELL:
           tp_request.type=ORDER_TYPE_BUY_LIMIT;
           break;
        }
      if(CheckLimitOrder(tp_request) && tp_request.volume>=0)
        {
         closed+=tp_request.volume;
         closed_perc=closed/position_volume*100;
        }
      else
        {
         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(tp_request.volume<=0)
           {
            tp_request.volume=position_volume-closed;
            tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
            tp_request.volume=NormalizeDouble(tp_request.volume,2);
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }      

Überprüfen Sie am Ende der Methode, ob das Volumen der platzierten Limit-Order das Positionsvolumen abdeckt. Wenn dies der Fall ist, wird Take-Profit der Position auf Null gesetzt und die Funktion verlassen.

   if(closed>=position_volume && PositionGetDouble(POSITION_TP)>0)
     {
      ZeroMemory(tp_request);
      ZeroMemory(tp_result);
      tp_request.action=TRADE_ACTION_SLTP;
      tp_request.position=position_ticket;
      tp_request.symbol=c_Symbol.Name();
      tp_request.sl=PositionGetDouble(POSITION_SL);
      tp_request.tp=0;
      tp_request.magic=PositionGetInteger(POSITION_MAGIC);
      if(!OrderSend(tp_request,tp_result))
         return false;
     }
   return true;
  }

Werfen wir einen Blick auf den Algorithmus von CheckLimitOrder, um das Bild komplett zu machen. Funktional überprüft diese Methode das Vorhandensein einer zuvor erteilten Limit-Order für eine vorbereitete Handelsaufforderung. Wenn ein Auftrag bereits festgelegt ist, gibt die Methode "true" zurück und der neue Auftrag wird nicht festgelegt.

Bestimmen Sie zu Beginn der Methode die nächstmöglichen Level für die Platzierung von Limit-Order. Wir benötigen sie, wenn es notwendig ist, einen zuvor erteilten Auftrag zu ändern.

bool CLimitTakeProfit::CheckLimitOrder(MqlTradeRequest &request)
  {
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

Im nächsten Schritt beschäftigen wir uns mit der Schleife über alle offenen Positionen. Die gesuchte Position wird über ihren Kommentar identifiziert.

   for(int i=0;i<total;i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      if(OrderGetString(ORDER_COMMENT)!=request.comment)
         continue;

Wenn wir die Position mit dem notwendigen Kommentar finden, überprüfen wir deren Volumen und Auftragsart. Wenn einer der Parameter nicht übereinstimmt, löschen wir die bestehende Pending-Order und verlassen die Funktion mit dem Ergebnis "false". Im Falle eines Fehlers beim Löschen wird das Volumen der offenen Position im Feld Volumen des Auftrages angezeigt.

      if(OrderGetDouble(ORDER_VOLUME_INITIAL) != request.volume || OrderGetInteger(ORDER_TYPE)!=request.type)
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
            return false;
         request.volume=OrderGetDouble(ORDER_VOLUME_INITIAL);
        }

Überprüfen Sie im nächsten Schritt den Eröffnungspreis der erfassten Position und den in den Parametern angegebenen Preis. Ändern Sie gegebenenfalls die aktuelle Position und verlassen Sie die Methode mit dem Ergebnis "true".

      if(MathAbs(OrderGetDouble(ORDER_PRICE_OPEN)-request.price)>=c_Symbol.Point())
        {
         MqlTradeRequest mod_request={0};
         MqlTradeResult mod_result={0};
         mod_request.action=TRADE_ACTION_MODIFY;
         mod_request.price=request.price;
         mod_request.magic=request.magic;
         mod_request.symbol=request.symbol;
         switch(request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              if(mod_request.price>max_buy_limit)
                 return true;
              break;
            case ORDER_TYPE_SELL_LIMIT:
              if(mod_request.price<min_sell_limit)
                 return true;
              break;
           }
         bool mod=::OrderSend(mod_request,mod_result);
        }
      return true;
     }

Vergessen wir jedoch nicht, dass es Fälle geben kann, in denen die Limit-Order bereits mit diesem Volumen gearbeitet hat. Wenn also unter den offenen die gesuchte Position nicht gefunden wird, überprüfen Sie auch die Positionshistorie. Diese Funktionalität ist in der Methode CheckOrderInHistory implementiert, die wir am Ende aufrufen.

   if(!PositionSelectByTicket(request.position))
      return true;
//---
   return CheckOrderInHistory(PositionGetInteger(POSITION_IDENTIFIER),request.comment, request.type, request.volume);
  }

Je nach Kontoart haben wir zwei Möglichkeiten für die Aktivierung einer Limit-Order:

  1. Direkte Aktivierung in einer Position (Netting-Konten).
  2. Eine Limit-Order eröffnet eine entgegengesetzte Position und eine Position wird durch die andere geschlossen (Hedging-Konten).

Wenn Sie nach einer solchen Möglichkeit suchen, beachten Sie, dass sich solche Aufträge möglicherweise nicht auf diese Position beziehen, so dass wir die Suche nach Geschäften durchführen und ein Ticket von einem von ihnen erhalten werden.

bool CLimitTakeProfit::CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0)
  {
   if(!HistorySelectByPosition(position_id))
      return true;
   int total=HistoryDealsTotal();
   bool hedging=(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//---
   for(int i=0;i<total;i++)
     {
      ulong ticket=HistoryDealGetTicket((uint)i);
      ticket=HistoryDealGetInteger(ticket,DEAL_ORDER);
      if(!HistoryOrderSelect(ticket))
         continue;
      if(ticket<=0)
         continue;

Bei Hedging-Konten sollten wir zunächst prüfen, ob die Order mit einer anderen Position zusammenhängt. Wenn eine Position von einer anderen Position aus erkannt wird, suchen Sie nach dem Auftrag mit dem notwendigen Kommentar an dieser Position. Führen Sie dazu einen rekursiven Aufruf der Funktion CheckOrderInHistory durch. Um Schleifen zu vermeiden, überprüfen Sie vor dem Aufruf der Methode, ob die Methode von dieser Position aus aufgerufen wurde. Wenn die Reihenfolge erkannt wird, verlassen Sie die Methode mit dem Ergebnis "true". Andernfalls laden Sie die Historie der Position neu und fahren mit dem nächsten Geschäft fort.

      if(hedging && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=position_id && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=call_position)
        {
         if(CheckOrderInHistory(HistoryOrderGetInteger(ticket,ORDER_POSITION_ID),comment,type,volume))
            return true;
         if(!HistorySelectByPosition(position_id))
            continue;
        }

Kommentar und Auftragsart für die aktuellen Positionsorders überprüfen. Wenn die Reihenfolge erkannt wird, schreiben Sie ihr Volumen mit dem Minuszeichen in die Anforderung und verlassen Sie das Verfahren.

      if(HistoryOrderGetString(ticket,ORDER_COMMENT)!=comment)
         continue;
      if(HistoryOrderGetInteger(ticket,ORDER_TYPE)!=type)
         continue;
//---
      volume=-OrderGetDouble(ORDER_VOLUME_INITIAL);
      return true;
     }
   return false;
  }

Der vollständige Code aller Methoden und Funktionen findet sich im Anhang.

3.2. Abwicklung von Handelsoperation

Die Überwachung und Korrektur bestehender Positionen und offener Limit-Orders bildet den zweiten Block unseres Algorithmus'.

Die Ausführung eines Handels auf dem Konto erzeugt das Handelsereignis, was wiederum die Ausführung der Funktion OnTrade bewirkt. Fügen Sie der Klasse die entsprechende Methode hinzu, um Positionen zu bearbeiten.

Der Algorithmus der Methode beginnt mit einigen Vorbereitungsarbeiten: Wir holen uns die Anzahl der auf einem Konto eröffneten Positionen und überprüfen die Auftragsart.

bool CLimitTakeProfit::OnTrade(void)
  {
   int total=PositionsTotal();
   bool result=true;
   bool hedhing=AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;

Danach kommt die Schleife über alle offenen Positionen. Zu Beginn der Schleife prüfen wir die Position auf das Symbol, die Magicnummer (für Hedging-Konten).

   for(int i=0;i<total;i++)
     {
      ulong ticket=PositionGetTicket((uint)i);
      if(ticket<=0 || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
         continue;
//---
     if(i_Magic>0)
        {
         if(hedhing && PositionGetInteger(POSITION_MAGIC)!=i_Magic)
            continue;
        }

Bei Hedging-Konten prüfen wir, ob die Position eröffnet wird, um unser Limit-Take-Profit zu verarbeiten. Wenn ja, führen wir eine Close-By Operation durch. Nachdem die Positionen erfolgreich geschlossen wurden, fahren wir mit der nächsten Position fort.

      if(hedhing)
        {
         string comment=PositionGetString(POSITION_COMMENT);
         if(StringFind(comment,"TP")==0)
           {
            int start=StringFind(comment,"_");
            if(start>0)
              {
               long ticket_by=StringToInteger(StringSubstr(comment,start+1));
               long type=PositionGetInteger(POSITION_TYPE);
               if(ticket_by>0 && PositionSelectByTicket(ticket_by) && type!=PositionGetInteger(POSITION_TYPE))
                 {
                  MqlTradeRequest   request  ={0};
                  MqlTradeResult    trade_result   ={0};
                  request.action=TRADE_ACTION_CLOSE_BY;
                  request.position=ticket;
                  request.position_by=ticket_by;
                  if(::OrderSend(request,trade_result))
                     continue;
                 }
              }
           }
        }

Am Ende der Schleife wird die Methode SetTakeProfits aufgerufen, um die Limit-Order für die Position zu prüfen und festzulegen. Der Algorithmus der Methode wurde oben bereits beschrieben.

      result=(SetTakeProfits(PositionGetInteger(POSITION_TICKET)) && result);
     }

Nachdem dem Ende der Prüfschleife für offene Positionen vergewissern wir uns, dass die aktiven Limit-Orders den offenen Positionen entsprechen, und entfernen gegebenenfalls die nach dem Schließen der Positionen verbleibenden Limit-Orders. Dazu rufen wir die Methode CheckLimitOrder auf. In diesem Fall wird die Funktion im Gegensatz zur oben beschriebenen Funktion ohne Parameter aufgerufen. Dies geschieht, weil wir eine völlig andere Methode aufrufen, da der gleiche Namen aufgrund der Möglichkeit Funktionen zu überladen verwendet werden kann.

   CheckLimitOrder();
//---
   return result;
  }

Der Verfahrensalgorithmus basiert auf der Iteration über alle erteilten Aufträge. Die Notwendigen werden über die Kommentare ausgewählt.

void CLimitTakeProfit::CheckLimitOrder(void)
  {
   int total=OrdersTotal();
   bool res=false;
//---
   for(int i=0;(i<total && !res);i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      string comment=OrderGetString(ORDER_COMMENT);
      if(StringFind(comment,"TP")!=0)
         continue;
      int pos=StringFind(comment,"_",0);
      if(pos<0)
         continue;

Nachdem ein Limit-Take-Profit erkannt wurde, rufen wir die entgegengesetzte Positions-ID aus dem Kommentar ab. Wir verwenden die ID, um auf die angegebene Position zuzugreifen. Wenn keine solche Position vorhanden ist, entfernen wir den Auftrag.

      long pos_ticker=StringToInteger(StringSubstr(comment,pos+1));
      if(!PositionSelectByTicket(pos_ticker))
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
           {
            i--;
            total--;
           }
         continue;
        }

Wenn es Ihnen gelingt, auf die Position zuzugreifen, überprüfen Sie, ob die Auftragsart mit der Positionsart übereinstimmt. Die Prüfung ist notwendig für Netting-Konten, bei denen eine Gegenposition während des Handels möglich ist. Wenn eine Diskrepanz festgestellt wird, entfernen wir die Position und fahren mit der Überprüfung der nächsten fort.

      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT)
              continue;
           break;
         case POSITION_TYPE_SELL:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT)
              continue;
           break;
        }
      MqlTradeRequest del_request={0};
      MqlTradeResult del_result={0};
      del_request.action=TRADE_ACTION_REMOVE;
      del_request.order=ticket;
      if(::OrderSend(del_request,del_result))
        {
         i--;
         total--;
        }
     }
//---
   return;
  }

Der gesamte Code aller Klassenmethoden befindet sich in der Anlage.

4. Integration der Klasse in einen EA

Nachdem wir die Arbeit an der Klasse abgeschlossen haben, wollen wir sehen, wie sie in den bereits entwickelten EA integriert werden kann.

Wie Sie sich vielleicht erinnern, sind alle Methoden unserer Klasse 'static', was bedeutet, dass wir sie verwenden können, ohne eine Klasseninstanz deklarieren zu müssen. Ein solcher Ansatz wurde ursprünglich gewählt, um die Klassenintegration in bereits entwickelte EAs zu vereinfachen. Dies ist in der Tat der erste Schritt zur Integration einer Klasse in ein EA.

Wir erstellen anschließend die Funktion LimitOrderSend mit den Aufrufparametern ähnlich der Funktion OrderSend. Sie soll sich unterhalb des Klassencodes befinden und nur der Aufruf der Methode CLimitTakeProfit::OrderSend ist funktional. Anschließend verwenden wir die Direktive #define, um die ursprüngliche OrderSend-Funktion durch die benutzerdefinierte zu ersetzen. Die Anwendung der Methode ermöglicht es uns, den Code gleichzeitig in alle EA-Funktionen einzubetten, die Handelsaufforderung senden, so dass wir keine Zeit mit der Suche nach solchen Befehlen entlang des gesamten EA-Codes verlieren müssen.

bool LimitOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
 { return CLimitTakeProfit::OrderSend(request,result); } 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#define OrderSend(request,result)      LimitOrderSend(request,result)

Da viele EAs nicht über die Funktion OnTrade verfügen, können wir sie in die Klassendatei aufnehmen. Aber wenn Ihr EA diese Funktion bietet, müssen Sie den untenstehenden Code löschen oder auskommentieren und den Methodenaufruf CLimitTakeProfit::OnTrade zum Funktionsteil Ihres EA hinzufügen.

void OnTrade()
  {
   CLimitTakeProfit::OnTrade();
  }

Als Nächstes müssen wir den Verweis auf die Klassendatei mit der Direktive #include hinzufügen, um die Klasse in den EA zu integrieren. Beachten Sie, dass die Klasse eingebunden werden sollte, bevor andere Bibliotheken und EA-Code aufgerufen wird. Nachfolgend finden Sie ein Beispiel für das Hinzufügen der Klasse zum MACD Sample.mq5 EA aus dem Standardpaket des Terminals.

//+------------------------------------------------------------------+
//|                                          MACD Sample LimitTP.mq5 |
//|                   Copyright 2009-2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2017, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property version     "5.50"
#property description "It is important to make sure that the expert works with a normal"
#property description "chart and the user did not make any mistakes setting input"
#property description "variables (Lots, TakeProfit, TrailingStop) in our case,"
#property description "we check TakeProfit on a chart of more than 2*trend_period bars"

#define MACD_MAGIC 1234502
//---
#include <Trade\LimitTakeProfit.mqh>
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//---

Sie können dem Funktionscode OnInit das teilweise Schließen von Positionen hinzufügen. Unser EA ist damit einsatzbereit.

Vergessen Sie nicht, den EA zu testen, bevor Sie ihn auf realen Konten verwenden.

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Erstellen aller benötigten Objekte
   if(!ExtExpert.Init())
      return(INIT_FAILED);
   CLimitTakeProfit::AddTakeProfit(100,50);
//--- war erfolgreich
   return(INIT_SUCCEEDED);
  }

EA operation

Der vollständige Code des EAs befindet sich im Anhang.

Schlussfolgerung

Dieser Artikel bietet den Mechanismus, Take-Profit einer Position durch Close-By Limit-Orders zu ersetzen. Wir haben versucht, die Integration der Methode in jeden bestehenden EA-Code so weit wie möglich zu vereinfachen. Ich hoffe, dieser Artikel wird Ihnen nützlich sein, und Sie werden in der Lage sein, alle Vor- und Nachteile beider Methoden zu bewerten.

Die dieses Artikels

#
Name
Typ
Beschreibung
1 LimitTakeProfit.mqh Klassenbibliothek Klasse, um Take-Profits durch Limit-Orders zu ersetzen
2 MACD Sample.mq5 Expert Advisor Der Original-EA, verwendet als Beispiel
3 MACD Sample LimitTP.mq5 Expert Advisor Beispiel für die Integration der Klasse in den EA des MetaTrader 5


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

Beigefügte Dateien |
MQL5.zip (184.41 KB)
Kurslücke - eine profitabele Strategie oder 50/50? Kurslücke - eine profitabele Strategie oder 50/50?
Der Artikel beschäftigt sich mit Kurslücken (gaps) - signifikante Unterschiede zwischen dem Schlusskurs des vorherigen Balkens und dem Eröffnungskurs des darauf folgenden sowie auf der Prognose der Richtung des Tagesbalkens. Die Anwendung der Funktion GetOpenFileName durch die System-DLL wird ebenfalls besprochen.
Modell der Bewegungsfortsetzung - Suche im Chart und Ausführungsstatistik Modell der Bewegungsfortsetzung - Suche im Chart und Ausführungsstatistik
Dieser Artikel bietet eine programmtechnische Realisation eines Modells der Bewegungsfortsetzung. Die Hauptidee besteht darin, zwei Wellen zu definieren - die Haupt- und die Korrekturwelle. Für Extrempunkte verwende ich sowohl Fraktale als auch "potenzielle" Fraktale - Extrempunkte, die sich noch nicht als Fraktale gebildet haben.
Umkehrung: Reduzieren des maximalen Drawdown und Testen anderer Märkte Umkehrung: Reduzieren des maximalen Drawdown und Testen anderer Märkte
In diesem Artikel führen wir die Umkehrtechnik weiter. Wir werden versuchen, den maximalen Saldorückgang auf ein akzeptables Niveau für die zuvor betrachteten Instrumente zu reduzieren. Wir werden sehen, ob die Maßnahmen den Gewinn verringern. Wir werden auch prüfen, wie sich die Umkehrmethode auf anderen Märkten, einschließlich Aktien-, Rohstoff-, Index-, ETF- und Agrarmärkten, bewährt. Achtung, der Artikel enthält viele Bilder!
Methoden zur Fernsteuerung von EAs Methoden zur Fernsteuerung von EAs
Der Hauptvorteil der Handelsroboter liegt in der Möglichkeit, dass sie 24 Stunden am Tag auf einem entfernten VPS-Server arbeiten. Aber manchmal ist es notwendig, in ihre Arbeit einzugreifen, ohne dass es einen direkten Zugriff auf den Server gibt. Ist es möglich, EAs fernzusteuern? Der Artikel schlägt eine der Möglichkeiten vor, EAs über externe Befehle zu steuern.