English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida"

Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida"

MetaTrader 5Exemplos | 24 março 2014, 09:14
1 817 0
Anatoli Kazharski
Anatoli Kazharski

Introdução

Este artigo descreverá uma implementação de uma abordagem simples, adequada para um Consultor Especialista multi-moeda. Isso significa que você será capaz de montar o Consultor Especialista para testes/negócios sob condições idênticas, mas com parâmetros diferentes para cada símbolo. Como um exemplo, criaremos um padrão para dois símbolos, mas de forma a ser capaz de somar símbolos adicionais, se necessário, fazendo pequenas alterações ao código.

Um padrão multi-moeda pode ser implementado no MQL5 de várias formas:

  • Podemos utilizar um padrão onde o Consultor Especialista seja guiado pelo tempo, sendo capaz de realizar verificações mais precisas nos intervalos de tempo especificados na função OnTimer().

  • De forma alternativa, como em todos os Consultores Especialistas apresentados nos artigos anteriores da série, a verificação pode ser feita na função OnTick() na qual o Consultor Especialista dependerá de pontos para o símbolo atual que estiver operando. Então, se há uma barra completa em outro símbolo, enquanto ainda não há ponto para o símbolo atual, o Consultor Especialista somente realizará uma verificação assim que houver um novo ponto para o símbolo atual.

  • Ainda há outra opção interessante sugerida por seu autor Konstantin Gruzdev (Lizar). Ela emprega um modelo de evento: utilizando a função OnChartEvent(), um Consultor Especialista obtêm eventos que são reproduzidos por agentes do indicador localizados nos gráficos de símbolos envolvidos nos testes/negócios. Agentes indicadores podem reproduzir uma nova barra e eventos de ponto dos símbolos aos quais estão anexados. Esse tipo de indicador (EventsSpy.mq5) pode ser obtido no fim do artigo. Precisaremos dele para a operação do Consultor Especialista.


Desenvolvimento do Consultor Especialista

O Consultor Especialista apresentou o artigo "Guia rápido do MQL5: Usando indicadores para definir condições de negócios em Consultores Especialistas" servirá como um modelo. Eu já excluí dele tudo o que tinha relação com o painel de informações e também simplifiquei a condição de abertura da posição conforme implementado no artigo anterior intitulado "Guia rápido do MQL5: Desenvolvendo uma estrutura para um sistema de negócios baseado na estratégia de tela tripla". Já que pretendemos criar um Consultor Especialista para dois símbolos, cada um deles precisará de seu próprio conjunto de parâmetros externos:

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

Os parâmetros externos serão colocados em arranjos cujos tamanhos dependerão do número de símbolos utilizado. O número de símbolos utilizado no Consultor Especialista será determinado pelo valor da constante NUMBER_OF_SYMBOLS que precisamos criar no início do arquivo:

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

Vamos criar os arranjos que serão necessários para armazenar os parâmetros externos:

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

As funções de inicialização do arranjo serão colocadas no arquivo InitArrays.mqh incluso. Para inicializar o arranjo Symbols[], criaremos a função GetSymbol(). Ela obterá o nome do símbolo dos parâmetros externos e se o símbolo estiver disponível na lista de símbolos no servidor, ele será escolhido na janela Market Watch (Observação de mercado). Além disso, se o símbolo necessário não puder ser encontrado no servidor, a função retornará com uma cadeia vazia e o Diário do Consultor Especialista será atualizado de acordo.

Abaixo está o código da função GetSymbol():

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

O arranjo Symbols[] será inicializado da função GetSymbols():

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

Além disso, o implementaremos de tal forma que um valor vazio nos parâmetros externos de um certo símbolo indicará que o bloco correspondente não será envolvido nos testes/negócios. Isso é necessário a fim de ser capaz de otimizar parâmetros para cada símbolo separadamente, enquanto exclui completamente o resto.

Todos os outros arranjos de parâmetros externos são inicializados da mesma forma. Em outras palavras, precisamos criar uma função diferente para cada arranjo. Os códigos de todas essas funções são fornecidos abaixo:

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

Vamos criar agora uma função que nos ajudará a inicializar de forma prática todos os arranjos de parâmetros externos de uma vez - a função InitializeInputParameters():

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

Seguindo a inicialização dos arranjos de parâmetros externos, podemos seguir para a parte principal. Alguns procedimentos como a obtenção de identificadores de indicador, seus valores e informações de preço, assim como verificar quanto a uma nova barra, etc. serão realizados em ciclos consecutivamente para cada símbolo. Esse é o motivo pelo qual valores de parâmetros externos serem organizados em arranjos. Então, tudo será feito nos ciclos como a seguir:

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

Mas antes de começarmos a modificar as funções existentes e criarmos novas, vamos também criar arranjos que serão necessários neste padrão.

Precisaremos de dois arranjos para os identificadores de indicador:

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

Esses dois arranjos serão primeiramente inicializados em valores inválidos:

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

Arranjos de dados de preço e valores de indicador serão agora acessados usando estruturas:

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

Agora, se você precisa obter o valor do indicador na última barra completa do primeiro símbolo na lista, você deve escrever algo como isso:

double indicator_value=indicator[0].value[1];

Também precisamos criar arranjos ao invés das variáveis que foram anteriormente utilizadas na função CheckNewBar():

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

Então organizamos os arranjos. Agora precisamos modificar várias funções de acordo com as mudanças feitas acima. Vamos começar com a função GetIndicatorHandles():

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

Agora, independente do número de símbolos utilizados nos testes/negócios, o código da função permanecerá o mesmo.

Similarmente, criaremos outra função, GetSpyHandles(), para obter identificadores de agentes do indicador que transmitirão pontos para outros símbolos. Mas, antes disso, adicionaremos mais uma enumeração de todos os eventos por símbolo, ENUM_CHART_EVENT_SYMBOL, organizados como sinalizadores no arquivo Enums.mqh:

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

Essa enumeração é necessária para operar com o indicador personalizado EventsSpy.mq5 (o arquivo está anexo nesse artigo) na função GetSpyHandles() cujo código está fornecido abaixo:

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

Por favor, note o último parâmetro na função iCustom(): nesse caso, o identificador CHARTEVENT_TICK foi utilizado para obter eventos de ponto. Mas, se for necessário, ela pode ser modificada para obter os novos eventos de barra. Por exemplo, se você utilizar a linha conforme mostrado abaixo, o Consultor Especialista obterá novos eventos de barra no intervalo de tempo de um minuto (M1) e uma hora (H1):

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

Para obter todos os eventos (eventos de ponto e barra em todos os intervalos de tempo), você precisa especificar o identificador CHARTEVENT_ALL.

Todos os arranjos são inicializados na função OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

Conforme já mencionado no início do artigo, os eventos dos agentes do indicador são recebidos na função OnChartEvent(). Abaixo está o código que será utilizado nessa função:

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

Na função CheckSignalAndTrade() (a linha destacada no código acima), teremos um ciclo onde todos os símbolos serão verificados alternativamente quanto ao novo evento da barra e os sinais de negócios conforme implementado anteriormente na função OnTick():

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

Todas as funções que utilizaram parâmetros externos, assim como dados de indicador e símbolo, precisam ser modificadas em concordância com todas as alterações acima. Para essa finalidade, devemos acrescentar o número do símbolo como o primeiro parâmetro e substituir todas as variáveis e arranjos dentro da função com os novos arranjos descritos acima.

Para ilustrar, os códigos revisados das funções CheckNewBar(), TradingBlock() e OpenPosition() são fornecidos abaixo.

O código da função CheckNewBar():

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

O código da função TradingBlock():

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

O código da função OpenPosition():

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

Então, cada função agora recebe o número do símbolo (symbol_number). Por favor, note também a alteração introduzida na compilação 803:

Começando com a compilação 803, o Stop Loss e o Take Profit podem ser colocados na abertura de uma posição no modo SYMBOL_TRADE_EXECUTION_MARKET.

Os códigos revisados das outras funções podem ser encontrados nos arquivos anexos. Tudo o que precisamos fazer agora é otimizar os parâmetros e efetuar testes.


Otimizando parâmetros e testando o Consultor Especialista

Primeiro otimizaremos os parâmetros para o primeiro símbolo e depois para o segundo. Vamos começar com o EURUSD.

Abaixo estão as configurações do Testador de Estratégia:

Fig. 1. Configurações do Testador de Estratégia

Fig. 1. Configurações do Testador de Estratégia.

As configurações do Consultor Especialista precisam ser feitas conforme mostrados abaixo (para conveniência, os arquivos .set contendo configurações para cada símbolo estão anexados nesse artigo). Para excluir certo símbolo da otimização, você deve simplesmente deixar o campo do parâmetro do nome do símbolo vazio. A otimização de parâmetros realizada para cada símbolo separadamente também acelerará o processo de otimização.

