Funzioni mql4 scritte bene: Analizzare i trade recenti (aperti o chiusi)

Credo che ci sia, nei corsi e nei corsetti venduti da sedicenti guru multimiliardari, una forte mancanza di senno nello scrivere funzioni mql4 ben fatte e riutilizzabili.

O, come diciamo in Automazione Trading, funzioni “Agnostiche”.

Le funzioni mql4 agnostiche permettono di essere utilizzate largamente in modo non vincolante da parametri esterni o, peggio, da parametri “hardcoded” nel loro corpo.

In questo caso specifico, affrontiamo un problema che potresti incontrare nel tuo viaggio di scoperta della programmazione per i mercati finanziari.

Spesso mi capita di dover prendere delle decisioni sul comportamento di un EA in base alle performance recenti.

Per esempio, se devo chiudere tutte le operazioni al raggiungimento di un determinato profitto giornaliero, devo poter risalire facilmente ai miei dati, per richiamarli all’interno della logica dell’EA e farlo agire di conseguenza.

Immagina di dover scrivere un EA che, settimanalmente, non deve perdere più del 3% del conto. Come faresti?

Il problema è che devi recuperare lo storico dei trade, sommare il PnL (Profit ‘n’ Loss) chiuso con quello dei trade ancora aperti, ovvero il floating PnL.

Oggi voglio spiegarti come io gestisco questa cosa nei miei EA, utilizzando una funzione molto figa e versatile che puoi utilizzare come base per ulteriori sviluppi personali.

Ah, prima di iniziare, se smanetti con questa funzione, puoi contattarmi nella community Telegram, sarà un piacere vedere il tuo lavoro ed aiutarti nel momento in cui dovessi trovarti in affanno.

Scrivendo questa funzione (e ti invito a scriverla a manina e non copiare ed incollare, seguendo il tutorial) imparerai anche cosa significa passare un argomento per reference, a generare più di un output in una stessa funzione e a dare dei parametri di default agli argomenti della tua funzione.

Scheletro della funzione Mql4

Innanzitutto, ecco cosa deve fare la nostra funzione:

  1. Ricevere come input una data di inizio dell’analisi, in formato datetime
  2. Ciclarsi i trade chiusi e ignorare quelli che non rispettano i requisiti
  3. Eventualmente ciclarsi i trade aperti (poi vedremo perché)
  4. Mandare in output il conteggio dei trade, il profitto totale e i lotti utilizzati

Tutto questo lo faremo con due semplici for-loop, un paio di flag e avremo la nostra funzione importabile in qualsiasi EA.

Bando alle ciance, apri l’editor mql e procediamo!

Origins Hacking GIF - Find & Share on GIPHY

Dichiariamo la nostra funzione in modo che ci ritorni un intero, il conteggio dei nostri trade aperti:

int TradeCountFromDate()
{
   int _count=0;
   return _count;
}

1 – Ricevere come input una data di inizio dell’analisi, in formato datetime

Iniziamo facile: inseriamo un argomento di tipo datetime che ci dica da che giorno e ora vogliamo iniziare a contare.

int TradeCountFromDate(datetime _date)
  {
   datetime fromDate=_date;
   datetime toDate=TimeCurrent();
   
   int _count=0;
   return _count;
}

Utilizzeremo fromDate e toDate per identificare la finestra temporale in cui andiamo ad analizzare i trade.

Io ho inserito TimeCurrent(), ma ciò non toglie che tu possa utilizzare un parametro esterno per identificare, ad esempio, tutti i trade eseguiti o aperti a marzo 2019, ad esempio.

Ora andiamo a compiere il ciclo di analisi dei trade chiusi.

2 – Cicliamo i trade chiusi e ignoriamo quelli che non rispettano i requisiti

Ora che abbiamo stabilito le due date di analisi, andiamo a ciclare gli ordini chiusi. Ah, naturalmente nello storico di Meta Trader abbiamo anche tutti i pendenti non eseguiti e le operazioni di depoisto e prelievo. Ecco, queste dobbiamo ignorarle.

Il tipo di ordine ci dirà se l’operazione considerata sarà un buy, un sell o altro. Per escludere tutto ciò che non è un ordine eseguito a mercato, ci basterà filtrare per OrderType() le nostre operazioni nel ciclo.

int TradeCountFromDate(datetime _date)
  {
   datetime fromDate=_date;
   datetime toDate=TimeCurrent();
   int _count=0;

   //processiamo gli ordini chiusi
   int _closedOrders=OrdersHistoryTotal();

   for(int i=_closedOrders-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY))
        {
         Print("Errore nel recuperare l'ordine: "+IntegerToString(GetLastError()));
        }
      else
        {
         // scartiamo i pendenti e le operazioni sul balance.
         if(OrderType()>1)
            continue;
         //scartiamo subito i trade non dentro il nostro intervallo temporale
         if(OrderCloseTime()<fromDate || OrderCloseTime()>toDate)
            continue;
          else
           {
            _count++;
           }
        }
     } 
   return _count;
}

