Organizzazione di Accesso ai Dati

In this section questions connected with obtaining, storing and requesting price data (timeseries) are considered.

Ricezione dei Dati da un Trade Server

Prima che i dati sui prezzi saranno disponibili nel terminale MetaTrader 5, devono essere ricevuti ed elaborati. Per ricevere i dati, deve essere stabilita la connessione al trade server MetaTrader 5. I dati vengono ricevuti sotto forma di blocchi compatti di barre minute, dal server su richiesta del terminale.

Il meccanismo di riferimento server per i dati non dipende da come la richiesta è stata avviata - da un utente durante la navigazione nel grafico o in modo con il programma nella linguaggio MQL5.

Memorizzazione di Dati Intermedi

I dati ricevuti da un server vengono automaticamente decompressi e salvati nella formato intermedio HCC. I dati su ogni simbolo vengono scritti in una cartella separata: terminal_directory\bases\server_name\history\symbol_name. Per esempio, i dati relativi ad EURUSD ricevuti dal server MetaQuotes-Demo verranno memorizzati in terminal_directory\bases\MetaQuotes-Demo\history\EURUSD\.

I dati vengono scritti in file con estensione .hcc . Ogni file memorizza i dati delle barre minute per un anno. Ad esempio, il file denominato 2009.hcc nella cartella EURUSD contiene barre minute di EURUSD per l'anno 2009. Questi file vengono utilizzati per la preparazione di dati sui prezzi per tutti i timeframes e non sono destinati per l'accesso diretto.

Recupero dei Dati in un Timeframe Necessario su Dati Intermedi

File intermedi HCC vengono utilizzati come fonte di dati per la creazione di dati sui prezzi per i timeframe richiesti nel formato HCC. I dati di formato HCC sono timeseries che sono preparate massimalmente per un accesso rapido. Essi vengono creati su richiesta di un grafico o di un programma MQL5. Il volume dei dati non deve superare il valore del parametro "Max barre nei grafici". I dati sono conservati per un ulteriore utilizzo in file con estensione hcc.

Per risparmiare le risorse, i dati su un timeframe vengono memorizzati e salvati nella RAM solo se necessario. Se non vengono chiamati per lungo tempo, vengono rilasciati dalla RAM e salvati in un file. Per ogni timeframe, i dati vengono preparati indipendentemente dal fatto che ci sono dati pronti per altri timeframes o meno. Le regole di formazione ed accesso ai dati sono gli stessi per tutti i timeframes. Significa, che nonostante il fatto che i dati memorizzati in unità HCC siano nn minuto, la disponibilità di dati HCC non significa la disponibilità di dati sulla tempistica M1 come HC nello stesso volume.

La ricezione di nuovi dati da un server chiama l'aggiornamento automatico dei dati sui prezzi utilizzati in formato HC di tutti i timeframes. Essa comporta anche il ricalcolo di tutti gli indicatori che implicitamente li utilizzano come dati di input per i calcoli.

Parametro "Max barre nel grafico"

Il parametro "Max barre nei grafici" limita il numero di barre in formato HC a disposizione di grafici, indicatori e programmi MQL5. Questo è valido per tutti i timeframe disponibili e serve, prima di tutto, per risparmiare risorse informatiche.

Quando si imposta un valore elevato di questo parametro, va ricordato, che se sono disponibili dati storici in profondità per piccoli timeframes, la memoria utilizzata per la memorizzazione dei buffers di timeseries ed indicatori, possono diventare centinaia di megabyte e raggiungere la restrizione RAM per il programma del terminale client (2Gb per applicazioni a 32 bit di MS Windows).

La modifica di "Max barre nei grafici" avrà effetto dopo che il terminale client viene riavviato. Il cambio di questo parametro causa né il riferimento automatico ad un server per i dati aggiuntivi, né formazione di barre aggiuntive di di timeseries. Dati relativi ai prezzi aggiuntivi vengono richiesti dal server, e le timeseries vengono aggiornate tenendo conto della nuova limitazione, in caso di uno scorrimento del grafico all'area senza dati, o quando i dati vengono richiesti da un programma MQL5.

Volume dei dati richiesti dal server corrisponde al numero di barre richiesto di questo timeframe con il parametro "Max barre nel grafico" preso in considerazione. La restrizione impostata da questo parametro non è rigida, e in alcuni casi il numero di barre disponibili per un periodo di tempo può essere un po' di più del valore corrente del parametro.

