Criando EAs multimódulo

Sergey Pavlov | 2 maio, 2018


Introdução

Até agora, existem várias abordagens em programação: modular, orientada a objetos e estrutural. O artigo falará sobre a programação modular aplicada a robôs de negociação.

A programação modular é um método de desenvolvimento de programas que envolve sua divisão em módulos independentes.

A principal regra da programação modular é o princípio de dividir e conquistar. A vantagem no uso da arquitetura modular está na possibilidade de atualizar (substituir) o módulo, sem a necessidade de alterar o restante do sistema.

Três conceitos básicos constituem o alicerce da programação modular.

  • Princípio de encapsulamento de informações de Parnas. O módulo serve para ocultar informações e o algoritmo para resolver um problema específico. Mais tarde, o módulo pode ser substituído por outro.
  • Axioma da modularidade de Cowen. O módulo é uma unidade de software independente que serve para executar uma determinada função do programa.
  • Programação de montagem de Tséitin. Módulos são "tijolos" de software a partir dos quais o programa é construído.

A única alternativa à modularidade é um programa monolítico, que não é muito conveniente, pois se você precisar alterar ou complementar algumas das funções do programa, precisará editar o código do Expert Advisor, o que, na maioria dos casos, pode ser feito pelo autor do código ou por outro programador experiente. Além disso, se o programa monolítico é adicionalmente compilado, o produto pode ser editado apenas pelo proprietário dos direitos autorais, e isso não significa que você possa combinar com ele. Perante este cenário, é muito mais conveniente alterar as funções importantes do programa, usando seu próprio potencial ou os serviços de desenvolvedores de terceiros, sem ter de envolver seu autor.

Fig. 1. Esquema abstrato de um robô de negociação modular
Fig. 1. Esquema abstrato de um robô de negociação modular

Princípio da modularidade múltipla

A programação modular é a arte de separar uma tarefa em várias subtarefas, implementadas como módulos separados (arquivos). Em geral, um módulo de programa é um programa separado, ou uma unidade de software compilada autónoma e funcionalmente completa, de alguma forma identificada e combinada com o módulo chamado. Em outras palavras, o módulo é um fragmento funcionalmente finalizado do programa, projetado como um arquivo compilado separado, destinado a uso em outros programas.

Ao determinar o conjunto de módulos que implementam as funções de um determinado algoritmo, deve-se considerar o seguinte:

  • cada módulo é invocado pelo módulo pai e, após a conclusão do trabalho, retorna o controle para o módulo que o chamou;
  • a adoção das principais decisões no algoritmo é feita no nível mais alto da hierarquia;
  • módulos são independentes quanto a dados;
  • módulos não dependem do histórico de acesso a eles.

Resumindo, um programa modular é um programa no qual qualquer parte da estrutura lógica pode ser alterada sem causar alterações em outras partes.

Principais características do módulo:

  • uma entrada e uma saída: na entrada, o módulo do programa recebe um determinado conjunto de dados iniciais, processa-os e retorna um conjunto de dados de resultados, ou seja, implementa-se o princípio IPO (Input-Process-Output);
  • completude funcional: para executar uma função separada, o módulo executa uma lista completa de operações reguladas, suficiente para completar o processamento que foi iniciado;
  • independência lógica: o resultado do módulo de programa depende apenas dos dados de origem, não dependendo da operação de outros módulos;
  • vínculos de informações fracos com outros módulos de programa: a troca de informações entre módulos deve ser minimizada tanto quanto possível.

Você pode escrever, em linguagem MQL5, três tipos de programas: expert advisor, indicador ou script. Para o módulo principal, o formato de Expert Advisor mais adequado é aquele em que seja organizado o gerenciamento de todos os módulos e sejam colocadas as funções de negociação. Enquanto os outros módulos podem ser implementados, por exemplo, na forma de indicadores. Os indicadores são realmente ideais para formar o módulo, uma vez que os dados calculados com o algoritmo fornecido podem ser armazenados em buffers de indicador e transferidos para um Expert Advisor multimódulo, se necessário. O Expert Advisor, por sua vez, pode usar ou ignorar esses dados, dependendo da tarefa. Em alguns projetos, pode-se justificar o uso de EAs como módulos externos, mas, ao fazer isto, é necessário pensar em detalhes sobre o mecanismo de troca de dados.

