Elaborazione di eventi di trading nell'Expert Advisor utilizzando la funzione OnTrade()

KlimMalgin | 9 dicembre, 2021

Introduzione

Qualsiasi trader che scrive Expert su MQL, prima o poi si trova di fronte alla necessità di segnalare come sta lavorando il suo Expert. Oppure potrebbe aver bisogno di implementare le notifiche via SMS o e-mail sulle azioni dell’Expert. In ogni caso, dobbiamo "catturare" determinati eventi che si verificano nel mercato o azioni compiute da un esperto e avvisare gli utenti.

In questo articolo, voglio parlarti di come puoi implementare l'elaborazione di eventi di trading e offrirti la mia implementazione.

In questo articolo, considereremo l'elaborazione dei seguenti eventi:

1. Come funziona? 

Prima di iniziare, descriverò in termini generali come funzionano gli eventi di trading e tutti i dettagli necessari verranno spiegati al volo.

Ci sono eventi predefiniti e personalizzati in MQL5. Ci interessano quelli predefiniti, in particolare l'evento Trade.

L'evento Trade viene generato ogni volta che l'operazione di trading viene completata. Ogni volta, dopo la generazione dell'evento Trade, viene chiamata la funzione OnTrade(). L'elaborazione degli ordini e delle posizioni verrà effettuata esattamente all'interno della funzione OnTrade().

2. Template dell’Expert

Quindi, creiamo un nuovo Expert Advisor. In MetaEditor fare click su File -> New per lanciare MQL5 Wizard. Selezionare Expert Advisor e fare click su Avanti. Nella finestra di dialogo "Proprietà generali dell'Expert Advisor" inserisci il Nome dell'Expert Advisor e i tuoi dati, se necessario. Ho chiamato il mio Expert Advisor "TradeControl". Puoi prendere questo nome o sceglierne uno tuo, non è importante. Non specificheremo alcun parametro, poiché verranno creati al volo durante la scrittura di un expert.

Fatto! Il template dell’Expert Advisor è stato creato, dobbiamo aggiungere la funzione OnTrade() al suo interno.

Di conseguenza, dovresti ottenere il seguente codice:

//+------------------------------------------------------------------+
//|                                              TradeControl_en.mq5 |
//|                                             Copyright KlimMalgin |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "KlimMalgin"
#property link      ""
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

3. Lavorare con le posizioni

Cominciamo con l'evento di trading più semplice: apertura e chiusura di posizioni. Innanzitutto, dovresti capire quali processi si verificano dopo aver premuto i pulsanti "Vendi" e "Acquista".

Se effettuiamo una chiamata nella funzione OnTrade():

Alert("The Trade event occurred");

Allora vedremo che dopo l'apertura per funzione di mercato OnTrade() e insieme ad essa i nostri Alert sono stati eseguiti quattro volte:

Figura 1. Avvisi

Figura 1. Avvisi

Perché OnTrade() viene chiamato quattro volte e come possiamo rispondere a questi avvisi? Per capirlo, diamo un'occhiata alla documentazione:

 OnTrade

La funzione viene chiamata quando si verifica l'evento Trade. Ciò accade quando viene modificato l'elenco degli ordini effettuati, delle posizioni aperte, della cronologia degli ordini e della cronologia dei deal.

A questo punto, devo precisare una cosa:

Durante la scrittura di questo articolo e la comunicazione con gli sviluppatori, ho scoperto che i cambiamenti nella cronologia non portano alla chiamata OnTrade()! Il fatto è che la funzione OnTrade() viene chiamata solo quando l'elenco degli ordini effettuati e delle posizioni aperte viene modificato! Quando sviluppi un gestore di eventi di trading, potresti dover affrontare il fatto che gli ordini e le offerte eseguiti possono apparire nella cronologia con ritardo e non sarai in grado di elaborarli quando la funzione OnTrade() è in esecuzione.  

Ora torniamo agli eventi. Come abbiamo visto, quando apri per mercato, l'evento Trade si verifica 4 volte:

  1. Creare ordine per aprire per mercato.
  2. Modalità di esecuzione deal
  3. Passare l'ordine completo alla cronologia.
  4. Apertura Posizione

  Per tenere traccia di questo processo nel terminale, fai attenzione all'elenco degli ordini nel tab "Trade" della finestra MetaTrader: 

Figura 2. Elenco degli ordini nel tab "Trade"

Figura 2. Elenco degli ordini nel tab "Trade"

Una volta aperta una posizione (es. down), nella lista degli ordini compare un ordine che ha lo stato avviato (Fig. 2). Ciò cambia l'elenco degli ordini effettuati e viene chiamato l'evento Trade. È la prima volta che viene attivata la funzione OnTrade(). Quindi un deal viene eseguito in base all’ordine creato. In questa fase la funzione OnTrade() viene eseguita per la seconda volta. Non appena l'operazione viene eseguita, l'ordine completato e la sua operazione eseguita verranno inviati alla cronologia e la funzione OnTrade() viene richiamata per la terza volta. Nell'ultima fase viene aperta una posizione per operazione eseguita e la funzione OnTrade() viene richiamata per la quarta volta.

Per "catturare" il momento dell'apertura della posizione, ogni volta che chiami OnTrade() devi analizzare l'elenco degli ordini, la cronologia degli ordini e la cronologia delle operazioni. Questo è ciò che faremo ora!

OK, viene chiamata la funzione OnTrade() e dobbiamo sapere se il numero di ordini è cambiato nel tab "Trade". Per fare ciò, dobbiamo confrontare il numero di ordini nell'elenco al momento della precedente chiamata OnTrade() e ora. Per scoprire quanti ordini ci sono al momento nell'elenco, utilizzeremo la funzione OrdersTotal(). E per sapere quanti ordini erano elencati nella chiamata precedente, dovremo mantenere il valore di OrdersTotal() in ogni chiamata OnTrade(). Per questo creeremo una variabile speciale:

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call

Al termine della funzione OnTrade(), alla variabile OrdersPrev verrà assegnato il valore OrdersTotal().

Dovresti anche considerare la situazione quando esegui l’Expert Advisor e ci sono già ordini in sospeso nell'elenco. L’ Expert deve essere in grado di individuarli, quindi nella funzione OnInit() anche alla variabile OrdersPrev deve essere assegnato il valore OrdersTotal(). Le modifiche appena apportate all’Expert saranno simili a queste: 

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   OrdersPrev = OrdersTotal();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

OrdersPrev = OrdersTotal();
//---
  }

Ora che conosciamo il numero di ordini per le chiamate attuali e precedenti, possiamo scoprire quando l'ordine è apparso nell'elenco e quando, per qualsiasi motivo, era scomparso. Per fare ciò, utilizzeremo la seguente condizione:

if (OrdersPrev < OrdersTotal())
{
  // Order appeared
}
else if(OrdersPrev > OrdersTotal())
{
  // Order disappeared
}

Quindi, risulta che se per la chiamata precedente abbiamo meno ordini di adesso, l'ordine appare nell'elenco (più ordini non possono apparire contemporaneamente), ma se il contrario, cioè ora abbiamo meno ordini rispetto a una precedente chiamata OnTrade( ), allora l'ordine viene eseguito o annullato per qualche motivo. Quasi tutto il lavoro con le posizioni inizia con queste due condizioni.

Solo Stop Loss e Take Profit richiedono un lavoro separato con loro. Alla funzione OnTrade() aggiungerò il codice che funziona con le posizioni. Consideriamolo:

void OnTrade()
  {
//---
Alert("Trade event occurred");

HistorySelect(start_date,TimeCurrent());

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
}
else if(OrdersPrev > OrdersTotal())
{
   state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);

   // If order is not found, generate an error
   _GetLastError=GetLastError();
   if (_GetLastError != 0){Alert("Error #",_GetLastError," Order is not found!");LastOrderTicket = 0;}
   Print("Error #",_GetLastError," state: ",state);ResetLastError();


   // If order is fully executed
   if (state == ORDER_STATE_FILLED)
   {
      // Then analyze the last deal
      // --
      Alert(LastOrderTicket, "Order executed, going to deal");
      switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
      {
         
         // Entering the market
         case DEAL_ENTRY_IN:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
               break;
               
               case 1:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Exiting the market
         case DEAL_ENTRY_OUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Sell position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Sell position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
               break;
               
               case 1:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Buy position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Buy position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Reverse
         case DEAL_ENTRY_INOUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
                  Alert("Sell is reversed to Buy on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               case 1:
                  Alert("Buy is reversed to Sell on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Indicates the state record
         case DEAL_ENTRY_STATE:
            Alert("Indicates the state record. Unprocessed code of type: ", 
            HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
         break;
      }
      // --
   }
}

OrdersPrev = OrdersTotal();

//---
  }

Assicurati inoltre di aver dichiarato all'inizio del programma le seguenti variabili:

datetime start_date = 0;   // Date, from which we begin to read history

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call
int PositionsPrev = 0;     // Number of positions at the time of previous OnTrade() call
ulong LastOrderTicket = 0; // Ticket of the last processed order

int _GetLastError=0;       // Error code
long state=0;              // Order state

Torniamo ai contenuti di OnTrade().

Puoi commentare l'Avviso all'inizio, ma lo lascerò Avanti va la funzione HistorySelect(). Genera un elenco di offerte e cronologia degli ordini per il periodo di tempo specificato, definito da due parametri della funzione. Se questa funzione non viene chiamata prima di andare alla cronologia dei deal e degli ordini, non avremo alcuna informazione perché gli elenchi della cronologia saranno vuoti. Dopo aver chiamato HistorySelect() vengono valutate le condizioni, come è stato detto poco fa.

Quando arriva un nuovo ordine, prima lo selezioniamo e controlliamo gli errori:

OrderGetTicket(OrdersTotal()-1);// Select the last order for work
_GetLastError=GetLastError();
Print("Error #",_GetLastError);ResetLastError();

Dopo aver selezionato l'ordine, otteniamo il codice di errore utilizzando la funzione GetLastError(). Quindi usando la funzione Print() stampiamo il codice nel journal e usando la funzione ResetLastError() resettiamo il codice di errore a zero, quindi alla successiva chiamata GetLastError() per altre situazioni non otterremo lo stesso codice di errore.

Dopo aver verificato la presenza di errori, se l'ordine è stato selezionato con successo, verificane lo stato:

if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
   Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
   LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
}

Se l'ordine ha lo stato started, cioè è verificato per la correttezza, ma non ancora accettato, allora dovrebbe essere eseguito nel prossimo futuro e diamo semplicemente un Alert() che notifica che l'ordine è in elaborazione e salviamo il suo ticket sulla successiva chiamata di OnTrade(). Invece di Alert(), puoi usare qualsiasi altro tipo di notifica.

Nel codice sopra,

OrderGetTicket(OrdersTotal()-1)

restituirà l'ultimo ticket d'ordine dall'intero elenco degli ordini.

OrdersTotal()-1 indica che è necessario ottenere l'ultimo ordine. Poiché la funzione OrdersTotal() restituisce il numero totale di ordini (ad esempio, se c'è 1 ordine nell'elenco, OrdersTotal() restituirà 1), e il numero di indice dell'ordine viene contato da 0, allora per ottenere il numero di indice nell'ultimo ordine dobbiamo sottrarre 1 dal numero totale di ordini (se OrdersTotal() restituisce 1, il numero di indice di questo ordine sarà uguale a 0). E la funzione OrderGetTicket() a sua volta restituirà il ticket dell'ordine, il cui numero gli verrà passato.

Era la prima condizione, di solito viene attivata alla prima chiamata OnTrade(). Segue la seconda condizione che viene soddisfatta alla seconda chiamata OnTrade(), quando l'ordine viene eseguito, è passato alla cronologia e la posizione dovrebbe aprirsi.

Se l'ordine manca nell'elenco, allora è passato alla cronologia, deve essere sicuramente lì! Pertanto, facciamo appello alla cronologia degli ordini utilizzando la funzione HistoryOrderGetInteger() per ottenere lo stato dell'ordine. E per leggere i dati della cronologia per un ordine particolare, abbiamo bisogno del suo ticket. Per questo, se nella prima condizione il ticket di ordine in entrata è stato memorizzato nella variabile LastOrderTicket.

Quindi otteniamo lo stato dell'ordine, indicando il ticket dell'ordine come primo parametro per HistoryOrderGetInteger() e il tipo di proprietà necessaria - come secondo. Dopo aver provato a ottenere lo stato dell'ordine, otteniamo il codice di errore e lo scriviamo nel journal. È necessario nel caso in cui il tuo ordine, con cui dobbiamo lavorare, non sia ancora riuscito a entrare nella cronologia e ci rivolgiamo ad esso (l'esperienza dimostra che questo è possibile e non poco. Ne ho parlato all'inizio di questo articolo).

Se si verifica un errore, l'elaborazione si interrompe perché non ci sono dati con cui lavorare e nessuna delle seguenti condizioni viene soddisfatta. E se la chiamata HistoryOrderGetInteger() ha avuto successo e l'ordine ha lo stato "L'ordine è completamente eseguito":

// If order is fully executed
if (state == ORDER_STATE_FILLED)

Allora dai un'altra notifica:

// Then analyze the last deal
// --
  Alert(LastOrderTicket, "Order executed, going to deal");

E passiamo all'elaborazione del deal, che è stato chiamato da questo ordine. Innanzitutto, scopri la direzione dell'operazione (proprietà DEAL_ENTRY). La direzione non è il Buyo il Sellma l’ Entrata nel mercato, l' Uscita dal mercato, l' Inversioneo l' Indicazione del record di stato. Quindi, utilizzando la proprietà DEAL_ENTRY possiamo scoprire se l'ordine è stato impostato per aprire, chiudere o invertire. 

Per analizzare il deal ed i suoi risultati, facciamo anche appello alla cronologia utilizzando la seguente costruzione:

switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
{
  ...
}

Funziona come con gli ordini:

HistoryDealsTotal() restituisce il numero totale di deal. Per ottenere il numero dell'ultimo deal sottraiamo 1 dal valore di HistoryDealsTotal(). Il numero di deal risultante viene passato alla funzione HistoryDealGetTicket() che, a sua volta, passa il ticket del deal selezionato alla funzione HistoryDealGetInteger(). HistoryDealGetInteger() in base al ticket e al tipo di proprietà specificati restituirà la direzione del deal.

Esaminiamo in dettaglio la direzione dell’ Entrata nel mercato. Le altre direzioni verranno trattate brevemente, poiché vengono elaborate quasi allo stesso modo:

Il valore dell'espressione, ottenuto da HistoryDealGetInteger(), viene confrontato con i valori dei blocchi dei casi, finché non viene trovata una corrispondenza. Supponiamo di entrare nel mercato, cioè di aprire l'ordine Sell. Quindi, verrà eseguito il primo blocco:

// Entering the market
case DEAL_ENTRY_IN:

All'inizio del blocco ti viene notificata la creazione del deal. Allo stesso tempo, questa notifica garantisce che tutto sia andato bene e che il deal sia in fase di elaborazione.