Disponibilità dati

La presenza di dati su formato HCC o anche in preparazione per l'utilizzo del formato HC, non sempre denota la disponibilità assoluta di tali dati da visualizzare nel chart o da utilizzare in programmi MQL5.

Quando si accede ai dati sui prezzi o valori degli indicatori da un programma MQL5 va ricordato che la loro disponibilità in un certo istante di tempo o a partire da un certo momento di tempo non è garantita. E' collegato con il fatto che con il fine di risparmiare risorse, la copia completa di dati necessari per un programma MQL5 non è memorizzata in MetaTrader 5; viene dato solo l'accesso diretto al database terminale.

La cronistoria dei prezzi per tutti i timeframes è costruita a partire da dati comuni di formato HCC, e qualsiasi aggiornamento dei dati da un server conduce all'aggiornamento dei dati per tutti i timeframes e al ricalcolo degli indicatori. A causa di tale accesso ai dati, può essere chiusa, anche se questi dati erano disponibili poc'anzi.

Sincronizzazione dei Dati Terminale e Dati Server #

Sicchè un programma MQL5 può richiamare dati da qualsiasi simbolo e timeframe, vi è la possibilità che i dati di una timeseries necessari non sono ancora formati nel terminale o i dati sui prezzi necessari non sono sincronizzati con il trade server. In questo caso è difficile prevedere il tempo di latenza.

Gli algoritmi con cicli di latenza non sono la soluzione migliore. L'unica eccezione in questo caso sono gli script, perché non hanno nessun algoritmo alternativo a causa di non avere la gestione degli eventi (event handling). Per gli indicatori personalizzati, tali algoritmi, nonché eventuali altri cicli di latenza sono fortemente sconsigliati, perché portano alla cessazione del calcolo di tutti gli indicatori e qualsiasi altra manipolazione dei dati sui prezzi del simbolo.

Per Expert Advisors ed indicatori, è meglio utilizzare il modello di eventi di handling. Se durante l'handling di eventi OnTick() o OnCalculate(), la ricezione di dati per le serie temporali richieste è fallita, è necessario uscire dall'event handler, facendo affidamento sulla disponibilità di accesso durante la chiamata successiva dell'handler.

Esempio di script per l'Aggiunta di Storico

Consideriamo l'esempio di uno script che esegue una richiesta per ricevere lo storico per il simbolo selezionato da un trade server. Lo script è destinato all'esecuzione in un grafico di un simbolo selezionato; non importa il timeframe, perché, come è stato detto sopra, i dati sui prezzi sono ricevuti da un trade server come dei dati confezionati da 1 minuto, da cui tutte le timeseriespredefiniti, vengono costruite poi .

Scriviamo tutte le azioni riguardanti la ricezione di dati come una funzione separata CheckLoadHistory (simbolo, timeframe , start_date):

int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
  {
  }

La funzione CheckLoadHistory() è concepita come una funzione universale che può essere chiamata da qualsiasi programma (Expert Advisor, Script o Indicatore), e pertanto richiede tre parametri di input: nome simbolo, periodo e la data di inizio per indicare l'inizio dello storico prezzi di cui si bisogno.

Inserire i necessari controlli nel codice della funzione prima di richiedere la storia mancante. Prima di tutto, dobbiamo fare in modo che il nome del simbolo ed il valore del periodo siano corretti:

   if(symbol==NULL || symbol==""symbol=Symbol();
   if(period==PERIOD_CURRENT)     period=Period();

Quindi facciamo in modo che il simbolo sia disponibile nella finestra di MarketWatch, vale a dire, la storia del simbolo sarà disponibile quando si invia una richiesta ad un trade server. Se non vi è un tale simbolo in MarketWatch, aggiungerlo utilizzando la funzione SymbolSelect().

   if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {f
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOLreturn(-1);
      SymbolSelect(symbol,true);
     }

Ora dobbiamo ricevere la data di inizio dello storico disponibile per la coppia simbolo/periodo indicata. Forse, il valore del parametro di input startdate, passato a CheckLoadHistory(), è all'interno dello storico disponibile; quindi la richiesta ad un trade server non è necessaria. Per ottenere la prima data per il simbolo-periodo del momento, viene utilizzata la funzione SeriesInfoInteger() con il modificatore SERIES_FIRSTDATE.

   SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
   if(first_date>0 && first_date<=start_date) return(1);

Il prossimo importante controllo sta controllando il tipo di programma, da cui la funzione viene chiamata. Nota, inviando la richiesta per aggiornare le timeseries con lo stesso periodo di quello dell'indicatore, che chiama l'aggiornamento, è indesiderabile. La non desiderabilità della richiesta dei dati sullo stesso simbolo-periodo come quello dell'indicatore, è condizionata dal fatto che l'aggiornamento dei dati storici viene eseguito nello stesso thread in cui opera l'indicatore. Quindi, la possibilità di insorgenza di un punto morto è alta. Per controllare ciò, usare la funzione MQL5InfoInteger() con il modificatore MQL5_PROGRAM_TYPE.

   if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
      return(-4);

Se tutti i controlli hanno dato risultati positivi, fa l'ultimo tentativo senza fare riferimento al server commercio. Prima cerchiamo di scoprire la data di inizio, per i quali sono disponibili dati minute in formato HCC. Richiediamo questo valore utilizzando la funzione SeriesInfoInteger() con il modificatore SERIES_TERMINAL_FIRSTDATE e ancora confrontandolo con il valore del parametro start_date.

   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- ci sono dati caricati per costruire timeseries
      if(first_date>0)
        {
         //--- forza la costruzione di timeseries
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- controlla data
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }

Se dopo tutti i controlli il thread di esecuzione è ancora nel corpo della funzione CheckLoadHistory(), significa che vi è una necessità di richiedere i dati mancanti dei prezzi da un trade server. In primo luogo, viene restituito il valore di "Max barre nel chart" utilizzando la funzione TerminalInfoInteger():

  int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);

Avremo bisogno per impedire la richiesta di dati aggiuntivi. Poi trovare la prima vera data nello storico del simbolo sul trade server (indipendentemente dal periodo) utilizzando la già nota funzione SeriesInfoInteger() con il modificatore SERIES_SERVER_FIRSTDATE.

   datetime first_server_date=0;
   while(!SeriesInfoInteger(symbol,PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date) && !IsStopped())
      Sleep(5);

Dal momento che la richiesta è un'operazione asincrona, la funzione viene richiamata nel ciclo con un piccolo ritardo di 5 millisecondi fino a quando la variabile first_server_date riceve un valore, o l'esecuzione del ciclo viene interrotta da un utente (IsStopped() restituirà true in questo caso). Indichiamo un valore corretto della data di inizio, a partire dalla quale richiediamo i dati sui prezzi da un trade server.

   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Attenzione: prima data server ",first_server_date," for ",
symbol," non corrisponde alla prima data della serie",first_date);

Se la data di inizio first_server_date del server è inferiore alla data di inizio first_date del simbolo in formato HCC, la voce corrispondente viene emessa nel Journal.

Ora siamo pronti a fare una richiesta ad un trade server per chiedere dati mancanti di prezzi. Effettuare la richiesta sotto forma di un ciclo ed iniziare a riempire il suo corpo:

   while(!IsStopped())
     {
      //1. attende la sincronizzazione tra le timeseries ri-costruite e lo storico intermedio come HHC
      //2. ricevee il numero corrente di barre in questa serie storica
      //    se le barre sono più larghe di Max_bars_in_chart, possiamo uscire, il lavoro è svolto
      //3. ottiene la data di inizio first_date in una timeseries ricostruita e la confronta con START_DATE
      //    se first_date è più piccolo di start_date, possiamo uscire, il lavoro è svolto
      //4. richiede dal server una nuova parte di storico - 100 bars iniziano dall'ultima barra disponibile numerata come 'bars'
     }

I primi tre punti sono attuati tramite i mezzi già noti.

   while(!IsStopped())
     {
      //--- 1.attende finchè il processo di ricostruzione della timeseries viene completato
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- 2.richiede ora quante barre abbiamo
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         //--- barre più di quelle che possono essere disegnate nel chart, uscita
         if(bars>=max_bars) return(-2); 
         //--- 3. restituisce la corrente data di inizio nella timeseries
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            // la data di inizio era prima di quella richiesta, task completato
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //4. Richiede da un server una nuova parte dello storico - 100 barre a partire dalla scorsa barra disponibile numerata 'bars'
     }