Certamente você já usou abordagens modulares em seus robôs de negociação, por exemplo, indicadores personalizados como módulos para gerar e filtrar sinais de negociação.

A solução mais racional, na minha opinião, é baseada no fato de toda a funcionalidade básica estar concentrada no módulo principal e não exigir a participação de módulos externos. Módulos externos, por sua vez, são necessários para se adaptar às diferentes condições de mercado e aprimorar a estratégia de negociação. O conjunto de funções do programa não é determinado pelo autor do código ou estratégia, mas sim pelo trader usuário do robô de negociação. É importante notar que ninguém viola os direitos legítimos do outro.

O módulo principal é um EA

O módulo principal, no qual está concentrado o gerenciamento de todo o projeto, é o mais importante na hierarquia do EA. Nele, devem ser colocadas funções de negociação, sem as quais qualquer estratégia de negociação não teria sentido.

Examinemos no artigo a criação de um Expert Advisor multimódulo com um exemplo específico retirado do CodeBase. O Expert Advisor inicial negocia um lote fixo no canal indicador iBands, com reversão da posição, nas bordas do canal. O Expert Advisor é completamente autossuficiente e não requer nenhum programa externo.

Nem todo EA pode ser multimodular, em outras palavras, apenas alguns vêm com essa capacidade.

Então, o que se deve adicionar ao código para transformá-lo num projeto modular?

  1. Anunciar módulos externos (indicadores) que o usuário pode usar posteriormente. 
  2. Adicionar a funcionalidade necessária para sua integração.
  3. Preparar a documentação para desenvolvedores de módulos externos (habilitar a função que permite gerar documentação num arquivo separado). Portanto, para o desenvolvimento de módulos externos, são necessárias informações sobre a estrutura de dados que podem ser usadas corretamente pelo módulo principal. Por exemplo, neste exemplo, o módulo de gerenciamento de dinheiro deve transmitir o tamanho do lote para o EA, enquanto o módulo de rastreamento de posição - a distância em pontos do preço atual.

Como resultado da transformação, obtém-se um EA modular em que podem ser integrados até sete módulos externos.

  • Módulo №1 — módulo de gerenciamento de dinheiro. Na saída, ele dá o tamanho do lote.
  • Módulo №2 — módulo para rastrear a posição e colocar o SL. Na saída, ele dá a distância, em pontos, entre o preço de abertura da posição e o stop-loss.
  • Módulo №3 — módulo para rastrear a posição e colocar o TP. Na saída, ele dá a distância, em pontos, entre o preço de abertura da posição e o take-profit.
  • Módulo №4 — módulo para rastrear a posição e colocar o trailing-stop. Na saída, ele dá a distância, em pontos, entre o stop-loss e o preço atual.
  • Módulo №5 — módulo de geração de sinais de negociação. Na saída, ele dá o valor do sinal.
  • Módulo №6 — módulo para filtrar sinais de negociação. Na saída, ele dá o valor do filtro.
  • Módulo №7 — módulo para rastrear a posição e definir o nível de break-even. Na saída, ele dá a distância entre o preço de abertura da posição e o stop-loss.


Fig. 2. Função OnInit() e inicialização de módulos externos

 

Fig. 2. Função OnInit() e inicialização de módulos externos

Fig. 3. Função OnTick() e leitura de dados de módulos externos
Fig. 3. Função OnTick() e leitura de dados de módulos externos

Fig. 4. Função OnTrade() e leitura de dados de módulos externos

Fig. 4. Função OnTrade() e leitura de dados de módulos externos

Fig. 5. Função de geração de sinais de negociação e leitura de dados de módulos externos


Fig. 5. Função de geração de sinais de negociação e leitura de dados de módulos externos

 