Eccoci, il grosso della logica è fatto! In pratica, prendiamo l’intero numero di trade recuperati nello storico della MT4, ovvero questi:

Catfoot Funzioni mql4 scritte bene: Analizzare i trade recenti (aperti o chiusi)
Nota bene, ti consiglio sempre di cliccare con il destro e mostrare tutta la storia dei trade, quando stai operando con gli EA.

Partiamo dall’ultimo trade chiuso (infatti il loop conta dal massimo dei trade chiusi fino a zero) e andiamo a scendere.

Noterai che, se i trade chiusi sono 10, noi dobbiamo cominciare a contare da 9 (ovvero 10 – 1), perché la selezione per posizione (SELECT_BY_POS) ci dice che il resto del ciclo sarà applicato al trade selezionato in un array e come dovresti sapere, gli array si contano a partire dall’indice ZERO, e se hanno lunghezza 10, significa che gli elementi sono indicizzati da 0 a 9.

Una volta che abbiamo verificato che il trade sia effettivamente selezionato, (righe 12 – 15), procediamo a verificare che sia un ordine buy oppure un sell (e non un ordine stop-limit o una transazione sul conto) con l’if a riga 19.

Identico check lo facciamo sull’OrderOpenTime(), che restituisce un dato di tipo datetime e che possiamo quindi confrontare direttamente con il nostro fromDate e toDate.

Skippare questi casi è semplice perché basta segnalare all’esecutore che, in caso una di queste due if sia vera, dovrà saltare alla prossima iterazione del ciclo, senza procedere oltre nel codice del loop, grazie al comando continue.

Infine, se tutti i casi di esclusione non sussistono, allora possiamo aumentare il nostro contatore con _count++.

Al termine del loop, ritorniamo il conteggio e lo possiamo salvare in una variabile.

3 – Cicliamo i trade aperti

Ora, se dobbiamo contare anche i trade aperti, dobbiamo inserire un secondo ciclo per fare le stesse operazioni che abbiamo fatto sui trade chiusi, quindi escludere tutti i pendenti e vedere se l’ordine è stato aperto nella nostra finestra temporale.

Se volessimo usare questa funzione sia per logiche di trading sia per statistica, ci conviene impostare il secondo ciclo come facoltativo, immettendo un argomento della funzione di tipo booleano che ci dica se vogliamo considerare o meno i trade aperti.

Questa volta, il ciclo di analisi lo incapsuliamo in un if, e ricordati che dovrai usare OrderOpenTime() e non OrderCloseTime() visto che gli ordini aperti.. beh, non sono ancora chiusi!

int TradeCountFromDate(datetime _date, bool _openTrades=True)
  {
   datetime fromDate=_date;
   datetime toDate=TimeCurrent();
   int _count=0;

   //processiamo gli ordini chiusi
   int _closedOrders=OrdersHistoryTotal();

   for(int i=_closedOrders-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY))
        {
         Print("Errore nel recuperare l'ordine: "+IntegerToString(GetLastError()));
        }
      else
        {
         // scartiamo i pendenti e le operazioni sul balance.
         if(OrderType()>1)
            continue;
         //scartiamo subito i trade non dentro il nostro intervallo temporale
         if(OrderCloseTime()<fromDate || OrderCloseTime()>toDate)
            continue;
          else
           {
            _count++;
           }
        }
     }
   if(_openTrades)
     {
      int _orders = OrdersTotal();
      for(int i=_orders-1; i>=0; i--)
        {
         if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
           {
            Print("Errore nel recuperare l'ordine: "+IntegerToString(GetLastError()));
           }
         else
           {
            // scartiamo i pendenti
            if(OrderType()>1)
               continue;
            //scartiamo subito i trade non dentro il nostro
            if(OrderOpenTime()<fromDate || OrderOpenTime()>toDate)
               continue;
            else
              {
               _count++;
              }
           }
        }
     } 
   return _count;
}

Eccoci qui, SE impostiamo l’argomento _openTrades su True, allora conteremo anche gli ordini aperti con il loop a loro dedicato.

4 – Mandiamo in output il conteggio dei trade, il profitto totale e i lotti utilizzati

Ok, ora restituiamo il conteggio, ma che decisioni potremo mai prendere sul nostro EA sapendo che abbiamo aperto 4 trade o 18 o 230? Ci serve sapere magari anche il profitto totale e i lotti utilizzati.

Ma una funzione può ritornare solo un valore!

Sì, ma mentre si esegue possiamo passargli delle variabili “per referenza”, dove salvare altri dati.

Esatto! Una funzione, diversi output.

Facciamo un passo indietro però. Per poter evitare alcuni errori di compilazione, l’ordine in cui si inseriscono gli argomenti nelle funzioni deve essere questo:

  1. Argomenti “liberi”
  2. Argomenti “per referenza”
  3. Argomenti con un valore di default

