Die optimale Berechnungsmethode für das Gesamtvolumen an Positions nach der festgelegten Magischen Zahl

Dmitry Fedoseev | 9 März, 2016

Einleitung

Der MetaTrader 5 Client-Terminal gestattet das parallele Arbeiten mehrerer Expert Advisors mit einem Symbol. Das geht leicht - man öffnet einfach mehrere Charts und hängt dort Expert Advisors an. Es wäre schön, wenn jeder Expert Advisor unabhängig von den anderen Expert Advisors, die mit dem gleichen Symbol arbeiten, funktionieren würde (dieses Problem taucht bei Expert Advisors, die mit verschiedenen Symbolen arbeiten, nicht auf).

Zunächst kann ein Expert Advisor in kompletter Kompatibilität mit seiner Testleistung und Optimierung im Strategie- Tester Handel ausführen. Die Bedingungen zum Öffnen einer Position können von der Größe oder des Nichtvorhandenseins bereits geöffneter Positions abhängen. Wenn mehrere Expert Advisors mit dem gleichen Symbol arbeiten, beeinflussen sie sich gegenseitig.

Der zweite, und vielleicht wichtigere Punkt ist, Experts Adivors zu gestatten, verschiedene Geldmanagement-Systeme zu verwenden, je nach den, in den Expert Advisors implementierten, Handelsstrategien. Und schließlich sollte es auch die Möglichkeit geben, die Ergebnisse jedes Expert Advisors zu kontrollieren und ihn ggf. abzustellen.


1. Das allgemeine Prinzip der Berechnung von Position-Volumen

Wenn Sie eine Order öffnen, können Sie sie mit einer magischen Zahl markieren, indem Sie den Wert der magischen Variable in der MqlTradeRequest Struktur angeben, die an die OrderSend() Funktion übertragen wird. Wird die Order ausgeführt, ist der Abschluss zugleich mit der magischen Zahl der Order markiert. Wenn wir dann weiter die Abschlüsse in der History analysieren, sehen wir die, von den verschiedenen Expert Advisors eröffneten Abschlüsse.

Die Berechnungsmethode der Gesamt-Position ist ziemlich einfach: wenn Sie z.B. einen Buy-Abschluss mit Volumen 0,1 ausführen, dann noch einen Buy mit 0,1 und dann einen Sell mit 0,1, beläuft sich das Volumen der Gesamtposition auf 0,1+0,1-0,1=+0,1. Wir zählen die Menge der Buy-Abschlüsse zusammen, ziehen davon die Menge der Sell-Abschlüsse ab und schon haben wir das Volumen der Gesamtposition.

Hierbei ist es wichtig, Berechnungen zu beginnen, wenn das Volumen der Gesamtposition = 0 ist. Der erste und offensichtlichste Punkt ist der Moment, in dem der Account eröffnet wird. Mit anderen Worten: Sie können die History aller Abschlüsse des Accounts mit Hilfe der HistorySelect() Funktion abfragen, deren erster Parameter = 0 ist (die kleinstmögliche Zeit) und dem Wert des zweiten Parameters TimeCurrent() (die jüngste, bekannteste Zeit eines Servers):

HistorySelect(0,TimeCurrent()); // load all history

Dann gehen Sie die gesamte History von Anfang bis Ende durch und zählen für jeden Abschluss mit der festgelegten magischen Zahl die Menge der Buy-Abschlüsse zusammen und ziehen davon alle Sell-Abschlüsse ab. So kann man das machen, doch in der Praxis kann die History der Abschlüsse durchaus ziemlich lang sein. Und das bremst die Geschwindigkeit eines Expert Advisors ganz erheblich, vor allem während Tests und der Optimierung, und kann sogar dszu führen, dass so ein Expert Advisor gar nicht mehr angewendet werden kann. Wir müssen den allerletzten Moment in der Abschlüsse-History finden, den Augenblick, als das Volumen der gesamten Netto-Positions = 0 war.

Und dazu müssen wir zunächst die gesamte History durchgehen und den letzten Punkt finden, wo das Volumen der gesamten Netto-Positions = 0 war. Wenn wir ihn haben, speichern wir ihn in einer bestimmten Variable (festgelegte Zeit der Position). Der Expert Advisor geht später durch die History der Abschlüsse, und zwar angefangen bei der Zeit dieses gespeicherten Punkts. Besser ist es, diesen Punkt in einer globalen Variable des Client-Terminals zu speichern, statt in einer Variable eines Expert Advisors, da sie in diesem Fall beim Abhängen des Expert Advisors vernichtet wird.

