
Manuale MQL5: Scrittura della cronologia delle offerte in un file e creazione di grafici di bilanciamento per ogni simbolo in Excel
Introduzione
Quando comunicavo in vari forum, usavo spesso esempi dei risultati dei miei test visualizzati come schermate di grafici di Microsoft Excel. Molte volte mi è stato chiesto di spiegare come tali grafici possono essere creati. Excel offre ampie funzionalità per la creazione di grafici e ci sono molti libri su questo argomento. Per trovare le informazioni richieste in un libro, potrebbe essere necessario leggerle tutte. Ora finalmente, ho un po 'di tempo per spiegare tutto in questo articolo.
Nei due articoli precedenti Articolo Manuale MQL5: Multi-Currency Expert Advisor - Approccio semplice, pulito e veloce e Manuale MQL5: Sviluppando un Expert Advisor Multi-Valuta con numero Illimitato di Parametri ci siamo occupati dello sviluppo di EA multi-valuta in MQL5. Sappiamo che i risultati del test in MetaTrader 5 vengono visualizzati come una curva generale Balance/Equity, cioè se è necessario visualizzare i risultati per ciascun simbolo separatamente, si dovrebbe più e più volte andare su parametri esterni dell'Expert Advisor per disabilitare tutti i simboli tranne quello i cui risultati sono richiesti e quindi eseguire nuovamente il test. Questo è scomodo.
Quindi oggi ti mostrerò un semplice metodo su come ottenere grafici di bilanciamento per tutti i simboli insieme al risultato cumulativo di un Expert Advisor multi-valuta su un singolo diagramma Excel con solo un paio di clic. Per ricostruire l'esempio, prenderemo l'Expert Advisor multi-valuta dal Articolo precedente. Sarà potenziato con una funzione che scriverà la storia delle offerte e delle curve di bilanciamento per tutti i simboli in un file .csv al termine del test. Inoltre, aggiungeremo un'altra colonna al rapporto per mostrare i drawdown da tutti i massimi locali.
Creiamo un libro Excel impostato in modo da poter collegare il file di dati. Il libro può essere aperto tutto il tempo, quindi non sarà necessario chiuderlo prima di eseguire un altro test. Al termine del test, sarà sufficiente aggiornare i dati premendo un determinato tasto per poter vedere le modifiche nel report e nel grafico.
Sviluppo di Expert Advisor
Non ci saranno cambiamenti significativi nel nostro EA, aggiungeremo solo alcune funzioni. Iniziamo aggiungendo la struttura e l'array per i saldi di simboli al file principale.
//--- Arrays for balances struct Balance { double balance[]; }; //--- Array of balances for all symbols Balance symbol_balance[];
Quindi, creiamo il file di include Report.mqh separato per le funzioni che generano report di test e lo includiamo nel file principale di Expert Advisor (vedere la riga evidenziata nel codice seguente):
//--- Include custom libraries #include "Include/Auxiliary.mqh" #include "Include/Enums.mqh" #include "Include/Errors.mqh" #include "Include/FileFunctions.mqh" #include "Include/InitializeArrays.mqh" #include "Include/Report.mqh" #include "Include/ToString.mqh" #include "Include/TradeFunctions.mqh" #include "Include/TradeSignals.mqh"
Creiamo prima una struttura di proprietà dell'affare, come quella che abbiamo già nel progetto per le proprietà di posizione e simbolo. A tale scopo, aggiungiamo l'enumerazione degli identificatori di proprietà al file Enums.mqh:
//+------------------------------------------------------------------+ //| Enumeration of deal properties | //+------------------------------------------------------------------+ enum ENUM_DEAL_PROPERTIES { D_SYMBOL = 0, // Deal symbol D_COMMENT = 1, // Deal comment D_TYPE = 2, // Deal type D_ENTRY = 3, // Deal entry - entry in, entry out, reverse D_PRICE = 4, // Deal price D_PROFIT = 5, // Deal result (profit/loss) D_VOLUME = 6, // Deal volume D_SWAP = 7, // Cumulative swap on close D_COMMISSION = 8, // Deal commission D_TIME = 9, // Deal time D_ALL = 10 // All of the above mentioned deal properties };
Inoltre, nel file Report.mqhcreiamo la struttura della proprietà del deal e la funzione GetHistoryDealProperties() che restituisce una proprietà deal. La funzione accetta due parametri: deal ticket e property identifier.
Di seguito, puoi vedere il codice della struttura e la funzione GetHistoryDealProperties():
//--- Deal properties in the history struct HistoryDealProperties { string symbol; // Symbol string comment; // Comment ENUM_DEAL_TYPE type; // Deal type ENUM_DEAL_ENTRY entry; // Direction double price; // Price double profit; // Profit/Loss double volume; // Volume double swap; // Swap double commission; // Commission datetime time; // Time }; //--- Variable of deal properties HistoryDealProperties deal; //+------------------------------------------------------------------+ //| Gets deal properties by ticket | //+------------------------------------------------------------------+ void GetHistoryDealProperties(ulong ticket_number,ENUM_DEAL_PROPERTIES history_deal_property) { switch(history_deal_property) { case D_SYMBOL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); break; case D_COMMENT : deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); break; case D_TYPE : deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); break; case D_ENTRY : deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); break; case D_PRICE : deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); break; case D_PROFIT : deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); break; case D_VOLUME : deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); break; case D_SWAP : deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); break; case D_COMMISSION : deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); break; case D_TIME : deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; case D_ALL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; //--- default: Print("The passed deal property is not listed in the enumeration!"); return; } }
Avremo anche bisogno di diverse funzioni che convertiranno alcune proprietà dell'affare in valori stringa. Queste semplici funzioni restituiscono un trattino ("-") se il valore passato è vuoto o zero. Scriviamoli nel file ToString.mqh:
//+------------------------------------------------------------------+ //| Returns the symbol name, otherwise - dash | //+------------------------------------------------------------------+ string DealSymbolToString(string deal_symbol) { return(deal_symbol=="" ? "-" : deal_symbol); } //+------------------------------------------------------------------+ //| Converts deal type to string | //+------------------------------------------------------------------+ string DealTypeToString(ENUM_DEAL_TYPE deal_type) { string str=""; //--- switch(deal_type) { case DEAL_TYPE_BUY : str="buy"; break; case DEAL_TYPE_SELL : str="sell"; break; case DEAL_TYPE_BALANCE : str="balance"; break; case DEAL_TYPE_CREDIT : str="credit"; break; case DEAL_TYPE_CHARGE : str="charge"; break; case DEAL_TYPE_CORRECTION : str="correction"; break; case DEAL_TYPE_BONUS : str="bonus"; break; case DEAL_TYPE_COMMISSION : str="commission"; break; case DEAL_TYPE_COMMISSION_DAILY : str="commission daily"; break; case DEAL_TYPE_COMMISSION_MONTHLY : str="commission monthly"; break; case DEAL_TYPE_COMMISSION_AGENT_DAILY : str="commission agent daily"; break; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY : str="commission agent monthly"; break; case DEAL_TYPE_INTEREST : str="interest"; break; case DEAL_TYPE_BUY_CANCELED : str="buy canceled"; break; case DEAL_TYPE_SELL_CANCELED : str="sell canceled"; break; //--- Unknown deal type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts direction of deal to string | //+------------------------------------------------------------------+ string DealEntryToString(ENUM_DEAL_ENTRY deal_entry) { string str=""; //--- switch(deal_entry) { case DEAL_ENTRY_IN : str="in"; break; case DEAL_ENTRY_OUT : str="out"; break; case DEAL_ENTRY_INOUT : str="in/out"; break; case DEAL_ENTRY_STATE : str="status record"; break; //--- Unknown direction type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts volume to string | //+------------------------------------------------------------------+ string DealVolumeToString(double deal_volume) { return(deal_volume<=0 ? "-" : DoubleToString(deal_volume,2)); } //+------------------------------------------------------------------+ //| Converts price to string | //+------------------------------------------------------------------+ string DealPriceToString(double deal_price,int digits) { return(deal_price<=0 ? "-" : DoubleToString(deal_price,digits)); } //+------------------------------------------------------------------+ //| Converts deal result to string | //+------------------------------------------------------------------+ string DealProfitToString(string deal_symbol,double deal_profit) { return((deal_profit==0 || deal_symbol=="") ? "-" : DoubleToString(deal_profit,2)); } //+------------------------------------------------------------------+ //| Converts swap to string | //+------------------------------------------------------------------+ string DealSwapToString(double deal_swap) { return(deal_swap<=0 ? "-" : DoubleToString(deal_swap,2)); }
Ora tutto è pronto per scrivere la funzione CreateSymbolBalanceReport() che prepara i dati per il report e li scrive nel file LastTest.csv . È piuttosto semplice: prima scriviamo l'intestazione (nota come viene regolata la stringa se il test è stato eseguito per più di un simbolo), quindi le proprietà di deal richieste per il report vengono concatenato consecutivamente in stringa che viene ulteriormente scritta nel file.
Di seguito è riportato il codice della funzione CreateSymbolBalanceReport():
//+------------------------------------------------------------------+ //| Creates the test report on deals in .csv format | //+------------------------------------------------------------------+ void CreateSymbolBalanceReport() { int file_handle =INVALID_HANDLE; // File handle string path =""; // File path //--- If an error occurred when creating/getting the folder, exit if((path=CreateInputParametersFolder())=="") return; //--- Create file to write data in the common folder of the terminal file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- If the handle is valid (file created/opened) if(file_handle>0) { int digits =0; // Number of decimal places in the price int deals_total =0; // Number of deals in the specified history ulong ticket =0; // Deal ticket double drawdown_max =0.0; // Maximum drawdown double balance =0.0; // Balance //--- string delimeter =","; // Delimiter string string_to_write =""; // To generate the string for writing //--- Generate the header string string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE"; //--- If more than one symbol is involved, modify the header string if(SYMBOLS_COUNT>1) { for(int s=0; s<SYMBOLS_COUNT; s++) StringAdd(headers,","+InputSymbols[s]); } //--- Write the report headers FileWrite(file_handle,headers); //--- Get the complete history HistorySelect(0,TimeCurrent()); //--- Get the number of deals deals_total=HistoryDealsTotal(); //--- Resize the array of balances according to the number of symbols ArrayResize(symbol_balance,SYMBOLS_COUNT); //--- Resize the array of deals for each symbol for(int s=0; s<SYMBOLS_COUNT; s++) ArrayResize(symbol_balance[s].balance,deals_total); //--- Iterate in a loop and write the data for(int i=0; i<deals_total; i++) { //--- Get the deal ticket ticket=HistoryDealGetTicket(i); //--- Get all the deal properties GetHistoryDealProperties(ticket,D_ALL); //--- Get the number of digits in the price digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS); //--- Calculate the overall balance balance+=deal.profit+deal.swap+deal.commission; //--- Generate a string for writing via concatenation StringConcatenate(string_to_write, deal.time,delimeter, DealSymbolToString(deal.symbol),delimeter, DealTypeToString(deal.type),delimeter, DealEntryToString(deal.entry),delimeter, DealVolumeToString(deal.volume),delimeter, DealPriceToString(deal.price,digits),delimeter, DealSwapToString(deal.swap),delimeter, DealProfitToString(deal.symbol,deal.profit),delimeter, MaxDrawdownToString(i,balance,max_drawdown),delimeter, DoubleToString(balance,2)); //--- If more than one symbol is involved, write their balance values if(SYMBOLS_COUNT>1) { //--- Iterate over all symbols for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If the symbols are equal and the deal result is non-zero if(deal.symbol==InputSymbols[s] && deal.profit!=0) { //--- Display the deal in the balance for the corresponding symbol // Take into consideration swap and commission symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+ deal.profit+ deal.swap+ deal.commission; //--- Add to the string StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value else { //--- If the deal type is "Balance" (the first deal) if(deal.type==DEAL_TYPE_BALANCE) { //--- the balance is the same for all symbols symbol_balance[s].balance[i]=balance; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value to the current index else { symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } } } } //--- Write the generated string FileWrite(file_handle,string_to_write); //--- Mandatory zeroing out of the variable for the next string string_to_write=""; } //--- Close the file FileClose(file_handle); } //--- If the file could not be created/opened, print the appropriate message else Print("Error creating file: "+IntegerToString(GetLastError())+""); }
La funzione MaxDrawdownToString() evidenziata nel codice precedente calcola tutti i drawdown dai massimi locali e restituisce una rappresentazione in stringa dell'ora del nuovo massimo locale. In tutti gli altri casi la funzione restituisce una stringa contenente "-" (un trattino).
//+------------------------------------------------------------------+ //| Returns the maximum drawdown from the local maximum | //+------------------------------------------------------------------+ string MaxDrawdownToString(int deal_number,double balance,double &max_drawdown) { //--- The string to be displayed in the report string str=""; //--- To calculate the local maximum and drawdown static double max=0.0; static double min=0.0; //--- If this is the first deal if(deal_number==0) { //--- No drawdown yet max_drawdown=0.0; //--- Set the initial point as the local maximum max=balance; min=balance; } else { //--- If the current balance is greater than in the memory if(balance>max) { //--- calculate the drawdown using the previous values max_drawdown=100-((min/max)*100); //--- update the local maximum max=balance; min=balance; } else { //--- Return zero value of the drawdown max_drawdown=0.0; //--- Update the minimum min=fmin(min,balance); } } //--- Determine the string for the report if(max_drawdown==0) str="-"; else str=DoubleToString(max_drawdown,2); //--- Return result return(str); }
Quindi tutte le funzioni di generazione dei report sono pronte. Abbiamo solo bisogno di vedere come dovremmo usare tutto quanto sopra. Ciò richiederà la della funzione OnTester() chiamata al termine del test. Assicurarsi di controllare la descrizione dettagliata di questa funzione in MQL5 Reference.
È sufficiente scrivere alcune righe di codice nel corpo della funzione OnTester() per specificare la condizione in cui il report deve essere generato. Lo snippet di codice corrispondente è fornito di seguito:
//+------------------------------------------------------------------+ //| Handler of the event of testing completion | //+------------------------------------------------------------------+ double OnTester() { //--- Write the report only after testing if(IsTester() && !IsOptimization() && !IsVisualMode()) //--- Generate the report and write it to the file CreateSymbolBalanceReport(); //--- return(0.0); }
Ora, se esegui Expert Advisor in Strategy Tester, al termine del test vedrai una cartella di Expert Advisor creata nella cartella dei terminali comuni C:\ProgramData\MetaQuotes\Terminal\Common\Files. E il file di report LastTest.csv verrà generato nella cartella dell'Expert Advisor. Se apri il file con Blocco note, vedrai qualcosa di simile a questo:
Figura 1. File di report in formato .csv.
Creazione di grafici in Excel
Possiamo aprire il file creato in Excel e vedere che ogni tipo di dati si trova in una colonna separata. In questo modo i dati appaiono molto più convenienti per la visualizzazione. A questo punto, siamo tecnicamente pronti per creare grafici e salvare il file come libro Excel in formato *.xlsx. Tuttavia, se in seguito eseguiamo il test e riapriamo il libro, vedremo ancora i vecchi dati.
Se proviamo ad aggiornare i dati, mentre il file LastTest.csv è già in uso in Excel, il file non verrà aggiornato, poiché Expert Advisor non sarà in grado di aprirlo per la scrittura mentre viene utilizzato da un'altra applicazione.
Figura 2. Il file di report in formato .csv in Excel 2010.
C'è una soluzione che può essere utilizzata nel nostro caso. Per prima cosa crea un libro Excel in formato *.xlsx in qualsiasi cartella che ti piace. Quindi aprilo e vai alla scheda Dati.
Figura 3. Scheda Dati in Excel 2010.
Sulla barra multifunzione di questa scheda selezionare l'opzione Da testo. Verrà visualizzata la finestra di dialogo Importa file di testo in cui è necessario selezionare il file "LastTest.csv". Selezionare il file e fare clic sul pulsante Apri. Verrà visualizzata la finestra di dialogo Importazione guidata testo - Passaggio 1 di 3 come mostrato di seguito:
Figura 4. La finestra di dialogo "Importazione guidata testo - Passaggio 1 di 3".
Regolare le impostazioni come mostrato sopra e fare clic su Avanti >. Qui, (Passaggio 2 di 3) è necessario specificare il delimitatore utilizzato nel file di dati. Nel nostro file, è "," (virgola).
Figura 5. La finestra di dialogo "Importazione guidata testo - Passaggio 2 di 3".
Fare clic su Avanti > per passare all'Importazione guidata testo - Passaggio 3 di 3. Qui, lasciare il generale come formato di dati per tutte le colonne. È possibile modificare il formato in un secondo momento.
Figura 6. La finestra di dialogo "Importazione guidata testo - Passaggio 3 di 3".
Dopo aver fatto clic sul pulsante Fine, verrà visualizzata la finestra Importa dati in cui è necessario specificare il foglio di lavoro e la cella per l'importazione dei dati.
Figura 7. Selezione della cella per l'importazione dei dati in Excel 2010.
Di solito, selezioniamo la cella in alto a sinistra A1. Prima di fare clic su OK, fare clic sul pulsante Proprietà... per impostare le proprietà dell'intervallo di dati esterno. Verrà visualizzata una finestra di dialogo come mostrato di seguito.
Figura 8. Proprietà dell'intervallo di dati esterni durante l'importazione di dati da file di testo in Excel 2010.
Regolare le impostazioni esattamente come mostrato sopra e fare clic su OK nella finestra corrente e successiva.
Di conseguenza, i tuoi dati appariranno come se avessi semplicemente caricato il file .csv. Ma ora puoi eseguire test ripetuti in MetaTrader 5, senza dover chiudere il libro Excel. Tutto quello che devi fare dopo aver eseguito il test è semplicemente aggiornare i dati usando il collegamento Ctrl + Alt + F5 o il pulsante Aggiorna tutto sulla barra multifunzione della scheda Dati.
Utilizzando le opzioni di formattazione condizionale sulla barra multifunzione della scheda Home è possibile impostare le proprietà visive necessarie per la rappresentazione dei dati.
Figura 9. Formattazione condizionale in Excel 2010.
Ora dobbiamo visualizzare i dati sui grafici di Excel. Un grafico mostrerà tutti i grafici di equilibrio e l'altro mostrerà tutti i prelievi dai massimi locali come istogramma.
Creiamo prima un diagramma per i grafici di equilibrio. Selezionare le intestazioni di tutti i saldi e l'intero array di dati dall'alto verso il basso (tenendo premuto il tasto Maiusc, premere il tasto Fine e quindi il tasto freccia GIÙ). Ora nella scheda Inserisci, selezionare il tipo di grafico desiderato.
Figura 10. Selezione di un tipo di grafico in Excel 2010.
Di conseguenza, verrà creato il grafico che può essere spostato in un altro foglio di lavoro per comodità. Per fare ciò, è sufficiente selezionarlo e premere Ctrl + X (Taglia). Quindi vai al foglio di lavoro appena creato, seleziona la cella A1 e premi Ctrl + V (Incolla).
Il grafico creato con le impostazioni predefinite è mostrato nell'immagine seguente:
Figura 11. Aspetto del grafico con impostazioni predefinite.
Puoi personalizzare qualsiasi elemento del grafico: cambiane le dimensioni, il colore, lo stile, ecc.
Nell'immagine sopra, l'asse orizzontale mostra il numero di offerte. Modifichiamolo in modo che visualizzi invece le date. A tale scopo, fare clic con il pulsante destro del mouse sul grafico e selezionare l'opzione Seleziona dati dal menu di scelta rapida. Verrà visualizzata la finestra di dialogo Seleziona origine dati. Fare clic sul pulsante Modifica quindi selezionare l'intervallo di dati richiesto nella colonna TIME e fare clic su OK.
Figura 12. Finestra di dialogo "Seleziona origine dati".
Prova a creare il grafico dei drawdown da solo e posizionalo sotto il primo grafico. Ora le loro proprietà visive possono essere personalizzate, se necessario. Personalmente, di solito lo faccio:
Figura 13. Grafici personalizzati in Excel 2010.
Conclusione
Quindi, abbiamo i grafici excel con risultati di test che sembrano abbastanza decenti. In uno dei miei prossimi articoli, ti mostrerò come creare report ancora più informativi. Allegato all'articolo è l'archivio scaricabile con i file dell'Expert Advisor per la vostra considerazione.
Dopo aver estratto i file dall'archivio, posizionare la cartella ReportInExcel nella directory MetaTrader 5\MQL5\Experts. Inoltre, l'indicatore EventsSpy.mq5 deve essere inserito nella directory MetaTrader 5\MQL5\Indicators.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/651





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso