English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
Multibot in MetaTrader: Avvio di più robot da un singolo grafico

Multibot in MetaTrader: Avvio di più robot da un singolo grafico

MetaTrader 5Esempi | 25 settembre 2024, 14:51
615 0
Evgeniy Ilin
Evgeniy Ilin

Contenuto


Introduzione

Nel mondo dei mercati finanziari, i sistemi di trading automatizzati sono diventati parte integrante del processo decisionale. Questi sistemi possono essere configurati per analizzare il mercato, prendere decisioni di entrata e di uscita ed eseguire operazioni utilizzando regole e algoritmi predefiniti. Tuttavia, l'impostazione e l'esecuzione di robot su più grafici può essere un compito che richiede molto tempo. Ogni robot deve essere configurato individualmente per ogni grafico, il che richiede uno sforzo supplementare.

In questo articolo, vi mostrerò l'implementazione di un semplice modello che consente di creare un robot universale per più grafici in MetaTrader 4 e 5. Il nostro modello vi consentirà di allegare il robot ad un grafico, mentre il resto dei grafici verrà elaborato all'interno dell'EA. Pertanto, il nostro modello semplifica notevolmente il processo di impostazione e di esecuzione dei robot su più grafici, facendo risparmiare tempo e fatica ai trader. In questo articolo, considererò in dettaglio il processo di creazione di un robot di questo tipo in MQL5, dall'idea al test.


Definizione del problema e limiti di applicabilità

Questa idea mi è venuta non molto tempo fa, anche se da tempo osservo decisioni simili da parte di venditori professionisti. In altre parole, non sono né il primo né l'ultimo ad avere un'idea in questo campo, ma come sempre devono verificarsi alcune condizioni perché il programmatore possa iniziare a prendere tali decisioni. La ragione principale dello sviluppo di tali Expert Advisor nel negozio MQL5 è il desiderio di comodità per l'utente. Tuttavia, nel mio caso la motivazione era leggermente differente. La motivazione era che dovevo prima testare diverse strategie contemporaneamente per diversi strumenti, oppure la stessa strategia, ma per vedere le sue caratteristiche multi valuta.

Inoltre, un fattore molto importante quando si testa una strategia nel tester, soprattutto in modalità multi valuta, è la curva generale di redditività , che è alla base di qualsiasi valutazione dei sistemi di trading automatici durante il backtesting sui dati storici. Quando si testano i sistemi di trading separatamente su uno strumento, è piuttosto difficile combinare tali rapporti in un secondo momento. Non sono a conoscenza di strumenti del genere, almeno per MetaTrader 5. Per quanto riguarda la quarta versione del terminale, esiste uno strumento non ufficiale per tali manipolazioni. L'ho usato in almeno un articolo, ma ovviamente questo approccio non è preferibile.

Oltre al processo di test, è altrettanto importante il processo di trading automatico stesso e la sincronizzazione di EA simili che lavorano in modo indipendente, ciascuno sul proprio grafico. Se i grafici di questo tipo sono troppi, possono richiedere risorse informatiche aggiuntive , rallentando o peggiorando le prestazioni del trading e causando errori imprevisti e altri spiacevoli incidenti che possono avere un effetto negativo sul risultato finale del trading. Per ogni EA di questo tipo, dobbiamo escogitare ID ordine unici, protezione contro le richieste di server ad alta frequenza e molte altre cose che non sono ovvie a prima vista.

L'elaborazione della parte grafica dell'EA è un problema separato e molto delicato. Ora tutti i creatori di EA, più o meno abili, fanno almeno una versione minima con qualche indicazione sul grafico a cui l'EA è allegato. In questo modo l'EA appare più serio e ispira maggiore fiducia; infine, quasi sempre, la visualizzazione di alcune informazioni sul grafico per l'utente consente talvolta un controllo più efficace sul processo di trading dell'EA. Inoltre, se necessario, è possibile aggiungere elementi per il controllo manuale. Tutto questo si chiama interfaccia utente. La distribuzione di questi EA tramite grafici comporta un aumento esponenziale del carico dell’aggiornamento delle informazioni grafiche, testuali e numeriche nelle interfacce. Naturalmente, quando si utilizza un multi-template, si ha un'interfaccia che richiede la minima quantità di risorse per il terminale.

Naturalmente, tale modello non risolve tutti i problemi, ma comunque mi aiuta molto nei miei progetti. Io uso diversi robot e in generale, tutti gli approcci hanno il diritto di esistere, ma credo che molti programmatori alle prime armi possano trovare utile questo schema. Non è necessario copiarlo completamente, ma se lo desiderate potete facilmente adattarlo alle vostre esigenze. Il mio obiettivo non è quello di darvi qualcosa di straordinario, ma di mostrare e spiegare una delle opzioni per risolvere un problema del genere.


