Cross-Plattform Expert Advisor: Order-Manager

Enrico Lambino | 6 Juni, 2017


Inhaltsverzeichnis

Einführung

Wie bereits in früheren Artikeln dieser Serie besprochen, unterscheiden sich MetaTrader 4 und MetaTrader 5, so dass es schwierig ist, einfach nur einen Quellcodes für MQL4 zu kopieren und für MQL5 zu kompilieren. Der größte Unterschied betrifft die Art, wie beide Plattformen Handelsoperationen ausführen. Dieser Artikel widmet sich der Erstellung der Klasse COrderManager. Diese Klasse, zusammen mit weiteren Hilfsklassen, werden hauptsächlich für die Ausführung und die Verwaltung des Handels eines Expert Advisors verantwortlich sein.


Ziele

Der Order-Manager dieses Artikels ist in der Lage folgende Operationen auszuführen:

  1. Berechnung der Lotgröße
  2. StopLoss und TakeProfit
  3. Verschiedene Parameter der Positionseröffnung (Verfall, Kommentar, Magicnummer)
  4. Einige Voraussetzungen vor dem Absenden der Order
  5. Verwaltung von offenen und geschlossenen Positionen

Es ist oftmals günstig, die Berechnung der Lotgröße einem eigenen Objekt zu übertragen, da es mehrere Arten gibt, eine optimale Lotgröße für die nächste Position zu berechnen (abhängig von der verfolgten Handelsstrategie). Das gleiche gilt für die Berechnung von StopLoss und TakeProfit.

Verschiedene Parameter werden für eine Order benötigt, wie die Verfallszeit, der Kommentar einer Order und die Magicnummer, sie sind relativ einfach und können am besten durch den Order-Manager selbst verwaltet werden.

Bestimmte Voraussetzungen müssen erfüllt sein, bevor eine Order durch den Expert Advisor auf dem Markt platziert werden kann. Das umfasst auch Handelssignale auf Basis von Marktsituationen, zeitliche Einschränkungen oder einer Maximalzahl von offenen Positionen zu einem gegeben Zeitpunkt und die Maximalzahl von zu öffnenden Positionen, so lange das Programm läuft (von OnInit bis OnDeinit). Für den Order-Manager werden noch zwei Voraussetzung ergänzt, bevor eine Order auf dem Markt platziert wird. Die Umsetzung stellt sicher, dass keine doppelten Positionen entstehen, solange der Expert Advisor läuft. Die andere Voraussetzungen, andererseits, kann an andere, externe Komponenten des Order-Managers übertragen werden.


Basisumsetzung

Ähnlich anderen, in dieser Artikelserie besprochenen Klassenobjekten, muss die gemeinsame Basis von MQL4 und MQL5 gefunden und der Basisklasse implementiert werden, während sich die unterschiedlichen Teile in sprachspezifischen, abgeleiteten Klassen der Basisklasse befinden. Für die Entwicklung der Basisklasse müssen wir folgenden Bereiche der Abwicklung einer Auftragserteilung verstehen:

  1. Das Verschicken der Handelsaufträge ist unterschiedlich
  2. Die Dokumentation des Handels ist unterschiedlich
  3. Bestimmte Merkmale in MQL5 gibt es nicht in MQL4

In diesem Bereich sind unterscheiden sich einig Komponenten aus MQL4 und MQL5. Betrachten wir die Funktion OrderSend (mql4, mql5), wie sie in der Dokumentation für beide Plattformen dargestellt sind:

(MQL4)

int  OrderSend(
   string   symbol,              // Symbol
   int      cmd,                 // Aktion
   double   volume,              // Volumen
   double   price,               // Preis
   int      slippage,            // Schlupf
   double   stoploss,            // StopLoss
   double   takeprofit,          // TakeProfit
   string   comment=NULL,        // Kommentar
   int      magic=0,             // Magicnummer
   datetime expiration=0,        // Verfallszeit eine Pendingorder
   color    arrow_color=clrNONE  // Farbe
       );

(MQL5)

bool  OrderSend(
   MqlTradeRequest&  request,      // Struktur der Handelsaufforderung
   MqlTradeResult&   result        // Struktur des Auftragsantwort
       );

Die Funktion für MQL4 ist ein eher direkter Ansatz. Die Funktion für MQL5 dagegen ist etwas komplizierter, aber sie reduziert die Parameterzahl auf lediglich zwei, mit den Daten (struct) des Auftrages und des Ergebnisses. Dies Hindernis bei der Übertragung bestimmter Komponenten aus der Standardbibliothek für MQL5 wurden in den früheren Artikeln dieser Serie bereits besprochen, besonders die Klassen CExpertTrade und CExpertTradeX. Daher wird der Order-Manager einfach diese Klassen verwenden, um die Kompatibilität zwischen beiden Sprachen zu gewährleisten.

Ein weiterer Aspekt ist die Art und Weise, wie eine Position in MetaTrader 4 oder MetaTrader 5 geschlossen oder gelöscht wird. Einerseits gibt es keinen großen Unterschied beim Löschen von Pending-Orders, anderseits aber einen großen Unterschied, wenn Marktorder (MQL4) oder Positionen (MQL5) geschlossen werden. In MQL4 wird eine offene Marktorder durch die Funktion OrderClose geschlossen. In MetaTrader 5 wird dafür die Funktion PositionClose aufgerufen, oder es wird ein Auftrag mit demselben Volumen in der Gegenrichtung der offenen Position erteilt.

In MetaTrader 5 wird jede Handelsaktivität dokumentiert. Egal, ob die Handelsaktivität eine Eröffnung, Änderung oder das Schließen betrifft, alles wird erfasst und die Daten, die diese Aktion betreffen, können von einem Expert Advisor abgefragt werden. Das ist im MetaTrader 4 nicht so. Wenn zum Beispiel einer Pending-Order eine Ticket-ID zugewiesen wurde, wird diese ID für diese Order während ihrer Gesamtzeit nicht mehr geändert, ob nun aus ihr im Laufe der Zeit eine offene Position im Markt oder sie wieder geschlossen wurde. Um sich die gesamte Abwicklung einer bestimmten Position anzuschauen, müssen das Log der Experten und des Journals durchgegangen werden, und das kann dauern. Darüber hinaus sind die Log-Dateien dazu bestimmt, von Menschen gelesen zu werden, es gibt keine Funktion in MQL4, die es einem Expert Advisor erleichtert, sich durch diese Informationen zu wühlen.

Aber in MQL5 gibt es solche Möglichkeiten, die in MQL4 schlichtweg nicht existieren. Ein Beispiel dafür sind die Ausführungsarten der Aufträge. MQL5 verfügt über folgende Volumensauftragsarten einer Order:

  • ORDER_FILLING_FOK – wenn das verlangte Volumen nicht ausgefüllt werden kann, storniere den Auftrag
  • ORDER_FILLING_IOC – wenn das verlangte Volumen nicht ausgefüllt werden kann, akzeptiere das maximal Verfügbare und storniere das Restvolumen.
  • ORDER_FILLING_RETURN – wenn das verlangte Volumen nicht ausgefüllt werden kann, akzeptiere das verfügbare Volumen. Ein Auftrag in Höhe des Restvolumens verbleibt im Markt.

In MetaTrader 4 wird ein Auftrag entweder erfüllt oder nicht (storniert), welches im wesentlichem einem ORDER_FILLING_FOK entspricht, während die beiden anderen Optionen nicht existieren. Allerdings kommt es nur selten dazu, dass das verlangte Volumen das zur Verfügung stehende überschreitet, besonders, bei geringem Risiko und/oder bei kleinerem Kontostand. ORDER_FILLING_IOC und ORDER_FILLING_RETURN können schwierig werden, da es praktisch unmöglich ist das in MQL4 umzusetzen, auch deshalb, weil ein Expert Advisor keine Möglichkeit hat, das verfügbare Volumen für einen speziellen Handelsauftrag zu bestimmen (und selbst wenn es so wäre, wäre dies Information hoch volatil und würde sich ständig ändern). Daher, um die Kompatibilität zwischen MQL4 und MQL5 zu bewahren, wird ORDER_FILLING_FOK als einzige Option verwendet (die ist auch in MQL5 der Standard). Daneben gibt es aber die Möglichkeit für den Experten eine Lotgröße für eine Order zu ermitteln, die über dem SYMBOL_VOLUME_MAX, das ist die vom Broker maximal erlaubte Lotgröße, liegt. MetaTrader 5 berücksichtigt das, in dem er automatisch die Positionen in mehrere zerlegt, aber auch das gibt es nicht im MetaTrader 4 (das würde zum Stornieren der Order führen). Daher ist es für die Cross-Plattform besser, das vom Expert Advisor vorab überprüfen zu lassen (vorzugsweise nachdem das Handelsvolumen ermittelt oder berechnet wurde), bevor der Handelsauftrag mittels des Order-Managers gesendet wird.

Das folgende Bild zeigt, wie der Order-Manager einen übergebenen Handelsauftrag bearbeitet:

Diagramm der Bearbeitung einen Handelsauftrages

Wie das Biuld zeigt, beginnt die Methode mit der Vorbereitung der benötigten Daten. Wenn es zulässig ist, die Position abzuschicken, und wenn die Voraussetzungen erfüllt sind, wird die Order abgeschickt. Andernfalls endet der Prozesse. Vor dem Abschicken des Handelsauftrages müssen notwendige Werte berechnet werden. War der Handelsauftrag erfolgreich, wird das Ergebnis überprüft und eine neue Instanz von COrder erstellt, die der Liste der aktuellen Orders/Positionen (m_orders) hinzugefügt wird.

Die Methoden, die nur Berechnungen durchführen, befinden sich in dieser Basisklasse. Die Methoden aber, die für beide Sprachen unterschiedlich sind, erweitern die Basismethoden um sprachspezifischen Klassen. Diese Methoden unterscheiden sich jedoch sehr wenig bezüglich der Art der durchzuführenden Berechnungen. Daher ist die Basisklasse rein virtuell, und die Implementierung kann zwei getrennte Versionen aufweisen. Wir finden die Implementierungen der Methode der Basisklasse wie folgt:

COrder* COrderManagerBase::TradeOpen(const string,ENUM_ORDER_TYPE)
  {
   return NULL;
      }

Berechnung des Handelsvolumens

