Passaggio a MQL5 Algo Forge (parte 2): Lavorare con Più Repository

21
126

Introduzione

Nel primo articolo, abbiamo iniziato la transizione dal sistema di archiviazione MQL5 integrato in MetaEditor, basato su SVN, a una soluzione più flessibile e moderna, basata sul sistema di controllo delle versioni Git: MQL5 Algo Forge. La ragione principale di questo passo è stata la necessità di sfruttare appieno i rami del repository mentre si lavora su più progetti o su diverse funzionalità all'interno di un singolo progetto.

La transizione è iniziata con la creazione di un nuovo repository in MQL5 Algo Forge e la configurazione di un ambiente di sviluppo locale utilizzando Visual Studio Code, insieme alle necessarie estensioni MQL5 e Git e agli strumenti di supporto. Abbiamo quindi aggiunto un file .gitignore al repository per escludere i file standard e temporanei dal controllo della versione. Tutti i progetti esistenti sono stati caricati in un ramo di archivio dedicato, designato come archivio di tutto il codice scritto in precedenza. Il ramo main è stato lasciato vuoto e preparato per l'organizzazione di nuovi rami di progetto. In questo modo, abbiamo gettato le basi per la distribuzione di diversi codici di progetto in rami separati del repository.

Tuttavia, dalla pubblicazione del primo articolo, MetaEditor ha ampliato in modo significativo il suo supporto per il nuovo sistema di repository. Questi cambiamenti ci spingono a riconsiderare l'approccio precedentemente delineato. Pertanto, in questo articolo, ci discosteremo leggermente dal piano originale ed esploreremo come creare un progetto pubblico che integri altri progetti pubblici come componenti. Il progetto su cui ci concentreremo è lo sviluppo di un Expert Advisor multi valuta. Sono già stati pubblicati diversi articoli che descrivono l'approccio allo sviluppo e alle modifiche del codice per questo progetto. Ora sfrutteremo appieno il controllo di versione Git per organizzare e semplificare il processo di sviluppo.


Tracciare il percorso

È difficile da credere, ma al momento dell'articolo precedente MetaEditor non includeva ancora i comandi del menu "Git" o del menu contestuale del file per lavorare con i repository di MQL5 Algo Forge. Di conseguenza, è stato necessario un notevole sforzo per configurare i flussi di lavoro utilizzando strumenti esterni come Visual Studio Code. Poiché non sapevamo come MetaEditor avrebbe eventualmente implementato il supporto per i repository, abbiamo dovuto utilizzare gli strumenti disponibili in quel momento.

Da allora, le nuove versioni rilasciate di MetaEditor hanno introdotto il supporto integrato per MQL5 Algo Forge. MetaQuotes ha anche pubblicato un nuovo articolo, "Introduzione a MQL5 Algo Forge", che spiega le basi e dimostra le caratteristiche principali. Tuttavia, lo sviluppo più importante, a nostro avviso, è l'implementazione dei Progetti Condivisi in MetaEditor.

Perché è significativo? In precedenza, sapevamo che la cartella MQL5 avrebbe agito come un repository ospitato sui server Git di MQL5 Algo Forge. A quanto pare, questo repository avrebbe il nome fisso mql5. Ciò significa che ogni utente avrà un repository chiamato mql5 in Algo Forge. Questo repository verrebbe quindi clonato nella cartella MQL5 dopo aver installato un nuovo terminale, aver effettuato l'accesso alla Community e aver collegato il repository. Allo stesso tempo, MQL5 Algo Forge ha sempre permesso la creazione di repository aggiuntivi. Più precisamente, non aggiuntivi, ma separati, non legati al repository mql5. Naturalmente, questo ha sollevato una domanda: come MetaEditor gestisce questi altri repository? 

Gli utenti potrebbero selezionare quale repository utilizzare in ogni installazione del terminale? Oppure no? Sarebbe supportato solo il repository mql5, con gli utenti costretti a separare il loro lavoro in rami per progetti differenti? Inizialmente ci eravamo preparati a questo scenario peggiore. La gestione di più progetti attraverso rami in un unico repository non è particolarmente comoda. Fortunatamente, le nostre preoccupazioni si sono rivelate infondate.