//****** project (module expert): test_module_exp.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project TEST Main module."
#property link      "The project uses 7 external modules."
//---
#include <Trade\Trade.mqh>
//---
MqlTick    last_tick;
CTrade     trade;
//---
input int                  e_bands_period=80;            // Moving average period
int                        e_bands_shift=0;              // shift
input double               e_deviation=3.0;              // Number of standard deviations
input ENUM_APPLIED_PRICE   e_applied_price=PRICE_CLOSE;  // Price type
input bool                 on_module=false;              // whether or not to use plug-ins
//---
double lot=0.01;           // Fixed lot
double min_lot=0.01;       // Minimum allowable lot
bool   on_trade=false;     // Trade function flag
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//--- module 1
bool   on_lot=false;
int    handle_m1;
//--- module 2
bool   on_SL=false;
int    handle_m2;
//--- module 3
bool   on_TP=false;
int    handle_m3;
//--- module 4
bool   on_Trail=false;
int    handle_m4;
//--- module 5
bool   on_signals=false;
int    handle_m5;
//--- module 6
bool   on_Filter=false;
int    handle_m6;
//--- module 7
bool   on_Breakeven=false;
int    handle_m7;
//+------------------------------------------------------------------+
//| Structure of trading signals                                     |
//+------------------------------------------------------------------+
struct sSignal
  {
   bool              Buy;    // Buy signal
   bool              Sell;   // Sell signal
  };
//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create the indicator handle
   handle_Bands=iBands(_Symbol,_Period,e_bands_period,e_bands_shift,e_deviation,e_applied_price);
   if(handle_Bands==INVALID_HANDLE)
      return(INIT_FAILED);
   else
      on_trade=true;
   if(on_module)
     {
      //--- MODULE 1
      //--- check: whether there is an external module?
      handle_m1=iCustom(NULL,0,"Market\\test_module_MM");
      if(handle_m1!=INVALID_HANDLE)
         on_lot=true;
      //--- MODULE 2
      //--- check: whether there is an external module?
      handle_m2=iCustom(NULL,0,"Market\\test_module_SL");
      if(handle_m2!=INVALID_HANDLE)
         on_SL=true;
      //--- MODULE 3
      //--- check: whether there is an external module?
      handle_m3=iCustom(NULL,0,"Market\\test_module_TP");
      if(handle_m3!=INVALID_HANDLE)
         on_TP=true;
      //--- MODULE 4
      //--- check: whether there is an external module?
      handle_m4=iCustom(NULL,0,"Market\\test_module_Trail");
      if(handle_m4!=INVALID_HANDLE)
         on_Trail=true;
      //--- MODULE 5
      //--- check: whether there is an external module?
      handle_m5=iCustom(NULL,0,"Market\\test_module_signals");
      if(handle_m5!=INVALID_HANDLE)
         on_signals=true;
      //--- MODULE 6
      //--- check: whether there is an external module?
      handle_m6=iCustom(NULL,0,"Market\\test_module_Filter");
      if(handle_m6!=INVALID_HANDLE)
         on_Filter=true;
      //--- MODULE 7
      //--- check: whether there is an external module?
      handle_m7=iCustom(NULL,0,"Market\\test_module_Breakeven");
      if(handle_m7!=INVALID_HANDLE)
         on_Breakeven=true;
     }
//--- Minimum allowable volume for trading operationsn
   min_lot=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }
//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }
//---
   if(lot<min_lot) lot=min_lot;
//---
   if(on_trade)
     {
      sSignal signal=Buy_or_Sell();
      //--- The value of the required and free margin
      double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      //--- BUY
      if(signal.Buy)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: reversal");
              }
           }
        }
      //--- SELL
      if(signal.Sell)
        {
         if(!PositionSelect(_Symbol))
           {
            SymbolInfoTick(_Symbol,last_tick);
            if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
               if(margin<Equity)
                  trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: new position");
           }
         else
           {
            if(PositionGetDouble(POSITION_PROFIT)<0) return;
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               trade.PositionClose(_Symbol);
               SymbolInfoTick(_Symbol,last_tick);
               if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin))
                  if(margin<Equity)
                     trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: reversal");
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

Se o catálogo especificado no programa não contiver os módulos necessários (arquivos), o robô de negociação usará a funcionalidade padrão. Assim, a ausência de módulos externos não terá um impacto crítico na eficiência do EA.

Os módulos mais importantes