Wie bereits erwähnt, ist es am besten, die Berechnung der nächsten Handelsposition einem anderen Klassenobjekt zuzuweisen, das Teil des Order-Manager ist. Dieser Ansatz wird in der Expertenbibliothek der Standardbibliothek von MQL5 verwendet. Im Folgenden sehen Sie den Code der Methode LotSizeCalculate, die das Volumen des nächsten Handelsauftrages berechnet:

double COrderManagerBase::LotSizeCalculate(const double price,const ENUM_ORDER_TYPE type,const double stoploss)
  {
   if(CheckPointer(m_moneys))
      return m_moneys.Volume(m_symbol.Name(),0,type,stoploss);
   return m_lotsize;
      }

Diese Methode prüft zunächst den Pointer auf eine Instanz von CMoneys, die ja nur die Objekte für das Geldmanagement enthält (genauso wie COrders die Instanzen von COrder enthält). Das Objekt für das Geldmanagement wird in einem eigenen Artikel besprochen werden. An dieser Stelle genügt es zu wissen, dass es eine eigene Komponente ist, die eine gültige Lotgröße berechnet. In dem Augenblick, da keine Instanz für das Geldmanagement übergeben werden kann, verwendet der Order-Manager ganz einfach die standardmäßige Lotgröße der Klasse m_lotsize.

Berechnung von StopLoss und TakeProfit

StopLoss und TakeProfit werden jeweils von den Methoden StopLossCalculate und TakeProfitCalculate berechnet. Die folgenden Teile des Codes zeigen, wie diese Methoden im Order-Manager implementiert wurden:

double COrderManagerBase::StopLossCalculate(const ENUM_ORDER_TYPE type,const double price)
  {
   if(CheckPointer(m_main_stop))
      return m_main_stop.StopLossTicks(type,price);
   return 0;
      }
double COrderManagerBase::TakeProfitCalculate(const ENUM_ORDER_TYPE type,const double price)
  {
   if(CheckPointer(m_main_stop))
      return m_main_stop.TakeProfitTicks(type,price);
   return 0;
      }

Die Berechnung ist an ein eigenes Klassenobjekt delegiert, das ein Mitglied des Order-Managers ist (wir werden das in einem anderen Artikel besprechen). Das Objekt für die Stopps erfährt jeweils für MetaTrader 4 und MetaTrader 5 eine eigene Umsetzung. Wenn jedoch kein Pointer auf ein Objekt für die Stopps übergeben wird, wird standardmäßig für StopLoss oder TakeProfit Null (kein SL/TP) verwendet.

Schließen einer Order oder Position

Das folgende Bild illustriert wie der Order-Manager Positionen schließt oder löscht:

Das Diagramm des Exits

Wie die Abbildung zeigt, wird zuerst die Gültigkeit des Pointers auf eine Instanz von COrder überprüft. Dann werden die Instanzen des Symbols und der Position abgefragt, die für die Ausführen der Positionsschließung notwendig sind. Dann wird je nach Typ die Position geschlossen oder gelöscht. Nach dem erfolgreichen Schließen/Löschen einer Order, wird die Instanz von COrder aus der Liste der offenen Positionen in die Liste der historischen Orders des Order-Manager (archiviert) verschoben. Die Methode setzt auch das Flag, das das Objekt COrder als geschlossen kennzeichnet.

Überprüfung der Einstellungen

Ein Validierung der Einstellungen des Order-Managers wird durch die Methode Validate dieser Klasse durchgeführt. Ein Ausschnitt des Codes der genannten Methode sehen sie unten:

bool COrderManagerBase::Validate(void) const
  {
   if(CheckPointer(m_moneys)==POINTER_DYNAMIC)
     {
      if(!m_moneys.Validate())
         return false;
     }
   if(CheckPointer(m_stops)==POINTER_DYNAMIC)
     {
      if(!m_stops.Validate())
         return false;
     }
   return true;
      }

Dieser Code ist ähnlich der der Methode ValidationSettings aus der Standardbibliothek von MQL5. Es wird ganz einfach die Methode Validate aufgerufen und sie gibt 'false' zurück, falls eines der Objekte nicht validiert wurde (was schließlich auch zum Abbruch der Funktion OnInit des Experten oder der Rückgabe von INIT_FAILED führen kann). Diese Methode sollte während der Initialisierung eines Expert Advisor ausgeführt werden.

Zählen der Positionen

Die Gesamtzahl der Positionen ist die Zahl aller, die der Order-Manager bis jetzt abgesendet hat, inklusive der geschlossenen in der Historie, ab dem Moment, da der Expert Advisor oder das Skript gestartet wurden. 'orders total' bezieht sich auf die Zahl der offenen Positionen des Kontos und 'orders history total' auf die bereits geschlossenen, die der Order-Manager in der eigenen Liste der historischen Positionen gefunden hat. Daher:

int COrderManagerBase::OrdersTotal(void) const
  {
   return m_orders.Total();
      }
int COrderManagerBase::OrdersHistoryTotal(void) const
  {
   return m_orders_history.Total();
      }
int COrderManagerBase::TradesTotal(void) const
  {
   return m_orders.Total()+m_orders_history.Total()+m_history_count;
      }
In beiden Fällen verwenden wir für das Berechnen der Order das Standardkonzept des MetaTrader 4 (jeder Auftrag ist eine Position, egal ob Markt- oder Pending-Order). Die Implementierung für MQL4 zeigt uns zum Beispiel diese zwei Zeilen der Methode TradeOpen:
int trades_total =TradesTotal();
  int orders_total = OrdersTotal();

Es ist wichtig zu beachten, dass OrdersTotal sich auf die Originalmethode der Klasse bezieht und nicht auf die Funktion OrdersTotal, die es für MQL4 und MQL5 gibt. Würden wir statt dessen die Orginalfunktion der Sprache verwenden wollen, müsste OrdersTotal mit dem Operator für den Kontext vor der Funktion aufgerufen werden:

int orders_total = ::OrdersTotal();

Sichern der Instanz COrder

Da der Order-Manager seine Liste der eigenen Positionen in MetaTrader 4 und MetaTrader 5 (aber beiderseits kompatibel) führt, benötigt er ein Flag der Instanz von COrder für bereits geschlossener Positionen. Die Positionen sind in m_orders und m_orders_history, Instanzen von COrders, gesichert, abhängig davon, ob sie noch offen oder schon geschlossen sind. Daraus ergibt sich für beide Versionen, dass ein Cross-Plattform-Experte prüfen muss, ob eine Position bereits geschlossen wurde.

Da beide Plattformen sich darin unterscheiden, wie sie Handelsaufträge dokumentieren, muss der Order-Manager diese in einer eigenen Art und Weise erfassen. Nach dem erfolgreichen Eröffnen einer Position wird eine Instanz von COrder erstellt und eventuell m_orders hinzugefügt. Sobald eine Order oder Position geschlossen wird, muss der Order-Manager die Instanz von COrder in m_orders_history verschieben. Die Methode ArchiveOrder, wie unten gezeigt, führt das in beiden Versionen aus:

bool COrderManagerBase::ArchiveOrder(COrder *order)
  {
   return m_orders_history.Add(order);
      }

MQL4-Specifische Umsetzung

Eröffnen einer Order oder Position

Hier der folgende Codeausschnitt der MQL4-spezifischen Methode TradeOpen des abgeleiteten Klasse von CorderManagerBase:

COrder* COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type)
  {
   int trades_total = TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol = m_symbol_man.Get(symbol);
   if (!CheckPointer(m_symbol))
      return NULL;
   if(!IsPositionAllowed(type))
      return NULL;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      ENUM_ORDER_TYPE ordertype = type;
      double price=PriceCalculate(ordertype);
      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);
      ulong ticket = SendOrder(type,lotsize,price,sl,tp);
      if (ticket>0)
      {
         if (OrderSelect((int)ticket,SELECT_BY_TICKET))
            return m_orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),(ENUM_ORDER_TYPE)::OrderType(),::OrderLots(),::OrderOpenPrice());
      }            
     }
   return NULL;
      }

Die Funktion hat zwei Argumente, den Name des Symbols oder Handelsinstrumentes und den Typ der zu eröffnenden Order. Sie beginnt mit der Abfrage der benötigten Daten für den Auftrag: die Zahl der offenen und geschlossenen Positionen und das Objekt des Symbols des Auftrages (entsprechend dem ersten Argument der Methode).

Sind beide Voraussetzungen ('max orders' und 'max trades') erfüllt, beginnt die Methode mit der Berechnung von StopLoss, TakeProfit und des Volumens unter Verwendung der jeweiligen Objekte. Schließlich sendet sie den Auftrag und, nach einem Erfolg, wird eine neue Instanz von COrder erstellt und sie in der Liste der aktiven Order (innerhalb des Order-Managers) gespeichert.

Schließen einer Order oder Position

Der folgende Codeausschnitt der MQL4-spezifischen Methode CloseOrder des abgeleiteten Klasse von CorderManagerBase:

bool COrderManager::CloseOrder(COrder *order,const int index=-1)
  {
   bool closed=true;
   if(CheckPointer(order)==POINTER_DYNAMIC)
     {
      if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),order.Symbol())!=0)
         m_symbol=m_symbol_man.Get(order.Symbol());
      if(CheckPointer(m_symbol))
         m_trade=m_trade_man.Get(order.Symbol());
      if(order.Volume()>0)
        {
         if(order.OrderType()==ORDER_TYPE_BUY || order.OrderType()==ORDER_TYPE_SELL)
            closed=m_trade.OrderClose((ulong)order.Ticket());
         else
            closed=m_trade.OrderDelete((ulong)order.Ticket());
        }
      if(closed)
        {
         int idx = index>=0?index:FindOrderIndex(GetPointer(order));
         if(ArchiveOrder(m_orders.Detach(idx)))
           {
            order.Close();
            order.Volume(0);
           }
        }
     }
   return closed;
  }

Wie wir später sehen werden ist die Version für MQL4 viel einfacher als die für MQL5, hauptsächlich, weil es nur eine Berechnung der Marge ('hedging') gibt. Aber selbst, wenn 'hedging' durch den MQL4-Broker deaktiviert wurde, bleibt der Vorgang des Schließens gleich: Löschen der schwebenden ('pending') und Schließen der offenen Positionen.

Die Funktion hat zwei Parameter, das Order-Objekt und dessen Index in der Liste der aktiven Order/Positionen. Gibt es einen gültigem Pointer auf das Objekt COrder, erhalten wir die richtige Instanz von CExpertTradeX und CSymbolInfo, um die Order zu schließen und sie dann in die Liste der historischen Order des Terminals durch den Aufruf der entsprechenden Funktion zu verschieben.

Sobald die Order oder die Position geschlossen wurde, muss das Objekt COrder aktualisiert werden. Zuerst muss es aus der Liste der offenen Order entfernt werden und dann an das Ende der Liste der historischen Order hinzugefügt werden. Dann muss noch das Flag auf geschlossen und das Volumen auf Null gesetzt werden.

Das zweite Argument der Methode der Klasse akzeptiert optional den Index. Das hilft die Ausführung zu beschleunigen, wenn der Index der Instanz von COrder bereits bekannt ist (das ist der Normalfall, da oft durch die Liste iteriert wird). Falls der Index nicht bekannt ist, kann die Methode mit nur einem Argument aufgerufen werden und die Methode würde dann eine weitere Methode der Klasse, FindOrderIndex, aufrufen, die den Platz der Instanz COrder in der Liste findet.


MQL5-Specifische Umsetzung

Eröffnen einer Order oder Position

Hier der folgende Codeausschnitt der MQL5-spezifischen Methode TradeOpen des abgeleiteten Klasse von CorderManagerBase:

COrder* COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type)
  {
   double lotsize=0.0,price=0.0;
   int trades_total =TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!IsPositionAllowed(type))
      return NULL;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      price=PriceCalculate(type);
      lotsize=LotSizeCalculate(price,type,m_main_stop==NULL?0:m_main_stop.StopLossCalculate(symbol,type,price));
      if (SendOrder(type,lotsize,price,0,0))
         return m_orders.NewOrder((int)m_trade.ResultOrder(),m_trade.RequestSymbol(),    (int)m_trade.RequestMagic(),m_trade.RequestType(),m_trade.ResultVolume(),m_trade.ResultPrice());
     }      
   return NULL;
      }

Wie man sieht, unterscheidet sich diese Version nicht besonders von der für MQL4. Ein wichtiger Unterschied ist jedoch, dass wir die Werte für StopLoss und TakeProfit nicht benötigen. Das liegt daran, dass sich die Stopps im MetaTrader 5 anders verhalten. Ein Programmierer mit Erfahrungen über Expert Advisor in MQL5 weiß, dass wir in dieser Bibliothek Pending-Order als Äquivalent zu den brokerseitigen Stopps in MQL4 verwenden.

Schließen einer Order oder Position

Hier der folgende Codeausschnitt der MQL5-spezifischen Methode CloseOrder der abgeleiteten Klasse von CorderManagerBase:

bool COrderManager::CloseOrder(COrder *order,const int index=-1)   {    bool closed=true;    COrderInfo ord;    if(!CheckPointer(order))       return true;    if(order.Volume()<=0)       return true;    if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),order.Symbol())!=0)       m_symbol=m_symbol_man.Get(order.Symbol());    if(CheckPointer(m_symbol))       m_trade=m_trade_man.Get(order.Symbol());    if(ord.Select(order.Ticket()))    {       closed=m_trade.OrderDelete(order.Ticket());    }      else      {       ResetLastError();       if(IsHedging())       {          closed=m_trade.PositionClose(order.Ticket());       }         else         {          if(COrder::IsOrderTypeLong(order.OrderType()))             closed=m_trade.Sell(order.Volume(),0,0,0);          else if(COrder::IsOrderTypeShort(order.OrderType()))             closed=m_trade.Buy(order.Volume(),0,0,0);         }      }    if(closed)      {

      if(ArchiveOrder(m_orders.Detach(index)))         {          order.Close();          order.Volume(0);         }      }    return closed;   }

Beim Schließen einer Order oder Position in MQL5 müssen wir den Modus der Marge ('netting' oder 'hedge') beachten. Aber zuerst müssen wir erkennen, ob es sich um eine MQL5-Order oder eine MQL5-Position handelt. Wir können die Funktionen OrderSelect und HistoryOrderSelect dafür verwenden, aber um den Code für diese Methode zu verkürzen und den Vorgang zu vereinfachen, verwenden wir einfach die Klasse COrderInfo aus der Standardbibliothek von MQL5.

Eine Order in MQL5 entsteht als Ergebnis eines Handelsauftrages, und das führt oft zu einem 'Deal' oder einer Reihe von 'Deals' (entspricht in etwa einer Marktorder in MQL4). Wenn jedoch der Auftrag nicht sofort ausgeführt wird, dann bezieht es sich auf eine Pending-Order (anders als in MetaTrader 4, wo es Markt- und Pending-Order gibt). Jetzt, um beides zu differenzieren, prüft die Methode mittels COrderInfo zuerst, ob es eine Pending-Order ist, und, wenn ja, wird sie gelöscht. Schlägt die Prüfung fehl, sind wir sicher, es ist eine Marktorder oder eine Position. Im Modus 'hedging' wird die Position mit der Funktion PositionClose geschlossen. Andernfalls, im Modus 'netting', neutralisieren wir die Position durch einen Gegenposition gleichen Volumens.

Erstellen einer Instanz von COrder

Wir haben jetzt also gesehen, wie der Order-Manager Positionen öffnet und schließt. In einen früheren Artikel haben wir bereits gezeigt, wie die Klasse CExpertTrade geändert werden kann, so dass sie kompatibel mit beiden Plattformen ist. Nun gibt es einen Unterschied, wie StopLoss und TakeProfit von den beiden Plattformen umgesetzt werden, und der Order-Manager implementiert diesen Vorgang nur ganz einfach. Der Rest dieses Vorgangs wird bei der Initialisierung für die Instanz COrder festgelegt, die durch die Methode NewOrder von COrders aufgerufen wird. Der folgende Code zeigt die Initialisierungsmethode von COrdersBase:

COrder* COrdersBase::NewOrder(const ulong ticket,const string symbol,const int magic,const ENUM_ORDER_TYPE type,const double volume,const double price)
  {
   COrder *order=new COrder(ticket,symbol,type,volume,price);
   if(CheckPointer(order)==POINTER_DYNAMIC)
      if(InsertSort(GetPointer(order)))
      {  
         order.Magic(magic);
         order.Init(GetPointer(this),m_stops);
         return order;
      }  
   return NULL;
      }

Wie man sieht akzeptiert die Initialisierungsmethode der Klasse COrder ein Objekt des Nutzers (CStops) als zweites Argument. Das ist eine Container für Stopp-Objekte (wie das Objekt m_main_stop von früher). Dieser Klasse wird ein eigener Artikel gewidmet.

Ändern einer Order oder Position

Bis jetzt haben wir noch nichts gezeigt, mit dem der Order-Manager bestehende Positionen verändern kann. Das wird einem eigenen Stopp-Objekt übertragen (CStop und CorderStop), die in einem eigenen Artikel besprochen werden. Diese Objekte werden für Aktualisierung und Modifikation der Stopps einer Position verantwortlich sein, so wie auch für die Koordination mit der COrder, der es angehört.

In MetaTrader 4 kann der Eröffnungspreis einer Pending-Order beliebig oft verändert werden. Dies ist beim MetaTrader 5 nicht der Fall. Jetzt ist die Version für MQL5 der limitierende Faktor, wir müssen daher den Standard von MQL5 anpassen. Das Ändern einer Pending-Order verlangt das Löschen der existierenden und das Senden einer neuen Pending-Order mit den aktualisierten Eigenschaften.

Beispiel

Als Beispiel nehmen wir einen Expert Advisor mit den in dieser Artikelserie besprochenen Klassenobjekten. Nach dem Erstellen des Expert Advisors im MetaEditor beginnen wir mit den Verweisen auf die Bibliothek:

#include "MQLx\Base\OrderManager\OrderManagerBase.mqh"

Beachte, dass wir die Anführungszeichen verwenden, und nicht "<" und ">". Wir platzieren die Bibliothek im selben Verzeichnis, wie die Datei mit dem Quellcode des Expert Advisors.

Für diesen EA benötigen wir mindestens drei Pointer, die im Programm global deklariert werden müssen (COrderManager, CSymbolInfo und CSymbolManager):

COrderManager *order_manager;
CSymbolManager *symbol_manager;
CSymbolInfo *symbol_info;

In der Funktion OnInit müssen wir diese drei Pointer initialisieren, besonders die Instanz CSymbolInfo, die den spezifizierten Namen des Handelsinstrumentes während der Initialisierung verlangt .

int OnInit()
  {
//---
   order_manager = new COrderManager();
   symbol_manager = new CSymbolManager();
   symbol_info = new CSymbolInfo();
   if (!symbol_info.Name(Symbol()))
   {
      Print("symbol not set");
      return (INIT_FAILED);
   }   
   symbol_manager.Add(GetPointer(symbol_info));   
   order_manager.Init(symbol_manager,NULL);
//---
   return(INIT_SUCCEEDED);
  }

In OnDeinit müssen wir die drei Pointer wieder löschen, damit kein Speicherleck ('memory leak') entsteht (zumindest für diese Plattform):

void OnDeinit(const int reason)
  {
//---
   delete symbol_info;
   delete symbol_manager;
   delete order_manager;
  }

In OnTick müssen wir die aktuelle Strategie implementieren. Der Experte in diesem Beispiel verwendet eine einfache Methode, um eine neue Bar zu erkennen (durch das Zählen der Bars auf dem Chart). Die vorherige Zahl von Bars wird in einer statischen (oder globalen) Variablen gesichert. Das gleiche gilt für eine Variable der Richtung, die zur Sicherung der vorherigen Handelsrichtung des Experten verwendetet wird (oder Null bei ersten Mal). Da sich aber die Funktion, die die Bars des Charts zählt, in beiden Plattformen unterscheidet, müssen wir die Implementation dies bezüglich teilen:

static int bars = 0;
static int direction = 0;
int current_bars = 0;
#ifdef __MQL5__
   current_bars = Bars(NULL,PERIOD_CURRENT);
#else 
   current_bars = Bars;
#endif

In der Version für MQL4 verwenden wir einfach die zugeordnete Konstante Bars (ein Aufruf der Funktion iBars könnte auch verwendet werden). Andererseits verwenden wir für die Version von MQL5 die Funktion Bars.

Der nächste Codeausschnitt implementiert das beobachtbare, aktuelle Verhalten des Experten. Ändert sich die Zahl der Bars, beginnt der Experte mit der Initialisierung der Preise des Symbol (CSymbolInfo) für die weitere Bearbeitung. Dann wird geprüft, ob eine vorherige Handelsaktion zu schließen ist. Wurde eine gefunden, schließt sie der Experte und fährt fort, einen neue Position auf Basis der vorherigen Richtung zu eröffnen. Der Code endet mit der Aktualisierung des Zählers der Bars des EAs. 

if (bars<current_bars)
   {   
      symbol_info.RefreshRates();
      COrder *last = order_manager.LatestOrder();
      if (CheckPointer(last) && !last.IsClosed())
         order_manager.CloseOrder(last);
      if (direction<=0)
      {
         Print("Entering buy trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
         direction = 1;
      }
      else
      {
         Print("Entering sell trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
         direction = -1;
      }   
      bars = current_bars;
   }

Um sicherzustellen, dass wir den selben Code für die erste Version wie für alle weiteren verwenden, verschieben wir den Code von oben in eine "Header-Datei" und laden sie in die Hauptdatei (jeweils für die Version von MQL4 und MQL5). Beide Dateien mit dem Quellcode (test_ordermanager.mq4 oder test_ordermanager.mq5, abhängig von der Zielplattform) erhalten eine einzelne Zeile mit der Referenz zu der "Header-Datei":

#include "test_ordermanager.mqh"

Die folgenden Tabellen zeigen die Ergebnisse der Expert Advisor auf MetaTrader 4, und auf MetaTrader 5 im 'netting' und 'hedging' Modus so, wie sie sich jeweils im Strategietester darstellen. Der Kürze halber sind in diesem Artikel nur die jeweils ersten 10 Positionen angeführt (das Gesamtergebnis findet sich im Zip-Paket am Ende des Artikels).

MT4:

# Zeit Typ Order Größe Preis S / L T / P Gewinn Kontostand
1 2017.01.02 00:00 buy 1 0.10 1.05102 0.00000 0.00000
2 2017.01.02 01:00 close 1 0.10 1.05172 0.00000 0.00000 7.00 10007.00
3 2017.01.02 01:00 sell 2 0.10 1.05172 0.00000 0.00000
4 2017.01.02 02:00 close 2 0.10 1.05225 0.00000 0.00000 -5.30 10001.70
5 2017.01.02 02:00 buy 3 0.10 1.05225 0.00000 0.00000
6 2017.01.02 03:00 close 3 0.10 1.05192 0.00000 0.00000 -3.30 9998.40
7 2017.01.02 03:00 sell 4 0.10 1.05192 0.00000 0.00000
8 2017.01.02 04:00 close 4 0.10 1.05191 0.00000 0.00000 0.10 9998.50
9 2017.01.02 04:00 buy 5 0.10 1.05191 0.00000 0.00000
10 2017.01.02 05:00 close 5 0.10 1.05151 0.00000 0.00000 -4.00 9994.50
11 2017.01.02 05:00 sell 6 0.10 1.05151 0.00000 0.00000
12 2017.01.02 06:00 close 6 0.10 1.05186 0.00000 0.00000 -3.50 9991.00
13 2017.01.02 06:00 buy 7 0.10 1.05186 0.00000 0.00000
14 2017.01.02 07:00 close 7 0.10 1.05142 0.00000 0.00000 -4.40 9986.60
15 2017.01.02 07:00 sell 8 0.10 1.05142 0.00000 0.00000
16 2017.01.02 08:00 close 8 0.10 1.05110 0.00000 0.00000 3.20 9989.80
17 2017.01.02 08:00 buy 9 0.10 1.05110 0.00000 0.00000
18 2017.01.02 09:00 close 9 0.10 1.05131 0.00000 0.00000 2.10 9991.90
19 2017.01.02 09:00 sell 10 0.10 1.05131 0.00000 0.00000
20 2017.01.02 10:00 close 10 0.10 1.05155 0.00000 0.00000 -2.40 9989.50


MT5 ('netting'):

Eröffnungszeit Order Symbol Typ Volumen Preis S / L T / P Zeit Status Kommentar
2017.01.02 00:00:00 2 EURUSD buy 0.10 / 0.10 1.05140 2017.01.02 00:00:00 gefüllt
2017.01.02 01:00:00 3 EURUSD sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 gefüllt
2017.01.02 01:00:00 4 EURUSD sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 gefüllt
2017.01.02 02:00:00 5 EURUSD buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 gefüllt
2017.01.02 02:00:00 6 EURUSD buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 gefüllt
2017.01.02 03:00:00 7 EURUSD sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 gefüllt
2017.01.02 03:00:00 8 EURUSD sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 gefüllt
2017.01.02 04:00:00 9 EURUSD buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 gefüllt
2017.01.02 04:00:00 10 EURUSD buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 gefüllt
2017.01.02 05:00:00 11 EURUSD sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 gefüllt
2017.01.02 05:00:00 12 EURUSD sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 gefüllt
2017.01.02 06:00:00 13 EURUSD buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 gefüllt
2017.01.02 06:00:00 14 EURUSD buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 gefüllt
2017.01.02 07:00:00 15 EURUSD sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 gefüllt
2017.01.02 07:00:00 16 EURUSD sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 gefüllt
2017.01.02 08:00:00 17 EURUSD buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 gefüllt
2017.01.02 08:00:00 18 EURUSD buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 gefüllt
2017.01.02 09:00:00 19 EURUSD sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 gefüllt
2017.01.02 09:00:00 20 EURUSD sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 gefüllt
2017.01.02 10:00:00 21 EURUSD buy 0.10 / 0.10 1.05164 2017.01.02 10:00:00 gefüllt


MT5 ('hedging'):














Eröffnungszeit Order Symbol Typ Volumen Preis S / L T / P Zeit Status Kommentar
2017.01.02 00:00:00 2 EURUSD buy 0.10 / 0.10 1.05140 2017.01.02 00:00:00 gefüllt
2017.01.02 01:00:00 3 EURUSD sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 gefüllt
2017.01.02 01:00:00 4 EURUSD sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 gefüllt
2017.01.02 02:00:00 5 EURUSD buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 gefüllt
2017.01.02 02:00:00 6 EURUSD buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 gefüllt
2017.01.02 03:00:00 7 EURUSD sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 gefüllt
2017.01.02 03:00:00 8 EURUSD sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 gefüllt
2017.01.02 04:00:00 9 EURUSD buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 gefüllt
2017.01.02 04:00:00 10 EURUSD buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 gefüllt
2017.01.02 05:00:00 11 EURUSD sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 gefüllt
2017.01.02 05:00:00 12 EURUSD sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 gefüllt
2017.01.02 06:00:00 13 EURUSD buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 gefüllt
2017.01.02 06:00:00 14 EURUSD buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 gefüllt
2017.01.02 07:00:00 15 EURUSD sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 gefüllt
2017.01.02 07:00:00 16 EURUSD sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 gefüllt
2017.01.02 08:00:00 17 EURUSD buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 gefüllt
2017.01.02 08:00:00 18 EURUSD buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 gefüllt
2017.01.02 09:00:00 19 EURUSD sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 gefüllt
2017.01.02 09:00:00 20 EURUSD sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 gefüllt
2017.01.02 10:00:00 21 EURUSD buy 0.10 / 0.10 1.05164 2017.01.02 10:00:00 gefüllt


Beachten Sie, die Ergebnisse für die Modi 'hedging' und 'netting' in MT5 sind identisch. Obwohl die grundlegende Implementierungen gleich sind, gibt es den Unterschied zwischen dem 'netting' Modus, in dem eine Position durch eine Gegenposition gleichen Volumens neutralisiert wird und dem 'hedging' Modus, in dem die Positionen so geschlossen werden, wie es viele vom MetaTrader 4 gewohnt sind. Im 'hedging' Modus sehen die Nachrichten so aus:

PE      0       16:19:15.747    Trade   2017.01.02 01:00:00   instant sell 0.10 EURUSD at 1.05172, close #2 (1.05172 / 1.05237 / 1.05172)
GP      0       16:19:15.747    Trades  2017.01.02 01:00:00   deal #3 sell 0.10 EURUSD at 1.05172 done (based on order #3)
DS      0       16:19:15.747    Trade   2017.01.02 01:00:00   deal performed [#3 sell 0.10 EURUSD at 1.05172]

Beachten Sie das "close #2" bereits in der ersten Zeile. Der 'hedging' Modus führt zum Neutralisieren (Schließen) einer bestimmten Position. Andererseits im 'netting' Modus sehen wir hingegen nur:

PG      0       16:20:51.958    Trade   2017.01.02 01:00:00   instant sell 0.10 EURUSD at 1.05172 (1.05172 / 1.05237 / 1.05172)
MQ      0       16:20:51.958    Trades  2017.01.02 01:00:00   deal #3 sell 0.10 EURUSD at 1.05172 done (based on order #3)
KN      0       16:20:51.958    Trade   2017.01.02 01:00:00   deal performed [#3 sell 0.10 EURUSD at 1.05172]

In diesem Modus ist es weniger offensichtlich, ob der durchgeführte 'deal' eine neue Position eröffnet oder eine bestehende Position neutralisiert.

Blick auf die Struktur

Die Klasse COrderManager ist eine der komplexesten Objekte dieser Artikelserie. Um eine Idee zu erhalten, wie am Ende der Order-Manager mit all seinen Objekten strukturiert ist, betrachten Sie folgendes Diagramm:

Überblick über die Struktur des Order-Managers

Einfach gesagt, besitzt der Order-Manager (COrder) zwei Instanzen von COrders (aktuelle und historische), die als Container der eigenen Aufträge dienen. Jedem von ihnen können Stopps zugewiesen werden (möglich sind kein Stopp oder mehrere), und jedem Stopp kann einen eigene Methode des Stopp-Nachziehens zugewiesen werden (keine oder mehrere). Obwohl die meisten Expert Advisor diese Komplexität nicht benötigen, werden gerade die von ihm profitieren, die mehrere Unterstützungs- oder Widerstandsebenen verwenden. Diese Mitglieder der Klasse werden in späteren Artikeln besprochen.

Schlussfolgerung

In diesem Artikel besprechen wir die Klasse COrderManager, die für die Verwaltung der Handelsaktivitäten eines EAs verantwortlich ist. Die Klasse COrderManager wurde so konzipiert, dass sie für MQL4 und MQL5 verwendet werden kann, so dass Händler und Programmierer, die Expert Advisors erstellen, einen kompatiblen Code für beide Plattformen schreiben können, sei es im Hauptteil oder in Headerdateien.