MetaQuotes ha introdotto una soluzione più elegante che risolve efficacemente due problemi contemporaneamente. Da un lato, abbiamo il repository principale chiamato mql5. Questo funziona bene per chi è già abituato a MQL5 Storage. Ora possono continuare a usare il controllo di versione senza preoccuparsi di quale sia il sistema di controllo di versione sottostante.

D'altra parte, tutti gli altri repository degli utenti sono ora disponibili come cartelle all'interno di Progetti Condivisi. Questo fornisce una nuova cartella principale standard, accanto a quelle esistenti (come MQL5, MQL5/Experts, MQL5/Include, ecc.), destinata alla memorizzazione del codice proveniente dai repository aggiuntivi dell'utente.

Consideriamo un esempio. Supponiamo di avere due repository separati in MQL5 Algo Forge, nessuno dei quali è il mql5 predefinito. Il primo repository (Adwizard) contiene solo codice di libreria, cioè solo file include *.mqh, senza alcun file *.mq5 che potrebbe essere compilato in un Expert Advisor o in un indicatore. Il secondo repository (SimpleCandles) contiene file *.mq5 che utilizzano i file include del primo repository. Per semplicità, ci riferiremo al primo repository come repository delle librerie e al secondo come repository del progetto.

Il nostro obiettivo è determinare come utilizzare il codice del repository delle librerie mentre si sviluppa all'interno del repository del progetto. Questo scenario potrebbe diventare sempre più comune se, ad esempio, il codice condiviso nel Code Base di mql5.com venisse rispecchiato dai suoi autori anche in MQL5 Algo Forge come repository pubblico. In questi casi, il collegamento di uno o più repository come librerie esterne ad un progetto potrebbe essere gestito nello stesso modo che stiamo per esplorare in questo articolo.


Iniziamo

Vediamo innanzitutto la situazione dal punto di vista dello sviluppatore che possiede entrambi i repository. Ciò significa che possiamo apportare liberamente modifiche al codice in entrambi i due repository senza attendere la revisione e l'approvazione tramite il meccanismo Pull Request. Si inizia creando una cartella pulita del terminale e copiando due file da qualsiasi copia di MetaTrader 5 precedentemente installata:

Forge MQL5


Per evitare di cercare la cartella di lavoro del terminale all'interno del file system, si consiglia di eseguire il terminale in modalità Portatile. In Windows, un modo per farlo è creare un collegamento al file eseguibile del terminale e aggiungere il flag /portable al campo target nelle proprietà del collegamento. 


Dopo aver lanciato il terminale, aprite un nuovo conto demo per sicurezza, aggiornate all'ultima versione, accedete alla Community e poi lanciate MetaEditor premendo F4. Collegare MQL5 Algo Forge, se non si è già collegato automaticamente.

Nel Navigatore, ora vediamo la cartella "Progetti condivisi", che elenca i repository creati in precedenza tramite l'interfaccia web. Tuttavia, se apriamo questa cartella in Explorer, è ancora vuota. Ciò significa che i file dell’attuale repository non sono ancora stati scaricati sul nostro computer.

Portatile

Per clonarli, fare clic con il tasto destro del mouse su ciascuno dei repository necessari e selezionare "Git Clone" dal menu contestuale. Il registro conferma l'avvenuta clonazione sia di Adwizard che di Simple Candles. Le cartelle con i repository clonati appaiono ora in Explorer:

A questo punto, il codice per entrambi i progetti è disponibile localmente e pronto all'uso.


Primo Problema

Apriamo SimpleCandles.mq5 e proviamo a compilarlo:

Come previsto, si verificano errori di compilazione. Cerchiamo di capire le cause. Questi non sono critici, poiché sappiamo che il codice è stato compilato correttamente in precedenza. L'unica cosa che è cambiata è il posizionamento relativo ai file della libreria e del progetto. I primi due errori fondamentali derivano dal fatto che il compilatore non riesce a trovare i file della libreria dove se li aspetta. Nella Parte 28 abbiamo deciso di utilizzare la seguente struttura di cartelle per archiviare le parti della libreria e dei progetti:

Cioè, memorizziamo il repository della libreria in una sottocartella del repository del progetto. In questo modo si è ottenuta una struttura prevedibile per la localizzazione dei file di libreria. Questa volta, però, cambieremo le cose. Invece di usare una sottocartella Include obbligatoria all'interno del progetto, useremo la cartella MQL5/Shared Projects. Idealmente, questa cartella rimarrà stabile e continuerà a svolgere la stessa funzione nelle future versioni di MetaEditor.

Per risolvere il problema, aggiorneremo le direttive #include in due file. Ma prima di apportare modifiche al codice, seguiamo le buone pratiche di sviluppo e creiamo un ramo separato per questo compito isolato. Una volta testate le correzioni, possiamo unire il ramo al ramo di sviluppo principale.

Vediamo quali rami abbiamo già nel repository del progetto. Ciò può avvenire in diversi modi:

  • Tramite il menu contestuale della cartella del repository in MetaEditor: 

  • Attraverso l'interfaccia web della pagina del repository, branches:

  • Utilizzando uno strumento Git esterno come Visual Studio Code. Accanto al nome del repository vediamo main, che è il nome del ramo corrente. Facendo clic su di esso si ottiene un elenco dei rami disponibili (e le voci di menu per la creazione di nuovi rami):

Attualmente, il repository ha quattro rami:

  • main - il ramo primario. Viene creato con il repository. Nel caso più semplice, tutto il lavoro può essere fatto qui senza creare rami aggiuntivi. Nei casi più complessi, questo ramo viene utilizzato per memorizzare gli stati dei file che forniscono le versioni stabili del codice. Tutte le modifiche in corso che non sono ancora state completate e testate vengono fatte in altri rami.
  • develop - il ramo di sviluppo. In casi semplici, può essere usato come unico ramo per aggiungere modifiche e implementare nuove funzionalità. Questa opzione è sufficiente se le nuove funzioni vengono implementate in sequenza. In altre parole, non iniziamo a implementare nuove funzionalità finché il progetto non è in uno stato completamente funzionale e stabile dopo l'aggiunta delle funzionalità precedenti. Prima di iniziare a lavorare su una nuova funzionalità, i rami vengono uniti: le modifiche apportate nel ramo develop vengono unite al ramo main. Se si sviluppano più funzioni contemporaneamente, diventa scomodo lavorare in un solo ramo di sviluppo. In questi casi, si possono creare rami di funzionalità aggiuntive.
  • Esempi di questi rami sono l'articolo-17608-close-manager e l'articolo-17607. Il primo è un ramo di funzionalità per la logica di chiusura delle posizioni basata su soglie di profitto/perdita. Questo ramo è già unito a develop e develop è poi unito a main. Il secondo ramo di funzionalità viene utilizzato per migliorare l'ottimizzazione automatica. È ancora in corso, non è ancora stato unito a develop o main.

È importante sottolineare che Git non applica alcuna regola specifica sull'uso dei rami. Possiamo quindi scegliere l'opzione che più ci conviene. Ci sono dei flussi di lavoro che alcuni sviluppatori hanno trovato utili e condiviso con altri. Ecco come appaiono le "Best Practices". Siete liberi di adottare o adattare il modello di ramificazione più adatto al vostro progetto. A titolo di esempio, date un'occhiata ad uno dei principi di ramificazione proposti descritti in questo articolo.

Ora torniamo al nostro repository.

Un dettaglio che può sollevare dubbi è il prefisso origin/ (o refs/remotes/origin/ come mostrato in MetaEditor). Questo prefisso indica semplicemente che il ramo esiste nel repository remoto, non solo localmente. In genere, manteniamo sincronizzati i rami locali e remote. In MetaEditor, l'esecuzione di un comando Commit attiva automaticamente un comando Push e quindi il commit viene inviato al repository remoto.

Se i commit sono fatti al di fuori di MetaEditor, è possibile eseguire il commit localmente senza eseguire il push. In questi casi, i rami locali e remoti con lo stesso nome possono essere differenti. Il prefisso origin aiuta a distinguerli. Con questo prefisso si intende un ramo in un repository remoto; altrimenti, si tratta di un ramo locale.


Creazione di un Nuovo Ramo