Selbst wenn in so einem fall der Expert Advisor gestartet wird, müssen Sie die minimal notwendige History laden und eben nicht mehr die ganze Abschlüsse-History. Es gibt viele Expert Advisors, die auf dem gleichen Symbol handeln können: also teilen wir diese globale Variable (mit der gespeicherten Zeit des jüngsten Punkts mit dem Volumen = 0) mit allen Expert Advisors.

Lassen wir unser Hauptthema für einen Augenblick beiseite und betrachten uns jetzt die Verwendung der globalen Variablen des Client-Terminals, mit deren Hilfe verschiedene Expert Advisors mit dem gleichen Symbol arbeiten können (vielleicht mit unterschiedlichen Parametern), und die Überschneidung von Namen, die von unterschiedlichen Instanzen von Expert Advisors erzeugt wurden, vermieden wird.


2. Verwendung der globalen Variablen des Client-Terminal

Die MQL5 Pogrammiersprache enthält die MQLInfoString() Funktion, mit der man unterschiedliche Informationen über ein mql5-Programm erhalten kann.

Möchte man etwas über den Dateinamen wissen, muss man diese Funktion mit dem MQL_PROGRAM_NAME Identifikator aufrufen:

MQL5InfoString(MQL_PROGRAM_NAME); // Expert Advisor name

Also starten wir die Namen der globalen Variablen mit dem Namen eines Expert Advisors. Ein Expert Advisor kann mit verschiedenen Symbolen arbeiten, d.h., wir müssen den Namen eines Symbols (Symbol) hinzufügen. Expert Advisors können mit dem selben Symbol arbeiten, doch mit unterschiedlichen Zeiträumen (mit verschiedenen Einstellungen), und für solche Fälle brauchen wir die magische Zahl. Also fügen wir auch die magische Zahl hinzu.

Hat also der Expert Advisor eine magische Zahl, die in der Variable Magic_N abgelegt ist, fügen wir sie dem Namen der globalen Variable hinzu.

Die Namen aller globalen Variablen sehen dann so aus:

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor and symbol name 
                                                                            // and its magic number

wobei gvp (globale Variable Prefix) - eine String-Variable ist, die im Bereich gemeinsame Variablen deklariert wurde.

Ich erkläre hier kurz die Terminologie, damit es zu keinen Verwechslungen bei den globalen Variablen,so wie sie beim Programmieren verwendet werden, kommt (globale Variablen sind in allen Funktionen sichtbar; lokale Variablen von Funktionen hingegen nur innerhalb dieser Funktion).

Doch unser Fall hier ist anders - der Terminus "globale Variablen" bezieht sich auf die globalen Variablen des Client-Terminals (spezielle, in einer Datei gespeicherte Variablen, die durch die GlobalVariable...() Funktionen verfügbar sind). Wenn wir also von globalen Variablen sprechen (so wie sie beim Programmieren verwendet werden), verwenden wir den Begriff "gemeinsame Variablen". "Lokale Variablen" bezeichnet weiterhin unverändert lokale Variablen..

Globale Variablen sind sinnvoll, da sie ihre Werte nach der De-Initialisierung eines Expert Advisors speichern (beim Neustart eines Expert Advisors, Client-Terminals, Computers). Im Test-Modus jedoch müssen alle Variablen gelöscht werden (oder bei einer Optimierung zuvor übertragen werden). Die in echten Handelsabläufen verwendeten, globalen Variablen, sollten von den globalen Variablen, die beim Testen erzeugt werden, abgetrennt werden, da die "Test-Variablen" nach abgeschlossenem Test wieder gelöscht werden müssen. Vom Expert Advisor erzeugte globale Variablen dürfen jedoch nicht verändert oder gelöscht werden.

Mit Hilfe der AccountInfoInteger() Funktion und ihrem Aufruf mittels des ACCOUNT_TRADE_MODE Identifikators, lässt sich der aktuelle Modus abgrenzen: Test-, Demo- oder echter Account.

Erweitern wir die globalen Variablen um ein Präfix: "d" - bei Demo-Accounts, "r" - bei echten Accounts, "t" - für den Strategie-Tester:

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor, symbol name
                                                                                  // and the Expert Advisor magic number
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO))
  {
   gvp=gvp+"d_"; // demo account
  }
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
   gvp=gvp+"r_"; // real
  }
if(MQL5InfoInteger(MQL_TESTER))
  {
   gvp=gvp+"t_"; // testing
  }

Diese Funktion sollte von der OnInit() Funktion des Expert Advisors aufgerufen werden.

Wie oben bereits angesprochen, sollten globale Variablen bei Tests gelöscht werden. Wir müssen also die Funktion, die dieses Löschen ausführt, in die OnDeinit() Funktion eines Expert Advisors einfügen:

void fDeleteGV()
  {
   if(MQL5InfoInteger(MQL_TESTER)) // Testing mode
     {
      for(int i=GlobalVariablesTotal()-1;i>=0;i--) // Check all global variables (from the end, not from the begin)
        {
         if(StringFind(GlobalVariableName(i),gvp,0)==0) // search for the specified prefix
           {
            GlobalVariableDel(GlobalVariableName(i)); // Delete variable
           }
        }
     }
  }

Momentan ist es nicht möglich, Tests in MetaTrader 5 zu unterbrechen, d.h. die Ausführung der OnDeinit() Funktion ist nicht garantiert - doch dies kann sich in Zukunft durchaus noch ändern. Wir wissen nicht, ob die OnDeinit() Funktion nach Unterbrechung des Strategie-Testers ausgeführt wird, sodass wir daher die globalen Variablen zu Beginn des Laufens eines Expert Advisors löschen - und zwar innerhalb der OnInit() Funktion.

Das ergibt dann folgenden Code der OnInit() und OnDeinit() Funktionen:

int OnInit()
  {
   fCreateGVP(); // Creating a prefix for the names of global variables of the client terminal
   fDeleteGV();  // Delete global variables when working in Tester
   return(0);
  }

void OnDeinit(const int reason)
  {
   fDeleteGV();  // Delete global variables when working in tester
  }

Die Verwendung der globalen Variablen können wir durch Erzeugung der Funktionen mit Kurznamen für das Anlegen von globalen Variablen noch vereinfachen (anstelle von GlobalVariableSet(gvp+...),.

Die Funktion zur Einrichtung des Werts der globalen Variable:

void fGVS(string aName,double aValue)
  {
   GlobalVariableSet(gvp+aName,aValue);
  }

Die Funktion zum Erhalt des Werts der globalen Variable:

double fGVG(string aName)
  {
   return(GlobalVariableGet(gvp+aName));
  }

Die Funktion zum Löschen der globalen Variable:

void fGVD(string aName)
  {
   GlobalVariableDel(gvp+aName);
  }

Wir haben nun globale Variablen besprochen, aber das ist noch nicht alles.

Wir müssen noch eine Möglichkeit schaffen, um globale Variablen für ein Symbol einrichten und ihre unterschiedliche Aktion im Account und im Strategie-Tester anbieten zu können. Die Namen dieser globale Variablen sollten nicht vom Namen und der magischen Zahl eines Expert Advisors abhängen.

Deklarieren wir also eine andere Variable für ein globales Variablen-Präfix, das wir "Commom_gvp" nennen. Bei der Arbeit mit einem Account hat diese Variable dann den Wert "COMMON". Bei der Arbeit mit dem Strategie-Tester hat sie denselben Wert wie eine Variable gvp (zum Löschen der Variable vor oder nach dem Strategie-Backtesting).

Die Funktion zur Vorbereitung der Präfixe für die globalen Variablen sieht so aus:

void fCreateGVP()
  {
   gvp=MQL5InfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_";
   Commom_gvp="COMMOM_"; // Prefix for common variables for all Expert Advisors
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)
     {
      gvp=gvp+"d_";
     }
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
     {
      gvp=gvp+"r_";
     }
   if(MQLInfoInteger(MQL_TESTER))
     {
      gvp=gvp+"t_";
      Commom_gvp=gvp; // To be used in tester, the variables with such a prefix 
                      // will be deleted after the testing
     }
  }

Sie mögen jetzt vielleicht denken, dass die Präfixe globaler Variablen Extra-Information enthalten - die Trennung von Demo- und echten Accounts und das "t"-Präfix fürs Testen, was jedoch auch durch Hinzufügen des "t" Chars erreicht werden könnten, das angibt, dass unser Expert Advisor im Strategie-Tester arbeitet. Ich habe das hier so gemacht. Wir wissen nicht, was uns die Zukunft noch bringt und kennen auch die Punkte nicht, die für eine Analyse der Arbeit eines Expert Advisors noch alle erforderlich werden könnten.

Speichern schadet also nichts.

Die oben dargestellte Funktion bedeutet, dass der Client-Terminal mit einem Account arbeitet und sich während dieser Arbeit auch keine Veränderung am Account stattfindet. Die Veränderung eines Accounts während der Arbeit eines Expert Advisors ist untersagt. Klar kann dies ggf. umgangen werden, indem man die Namen der globalen Variablen um eine Account-Ziffer ergänzt.

Ein weiterer wichtiger Hinweis! Die Länge des Namens der globalen Variable ist auf 63 Zeichen begrenzt. Daher bitte Ihren Expert Advisors keinen allzu langen Namen geben.

So, jetzt sind wir mit den globalen Variablen durch, und können uns dem Hauptthema dieses Beitrags widmen - der Berechnung des Positionvolumens durch eine festgelegte magische Zahl.


3. Das Volumen einer Position berechnen

Schauen wir zuerst mal nach, ob es eine globale Variable mit der Information über die letzte Zeit der Nullvolumen-Position gibt. Das tun wir mit Hilfe der GlobalVariableCheck() Funktion (aus Gründen der Vereinfachung, nennen wir dies einen "Null-Position" Fall, falls es keine geöffneten Positionen gibt).

Finden wir so eine Variable, laden wir nur die History der Abschlüsse zu Beginn dieser Zeit, die in der Variable gespeichert ist. Ansonsten laden wir die gesamte History:

if(GlobalVariableCheck(Commom_gvp+sSymbol+"_HistStTm")) // Saved time of a "zero" total position
  {
   pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); // initial date setting 
                                                                             // select only the history needed
  }
else
 {
   GlobalVariableSet(Commom_gvp+sSymbol+"_HistStTm",0);
 }
if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Load the necessary part of the deal history
  { 
   return(false);
  }

Anschließend definieren wir das Volumen der gesamten Netto-Position für ein Symbol:

double CurrentVolume=fSymbolLots(pSymbol);

Das Volumen einer Position wird mit Hilfe der fSymbolLots() Funktion festgelegt.

Das Volumen einer Position kann man auf verschiedene Arten bekommen: beispielsweise mit Hilfe der PositionSelect() Funktion. Liefert die Funktion 'false', heißt das, dass es keine Position gibt (das Volumen = 0). Liefert die Funktion 'true', erhält man das Volumen mit Hilfe der PositionGetDouble() Funktion mit dem POSITION_VOLUME Identifikator. Die Art der Position (Buy oder Sell) wird mit Hilfe der PositionGetInteger() Funktion mit dem POSITION_TYPE Identifikator ermittelt. Die Funktion liefert für Long Positions einen positiven und für Short Positions einen negativen Wert.

Und so sieht die komplette Funktion aus:

double fSymbolLots(string aSymbol)
  {
   if(PositionSelect(aSymbol,1000)) // the position has been selected successfully, so it exists
     {
      switch(PositionGetInteger(POSITION_TYPE)) // It returns the positive or negative value dependent on the direction
        {
         case POSITION_TYPE_BUY:
            return(NormalizeDouble(PositionGetDouble(POSITION_VOLUME),2));
            break;
         case POSITION_TYPE_SELL:
            return(NormalizeDouble(-PositionGetDouble(POSITION_VOLUME),2));
            break;
        }
     }
   else
     {
      return(0);
     }
  }

Alternativ lässt sich das Volumen der Gesamt-Position des Symbols durch die Schleife durch alle Positions bestimmen, wobei die Anzahl der Positions durch die PositionsTotal() Funktion festgelegt ist. Danach müssen Sie das notwendige Symbol mit Hilfe der PositionGetSymbol() Funktion finden und das Volumen und die Richtung der Position ermitteln (die PositionGetDouble() Funktion mit dem POSITION_VOLUME Identifikator und die PositionGetInteger() Funktion mit dem POSITION_TYPE Identifikator).

Anschließend sieht die fertige Funktion folgendermaßen aus:

double fSymbolLots(string aSymbol)
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++) // Go through all positions
     {
      if(PositionGetSymbol(i)==aSymbol) // we have found a position with specified symbol
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1; // the sign is dependent on the position type           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Nach Feststellung des aktuellen Volumens, gehen wir durch die History der Abschlüsse vom Ende bis zum Anfang, bis die Summe der Volumen dem Volumen gleicht.

Die Länge der ausgewählten History der Abschlüsse wird festgelegt mittels der HistoryDealsTotal() Funktion, wobei das Ticket für jeden Abschluss mit Hilfe der HistoryDealGetTicket() Funktion ermittelt wird und die Daten des Abschlusses dann mit Hilfe der HistoryDealGetInteger() Funktion (der DEAL_TYPE Identifikator für die Art des Abschlusses) und der HistoryDealGetDouble() Funktion extrahiert werden (der DEAL_VOLUME Identifikator für das Volumen des Abschlusses):

double Sum=0; 
int FromI=0;
int FromTicket=0;
for(int i=HistoryDealsTotal()-1;i>=0;i--) // go through all the deals from the end to the beginning 
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticket of the deal
   if(ticket!=0)
     {
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // We add or subtract the volume depending on deal direction
        {
         case DEAL_TYPE_BUY:
            Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
         case DEAL_TYPE_SELL:
            Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
        }
      if(CurrentVolume==Sum) // all the deals has scanned
        {
         sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); // Save the time of a "zero" position
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
         FromI=i; // Save the index
         break;
        }
     }
  }

Sobald wir hier angelangt sind, speichern wir die Zeit in die globale Variable, die dann beim Laden der Abschlüsse-History später verwendet wird (der Abschluss-Index in der History wird in der FromI Variable abgelegt).

Vor dem Abschluss mit einem FromI-Index war die Gesamt-Position auf dem Symbol = 0.

Jetzt bewegen wir uns von FromI auf das Ende der History zu und zählen das Volumen der Abschlüsse mit der festgelegten magischen Zahl:

static double sVolume=0;
static ulong sLastTicket=0;
for(int i=FromI;i<HistoryDealsTotal();i++) // from the first deal until the end
  {
   ulong ticket=HistoryDealGetTicket(i);   // Get deal ticket
   if(ticket!=0)
     {
      if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) // Specified symbol
        {
         long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
         if(PosMagic==aMagic || aMagic==-1) // Specified magic
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // add or subtract the deal volumes 
                                                       // depending on the deal type
              {
               case DEAL_TYPE_BUY:
                  sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
               case DEAL_TYPE_SELL:
                  sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
              }
           }
        }
     }
  }

Am Ende der Schleife erhalten wir dann das Volumen einer aktuellen Position nach der festgelegten magischen Zahl, wobei das Ticket eines letzten Abschlusses mit der festgelegten magischen Zahl in der sLastTicket Variable abgelegt wird, sodass nach der Ausführung des Abschlusses das Gesamtvolumen einer Position mit der festgelegten magischen Zahl = sVolume sein wird. Die Vorarbeit der Funktion ist beendet.

Die sLoadHistoryFrom, sLastTicket und sVolume Variable werden als statisch deklariert (sie speichern ihre Werte nach Abschluss der Funktion). Diese Werte werden später bei jedem Aufruf der Funktion verwendet.

Jetzt haben wir also die Zeit (Startpunkt der History der Abschlüsse) und das Ticket des Abschlusses, nach dessen Ausführung das Volumen der Gesamt-Position (mit festgelegtem Symbol) den aktuellen Wert haben wird.

Für die Zeit der Position mit Volumen Null, genügt es, die History von der aktuellen bis zur gespeicherten Zeit durch zu gehen, die Addition der Abschluss-Volumen durchzuführen und die Volumen und das Ticket des letzten Abschlusses zu speichern.

Daher besteht die Berechnung der Gesamt-Position des Expert Advisors in der Verarbeitung der letzten paar Abschlüsse:

if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Request for the deals history up to the current time
  {
   return(false);
  }
for(int i=HistoryDealsTotal()-1;i>=0;i--) // Loop from the end
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticke
   if(ticket!=0)
     {
      if(ticket==sLastTicket) // We have found the already calculated deal, save the ticket and break
        {
         sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
         break;
        }
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // Add or subtract deal volume depending on deal type      
        {
         case DEAL_TYPE_BUY:
            sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
         case DEAL_TYPE_SELL:
            sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
        }
     }
  }

Der Algorithmus der Funktion kann folgendermaßen dargestellt werden:

Die vollständige Funktion:

bool fGetPositionVolume(string aSymbol,int aMagic,double aVolume)
  {
   static bool FirstStart=false;
   static double sVolume=0;
   static ulong sLastTicket=0;
   static datetime sLoadHistoryFrom=0;
   // First execution of function when Expert Advisor has started
   if(!FirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+aSymbol+"_HistStTm"))
        {
         sLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+aSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Return if unsuccessful, 
                                                      // we will repeat on the next tick
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(aSymbol); // Total volume
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      // Search the last time when position volume was equal to zero
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      // Calculate the volume of position with specified magic number and symbol
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==aMagic || aMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      FirstStart=true;
     }

   // Recalculate the volume of a position (with specified symbol and magic)
   // for the deals, after the zero position time
   if(!HistorySelect(sLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==sLastTicket)
           {
            sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
            break;
           }
         switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
           {
            case DEAL_TYPE_BUY:
               sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
            case DEAL_TYPE_SELL:
               sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
           }
        }
     }
   aVolume=NormalizeDouble(sVolume,2);;
   return(true);
  }

Das Symbol und die magische Zahl werden an die Funktion übertragen, die ihrerseits das Volumen der Position liefert. Bei Erfolg liefert sie 'true', ansonsten 'false'.

Bei Erfolg liefert sie das angefragte Volumen an die Variable aVolume, das dann per Referenz an die Funktion übertragen wird. Statische, in der Funktion deklarierte Variablen, gestatten keine Verwendung dieser Funktion mit unterschiedlichen Parametern (Symbol und magische Zahl).

Bei MQL4 konnte man dies durch Erzeugung einer Kopie dieser Funktion mit einem anderen Namen umgehen, und diese für das andere Paar "Symbol-magisch" aufrufen, oder die Variablen FirstStart, sVolume, sLastTicket, sLoadHistoryFrom als gemeinsame Variablen deklarieren - für jedes Paar "Symbol-magisch" und sie dann in die Funktion übertragen.

Genauso kann man auch in MQL5 vorgehen, doch besitzt MQL5 inzwischen ein weitaus bequemeres Feature - die Klassen. Und in diesem Fall ist die Verwendung von Klassen wirklich sinnvoll. Bei der Arbeit mit Klassen muss man für jedes Paar "Symbol-magische Zahl" eine Klasseninstanz erzeugen, damit die Daten des Pars in jeder Klasseninstanz gespeichert werden können.

Deklarieren wir also eine PositionVolume Klasse. Alle, innerhalb der Funktion als statisch deklarierten, Variablen, werden nun als private deklariert. Wir verwenden sie nicht direkt vom Expert Advisor aus - mit Ausnahme der Volumen Variable, doch brauchen wir sie nur nach Ausführung der Volumenberechnungsfunktion. Wir deklarieren auch die Symbol und magische Zahl Variablen - sie in die Funktion zu übertragen ist unpraktisch, daher dies bitte nur einmal bei der Initialisierung der Klasseninstanz tun.

Die Klasse wird zwei public Funktionen besitzen: die Initialisierungsfunktion und die Funktion zur Berechnung des Positions-Volumen, sowie auch eine private Funktion zur Ermittlung des Gesamtvolumens der Position:

class PositionVolume
  {
private:
   string            pSymbol;
   int               pMagic;
   bool              pFirstStart;
   ulong             pLastTicket;
   double            pVolume;
   datetime         pLoadHistoryFrom;
   double            SymbolLots();
public:
   void Init(string aSymbol,int aMagic)
     {
      pSymbol=aSymbol;
      pMagic=aMagic;
      pFirstStart=false;
      pLastTicket=0;
      pVolume=0;
     }
   bool              GetVolume(double  &aVolume);
  };
bool PositionVolume::GetVolume(double  &aVolume)
  {
   if(!pFirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+pSymbol+"_HistStTm"))
        {
         pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(pSymbol);
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               pLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",pLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==pMagic || pMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      pFirstStart=true;
     }
   if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==pLastTicket)
           {
            break;
           }
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
           {
            long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
            if(PosMagic==pMagic || pMagic==-1)
              {
               switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                 {
                  case DEAL_TYPE_BUY:
                     pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                  case DEAL_TYPE_SELL:
                     pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                 }
              }
           }
        }
     }
   if(HistoryDealsTotal()>0)
     {
      pLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
     }
   pVolume=NormalizeDouble(pVolume,2);
   aVolume=pVolume;
   return(true);
  }
double PositionVolume::SymbolLots()
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++)
     {
      if(PositionGetSymbol(i)==pSymbol)
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1;
           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Bei der Verwendung dieser Klasse bei jedem "Symbol-magische Zahl" Paar, muss unbedingt eine Klasseninstanz erzeugt werden:

PositionVolume PosVol11;
PositionVolume PosVol12;
PositionVolume PosVol21;
PositionVolume PosVol22;

Sie sollte in der OnInit() Funktion eines Expert Advisors initialisiert werden, z.B.

PosVol11.Init(Symbol_1,Magic_1); 
PosVol12.Init(Symbol_1,Magic_2);
PosVol21.Init(Symbol_2,Magic_1); 
PosVol22.Init(Symbol_2,Magic_2);  

Danach erhält man das Volumen einer Position durch das festgelegte Symbol und die magische Zahl. Rufen wir also die GetVolume Funktion der entsprechenden Klasseninstanz auf.

Bei Erfolg erhalten wir 'true' und der Wert wird in die Variable geschrieben und per Referenz als Parameter dieser Funktion übertragen:

double Vol11;
double Vol12;
double Vol21;
double Vol22;
PosVol11.GetVolume(Vol11);
PosVol12.GetVolume(Vol12);
PosVol21.GetVolume(Vol21);
PosVol22.GetVolume(Vol22);

An dieser Stelle sind wir nun eigentlich fertig - bleibt nur noch der Kontrolltest.


4. Kontrolltest

Um die Funktion, die wir in einem Expert Advisor verwendet haben, und die gleichzeitig auf vier Positions arbeitet, zu testen, gibt es folgende Möglichkeiten:

  1. mit Hilfe des RSI-Indikators mit Zeitraum 14 auf EURUSD mit der magischen Zahl 1;
  2. mit Hilfe des RSI-Indikators mit Zeitraum 21 auf EURUSD mit der magischen Zahl 2;
  3. mit Hilfe des RSI-Indikators mit Zeitraum 14 auf GBPUSD mit der magischen Zahl 1;
  4. mit Hilfe des RSI-Indikators mit Zeitraum 21 auf GBPUSD mit der magischen Zahl 2;

Der Expert Advisor mit der magischen Zahl 1 handelte 0,1 Posten des Volumens; der Expert Advisor mit der magischen Zahl 2 handelte 0,2 Posten des Volumens.

Das Volumen eines Abschlusses wird den Variablen des Expert Advisors bei der Ausführung des Abschlusses hinzugefügt, und zwar vor und nachdem der Abschluss des Volumens jeder Position mit Hilfe der o.g. Funktion festgelegt wurde.

Sollte es zu einem Fehler bei der Berechnung der Volumen gekommen sein, erstellt die Funktion eine Meldung.

Der Code des Expert Advisors ist im Anhang an diesen Beitrag zu finden (Dateiname: ePosVolTest.mq5).


Fazit

Für einen Expert Advisor braucht man viele Funktionen, die alle so implementiert werden sollten, dass sie in jeder Phase auch bequem zu nutzen sind. Diese Funktionen sollten zudem so geschrieben werden, dass sie die Rechnerressourcen optimal nutzen.

Die in diesem Beitrag vorgestellte Berechnungsmethode des Position-Volumens, erfüllt all diese Bedingungen - sie lädt beim Start nur das minimal Erforderliche der Abschlüsse-History. Und sie berechnet in ihrer Arbeit das aktuelle Volumen der Position mit Hilfe der letzten Abschlüsse.