L'ultimo quarto punto viene lasciato - richiesta storico. Non possiamo fare riferimento ad un server direttamente, ma qualsiasi funzione-Copy inizia automaticamente la richiesta di invio ad un server, se lo storico in formato HCC non è sufficiente. Siccome il tempo della primissima data di inizio nella variabile first_date è il criterio semplice e naturale per valutare il grado richiesta di esecuzione, allora il modo ancor più semplice è quello di utilizzare la funzione CopyTime().

Quando si chiamano funzioni che copiano i dati provenienti da timeseries, si deve rilevare che il parametro start (numero della barra, a partire dal quale i dati di prezzo dovrebbero essere copiati) deve essere sempre all'interno dello storico disponibile del terminale. Se si hanno solo 100 barre, è privo di senso provare a copiare 300 barre partendo dalla barra con l'indice 500. Tale richiesta sarà intesa come erronea e non sarà trattata, cioè nessuna storia supplementare sarà caricata da un trade server.

Ecco perché si copierà dala barra 100 a partire dalla barra con indice bars (l'index). Ciò fornirà il corretto caricamento dello storico mancante, da un trade server. In realtà verrà caricato un po' di più rispetto alle 100 barre richieste, mentre il server invia lo storico di dimensioni più grandi.

   int copied=CopyTime(symbol,period,bars,100,times);

Dopo l'operazione di copia, dobbiamo analizzare il numero di elementi copiati. Se il tentativo fallisce, allora il valore di copied sarà uguale a null ed il valore del contatore fail_cnt sarà aumentato di 1. Dopo 100 tentativi di fallimento, l'operazione della funzione viene interrotta.

int fail_cnt=0;
...
   int copied=CopyTime(symbol,period,bars,100,times);
   if(copied>0)
     {
      //--- controllo dati
      if(times[0]<=start_date)  return(0);  // il valore copiato è più piccolo,
      if(bars+copied>=max_bars) return(-2); // le barre pronte sono più di quelle che possono essere disegnate nel chart
      fail_cnt=0;
     }
   else
     {
      //--- non più di 100 tentativi di fallimento in successo
      fail_cnt++;
      if(fail_cnt>=100) return(-5);
      Sleep(10);
     }
 

Così, non solo viene implementato nella funzione il corretto handling della situazione attuale in qualsiasi fase di esecuzione, ma viene restituito anche il codice di terminazione, che può essere gestito dopo la chiamata della funzione CheckLoadHistory() per ottenere ulteriori informazioni. Ad esempio, in questo modo:

   int res=CheckLoadHistory(InpLoadedSymbol,InpLoadedPeriod,InpStartDate);
   switch(res)
     {
      case -1 : Print("Simbolo sconosciuto",InpLoadedSymbol);                     break;
      case -2 : Print("Richieste più barre di quelle che possono essere disegnate nel chart"); break;
      case -3 : Print("Esecuzione fermata dall'utente");                          break;
      case -4 : Print("L'indicatore non deve caricare i suoi propri dati");                break;
      case -5 : Print("Caricamento fallito");                                     break;
      case  0 : Print("Tutti i dati caricati");                                    break;
      case  1 : Print("I dati già disponibili nella timeseries sono sufficienti");    break;
      case  2 : Print("La timeseries è costruita da dati disponibili sul terminale");   break;
      default : Print("L'esecuzione risulta non definita");
     }

Il codice completo della funzione può essere trovato nel esempio di uno script che mostra la corretta organizzazione di accesso ai dati con l'handling dei risultati della richiesta.

Codice:

//+--------------------------------------------------------------------------------+
//|                                                            TestLoadHistory.mq5 |
//|                                      Copyright 2009, MetaQuotes Software Corp. | 
//|                                                           https://www.mql5.com |
//+--------------------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.02"
#property script_show_inputs
//--- parametri di input
input string          InpLoadedSymbol="NZDUSD";   // Simbolo da caricare
input ENUM_TIMEFRAMES InpLoadedPeriod=PERIOD_H1;  // Periodo da caricare
input datetime        InpStartDate=D'2006.01.01'; // Data di inizio
//+--------------------------------------------------------------------------------+
//| Funzione di avvio del programma Script                                         |
//+--------------------------------------------------------------------------------+
voidOnStart()
  {
   Print("Start load",InpLoadedSymbol+","+GetPeriodName(InpLoadedPeriod),"from",InpStartDate);
//---
   int res=CheckLoadHistory(InpLoadedSymbol,InpLoadedPeriod,InpStartDate);
   switch(res)
     {
      case -1 : Print("Simbolo sconosciuto ",InpLoadedSymbol);             break;
      case -2 : Print("Richieste più barre delle massime barre (max bars) nel chart"); break;
      case -3 : Print("Il progamma è stato fermato");                        break;
      case -4 : Print("L'indicatore non dovrebbe caricare i suoi stessi dati");      break;
      case -5 : Print("Caricamento fallito");                                break;
      case  0 : Print("Caricamento OK");                                  break;
      case  1 : Print("Caricato precedentemente");                          break;
      case  2 : Print("Caricato precedentemente e costruito");                break;
      default : Print("Risultato sconosciuto");
     }
//---
   datetime first_date;
   SeriesInfoInteger(InpLoadedSymbol,InpLoadedPeriod,SERIES_FIRSTDATE,first_date);
   int bars=Bars(InpLoadedSymbol,InpLoadedPeriod);
   Print("First date ",first_date," - ",bars," bars");
//---
  }
//+--------------------------------------------------------------------------------+
//|                                                                                |
//+--------------------------------------------------------------------------------+
int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
  {
   datetime first_date=0;
   datetime times[100];
//--- controlla symbol & period
   if(symbol==NULL || symbol==""symbol=Symbol();
   if(period==PERIOD_CURRENT)     period=Period();
//--- controlla se il simbolo è selezionato nel MarketWatch
   if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOLreturn(-1);
      SymbolSelect(symbol,true);
     }
//--- controllare se sono presenti dati
   SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
   if(first_date>0 && first_date<=start_date) return(1);
//--- non chiedere per il caricamento dei propri dati se si tratta di un indicatore
   if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
      return(-4);
//--- secondo tentativo
   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- ci sono dati caricati per costruire timeseries
      if(first_date>0)
        {
         //--- forza la costruzione di timeseries
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- controlla data
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }
//--- Massime barre nel chart dalle opzioni del terminale
   int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);
//--- Carica info storiche dei simboli
   datetime first_server_date=0;
   while(!SeriesInfoInteger(symbol,PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date) && !IsStopped())
      Sleep(5);
//--- Fissa la data di inizio per il caricamento
   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Avviso: prima data del server",first_server_date," for ",symbol,
            " non corrisponde alla data della prima serie ",first_date);