Fig. 2. Configurações do Consultor Especialista para otimização de parâmetro: EURUSD

Fig. 2. Configurações do Consultor Especialista para otimização de parâmetro: EURUSD.

A otimização levará cerca de uma hora em um processador dual-core. Os resultados do teste do fator de recuperação máximo são conforme mostrado abaixo:

Fig. 3. Resultados do teste do fator de recuperação máximo para EURUSD

Fig. 3. Resultados do teste do fator de recuperação máximo para EURUSD.

Agora defina o NZDUSD como o segundo símbolo. Para a otimização, deixe a linha com o nome do símbolo para o primeiro bloco de parâmetro vazio.

Alternativamente, você pode simplesmente adicionar um hífen no fim do nome do símbolo. O Consultor Especialista não encontrará o símbolo com tal nome na lista de símbolos e inicializará o índice do arranjo para uma cadeia vazia.

Os resultados para NZDUSD parecem ter sido conforme a seguir:

Fig. 4. Resultados do teste para o fator de recuperação máximo para NZDUSD

Fig. 4. Resultados do teste para o fator de recuperação máximo para NZDUSD.

Agora podemos testar dois símbolos juntos. Nas configurações do Testador de Estratégia, você pode ajustar qualquer símbolo no qual o Consultor Especialista seja inicializado já que os resultados serão idênticos. Pode até mesmo ser um símbolo que não esteja envolvido nos negócios/testes.

Abaixo estão os resultados para dois símbolos testados juntos.

Fig. 5. Resultados do teste para dois símbolos: EURUSD e NZDUSD

Fig. 5. Resultados do teste para dois símbolos: EURUSD e NZDUSD.


Conclusão

Isso é tudo. Os códigos-fonte estão anexos abaixo e podem ser baixados para um estudo mais detalhado do acima mencionado. Para praticar, tente selecionar um ou mais símbolos ou altere as condições de abertura de posição utilizando outros indicadores.

Após extrair os arquivos do arquivo, coloque a pasta MultiSymbolExpert dentro do diretório MetaTrader 5\MQL5\Experts. Além disso, o indicador EventsSpy.mq5 deve ser colocado dentro do diretório MetaTrader 5\MQL5\Indicators.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/648

Guia prático do MQL5: Desenvolvendo um Consultor Especialista multi-moeda com um número ilimitado de parâmetros Guia prático do MQL5: Desenvolvendo um Consultor Especialista multi-moeda com um número ilimitado de parâmetros
Neste artigo, criaremos um padrão que utiliza um único conjunto de parâmetros para otimização do sistema de negociação, enquanto permite um número ilimitado de parâmetros. A lista de símbolos será criada no arquivo de texto padrão (*.txt). Os parâmetros de entrada para cada símbolo também serão armazenados nos arquivos. Desta forma poderemos ser capazes de contornar a restrição do terminal sobre o número de parâmetros de entrada de um Expert Advisor.
Guia prático do MQL5: Desenvolvendo uma estrutura para um sistema de negócios baseado na estratégia de tela tripla Guia prático do MQL5: Desenvolvendo uma estrutura para um sistema de negócios baseado na estratégia de tela tripla
Nesse artigo, desenvolveremos uma estrutura para um sistema de negócios baseado na estratégia de tela tripla no MQL5. O Consultor Especialista não será desenvolvido do zero. Ao invés disso, simplesmente modificaremos o programa do artigo anterior "Guia prático do MQL5: Usando indicadores para definir condições de negócios em Consultores Especialistas" que já servem substancialmente ao nosso propósito. Então o artigo também demonstrará como você pode modificar facilmente padrões de programas já prontos.
Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel
Ao me comunicar em vários fóruns, utilizei frequentemente exemplos de meus resultados de teste exibidos como capturas de tela de gráficos do Microsoft Excel. Por muitas vezes me foi pedido para explicar como tais gráficos podem ser criados. Agora, enfim, eu tenho algum tempo para explicar tudo nesse artigo.
O indicador ZigZag: nova abordagem e novas soluções O indicador ZigZag: nova abordagem e novas soluções
O artigo examina a possibilidade de criar um indicador ZigZag avançado. A ideia de identificar nós é baseada no uso de indicador Envelopes. Presumimos que podemos encontrar uma certa combinação de parâmetros de entrada para uma série de Envelopes, pelos quais a maioria dos nós dos ZigZags se encontram nos confins das faixas dos Envelopes. Consequentemente, podemos tentar prever as coordenadas do novo nó.