Como um Expert Advisor multimódulo pode ser feito a partir de um programa monolítico? O projeto modular começa com a análise de uma tarefa comum e a seleção de fragmentos funcionalmente fechados que podem ser implementados posteriormente como módulos compilados. Nesse caso, você precisa selecionar as funções mais comuns, que podem alterar significativamente o trabalho do Expert Advisor, baseadas em vários algoritmos. Não é segredo que a maioria dos EAs usa os mesmos procedimentos: 

  • módulo de gerenciamento de dinheiro (riscos);
  • módulo para rastrear posições (SL e TP);
  • módulo de trailing-stop;
  • módulo de geração de sinais de negociação;
  • módulo de filtragem de sinal.

Existem muitas formas de implementar cada um dos módulos listados. Neste artigo, mostraremos as soluções mais primitivas, porque para nós a abordagem da programação modular em si é mais importante que a funcionalidade multilinha.

Tecnologia de criação de módulos auxiliares

O módulo auxiliar (externo) é um indicador que executa uma função específica e coloca os dados de saída nos buffers do indicador. O módulo principal usa esses dados, se necessário. Assim, o Expert Advisor se adapta às exigências do trader que usa essa estratégia de negociação. O mesmo EA de origem pode ser recompilado para cada instrumento financeiro ou agente de corretagem específico. Na verdade, nas mãos do trader aparece um designer, a partir do qual é possível montar um número ilimitado de robôs de negociação.

A programação é um processo dispendioso. Apesar da presença de um componente criativo, existem muitas operações de rotina e de tipo único que é melhor automatizar. A automação, entre outras coisas, melhora o desempenho da programação e reduz os erros.

Ao artigo é anexado um gerador de módulos que permite, em poucos segundos, gerar até 8 arquivos vinculados num projeto de vários módulos. Isso simplifica e acelera enormemente o processo de desenvolvimento e compilação (veja o vídeo).

Painel de controle do gerador de projetos multimódulo

Vídeo 1. Painel de controle do gerador de projetos multimódulo

No painel de controle, você pode especificar quais módulos você deseja gerar para o novo projeto. No nosso exemplo, é criado o projeto "test". Dependendo da combinação escolhida de módulos, o gerador irá criar um código automaticamente, no qual não haverá blocos e arquivos desnecessários.

Os arquivos gerados são colocados na pasta "Files" (veja a fig. 2). O nome do módulo principal "test_module_exp.mq5" consiste no nome do projeto "test" e o prefixo "_module_exp.mq5". Ele precisa ser movido para a pasta "Experts", enquanto os arquivos restantes dos módulos externos, para a pasta "Indicators\Market".

Fig. 6 Arquivos gerados do projeto "test".

Fig. 6 Arquivos gerados do projeto "test".

Depois disso, compilamos todos os arquivos e passamos a testar o projeto multimódulo.

Vídeo 2. Compilação dos arquivos gerados do projeto "test"
  

Criar manualmente um projeto semelhante leva mais de uma hora e, obviamente, é necessário começar pelo módulo principal. Após determinar os módulos externos que mais tarde podem ser conectados ao projeto, prosseguimos com o design e programação. A principal coisa a monitorar são os dados de saída dos módulos auxiliares, dados esses aguardados pelo módulo principal. Como os módulos são indicadores e os buffers de indicador contêm valores de tipo exclusivamente real, no módulo principal é necessário fornecer a conversão de variáveis ​​do tipo real no tipo correspondente ao algoritmo.

Os módulos externos devem ser projetados de forma que possam ser chamados no módulo principal sem parâmetros de entrada, ou seja, por padrão. Tal mecanismo de chamada simplifica a projeção do sistema de gerenciamento de dados externos.

Considerar com mais detalhes quais módulos externos são mais procurados nas estratégias de negociação.

Exemplo 1: módulo de gerenciamento de dinheiro

Este módulo externo calcula o volume do lote para abrir a próxima ordem. Abaixo é implementada a versão mais simples do cálculo do volume de negociação (como percentual dos fundos disponíveis no depósito):

//****** project (module MM): test_module_MM_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module MM"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
input double   lot_perc=0.1;  // Percentage of Equity value
double         Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- calculation of the lot of equity
   Buffer1[0]=NormalizeDouble(Equity*lot_perc/1000.0,2); // Lot size determination function
   if(Buffer1[0]<0.01) Buffer1[0]=0.01;
   return(rates_total);
  };

Se você chamar este módulo por padrão (sem especificar os valores dos parâmetros de entrada), o módulo principal receberá o tamanho do lote autorizado para concluir a transação no valor de 0,1% dos fundos disponíveis. Exemplo de chamada deste módulo a partir do programa principal:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double Equity=AccountInfoDouble(ACCOUNT_EQUITY);
//--- MODULE 1
   if(on_lot)
     { // If there is an additional module
      double buffer_m1[];
      ArraySetAsSeries(buffer_m1,true);
      if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return;
      lot=buffer_m1[0];
     }
  
  ...
  
  }

Exemplo 2: módulo de rastreamento de posição (SL, TP e trailing)

A colocação de stop-loss (SL) e take-profit (TP) é uma das formas de manter uma posição aberta. Como diferentes combinações de cálculo e definição de SL e TP são usadas em diferentes estratégias de negociação, usamos uma divisão em dois módulos, isto é, separadamente para SL e TP. Mas, se ainda decidirmos combinar SL e TP num módulo, seus valores devem ser colocados em diferentes buffers de indicador.

Módulo de colocação de SL:

//****** project (module SL): test_module_SL_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module SL"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double SL=100; // SL in points
//--- calculation of the SL
   Buffer1[0]=SL;
   return(rates_total);
  };

Módulo de colocação de TP:

//****** project (module TP): test_module_TP_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module TP"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TP=100; // TP in points
//--- calculation of the TP
   Buffer1[0]=TP;
   return(rates_total);
  };

Nos códigos apresentados, é mostrada a opção mais óbvia para calcular os valores de SL e TP, isto é, em pontos. Realmente, ela não é calculada, mas sim definida por uma constante. Neste caso, os valores são especificados diretamente no programa, e não - nos parâmetros de entrada. Isso é feito para mostrar a implementação de módulos externos sem dados de entrada. Para qualquer programador iniciante, escrever esse código não requer um esforço.

Recomendo colocar a chamada dos módulos discutidos na função OnTrade. Isso tem a seguinte aparência:

//+------------------------------------------------------------------+
//| Trade function                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(on_SL && on_TP) // If there is an additional module
     {
      //--- MODULE 2
      double buffer_m2[];
      ArraySetAsSeries(buffer_m2,true);
      if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return;
      double SL=buffer_m2[0];
      //--- MODULE 3
      double buffer_m3[];
      ArraySetAsSeries(buffer_m3,true);
      if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return;
      double TP=buffer_m3[0];
      //--- Position modification
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_SL)==0)
           {
            //--- BUY
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
            //--- SELL
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point;
               double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point;
               trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits()));
              }
           }
     }
  }

Além dos valores estáticos de SL e TP definidos imediatamente após a posição ser aberta, com frequência é usado um trailing-stop ou SL flutuante. Na maioria das vezes, ele é colocado depois que a posição se torna lucrativa. A opção mais óbvia de implementação é definir, em pontos, a distância entre o SL e o preço atual.

//****** project (module Trail): test_module_Trail_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trail"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double TR=50;  // Trail in points
//--- calculation of Trail
   Buffer1[0]=TR;
   return(rates_total);
  };

Aqui, como nos códigos anteriores para SL e TP, a distância para calcular o trailing-stop é definida por uma constante para simplificação do programa e leitura.

A chamada do módulo de trailing-stop deve ser realizada na função OnTick, porque o preço atual muda a cada tick e o nível de stop deve ser monitorado continuamente. O módulo principal decide se ele deve ser alterado ou não. Tendo recebido o valor de recuo em pontos, o Expert Advisor modifica a posição e move o nível de SL na direção do crescimento do lucro.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 4
   if(on_Trail)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m4[];
            ArraySetAsSeries(buffer_m4,true);
            if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return;
            double TR=buffer_m4[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0);
                 }
           }

   ...

  }

Há mais um método de rastreamento de posição, isto é, a colocação do SL no ponto de break-even. Se o SL é executado, a posição é fechada com um resultado zero ou com um lucro predeterminado. O módulo pode ser algo como isto:

//****** project (module Breakeven): test_module_Breakeven_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Breakeven"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double Breakeven=100; // Breakeven in points
//--- calculation of the Breakeven
   Buffer1[0]=Breakeven;
   return(rates_total);
  };

Neste módulo, o recuo do preço atual é definido a partir do preço de abertura da posição, em pontos, para colocação do SL no ponto de break-even. A chamada do módulo break-even também precisa ser colocada na função OnTick.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   ...

//--- MODULE 7
   if(on_Breakeven)
      if(PositionSelect(_Symbol))
         if(PositionGetDouble(POSITION_PROFIT)>0)
           { // If there is an additional module
            double buffer_m7[];
            ArraySetAsSeries(buffer_m7,true);
            if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return;
            double TRB=buffer_m7[0];
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point;
                  if(price_SL>PositionGetDouble(POSITION_SL))
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
               if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN))
                 {
                  double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point;
                  if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL)
                     trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP));
                 }
           }

   ...
   
  }

Exemplo 3: módulo de geração de sinais de negociação

Talvez este seja o módulo mais difícil de realizar. É a partir dele que devem vir sinais para executar operações de negociação: colocação de ordens, fechamento de posições, etc. A complexidade de seu design é que quase todos os indicadores devem ser adaptados às condições de negociação. Praticamente não existem indicadores com os mesmos parâmetros de entrada que geram sinais operacionais para diferentes instrumentos financeiros.

Obviamente, o programa principal não deve configurar sozinho módulos de sinal, uma vez que o trabalho do projeto modular pode ficar paralisado devido à carga. Por conseguinte, os indicadores que geram sinais de negociação devem ser preparados com antecedência, mesmo antes da conexão ao projeto geral. Falaremos sobre isso um pouco mais adiante, na seção dedicada à otimização de EAs multimódulo. Agora, veja o código do módulo dos sinais de negociação:

//****** project (module signals): test_module_signals_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,20,0,3.5,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double signal=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- calculation of the Trading signals
   if(high[0]>UpperBuffer[0]) signal=-2.0;
   if(low[0]<LowerBuffer[0]) signal=2.0;
   Buffer1[0]=signal;
   return(rates_total);
  };

Os seguintes valores são gravados no buffer de indicador do módulo de sinal:

  • 2.0 — se for gerado um sinal BUY;
  • 0.0 — se não houver sinais de negociação;
  • -2.0 — se for gerado um sinal SELL.

O melhor é usar os valores recebidos do módulo de sinais de negociação numa função especial do módulo principal, por exemplo:

//+------------------------------------------------------------------+
//| Trading signals generator                                        |
//+------------------------------------------------------------------+
sSignal Buy_or_Sell()
  {
   sSignal res={false,false};
//--- MODULE 5
   if(on_signals)
     { // If there is an additional module
      double buffer_m5[];
      ArraySetAsSeries(buffer_m5,true);
      if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res);
      if(buffer_m5[0]<-1) res.Sell=true;
      if(buffer_m5[0]>1) res.Buy=true;
     }
//--- MODULE 6
   if(on_Filter)
     { // If there is an additional module
      double buffer_m6[];
      ArraySetAsSeries(buffer_m6,true);
      if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res);
      lot=buffer_m6[0];
      if(buffer_m6[0]<1) res.Buy=false;
      if(buffer_m6[0]>-1) res.Sell=false;
     }
//---
//--- Indicator buffers
   double         UpperBuffer[];
   double         LowerBuffer[];
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
   ArraySetAsSeries(UpperBuffer,true);  CopyBuffer(handle_Bands,1,0,1,UpperBuffer);
   ArraySetAsSeries(LowerBuffer,true);  CopyBuffer(handle_Bands,2,0,1,LowerBuffer);
//--- Timeseries
   double L[];
   double H[];
   ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L);
   ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H);
   if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true;
   if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true;
//---
   return(res);
  }

Existem muitas estratégias de negociação e cada uma delas tem seus próprios sinais. Por isso, é necessário organizar o trabalho do módulo de sinais de negociação para que eles compatibilizem com esta estratégia. Na documentação do Expert Advisor, deve ser descrita uma estratégia de negociação, para que os desenvolvedores do módulo de sinal ajam de acordo com os requisitos técnicos do projeto modular.

Exemplo 4: módulo de filtro de sinal

Para aumentar a rentabilidade dos robôs de negociação, geralmente, usam-se filtros de sinal de negociação. Eles podem, por exemplo, levar em consideração a tendência, hora de negociação, notícias, indicadores de sinal adicionais, etc.

//****** project (module Filter): test_module_Filter_ind.mq5
//+------------------------------------------------------------------+
//|          The program code is generated Modular project generator |
//|                      Copyright 2010-2017, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Filter"
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//---
double      Buffer1[];
//--- Variable for storing the indicator iBands handle 
int    handle_Bands;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   handle_Bands=iBands(_Symbol,_Period,35,0,4.1,PRICE_CLOSE);
//---
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,
                 const int prev_calculated,
                 const datetime& time[],
                 const double& open[],
                 const double& high[],
                 const double& low[],
                 const double& close[],
                 const long& tick_volume[],
                 const long& volume[],
                 const int &spread[])
  {
   double filtr=0.0;
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- Indicator buffers
   double         MiddleBuffer[];
   ArraySetAsSeries(MiddleBuffer,true);  CopyBuffer(handle_Bands,0,0,1,MiddleBuffer);
//--- calculation of the filter
   if(high[0]<MiddleBuffer[0]) filtr=2.0;
   if(low[0]>MiddleBuffer[0]) filtr=-2.0;
   Buffer1[0]=filtr;
   return(rates_total);
  };

Assim, nós examinamos diversas maneiras de implementar módulos e o princípio para a sua integração no Expert Advisor modular.

Otimização de EAs multimódulo

A otimização de EAs multimódulo é talvez uma questão fundamental. De fato, surge a pergunta: "como otimizar os parâmetros de entrada de módulos externos no testador de estratégias?" Pois, se eles não são especificados no módulo principal, é impossível (ou quase impossível) levar a cabo essa tarefa. Pode-se tentar especificar, discretamente, os dados de entrada dos módulos externos e depois testar o Expert Advisor. Mas, muito provavelmente, esse árduo e absurdo trabalho não nos convém. O que fazer então?

Eis uma das possíveis opções, isto é, usar indicadores, que se automatizem sozinhos, como módulos externos. Muitos artigos e exemplos já foram escritos sobre a otimização automática. Eu também contribuirei para o desenvolvimento deste tópico. Vamos usar as ideias do artigo "Teste visual da rentabilidade de indicadores e sinais". Seu autor sugere usar, como preço de transação virtual, o valor máximo da vela, para abertura de BUY, enquanto o valor mínimo, para abertura de SELL. Assim, são garantidas as piores condições de negociação e, com tal abordagem de preços, são otimizados os parâmetros de entrada. A partir dos valores ideais obtidos, é assumido que, na negociação real, o resultado não será pior (no mesmo trecho dos dados históricos). Na negociação real, não se pode garantir lucro, após qualquer otimização.

A estratégia do nosso EA é baseada na negociação dentro do canal do indicador Bollinger com uma reversão da posição em suas bordas. Substituamos este indicador e construamos o canal no indicador Envelope: a partir do indicador da média móvel, formamos bordas equidistantes em relação à MA. O novo indicador de sinal será automaticamente auto-otimizado antes do uso. Como parâmetros de entrada, serão usados os valores ideais, em que foi mostrado o lucro máximo. Para otimização, são selecionados dois parâmetros, nomeadamente, o período do MA e a distância entre as bordas e a média móvel.

Algoritmo para criação do indicador de sinal com função de otimização automática:

  1. Determinamos os parâmetros e o critério de otimização. No nosso caso, os parâmetros serão o período do MA e a distância do deslocamento das bordas, e o critério, o lucro máximo.
  2. Criamos o bloco de otimização no indicador. No exemplo proposto, ocorre uma pesquisa completa dos dados de entrada, no intervalo especificado, com uma etapa fixa. O período da média móvel é de 10 a 100 em etapas de 10 e, para deslocamento, os valores são movidos de 1 000 para 10 000 em etapas de 1 000.
//+------------------------------------------------------------------+
//|                           Copyright 2018, Sergey Pavlov (DC2008) |
//|                              http://www.mql5.com/pt/users/dc2008 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Sergey Pavlov (DC2008)"
#property link      "http://www.mql5.com/pt/users/dc2008"
#property link      "1.00"
#property link      "Example of a multimodule expert: project test module Trading signals"
//---
#include <MovingAverages.mqh>
//--- Display indicator in the chart window
#property indicator_chart_window
//--- Number of buffers to calculate the indicator
#property indicator_buffers 1
//--- Number of graphic series in the indicator
#property indicator_plots   1
//+------------------------------------------------------------------+
//| Estrutura de resultados de otimização                            |
//+------------------------------------------------------------------+
struct Opt
  {
   int               var1;          // valor ótimo do parâmetro 1
   int               var2;          // valor ótimo do parâmetro 2
   double            profit;        // lucro
  };
//---
double      Buffer1[];
bool        optimum=false;
Opt         test={NULL,NULL,NULL};
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArraySetAsSeries(Buffer1,true);
   SetIndexBuffer(0,Buffer1,INDICATOR_DATA);
   optimum=false;
   return(INIT_SUCCEEDED);
  };
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   double signal=0.0;
   Buffer1[0]=signal;
//--- optimization of input parameters
   if(!optimum)
     {
      ArraySetAsSeries(close,false);
      int count=rates_total;
      int total=0;
      int total_profit=0;
      for(int d=1000;d<10001;d+=1000)
         for(int j=10;j<101;j+=10)
           {
            double shift=d*_Point;
            bool open_buy=false;
            bool open_sell=false;
            double price_buy=0;
            double price_sell=0;
            double profit=0;
            int order=0;
            for(int i=j+1;i<count;i++)
              {
               double ma=SimpleMA(i,j,close);
               double sell=ma+shift;
               double buy=ma-shift;
               //--- BUY
               if(buy>close[i] && !open_buy)
                 {
                  price_buy=high[i]+spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_buy=true;
                  open_sell=false;
                 }
               //--- SELL
               if(sell<close[i] && !open_sell)
                 {
                  price_sell=low[i]-spread[i]*_Point;
                  if(order==0) profit=0;
                  else profit+=price_sell-price_buy;
                  order++;
                  open_sell=true;
                  open_buy=false;
                 }
               //---
              }
            if(profit>0)
               if(profit>test.profit)
                 {
                  test.var1=j;
                  test.var2=d;
                  test.profit=profit;
                  total_profit++;
                 }
            //---
            Comment("Otimização de parâmetros de entrada..."," passagens=",total," // lucrativas =",total_profit);
            total++;
           }
      //---
      Print(" Otimização concluída: ",test.var1," ",test.var2);
      Comment("Otimização concluída");
      optimum=true;
     }
//---
   if(optimum)
      if(test.profit>0)
        {
         ArraySetAsSeries(close,true);
         double ma=SimpleMA(0,test.var1,close);
         double sell=ma+test.var2*_Period;
         double buy=ma-test.var2*_Period;
         //--- calculation of the Trading signals
         if(buy>close[0]) signal=2.0;
         if(sell<close[0]) signal=-2.0;
        }
//--- Indicator buffers
   Buffer1[0]=signal;
   return(rates_total);
  };

Naturalmente, a otimização vai levar algum tempo, durante o qual o Expert Advisor não pode negociar. Se o robô de negociação modular funciona ininterruptamente, o atraso na otimização automática não deve ter um impacto significativo no tempo total de negociação.

Fim do artigo

  1. Na verdade, escrever um EA multimódulo em MQL5 não só é possível, mas, também, em alguns casos, razoável e rentável.
  2. Embora o artigo mostre o conceito primitivo de um robô de negociação com módulos externos, a tecnologia de programação modular permite criar projetos bastante complexos, atrativos a desenvolvedores de terceiros. Ao criar módulos, eles estão livres para não divulgar seu código e, assim, preservar os direitos autorais dos algoritmos.
  3. A questão da otimização de projetos modulares permanece em aberto. A auto-otimização dos indicadores de sinal usados ​​como módulos de sinal ou filtros é um tópico que precisa ser desenvolvido.

Nota: o arquivo anexado ao artigo permite gerar os códigos fonte do projeto modular com a configuração necessária.