Quindi è bene strutturare SEMPRE le funzioni in modo che prendano per primi i parametri che ne modificano l’output direttamente, POI i parametri che eventualmente passiamo per referenza, e SUCCESSIVAMENTE quei parametri opzionali che hanno un valore di default.

Mantenendo questa logica in tutto il tuo codebase potrai riscrivere e rileggere il tuo codice con grande facilità anche a distanza di anni.

Tornando a noi, come prima cosa, correggiamo gli input e andiamo a togliere quella dichiarazione int _count =0, che ora non ci serve più.

Ci servirà invece azzerare i valori di _count, _lots, _profit ad ogni call della funzione, altrimenti continueranno ad aumentare all’infinito.

Gli input verranno preceduti dal simbolo “&” per dichiarare che le variabili sono passate per referenza.

int TradeCountFromDate(datetime _date, int &_count, double &_profit, double &_lots, bool 
                       _openTrades=True)
  {
   datetime fromDate=_date;
   datetime toDate=TimeCurrent();
   //int _count=0;
   _count=0;
   _profit=0.0;
   _lots=0.0;
  

   //...
   
   }

E andiamo ad aggiornare i due loop:

int TradeCountFromDate(datetime _date, int &_count, double &_profit, double &_lots, bool 
                       _openTrades=True)
  {
   datetime fromDate=_date;
   datetime toDate=TimeCurrent();
   int _count=0;

   //processiamo gli ordini chiusi
   int _closedOrders=OrdersHistoryTotal();

   for(int i=_closedOrders-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY))
        {
         Print("Errore nel recuperare l'ordine: "+IntegerToString(GetLastError()));
        }
      else
        {
         // scartiamo i pendenti e le operazioni sul balance.
         if(OrderType()>1)
            continue;
         //scartiamo subito i trade non dentro il nostro intervallo temporale
         if(OrderCloseTime()<fromDate || OrderCloseTime()>toDate)
            continue;
          else
           {
            _count++;
            _profit+=OrderProfit()+OrderSwap()+OrderCommission();
            _lots+= OrderLots();
           }
        }
     }
   if(_openTrades)
     {
      int _orders = OrdersTotal();
      for(int i=_orders-1; i>=0; i--)
        {
         if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
           {
            Print("Errore nel recuperare l'ordine: "+IntegerToString(GetLastError()));
           }
         else
           {
            // scartiamo i pendenti
            if(OrderType()>1)
               continue;
            //scartiamo subito i trade non dentro il nostro
            if(OrderOpenTime()<fromDate || OrderOpenTime()>toDate)
               continue;
            else
              {
               _count++;
               _profit+=OrderProfit()+OrderSwap()+OrderCommission();
               _lots+= OrderLots();
              }
           }
        }
     } 
   return _count;
}

La nostra funzione ritornerà comunque solo il conteggio delle operazioni, ma le variabili che passeremo come parametro saranno aggiornate secondo i calcoli fatti.

In questo modo, possiamo già utilizzare la nostra funzione: basterà istanziare nello scope globale (prima della OnInit()) le nostre tre variabili, e poi passarle come argomento alla funzione.

Inoltre, possiamo omettere la booleana al termine degli argomenti, e la funzione prenderà il valore True, impostato come default.

int Count;
double Profit;
double Lots;
datetime START_DATE= iTime(_Symbol,PERIOD_W1,0);
//+------------------------------------------------------------------+
//| Expert Advisor initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   Count = TradeCountFromDate(START_DATE,Count,Profit,Lots);
  }

In questo caso, impostando come START_DATE l’orario di apertura della candela settimanale a shift 0, sto elaborando i trade di questa settimana, da lunedì ad oggi.

È molto interessante per esempio per poter valutare la performance di trading settimanale e printarla in qualche luogo sullo schermo.

Vuoi provare ad implementarla in un EA? Eccoti un’idea semplice per scrivere un EA al volo: https://www.automazionetrading.com/strategie-forex-intraday-e-freccette-3/

Nella funzione che utilizzo nei miei programmi ho aggiunto anche un controllo (opzionale) su simbolo e magic number, tu come lo scriveresti?

Questi sono i miei parametri di input:

int TradeCountFromDate(datetime _date, 
                       int &_count, 
                       double &_profit, 
                       double &_lots, 
                       bool _openTrades=True, 
                       string _s=NULL, 
                       int _mn=NULL)
  {
   // ... BUSINESS LOGIC
   return _count
  }

Considera che ho usato solo 3 ulteriori if e due variabili extra. Codice molto leggero e funzionante.

Fammi sapere nei commenti come risolveresti questo problema, in modo elegante e sempre “Agnostico” 😉

Ricordati che, come ti ho scritto all’inizio, se smanetti con questa funzione DEVI venirci a trovare nel gruppo Telegram (l’unico gruppo che non manda segnali e non vende nulla!) e raccontarci la tua esperienza.

CI TROVI QUI: GRUPPO TELEGRAM.

Ci vediamo alla prossima funzione!

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *