English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Calcoli paralleli su MetaTrader 5

Calcoli paralleli su MetaTrader 5

MetaTrader 5Esempi | 17 dicembre 2021, 14:43
223 0
ds2
ds2


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

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

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.

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

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:

  1. variabili globali del terminale;
  2. file;
  3. 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

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

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)

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

Figura 8. Situazione attuale del mercato

L'EA ha trovato nello storico il seguente modello simile:

Figura 9. Situazione di mercato 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

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

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

File allegati |
i-flood.mq5 (0.7 KB)
e-flood.mq5 (0.58 KB)
i-thread.mq5 (3.1 KB)
threadcalc.mqh (3.93 KB)
Disegnare i canali - Vista interna ed esterna Disegnare i canali - Vista interna ed esterna
Suppongo che non risulterà esagerato dire che i canali sono lo strumento più popolare per l'analisi del mercato e per prendere decisioni di trading dopo le medie mobili. Senza andare a fondo nella moltitudine delle strategie di trading che utilizzano i canali e i loro componenti, discuteremo le basi matematiche e l'implementazione pratica di un indicatore, il quale disegna un canale determinato da tre estremi sullo schermo del client terminal.
Progettare e implementare nuovi widget GUI basati sulla classe CChartObject Progettare e implementare nuovi widget GUI basati sulla classe CChartObject
Dopo l’articolo sull’Expert Advisor semiautomatico con interfaccia GUI che ho scritto in precedenza, ho realizzato che sarebbe stato opportuno migliorare l'interfaccia con alcune nuove funzionalità per gli indicatori e gli Expert Advisor più complessi. Dopo aver familiarizzato con le classi della libreria standard MQL5 ho implementato nuovi widget. Questo articolo descrive un processo di progettazione e implementazione di nuovi widget GUI MQL5 che possono essere utilizzati negli indicatori e negli Expert Advisor. I widget presentati nell'articolo sono CChartObjectSpinner, CChartObjectProgressBar e CChartObjectEditTable.
Creare Expert Advisor multipli sulla base dei modelli di trading Creare Expert Advisor multipli sulla base dei modelli di trading
L'utilizzo di un approccio orientato agli oggetti in MQL5 semplifica enormemente la creazione di Expert Advisor multivaluta/multisistema/multitimeframe. Immagina, il tuo unico EA esegue le operazioni su diverse dozzine di strategie di trading, su tutti gli strumenti disponibili e su tutti i possibili intervalli di tempo! Inoltre, l'EA è facilmente testabile nel tester e per tutte le strategie incluse nella sua composizione possiede uno o più sistemi di gestione del denaro funzionanti.
Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R
Alcuni trader eseguono automaticamente tutte le loro operazioni, mentre alcuni combinano operazioni automatiche e manuali in base all'output dei diversi indicatori. Facendo parte di quest'ultimo gruppo, avevo bisogno di uno strumento interattivo per valutare dinamicamente il rischio e i livelli di prezzo del rendimento direttamente dal grafico. Questo articolo presenterà un modo per implementare un Expert Advisor interattivo semiautomatico con rischio azionario predefinito e rapporto R/R. I parametri di rischio, R/R e dimensione del lotto dell’Expert Advisor possono essere modificati durante l’esecuzione sul pannello EA.