Manuale MQL5: La cronologia delle offerte e la libreria di funzioni per ottenere proprietà di posizione
Introduzione
È il momento di riassumere brevemente le informazioni fornite nei precedenti articoli sulle proprietà della posizione. In questo articolo creeremo alcune funzioni aggiuntive per ottenere le proprietà che possono essere ottenute solo dopo aver effettuato l'accesso alla cronologia delle offerte. Acquisiremo anche familiarità con le strutture dati che ci consentiranno di accedere alle proprietà di posizione e simbolo in modo più comodo.
I sistemi di trading in cui i volumi delle posizioni rimangono gli stessi per tutta la loro esistenza non richiedono realmente l'uso delle funzioni che verranno fornite in questo articolo. Ma se hai intenzione di implementare un sistema di gestione del denaro e controllare la dimensione del lotto di una posizione nella tua strategia di trading in una fase successiva, queste funzioni saranno indispensabili.
Prima di iniziare, vorrei dare un suggerimento a quei lettori che hanno seguito un collegamento a questo articolo, mentre visitavano per la prima volta questo sito Web o hanno appena iniziato a imparare il linguaggio MQL5, per iniziare con i precedenti articoli del " Manuale MQL5".
Sviluppo di Expert Advisor
Per poter vedere il funzionamento delle nuove funzioni nell'Expert Advisor modificato nel precedente articolo chiamato "MQL5 Cookbook: Come evitare errori durante l'impostazione/modifica dei livelli commerciali", aggiungeremo la possibilità di aumentare il volume della posizione se si verifica nuovamente un segnale di apertura, mentre la posizione è già presente.
Potrebbero esserci diverse operazioni nella cronologia delle posizioni e, se si sono verificati cambiamenti nel volume della posizione nel corso del trading, devono esserci stati anche cambiamenti nel prezzo della posizione corrente. Per conoscere il prezzo del primo punto di ingresso, dobbiamo accedere allo storico delle trattative rispetto a quella specifica posizione. La figura seguente è una dimostrazione del caso in cui una posizione ha una sola operazione (punto di ingresso):
Fig. 1. Primo deal nella posizione.
La figura successiva mostra una variazione del prezzo della posizione dopo la seconda operazione:
Fig. 2. Secondo affare nella posizione.
Come dimostrato negli articoli precedenti, gli identificatori standard consentono di ottenere solo il prezzo della posizione corrente (POSITION_PRICE_OPEN) e il prezzo corrente di un simbolo (proprietà della posizione: POSITION_PRICE_CURRENT) per cui viene aperta una posizione.
Tuttavia, in alcuni sistemi di negoziazione è necessario conoscere la distanza coperta dal prezzo dal primo punto di ingresso, nonché il prezzo dell'ultima transazione. Tutte queste informazioni sono disponibili nella cronologia delle trattative/ordini dell'account. Di seguito l'elenco delle offerte associate alla figura precedente:
Fig. 3. La cronologia delle trattative nel conto.
Credo che ora la situazione sia chiara e tutti gli obiettivi siano fissati. Continuiamo a modificare l'Expert Advisor presente negli articoli precedenti. Innanzitutto, aggiungeremo nuovi identificatori numerati 0, 6, 9, 12 e 16 all'enumerazione delle proprietà di posizione:
//--- Enumeration of position properties enum ENUM_POSITION_PROPERTIES { P_TOTAL_DEALS = 0, P_SYMBOL = 1, P_MAGIC = 2, P_COMMENT = 3, P_SWAP = 4, P_COMMISSION = 5, P_PRICE_FIRST_DEAL= 6, P_PRICE_OPEN = 7, P_PRICE_CURRENT = 8, P_PRICE_LAST_DEAL = 9, P_PROFIT = 10, P_VOLUME = 11, P_INITIAL_VOLUME = 12, P_SL = 13, P_TP = 14, P_TIME = 15, P_DURATION = 16, P_ID = 17, P_TYPE = 18, P_ALL = 19 };
I commenti per ciascuna delle proprietà verranno forniti in una struttura che verrà esaminata un po' più in basso.
Aumentiamo il numero dei parametri esterni. Ora, saremo in grado di specificare:
- MagicNumber - un ID univoco dell'Expert Advisor (numero magico);
- Deviazione - slittamento;
- VolumeIncrease - un valore di cui verrà aumentato il volume della posizione;
- InfoPanel - un parametro che permette di abilitare/disabilitare la visualizzazione del pannello info.
Ecco come viene implementato:
//--- External parameters of the Expert Advisor sinput long MagicNumber=777; // Magic number sinput int Deviation=10; // Slippage input int NumberOfBars=2; // Number of Bullish/Bearish bars for a Buy/Sell input double Lot=0.1; // Lot input double VolumeIncrease=0.1; // Position volume increase input double StopLoss=50; // Stop Loss input double TakeProfit=100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse=true; // Position reversal sinput bool ShowInfoPanel=true; // Display of the info panel
Si prega di notare i parametri le cui sinput è impostato. Questo modificatore consente di disabilitare l'ottimizzazione in Strategy Tester. In effetti, quando si sviluppa un programma per uso personale, si ha una perfetta comprensione di quali parametri influenzeranno il risultato finale, quindi è sufficiente deselezionarli dall'ottimizzazione. Ma quando si tratta di un numero molto elevato di parametri, questo metodo consente di separarli visivamente dagli altri man mano che vengono visualizzati in grigio:
Fig. 4. I parametri disabilitati per l'ottimizzazione sono disattivati.
Sostituiamo ora le variabili globali che memorizzavano i valori delle proprietà di posizione e simbolo con strutture di dati (struct):
//--- Position properties struct position_properties { uint total_deals; // Number of deals bool exists; // Flag of presence/absence of an open position string symbol; // Symbol long magic; // Magic number string comment; // Comment double swap; // Swap double commission; // Commission double first_deal_price; // Price of the first deal in the position double price; // Current position price double current_price; // Current price of the position symbol double last_deal_price; // Price of the last deal in the position double profit; // Profit/Loss of the position double volume; // Current position volume double initial_volume; // Initial position volume double sl; // Stop Loss of the position double tp; // Take Profit of the position datetime time; // Position opening time ulong duration; // Position duration in seconds long id; // Position identifier ENUM_POSITION_TYPE type; // Position type };
//--- Symbol properties struct symbol_properties { int digits; // Number of decimal places in the price int spread; // Spread in points int stops_level; // Stops level double point; // Point value double ask; // Ask price double bid; // Bid price double volume_min; // Minimum volume for a deal double volume_max; // Maximum volume for a deal double volume_limit; // Maximum permissible volume for a position and orders in one direction double volume_step; // Minimum volume change step for a deal double offset; // Offset from the maximum possible price for a transaction double up_level; // Upper Stop level price double down_level; // Lower Stop level price }
Ora, per accedere a un determinato elemento della struttura, dobbiamo creare una variabile di questo tipo di struttura. La procedura è simile alla creazione di un oggetto per una classe commerciale che è stata considerata nell'articolo chiamato "Manuale MQL5: Analisi delle proprietà di posizione nel tester di strategia MetaTrader 5".
//--- variables for position and symbol properties
position_properties pos;
symbol_properties symb;
È possibile accedere agli elementi nello stesso modo in cui si ha a che fare con i metodi di classe. In altre parole, è sufficiente mettere un punto dopo il nome di una variabile di struttura per visualizzare l'elenco degli elementi contenuti in quella specifica struttura. Questo è molto conveniente. Nel caso in cui vengano forniti commenti a riga singola per i campi della struttura (come nel nostro esempio), verranno visualizzati in un tooltip sulla destra.
Fig. 5. Elenco dei campi della struttura.
Un altro punto importante. Nel modificare l'Expert Advisor, abbiamo cambiato praticamente tutte le sue variabili globali utilizzate in molte funzioni, quindi ora dobbiamo sostituirle con i campi della struttura corrispondenti per le proprietà di simboli e posizioni. Ad esempio, la variabile globale pos_open che era utilizzata per memorizzare il flag di presenza/assenza di una posizione aperta è stata sostituita con il campo exists del tipo di struttura position_properties. Pertanto, ovunque sia stata utilizzata la variabile pos_open, deve essere sostituita con pos.exists.
Sarà un processo lungo ed estenuante se riesci a farlo manualmente. Quindi sarebbe meglio automatizzare la soluzione a questa attività utilizzando le funzionalità di MetaEditor: Trova e sostituisci -> Sostituisci nel menu Modifica o nella combinazione di tasti Ctrl+H:
Fig. 6. Trovare e sostituire il testo.
Abbiamo bisogno di trovare e sostituire tutte le variabili globali per le proprietà di posizione e simbolo per eseguire ulteriormente un test, dopo aver compilato il file. Se non vengono rilevati errori, vorrà dire che abbiamo fatto tutto bene. Non fornirò qui il codice per non rendere l'articolo inutilmente lungo. Inoltre, un codice sorgente pronto per l'uso è disponibile alla fine dell'articolo per il download.
Ora che abbiamo risolto il problema con le variabili, procediamo con la modifica delle funzioni esistenti e la creazione di quelle nuove.
Nei parametri esterni, ora puoi impostare il numero magico e lo slippage in punti. Dobbiamo quindi apportare le modifiche rilevanti anche al codice dell'Expert Advisor. Creeremo una funzione ausiliaria definita dall'utente OpenPosition(), dove queste proprietà verranno impostate utilizzando le funzioni della classe CTrade prima di inviare un ordine per l'apertura della posizione.
//+------------------------------------------------------------------+ //| Opening a position | //+------------------------------------------------------------------+ void OpenPosition(double lot, ENUM_ORDER_TYPE order_type, double price, double sl, double tp, string comment) { trade.SetExpertMagicNumber(MagicNumber); // Set the magic number in the trading structure trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); // Set the slippage in points //--- If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } }
Abbiamo solo bisogno di apportare alcune piccole modifiche al codice della principale funzione di trading dell'Expert Advisor - TradingBlock(). Di seguito è riportata la parte del codice funzione che ha subito modifiche:
//--- If there is no position if(!pos.exists) { //--- Adjust the volume lot=CalculateLot(Lot); //--- Open a position OpenPosition(lot,order_type,position_open_price,sl,tp,comment); } //--- If there is a position else { //--- Get the position type GetPositionProperties(P_TYPE); //--- If the position is opposite to the signal and the position reversal is enabled if(pos.type==opposite_position_type && Reverse) { //--- Get the position volume GetPositionProperties(P_VOLUME); //--- Adjust the volume lot=pos.volume+CalculateLot(Lot); //--- Reverse the position OpenPosition(lot,order_type,position_open_price,sl,tp,comment); return; } //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume if(!(pos.type==opposite_position_type) && VolumeIncrease>0) { //--- Get the Stop Loss of the current position GetPositionProperties(P_SL); //--- Get the Take Profit of the current position GetPositionProperties(P_TP); //--- Adjust the volume lot=CalculateLot(Increase); //--- Increase the position volume OpenPosition(lot,order_type,position_open_price,pos.sl,pos.tp,comment); return; }
Il codice sopra è stato migliorato con il blocco in cui la direzione della posizione corrente viene verificata rispetto alla direzione del segnale. Se le loro direzioni coincidono e nei parametri esterni è abilitato l'aumento del volume di posizione (il valore del parametro VolumeIncrease è maggiore di zero), controlliamo/rettifichiamo un determinato lotto e inviamo il relativo ordine. Ora, tutto ciò che devi fare per inviare un ordine per aprire o invertire una posizione o per aumentare il volume della posizione è scrivere una riga di codice.
Creiamo funzioni per ottenere le proprietà di posizione dalla cronologia delle trattative. Inizieremo con una funzione CurrentPositionTotalDeals() che restituisce il numero di operazioni nella posizione corrente:
//+------------------------------------------------------------------+ //| Returning the number of deals in the current position | //+------------------------------------------------------------------+ uint CurrentPositionTotalDeals() { int total =0; // Total deals in the selected history list int count =0; // Counter of deals by the position symbol string deal_symbol =""; // symbol of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, increase the counter if(deal_symbol==_Symbol) count++; } } //--- return(count); }
Il codice sopra è fornito con commenti abbastanza dettagliati. Ma dovremmo dire alcune parole su come viene selezionata la cronologia. Nel nostro caso, abbiamo ottenuto l'elenco dal punto di apertura della posizione corrente determinata dal tempo di apertura al momento corrente utilizzando la funzione HistorySelect(). Dopo aver selezionato la cronologia, possiamo scoprire il numero di offerte nell'elenco utilizzando la funzione HistoryDealsTotal(). Il resto dovrebbe essere chiaro dai commenti.
La cronologia di una particolare posizione può essere selezionata anche tramite il suo identificatore utilizzando la funzione HistorySelectByPosition(). Qui, devi considerare che l'identificatore di posizione rimane lo stesso quando la posizione viene invertita, come a volte accade nel nostro Expert Advisor. Tuttavia, il tempo di apertura della posizione cambia all'inversione, quindi questa variante è più facile da implementare. Ma se hai a che fare con la cronologia delle operazioni che non si applica solo alla posizione attualmente aperta, devi utilizzare gli identificatori. Torneremo alla cronologia delle operazioni nei prossimi articoli.
Continuiamo creando una funzione CurrentPositionFirstDealPrice() che restituisce il prezzo della prima operazione nella posizione, ovvero il prezzo dell'operazione a cui è stata aperta la posizione.
//+------------------------------------------------------------------+ //| Returning the price of the first deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionFirstDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // symbol of the deal double deal_price =0.0; // Price of the deal datetime deal_time =NULL; // Time of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- Get the time of the deal deal_time=(datetime)HistoryDealGetInteger(HistoryDealGetTicket(i),DEAL_TIME); //--- If the time of the deal equals the position opening time, // and if the symbol of the deal and the current symbol are the same, exit the loop if(deal_time==pos.time && deal_symbol==_Symbol) break; } } //--- return(deal_price); }
Il principio qui è lo stesso della funzione precedente. Otteniamo la cronologia dal punto di apertura della posizione e quindi controlliamo l'ora dell'operazione e l'ora di apertura della posizione ad ogni iterazione. Insieme al prezzo dell'affare, otteniamo il nome del simbolo e l'ora dell'affare. La primissima operazione viene individuata quando l'orario dell'operazione coincide con l'orario di apertura della posizione. Poiché il suo prezzo è già stato assegnato alla relativa variabile, dobbiamo solo restituire il valore.
Andiamo avanti. A volte, potrebbe essere necessario ottenere il prezzo dell'ultima operazione nella posizione corrente. A questo scopo, creeremo una funzione CurrentPositionLastDealPrice():
//+------------------------------------------------------------------+ //| Returning the price of the last deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionLastDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // Symbol of the deal double deal_price =0.0; // Price //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, exit the loop if(deal_symbol==_Symbol) break; } } //--- return(deal_price); }
Questa volta il ciclo è iniziato con l'ultimo affare nell'elenco e spesso l'accordo richiesto viene identificato alla prima iterazione del ciclo. Ma se scambi su più simboli, il ciclo continuerà fino a quando il simbolo dell'operazione non corrisponderà al simbolo corrente.
Il volume della posizione corrente può essere ottenuto utilizzando le POSITION_VOLUME. Per scoprire il volume della posizione iniziale (il volume della prima transazione), creeremo una funzione CurrentPositionInitialVolume():
//+------------------------------------------------------------------+ //| Returning the initial volume of the current position | //+------------------------------------------------------------------+ double CurrentPositionInitialVolume() { int total =0; // Total deals in the selected history list ulong ticket =0; // Ticket of the deal ENUM_DEAL_ENTRY deal_entry =WRONG_VALUE; // Position modification method bool inout =false; // Flag of position reversal double sum_volume =0.0; // Counter of the aggregate volume of all deals, except for the first one double deal_volume =0.0; // Volume of the deal string deal_symbol =""; // Symbol of the deal datetime deal_time =NULL; // Deal execution time //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- If the order ticket by its position is obtained, then... if((ticket=HistoryDealGetTicket(i))>0) { //--- Get the volume of the deal deal_volume=HistoryDealGetDouble(ticket,DEAL_VOLUME); //--- Get the position modification method deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); //--- Get the deal execution time deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); //--- When the deal execution time is less than or equal to the position opening time, exit the loop if(deal_time<=pos.time) break; //--- otherwise calculate the aggregate volume of deals by the position symbol, except for the first one if(deal_symbol==_Symbol) sum_volume+=deal_volume; } } } //--- If the position modification method is a reversal if(deal_entry==DEAL_ENTRY_INOUT) { //--- If the position volume has been increased/decreased // I.e. the number of deals is more than one if(fabs(sum_volume)>0) { //--- Current volume minus the volume of all deals except for the first one double result=pos.volume-sum_volume; //--- If the resulting value is greater than zero, return the result, otherwise return the current position volume deal_volume=result>0 ? result : pos.volume; } //--- If there are no more deals, other than the entry, if(sum_volume==0) deal_volume=pos.volume; // return the current position volume } //--- Return the initial position volume return(NormalizeDouble(deal_volume,2)); }
Questa funzione è risultata più complessa delle precedenti. Ho cercato di prendere in considerazione tutte le possibili situazioni che possono portare a un valore sbagliato. Un attento test non ha rivelato alcun problema. I commenti dettagliati forniti nel codice dovrebbero aiutarti a capire il punto.
Sarà anche utile avere una funzione che restituisca la durata della posizione. Lo organizzeremo in modo da consentire all'utente di selezionare il formato appropriato del valore restituito: secondi, minuti, ore o giorni. A tal fine, creiamo un'altra enumerazione:
//--- Position duration enum ENUM_POSITION_DURATION { DAYS = 0, // Days HOURS = 1, // Hours MINUTES = 2, // Minutes SECONDS = 3 // Seconds };
Di seguito è riportato il codice della funzione CurrentPositionDuration() responsabile di tutti i calcoli rilevanti:
//+------------------------------------------------------------------+ //| Returning the duration of the current position | //+------------------------------------------------------------------+ ulong CurrentPositionDuration(ENUM_POSITION_DURATION mode) { ulong result=0; // End result ulong seconds=0; // Number of seconds //--- Calculate the position duration in seconds seconds=TimeCurrent()-pos.time; //--- switch(mode) { case DAYS : result=seconds/(60*60*24); break; // Calculate the number of days case HOURS : result=seconds/(60*60); break; // Calculate the number of hours case MINUTES : result=seconds/60; break; // Calculate the number of minutes case SECONDS : result=seconds; break; // No calculations (number of seconds) //--- default : Print(__FUNCTION__,"(): Unknown duration mode passed!"); return(0); } //--- Return result return(result); }
Creiamo una funzione CurrentPositionDurationToString() per il pannello informazioni in cui vengono visualizzate le proprietà della posizione. La funzione convertirà la durata della posizione in secondi in un formato facilmente comprensibile dall'utente. Il numero di secondi verrà passato alla funzione, e la funzione a sua volta restituirà una stringa contenente la durata della posizione in giorni, ore, minuti e secondi:
//+------------------------------------------------------------------+ //| Converting the position duration to a string | //+------------------------------------------------------------------+ string CurrentPositionDurationToString(ulong time) { //--- A dash if there is no position string result="-"; //--- If the position exists if(pos.exists) { //--- Variables for calculation results ulong days=0; ulong hours=0; ulong minutes=0; ulong seconds=0; //--- seconds=time%60; time/=60; //--- minutes=time%60; time/=60; //--- hours=time%24; time/=24; //--- days=time; //--- Generate a string in the specified format DD:HH:MM:SS result=StringFormat("%02u d: %02u h : %02u m : %02u s",days,hours,minutes,seconds); } //--- Return result return(result); }
Tutto è pronto e pronto ora. Non fornirò i codici funzione GetPositionProperties() e GetPropertyValue() che devono essere modificati in conformità con tutte le modifiche di cui sopra. Se leggi tutti gli articoli precedenti della serie, non dovresti trovare alcuna difficoltà a farlo da solo. In ogni caso, il file del codice sorgente è allegato all'articolo.
Di conseguenza, il pannello delle informazioni dovrebbe apparire come mostrato di seguito:
Fig. 7. Dimostrazione di tutte le proprietà di posizione sul pannello informativo.
Quindi, ora abbiamo la libreria di funzioni per ottenere le proprietà di posizione e probabilmente continueremo a lavorarci nei prossimi articoli, come e quando richiesto.
Ottimizzazione dei parametri e test Expert Advisor
Come esperimento, proviamo a ottimizzare i parametri dell'Expert Advisor. Sebbene quello che abbiamo attualmente non possa ancora essere definito un sistema di trading completo, il risultato che otterremo aprirà i nostri occhi su alcune cose e migliorerà la nostra esperienza come sviluppatori di sistemi di trading.
Effettueremo le impostazioni di Strategy Tester come mostrato di seguito:
Fig. 8. Impostazioni di Strategy Tester per l'ottimizzazione dei parametri.
Le impostazioni dei parametri esterni dell'Expert Advisor dovrebbero essere le seguenti:
Fig. 9. Impostazioni dei parametri di Expert Advisor per l'ottimizzazione.
A seguito dell'ottimizzazione, ordiniamo i risultati ottenuti in base al fattore di recupero massimo:
Fig. 10. Risultati ordinati in base al fattore di recupero massimo.
Proviamo ora il set di parametri più in alto, con il valore del fattore di recupero pari a 4,07. Anche considerando che l'ottimizzazione è stata eseguita per EURUSD, possiamo vedere risultati positivi per molti simboli:
Risultati per EURUSD:
Fig. 11. Risultati per EURUSD.
Risultati per AUDUSD:
Fig. 12. Risultati per AUDUSD.
Risultati per NZDUSD:
Fig. 13. Risultati per NZDUSD.
Conclusione
Praticamente qualsiasi idea può essere sviluppata e migliorata. Ogni sistema di trading dovrebbe essere testato molto attentamente prima di essere consoderato difettoso. Nei prossimi articoli daremo uno sguardo a vari meccanismi e schemi che possono svolgere un ruolo molto positivo nella personalizzazione e nell'adattamento di quasi tutti i sistemi di trading.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/644
- 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