//--- Carica dati passo per passo
   int fail_cnt=0;
   while(!IsStopped())
     {
      //--- attendi per la costruzione di timeseries
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- chiedi per barre costruite
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         if(bars>=max_bars) return(-2);
         //--- chiedi per la prima data
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //--- la copia della prossima parte, forza il caricamento dei dati
      int copied=CopyTime(symbol,period,bars,100,times);
      if(copied>0)
        {
         //--- controlla i dati
         if(times[0]<=start_date)  return(0);
         if(bars+copied>=max_bars) return(-2);
         fail_cnt=0;
        }
      else
        {
         //--- non più di 100 tentativi di fallimento
         fail_cnt++;
         if(fail_cnt>=100) return(-5);
         Sleep(10);
        }
     }
//--- fermato
   return(-3);
  }
//+--------------------------------------------------------------------------------+
//| Restituisce i valori stringa del periodo                                       |
//+--------------------------------------------------------------------------------+
string GetPeriodName(ENUM_TIMEFRAMES period)
  {
   if(period==PERIOD_CURRENTperiod=Period();
//---
   switch(period)
     {
      case PERIOD_M1:  return("M1");
      case PERIOD_M2:  return("M2");
      case PERIOD_M3:  return("M3");
      case PERIOD_M4:  return("M4");
      case PERIOD_M5:  return("M5");
      case PERIOD_M6:  return("M6");
      case PERIOD_M10return("M10");
      case PERIOD_M12return("M12");
      case PERIOD_M15return("M15");
      case PERIOD_M20return("M20");
      case PERIOD_M30return("M30");
      case PERIOD_H1:  return("H1");
      case PERIOD_H2:  return("H2");
      case PERIOD_H3:  return("H3");
      case PERIOD_H4:  return("H4");
      case PERIOD_H6:  return("H6");
      case PERIOD_H8:  return("H8");
      case PERIOD_H12return("H12");
      case PERIOD_D1:  return("Daily");
      case PERIOD_W1:  return("Weekly");
      case PERIOD_MN1return("Monthly");
     }
//---
   return("periodo sconosciuto");
  }