Differenze tra i terminali MetaTrader 4 e MetaTrader 5 in termini di utilizzo di un multibot

Ciò che mi piace dell'ultima MetaTrader 5 è la potenza del suo tester, che offre tutte le funzionalità necessarie per eseguire test su più strumenti contemporaneamente, purché si utilizzi l'approccio allo sviluppo degli EA descritto sopra. Il tester sincronizza automaticamente le quotazioni per tempo, fornendo una curva di profittabilità chiaramente sincronizzata su scala temporale. MetaTrader 4 non dispone di questa funzionalità. Credo che questo sia il suo più grande svantaggio. Tuttavia, vale la pena notare che MetaQuotes sta facendo del suo meglio per supportare il quarto terminale e la sua popolarità è ancora alta. Come utente attivo di MetaTrader 4, posso dire che queste carenze non sono così significative come potrebbero sembrare.

Il linguaggio MQL4 è stato recentemente aggiornato a MQL5. Questo significa che quando si scrivono modelli simili al nostro, le differenze nel codice saranno minime. È mia buona consuetudine cercare di implementare le cose per entrambi i terminali, quindi riceverete un modello per entrambi i terminali. Tali miglioramenti al vecchio terminale, tra l'altro, ci permettono di utilizzare le seguenti funzioni di cui abbiamo veramente bisogno:

  • CopyClose - richiesta dei prezzi di chiusura delle barre
  • CopyOpen - richiesta di prezzi di apertura delle barre
  • CopyHigh - richiesta dei massimi delle barre
  • CopyLow - richiesta dei minimi delle barre
  • CopyTime - richiesta dell'orario di apertura della barra
  • SymbolInfoTick - richiesta dell'ultimo tick in entrata per il simbolo richiesto
  • SymbolInfoInteger - richiesta di dati del simbolo, che possono essere descritti da numeri interi ed elenchi numerati.
  • SymbolInfo******* - altre funzioni necessarie

Queste caratteristiche sono presenti sia in MQL4 che in MQL5. Queste funzioni consentono di ottenere i dati delle barre per qualsiasi simbolo e periodo. Pertanto, l'unica differenza spiacevole tra il tester della quarta e della quinta versione è il fatto che queste funzioni nel quarto terminale funzioneranno solo per il grafico corrente su cui si sta effettuando il test, mentre il resto delle richieste informeranno semplicemente che non ci sono dati a causa delle peculiarità del tester MetaTrader 4. Perciò, quando si testa il nostro modello, si otterrà solo il trading sul simbolo selezionato e solo una delle curve di profitto per un singolo robot.

Nel quinto terminale, riceverete già il trading per tutti i simboli richiesti e la linea comune di profittabilità. Per quanto riguarda l'applicazione nel trading, quando si opera direttamente con tale robot in entrambi i terminali, si ricevono le prestazioni complete di tale modello. In altre parole, la differenza è solo nel tester. Ma anche in questi casi, si può fare a meno del fatto che, quando si crea un EA, è meglio iniziare con la versione per MetaTrader 5. Dopo aver effettuato tutti i test necessari, è possibile realizzare rapidamente la versione per MetaTrader 4.

Naturalmente, ci sono numerose differenze che non ho trattato. Voglio solo sottolineare l'importanza di alcune di esse, poiché queste sfumature devono essere conosciute quando si costruisce una struttura elaborata per un modello di questo tipo. MetaTrader 5 è sicuramente migliore del suo predecessore, ma tuttavia non ho alcun desiderio di sbarazzarmi del quarto terminale, perché in molte situazioni la sua richiesta di risorse computazionali non è così grande rispetto al quinto. Entrambi gli strumenti sono ancora validi.


Le sfumature della costruzione di un modello universale

Per costruire un modello di questo tipo, è necessario comprendere come funziona il terminale, cos'è un Expert Advisor e cos'è un grafico MetaTrader. Inoltre, è necessario comprendere che ogni grafico è un oggetto separato. Ogni grafico di questo tipo può essere associato a diversi indicatori e ad un solo EA. Potrebbero esserci svariati grafici identici. Di solito vengono creati molti grafici per eseguire diversi EA su un unico simbolo e periodo o per eseguire copie multiple di un EA con impostazioni differenti. Comprendendo queste sottigliezze, dovremmo giungere alla conclusione che per abbandonare i grafici multipli a favore del nostro modello, dovremo implementare tutto questo all'interno del nostro modello. Questo può essere rappresentato sotto forma di diagramma:

struttura degli oggetti

Un discorso a parte va fatto per i tick. Lo svantaggio di questo approccio è che non saremo in grado di sottoscrivere il gestore per la comparsa di un nuovo tick per ogni grafico. Dovremo applicare i tick dal grafico su cui lavora il nostro robot o utilizzare il timer. In definitiva, ciò comporterà momenti spiacevoli per i robot che lavorano con i tick, che sono i seguenti:

  • Dovremo scrivere dei gestori OnTick personalizzati
  • Questi gestori dovranno essere implementati come un derivato di OnTimer
  • I tick non saranno perfetti perché OnTimer funziona con un ritardo (il valore del ritardo non è importante, ma lo è la sua presenza).
  • Per ottenere i tick, è necessario utilizzare la funzione SymbolInfoTick

Per chi conta ogni millisecondo, questo può essere un momento irresistibile, soprattutto per chi ama l'arbitraggio. Tuttavia, non lo enfatizzo nel mio modello. Nel corso degli anni, dopo aver costruito diversi sistemi, sono arrivato al paradigma della barra di trading. Ciò significa che le operazioni di trading e gli altri calcoli avvengono per lo più quando compare una nuova barra. Questo approccio presenta una serie di evidenti vantaggi:

  • L'imprecisione nel determinare l'inizio di una nuova barra non influisce significativamente sul trading.
  • Più lungo è il periodo della barra, minore è questa influenza.
  • La discretizzazione sotto forma di barre fornisce un aumento nella velocità di test attraverso ordini di grandezza
  • La stessa qualità dei test sia su tick veri che su quelli artificiali

L'approccio insegna un certo paradigma di costruzione degli EA. Questo paradigma elimina molti problemi associati ai tick degli EA, velocizza il processo di test, fornisce una maggiore aspettativa matematica di profitto, che è l'ostacolo principale, e fa risparmiare molto tempo e potenza di calcolo. Penso che si possano trovare molti altri vantaggi, ma credo che questo sia sufficiente nel contesto di questo articolo.

Per implementare il nostro modello, non è necessario implementare l'intera struttura dell'area di lavoro del terminale di trading all'interno del nostro modello, ma è sufficiente implementare un grafico separato per ogni robot. Questa non è la struttura più ottimale, ma se siamo d'accordo che ogni singolo strumento sarà presente solo una volta nell'elenco degli strumenti, allora questa ottimizzazione non è necessaria. Il risultato sarà:

la nostra realizzazione

Abbiamo realizzato la struttura più semplice per l'implementazione dei grafici. Ora è il momento di pensare agli input di un tale modello e soprattutto, a come tenere conto del numero dinamico di grafici e di EA per ogni situazione nell'ambito delle possibilità consentite dal linguaggio MQL5. L'unico modo per risolvere questo problema è utilizzare variabili di input di tipo stringa. Una stringa ci permette di memorizzare una grande quantità di dati. Infatti, per descrivere tutti i parametri necessari per un modello di questo tipo, avremo bisogno di array dinamici nei dati di input. Naturalmente, nessuno implementerà queste cose semplicemente perché poche persone sfrutterebbero tali opportunità. La stringa è il nostro array dinamico, in cui possiamo inserire tutto ciò che vogliamo. Quindi usiamola. Per il mio modello più semplice, ho deciso di introdurre tre variabili in questo modo:

  • Charts - i nostri grafici (elenco)
  • Charts Lots - lotti per il trading (elenco)
  • Charts Timeframes - periodi dei grafici (elenco)

In generale, possiamo combinare tutti questi dati in un'unica stringa, ma la sua struttura sarà complessa e sarà difficile per un potenziale utente capire come descrivere correttamente i dati. Inoltre, sarà molto facile commettere errori nella compilazione e si potranno verificare molte cose spiacevoli durante l'utilizzo, per non parlare dell'incredibile complessità della funzione di conversione, che estrarrà questi dati dalle stringhe. Ho visto soluzioni simili tra i venditori e in generale hanno fatto tutto bene. Tutti i dati sono semplicemente elencati separati da virgole. All'inizio dell'EA, questi dati vengono estratti da una stringa utilizzando funzioni speciali e inseriti negli array dinamici corrispondenti, che vengono poi utilizzati nel codice. Anche noi seguiremo questo percorso. Possiamo aggiungere altre stringhe simili con regole di enumerazione identiche. Ho deciso di utilizzare ":" come separatore. Se si utilizza una virgola, non è chiaro come trattare gli array double come Chart Lots. È possibile aggiungere altre variabili stringa e, in generale, è possibile costruire un modello ancora più completo e versatile, ma il mio compito qui è solo quello di mostrare come implementare tutto ciò e fornire una prima versione del modello modificabile in modo semplice e veloce.