Poiché le modifiche previste assicurano solo la corretta compilazione del codice dopo la modifica della parte del posizionamento della libreria, baseremo un nuovo ramo generato da develop. Passiamo prima a origin/develop, dopodiché appare nell'elenco come ramo develop locale.

Quindi creiamo un nuovo ramo (eseguendo il comando Nuovo) e inseriamo il nome desiderato. Secondo la nostra convenzione, i rami relativi agli articoli iniziano con article- più l'identificativo univoco dell'articolo. Questo può essere opzionalmente seguito da un breve suffisso che descrive l'argomento dell'articolo. Per questo motivo il nuovo ramo si chiama "article-17698-forge2".

Possiamo creare un ramo anche in altri modi: utilizzando l'interfaccia web, l'interfaccia di Visual Studio Code o l'interfaccia della riga di comando. Dalla riga di comando nella cartella principale del repository, possiamo eseguire:

git checkout -b article-17698-forge2 develop

Questo dice a Git di passare (checkout) a un nuovo ramo (-b) chiamato article-17698-forge2, basato sul ramo develop.

Se un ramo viene creato al di fuori dell'interfaccia web, esisterà solo sulla nostra macchina locale fino al primo push al repository remoto. È vero anche il contrario: se un ramo viene creato nel repository remoto attraverso l'interfaccia web, non apparirà sul nostro computer locale fino al primo pull dal repository remoto.

È possibile inviare modifiche in questo modo:

o così:

Il comando della console per l'operazione Push, quando prevede la creazione di un nuovo ramo, deve contenere parametri aggiuntivi che confermino che si vuole veramente che il ramo venga creato nel repository remoto:

git push --set-upstream origin article-17698-forge2

In seguito, il ramo esiste sia nella copia locale del repository sia nel repository remoto ospitato in MQL5 Algo Forge. A questo punto, possiamo iniziare a fare modifiche senza temere di interrompere la funzionalità di altri rami.


Apportare Modifiche

Le modifiche necessarie sono molto semplici. Nel file SimpleCandles.mq5, aggiorniamo la riga che include un file della libreria Adwizard. Poiché le cartelle principali dei repository Simple Candles e Adwizard si trovano ora allo stesso livello all'interno della cartella Shared Projects, il percorso di Expert.mqh deve prima spostarsi di un livello in alto (../) prima di scendere nelle sottocartelle del repository della libreria:

#include "Include/Adwizard/Experts/Expert.mqh" #include "../Adwizard/Experts/Expert.mqh"

Una modifica analoga è necessaria nel file Strategies/SimpleCandlesStrategy.mqh:

#include "../Include/Adwizard/Virtual/VirtualStrategy.mqh" #include "../../Adwizard/Virtual/VirtualStrategy.mqh"

Dopo queste modifiche, SimpleCandles.mq5 viene nuovamente compilato con successo. Ora possiamo fare il commit delle modifiche al repository:

Come già detto, quando si esegue il commit attraverso l'interfaccia di MetaEditor, il comando Push viene eseguito automaticamente, inviando le modifiche al repository remoto di MQL5 Algo Forge.

Quando si lavora con i comandi della console, si può ottenere lo stesso risultato come segue. Se, oltre a modificare i file esistenti, ne abbiamo creati di nuovi, dobbiamo prima aggiungerli all'indice del repository:

git add .

Questo comando aggiunge tutti i nuovi file trovati nella cartella del repository. Per aggiungere solo file specifici, sostituite il punto (.) con il loro nome. Dopodiché, si esegue il commit delle modifiche con un commento specificato e le si invia al repository remoto:

git commit -m "Correggere i percorsi relativi per includere i file da Adwizard".
git push

A questo punto, le modifiche nel ramo article-17698-forge2 sono complete. Può essere unito al ramo develop e poi chiuso.


Secondo Problema

Qui ci imbattiamo in una spiacevole sorpresa. MetaEditor attualmente non dispone di strumenti per la fusione dei rami. In altre parole, ora possiamo creare nuovi rami, ma non possiamo trasferire le modifiche da un ramo all'altro! Si spera che questa funzionalità venga aggiunta nel prossimo futuro. Per il momento, dobbiamo ancora una volta ricorrere a strumenti alternativi per eseguire le operazioni sul repository.

