English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
L'Handler dell'Evento "Nuova Barra"

L'Handler dell'Evento "Nuova Barra"

MetaTrader 5Esempi | 16 dicembre 2021, 11:05
486 0
Konstantin Gruzdev
Konstantin Gruzdev

Introduzione

Autori di indicatori ed expert sono da sempre interessati a scrivere il codice compatto in termini di tempo di esecuzione. È possibile affrontare questo problema da diverse prospettive. Da questo ampio argomento in questo articolo tratteremo il problema, che è apparentemente già stato risolto: controlla una nuova barra. Questo è un modo abbastanza conosciuto per limitare i cicli di calcolo, poiché tutti i calcoli e le operazioni di trading vengono eseguiti una volta sola durante la generazione di una nuova barra sul grafico. Quindi, cosa verrà discusso:  

  • Modi per rilevare nuove barre.
  • Carenze degli algoritmi esistenti di rilevamento di nuove barre.
  • Creazione di un metodo universale di rilevamento di nuove barre.
  • Finezze e modi di applicare questo metodo.
  • Evento NewBar e handler di questo evento - OnNewBar().

Modi per Rilevare Nuove Barre

Ora, ci sono alcune soluzioni accettabili su come rilevare una nuova barra. Ad esempio, possono essere trovati negli articoli Limitazioni e verifiche in Expert Advisors,I Principi del Calcolo Economico degli Indicatori qui. A proposito, consiglio di studiare questi materiali. Sarà più facile capire di cosa sto parlando.  

Questi materiali utilizzano il principio di tracciare l'orario di apertura del bar attualmente incompiuto. Questo è un modo molto semplice e affidabile. Esistono altri metodi per rilevare una nuova barra.

Ad esempio, negli indicatori personalizzati per questo scopo è possibile utilizzare due parametri di input della funzione OnCalculate() : rates_total e prev_calculated. Limitazione di questo metodo - è fondamentalmente il fatto, che può essere utilizzato solo per rilevare una nuova barra sul grafico corrente e solo negli indicatori. Se si desidera trovare una nuova barra su un altro periodo o simbolo, è necessario utilizzare tecniche aggiuntive.

Oppure, ad esempio, puoi provare a catturare una nuova barra sul suo primo segno di spunta, quando Tick Volume = 1 o quando tutti i prezzi della barra sono uguali: Open = High = Low = Close. Questi metodi possono essere ben utilizzati per i test, ma nel trading reale sono spesso un bug. Ciò è dovuto al momento tra il primo e il secondo segno di spunta a volte non è sufficiente per catturare la barra generata. Ciò è particolarmente evidente in un forte movimento di mercato o quando la qualità della connessione Internet è scarsa.  

Esiste un modo per rilevare una nuova barra basata sulla funzione TimeCurrent(). A proposito, è un buon metodo se è necessario rilevare una nuova barra per il grafico corrente. Lo useremo alla fine di questo articolo.

Bene, puoi persino chiedere a un vicino: "Ehi, c'è un nuovo bar?". Mi chiedo cosa risponderà? Bene, ok, fermiamo la tua scelta sul principio del tempo di tracciamento dell'apertura della barra non finita corrente per rilevarne una nuova. La sua semplicità e affidabilità sono davvero collaudate. 

Punto di partenza

Nei materiali sopra menzionati le cose non sono niente male con il rilevamento di una nuova barra. Ma...  

Per capire cos'è questo "ma", come punto di partenza (o prototipo) prenderemo una funzione di lavoro semplice e buona per rilevare una nuova barra dall'articolo Limitazioni e verifiche in Expert Advisor. Eccolo:

//+------------------------------------------------------------------+
//| Returns true if a new bar has appeared for a symbol/period pair  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

Questa funzione prototipo funziona davvero e ha il pieno diritto alla vita. Ma... 

Analisi della Funzione del Prototipo

Ho copiato questa funzione nel codice sorgente del mio (ovviamente) più grande e migliore dei migliori Expert Advisor. Non ha funzionato. Ho iniziato a indagare. Di seguito dico come la penso su questa funzione.

Intestazione della funzione. Pertanto, diamo un'occhiata a tutto. Iniziamo con l'intestazione della funzione:

bool isNewBar()

Mi piace l'intestazione della funzione: è molto semplice, intuitiva e non ha bisogno di gestire i parametri trasferiti. Sarebbe bello in futuro usarlo in questa forma.

Limitazione del numero di chiamate. Dopo l'intestazione c'è la prima istruzione che inizializza la variabile statica:

//---  memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;

Tutto sembra procedere abbastanza bene. Ma...

Il problema è che usiamo la variabile statica. Gli argomenti della Guida ci dicono: Gli argomenti della Guida ci dicono:

Le Variabili statiche esistono dal momento dell’ esecuzione del programma e vengono inizializzate solo una volta prima che le funzioni specializzate OnInit() vengano chiamate. Se i valori iniziali non vengono specificati, le variabili della classe dello storage static stanno assumendo i valori zero iniziali. 

Le variabili locali, dichiarate con la parola chiave static mantengono i loro valori per tutta la durata della funzione. Ad ogni chiamata di funzione successiva, tali variabili locali contengono i valori che avevano durante la chiamata precedente.

Se chiami questa funzione prototipo da un luogo, allora abbiamo ciò di cui abbiamo bisogno. Ma se vogliamo usare questa funzione, ad esempio, di nuovo in un altro luogo nello stesso ciclo di calcolo, restituirà sempre false, il che significa che non c'è barra. E questo non sarà sempre così. La variabile statica in questo caso impone un limite artificiale al numero di chiamate di funzione prototipo.

Questione di universalità. L'istruzione seguente nella funzione prototipo è simile alla seguente:

//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

È logico che per ottenere l'ora di apertura dell'ultima barra non finita venga utilizzata la funzione SeriesInfoInteger() con il modificatore SERIES_LASTBAR_DATE.

La nostra funzione prototipo isNewBar() è stata originariamente concepita da semplice e per impostazione predefinita utilizza lo strumento di trading e il periodo del grafico corrente. Questo è accettabile se si desidera tenere traccia della nuova barra solo sul grafico corrente. Ma cosa fare, se uso il periodo e lo strumento non solo per il grafico corrente? Inoltre, cosa succede se ho qualche grafico complesso? Ad esempio, cosa succede se ho deciso di tracciare Renko o Kagi?

La mancanza può limitarci gravemente. Più tardi, ci occuperemo di come risolverlo.  

Gestione degli errori. Diamo un'occhiata alla funzione SeriesInfoInteger(). Cosa pensi che ritornerebbe, se verrà eseguito quando il grafico non è ancora stato formato? Tale situazione può verificarsi, ad esempio, se hai allegato il tuo Expert Advisor o indicatore a un grafico e hai deciso di modificare il periodo o il simbolo, o quando riavvii il terminale. Cosa succederà durante l'aggiornamento della timeserie? Tra l’altro, è un avviso negli argomenti della Guida:

Disponibilità dati

La presenza di dati sul formato HCC o anche in preparazione per l'utilizzo del formato HC, non sempre denota la disponibilità assoluta dei dati da visualizzare nel grafico o da utilizzare nei 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 al fatto che con lo scopo 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 cronologia dei prezzi per tutti i timeframe è 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 timeframe e al ricalcolo degli indicatori. A causa di ciò, l’accesso ai dati può essere negata, anche se questi dati erano disponibili un attimo fa.

Quindi, quale funzione restituirà? Per evitare questa incertezza, è necessario iniziare in qualche modo a trovare gli errori della query dell'ora di apertura dell'ultima barra non finita.  

Possibilità di inizializzazione. Andiamo avanti. Considera le seguenti affermazioni della nostra funzione prototipo:

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

Qui tutto va decisamente bene. Tuttavia, c'è una sfumatura. Hai notato la frase sopra dalla Guida: “Le variabili statiche esistono dal momento dell’esecuzione del programma e vengono inizializzate solo una volta prima che le funzioni specializzate OnInit() vengano chiamate.”? E se avessimo bisogno di più tempo per inizializzare la variabile last_time? Più precisamente, cosa bisogna fare se si vuole creare artificialmente una situazione della prima chiamata? O qualche altra situazione? È facile fare domande quando si conoscono le risposte. Ma ne parleremo più avanti.

Numero di barre. Successivamente la nostra funzione prototipo avrà il seguente codice:

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }

Vedi, un programmatore come me può fare in modo che l'operatore "sorprendi" il terminale client e lo Strategy Tester. Il punto è che, logicamente, il tempo passato è sempre inferiore al presente. Questo è last_time < lastbar_time. A causa di un errore accidentale del programma ho la macchina del tempo, o più esattamente - è successo il contrario: lastbar_time < last_time. Che sorpresa! In generale, un tale paradosso temporale è facile da rilevare e da visualizzare un messaggio di errore.

Ma ogni nuvola ha un rivestimento d'argento. Mentre guardavo la mia "macchina del tempo", ho scoperto che tra le chiamate isNewBar() non può apparire solo una nuova barra. Più piccolo è il periodo del grafico, maggiore è la probabilità che si verifichino diverse barre tra le chiamate di funzione. Ci possono essere molte ragioni per questo: a partire dal lungo tempo di calcolo e terminare con una temporanea mancanza di connessione con il server. L'opportunità di ricevere non solo il segnale di una nuova barra ma anche il numero di barre, sarà sicuramente utile.

La nostra funzione prototipo termina così:

//--- if we passed to this line, then the bar is not new; return false
   return(false);

Sì, se siamo passati a questa riga- la barra non è nuova.

Creazione di una nuova funzione isNewBar() 

Qui ha inizio una cosa interessante. Risolveremo i punti deboli rilevati. Sai, sono stato un po’ troppo modesto, chiamando la sezione come "Creazione di una nuova funzione isNewBar()". Faremo qualcosa di più solido.

Inizieremo con l'eliminazione delle restrizioni sul numero di chiamate di funzione.

La prima cosa, che mi viene in mente è che è possibile utilizzare funzioni con lo stesso nome di isNewBar() dall’articolo I Principi del Calcolo Economico degli Indicatori o da qui isNewBar. In altre parole, per includere matrici che memorizzano più valori di last_time nel corpo della funzione, inserire contatori delle chiamate di funzione isNewBar() da posizioni diverse e così via. Naturalmente, queste sono tutte versioni funzionanti e possono essere implementate. Tuttavia immagina se stiamo scrivendo un Expert Advisor multi-valuta per lavorare su 12 coppie di valute. Ci saranno così tante sfumature necessarie da considerare e da non confondere?

Che cosa dobbiamo fare? La risposta è qui!

La bellezza della Programmazione Orientata agli Oggetti è che un oggetto o la richiesta di una classe può "vivere la propria vita" indipendentemente da altre richieste della stessa classe. Quindi iniziamo a creare una classe CisNewBar, in modo da essere in grado di produrre richieste di questa classe in qualsiasi luogo del nostro Expert Advisor o Indicator un numero qualsiasi di volte. E che ogni istanza "viva la propria vita".

Questo è ciò che dobbiamo iniziare a fare:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar();       // First type of request for new bar
  };  

bool CisNewBar::isNewBar()
  {
   //--- here is the definition of static variable

   //--- here will be the rest of method's code   
   ...

   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Quella che era la funzione isNewBar(), ora è il metodo. Si noti che non esiste ora una variabile statica last_time - invece abbiamo una variabile di classe protetta m_lastbar_time. Se avessimo lasciato la variabile statica nel metodo isNewBar(), tutti i nostri sforzi sarebbero andati persi, poiché affronteremmo gli stessi problemi di prima con la funzione isNewBar() - queste sono caratteristiche delle variabili statiche.

E ora il tempo dell'ultima barra verrà memorizzato nella variabile protetta di classe m_lastbar_time e in ogni istanza di classe la memoria verrà allocata per questa variabile. In questo modo siamo stati in grado di rimuovere la restrizione sul numero di chiamate, che era nella funzione prototipo. Possiamo chiamare il metodo isNewBar() in luoghi diversi nel nostro programma MQL tutte le volte che vogliamo, creando un'istanza di classe per ogni luogo.

In questo ci siamo riusciti. Ora lavoriamo sull'universalità. Prima di aggiungere qualcosa alla nostra nuova classe, vorrei condurvi a un'idea divertente.

Ragioniamo. Cosa vogliamo? Vogliamo ottenere segnali sulla nuova barra. Come vogliamo farlo? Quindi, se il tempo di apertura della barra corrente non finita sull'ultimo tick (o all'ultimo momento) è superiore al tempo di apertura della barra corrente non finita sul tick precedente (o al momento precedente), si forma una nuova barra. Frase complicata, ma è corretta. La linea di fondo è che dobbiamo confrontare il tempo. Pertanto, ho deciso che sarebbe stato logico passare il tempo di apertura dell'attuale newbar_time di barre non completate nel metodo isNewBar(). Quindi l'intestazione del metodo sarà la seguente:

bool isNewBar(datetime newbar_time)

Non chiedere ancora dove porteremo il newbar_time - diamo per scontato che sia già noto. Ne parleremo più avanti.  

A proposito, passando il tempo nel metodo isNewBar(), otteniamo uno strumento molto flessibile per rilevare una nuova barra. Saremo in grado di coprire tutti i periodi dei grafici standard con tutti i tipi di strumenti di trading. È successo in modo che ora non dipendiamo dal nome del simbolo e dalla dimensione del periodo.  

Possiamo anche usare grafici non standard. Ad esempio, se stai tracciando candele tick, grafici Renko o Kagi, il loro tempo di apertura della barra non coincide praticamente mai con il tempo dei periodi standard del grafico. In questo caso, la nostra funzione sarà indispensabile.

Bene, ora va bene con la versatilità. Integriamo la nostra classe CisNewBar in conformità con la nostra idea:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      uint              m_retcode;        // Result code of detecting new bar
      int               m_new_bars;       // Number of new bars
      string            m_comment;        // Comment of execution
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar(datetime new_Time); // First type of request for new bar
  };
   
//+------------------------------------------------------------------+
//| First type of request for new bar                     |
//| INPUT:  newbar_time - time of opening (hypothetically) new bar   |
//| OUTPUT: true   - if new bar(s) has(ve) appeared                  |
//|         false  - if there is no new bar or in case of error      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Initialization of protected variables
   m_new_bars = 0;      // Number of new bars
   m_retcode  = 0;      // Result code of detecting new bar: 0 - no error
   m_comment  =__FUNCTION__+" Successful check for new bar";
   //---
   
   //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // If new bar is older than last bar, print error message
      m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+
                                                  ", time of new bar request "+TimeToString(newbar_time);
      m_retcode=-1;     // Result code of detecting new bar: return -1 - synchronization error
      return(false);
     }
   //---
        
   //--- if it's the first call
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- set time of last bar and exit
      m_comment   =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Check for new bar:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Number of new bars
      m_lastbar_time=newbar_time; // remember time of last bar
      return(true);
     }
   //---
   
   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Guardando il codice sorgente della nostra classe, probabilmente avrai notato che abbiamo preso in considerazione il monitoraggio degli errori di runtime e abbiamo introdotto la variabile che memorizza il numero di nuove barre.

Va bene così, ma il nostro metodo universale èNewBar(datetime newbar_time) contiene un grosso inconveniente. Questo inconveniente è che dobbiamo sempre preoccuparci di calcolare il tempo di (ipoteticamente) newbar_time nel codice sorgente del nostro expert o indicatore.  

Fortunatamente, in alcuni casi, possiamo semplificarti la vita, affidando questa funzione al nuovo metodo aggiuntivo della nostra classe. Per i periodi e i simboli standard nella nostra funzione prototipo questo può essere fatto usando la seconda versione della funzione SeriesInfoInteger() con il modificatore SERIES_LASTBAR_DATE e, in tutti gli altri casi, utilizzando un metodo generico. Quindi, ecco cosa ho:

//+------------------------------------------------------------------+
//| Second type of request for new bar                               |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - Number of new bars                          |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Request time of opening last bar:
   ResetLastError(); // Set value of predefined variable _LastError as 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // If request has failed, print error message:
      m_retcode=GetLastError();  // Result code of detecting new bar: write value of variable _LastError
      m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Next use first type of request for new bar, to complete analysis:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Correct number of new bars:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- If we've reached this line - then there is(are) new bar(s), return their number:
   return(m_new_bars);
  }

Quindi, cosa abbiamo in questo momento? Ora per i periodi standard non dobbiamo preoccuparci di determinare l'orario di apertura dell'ultima barra incompiuta. Ci siamo avvicinati alla nostra funzione prototipo con la sua semplice chiamata e senza i difetti che aveva. E ha anche ottenuto ulteriori vantaggi, tra cui codici di errore, commenti di runtime e numero di nuove barre.   

E’ rimasto qualcosa? Sì C'è l'ultimo momento: l'inizializzazione. Per questo useremo il costruttore di classi e diversi metodi Set. Il nostro costruttore di classe è simile al 19:  

//+------------------------------------------------------------------+
//| CisNewBar constructor.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Result code of detecting new bar
   m_lastbar_time=0;    // Time of opening last bar
   m_new_bars=0;        // Number of new bars
   m_comment="";        // Comment of execution
   m_symbol=Symbol();   // Symbol name, by default - symbol of current chart
   m_period=Period();   // Chart period, by default - period of current chart
  }

E metodi Set come questo:

//--- Methods of initializing protected data:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

Grazie al costruttore di classi, non è necessario prestare attenzione all'inizializzazione del simbolo e del periodo del grafico corrente. Come nella funzione prototipo, verranno utilizzati per impostazione predefinita. Ma se abbiamo bisogno di usare un altro simbolo o periodo grafico, allora possiamo usare per questo i nostri metodi Set creati. Inoltre, utilizzando SetLastBarTime(datetime lastbar_time) è possibile ricreare la situazione della "prima chiamata".

In conclusione, consente di creare diversi metodi Get per ottenere dati dalla nostra classe in Expert Advisor e Indicatori: 

      //--- Methods of access to protected data:
uint              GetRetCode()     const  {return(m_retcode);     }  // Result code of detecting new bar 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Time of opening last bar
int               GetNewBars()     const  {return(m_new_bars);    }  // Number of new bars
string            GetComment()     const  {return(m_comment);     }  // Comment of execution
string            GetSymbol()      const  {return(m_symbol);      }  // Symbol name
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Chart period

Ora possiamo ottenere tutte le informazioni necessarie nei nostri programmi mql5. Per ora possiamo fare un punto fermo nella creazione della classe CisNewBar.

Il codice sorgente completo per la nostra classe si trova nel file allegato Lib CisNewBar.mqh.

Esempi di Utilizzo della Classe CisNewBar

Ti propongo di considerare gli esempi del nostro utilizzo della classe per approfondire tutte le finezze di ciò che abbiamo creato. Forse ci possono essere non solo vantaggi ma anche svantaggi.

Esempio 1. Per iniziare, creiamo un Expert Advisor assolutamente identico per la funzione isNewBar() dall'articolo Limitazioni e verifiche in Expert Advisor

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Eseguiamo entrambi gli Expert Advisor su grafici con la stessa coppia e lo stesso periodo. Vediamo cosa abbiamo:


Innanzitutto, entrambi gli Expert Advisor segnalano in modo sincrono la nuova barra. Poi tacciono e solo quattro minuti dopo informano che c'è una nuova barra (questa volta è contrassegnata come 1). Va bene - mi sono appena disconnesso da Internet per alcuni minuti e ho deciso di vedere cosa succederà. Nonostante il fatto che siano state formate poche barre, non abbiamo ricevuto queste informazioni. Nel nostro nuovo Expert Advisor possiamo correggere questo svantaggio, poiché il nostro metodo isNewBar() consente di fare una cosa del genere.

Successivamente, ho cambiato il periodo del grafico in M2. La reazione degli Expert Advisor è stata diversa. CheckLastBar ha iniziato a segnalare una nuova barra ogni 2 minuti e Example1NewBar informa sulle nuove barre ogni minuto, come se il periodo non fosse stato modificato (contrassegnato come 2).

Il fatto è che la nostra istanza current_chart è stata inizializzata dal costruttore di classe quando Expert Advisor è stato allegato al grafico. Quando si modifica il periodo di Expert Advisor, già allegato al grafico, il costruttore di classe non viene avviato e Expert Advisor continua a lavorare con il periodo M1. Questo ci dice che la nostra istanza di classe vive di vita propria e non è influenzata dai cambiamenti nell'ambiente. Può essere sia pro che contro - tutto dipende dai compiti.  

Affinché il nostro Expert Advisor funga da CheckLastBar, dobbiamo inizializzare le variabili di classe protetta m_symbol e m_period nella funzione OnInit(). Facciamolo.

Esempio 2. Introduciamo alcune aggiunte al nostro Expert Advisor e confrontiamo nuovamente le sue prestazioni con CheckLastBar. Il codice sorgente di Expert Advisor è allegato come file Example2NewBar.mq5. Esegui Expert Advisor su grafici con la stessa coppia e lo stesso periodo. Creiamo per loro gli stessi ostacoli dell'ultima volta. Vediamo cosa abbiamo:


Come l'ultima volta, gli Expert Advisor segnalano per la prima volta in modo sincrono la nuova barra. Poi mi disconnetto da Internet per qualche minuto... Quindi lo riaccendo. Il nostro nuovo Expert Advisor non solo ha riferito su una nuova barra, ma anche su quanti di essi sono apparsi (contrassegnati come 1). Per la maggior parte degli indicatori e degli esperti, questo numero indicherà il numero di barre non calcolate. Pertanto, abbiamo una buona base per algoritmi di ricalcolo convenienti.  

Successivamente, ho cambiato il periodo dei grafici in M2. A differenza dell'esempio 1, Expert Advisor funziona in modo sincrono (contrassegnato come 2). L'inizializzazione delle variabili di classe protette m_symbol e m_period nella funzione OnInit() ha aiutato! Quando si modifica il simbolo (contrassegnato come 3), anche gli Expert Advisor funzionano allo stesso modo. 

Esempio 3. Nella nostra classe CisNewBar abbiamo messo la possibilità di tenere traccia degli errori. Può accadere che Expert Advisor sia progettato in modo che non sia necessario tenere traccia degli errori. Bene, allora non usare questa possibilità. Cercheremo di creare artificialmente una situazione in cui è possibile che si presenti l'errore e cercheremo di coglierlo. Per questo integreremo leggermente il codice sorgente di Expert Advisor (il file Example3NewBar.mq5).

Cosa farò? Come al solito eseguirò Example3NewBar su grafici minuti. Poi inizierò a cambiare gli strumenti del grafico nella speranza che si verifichi una situazione in cui il terminale non avrà il tempo di accumulare timeserie prima della richiesta di Expert Advisor. In generale, tormenterò il terminale del cliente e vedrò cosa succederà ...  

Dopo diversi tentativi, il nostro Expert Advisor ha rilevato un errore:

 

Ora possiamo dire con sicurezza che siamo in grado di rilevare errori di runtime. Come gestirli - è una questione di gusti. Tieni presente che abbiamo monitorato questo errore quattro volte. Quando il download è completato e il grafico è formato, l'Expert Advisor ha suggerito che abbiamo perso solo 1 barra.

A proposito, coloro che hanno guardato il codice sorgente di Expert Advisor potrebbero aver notato che ha senso verificare la disponibilità di errori solo quando il metodo isNewBar() restituisce un valore inferiore o uguale a zero.

Attenzione: Se durante questo esperimento inizierai a modificare il periodo del grafico, quando cambi il periodo del grafico da piccolo a più grande, riceverai un errore di sincronizzazione. Questo perché l'orario di apertura della barra (ad esempio) di H1 è precedente a M1 in 59 casi. Per evitare questo errore quando si cambia periodo grafico, è necessario inizializzare correttamente la variabile m_lastbar_time nella funzione OnInit() con il metodo SetLastBarTime (datetime lastbar_time).

Esempio 4. In questo esempio complichiamo il compito di Expert Advisor. Prendi tre coppie di valute: EURUSD su M1, GBPUSD su M1 e USDJPY su M2. Il grafico con la prima coppia sarà attuale e su di esso guarderemo solo una nuova barra. Con la seconda coppia, calcoleremo il numero di barre formate dopo l'inizio di Expert Advisor. Conteremo fino a quando la prima coppia segnalerà che c'è una nuova barra. E sulla terza coppia effettueremo costantemente (quando appare una barra sull'EURUSD) l'inizializzazione della variabile di classe protetta m_lastbar_time. Il codice sorgente di Expert Advisor è allegato come file Example4NewBar.mq5.

Creando questo esempio, voglio scoprire come funzionerà la nostra classe CisNewBar in modalità multi-valuta. Beh, inizio... Ecco cosa ho:


I risultati sollevano domande. Aggiungerò benzina sul fuoco ed eseguirò proprio questo intervallo di tempo in Strategy Tester. Risultati dello Strategy Tester:


Quindi puoi giocare al gioco "trova dieci differenze". Oltre alle stranezze del lavoro di Expert Advisor sull'account demo, è ovvio che ci sono differenze tra conto demo e Strategy Tester - e sono chiaramente visibili. Un confronto simile con l'approccio giusto non solo rivelerà gli svantaggi di Expert Advisor, ma consentirà anche di eliminarli. Forse, non ho intenzione di analizzare perché è successo, come è successo e cosa deve essere risolto in Expert Advisor.  

Esempio 5. Negli esempi non abbiamo mai usato esplicitamente il metodo più universale per rilevare una nuova barra - isNewBar(datetime newbar_time). Per fare ciò, prenderò il tick candela dall'articolo Creazione di Indicatori Tick in MQL5 e aggiungerò un buffer per memorizzare il tempo di apertura della barra (file TickColorCandles v2.00.mq5). Scriverò un brevissimo Expert Advisor che racconterà il tempo della nuova candela teak (file Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick
int HandleIndicator;  // indicator handle
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Get indicator handle:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Error when creating indicator handle, error code: ",GetLastError());
      Print(" Incorrect initialization of Expert Advisor. Trade is not allowed.");
      return(1);
     }

//--- Attach indicator to chart:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Error when attaching indicator to chart, error code: ",GetLastError());
      return(1);
     }
//--- If you passed until here, initialization was successful
   Print(" Successful initialization of Expert Advisor. Trade is allowed.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Get time of opening last unfinished tick candlestick:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Failed to get time value of indicator. "+
            "\nNext attempt to get indicator values will be made on the next tick.",GetLastError());
      return;
     }
//--- Detect the next tick candlestick:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("New bar. Opening time: %s  Time of last tick: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Sicuramente hai notato come otteniamo il tempo di apertura delle candele. Molto facile, non è vero? Metto l'indicatore e Expert Advisor nelle loro cartelle, compilo ed eseguo Expert Advisor. Funziona, ed ecco i risultati:  

 

L’Handler dell’Evento “Nuova Barra”


Avvicinandomi alla fine di questo articolo, vorrei condividere un'altra idea. Su Forum (in russo) c'era l'idea che sarebbe stato bello avere un handler di eventi standard "nuova barra". Forse una volta che gli sviluppatori arriveranno a questo, ma forse no. Ma la bellezza di MQL5 è che è possibile implementare le idee più sorprendenti in modo elegante e semplice.

Se vuoi avere un handler eventi "nuova barra" (o NewBar) - quindi creiamolo! Soprattutto adesso che possiamo catturare questo evento con facilità, usando la nostra classe. Ecco come sarà il nostro expert (con l’handler eventi NuovaBarra OnNewBar()):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler

//+------------------------------------------------------------------+
//| New bar event handler function                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

Sembra abbastanza buono. Il nostro Expert Advisor sembra molto semplice. Questo gestore stampa la stringa sulla nuova barra. Questo è tutto ciò che fa. Per comprendere come tenere traccia dell'evento NewBar e come eseguire il gestore, è necessario esaminare il file OnNewBar.mqh:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Number of seconds in current chart period
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart
   if(current_chart.isNewBar(new_time)) OnNewBar();               // When new bar appears - launch the NewBar event handler
  }

Come puoi vedere, anche qui non c'è nulla di complicato. Ma ci sono un paio di momenti su cui vorrei attirare la vostra attenzione:

Primo. Come hai notato, uso la funzione TimeCurrent() per calcolare il tempo di apertura della barra e uso il primo metodo di controllo per l'evento NewBar dalla nostra classe. Questo è un bel vantaggio. Risiede nel fatto che tale metodo non richiede alcuna elaborazione degli errori, come quando si utilizza SeriesInfoInteger() con il modificatore SERIES_LASTBAR_DATE. Per noi è importante, dato che il nostro gestore OnNewBar() dovrebbe essere affidabile il più possibile.

Secondo. L'uso della funzione TimeCurrent() per calcolare il tempo di apertura della barra è il modo più veloce. L'uso della funzione SeriesInfoInteger(), anche senza controllo degli errori per lo stesso scopo è un modo più lento.

Il risultato del nostro handler:

   

Conclusione

Nel corso della presentazione del materiale abbiamo fatto una buona analisi dei modi per rilevare una nuova barra. Abbiamo esposto pro e contro dei metodi esistenti per rilevare nuove barre. Sulla base di ciò che avevamo, abbiamo creato la classe CisNewBar, consentendo senza costi aggiuntivi di programmazione di catturare l'evento "nuova barra" in quasi tutte le attività. Allo stesso tempo, ci siamo sbarazzati della maggior parte degli inconvenienti delle soluzioni precedenti.    

Questi esempi ci hanno aiutato a capire i pro e i contro dei metodi inventati da noi. Un'attenzione particolare in termini di lavoro corretto richiede la modalità multi-valuta. Devi fare un'analisi approfondita delle inefficienze identificate e sviluppare modi per risolverle.

L’handler eventi "nuova barra" creato è adatto solo per Expert Advisor con valuta singola. Ma abbiamo imparato a usare il modo più affidabile e veloce per questo motivo. Ora puoi andare avanti e creare un handler eventi NewBar multivaluta. Ma questo è l'argomento di un altro articolo.  

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

File allegati |
example1newbar.mq5 (0.94 KB)
example2newbar.mq5 (2.61 KB)
example3newbar.mq5 (2.98 KB)
example4newbar.mq5 (6.41 KB)
example5newbar.mq5 (2.39 KB)
onnewbar.mq5 (0.8 KB)
onnewbar.mqh (1.12 KB)
Growing Neural Gas: Implementazione in MQL5 Growing Neural Gas: Implementazione in MQL5
L'articolo mostra un esempio di come sviluppare un programma MQL5 che implementa l'algoritmo adattivo di clustering chiamato Growing neural gas (GNG). L'articolo è destinato agli utenti che hanno studiato la documentazione linguistica e hanno determinate capacità di programmazione e conoscenze di base nell'area della neuro informatica.
Simulink: una Guida per gli Sviluppatori di Expert Advisor Simulink: una Guida per gli Sviluppatori di Expert Advisor
Non sono un programmatore professionista. E così, il principio di "passare dal semplice al complesso" è di primaria importanza per me quando lavoro allo sviluppo del sistema di trading. Cosa esattamente è semplice per me? Prima di tutto, è la visualizzazione del processo di creazione del sistema e la logica del suo lavoro. Inoltre, è un minimo di codice scritto a mano. In questo articolo, tenterò di creare e testare il sistema di trading basato su un pacchetto Matlab, e quindi scrivere un Expert Advisor per MetaTrader 5. I dati storici di MetaTrader 5 verranno utilizzati per il processo del test.
MQL5 Wizard: Creazione di Expert Advisor senza Programmazione MQL5 Wizard: Creazione di Expert Advisor senza Programmazione
Vuoi provare una strategia di trading senza perdere tempo per la programmazione? In MQL5 Wizard puoi semplicemente selezionare il tipo di segnali di trading, aggiungere moduli di posizioni trailing e gestione del denaro - e il tuo lavoro è fatto! Crea le tue implementazioni di moduli o ordinale tramite il servizio Jobs e combina i tuoi nuovi moduli con quelli esistenti.
Ricerca di Errori e Registrazione Ricerca di Errori e Registrazione
MetaEditor 5 ha la funzione di debug. Tuttavia quando scrivi i tuoi programmi MQL5, spesso non vuoi visualizzare solo i singoli valori, ma tutti i messaggi che appaiono durante i test e il lavoro online. Quando il contenuto del file del log è di grandi dimensioni, è ovvio automatizzare il recupero rapido e semplice del messaggio richiesto. In questo articolo considereremo i modi per trovare errori nei programmi MQL5 e i metodi di logging. Inoltre semplificheremo l'accesso ai file e conosceremo un semplice programma LogMon per una comoda visualizzazione dei log.