Dopo la notifica arriva un altro blocco switch, che analizza il tipo di deal:

   switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
   {
      case 0:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
      break;
      
      case 1:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         
      break;
      
      default:
         Alert("Unprocessed code of type: ",
               HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
      break;
   }

Ottieni informazioni sul deal dalla cronologia, allo stesso modo di prima, eccetto per la proprietà specificata. Questa volta devi specificare il DEAL_TYPE per sapere se è stato effettuato un’operazione di Acquisto o Vendita. Analizzo solo le tipologie Buy e Sell, ma oltre ad esse ce ne sono altre quattro. Tuttavia, questi restanti quattro tipi di deal sono meno comuni, quindi invece di quattro blocchi di casi viene utilizzato solo un blocco predefinito per essi. Darà un Alert() con il tipo di codice.

Come probabilmente avrai notato nel codice, non vengono elaborate solo le aperture di posizioni di acquisto e vendita, ma anche il loro incremento. Per determinare quando la posizione è stata incrementata e quando è stata aperta, è necessario confrontare il volume dell'operazione eseguita e la posizione, che è diventata il risultato di questa operazione. Se il volume della posizione è uguale al volume dell'operazione eseguita - questa posizione è stata aperta e se i volumi della posizione e dell'operazione sono diversi - questa posizione è stata incrementata. Questo vale sia per le posizioni Buy (nel caso blocco '0') che per le posizioni Sell (nel caso blocco '1'). L'ultimo blocco è quello predefinito, che gestisce tutte le situazioni diverse da Buy and Sell. L'intera elaborazione consiste nella notifica del tipo di codice, restituito dalla funzione HistoryDealGetInteger().

E infine, l'ultima preoccupazione per il lavoro con le posizioni. Questa è l'elaborazione delle modifiche ai valori di Stop Loss e Take Profit. Per sapere quale dei parametri di posizione è cambiato, dobbiamo confrontare lo stato attuale e precedente dei suoi parametri. I valori attuali dei parametri di posizione possono sempre essere ottenuti utilizzando le funzioni di servizio, ma i valori precedenti devono essere salvati.

Per questo, scriveremo una funzione speciale che salverà i parametri di posizione nell'array delle strutture:

void GetPosition(_position &Array[])
  {
   int _GetLastError=0,_PositionsTotal=PositionsTotal();

   int temp_value=(int)MathMax(_PositionsTotal,1);
   ArrayResize(Array, temp_value);

   _ExpertPositionsTotal=0;
   for(int z=_PositionsTotal-1; z>=0; z--)
     {
      if(!PositionSelect(PositionGetSymbol(z)))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect() - Error #",_GetLastError);
         continue;
        }
      else
        {
            // If the position is found, then put its info to the array
            Array[z].type         = PositionGetInteger(POSITION_TYPE);
            Array[z].time         = PositionGetInteger(POSITION_TIME);
            Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
            Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
            Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
            Array[z].sl           = PositionGetDouble(POSITION_SL);
            Array[z].tp           = PositionGetDouble(POSITION_TP);
            Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            Array[z].comission    = PositionGetDouble(POSITION_COMMISSION);
            Array[z].swap         = PositionGetDouble(POSITION_SWAP);
            Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
            Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
            Array[z].comment      = PositionGetString(POSITION_COMMENT);
        _ExpertPositionsTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertPositionsTotal,1);
   ArrayResize(Array,temp_value);
  }

Per utilizzare questa funzione, dobbiamo aggiungere il seguente codice nel blocco di dichiarazione delle variabili globali:

/*
 *
 * Structure that stores information about positions
 *
 */
struct _position
{

long     type,          // Position type
         magic;         // Magic number for position
datetime time;          // Time of position opening

double   volume,        // Position volume
         priceopen,     // Position price
         sl,            // Stop Loss level for opened position
         tp,            // Take Profit level for opened position
         pricecurrent,  // Symbol current price
         comission,     // Commission
         swap,          // Accumulated swap
         profit;        // Current profit

string   symbol,        // Symbol, by which the position has been opened
         comment;       // Comment to position
};

int _ExpertPositionsTotal = 0;

_position PositionList[],     // Array that stores info about position
          PrevPositionList[];

Il prototipo della funzione GetPosition() è stato trovato molto tempo fa negli articoli di www.mql4.com, ma non sono riuscito a trovarlo ora e non riesco a specificare la fonte. Non tratterò in dettaglio il lavoro di questa funzione. Il punto è che come parametro per riferimento è passato un array del tipo _position (struttura con campi corrispondenti ai campi posizione), a cui vengono passate tutte le informazioni sulle posizioni attualmente aperte e sui valori dei loro parametri.

Per tenere traccia comodamente delle modifiche nei parametri di posizione, creiamo due array del tipo _position. Questi sono PositionList[] (lo stato attuale delle posizioni) e PrevPositionList[] (lo stato precedente delle posizioni).

Per iniziare a lavorare con le posizioni, dobbiamo aggiungere la chiamata successiva in OnInit() e alla fine di OnTrade():

GetPosition(PrevPositionList);

Inoltre, all'inizio di Ontrade() dobbiamo aggiungere la chiamata:

GetPosition(PositionList);

Ora negli array PositionList[] e PrevPositionList[] a nostra disposizione ci saranno le informazioni sulle posizioni rispettivamente sulla chiamata OnTrade() corrente e precedente.

Consideriamo ora il codice effettivo di rilevamento delle modifiche in sl e tp:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
   string _alerts = "";
   bool modify = false;
     
   for (int i=0;i<_ExpertPositionsTotal;i++)
   {
      if (PrevPositionList[i].sl != PositionList[i].sl)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Stop Loss changed from "+ PrevPositionList[i].sl +" to "+ PositionList[i].sl +"\n";
         modify = true;
      }
      if (PrevPositionList[i].tp != PositionList[i].tp)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Take Profit changed from "+ PrevPositionList[i].tp +" to "+ PositionList[i].tp +"\n";
         modify = true;
      }
      
   }
   if (modify == true)
   {
      Alert(_alerts);
      modify = false;
   }
}

Come si vede, il codice non è troppo grande, ma questo è solo a causa del notevole lavoro preparatorio. Approfondiamolo.

Tutto inizia con la condizione:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))

Qui vediamo che né gli ordini né le posizioni sono stati inseriti o cancellati. Se la condizione viene soddisfatta, molto probabilmente i parametri di alcune posizioni o ordini sono cambiati.

All'inizio della funzione vengono dichiarate due variabili:

  • _alerts - memorizza tutte le notifiche sui cambiamenti.
  • modifiy - consente di visualizzare i messaggi sulle modifiche solo se ci sono state realmente.

Successivamente nel ciclo controlliamo la corrispondenza dei valori di Take Profits e Stop Losses sulla chiamata OnTrade() precedente e corrente per ogni posizione. Le informazioni su tutte le discrepanze verranno memorizzate nella variabile _alerts e successivamente verranno visualizzate dalla funzione Alert(). A proposito, l'elaborazione della modifica degli ordini in sospeso verrà eseguita allo stesso modo.

Per ora, finiamo con le posizioni e procediamo all’inserimento degli ordini in sospeso

4. Lavorare con OpenCL

Iniziamo con il posizionamento dell'evento ordini in sospeso.

Quando compare un nuovo ordine in sospeso, l'evento Trade viene generato solo una volta, ma è sufficiente per elaborarlo! Inserisci il codice che funziona con gli ordini in sospeso nel corpo dell'operatore:

if (OrdersPrev < OrdersTotal())

e ottieni quanto segue:

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {
      switch(OrderGetInteger(ORDER_TYPE))
      {
         case 2:
            Alert("Pending order Buy Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 3:
            Alert("Pending order Sell Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 4:
            Alert("Pending order Buy Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 5:
            Alert("Pending order Sell Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 6:
            Alert("Pending order Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
                 
         case 7:
            Alert("Pending order Sell Stop Limit  #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;         
      }
   }
}

Qui il codice, che funziona con gli ordini in sospeso, inizia con quanto segue:

   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {

Per prima cosa viene verificato lo stato dell'ordine. L'ordine deve avere lo stato ORDER_STATE_PLACED, ovvero deve essere accettato. E se questa condizione viene soddisfatta, arriva l'operatore switch che stampa un messaggio a seconda del tipo di ordine.

Successivamente lavoreremo con gli eventi che si verificano quando gli ordini vengono modificati. La modifica degli ordini è simile alla modifica delle posizioni. Allo stesso modo, viene creata la struttura che memorizza le proprietà degli ordini: 

/*
 *
 * Structure that stores information about orders
 *
 */
struct _orders
{

datetime time_setup,       // Time of order placement
         time_expiration,  // Time of order expiration
         time_done;        // Time of order execution or cancellation
         
long     type,             // Order type
         state,            // Order state
         type_filling,     // Type of execution by remainder
         type_time,        // Order lifetime
         ticket;           // Order ticket
         
long     magic,            // Id of Expert Advisor, that placed an order
                           // (intended to ensure that each Expert 
                           // must place it's own unique number)
                           
         position_id;      // Position id, that is placed on order,
                           // when it is executed. Each executed order invokes a
                           // deal, that opens new or changes existing 
                           // position. Id of that position is placed on 
                           // executed order in this moment.
                           
double volume_initial,     // Initial volume on order placement
       volume_current,     // Unfilled volume
       price_open,         // Price, specified in the order
       sl,                 // Stop Loss level
       tp,                 // Take Profit level
       price_current,      // Current price by order symbol
       price_stoplimit;    // Price of placing Limit order when StopLimit order is triggered
       
string symbol,             // Symbol, by which the order has been placed
       comment;            // Comment
                           
};

int _ExpertOrdersTotal = 0;

_orders OrderList[],       // Arrays that store info about orders
        PrevOrderList[];

Ogni campo della struttura corrisponde ad una delle proprietà dell'ordine. Dopo aver dichiarato la struttura, vengono dichiarate la variabile di tipo int e due array di tipo _orders. La variabile _ExpertOrdersTotal memorizzerà il numero totale di ordini e gli array OrderList[] e PrevOrderList[] memorizzeranno le informazioni sugli ordini rispettivamente nella chiamata OnTrade() corrente e precedente.

La funzione stessa apparirà come segue:

void GetOrders(_orders &OrdersList[])
  {
   
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

   int temp_value=(int)MathMax(_OrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z--)
     {
      if(!OrderGetTicket(z))
        {
         _GetLastError=GetLastError();
         Print("GetOrders() - Error #",_GetLastError);
         continue;
        }
      else
        {
        OrdersList[z].ticket          = OrderGetTicket(z);
        OrdersList[z].time_setup      = OrderGetInteger(ORDER_TIME_SETUP);
        OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION);
        OrdersList[z].time_done       = OrderGetInteger(ORDER_TIME_DONE);
        OrdersList[z].type            = OrderGetInteger(ORDER_TYPE);
        
        OrdersList[z].state           = OrderGetInteger(ORDER_STATE);
        OrdersList[z].type_filling    = OrderGetInteger(ORDER_TYPE_FILLING);
        OrdersList[z].type_time       = OrderGetInteger(ORDER_TYPE_TIME);
        OrdersList[z].magic           = OrderGetInteger(ORDER_MAGIC);
        OrdersList[z].position_id     = OrderGetInteger(ORDER_POSITION_ID);
        
        OrdersList[z].volume_initial  = OrderGetDouble(ORDER_VOLUME_INITIAL);
        OrdersList[z].volume_current  = OrderGetDouble(ORDER_VOLUME_CURRENT);
        OrdersList[z].price_open      = OrderGetDouble(ORDER_PRICE_OPEN);
        OrdersList[z].sl              = OrderGetDouble(ORDER_SL);
        OrdersList[z].tp              = OrderGetDouble(ORDER_TP);
        OrdersList[z].price_current   = OrderGetDouble(ORDER_PRICE_CURRENT);
        OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT);
        
        OrdersList[z].symbol          = OrderGetString(ORDER_SYMBOL);
        OrdersList[z].comment         = OrderGetString(ORDER_COMMENT);
        
        _ExpertOrdersTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertOrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

  }

Analogamente alla funzione GetPosition(), legge le informazioni sulle proprietà di ogni ordine effettuato e le inserisce nell'array, passandole come parametro di input. Il codice della funzione deve essere posizionato alla fine del tuo expert e delle sue chiamate, come segue:

GetOrders(PrevOrderList);

Inserito in OnInit() e alla fine di OnTrade().

GetOrders(OrderList);

Posizionato all'inizio di OnTrade().

Consideriamo ora il codice, che elaborerà la modifica degli ordini. È un ciclo e integra il codice di modifica delle posizioni:

   for (int i = 0;i<_ExpertOrdersTotal;i++)
   {
      if (PrevOrderList[i].sl != OrderList[i].sl)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Stop Loss from "+ PrevOrderList[i].sl +" to "+ OrderList[i].sl +"\n";
         modify = true;
      }
      if (PrevOrderList[i].tp != OrderList[i].tp)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Take Profit from "+ PrevOrderList[i].tp +" to "+ OrderList[i].tp +"\n";
         modify = true;
      }
   }

Il ciclo elabora tutti gli ordini e confronta i valori di Stop Losses e Take Profit sulle chiamate OnTrade() correnti e precedenti. Se ci sono differenze, vengono salvate nella variabile _alerts e quando il ciclo è completo verranno visualizzate dalla funzione Alert().

Questo codice viene inserito nel corpo dell'operatore:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{

Immediatamente dopo il ciclo, funziona con le posizioni.

Per ora, il lavoro con gli eventi di trading non finisce. Questo articolo tratta solo i principi fondamentali del lavoro con l'evento Trade. In generale, le possibilità offerte da questo metodo sono piuttosto ampie e vanno oltre lo scopo di questo articolo.

Conclusione

La capacità di lavorare con gli eventi di trading (come parte del linguaggio MQL5) è potenzialmente uno strumento potente, che consente non solo di implementare in tempi relativamente brevi algoritmi di verifica degli ordini e di generare report di trading, ma anche di ridurre il costo delle risorse di sistema e il volume di codice sorgente, che sarà di indubbio vantaggio per gli sviluppatori.