Esistono due modi principali per unire i rami. Il primo consiste nell'utilizzare l'interfaccia di unione di Visual Studio Code o i comandi standard della console. Nel nostro caso, si possono usare i seguenti comandi:

git checkout develop
git pull
git merge --no-ff article-17698-forge2

Per prima cosa, passiamo al ramo develop. Poi, per precauzione, lo aggiorniamo (nel caso in cui le modifiche fatte non abbiano ancora raggiunto la nostra macchina locale). Infine, l'ultimo comando esegue la fusione vera e propria. I conflitti di unione sono possibili, ma nel nostro scenario sono improbabili, dato che stiamo ancora considerando il caso di un singolo sviluppatore che lavora sul progetto. Anche quando si lavora da più sedi, finché si aggiornano regolarmente i repository allo stato più recente, non dovrebbero sorgere conflitti.

Tuttavia, non soffermiamoci sulle sfumature di questo metodo. Analizzeremo invece più da vicino il secondo approccio. Qui utilizziamo l'interfaccia web di MQL5 Algo Forge.


Usare Pull Request per l’Unione (Merging)

Come altre piattaforme basate su Git (come GitHub, GitLab o Bitbucket), anche MQL5 Algo Forge include un meccanismo chiamato Pull Request (PR).

Una Pull Request consente a uno sviluppatore di proporre le modifiche apportate in un ramo da unire in un repository. In altre parole, la creazione di una PR è un modo per informare il proprietario del repository e gli altri collaboratori: "Ho completato il lavoro nel mio ramo, per favore rivedete e unite queste modifiche nel ramo principale (main/master/develop)".

Una PR non è una caratteristica di Git stesso, ma un livello aggiuntivo costruito sopra di esso. Organizza il processo di revisione e discussione del codice prima che le modifiche vengano unite al ramo principale.

Le Pull Request risolvono anche diversi altri compiti critici dello sviluppo moderno: l'integrazione continua (CI) con test automatizzati, il controllo di qualità da parte di altri sviluppatori e la documentazione delle modifiche sotto forma di commenti PR che spiegano perché sono state apportate determinate modifiche. Tuttavia, queste pratiche sono più rilevanti per i progetti basati su team, mentre i progetti MQL5 sono solitamente individuali. In ogni caso, il flusso di lavoro potrebbe diventare più importante con l'emergere di progetti collaborativi in futuro.

Detto questo, abbiamo già replicato l'inizio di un tipico flusso di lavoro per l'aggiunta di nuove funzionalità o correzioni utilizzando le PR:

  1. Ultime Modifiche Pull. Prima di iniziare il lavoro, abbiamo aggiornato il ramo locale develop.

  2. Creare un nuovo ramo per il compito. Dal ramo develop aggiornato, abbiamo creato un ramo con il nome evidente article-17698-forge2.

  3. Apportare le modifiche nel nuovo ramo. Abbiamo modificato e testato diversi file, poi abbiamo effettuato il commit delle modifiche.

Le fasi successive sono le seguenti.
  1. Creare una Pull Request. Nell'interfaccia web di MQL5 Algo Forge, navigare nella scheda Pull Request e fare clic sul grande pulsante rosso "New pull request".

Si apre la pagina di selezione del ramo. In questa fase, il PR non è ancora stato creato; prima dobbiamo definire dove verranno unite le modifiche. Una volta selezionati i rami, possiamo esaminare l'elenco delle modifiche. Quindi, fare di nuovo clic su "New pull request".

Si apre una nuova pagina in cui è possibile fornire una descrizione dettagliata delle modifiche. Qui possiamo anche assegnare i revisori. Per impostazione predefinita, la richiesta è diretta a noi stessi, che è esattamente ciò di cui abbiamo bisogno in questo caso.

  1. Revisione e discussione. Poiché stiamo lavorando da soli, possiamo saltare questo passaggio. Di norma, questa fase prevede:

    • revisori che esaminano il codice e lasciano commenti (generali o legati a righe specifiche),

    • l'autore del PR che risponde ai commenti e apporta correzioni nello stesso ramo,

    • e tutti i nuovi push vengono aggiunti automaticamente al PR esistente.

  1. Unione (merging). Dopo l'approvazione dei revisori (se presenti) e l'approvazione del CI (se configurato), il PR può essere unito. In genere, esistono diverse opzioni di unione:

    • Merge commit: Crea un commit di unione separato, conservando la cronologia del ramo.

    • Squash e merge: Combina tutti i commit delle PR in un unico commit aggiunto al ramo di destinazione. Utile per evitare di ingombrare la cronologia con commit minori come "correzioni di errori di battitura".

    • Rebase and merge: Riapplica i commit PR al ramo di destinazione (nel nostro caso è develop). In questo modo si ottiene una cronologia pulita e lineare.

Per i nostri scopi, sceglieremo la prima opzione, poiché vogliamo conservare l'intera cronologia dei commit. Quindi, fare clic su "Create merge commit".

    Ora arriva la pagina finale delle operazioni di Pull Request. Qui si seleziona l'opzione "Delete branch ..." per chiudere il ramo di sviluppo temporaneo. La cronologia dei commit rifletterà comunque l'esistenza del ramo. Ma tenerlo aperto non serve a nulla, visto che abbiamo raggiunto il nostro obiettivo. Per le modifiche future che risolvono altri compiti, creeremo nuovi rami. In questo modo, l'elenco dei rami del repository fornisce sempre un'istantanea chiara del lavoro parallelo in corso.

    Lasciare invariate le altre impostazioni e fare clic su "Create merge commit".

    Il processo è ora completo: il ramo article-17698-forge2 è stato unito a develop e cancellato:

    In generale, l'uso di Pull Request anche nel proprio repository è una pratica buona e consigliata, anche per i progetti in solitaria. Prima dell'unione, è possibile rivedere visivamente tutte le modifiche, spesso individuando cose che non sono state notate durante i commit: commenti non necessari, file vaganti o modifiche non ottimali. In sostanza, si tratta di una forma di autodisciplina. Inoltre, l'adozione di questo flusso di lavoro crea buone abitudini (ramificazione, revisione del codice, ecc.). Quindi, se in seguito entrerete a far parte di un team di sviluppo, questo processo vi sarà già familiare.



    Quindi sì - inviare PR a se stessi non solo è possibile, ma anche incentivato per qualsiasi progetto serio. Migliora la qualità del codice e impone la disciplina. Naturalmente, per correzioni rapide che consistono in uno o due commit, nulla vieta di eseguire l'unione direttamente con git merge. Ma per le modifiche più importanti, l'approccio migliore è quello di una PR.


    Conclusioni

    Nel complesso, il flusso di lavoro con i repository personali è ormai consolidato. Abbiamo affrontato il percorso dalla clonazione dei repository al perfezionamento di un processo in cui le modifiche lasciano il codice funzionale e pronto per ulteriori sviluppi. Il repository del progetto può ora utilizzare in modo significativo il codice di un altro repository (o di più repository) come librerie.

    Tuttavia, abbiamo considerato un solo scenario: quando lo stesso utente possiede sia il repository del progetto che quello della libreria. L'altro scenario è quando il proprietario del progetto vuole usare i repository di qualcun altro come librerie. Questo caso non è così semplice. Eppure, questo tipo di flusso di lavoro, con il riutilizzo attivo del codice della comunità e la collaborazione, era uno degli obiettivi dichiarati dietro al passaggio al nuovo sistema di repository. Tuttavia, le fondamenta sono state gettate.

    Per ora ci fermiamo qui. Ci vediamo nella prossima parte!

    Articolo seguente >>
    Yuriy Bykov:
    Confermo che dopo aver aggiunto le lettere russe e salvato il file la codifica passa da UTF-8 a UTF-16 LE. Se tutte le lettere russe vengono rimosse e salvate, il file rimane UTF-16 LE.
    Vladislav Boyko:
    Ecco una prova che consente di rendere compatibili UTF-8, cirillico e Git: Tutto quello che si deve fare è chiedere a MetaEditor di non cambiare la codifica del file.
    Stanislav Korotky:
    Molto probabilmente UTF-8 era senza BOM, a ME non piace. Almeno di solito lasciava i file in UTF-8 solo se era presente la BOM. Altri editor sono più intelligenti e lavorano senza BOM.