English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato

Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato

MetaTrader 5Sistemi di trading | 16 dicembre 2021, 10:52
252 0
Dmitry Fedoseev
Dmitry Fedoseev

Introduzione

Il terminale client MetaTrader 5 consente il lavoro parallelo di diversi Expert Advisor con un solo simbolo. È semplice: basta aprire diversi grafici e allegarne gli Expert Advisor. Sarebbe bello se ogni Expert Advisor lavorasse indipendentemente dagli altri Expert Advisor che lavorano con lo stesso simbolo (non esiste un problema del genere per gli Expert Advisor che lavorano con simboli diversi).

Prima di tutto, consentirà a un Expert Advisor di negoziare in piena conformità con le sue prestazioni di test e l'ottimizzazione nello Strategy Tester. Le condizioni per l'apertura della posizione possono dipendere dalle dimensioni o dall'assenza di posizione già aperta. Se diversi Expert Advisor lavorano con lo stesso simbolo, si influenzeranno a vicenda.

La seconda e probabilmente più importante cosa è consentire agli Expert Advisor di utilizzare diversi sistemi di gestione del denaro, a seconda delle strategie di trading implementate negli Expert Advisor. E infine - la possibilità di monitorare i risultati di ogni Expert Advisor e disattivarli se necessario.

1. Il Principio Generale del Calcolo del Volume di Posizione

Quando si apre un ordine, è possibile contrassegnarlo con un numero magico specificando il valore della variabile magica nella struttura MqlTradeRequest, passata alla funzione OrderSend(). Quando l'ordine viene eseguito, l'affare è anche contrassegnato con il numero magico dell'ordine. Inoltre, analizzando le offerte nella cronologia, possiamo vedere le offerte aperte da diversi Expert Advisor.

Il metodo di calcolo della posizione totale è abbastanza semplice: ad esempio, se si esegue un affare di acquisto con volume 0,1, quindi un altro acquista 0,1 e vende 0,1, il volume della posizione totale sarà pari a 0,1 + 0,1-0,1 = + 0,1. Aggiungiamo i volumi degli affari di acquisto e sottraiamo i volumi degli affari di vendita e otteniamo il volume della posizione totale.

È importante iniziare i calcoli quando il volume della posizione totale è uguale a 0. Il primo e il più ovvio punto del genere è il momento dell'apertura dell’account. In altre parole, è possibile richiedere tutta la cronologia dei deal dell'account utilizzando la funzione HistorySelect() con il primo parametro pari a 0 (il minor tempo possibile) e il valore del secondo parametro TimeCurrent() (il tempo noto recente di un server):

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

Quindi, passa attraverso l'intera storia dall'inizio alla fine, aggiungendo volumi di offerte di acquisto e sottraendo i volumi di offerte di vendita per ogni operazione con il numero magico specificato. È anche una soluzione, ma in pratica, la storia delle offerte può essere piuttosto grande. Ciò può influire in modo significativo sulla velocità di un Expert Advisor, soprattutto durante il test e l'ottimizzazione, fino all'impossibilità dell'uso pratico di tale Expert Advisor. Dobbiamo trovare l'ultimo momento nella storia delle operazioni, quando il volume della posizione netta totale era pari a zero.

Per farlo, dobbiamo prima passare attraverso l'intera storia e trovare l'ultimo punto, quando il volume della posizione netta totale era zero. Trovando questo punto, lo salviamo in una certa variabile (tempo di posizione fisso). Successivamente, l'Expert Advisor esaminerà la cronologia degli affari dal momento del punto salvato. La soluzione migliore è salvare questo punto in una variabile globale del terminale client invece che variabile di un Expert Advisor, perché in tal caso verrà distrutto quando si stacca l'Expert Advisor.

In tal caso, anche quando viene lanciato l'Expert Advisor, è necessario caricare la cronologia minima necessaria, anziché l'intera cronologia degli affari. Ci sono molti Expert Advisor che possono fare trading sullo stesso simbolo, quindi condivideremo questa variabile globale (con il tempo memorizzato del recente punto del volume zero) con tutti gli Expert Advisor.

Facciamo una digressione dall'argomento principale e consideriamo l'utilizzo delle variabili globali del client terminale che permettono a più Expert Advisor di lavorare con lo stesso simbolo (magari con parametri diversi), ed evita la coincidenza dei nomi, creati da diverse istanze di Expert Advisor.

2. Utilizzare le Variabili Globali del Terminale Client

Il linguaggio MQL5 ha la funzione MQLInfoString(), che permette di ottenere diverse informazioni su un programma mql5.

Per ottenere informazioni sul nome del file, chiamare questa funzione con l'identificatore MQL_PROGRAM_NAME:

MQL5InfoString(MQL_PROGRAM_NAME); // Expert Advisor name

Quindi, iniziamo i nomi delle variabili globali con il nome di un Expert Advisor. Un Expert Advisor può lavorare con diversi simboli; significa che dobbiamo aggiungere il nome di un simbolo (Simbolo). Gli Expert Advisor possono lavorare con lo stesso simbolo, ma con tempistiche diverse (con impostazioni diverse), per questi casi è necessario utilizzare il numero magico. Quindi aggiungiamo anche il numero magico.

Ad esempio, se l'Expert Advisor ha un numero magico, memorizzato in Magic_N variabile, lo aggiungiamo al nome della variabile globale.

I nomi di tutte le variabili globali saranno i seguenti:

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

dove gvp (Global Variable Prefix) è una variabile stringa, dichiarata nella sezione delle variabili comuni.

Vorrei chiarire la terminologia per evitare la confusione delle variabili globali, in quanto sono utilizzate nella programmazione (le variabili globali sono visibili all'interno di tutte le funzioni, le variabili locali delle funzioni sono visibili solo all'interno della funzione).

Ma qui abbiamo un caso diverso: il termine "variabili globali" indica le variabili globali del terminale client (variabili speciali, memorizzate in un file, sono disponibili da GlobalVariable ... () funzioni). Quando parliamo di variabili globali (come vengono utilizzate nella programmazione), useremo il termine "variabili comuni". Il termine di variabili locali significherà variabili locali.

Le variabili globali sono utili, perché salvano i loro valori dopo la reinizializzazione di un Expert Advisor (riavvio di Expert Advisor, terminale client, computer), ma in modalità test è necessario cancellare tutte le variabili (o un passaggio precedente durante l'ottimizzazione). Le variabili globali, utilizzate nelle operazioni reali dovrebbero essere separate dalle variabili globali, create durante il test, è necessario eliminarle dopo il test. Ma non dovresti modificare o eliminare le variabili globali, create da un Expert Advisor.

Utilizzando la funzione AccountInfoInteger() e chiamandola con l'identificatore ACCOUNT_TRADE_MODE, è possibile distinguere la modalità corrente: tester, demo o account reale.

Aggiungiamo un prefisso alle variabili globali: "d" - quando si lavora su conti demo, "r" - quando si lavora su conti reali, "t" - quando si lavora in Strategy 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
  } 

La funzione deve essere chiamata dalla funzione OnInit() dell'Expert Advisor.

Come accennato in precedenza, le variabili globali dovrebbero essere eliminate durante il test, in altre parole, dobbiamo aggiungere la funzione che elimina le variabili globali nella funzione OnDeinit() di un Expert Advisor:

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
           }
        }
     }
  }

Al momento è impossibile interrompere i test in MetaTrader 5, in altre parole, l'esecuzione della funzione OnDeinit() non è garantita, comunque è possibile che appaia in futuro. Non sappiamo se la funzione OnDeinit() verrà eseguita dopo l'interruzione dello Strategy Tester, pertanto eliminiamo le variabili globali all'inizio dell'esecuzione di Expert Advisor - all'interno della funzione OnInit().

Otterremo il seguente codice delle funzioni OnInit() e OnDeinit():

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
  }

Inoltre possiamo semplificare l'uso delle variabili globali, creando le funzioni con nomi brevi per la creazione di variabili globali (invece di GlobalVariableSet(gvp + ...),.

La funzione per impostare il valore della variabile globale:

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

La funzione per ottenere il valore della variabile globale:

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

La funzione per eliminare la variabile globale:

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

Abbiamo discusso delle variabili globali, ma non è tutto.

Dobbiamo fornire la possibilità di creare variabili globali per un simbolo e fornire il loro diverso funzionamento sull'account e nello Strategy Tester. I nomi di queste variabili globali non dovrebbero dipendere dal nome e dal numero magico di un Expert Advisor.

Dichiariamo un'altra variabile per un prefisso di variabile globale, denominata "Commom_gvp". Quindi, lavorando con un account, avrà il valore "COMMON" e avrà lo stesso valore, come una variabile gvp quando si lavora con lo Strategy Tester (per eliminare la variabile dopo o prima del processo di backtesting della strategia).

Infine, la funzione per preparare i prefissi delle variabili globali ha la seguente forma:

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
     }
  }

Qualcuno potrebbe pensare che i prefissi delle variabili globali includano informazioni extra - la separazione dei conti demo e reali e il prefisso "t" durante il test, anche se potrebbe essere fatto semplicemente aggiungendo il carattere "t", che indica che il nostro Expert Advisor sta lavorando nello Strategy Tester. Ma l'ho fatto in questo modo. Non conosciamo il futuro e le cose che potrebbero essere necessarie per analizzare il lavoro degli Expert Advisor.

Lo store non è una piaga dicono.

Le funzioni presentate sopra significano che il terminale client funziona con un account, non c'è alcun cambiamento di account durante il suo lavoro. È vietato modificare un account durante il lavoro di un Expert Advisor. Naturalmente, se necessario, questo problema può essere risolto aggiungendo un numero di account ai nomi delle variabili globali.

Un'altra nota molto importante! La lunghezza del nome della variabile globale è limitata a 63 simboli. Per questo motivo, non dare nomi lunghi ai tuoi Expert Advisor.

Abbiamo finito con le variabili globali, ora è il momento di considerare l'argomento principale dell'articolo: il calcolo del volume di posizione con un numero magico specificato.

3. Calcolo del Volume di una Posizione

Innanzitutto, controlliamo se esiste una variabile globale con le informazioni sull'ultima volta della posizione del volume zero usando la funzione GlobalVariableCheck() (per semplicità, se non c'è alcuna posizione aperta, la chiamiamo caso di "posizione zero").

Se c'è una tale variabile, carichiamo la cronologia delle offerte a partire dal tempo, memorizzata nella variabile, altrimenti caricheremo l'intera cronologia:

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);
  } 

Successivamente, definiamo il volume della posizione netta totale per un simbolo:

double CurrentVolume=fSymbolLots(pSymbol);

Il volume di una posizione viene determinato utilizzando la funzione fSymbolLots().

Esistono diversi modi per ottenere il volume di una posizione: ad esempio, può essere fatto usando la funzione PositionSelect(). Se la funzione restituisce false, significa che non c'è alcuna posizione (il suo volume è uguale a zero). Se la funzione ritorna true, il volume può essere ottenuto utilizzando la funzione PositionGetDouble() con identificatore POSITION_VOLUME. Il tipo di posizione (buy o sell), viene determinato utilizzando la funzione PositionGetInteger() con l’identificatore POSITION_TYPE. La funzione ritorna un valore positivo per le posizioni lunghe e negativo per le posizioni corte.

La funzione completa ha il seguente aspetto:

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);
     }
  }

In alternativa, è possibile determinare il volume della posizione totale del simbolo attraverso tutte le posizioni, il numero di posizioni è determinato dalla funzione PositionsTotal(). Quindi, trova il simbolo necessario utilizzando la funzione PositionGetSymbol() e determina il volume e la direzione della posizione (PositionGetDouble() con l’identificatore POSITION_VOLUME e la funzione PositionGetInteger() con l’identificatore POSITION_TYPE.

In questo caso, la funzione ready avrà la forma: 

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);
  }

Dopo aver seguito la determinazione del volume corrente, esamineremo la storia delle offerte dalla fine all'inizio, fino a quando la somma dei volumi diventa uguale al volume.

La lunghezza della cronologia selezionata ща offerte viene determinata utilizzando la funzione HistoryDealsTotal(), il ticket viene determinato per ogni deal utilizzando la funzione HistoryDealGetTicket(), i dati del deal vengono estratti utilizzando la funzione HistoryDealGetInteger() (l'identificatore DEAL_TYPE per il tipo di deal) e HistoryDealGetDouble() (l'identificatore DEAL_VOLUME per il volume del deal):

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;
        }
     }
  }

Quando abbiamo trovato questo punto, memorizziamo il tempo nella variabile globale, che verrà utilizzata ulteriormente durante il caricamento della cronologia delle operazioni (l'indice delle operazioni nella cronologia è memorizzato nella variabile FromI).

Prima dell'accordo con un indice FromI la posizione totale sul simbolo era uguale a zero.

Ora andiamo dal FromI verso la fine della cronologia e contiamo il volume delle offerte con il numero magico specificato:

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;
              }
           }
        }
     }
  } 

Dopo la fine di un ciclo avremo il volume di una posizione corrente dal numero magico specificato, il ticket di un ultimo affare con il numero magico specificato verrà memorizzato nella variabile sLastTicket, dopo l'esecuzione dell'affare il volume totale di una posizione con il numero magico specificato sarà uguale a sVolume. Il lavoro preliminare della funzione è finito.

Le variabili sLoadHistoryFrom, sLastTicket e sVolume sono dichiarate statiche (memorizzano i loro valori dopo il completamento della funzione), questi valori verranno utilizzati ulteriormente per ogni chiamata della funzione.

Abbiamo il tempo (il punto di partenza della cronologia delle offerte), il ticket dell'affare, dopo la sua esecuzione il volume della posizione totale (con il simbolo specificato) avrà il valore corrente.

A causa del tempo della posizione del volume zero, è sufficiente passare attraverso la cronologia dal tempo corrente al tempo risparmiato ed eseguire la somma dei volumi dell'affare e salvare il volume e il biglietto dell'ultima operazione.

Pertanto, il calcolo della posizione totale dell'Expert Advisor è l'elaborazione di alcune ultime operazioni:

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;
        }
     }
  }

L'algoritmo della funzione può essere presentato come segue:


La funzione completa:

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);
  }

Il simbolo e il numero magico vengono passati alla funzione che ritorna il volume della posizione. Ritorna true in caso di esito positivo e false in caso contrario.

In caso di esito positivo, ritorna il volume richiesto alla variabile aVolume passato alla funzione per riferimento. Le variabili statiche, dichiarate nella funzione, non consentono di utilizzare questa funzione con parametri diversi (simbolo e numero magico).

Nel caso di MQL4, questo problema potrebbe essere risolto con la creazione di una copia di questa funzione con un nome diverso e chiamarla per l'altra coppia "simbolo-magia" o dichiarare le variabili FirstStart, sVolume, sLastTicket, sLoadHistoryFrom come variabili comuni - per ogni coppia "simbolo-magia" e passarle nella funzione.

Può anche essere implementato in MQL5 allo stesso modo ma MQL5 ha una caratteristica molto più conveniente: le classi, è il caso in cui l'uso delle classi è ragionevole. Quando si utilizzano le classi, è necessario creare un'istanza di classe per ogni coppia di simboli magici, i dati verranno memorizzati in ogni istanza di classe.

Dichiariamo una classe PositionVolume. Tutte le variabili dichiarate statiche all'interno della funzione saranno dichiarate private, non le utilizzeremo direttamente dall'Expert Advisor, ad eccezione della variabile Volume. Ma ne avremo bisogno solo dopo l'esecuzione della funzione di calcolo del volume. Inoltre dichiariamo le variabili Symbol e Magic - non è pratico passarle nella funzione, basta farlo una volta quando si inizializza l'istanza di classe.

La classe avrà due funzioni pubbliche: la funzione di inizializzazione e la funzione per il calcolo del volume di posizione, e una funzione privata per determinare il volume totale della posizione:

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);
  }

Quando si utilizza questa classe per ogni coppia di simboli-numeri magici, è necessario creare un'istanza di classe:

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

Dovrebbe essere inizializzato nella funzione OnInit() di un Expert Advisor, ad esempio:

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

Dopodiché è possibile ottenere il volume di una posizione dal simbolo e dal numero magico specificati. Chiamiamo la funzione GetVolume dell'istanza della classe corrispondente.

In caso di esito positivo, ritorna true e inserisce il valore alla variabile, passata per riferimento come parametro della funzione:

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

Si può dire che hai finito, ma il test di controllo è rimasto.

4. Test di Controllo

Per testare il lavoro della funzione abbiamo utilizzato un Expert Advisor, che lavora contemporaneamente con quattro posizioni:

  1. utilizzando l'indicatore RSI con il periodo 14 su EURUSD con il numero magico 1;
  2. utilizzando l'indicatore RSI con il periodo 21 su EURUSD con il numero magico 2;
  3. utilizzando l'indicatore RSI con il periodo 14 su GBPUSD con il numero magico 1;
  4. utilizzando l'indicatore RSI con il periodo 21 su GBPUSD con il numero magico 2;

L'Expert Advisor con il numero magico 1 ha scambiato 0,1 lotti del volume, l'Expert Advisor con il numero magico 2 ha scambiato il volume pari a 0,2 lotti.

Il volume di un'operazione viene aggiunto alle variabili dell'Expert Advisor durante l'esecuzione di un'operazione, prima e dopo l'operazione il volume di ciascuna posizione è stato determinato utilizzando la funzione, presentata sopra. 

La funzione genera un messaggio se si è verificato un errore nel calcolo dei volumi.

Il codice dell'Expert Advisor si trova nell'allegato all'articolo (nome file: ePosVolTest.mq5).

Conclusione

Molte funzioni sono necessarie per un Expert Advisor e dovrebbero essere implementate in un modo pratico da usare in tutte le fasi. Queste funzioni dovrebbero essere scritte in termini di miglior uso delle risorse di calcolo.

Il metodo di calcolo del volume di posizione proposto in questo articolo soddisfa queste condizioni: carica solo il minimo richiesto della storia delle offerte quando viene lanciato. Quando lavora, ricalcola il volume corrente della posizione utilizzando gli ultimi affari.

Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/125

File allegati |
eposvoltest.mq5 (17.98 KB)
Utilizzo della Funzione TesterWithdrawal() per il Modeling dei Prelievi di Profitto Utilizzo della Funzione TesterWithdrawal() per il Modeling dei Prelievi di Profitto
Questo articolo descrive l'uso della funzione TesterWithDrawal() per stimare i rischi nei sistemi commerciali che implicano il ritiro di una certa parte delle attività durante il loro funzionamento. Inoltre, descrive l'effetto di questa funzione sull'algoritmo di calcolo del drawdown del capitale nello Strategy Tester. Questa funzione è utile quando si ottimizzano i parametri dei propri Expert Advisor.
Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5 Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5
Questo articolo si concentra sull'approccio orientato agli oggetti per fare ciò che abbiamo fatto nell'articolo "Guida Passo per Passo per Scrivere un Expert Advisor in MQL5 per Principianti" - creazione di un semplice Expert Advisor. La maggior parte delle persone pensa che sia difficile, ma voglio assicurarti che quando avrai finito di leggere questo articolo, sarai in grado di scrivere il tuo Expert Advisor che è basato sugli oggetti.
L'uso delle Librerie MQL5 Standard Trade Class nella scrittura di un Expert Advisor L'uso delle Librerie MQL5 Standard Trade Class nella scrittura di un Expert Advisor
Questo articolo spiega come utilizzare le principali funzionalità di MQL5 Standard Trade Class nella scrittura di Expert Advisor che implementano la chiusura e la modifica della posizione, l'immissione e l'eliminazione di ordini in sospeso e la verifica del margine prima di piazzare un trade. Abbiamo anche dimostrato come le classi di trading possono essere utilizzate per ottenere dettagli su ordini e transazioni.
Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe
L'articolo descrive come implementare la comunicazione tra processi tra i terminali client MetaTrader 5 utilizzando le named pipe. Per l'utilizzo delle named pipe, viene sviluppata la classe CNamedPipes. Per il test del suo utilizzo e per misurare il throughput della connessione, vengono presentati l'indicatore di tick, gli script server e client. L'uso di named pipe è sufficiente per le quotazioni in tempo reale.