Calcoli paralleli su MetaTrader 5
Introduzione al parallelismo del processore
Quasi tutti i PC moderni sono in grado di eseguire più attività contemporaneamente a causa della presenza di diversi core del processore. Il loro numero cresce ogni anno, 2, 3, 4, 6 core... Intel ha di recente mostrato un processore sperimentale funzionante a 80 core (sì, non è un errore di battitura. Ottanta core. Sfortunatamente, questo computer non apparirà nei negozi perché questo processore è stato creato esclusivamente allo scopo di studiare le potenziali capacità della tecnologia).
Non tutti coloro che usano un computer (e nemmeno tutti i programmatori alle prime armi) capiscono come funziona. Pertanto, qualcuno sicuramente chiederà: perché abbiamo bisogno di un processore con così tanti core quando prima (con un singolo core) il computer poteva eseguire molti programmi contemporaneamente e tutti funzionavano? La verità è che non è vero. Diamo un'occhiata al seguente diagramma.
Figura 1. Esecuzione parallela delle applicazioni
Il caso A nel diagramma mostra cosa succede quando un singolo programma viene eseguito su un processore single-core. Il processore dedica tutto il tempo alla sua implementazione e il programma esegue una certa quantità di lavoro nel tempo T.
Caso B - Lancio di 2 programmi. Tuttavia, il processore è organizzato in modo tale che fisicamente, in qualsiasi momento, uno dei suoi core possa eseguire un solo comando. Quindi dovrà passare costantemente tra i due programmi: eseguirà alcuni dei primi, poi il secondo, ecc. Questo accade in maniera molto rapida, molte volte al secondo, quindi sembra che il processore esegua entrambi i programmi contemporaneamente. In realtà, comunque, la loro esecuzione richiederà il doppio del tempo rispetto a se ogni programma fosse eseguito sul processore separatamente.
Il caso C mostra che questo problema viene risolto in modo efficace se il numero di core in un processore corrisponde al numero di programmi in esecuzione. Ogni programma ha a sua disposizione un nucleo separato e la velocità della sua esecuzione aumenta, proprio come nel caso A.
Il caso D è una risposta all'illusione condivisa da molti utenti. Credono che se un programma è in esecuzione su un processore multi-core, questo verrà eseguito più volte e più velocemente. In generale, ciò non può essere vero perché il processore non è in grado di dividere autonomamente il programma in parti separate ed eseguirle tutte contemporaneamente.
Ad esempio, se il programma richiede prima una password, e quindi vengono eseguite le sue verifiche, sarebbe inaccettabile eseguire contemporaneamente la richiesta di password su un core e la verifica su un altro. La verifica non avrà mai successo, perché la password, inizialmente, non è ancora stata inserita.
Il processore non conosce tutti i progetti che il programmatore ha implementato, né l'intera logica del lavoro del programma, quindi non può separare indipendentemente il programma tra i core. Per cui, se eseguiamo un singolo programma in un sistema multi-core, utilizzerà solo un core e verrà eseguito con la stessa velocità come se fosse eseguito su un processore single-core.
Il caso E spiega cosa occorre fare per far sì che il programma utilizzi tutti i suoi core e venga eseguito più velocemente. Poiché il programmatore conosce la logica del programma, dovrebbe, durante il suo sviluppo, contrassegnare in qualche modo quelle parti del programma che possono essere eseguite contemporaneamente. Il programma, durante la sua esecuzione, comunicherà queste informazioni al processore e il processore assegnerà quindi il programma al numero richiesto di core.
Parallelismo su MetaTrader
Nel capitolo precedente abbiamo capito cosa occorre fare per utilizzare tutti i core della CPU e accelerare l'esecuzione dei programmi: dobbiamo in qualche modo allocare il codice parallelizzabile del programma in thread separati. In molti linguaggi di programmazione ci sono classi o operatori speciali per questo. Tuttavia, non esiste uno strumento integrato nel linguaggio MQL5. Quindi cosa possiamo fare?
Esistono due modi per ovviare a questo problema:
1. Usa la DLL | 2. Utilizzare risorse non linguistiche di MetaTrader |
---|---|
Creando una DLL in un linguaggio che possiede uno strumento integrato per la parallelizzazione, otterremo la parallelizzazione anche in MQL5-EA. | Secondo le informazioni degli sviluppatori MetaTrader, l'architettura del terminale client è multi-thread. Quindi, in determinate condizioni, i dati di mercato in entrata vengono elaborati in thread separati. Pertanto, se riusciamo a trovare un modo per separare il codice del nostro programma da un numero di EA o indicatori, allora MetaTrader sarà in grado di utilizzare un numero di core CPU per la sua esecuzione. |
In questo articolo non parleremo del primo metodo. È chiaro che sulla DLL possiamo implementare tutto ciò che vogliamo. Cercheremo di trovare una soluzione che coinvolgerà solo i mezzi standard di MetaTrader e che non richiederà l'uso di linguaggi diversi da MQL5.
E quindi, ecco più informazioni sul secondo metodo. Dovremo eseguire una serie di esperimenti per scoprire esattamente come sono supportati più core su MetaTrader. Per fare questo, creiamo un indicatore di test e un EA di test che eseguiranno qualsiasi lavoro in corso. Questo caricherà pesantemente la CPU.
Ho scritto il seguente indicatore i-flood:
//+------------------------------------------------------------------+ //| i-flood.mq5 | //+------------------------------------------------------------------+ #property indicator_chart_window input string id; //+------------------------------------------------------------------+ void OnInit() { Print(id,": OnInit"); } //+------------------------------------------------------------------+ int OnCalculate(const int rt,const int pc,const int b,const double &p[]) { Print(id,": OnCalculate Begin"); for (int i=0; i<1e9; i++) for (int j=0; j<1e1; j++); Print(id,": OnCalculate End"); return(0); } //+------------------------------------------------------------------+
E l'e-flood EA analogo:
//+------------------------------------------------------------------+ //| e-flood.mq5 | //+------------------------------------------------------------------+ input string id; //+------------------------------------------------------------------+ void OnInit() { Print(id,": OnInit"); } //+------------------------------------------------------------------+ void OnTick() { Print(id,": OnTick Begin"); for (int i=0; i<1e9; i++) for (int j=0; j<1e1; j++); Print(id,": OnTick End"); } //+------------------------------------------------------------------+
Inoltre, aprendo varie combinazioni di finestre del grafico (un grafico, due grafici con lo stesso simbolo, due grafici con simboli diversi) e posizionando su di essi una o due copie di questo indicatore o EA, possiamo osservare come il terminale utilizza i core della CPU.
Questi indicatori ed EA inviano anche messaggi al log ed è interessante osservare la sequenza del loro aspetto. Non fornirò questi log poiché puoi generarli tu stesso, ma in questo articolo ci interessa scoprire quanti core e in quali combinazioni di grafici vengono utilizzati dal terminale.
Possiamo misurare il numero di core funzionanti tramite il "Task Manager" di Windows:
Figura 2. Core della CPU
I risultati di tutte le misurazioni sono raccolti nella tabella seguente:
№ combinazione | I contenuti del terminale | Uso della CPU |
---|---|---|
1 | 2 indicatori su un grafico | 1 core |
2 | 2 indicatori su grafici diversi, stessa coppia | 1 core |
3 | 2 indicatori su diversi grafici, diverse coppie | 2 core |
4 | 2 EA sullo stesso grafico: questa situazione è impossibile | - |
5 | 2 EA su grafici diversi, stessa coppia | 2 core |
6 | 2 EA su diversi grafici, diverse coppie | 2 core |
7 | 2 indicatori su diverse coppie, creati dall'EA | 2 core |
La settima combinazione è un modo comune per creare un indicatore, utilizzato in molte strategie di trading.
L'unica caratteristica speciale è che ho creato due indicatori su due diverse coppie di valute, poiché le combinazioni 1 e 2 fanno capire che non ha senso posizionare gli indicatori sulla stessa coppia. Per questa combinazione ho usato l'EA e-flood-starter, il quale ha prodotto due copie di i-flood:
//+------------------------------------------------------------------+ //| e-flood-starter.mq5 | //+------------------------------------------------------------------+ void OnInit() { string s="EURUSD"; for(int i=1; i<=2; i++) { Print("Indicator is created, handle=", iCustom(s,_Period,"i-flood",IntegerToString(i))); s="GBPUSD"; } } //+------------------------------------------------------------------+
Quindi tutti i calcoli dei core sono stati eseguiti e ora sappiamo per quali combinazioni MetaTrader utilizza più core. Successivamente, cercheremo di applicare questa conoscenza per implementare le idee dei calcoli paralleli.
Progettiamo un sistema parallelo
Per quanto riguarda il terminale di trading per il sistema parallelo, intendiamo un gruppo di indicatori o EA (o una miscela di entrambi) che insieme svolgono un compito comune, come ad esempio condurre operazioni di trading o disegnare sul grafico. Significa che questo gruppo funziona come un grande indicatore o come un grande EA. Ma allo stesso tempo distribuisce il carico computazionale su tutti i core del processore disponibili.
Tale sistema è costituito da due tipi di componenti software:
- CM, modulo computazionale. Il loro numero può andare da 2 e fino al numero di core del processore. È nel CM che viene posizionato tutto il codice che deve essere parallelizzato. Come abbiamo scoperto nel capitolo precedente, il CM può essere implementato come indicatore, così come un EA - per qualsiasi forma di implementazione, esiste una combinazione che utilizza tutti i core del processore;
- MM, il modulo principale. Esegue le principali funzioni del sistema. Quindi se l’MM è un indicatore, allora eseguirà il disegno sul grafico, mentre se l’MM è un EA, allora svolgerà le funzioni di trading. L’MM gestisce anche tutti i CM.
Ad esempio, per un MM EA e un processore a 2 core lo schema del lavoro del sistema sarà simile al grafico:
Figura 3. Schema del sistema con CPU a 2 core.
Dovrebbe essere chiaro che il sistema che abbiamo sviluppato non è un programma tradizionale, in cui è possibile chiamare semplicemente la procedura necessaria al momento dato. L’MM e il CM sono EA o indicatori, cioè si tratta di programmi indipendenti e a sé stanti. Non esiste una connessione diretta tra di essi, operano in modo indipendente e non possono comunicare direttamente tra loro.
L'esecuzione di uno qualsiasi di questi programmi inizia solo con l'apparizione nel terminale di qualsiasi evento (ad esempio, l'arrivo di citazioni o un timer tick) E, tra gli eventi, tutti i dati che questi programmi vogliono trasmettere tra loro devono essere archiviati da qualche parte al di fuori, in un luogo accessibile pubblicamente (chiamiamolo "Data Exchange Buffer"). Quindi, lo schema di cui sopra è implementato nel terminale nel modo seguente:
Figura 4. Dettagli di implementazione
Per l'implementazione di questo sistema, dobbiamo rispondere alle seguenti domande:
- quale delle combinazioni di core multipli che si trovano nel capitolo precedente useremo nel nostro sistema?
- poiché il sistema è costituito da diversi EA o indicatori, come possiamo organizzare meglio lo scambio di dati (bilaterale) tra di loro (ad esempio, come appariranno nel concreto i dati)?
- come possiamo organizzare il coordinamento e la sincronizzazione delle loro azioni?
Per ognuna di queste domande 'esiste più di una risposta, tutte fornite di seguito. In pratica, le opzioni specifiche dovrebbero essere selezionate in base a una situazione particolare. Lo faremo nel prossimo capitolo. Nel frattempo, consideriamo tutte le possibili risposte.
Combinazione
La combinazione 7 è la più conveniente per un uso pratico e regolare (tutte le altre combinazioni sono elencate nel capitolo precedente), perché non è necessario aprire finestre aggiuntive nel terminale e posizionare su di esse EA o indicatori. L'intero sistema si trova in un'unica finestra e tutti gli indicatori (CM-1 e CM-2) vengono creati automaticamente dall'EA (MM). L’assenza di finestre extra e di azioni manuali ha eliminato la confusione per il trader e, quindi, anche tutto ciò che era correlato a tali errori di confusione.
In alcune strategie di trading, possono essere più utili altre combinazioni. Ad esempio, sulla base di una di esse, possiamo creare interi sistemi software, operando sul principio "client-server". Dove gli stessi CM saranno comuni per più MM. Tali CM comuni possono svolgere non solo un ruolo secondario di "computer", ma essere "server" che memorizza una sorta di informazioni unificate per tutte le strategie o anche i coordinatori del loro lavoro collettivo. Un CM-server cloud potrebbe, ad esempio, controllare centralmente la distribuzione dei mezzi in alcuni portafogli di strategie e coppie di valute, mantenendo il livello di rischio complessivo desiderato.
Scambio di dati
Possiamo trasmettere le informazioni tra MM e CM utilizzando uno dei 3 modi:
- variabili globali del terminale;
- file;
- Indicatore di buffer.
Il primo metodo è ottimale quando si trasferisce un piccolo numero di variabili numeriche. Se è necessario trasferire dati di testo, dovranno in qualche modo essere codificati in numeri perché le variabili globali hanno solo il tipo doppio.
L'alternativa è il secondo metodo, perché tutto può essere scritto all’interno del/dei file. E questo è un metodo conveniente (e forse più veloce del primo) nella circostanza in cui è necessario trasferire una grande quantità di dati.
Il terzo metodo è adatto quando MM e CM sono indicatori. Solo i dati di tipo doppio possono essere trasferiti, ma è più conveniente trasferire matrici numeriche di grandi dimensioni. Ma c'è uno svantaggio: durante la formazione di una nuova barra, la numerazione degli elementi nei buffer viene spostata. Poiché MM e CM si trovano su coppie di valute diverse, le nuove barre non verranno visualizzate contemporaneamente. Dobbiamo tenere conto di questi cambiamenti.
Sincronizzazione
Quando il terminale riceve una quotazione per l’MM e inizia a elaborarlo, non può trasferire immediatamente il controllo al CM. Può solo (come mostrato nel diagramma sopra) formare un'attività (inserendola nelle variabili globali, in un file o in un buffer di indicatori) e attendere l'esecuzione del CM. Poiché tutti i CM si trovano su diverse coppie di valute, l'attesa potrebbe richiedere del tempo. Questo perché una coppia può ricevere la quotazione, mentre l'altro non lo ha ancora ricevuto e arriverà solo in pochi secondi o addirittura minuti (ad esempio, questo può verificarsi durante la notte su coppie non liquide).
Quindi, affinché il CM ottenga il controllo, non dovremmo usare gli eventi OnTick e OnCalculate, i quali dipendono dalle quotazioni. Al loro posto dobbiamo usare l'evento OnTimer (innovazione di MQL5), il quale viene eseguito con una frequenza specificata (ad esempio, 1 secondo). In questo caso, i ritardi nel sistema saranno fortemente limitati.
Inoltre, invece di OnTimer, possiamo usare la tecnica ciclica: ovvero posizionare un ciclo infinito per il CM su OnInit o OnCalculate. Ciascuna delle sue iterazioni sarà l’analogo di un segno di un timer tick.
Attenzione. Ho eseguito alcuni esperimenti e ho scoperto che quando si utilizza la combinazione 7, l'evento OnTimer non funziona negli indicatori (per qualche motivo), sebbene i timer siano stati creati con successo.
Devi anche fare attenzione ai loop infiniti su OnInit e OnCalculate: se anche uno solo degli indicatori CM si trova sulla stessa coppia di valute dell'MM-EA, allora il prezzo smetterà di muoversi sul grafico e l'EA smetterà di funzionare (cesserà di generare gli eventi OnTick). Gli sviluppatori del terminale hanno spiegato le ragioni di questo comportamento.
Dagli sviluppatori: script ed EA funzionano nei propri thread separati, mentre tutti gli indicatori su un singolo simbolo lavorano nello stesso thread. Nello stesso flusso degli indicatori, anche tutte le altre azioni su questo simbolo vengono eseguite consecutivamente: l'elaborazione dei tick, la sincronizzazione della cronologia e il calcolo degli indicatori. Quindi, se l'indicatore esegue un'azione infinita, tutti gli altri eventi per il suo simbolo non verranno mai eseguiti.
Programma | Esecuzione | Nota |
---|---|---|
Script | Nel suo thread, ci sono tanti thread di esecuzione quanti sono gli script | Uno script ciclico non può interrompere il lavoro di altri programmi |
Expert Advisor | Nel suo thread, ci sono tanti thread di esecuzione quanti sono gli EA | Uno script ciclico non può interrompere il lavoro di altri programmi |
Indicatore | Un thread di esecuzione per tutti gli indicatori su un simbolo. Tanti simboli con indicatori quanti sono i loro thread di esecuzione | Un ciclo infinito in un indicatore interromperà il lavoro di tutti gli altri indicatori su quel simbolo |
Creazione di un Expert Advisor di prova
Selezioniamo una strategia di trading che avrebbe senso parallelizzare e un algoritmo adatto.
Ad esempio, questa può essere una strategia semplice: compilare la sequenza da N delle ultime barre e trovare la sequenza più simile a questa nella cronologia. Sapendo in che punto il prezzo si è spostato, apriamo l'operazione pertinente.
Se la lunghezza della sequenza è relativamente piccola, questa strategia funzionerà molto rapidamente su MetaTrader 5 (pochi secondi). Ma se prendiamo una lunghezza grande - ad esempio, tutte le barre dell'intervallo di tempo M1 per le ultime 24 ore (che saranno 1440 barre) - e se cerchiamo indietro nello storico fino a un anno fa (circa 375.000 barre), ciò richiederà una notevole quantità di tempo. Eppure, questa ricerca può essere facilmente parallelizzata: è sufficiente dividere la cronologia in parti uguali sul numero di core del processore disponibili e assegnare ogni core per cercare una posizione specifica.
I parametri del sistema parallelo saranno i seguenti:
- MM è l'EA che implementa la strategia di trading modellata;
- il calcolo parallelo viene eseguito negli indicatori CM, generati automaticamente dall'Ea (ad es. utilizzando la combinazione 7);
- il codice di calcolo negli indicatori CM è posto all'interno di un ciclo infinito nell'OnInit;
- scambio di dati tra gli indicatori MM-EA e CM - effettuato attraverso le variabili globali del terminale.
Per comodità di sviluppo e successivo utilizzo, creeremo l'EA in modo tale che, a seconda delle impostazioni, possa funzionare come parallelo (con calcoli negli indicatori) e come un normale EA (cioè senza l'uso di indicatori). Il codice dell'Expert Advisor e-MultiThread ottenuto:
//+------------------------------------------------------------------+ //| e-MultiThread.mq5 | //+------------------------------------------------------------------+ input int Threads=1; // How many cores should be used input int MagicNumber=0; // Strategy parameters input int PatternLen = 1440; // The length of the sequence to analyze (pattern) input int PrognozeLen = 60; // Forecast length (bars) input int HistoryLen = 375000; // History length to search input double Lots=0.1; //+------------------------------------------------------------------+ class IndData { public: int ts,te; datetime start_time; double prognoze,rating; }; IndData Calc[]; double CurPattern[]; double Prognoze; int HistPatternBarStart; int ExistsPrognozeLen; uint TicksStart,TicksEnd; //+------------------------------------------------------------------+ #include <ThreadCalc.mqh> #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ int OnInit() { double rates[]; //--- Make sure there is enough history int HistNeed=HistoryLen+Threads+PatternLen+PatternLen+PrognozeLen-1; if(TerminalInfoInteger(TERMINAL_MAXBARS)<HistNeed) { Print("Change the terminal setting \"Max. bars in chart\" to the value, not lesser than ", HistNeed," and restart the terminal"); return(1); } while(Bars(_Symbol,_Period)<HistNeed) { Print("Insufficient history length (",Bars(_Symbol,_Period),") in the terminal, upload..."); CopyClose(_Symbol,_Period,0,HistNeed,rates); } Print("History length in the terminal: ",Bars(_Symbol,_Period)); //--- For a multi-core mode create computational indicators if(Threads>1) { GlobalVarPrefix="MultiThread_"+IntegerToString(MagicNumber)+"_"; GlobalVariablesDeleteAll(GlobalVarPrefix); ArrayResize(Calc,Threads); // Length of history for each core int HistPartLen=MathCeil(HistoryLen/Threads); // Including the boundary sequences int HistPartLenPlus=HistPartLen+PatternLen+PrognozeLen-1; string s; int snum=0; // Create all computational indicators for(int t=0; t<Threads; t++) { // For each indicator - its own currency pair, // it should not be the same as for the EA do s=SymbolName(snum++,false); while(s==_Symbol); int handle=iCustom(s,_Period,"i-Thread", GlobalVarPrefix,t,_Symbol,PatternLen, PatternLen+t*HistPartLen,HistPartLenPlus); if(handle==INVALID_HANDLE) return(1); Print("Indicator created, pair ",s,", handle ",handle); } } return(0); } //+------------------------------------------------------------------+ void OnTick() { TicksStart=GetTickCount(); // Fill in the sequence with the last bars while(CopyClose(_Symbol,_Period,0,PatternLen,CurPattern)<PatternLen) Sleep(1000); // If there is an open position, measure its "age" // and modify the forecast range for the remaining // planned life time of the deal CalcPrognozeLen(); // Find the most similar sequence in the history // and the forecast of the movement of its price on its basis FindHistoryPrognoze(); // Perform the necessary trade actions Trade(); TicksEnd=GetTickCount(); // Debugging information in PrintReport(); } //+------------------------------------------------------------------+ void FindHistoryPrognoze() { Prognoze=0; double MaxRating; if(Threads>1) { //-------------------------------------- // USE COMPUTATIONAL INDICATORS //-------------------------------------- // Look through all of the computational indicators for(int t=0; t<Threads; t++) { // Send the parameters of the computational task SetParam(t,"PrognozeLen",ExistsPrognozeLen); // "Begin computations" signal SetParam(t,"Query"); } for(int t=0; t<Threads; t++) { // Wait for results while(!ParamExists(t,"Answer")) Sleep(100); DelParam(t,"Answer"); // Obtain results double progn = GetParam(t, "Prognoze"); double rating = GetParam(t, "Rating"); datetime time[]; int start=GetParam(t,"PatternStart"); CopyTime(_Symbol,_Period,start,1,time); Calc [t].prognoze = progn; Calc [t].rating = rating; Calc [t].start_time = time[0]; Calc [t].ts = GetParam(t, "TS"); Calc [t].te = GetParam(t, "TE"); // Select the best result if((t==0) || (rating>MaxRating)) { MaxRating = rating; Prognoze = progn; } } } else { //---------------------------- // INDICATORS ARE NOT USED //---------------------------- // Calculate everything in the EA, into one stream FindPrognoze(_Symbol,CurPattern,0,HistoryLen,ExistsPrognozeLen, Prognoze,MaxRating,HistPatternBarStart); } } //+------------------------------------------------------------------+ void CalcPrognozeLen() { ExistsPrognozeLen=PrognozeLen; // If there is an opened position, determine // how many bars have passed since its opening if(PositionSelect(_Symbol)) { datetime postime=PositionGetInteger(POSITION_TIME); datetime curtime,time[]; CopyTime(_Symbol,_Period,0,1,time); curtime=time[0]; CopyTime(_Symbol,_Period,curtime,postime,time); int poslen=ArraySize(time); if(poslen<PrognozeLen) ExistsPrognozeLen=PrognozeLen-poslen; else ExistsPrognozeLen=0; } } //+------------------------------------------------------------------+ void Trade() { // Close the open position, if it is against the forecast if(PositionSelect(_Symbol)) { long type=PositionGetInteger(POSITION_TYPE); bool close=false; if((type == POSITION_TYPE_BUY) && (Prognoze <= 0)) close = true; if((type == POSITION_TYPE_SELL) && (Prognoze >= 0)) close = true; if(close) { CTrade trade; trade.PositionClose(_Symbol); } } // If there are no position, open one according to the forecast if((Prognoze!=0) && (!PositionSelect(_Symbol))) { CTrade trade; if(Prognoze > 0) trade.Buy (Lots); if(Prognoze < 0) trade.Sell(Lots); } } //+------------------------------------------------------------------+ void PrintReport() { Print("------------"); Print("EA: started ",TicksStart, ", finished ",TicksEnd, ", duration (ms) ",TicksEnd-TicksStart); Print("EA: Forecast on ",ExistsPrognozeLen," bars"); if(Threads>1) { for(int t=0; t<Threads; t++) { Print("Indicator ",t+1, ": Forecast ", Calc[t].prognoze, ", Rating ", Calc[t].rating, ", sequence from ",TimeToString(Calc[t].start_time)," in the past"); Print("Indicator ",t+1, ": started ", Calc[t].ts, ", finished ", Calc[t].te, ", duration (ms) ",Calc[t].te-Calc[t].ts); } } else { Print("Indicators were not used"); datetime time[]; CopyTime(_Symbol,_Period,HistPatternBarStart,1,time); Print("EA: sequence from ",TimeToString(time[0])," in the past"); } Print("EA: Forecast ",Prognoze); Print("------------"); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Send the "finish" command to the indicators if(Threads>1) for(int t=0; t<Threads; t++) SetParam(t,"End"); } //+------------------------------------------------------------------+
Il codice dell'indicatore computazionale i-Thread utilizzato dall'Expert Advisor:
//+------------------------------------------------------------------+ //| i-Thread.mq5 | //+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 //--- input parameters input string VarPrefix; // Prefix for global variables (analog to MagicNumber) input int ThreadNum; // Core number (so that indicators on different cores could // differentiate their tasks from the tasks of the "neighboring" cores) input string DataSymbol; // On what pair is the MM-EA working input int PatternLen; // Length of the sequence for analysis input int BarStart; // From which bar in the history the search for a similar sequence began input int BarCount; // How many bars of the history to perform a search on //--- indicator buffers double Buffer[]; //--- double CurPattern[]; //+------------------------------------------------------------------+ #include <ThreadCalc.mqh> //+------------------------------------------------------------------+ void OnInit() { SetIndexBuffer(0,Buffer,INDICATOR_DATA); GlobalVarPrefix=VarPrefix; // Infinite loop - so that the indicator always "listening", // for new commands from the EA while(true) { // Finish the work of the indicator, if there is a command to finish if(ParamExists(ThreadNum,"End")) break; // Wait for the signal to begin calculations if(!ParamExists(ThreadNum,"Query")) { Sleep(100); continue; } DelParam(ThreadNum,"Query"); uint TicksStart=GetTickCount(); // Obtain the parameters of the task int PrognozeLen=GetParam(ThreadNum,"PrognozeLen"); // Fill the sequence from the last bars while(CopyClose(DataSymbol,_Period,0,PatternLen,CurPattern) <PatternLen) Sleep(1000); // Perform calculations int HistPatternBarStart; double Prognoze,Rating; FindPrognoze(DataSymbol,CurPattern,BarStart,BarCount,PrognozeLen, Prognoze,Rating,HistPatternBarStart); // Send the results of calculations SetParam(ThreadNum,"Prognoze",Prognoze); SetParam(ThreadNum,"Rating",Rating); SetParam(ThreadNum,"PatternStart",HistPatternBarStart); SetParam(ThreadNum,"TS",TicksStart); SetParam(ThreadNum,"TE",GetTickCount()); // Signal "everything is ready" SetParam(ThreadNum,"Answer"); } } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { // The handler of this event is required return(0); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { SetParam(ThreadNum,"End"); } //+------------------------------------------------------------------+
L’Expert Advisor e l'indicatore utilizzano una libreria ThreadCalc.mqh comune.
Ecco il suo codice:
//+------------------------------------------------------------------+ //| ThreadCalc.mqh | //+------------------------------------------------------------------+ string GlobalVarPrefix; //+------------------------------------------------------------------+ // It finds the price sequence, most similar to the assigned one. // in the specified range of the history // Returns the estimation of similarity and the direction // of the further changes of prices in history. //+------------------------------------------------------------------+ void FindPrognoze( string DataSymbol, // symbol double &CurPattern[],// current pattern int BarStart, // start bar int BarCount, // bars to search int PrognozeLen, // forecast length // RESULT double &Prognoze, // forecast (-,0,+) double &Rating, // rating int &HistPatternBarStart // starting bar of the found sequence ) { int PatternLen=ArraySize(CurPattern); Prognoze=0; if(PrognozeLen<=0) return; double rates[]; while(CopyClose(DataSymbol,_Period,BarStart,BarCount,rates) <BarCount) Sleep(1000); double rmin=-1; // Shifting by one bar, go through all of the price sequences in the history for(int bar=BarCount-PatternLen-PrognozeLen; bar>=0; bar--) { // Update to eliminate the differences in the levels of price in the sequences double dr=CurPattern[0]-rates[bar]; // Calculate the level of differences between the sequences - as a sum // of squares of price deviations from the sample values double r=0; for(int i=0; i<PatternLen; i++) r+=MathPow(MathAbs(rates[bar+i]+dr-CurPattern[i]),2); // Find the sequence with the least difference level if((r<rmin) || (rmin<0)) { rmin=r; HistPatternBarStart = bar; int HistPatternBarEnd = bar + PatternLen-1; Prognoze=rates[HistPatternBarEnd+PrognozeLen]-rates[HistPatternBarEnd]; } } // Convert the bar number into an indicator system of coordinates HistPatternBarStart=BarStart+BarCount-HistPatternBarStart-PatternLen; // Convert the difference into the rating of similarity Rating=-rmin; } //==================================================================== // A set of functions for easing the work with global variables. // As a parameter contain the number of computational threads // and the names of the variables, automatically converted into unique // global names. //==================================================================== //+------------------------------------------------------------------+ string GlobalParamName(int ThreadNum,string ParamName) { return GlobalVarPrefix+IntegerToString(ThreadNum)+"_"+ParamName; } //+------------------------------------------------------------------+ bool ParamExists(int ThreadNum,string ParamName) { return GlobalVariableCheck(GlobalParamName(ThreadNum,ParamName)); } //+------------------------------------------------------------------+ void SetParam(int ThreadNum,string ParamName,double ParamValue=0) { string VarName=GlobalParamName(ThreadNum,ParamName); GlobalVariableTemp(VarName); GlobalVariableSet(VarName,ParamValue); } //+------------------------------------------------------------------+ double GetParam(int ThreadNum,string ParamName) { return GlobalVariableGet(GlobalParamName(ThreadNum,ParamName)); } //+------------------------------------------------------------------+ double DelParam(int ThreadNum,string ParamName) { return GlobalVariableDel(GlobalParamName(ThreadNum,ParamName)); } //+------------------------------------------------------------------+
Il nostro sistema di trading, in grado di utilizzare più di un core nel suo lavoro, è pronto!
Quando lo si utilizza, è necessario ricordare che in questo esempio abbiamo utilizzato indicatori CM con loop infiniti.
Se hai intenzione di eseguire altri programmi nel terminale insieme a questo sistema, devi assicurarti di usarli sulle coppie di valute che non vengono utilizzate dagli indicatori CM. Un buon modo per evitare un tale conflitto è modificare il sistema in modo che nei parametri di input dell'MM-EA sia possibile specificare direttamente le coppie di valute per gli indicatori CM.
Misura la velocità del lavoro dell'EA
Modalità normale
Apri il grafico EURUSD M1 e lancia il nostro Expert Advisor, creato nel capitolo precedente. Nelle impostazioni, specifica la lunghezza dei modelli come 24 ore (1440 barre min) e la profondità della ricerca nella cronologia - come 1 anno (375 000 barre).
Figura 4. Parametri di input dell’Expert Advisor
Parametro "Threads" impostato su 1. Ciò significa che tutti i calcoli dell'EA saranno prodotti in un unico thread (su un singolo core). Nel frattempo, non utilizzerà indicatori computazionali, ma calcolerà tutto da solo. In pratica, secondo il principio di lavoro di un normale EA.
Log della sua esecuzione:
Figura 6. Log dell’Expert Advisor (1 thread)
Modalità parallela
Ora eliminiamo questo EA e la posizione che ha aperta. Aggiungi di nuovo l'EA, ma questa volta con il parametro "Threads" uguale a 2.
Ora, l'EA dovrà creare e utilizzare nel suo lavoro 2 indicatori computazionali, occupando due core del processore. Log della sua esecuzione:
Figura 7. Log dell’Expert Advisor (2 thread)
Confronto della velocità
Analizzando entrambi questi log, deduciamo che il tempo approssimativo di esecuzione dell'EA è:
- 52 secondi in modalità normale;
- 27 secondi in modalità 2 core.
Quindi, eseguendo la parallelizzazione su una CPU a 2 core, siamo stati in grado di aumentare la velocità di un EA di 1,9 volte. Si può presumere che quando si utilizza un processore con una grande quantitàdi core, la velocità di esecuzione aumenterà ancora di più in proporzione al numero di core.
Controllo della correttezza del lavoro
Oltre al tempo di esecuzione, i log forniscono informazioni aggiuntive che ci permettono di verificare che tutte le misurazioni siano state eseguite correttamente. Le linee EA: Inizio lavoro ... fine lavoro ... " e "Indicatore ...: Inizio lavoro ... fine del lavoro ..." mostrano che gli indicatori hanno iniziato i loro calcoli non un secondo prima che l'EA desse loro quel comando.
Verifichiamo inoltre che non vi siano violazioni della strategia di trading durante il lancio dell'EA in modalità parallela. Secondo i log, è chiaro che il lancio dell'EA in modalità parallela è stato effettuato quasi immediatamente dopo il suo lancio in modalità normale. Ciò significa che le situazioni di mercato, in entrambi i casi, erano simili. I log mostrano che anche le date, trovate nello storico dei modelli, in entrambi i casi erano molto simili. Quindi va tutto bene: l'algoritmo della strategia funziona in entrambi i casi ugualmente bene.
Ecco i modelli descritti nelle situazioni dei log. Questa era la situazione corrente del mercato (lunghezza - 1440 barre al minuto) al momento dell'esecuzione dell'EA in modalità normale:
Figura 8. Situazione attuale del mercato
L'EA ha trovato nello storico il seguente modello simile:
Figura 9. Situazione di mercato simile
Quando si esegue l'EA in modalità parallela, lo stesso modello è stato trovato dall’"Indicatore 1". L’"indicatore 2", come segue dal log, stava cercando modelli nell'altro semestre dello storico, quindi ha trovato un modello simile diverso:
Figura 10. Situazione di mercato simile
Ed ecco come appaiono le variabili globali su MetaTrader 5 durante il lavoro dell’EA in modalità parallela:
Figura 11. Variabili globali
Lo scambio di dati tra l'EA e gli indicatori attraverso le variabili globali è stato implementato con successo.
Conclusione
In questo studio, abbiamo scoperto che è possibile parallelizzare algoritmi pieni di risorse con i mezzi standard di MetaTrader 5. E la soluzione che abbiamo trovato a questo problema è adatta per un uso conveniente nelle strategie di trading del mondo reale.
Questo programma, in un sistema multi-core, funziona veramente in modo proporzionalmente più veloce. Il numero di core in un processore cresce ogni anno ed è bene che i trader che utilizzano MetaTrader abbiano l'opportunità di utilizzare efficacemente queste risorse hardware. Possiamo tranquillamente creare delle strategie di trading più ingegnose, che saranno comunque in grado di analizzare il mercato in tempo reale.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/197
- 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