English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Utilizzo di pseudo-modelli come alternativa ai modelli C++

Utilizzo di pseudo-modelli come alternativa ai modelli C++

MetaTrader 5Esempi | 17 dicembre 2021, 15:27
87 0
Mykola Demko
Mykola Demko


Introduzione

La questione dell'implementazione dei modelli come standard della lingua è stata sollevata più volte nel forum mql5.com. Di fronte al muro di rifiuto eretto dagli sviluppatori di MQL5, il mio interesse per l'implementazione di modelli utilizzando metodi personalizzati ha iniziato a crescere. Il risultato dei miei studi è presentato in questo articolo.


Un po' di storia su C e C++

Fin dall'inizio, il linguaggio C è stato sviluppato per offrire la possibilità di eseguire attività di sistema. I creatori del linguaggio C non hanno implementato un modello astratto di ambiente di esecuzione del linguaggio; hanno solo implementato funzionalità per le esigenze dei programmatori di sistema. Innanzitutto, questi sono i metodi di lavoro diretto con la memoria, le costruzioni strutturali di controllo e la gestione dei moduli delle applicazioni.

In realtà, non è stato incluso altro nel linguaggio; tutte il resto è stato portato nella libreria di runtime. Ecco perché alcune persone maldisposte a volte si riferiscono al linguaggio C come all'assemblatore strutturato. Ma qualunque cosa dicano, l'approccio sembra aver avuto molto successo. Grazie ad esso è stato raggiunto un nuovo livello di rapporto tra semplicità e potenza del linguaggio. 

Quindi, il C è apparso come un linguaggio universale per la programmazione del sistema. Ma non è rimasto entro quei limiti. Alla fine degli anni '80, spingendo Fortran oltre la leadership, C ha guadagnato un'ampia popolarità tra i programmatori di tutto il mondo ed è diventato ampiamente utilizzato in diverse applicazioni. Un contributo significativo alla sua popolarità è stato dato dalla distribuzione di Unix (e quindi del linguaggio C) nelle università, dove è stata educata una nuova generazione di programmatori.

Ma se tutto è così roseo, allora perché tutti gli altri linguaggi sono ancora utilizzati e cosa sostiene la loro esistenza? Il tallone d'Achille del linguaggio C è essere di livello troppo basso per problemi ambientati negli anni '90. Questo problema ha due aspetti.

In primo luogo, il linguaggio contiene mezzi di livello troppo basso: prima di tutto, è il lavoro con la memoria e l'aritmetica degli indirizzi. Pertanto, un cambiamento nel bit del processore causa molti problemi per molte applicazioni C. D'altra parte, in C mancano mezzi di alto livello: tipi astratti di dati e oggetti, polimorfismo e gestione delle eccezioni. Pertanto, nelle applicazioni C, una tecnica di implementazione di un compito domina spesso sul suo lato materiale.

I primi tentativi di correggere questi svantaggi sono stati fatti all'inizio degli anni '80. A quel tempo, Bjarne Stroustrup in AT&T Bell Labs iniziò a sviluppare l'estensione del linguaggio C chiamato "C con classi". Lo stile dello sviluppo corrispondeva allo spirito di creazione del linguaggio C stesso: l'aggiunta di diverse funzionalità per rendere più conveniente il lavoro di determinati gruppi di persone.

La principale innovazione in C++ è il meccanismo delle classi che dà la possibilità di utilizzare nuovi tipi di dati. Un programmatore descrive la rappresentazione interna dell'oggetto classe e l'insieme di funzioni-metodi per accedere a tale rappresentazione. Uno degli scopi principali della creazione del C++ era aumentare la percentuale di riutilizzo del codice già scritto.

L'innovazione del linguaggio C++ non consiste solo nell'introduzione di classi. È stato implementato un meccanismo di gestione strutturale delle eccezioni (la sua mancanza aveva complicato lo sviluppo di applicazioni fail-safe), un meccanismo di modelli e molto altro. Pertanto, la principale linea di sviluppo del linguaggio è stata diretta ad ampliare le sue possibilità introducendo nuove costruzioni di alto livello, mantenendo la piena compatibilità con ANSI_CANSI С


Modello come meccanismo di sostituzione macro

Per capire come implementare un template in MQL5, devi capire come questi funzionano su C++.

Vediamo la definizione.

I modelli sono una funzionalità del linguaggio di programmazione C++ che consente a funzioni e classi di operare con tipi generici. Ciò consente a una funzione o a una classe di lavorare su molti tipi di dati diversi senza essere riscritti per ciascuno di essi.

MQL5 non ha modelli, ma ciò non significa che sia impossibile utilizzare uno stile di programmazione con essi. Il meccanismo dei template nel linguaggio C++ è, in realtà, un sofisticato meccanismo di generazione macro profondamente radicato nel linguaggio. In altre parole, quando un programmatore utilizza un modello, il compilatore determina il tipo di dati in cui viene chiamata la funzione corrispondente, non dove viene dichiarata.

I modelli sono stati introdotti in C++ per ridurre la quantità di codice scritto dai programmatori. Ma non bisogna dimenticare che un codice digitato su una tastiera da un programmatore non è uguale a quello creato dal compilatore. Il meccanismo dei modelli in sé non ha comportato una diminuzione delle dimensioni dei programmi; ha solo ridotto la dimensione del loro codice sorgente. Ecco perché il problema principale, risolto utilizzando i modelli, è la diminuzione del codice scritto dai programmatori.

Poiché il codice macchina viene generato durante la compilazione, i comuni programmatori non vedono se il codice di una funzione viene generato una o più volte. Durante la compilazione di un codice modello, il codice della funzione viene generato tante volte quanti sono i tipi in cui è stato utilizzato il modello. Fondamentalmente, un modello ha la priorità nella fase di compilazione.

Il secondo aspetto dell'introduzione dei modelli in C++ è l'allocazione della memoria. La questione è che la memoria nel linguaggio C è allocata staticamente. Per rendere questa allocazione più flessibile, viene utilizzato un modello che imposta la dimensione della memoria per gli array. Ma questo aspetto è già stato implementato dagli sviluppatori di MQL4 sotto forma di array dinamici ed è stato fatto anche in MQL5 sotto forma di oggetti dinamici.

Resta quindi irrisolto solo il problema della sostituzione dei tipi. Gli sviluppatori di MQL5 si sono rifiutati di risolverlo, riferendosi all'utilizzo del meccanismo di sostituzione del modello che avrebbe consentito il cracking del compilatore, cosa che avrebbe portato alla comparsa di un decompilatore.

Beh, loro lo sanno meglio di noi. E abbiamo solo una scelta: implementare questo paradigma in modo personalizzato.

Prima di tutto, lasciatemi fare un'osservazione: non cambieremo il compilatore o gli standard del linguaggio. Suggerisco di cambiare l'approccio ai modelli stessi. Se non possiamo creare modelli in fase di compilazione, non significa che non siamo autorizzati a scrivere il codice macchina. Suggerisco di spostare l'uso dei modelli dalla parte di generazione del codice binario alla parte in cui viene scritto il codice di testo. Chiamiamo questo approccio "pseudo-modelli".


Gli pseudo-modelli

Uno pseudo-modello ha i suoi vantaggi e svantaggi rispetto a un modello C++. Gli svantaggi includono ulteriori manipolazioni con lo spostamento dei file. I vantaggi includono possibilità più flessibili di quelle determinate dagli standard del linguaggio. Passiamo dalle parole ai fatti.

Per utilizzare gli pseudo-modelli, abbiamo bisogno di un analogo del preprocessore. Per questo scopo useremo lo script 'Modelli'. Ecco i requisiti generali per lo script: deve leggere un file specificato (mantenendo la struttura dei dati), trovare un modello e sostituirlo con i tipi specificati.

Qui devo fare un'osservazione. Poiché utilizzeremo il meccanismo dell'override invece dei modelli, il codice verrà riscritto tante volte quanti sono i tipi che devono essere sovrascritti. In altre parole, la sostituzione verrà eseguita nell'intero codice fornito per l'analisi. Quindi, il codice verrà riscritto più volte dallo script, creando ogni volta una nuova sostituzione. Quindi metteremo in pratica lo slogan "lavoro manuale eseguito dalle macchine".


Sviluppo del codice dello script

Determiniamo le variabili di input richieste:

  1. Nome di un file da elaborare.
  2. Una variabile per memorizzare il tipo di dati da sovrascrivere.
  3. Nome di un modello che verrà utilizzato al posto dei tipi reali di dati.
input string folder="Example templat";//name of file for processing

input string type="long;double;datetime;string"
                 ;//names of custom types, separator ";"
string TEMPLAT="_XXX_";// template name

Per fare in modo che lo script moltiplichi solo una parte del codice, imposta i nomi dei marcatori. Il marcatore di apertura ha lo scopo di indicare l'inizio della parte da elaborare, mentre il marcatore di chiusura ne indica la fine.

Durante l'utilizzo dello script, ho affrontato il problema della lettura dei marcatori.

Durante l'analisi ho scoperto che quando si formatta un documento su MetaEditor, spesso alle righe dei commenti viene aggiunto uno spazio o una tabulazione (a seconda della situazione). Il problema è stato risolto eliminando gli spazi prima e dopo un simbolo significativo durante la determinazione dei marcatori. Questa funzione è realizzata come automatica nello script, ma c'è un'osservazione da fare.

Il nome di un marcatore non deve iniziare o terminare con uno spazio.

Non è obbligatorio avere un marcatore di chiusura; se è assente, il codice verrà elaborato fino alla fine del file. Ma ce ne deve essere uno di apertura. Poiché i nomi dei marcatori sono costanti, utilizzo la direttiva del preprocessore #define invece delle variabili.

#define startread "//start point"
#define endread "//end point"

Per formare un array di tipi, ho creato la funzione void ParserInputType(int i,string &type_d[],string text), che compila l'array type_dates[] con i valori utilizzando la variabile 'type'.    

Una volta che lo script riceve il nome di un file e dei marcatori, inizia a leggere il file. Per salvare la formattazione del documento, lo script legge le informazioni riga per riga, salvando le righe trovate nell'array.

Ovviamente puoi svuotare tutto in una variabile; ma in questo caso perderai la sillabazione e il testo si trasformerà in una riga infinita. Ecco perché la funzione di lettura del file utilizza l'array di stringhe che cambia la sua dimensione ad ogni iterazione per ottenere una nuova stringa.

//+------------------------------------------------------------------+
//| downloading file                                                 |
//+------------------------------------------------------------------+
void ReadFile()
  {
   string subfolder="Templates";
   int han=FileOpen(subfolder+"\\"+folder+".mqh",FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI,"\r"); 
   if(han!=INVALID_HANDLE)
     {
      string temp="";
      //--- scrolling file to the starting point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

      string text=""; int size;
      //--- reading the file to the array until a break point or the end of the file
      while(!FileIsEnding(han))
        {
         temp=text=FileReadString(han);
         // deleting symbols of tabulation to check the end
         StringTrimLeft(temp);StringTrimRight(temp);
         if(endread==temp)break;
         // flushing data to the array
         if(text!="")
           {
            size=ArraySize(fdates);
            ArrayResize(fdates,size+1);
            fdates[size]=text;
           }
        }
      FileClose(han);
     }
   else
     {
      Print("File open failed"+subfolder+"\\"+folder+".mqh, error",GetLastError());
      flagnew=true;
     }
  }

Per comodità d'uso, il file viene aperto in modalità FILE_SHARE_READ. Dà la possibilità di avviare lo script senza chiudere il file modificato. L'estensione del file è specificata come 'mqh'. Pertanto, lo script legge direttamente il testo del codice memorizzato nel file di inclusione. Il problema è che un file con estensione 'mqh' è, in realtà, un file di testo; puoi assicurartene semplicemente rinominando il file come 'txt' e aprendo il file 'mqh' usando qualsiasi editor di testo. 

Alla fine della lettura, la lunghezza dell'array è uguale al numero di righe tra i marcatori di inizio e fine.

Il nome del file aperto deve avere l'estensione "tempplat", altrimenti il file iniziale verrà sovrascritto e tutte le informazioni andranno perse.

Passiamo ora all'analisi delle informazioni. La funzione che analizza e sostituisce le informazioni viene chiamata dalla funzione di scrittura su un file void WriteFile(int count). I commenti vengono forniti all'interno della funzione.

void WriteFile(int count)
  {
   ...
   if(han!=INVALID_HANDLE)
     {
      if(flagnew)// if the file cannot be read
        {
         ...
        }
      else
        {// if the file exists
         ArrayResize(tempfdates,count);
         int count_type=ArraySize(type_dates);
         //--- the cycle rewrites the contents of the file for each type of the type_dates template
         for(int j=0;j<count_type;j++)
           {
            for(int i=0;i<count;i++) // copy data into the temporary array
               tempfdates[i]=fdates[i];
            for(int i=0;i<count;i++) // replace templates with types
               Replace(tempfdates,i,j);

            for(int i=0;i<count;i++)
               FileWrite(han,tempfdates[i]); // flushing array in the file
           }
        }
     ...
  }

Poiché i dati vengono sostituiti al loro posto e l'array viene modificato dopo la trasformazione, lavoreremo con una sua copia. Qui impostiamo la dimensione dell'array tempfdates[] utilizzato per l'archiviazione temporanea dei dati e lo riempiamo secondo l'esempio fdates[].

Quindi viene eseguita la sostituzione dei modelli utilizzando la funzione Replace(). I parametri della funzione sono: array da elaborare (dove viene eseguita la sostituzione del template), il contatore di righe i (per spostarsi all'interno dell'array), e il contatore di tipi j (per navigare nell'array di tipi).

Poiché abbiamo due cicli annidati, il codice sorgente viene stampato tante volte quanti sono i tipi specificati.

//+------------------------------------------------------------------+
//| replacing templates with types                                   |
//+------------------------------------------------------------------+
void Replace(string &temp_m[],int i,int j)
  {
   if(i>=ArraySize(temp_m))return;
   if(j<ArraySize(type_dates))
      StringReplac(temp_m[i],TEMPLAT,type_dates[j]);// replacing  templat with types   
  }

La funzione Replace() contiene dei controlli (per evitare di chiamare un indice inesistente di un array) e chiama la funzione nidificata StringReplac(). C'è un motivo per cui il nome della funzione è simile alla funzione standard StringReplace: hanno anche lo stesso numero di parametri.

Quindi, aggiungendo una singola lettera "e" possiamo cambiare l'intera logica di sostituzione. La funzione standard prende il valore dell'esempio 'find' e lo sostituisce con la stringa 'replacement' specificata. E la mia funzione non solo sostituisce, ma analizza se ci sono simboli prima di 'find' (cioè controlla se 'find' è una parte di una parola); e se ci sono, sostituisce 'find' con 'replacement', ma in maiuscolo. Altrimenti la sostituzione viene eseguita così com'è. Pertanto, oltre all'impostazione dei tipi, è possibile utilizzarli nei nomi dei dati sovrascritti.


Innovazioni

Ora lasciatemi parlare delle innovazioni che sono state aggiunte durante l'utilizzo. Ho già detto che c'erano dei problemi di lettura dei marcatori durante l'utilizzo dello script.

Il problema è risolto dal seguente codice all'interno della funzione void ReadFile():

      string temp="";
      //--- scrolling the file to the start point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

Il ciclo stesso era implementato nella versione precedente, ma il taglio dei simboli di tabulazione utilizzando le funzioni StringTrimLeft() e StringTrimRight() appariva solo nella versione migliorata.

Inoltre, le innovazioni includono il taglio dell'estensione "tempplat" dal nome del file di output, in modo che il file di output sia pronto per essere utilizzato. Viene implementato utilizzando la funzione di eliminazione di un esempio specificato da una stringa specificata.

Codice della funzione di cancellazione:

//+------------------------------------------------------------------+
//| Deleting the 'find' template from the 'text' string              |
//+------------------------------------------------------------------+
string StringDel(string text,const string find)
  {
   string str=text;
   StringReplace(str,find,"");
   return(str);
  }

Il codice che esegue il taglio di un nome file si trova nella funzione void WriteFile(int count):

   string newfolder;
   if(flagnew)newfolder=folder;// if it is the first start, create an empty file of pre-template
   else newfolder=StringDel(folder," templat");// or create the output file according to the template

Oltre ad esso, viene introdotta la modalità di preparazione di un pre-modello. Se il file richiesto non esiste nella directory Files/Templates, verrà formato come file pre-modello.

Esempio:

//#define _XXX_ long
 
//this is the start point
 _XXX_
//this is the end point

Il codice che crea quelle righe si trova nella funzione void WriteFile(int count):

      if(flagnew)// if the file couldn't be read
        {// fill the template file with the pre-template
         FileWrite(han,"#define "+TEMPLAT+" "+type_dates[0]);
         FileWrite(han," ");
         FileWrite(han,startread);
         FileWrite(han," "+TEMPLAT);
         FileWrite(han,endread);
         Print("Creating pre-template "+subfolder+"\\"+folder+".mqh");
        }

L'esecuzione del codice è protetta dalla variabile globale flagnew, la quale assume il valore 'true' se si è verificato un errore di lettura del file.

Durante l'utilizzo dello script, ho aggiunto un ulteriore modello. Il processo di connessione del secondo modello è lo stesso. Le funzioni che richiedono modifiche in esse vengono posizionate più vicino alla funzione OnStart() per la connessione di un modello aggiuntivo. Un percorso per il collegamento di nuovi modelli è stato battuto. Pertanto, abbiamo la possibilità di collegare tutti i modelli di cui abbiamo bisogno. Ora controlliamo il funzionamento.


Controllo del funzionamento

Prima di tutto, avviamo lo script specificando tutti i parametri richiesti. Nella finestra che appare, specifica il nome del file "Example templat".

Compila i campi dei tipi di dati personalizzati utilizzando il separatore ';'.

Finestra di avvio dello script

Non appena si preme il pulsante "OK", viene creata la directory Templates; contiene il file di pre-modello "Example templat.mqh".

Questo evento viene visualizzato nel journal con il messaggio:

Messaggi nel journal

Modifichiamo il pre-modello e avviamo nuovamente lo script. Questa volta il file esiste già nella directory Templates (oltre che nella directory stessa), ecco perché il messaggio sull'errore di apertura del file non verrà visualizzato. La sostituzione verrà eseguita secondo il modello specificato:

//this_is_the_start_point
 _XXX_ Value_XXX_;
//this_is_the_end_point

Apri nuovamente il file creato "Example.mqh".

 long ValueLONG;
 double ValueDOUBLE;
 datetime ValueDATETIME;
 string ValueSTRING;

Come puoi vedere, 4 righe provengono da una riga in base al numero di tipi che abbiamo passato come parametro. Ora scrivi la due righe seguenti nel file modello:

//this_is_the_start_point
 _XXX_ Value_XXX_;
 _XXX_ Type_XXX_;
//this_is_the_end_point

Il risultato dimostra in modo chiaro la logica dell'operazione di script.

Prima di tutto, l'intero codice viene riscritto con un tipo di dati, quindi viene eseguita l'elaborazione di un altro tipo. Questo viene fatto fino a quando tutti i tipi non vengono elaborati.

 long ValueLONG;
 long TypeLONG;
 double ValueDOUBLE;
 double TypeDOUBLE;
 datetime ValueDATETIME;
 datetime TypeDATETIME;
 string ValueSTRING;
 string TypeSTRING;

Ora includi il secondo modello nel testo dell'esempio.

//this_is_the_start_point
 _XXX_ Value_XXX_(_xxx_ ind){return((_XXX_)ind);};
 _XXX_ Type_XXX_(_xxx_ ind){return((_XXX_)ind);};

 //this_is_the_end_button

Risultato:

 long ValueLONG(int ind){return((long)ind);};
 long TypeLONG(int ind){return((long)ind);};
 
 double ValueDOUBLE(float ind){return((double)ind);};
 double TypeDOUBLE(float ind){return((double)ind);};
 
 datetime ValueDATETIME(int ind){return((datetime)ind);};
 datetime TypeDATETIME(int ind){return((datetime)ind);};
 
 string ValueSTRING(string ind){return((string)ind);};
 string TypeSTRING(string ind){return((string)ind);};

Nell'ultimo esempio, ho intenzionalmente inserito uno spazio dopo l'ultima riga. Quello spazio mostra dove lo script termina l'elaborazione di un tipo e inizia a elaborarne un altro. Per quanto riguarda il secondo modello, possiamo notare che l'elaborazione dei tipi viene eseguita in modo simile al primo modello. Se non viene trovato un tipo corrispondente per un tipo del primo modello, non viene stampato nulla.

Ora voglio chiarire la questione del debug del codice. Gli esempi forniti sono piuttosto semplici per il debug. Durante la programmazione, potrebbe essere necessario eseguire il debug di una parte piuttosto grande del codice e moltiplicarla non appena completata. Per farlo, c'è una riga commentata riservata nel pre-modello: "//#define _XXX_ long".

Se rimuovi i commenti, il nostro modello diventerà di tipo reale. In altre parole, diremo al compilatore come deve essere interpretato il template.

Sfortunatamente, non possiamo eseguire il debug di tutti i tipi in questo modo. Ma possiamo eseguire il debug di un tipo e quindi modificare il tipo del modello in 'define'; quindi possiamo eseguire il debug di tutti i tipi uno per uno. Ovviamente, per il debug dobbiamo spostare il file nella directory del file chiamato o nella directory Include. Questo è l'inconveniente del debug di cui ho parlato prima, quando ho menzionato gli svantaggi degli pseudo-modelli.


Conclusione

In conclusione, voglio dire che, sebbene l'idea di utilizzare gli pseudo-modelli sia interessante e piuttosto produttiva, è solo un'idea che si sta iniziando a implementare. Sebbene il codice descritto sopra funzioni e mi abbia risparmiato molte ore di scrittura, ci sono molte domande ancora aperte. Prima di tutto, c’è la questione dello sviluppo degli standard.

Il mio script implementa la sostituzione del blocco dei modelli. Ma questo approccio non è obbligatorio. È possibile creare un analizzatore più complesso che interpreta determinate regole. Ma questo è solo l'inizio. Spero in una grande dibattito. Il pensiero prospera con il conflitto. Buona fortuna!


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

File allegati |
templates.mq5 (9.91 KB)
Il ruolo delle distribuzioni statistiche nel lavoro del trader Il ruolo delle distribuzioni statistiche nel lavoro del trader
Questo articolo è una continuazione logica del mio articolo Statistical Probability Distributions in MQL5 che espone le classi per lavorare con alcune distribuzioni statistiche teoriche. Ora che abbiamo una base teorica, suggerisco di procedere direttamente a set di dati reali e provare a fare un uso informativo di questa base.
Utilizzo degli indicatori MetaTrader 5 con il framework di apprendimento automatico ENCOG per la previsione delle serie temporali Utilizzo degli indicatori MetaTrader 5 con il framework di apprendimento automatico ENCOG per la previsione delle serie temporali
Questo articolo presenta la connessione di MetaTrader 5 a ENCOG - Advanced Neural Network e Machine Learning Framework. Contiene la descrizione e l'implementazione di un semplice indicatore di rete neurale basato su indicatori tecnici standard e un Expert Advisor basato su un indicatore neurale. Il codice sorgente, i binari compilati, le DLL e una rete addestrata esemplare sono allegati all'articolo.
Filtraggio dei segnali basati su dati statistici di correlazione dei prezzi Filtraggio dei segnali basati su dati statistici di correlazione dei prezzi
Esiste una correlazione tra il comportamento dei prezzi passati e le sue tendenze future? Perché il prezzo ripete oggi il carattere del suo movimento del giorno precedente? Le statistiche possono essere utilizzate per prevedere le dinamiche dei prezzi? C'è una risposta ed è positiva. Se hai qualche dubbio, allora questo articolo fa al caso tuo. Ti spiegherò come creare un filtro funzionante per un sistema di trading con MQL5, rivelando un modello interessante nelle variazioni di prezzo.
Modello di regressione universale per la previsione dei prezzi di mercato Modello di regressione universale per la previsione dei prezzi di mercato
Il prezzo di mercato è formato da un equilibrio stabile tra domanda e offerta che, a sua volta, dipende da una varietà di fattori economici, politici e psicologici. Le differenze di natura e le cause di influenza di questi fattori rendono difficile considerare direttamente tutti i componenti. Questo articolo espone un tentativo di prevedere il prezzo di mercato sulla base di un modello di regressione elaborato.