Non è sufficiente implementare tali array, è necessario anche implementare variabili comuni, ad esempio:

  • Work Timeframe For Unsigned - un periodo grafico quando non specificato.
  • Fix Lot For Unsigned - un lottaggio quando non specificato

L'elenco Charts dovrebbe essere riempito. La stessa azione è facoltativa per Chart Lots e Charts Timeframes. Ad esempio, si possono prendere singoli lotti per tutti i grafici e lo stesso periodo per tutti i grafici. Una funzionalità simile sarà implementata nel nostro modello. È auspicabile applicare queste regole di implementazione ogni qualvolta sia possibile per garantire brevità e chiarezza nell'impostazione dei parametri di input di un EA costruito sulla base di tali modelli. 

Definiamo ora alcune variabili più importanti per un'implementazione minima di questo modello:

  • Last Bars Count - il numero delle ultime barre di un grafico che memorizziamo per ogni grafico.
  • Deposit For Lot - deposito per l'utilizzo di un determinato lotto.
  • First Magic - ID univoco per la separazione delle operazioni di un EA

Credo che la prima variabile sia abbastanza chiara. La seconda variabile è molto più difficile da comprendere. Questo è il modo in cui regolo il lotto automatico nei miei EA. Se lo imposto a "0", comunico all'algoritmo che deve negoziare solo un lotto fisso specificato nella stringa corrispondente o in una variabile condivisa che abbiamo considerato in precedenza. Altrimenti, imposto il deposito richiesto in modo da poter applicare il lotto specificato nelle impostazioni. È facile capire che con un deposito minore o maggiore, questo lotto cambia il suo valore in base all'equazione:

  • Lotto = Lotto in input * ( Deposito Corrente / Deposito Per Lotto )

Penso che ora tutto dovrebbe essere chiaro. Se vogliamo un lotto fisso, impostiamo zero, mentre negli altri casi adattiamo il deposito nelle impostazioni di input ai rischi. Penso che sia economico e allegro. Se necessario, è possibile modificare l'approccio alla valutazione del rischio per il lotto automatico, ma personalmente preferisco questa opzione, non ha senso pensarci troppo.

Vale la pena menzionare la sincronizzazione e in particolare sul problema dell'impostazione del Expert Magic Number. Quando si fa trading con gli EA o anche in forma mista, tutti i programmatori che si rispettino prestano particolare attenzione a questa particolare variabile. Quando si utilizzano più EA, è molto importante assicurarsi che ognuno di questi EA abbia un ID univoco. Altrimenti, quando si lavora con ordini, transazioni o posizioni, si creerà una confusione totale e le strategie smetteranno di funzionare correttamente, e nella maggior parte dei casi smetteranno di funzionare completamente. Spero di non dover spiegare il perché. Ogni volta che un EA viene inserito sul grafico, è necessario configurare questi ID e assicurarsi che non si ripetano. Anche un singolo errore può portare a conseguenze disastrose. Inoltre, se si chiude accidentalmente il grafico con l'EA, è necessario riconfigurarlo nuovamente. Di conseguenza, la probabilità di errore aumenta notevolmente. Inoltre, è molto sgradevole sotto molti altri aspetti. Ad esempio, se si chiude il grafico e si dimentica l'ID utilizzato. In questo caso, dovrete scavare nella cronologia di trading per cercarla. Senza l'ID, l'EA appena riavviato potrebbe funzionare in modo errato e potrebbero accadere molte altre cose spiacevoli.

L'uso di un modello come il mio ci libera da questo controllo e minimizza i possibili errori, poiché dobbiamo impostare solo l'ID iniziale nelle impostazioni dell'EA, mentre gli altri ID saranno generati automaticamente utilizzando un incremento e assegnati alle copie corrispondenti degli EA. Questo processo avverrà automaticamente ad ogni riavvio. In ogni caso, ricordare un solo ID di partenza è molto più facile che ricordare un ID a caso nel bel mezzo della strada.


Scrivere un modello universale

È il momento di implementare il modello. Cercherò di omettere gli elementi eccessivi, in modo che chiunque abbia bisogno di questo modello possa scaricare e vedere il resto nel codice sorgente. Qui mostrerò solo le cose che sono direttamente collegate alle nostre idee. I livelli di stop e altri parametri sono definiti dagli utenti. La mia implementazione è disponibile nel codice sorgente. Per prima cosa, definiamo le nostre variabili di input, che ci serviranno sicuramente:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts
input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots
input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes
input int LastBars=10;//Last Bars Count
input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned
input double RepurchaseLotE=0.01;//Fix Lot For Unsigned
input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix)
input int MagicE=156;//First Magic

Qui si può vedere un esempio di compilazione di variabili stringa che riflettono i nostri array dinamici, proprio come nell'esempio delle variabili condivise. A proposito, questo codice sarà lo stesso sia in MQL4 che in MQL5. Ho cercato di rendere tutto il più simile possibile.

Ora decidiamo come ottenere i nostri dati delle stringhe. Questo verrà fatto dalla funzione corrispondente, ma prima creeremo degli array in cui la nostra funzione aggiungerà i dati ottenuti dalle stringhe:

//+------------------------------------------------------------------+
//|Arrays                                                            |
//+------------------------------------------------------------------+
string S[];// Symbols array
double L[];//Lots array
ENUM_TIMEFRAMES T[];//Timeframes array

La funzione seguente riempie questi array:

//+------------------------------------------------------------------+
//| Fill arrays                                                      |
//+------------------------------------------------------------------+
void ConstructArrays()
   {
      int SCount=1;
      for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools
         {
         if (SymbolsE[i] == ':')
            {
            SCount++;
            }
         }
      ArrayResize(S,SCount);//set the size of the character array
      ArrayResize(CN,SCount);//set the size of the array to use bars for each character
      int Hc=0;//found instrument index
      for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools
         {
         if (i == 0)//if we just started
            {
            int LastIndex=-1;
            for (int j = i; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i,LastIndex);
               Hc++;
               }
            else
               {
               S[Hc]=SymbolsE;
               Hc++;
               }
            }          
         if (SymbolsE[i] == ':')
            {
            int LastIndex=-1;
            for (int j = i+1; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1));
               Hc++;
               }
            else
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1));
               Hc++;
               }               
            }
         }
      for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars
         {
         CN[i]=LastBars;
         }
      ConstructLots();
      ConstructTimeframe();         
   }

In breve, qui viene calcolata la quantità di dati in una stringa grazie ai separatori. In base al primo array, viene impostata la dimensione di tutti gli altri array in modo simile ad un array con i simboli, dopodiché i simboli vengono riempiti per primi, seguiti da funzioni come ConstructLots e ConstructTimeframe. La loro implementazione è simile a quella di questa funzione, con alcune differenze. È possibile vedere la loro implementazione nel codice sorgente. Non li ho aggiunti all'articolo per non mostrare il codice duplicato.

Ora dobbiamo creare le classi appropriate per il grafico virtuale e il robot virtuale ad esso collegato. Iniziamo definendo che i grafici virtuali e gli EA saranno memorizzati in array:

//+------------------------------------------------------------------+
//| Charts & experts pointers                                        |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];

Partiamo dalla classe del grafico:

//+------------------------------------------------------------------+
//| Chart class                                                      |
//+------------------------------------------------------------------+
class Chart
   {
   public:
   datetime TimeI[];
   double CloseI[];
   double OpenI[];
   double HighI[];
   double LowI[];
   string BasicSymbol;//the base instrument that was extracted from the substring
   double ChartPoint;//point size of the current chart
   double ChartAsk;//Ask
   double ChartBid;//Bid
   datetime tTimeI[];//auxiliary array to control the appearance of a new bar
   static int TCN;//tcn
   string CurrentSymbol;//symbol
   ENUM_TIMEFRAMES Timeframe;//timeframe
   int copied;//how much data is copied
   int lastcopied;//last amount of data copied
   datetime LastCloseTime;//last bar time
   MqlTick LastTick;//last tick fos this instrument
   
   Chart()
      {
      ArrayResize(tTimeI,2);
      }
   
   void ChartTick()//this chart tick
      {
      SymbolInfoTick(CurrentSymbol,LastTick);
      ArraySetAsSeries(tTimeI,false);
      copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI);
      ArraySetAsSeries(tTimeI,true);
      if ( copied == 2 && tTimeI[1] > LastCloseTime )
         {
         ArraySetAsSeries(CloseI,false);                        
         ArraySetAsSeries(OpenI,false);                           
         ArraySetAsSeries(HighI,false);                        
         ArraySetAsSeries(LowI,false);                              
         ArraySetAsSeries(TimeI,false);                                                            
         lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI);
         lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI);   
         lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI);   
         lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI);
         lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI);
         ArraySetAsSeries(CloseI,true);
         ArraySetAsSeries(OpenI,true);
         ArraySetAsSeries(HighI,true);                        
         ArraySetAsSeries(LowI,true);
         ArraySetAsSeries(TimeI,true);         
         LastCloseTime=tTimeI[1];
         }
      ChartBid=LastTick.bid;
      ChartAsk=LastTick.ask;
      ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT);
      }
   };
int Chart::TCN = 0;

La classe ha una sola funzione, che controlla l'aggiornamento dei tick e delle barre, oltre ai campi necessari per identificare alcuni parametri necessari di un particolare grafico. Mancano alcuni parametri. Se lo si desidera, è possibile aggiungere quelli mancanti aggiungendo il loro aggiornamento, ad esempio, come l'aggiornamento di ChartPoint. Gli array delle barre sono realizzate in stile MQL4. Trovo ancora molto comodo lavorare con array predeterminati in MQL4. È molto comodo quando si sa che la barra zero è la barra corrente. Comunque, questa è solo la mia visione. Siete liberi di seguire quello che preferite.

Ora dobbiamo descrivere la classe di un EA virtuale separato:

//+------------------------------------------------------------------+
//| Bot instance class                                               |
//+------------------------------------------------------------------+
class BotInstance//expert advisor object
   {
   public:
   CPositionInfo  m_position;// trade position object
   CTrade         m_trade;// trading object   
   ///-------------------this robot settings----------------------
   int MagicF;//Magic
   string CurrentSymbol;//Symbol
   double CurrentLot;//Start Lot
   int chartindex;//Chart Index
   ///------------------------------------------------------------   
      
   
   ///constructor
   BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index
      {
      chartindex=chartindex0;
      MagicF=MagicE+index;
      CurrentSymbol=Charts[chartindex].CurrentSymbol;
      CurrentLot=L[index];
      m_trade.SetExpertMagicNumber(MagicF);
      }
   ///
   
   void InstanceTick()//bot tick
      {
      if ( bNewBar() ) Trade();
      }
      
   private:
   datetime Time0;
   bool bNewBar()//new bar
      {
      if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 )
         {
         if (Time0 != 0)
            {
            Time0=Charts[chartindex].TimeI[1];
            return true;
            }
         else
            {
            Time0=Charts[chartindex].TimeI[1];
            return false;
            }
         }
      else return false;
      }
      
   //////************************************Main Logic********************************************************************
   void Trade()//main trade function
      {
      //Close[0]   -->   Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart
      //Open[0]   -->   Charts[chartindex].OpenI[0] -----------------------------------------------------------------------
      //High[0]   -->   Charts[chartindex].HighI[0] -----------------------------------------------------------------------
      //Low[0]   -->   Charts[chartindex].LowI[0] -------------------------------------------------------------------------
      //Time[0]   -->   Charts[chartindex].TimeI[0] -----------------------------------------------------------------------      

      if ( true )
         {
            CloseBuyF();
            //CloseSellF();       
         }
      if ( true )
         {
            BuyF();
            //SellF(); 
         }

      }
      
   double OptimalLot()//optimal lot calculation
      {
      if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE);
      else return CurrentLot;
      }
      
   //here you can add functionality or variables if the trading function turns out to be too complicated
   //////*******************************************************************************************************************
   
   ///trade functions
   int OrdersG()//the number of open positions / orders of this virtual robot
      {
      ulong ticket;
      bool ord;
      int OrdersG=0;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol )
            {
            OrdersG++;
            }
         }
      return OrdersG;
      }
   
   /////////********/////////********//////////***********/////////trade function code block
   void BuyF()//buy market
      {
      double DtA;
      double CorrectedLot;
   
      DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime
      if ( (DtA > 0 || DtA < 0) )
         {
         CorrectedLot=OptimalLot(Charts[chartindex]);
         if ( CorrectedLot > 0.0 )
            {
            //try buy logic
            }            
         }
      }
      
   void SellF()//sell market
      {
      //Same logic
      }

   void CloseSellF()//close sell position
      {
      ulong ticket;
      bool ord;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL 
         && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol )
            {
            //Close Sell logic
            }
         }    
      }
      
   void CloseBuyF()//close buy position
      {
      //same logic 
      }        
      
   bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot
      {
      int MagicT[];
      ArrayResize(MagicT,magiccount);
      for ( int i=0; i<magiccount; i++ )
         {
         MagicT[i]=MagicE+i;
         }
      for ( int i=0; i<ArraySize(MagicT); i++ )
         {
         if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true;
         }
      return false;
      }
   /////////********/////////********//////////***********/////////end trade function code block
   };

Ho eliminato alcune logiche ripetitive per ridurre la quantità di codice. Questa è la classe in cui deve essere implementato l'intero algoritmo del vostro EA. Le principali funzionalità presenti in questa classe:

  • Trade() - funzione di trading principale che viene richiamata nel gestore delle barre per il grafico corrispondente
  • BuyF() - funzione di acquisto a mercato
  • SellF() - funzione di vendita a mercato
  • CloseBuyF() - funzione di chiusura delle posizioni buy a mercato
  • CloseSellF() - funzione di chiusura delle posizioni di vendita a mercato

Questo è l'insieme minimo di funzioni per dimostrare il trading a barre. Per questa dimostrazione, dobbiamo solo aprire una posizione qualsiasi e chiuderla nella barra successiva. Ciò è sufficiente nell'ambito di questo articolo. Questa classe offre alcune funzionalità aggiuntive che dovrebbero completare la comprensione:

  • OrdersG() - conteggio delle posizioni aperte su un simbolo specifico collegato al grafico
  • OptimalLot() - prepara un lotto prima di inviarlo alla funzione di trading (selezionando un lotto fisso o calcolando un lotto automatico).
  • bOurMagic() - verifica la conformità delle transazioni della cronologia con l'elenco di quelle consentite (solo per l'ordinamento di una cronologia personalizzata)

Queste funzioni possono essere necessarie per implementare la logica di trading. Sarebbe anche ragionevole ricordare il nuovo gestore della barra:

  • InstanceTick() - simulazione di tick su un'istanza dell’EA separata
  • bNewBar() - attributo per verificare la comparsa di una nuova barra (utilizzato all'interno di InstanceTick)

Se l’attributo mostra una nuova barra, viene attivata la funzione Trade. Questa è la funzione in cui deve essere impostata la logica di trading principale. La connessione con il grafico corrispondente viene effettuata utilizzando la variabile chartindex assegnata al momento della creazione dell'istanza. In questo modo ogni istanza di EA conosce il grafico da cui deve prendere una quotazione. 

Consideriamo ora il processo di creazione dei grafici virtuali e degli EA stessi. I grafici virtuali vengono creati per primi:

//+------------------------------------------------------------------+
//| Creation of graph objects                                        |
//+------------------------------------------------------------------+
void CreateCharts()
   {
   bool bAlready;
   int num=0;
   string TempSymbols[];
   string Symbols[];
   ConstructArrays();//array preparation
   int tempcnum=CN[0];
   Chart::TCN=tempcnum;//required number of stored bars for all instruments
   for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph
      {
      Charts[j] = new Chart();
      Charts[j].lastcopied=0;
      ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays
      ArrayResize(Charts[j].OpenI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].HighI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].LowI,tempcnum+2);//-----------------------------------
      ArrayResize(Charts[j].TimeI,tempcnum+2);//----------------------------------
      Charts[j].CurrentSymbol = S[j];//symbol
      Charts[j].Timeframe = T[j];//timeframe
      }
   ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots      
   }

Dopo aver creato i grafici e impostato la dimensione dell'array con gli EA virtuali, è necessario creare le istanze degli EA stessi e implementare la connessione degli EA virtuali con i grafici:

//+------------------------------------------------------------------+
//| create and hang all virtual robots on charts                     |
//+------------------------------------------------------------------+
void CreateInstances()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      for (int j = 0; j < ArraySize(Charts); j++)
         {
         if ( Charts[j].CurrentSymbol == S[i] )
            {
            Bots[i] = new BotInstance(i,j);
            break;
            } 
         }
      }
   }

La connessione viene effettuata utilizzando l'indice "j" impostato in ogni istanza dell'EA virtuale al momento della sua creazione. La corrispondente variabile mostrata sopra è evidenziata. Naturalmente, tutto questo può essere fatto in molti modi e in modo molto più elegante, ma credo che la cosa principale sia che l'idea generale sia chiara. 

Non resta che mostrare come vengono simulati i tick su ogni grafico e sull'EA ad esso associato:

//+------------------------------------------------------------------+
//| All bcharts & all bots tick imitation                            |
//+------------------------------------------------------------------+
void AllChartsTick()
   {
   for (int i = 0; i < ArraySize(Charts); i++)
      {
      Charts[i].ChartTick();
      }
   }

void AllBotsTick()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick();
      }
   }

L'unica cosa che voglio far notare è che questo modello è stato ottenuto rielaborando il mio modello più complesso, che era destinato a scopi molto più seri, quindi potrebbero esserci elementi eccessivi qua e là. Penso che si possano facilmente rimuovere e rendere il codice più ordinato, se si vuole.

Oltre al modello, c'è un'interfaccia semplice che, a mio avviso, può tornare utile, ad esempio, quando si scrive un ordine in qualità di freelance o per altri scopi:


Ho lasciato dello spazio libero in questa interfaccia, che sarà sufficiente per tre voci nel caso in cui non abbiate abbastanza spazio. Se necessario, è possibile espandere o modificare completamente la struttura. Se vogliamo aggiungere i tre campi mancanti in questo particolare esempio, dobbiamo trovare i seguenti punti nel codice:

//+------------------------------------------------------------------+
//| Reserved elements                                                |
//+------------------------------------------------------------------+

   "template-UNSIGNED1",//UNSIGNED1
   "template-UNSIGNED2",//UNSIGNED2
   "template-UNSIGNED3",//UNSIGNED3

   //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1
   //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2
   //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3

   ////////////////////////////
   //TempText="UNSIGNED1 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED2 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED3 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);
   //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText);
   ///////////////////////////
 

Le prime tre voci assegnano i nomi dei nuovi elementi dell'interfaccia, le seconde tre sono usate quando si crea l'interfaccia all'inizio dell'EA, mentre le ultime tre sono usate nella funzione per aggiornare le informazioni sull'interfaccia. Ora è il momento di testare le prestazioni di entrambi i modelli. Il visualizzatore del tester sarà sufficiente per una dimostrazione visiva. Mostrerò solo l'opzione per MetaTrader 5, perché il suo visualizzatore è migliore. Inoltre, il risultato del lavoro mostrerà chiaramente tutto ciò che è necessario per confermare l'efficienza:

controllo utilizzando la visualizzazione del tester di MetaTrader 5


Come potete vedere, abbiamo caricato tutti e sette i grafici delle principali coppie Forex. Il registro di visualizzazione mostra che il trading è in corso per tutti i simboli elencati. Il trading viene svolto in modo indipendente, come richiesto. In altre parole, gli EA operano ognuno sul proprio grafico e non interagiscono affatto


Conclusione

In questo articolo abbiamo esaminato le principali sfumature della costruzione di modelli universali per i terminali MetaTrader 4 e MetaTrader 5, abbiamo realizzato un modello semplice ma funzionante, analizzato i punti più importanti del suo funzionamento e anche confermato la sua fattibilità utilizzando il visualizzatore del tester di MetaTrader 5. Penso che ormai sia abbastanza ovvio che un modello come questo non sia così complicato. In generale, è possibile realizzare diverse implementazioni di tali modelli, ma è ovvio che tali modelli possono essere completamente diversi pur rimanendo applicabili. La cosa principale è comprendere le sfumature di base della costruzione di queste strutture. Se necessario, è possibile rielaborare i modelli per uso personale.


Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/12434

File allegati |
MultiTemplate.mq4 (93.94 KB)
MultiTemplate.mq5 (91.41 KB)
Gli Swap (Parte I): Posizioni di Blocco e Sintetiche Gli Swap (Parte I): Posizioni di Blocco e Sintetiche
In questo articolo cercherò di ampliare il concetto classico dei metodi di swap trading. Spiegherò perché sono giunto alla conclusione che questo concetto merita un'attenzione particolare ed è assolutamente da studiare.
Sviluppare un Expert Advisor per il trading da zero (Parte 31): Verso il futuro (IV) Sviluppare un Expert Advisor per il trading da zero (Parte 31): Verso il futuro (IV)
Continuiamo a rimuovere le parti separate dal nostro EA. Questo è l'ultimo articolo di questa serie. L'ultima cosa da rimuovere è il sistema audio. Questo può creare un po' di confusione se non si è seguita questa serie di articoli.
Pattern con Esempi (Parte I): Massimi Multipli Pattern con Esempi (Parte I): Massimi Multipli
Questo è il primo articolo di una serie relativa ai pattern di inversione nel quadro del trading algoritmico. Inizieremo con la famiglia di pattern più interessante, che ha origine dai pattern Doppio Massimo e Doppio Minimo.
Sviluppare un Expert Advisor da zero (Parte 30): CHART TRADE come indicatore? Sviluppare un Expert Advisor da zero (Parte 30): CHART TRADE come indicatore?
Oggi utilizzeremo nuovamente Chart Trade, ma questa volta si tratterà di un indicatore su grafico che può essere presente o meno sul grafico.