Utilizando Redes Neurais No MetaTrader

Mariusz Woloszyn | 8 outubro, 2015


Introdução

Muitos de vocês provavelmente já consideraram a possibilidade da utilização de redes neurais nos EAs. Este assunto ficou muito quente especialmente depois de 2007 no Campeonato de Negociação Automatizado, com a espetacular vitória do EA Better, sistema baseado em redes neurais. Muitos fóruns de internet foram inundados com temas relacionados a redes neurais e negociação Forex. Infelizmente escrever uma implementação de rede neural em MQL4 nativo não é fácil. Isso requer algumas habilidades de programação e o resultado não seria muito eficiente, especialmente se você quiser analisar o resultado final no testador com grande número de dados.

Neste artigo vou mostrar como você pode usar em seu código MQL4 a renomada Biblioteca Fast Artificial Neural Network (FANN), disponível gratuitamente e evitar certos obstáculos e limitações. Além disso, presumo que leitor está familiarizado com as Redes Neurais Artificiais (RNAs) e a terminologia relacionada ao assunto, então eu vou concentrar-me nos aspectos práticos da utilização, em especial a execução das RNAs na linguagem MQL4.


Características da Biblioteca FANN

Para entender plenamente as possibilidades da implementação da bilbioteca FANN é necessário familiarizar-se com a sua documentação e funções comumente usadas. O uso típico da FANN é criar uma rede simples sem realimentação (feedforward) e treiná-la com alguns dados e prazo. A rede criada e treinada pode então ser salva num arquivo e restaurada posteriormente para uso futuro. Para criar uma "RNA" tem que usar a função fann_create_standard(). Vamos ver sua sintaxe:

FANN_EXTERNAL struct fann *FANN_API fann_create_standard(unsigned int num_layers, int lNnum, ... )

Onde num_layers representa o número total das camadas, incluindo a camada de entrada e a de saída. O lNnum e os argumentos seguintes representam o número de neurônios em cada camada, começando com a camada de entrada e terminando com a camada de saída. Para criar uma rede com uma camada escondida com 5 neurônios, 10 entradas e uma saída 1 teria que chamá-la da seguinte forma:

fann_create_standard(3,10,5,1);

Uma vez que o RNA é criada, a próxima operação seria treiná-la com alguns dados de entrada e saída. O método de treinamento mais simples é a formação gradual que pode ser alcançado pela seguinte função:

FANN_EXTERNAL void FANN_API fann_train(struct fann     *ann,
                                              fann_type*input,
                                              fann_type*desired_output)

Esta função recebe o ponteiro da struct fann, retornado anteriormente pela fann_create_standard() e ambas entrada e saída dos vetores de dados. Os vetores de entrada e saída são arrays do tipo fann_type. Dependendo da forma como a FANN é compilada, o tipo em questão é double ou float. Nesta implementação, os vetores de entrada e de saída serão arrays de tipo double.

Uma vez que a RNA é treinada, a próxima característica será a execução dessa rede. A seguir a implementação da função de execução:

FANN_EXTERNAL fann_type * FANN_API fann_run(struct fann     *ann,
                                                   fann_type*input)

Esta função recebe o ponteiro para a struct fann, representando a rede criada anteriormente e um vetor de entrada definido (array double). O valor retornado é um array do vetor de saída . Este fato é importante, para uma rede "utput" nós sempre obtemos um elemento array com o valor de saída, ao invés do próprio valor de saída.

Infelizmente, a maioria das funções FANN usam um ponteiro para uma struct fann representando a RNA que não pode ser manuseada diretamente pelo MQL4, pois esta não suporta estruturas como datatypes. Para evitar esta limitação, temos de enganar de alguma forma e ocultar do MQL4. O método mais fácil é criar um array dos ponteiros da struct fann, fixando os valores apropriados e referindo-se a eles com um índice representado por uma variavel int. Dessa forma, podemos substituir o tipo não suportado e criar uma biblioteca "wrapper" que pode ser facilmente integrada ao código MQL4.


Empacotando a FANN em torno de...

Ate quanto eu sei, a linguagem MQL4 não suporta funções com lista de argumentos de variáveis, então temos de lidar com isso também. Por outro lado, se a função C (comprimento de argumentos da variável) é chamada com muitos argumentos sem acontecer nada de errado, então podemos assumir um número máximo fixo de argumentos na função MQL4 transmitido à biblioteca C. A resultante da função "wrapper" seria o seguinte:

/* Cria um padrão completo conectado a uma rede neural "backpropagation"
* num_layers - representa o número total de camadas, incluindo as camadas de entrada e saída.
* l1num - número de neurônios na primeira camada (inputs)
* l2num, l3num, l4num - número de neurônios nas camadas ocultas e de saída (dependendo do num_layers).
* Retorno:
* Manipulador da ann (rede neural), -1 em caso de erro
*/

int __stdcall f2M_create_standard(unsigned int num_layers, int l1num, int l2num, int l3num, int l4num);

Mudamos o condutor fann_ com f2M_ (que significa FANN PARA MQL), usado número estático de argumentos (4 camadas) e retornando o valor que agora é um índice de array interna das anns (nota: ann = Artificial Neural Network, traduzido como Rede Neural Artificial - RNA) fixando os dados da struct fann exigidos pela FANN para operar. Dessa forma, podemos facilmente chamar essa função a partir do código MQL.

O mesmo vale para:

/* Treinar uma iteração com um conjunto de entradas e um conjunto de saídas desejadas.  
* Este treinamento é sempre um treinamento gradual, uma vez que apenas um padrão é apresentado.
* ann - manipulador de rede retornado por f2M_create_* 
* *input_vector - array de entradas
* *output_vector - array  de saídas
* Retorno:
* 0 em caso de sucesso e -1 em caso de erro
*/

int __stdcall f2M_train(int ann, double *input_vector, double *output_vector);

e

/* Executar rede fann
* ann - manipulador de rede retornado por f2M_create_* 
* *input_vector - array de entradas
* Retorno:
* 0 em caso de sucesso, valor negativo em caso de erro
* Nota:
* Para obter saída de rede use f2M_get_output().
* Qualquer saída existente será substituída
*/

int __stdcall f2M_run(int ann, double *input_vector);

Por último, mas não menos importante, é o fato de que você deve destruir a sua ann (rede neural artificial), uma vez criado pela chamada:

/* Destruir rede fann
* ann - manipulador de rede retornado por f2M_*
* Retorno:
* 0 em caso de sucesso -1 em caso de erro
* ATENÇÃO: os manipuladores de ann não podem ser reutilizados se ann!=(_ann-1)
* Outros manipuladores são reutilizáveis somente após a última ann ser destruída.
*/

int __stdcall f2M_destroy(int ann);

Para liberar o manipulador ann você deve destruir as redes na ordem inversa do que foram criadas. Alternativamente, você pode usar:

/* Destruir todas as redes fann
* Retorno:
* 0 em caso de sucesso -1 em caso de erro
*/
int __stdcall f2M_destroy_all_anns();

No entanto, eu tenho certeza que alguns de vocês podem preferir salvar a sua rede treinada para uso posterior com:

/* Salve toda a rede para um arquivo de configuração.
* ann - manipulador de rede retornado por f2M_create*
* Retorno:
* 0 em caso de sucesso e -1 em caso de erro
*/
int __stdcall f2M_save(int ann,char *path);

É claro que a rede salva pode ser carregada mais tarde (ou melhor recriada) com:

/* Carregar rede neural fann partir do arquivo
* path - caminho para o arquivo .net
* Retorno:
* Manipulador da ann (rede neural), -1 em caso de erro
*/
int __stdcall f2M_create_from_file(char *path);

Uma vez que sabemos as funções básicas, podemos tentar usar em nossa EA, mas primeiro é preciso instalar o pacote Fann2MQL.


Instalando Fann2MQL

Para facilitar a utilização deste pacote, eu tenho que criar o instalador "msi" que contém todo o código fonte mais as bibliotecas pré-compiladas e o arquivo de cabeçalho Fann2MQL.mqh que declara todas as funções Fann2MQL.

O procedimento de instalação é bastante simples. Primeiro, você é informado que Fann2MQL está sob a licença GPL:


Instalação do pacote Fann2MQL, passo 1

Escolha a pasta para instalar o pacote. Você pode usar o padrão Program Files\Fann2MQL\ ou instalar diretamente no seu diretório Meta Trader\experts\. Melhor colocar todos os arquivos diretamente em seus lugares caso contrário, você vai ter que copiá-los manualmente.


Instalação do pacote Fann2MQL, passo 2

O instalador coloca arquivos nas seguintes pastas :


include\ folder


libraries\ folder


src\ folder

Se você optar por instalar na pasta dedicada Fann2MQL, copie o conteúdo das suas subpastas include e libraries em seu diretório apropriado do terminal Meta Trader.

O instalador coloca também a biblioteca FANN na sua pasta da biblioteca de sistema (Windows\system32 na maioria dos casos). A pasta src contém todo o código fonte do pacote Fann2MQL. Precisando de mais informações sobre os códigos internos, você poderá ler o código fonte na documentação final. Querendo, você também pode melhorar o código e incluir recursos adicionais. Se você implementar qualquer coisa interessante, solicito que me envie suas correções.


O uso das redes neurais em seu EA

Uma vez que o Fann2MQL é instalado, você pode começar a escrever o seu próprio EA ou indicador. Há uma abundância de possíveis uso da Rede Neural. Você pode prever os movimentos futuros de preços, mas a qualidade de tais previsões e possibilidade de tomar vantagem real são duvidosos. Você pode tentar escrever sua própria estratégia usando técnicas de Aprendizado Por Reforço, um Q-Learning ou algo similar. Você pode tentar usar as Redes Neurais como um filtro de sinal para um EA heurístico ou combinar todas estas técnicas e adicionar o que você realmente deseja. Você é limitado somente por sua imaginação.

Aqui vou mostrar-lhe um exemplo do uso da Rede Neural como um filtro simples para sinais gerados pelo indicador MACD. Por favor, não considere um EA tão valioso, mas apenas um exemplo de aplicação de Fann2MQL. A seguir uma explicação de como funciona este EA exemplo:

O EA NeuroMACD.mq4 mostra como a Fann2MQL pode ser efetivamente usado em MQL. A primeira coisa para o EA é a declaração de variáveis globais, seções "define" e "include".

// Incluir pacote Rede Neural
#include <Fann2MQL.mqh>

// Definições Globais
#define ANN_PATH "C:\\ANN\\"
// Nome do EA
#define NAME "NeuroMACD"

//---- Parâmetros de entrada
extern double Lots=0.1;
extern double StopLoss=180.0;
extern double TakeProfit=270.0;
extern int FastMA=18;
extern int SlowMA=36;
extern int SignalMA=21;
extern double Delta=-0.6;
extern int AnnsNumber=16;
extern int AnnInputs=30;
extern bool NeuroFilter=true;
extern bool SaveAnn=false;
extern int DebugLevel=2;
extern double MinimalBalance=100;
extern bool Parallel=true;

// As variáveis globais

// Caminho para pasta anns (redes neurais artificiais)
string AnnPath;

// Número mágico de negociação
int MagicNumber=65536;

// AnnsArray[ann#] - Array da anns (redes neurais artificiais)
int AnnsArray[];

// Todas as anns (redes neurais artificiais) carregam o status corretamente
bool AnnsLoaded=true;

// AnnOutputs[ann#] - Array retornado da ann (rede neural artificial)
double AnnOutputs[];

// InputVector[] - Array de dados de entrada da ann (rede neural artificial)
double InputVector[];

// Ticket de posição vendida
int LongTicket=-1;

//Ticket de posição comprada
int ShortTicket=-1;

// Lembrando entradas de rede vendidas e compradas
double LongInput[];
double ShortInput[];

O comando "include" diz para carregar o arquivo de cabeçalho Fann2MQL.mqh que contém a declaração de todas as funções Fann2MQL. Depois que todas as funções do pacote Fann2MQL estão disponíveis para uso no script. A constante ANN_PATH define o caminho para armazenar e carregar os arquivos com redes FANN treinadas. Você precisa criar essa pasta ou seja C:\ANN. A constante NAME conterá nome do EA que é usado mais tarde para carregar e salvar os arquivos de rede. Os parâmetros de entrada são bastante óbvios e aqueles que não, serão explicados mais adiante, assim como as variáveis globais.


O ponto de entrada de cada EA é a sua função init():

int init()
  {
   int i,ann;

   if(!is_ok_period(PERIOD_M5)) 
     {
      debug(0,"Wrong period!");
      return(-1);
     }

   AnnInputs=(AnnInputs/3)*3; // Torná-lo inteiro divisível por 3

   if(AnnInputs<3) 
     {
      debug(0,"AnnInputs too low!");
     }
// Calcula MagicNumber e AnnPath 
   MagicNumber+=(SlowMA+256*FastMA+65536*SignalMA);
   AnnPath=StringConcatenate(ANN_PATH,NAME,"-",MagicNumber);

// Inicializar anns (redes neurais artificiais)
   ArrayResize(AnnsArray,AnnsNumber);
   for(i=0;i<AnnsNumber;i++) 
     {
      if(i%2==0) 
        {
         ann=ann_load(AnnPath+"."+i+"-long.net");
           } else {
         ann=ann_load(AnnPath+"."+i+"-short.net");
        }
      if(ann<0)
         AnnsLoaded=false;
      AnnsArray[i]=ann;
     }
   ArrayResize(AnnOutputs,AnnsNumber);
   ArrayResize(InputVector,AnnInputs);
   ArrayResize(LongInput,AnnInputs);
   ArrayResize(ShortInput,AnnInputs);

// Inicializar tópicos Intel TBB
   f2M_parallel_init();

   return(0);
  }

Primeiro verifica se o EA é aplicado para corrigir o período do timeframe. A variável AnnInputs contém o número de entradas da rede neural. Como vamos usar 3 conjuntos de diferentes argumentos que seja divisível por 3, AnnPath é calculada para refletir o NAME e o MagicNumber do EA, que é calculado a partir dos argumentos de entrada SlowMA, FastMA e SignalMA, que são posteriormente utilizados para sinalização do indicador MACD. Uma vez que se conhece a variável AnnPath, o EA tenta carregar as redes neurais usando a função ann_load(), descrição abaixo. Metade das redes carregadas são para filtrar a posição vendida e a outra metade, comprada. A variável AnnsLoaded é utilizada para indicar que todas as redes foram inicializados corretamente. Como você deve ter notado, este exemplo de EA está tentando carregar múltiplas redes. Dúvido que seja realmente necessário nesta aplicação, mas eu quero mostrar todo o potencial da Fann2MQL que está manipulando várias redes ao mesmo tempo, podendo processá-las em paralelo, aproveitando os múltiplos núcleos ou CPUs. Para tornar isto possível, a Fann2MQL está aproveitando a tecnologia Intel® Threading Building Blocks. A função f2M_parallel_init () é usada para inicializar essa interface.

Aqui é a maneira que eu usei para inicializar as redes:

int ann_load(string path)
  {
   int ann=-1;

   /* Carregar a ANN (Rede Neural Artificial) */
   ann=f2M_create_from_file(path);
   if(ann!=-1) 
     {
      debug(1,"ANN: '"+path+"' loaded successfully with handler "+ann);
     }
   if(ann==-1) 
     {

      /* Criar a ANN  (Rede Neural Artificial)*/
      ann=
          f2M_create_standard(4,AnnInputs,AnnInputs,AnnInputs/2+1,1);
      f2M_set_act_function_hidden(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
      f2M_set_act_function_output(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
      f2M_randomize_weights(ann,-0.4,0.4);
      debug(1,"ANN: '"+path+"' created successfully with handler "+ann);
     }
   if(ann==-1) 
     {
      debug(0,"INITIALIZING NETWORK!");
     }
   return(ann);
  }

Como você pode ver, se f2M_create_from_file() falhar através do retorno de valor negativo, a rede é criada pela função f2M_create_standard() com argumentos indicando que a rede criada deve ter 4 camadas (incluindo entrada e saída): insumos AnnInput, os neurônios AnnInput em primeira camada oculta, neurônios AnnInput/2+1 na segunda camada oculta e um neurônio na camada de saída. A f2M_set_act_function_hidden() é usada para definir a função de ativação das camadas ocultas para SIGMOID_SYMMETRIC_STEPWISE (consulte a documentação da FANN: fann_activationfunc_enum). O mesmo vale para a camada de saída. Então existe a chamada para f2m_randomize_weights () que é usada para inicializar conecções neurais ponderadas dentro da rede. Aqui eu usei a variação de <-0.4; 0.4>, mas você pode usar qualquer outra, dependendo da aplicação.

Neste ponto, você já deve ter notado que eu usei muitas vezes a função debug() . É um dos métodos mais simples para alterar o nível de detalhe do seu EA. Juntamente com esta função e o parâmetro de entrada DebugLevel , você pode ajustar a maneira que seu código está produzindo a saída da depuração.

void debug(int level,string text)
  {
   if(DebugLevel>=level) 
     {
      if(level==0)
         text="ERROR: "+text;
      Print(text);
     }
  }

Se no primeiro argumento da função debug (), o nível debug é maior do que DebugLevel, então a função não produz qualquer saída. Se for inferior a igualdade, a string de texto é impressa. Se o nível de depuração é 0, a string "ERROR:" é acrescentada ao iniciar. Desta forma, você pode dividir a depuração produzida pelo seu código a vários níveis. O mais importante são provavelmente os erros, de forma que são atribuídas ao nível 0. Eles serão impressos a menos que você diminua a função DebugLevel menor do que 0 (o que não é aconselhado). No nível 1 algumas informações importantes serão impressas, como a confirmação bem sucedida do carregamento ou criação da rede. No nível 2 ou superior a importância da informação impressa diminuirá gradualmente.

Antes da explicação detalhada da função start(), que é bastante longa, eu preciso mostrar mais algumas funções destinadas a preparar a entrada da rede e executar as redes atuais:

void ann_prepare_input()
  {
   int i;

   for(i=0;i<=AnnInputs-1;i=i+3) 
     {
      InputVector[i]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_MAIN,i*3);
      InputVector[i+1]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_SIGNAL,i*3);
      InputVector[i+2]=InputVector[i-2]-InputVector[i-1];
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double ann_run(int ann,double &vector[])
  {
   int ret;
   double out;
   ret=f2M_run(ann,vector);
   if(ret<0) 
     {
      debug(0,"Network RUN ERROR! ann="+ann);
      return(FANN_DOUBLE_ERROR);
     }
   out=f2M_get_output(ann,0);
   debug(3,"f2M_get_output("+ann+") returned: "+out);
   return(out);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int anns_run_parallel(int anns_count,int &anns[],double &input_vector[])
  {
   int ret;

   ret=f2M_run_parallel(anns_count,anns,input_vector);

   if(ret<0) 
     {
      debug(0,"f2M_run_parallel("+anns_count+") returned: "+ret);
     }
   return(ret);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void run_anns()
  {
   int i;

   if(Parallel) 
     {
      anns_run_parallel(AnnsNumber,AnnsArray,InputVector);
     }

   for(i=0;i<AnnsNumber;i++) 
     {
      if(Parallel) 
        {
         AnnOutputs[i]=f2M_get_output(AnnsArray[i],0);
           } else {
         AnnOutputs[i]=ann_run(AnnsArray[i],InputVector);
        }
     }
  }
//+------------------------------------------------------------------+

A função ann_prepare_input() é utilizada para preparar o nome de entrada às redes. O objetivo dela é bastante simples, mas este é o ponto que devo lembrar-lhe onde os dados de entrada tem de ser devidamente normalizados. Não existe uma normalização sofisticada, neste caso eu simplesmente usei a linha MACD principal e os valores de sinais que nunca excedem a faixa desejada com os dados contabilizados. No exemplo real, você provavelmente deve prestar mais atenção nesta questão. Como você provavelmente deve suspeitar, escolher os argumentos de entrada adequados para entrada de rede, codificar, decompor e normalizar é um dos fatores mais importantes no processamento da rede neural.

Como mencionei antes, a Fann2MQL tem a capacidade estender a funcionalidade normal do terminal MetaTrader, exatamente o processamento paralelo das redes neurais. O argumento mundial Parallel controla esse comportamento. A função run_anns() executa todas as redes inicializadas e recebe as saídas delas e armazena no array AnnOutput[]. A função anns_run_parallel é responsável para manipular o trabalho na forma de vários segmentos. Ela chama a f2m_run_parallel() para executar o primeiro argumento que é o número de redes para processar, o segundo argumento é um array contendo identificadores para todas as redes que você deseja executar, fornecendo o vetor de entrada como um terceiro argumento. Todas as redes têm de ser executadas sobre os mesmos dados de entrada. A saída a partir da rede é obtida por várias chamadas para f2m_get_output().

Agora vamos ver a função start():

int
start()
  {
   int i;
   bool BuySignal=false;
   bool SellSignal=false;

   double train_output[1];

   /*É permitido negociar? */
   if(!trade_allowed()) 
     {
      return(-1);
     }

   /* Preparar e executar redes neurais */
   ann_prepare_input();
   run_anns();

   /* Cálculo do último e anterior valor MACD .
* Atrasa uma barra, enquanto a barra atual está sendo construída
*/
   double MacdLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,1);
   double MacdPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,2);

   double SignalLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           1);
   double SignalPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           2);

   /* Sinal BUY */
   if(MacdLast>SignalLast && MacdPrev<SignalPrev) 
     {
      BuySignal=true;
     }
   /* Sinal SELL */
   if(MacdLast<SignalLast && MacdPrev>SignalPrev) 
     {
      SellSignal=true;
     }

   /* Sem posição comprada */
   if(LongTicket==-1) 
     {
      /* Sinal BUY */
      if(BuySignal) 
        {
         /* Se NeuroFilter é configurado para usar a ann (rede neural artificial), você decide!    :) */
         if(!NeuroFilter || ann_wise_long()>Delta) 
           {
            LongTicket=
          OrderSend(Symbol(),OP_BUY,Lots,Ask,3,
                    Bid-StopLoss*Point,
                    Ask+TakeProfit*Point,
                    NAME+"-"+"L ",MagicNumber,0,Blue);
           }
         /* Lembre-se da entrada da rede */
         for(i=0;i<AnnInputs;i++) 
           {
            LongInput[i]=InputVector[i];
           }
        }
        } else {
      /* Manter a posição comprada */
      OrderSelect(LongTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0) 
        {
         // A ordem é aberta
         if(SellSignal && OrderProfit()>0) 
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0) 
        {
         // A ordem é fechada
         LongTicket=-1;
         if(OrderProfit()>=0) 
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=0;i<AnnsNumber;i+=2) 
           {
            ann_train(AnnsArray[i],LongInput,train_output);
           }
        }
     }

   /* Sem posição vendida */
   if(ShortTicket==-1) 
     {
      if(SellSignal) 
        {
         /* Se NeuroFilter é configurado para usar  a ann (rede neural artificial), você decide!    :)  */
         if(!NeuroFilter || ann_wise_short()>Delta) 
           {
            ShortTicket=
          OrderSend(Symbol(),OP_SELL,Lots,Bid,3,
                    Ask+StopLoss*Point,
                    Bid-TakeProfit*Point,NAME+"-"+"S ",
                    MagicNumber,0,Red);
           }
         /* Lembre-se da entrada da rede */
         for(i=0;i<AnnInputs;i++) 
           {
            ShortInput[i]=InputVector[i];
           }
        }
        } else {
      /* Manter a posição vendida */
      OrderSelect(ShortTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0) 
        {
         // A ordem é aberta
         if(BuySignal && OrderProfit()>0) 
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0) 
        {
         // A ordem é fechada
         ShortTicket=-1;
         if(OrderProfit()>=0) 
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=1;i<AnnsNumber;i+=2) 
           {
            ann_train(AnnsArray[i],ShortInput,train_output);
           }
        }
     }

   return(0);
  }
//+------------------------------------------------------------------+

Vou descrever brevemente o código, pois é muito bem comentado. A função trade_allowed() verifica se é permitida a negociação, basicamente verifica a variável AnnsLoaded que indica que todas as anns (redes neurais artificiais) foram inicializadas corretamente, a seguir verifica o timeframe mínimo adequado de saldo do balanço da conta e bem no final permite a negociação apenas no primeiro tick de uma nova barra. Duas funções seguinte são usadas para preparar entrada e executar o processamento da rede, descritas apenas algumas linhas acima. Em seguida, calculamos e colocamos as variáveis para processar os valores da linha de sinal e principal do indicador MACD na última barra acumulada e na anterior. A barra atual é omitida, pois não está completa ainda e pode ser redesenhada. O SellSignal e BuySignal são calculados de acordo com o cruzamento da linha de sinal e principal do indicador MACD. Ambos os sinais são usados para o processamento da posição de compra e venda respectivamente, que são simétricas, então vou descrever apenas o caso das posições compradas.

A variável LongTicket contém o número do ticket da posição atualmente aberta. Se for igual a -1, então nenhuma posição foi aberta, o BuySignal é configurado para indicar uma boa oportunidade de abertura da posição de compra. Se a variável NeuroFilter não é definida, a posição comprada é aberta e o processo é sem a filtragem dos sinais da rede neural - a ordem é enviada para comprar. Neste ponto, a variável LongInput destina-se a lembrar que a InputVector é preparado pela ann_prepare_input () para uso posterior.

E se a variável LongTicekt contém o número de tick válido, o EA verifica se ainda está aberta ou foi fechada pelo StopLoss ou TakeProfit. Se a ordem não fecha, nada acontece, no entanto se a ordem é fechada, o vetor train_output[], que tem apenas um "otput", é calculado para aguardar o valor -1 se a ordem foi fechada com uma perda ou se a ordem foi fechada com lucro. Esse valor é então transmitido para a função ann_train() e todas as redes de responsabilidades para manipulação da posição longa são treinadas conjuntamente. Como o vetor de entrada da variável LongInput é utilizado, ele prende a InputVector no momento de abertura da posição. Desta forma, a rede é ensinada qual sinal está trazendo lucros e qual não está.

Uma vez que você tem uma rede treinada, altere NeuroFilter para true, acionando a rede de filtragem. A ann_wise_long() está usando a rede neural sabiamente calculada como uma média dos valores que retornam por todas as redes destinadas a manipular a posição de compra. O parâmetro Delta é utilizado como um valor de limiar, indicando que o sinal filtrado é válido ou não. Tal como muitos outros valores obtidos através do processo de otimização.

Uma vez que sabemos como funciona, agora vou lhe mostrar como pode ser usado. O par de moedas para o teste é o EURUSD. Eu usei os dados da corretora Alpari, convertidos no timeframe M5. Eu usei o período de 31/12/2007 a 01/01/2009 para treinamento/otimização e 01/01/2009-22/03/2009 para fins de testes. Na primeira corrida, tentei obter os valores mais rentáveis para os argumentos: StopLoss, TakeProfit, SlowMA, FastMA e SignalMA, codificados no arquivo NeuroMACD.mq4. O parâmetro NeuroFIlter foi desligado bem como o SaveAnn. O AnnsNumber foi definido como 0 para evitar o processamento neural. Eu usei o algoritmo genético para o processo de otimização. Uma vez que os valores foram obtidos, a relatório do resultado foi o seguinte:


Relatório sobre dados do treinamento após a otimização de parâmetros básicos

Este EA foi executado numa mini conta com o tamanho do lote igual 0,01 e o saldo inicial de 200. No entanto, você pode ajustar esses parâmetros de acordo com as configurações da conta ou preferências.

Neste ponto tivemos suficientes negócios rentáveis e com perdas para que pudéssemos acionar o SaveAnn e definir o AnnsNumber para 30. Uma vez feito isso eu executo o testador mais uma vez. O resultado foi exactamente o mesmo, exceto pelo fato que o processo foi muito mais lento (como um resultado de processamento neuronal) e a pasta C:\ANN foi preenchida com as redes treinadas como mostrado na imagem abaixo. Certifique-se a pasta C:/ANN existia antes desta execução!


A pasta C:ANN.

Apos treinar as redes, é hora de testar como o EA se comporta. Primeiro vamos experimentá-lo sobre os dados de treinamento. Mudar o NeuroFilter para true e SaveAnn para false e iniciar o teste. O resultado é mostrado abaixo. Note que poderá variar um pouco para você, caso que haja alguma aleatoriedade dentro das conexões ponderadas das redes neurais fornecidas no processo de inicialização da rede (neste exemplo eu usei chamada explícita para f2M_randomize_weights() dentro da ann_load()).


O resultado obtido a partir de dados de treinamento com a filtragem do sinal neural ligada.

O lucro líquido é pouco maior (20,03 contra 16,92), porém o fator de lucro é muito maior (1,25 versus 1,1). O número de negócios é muito menor (83 vs 1188) e o número médio de perdas consecutiva é reduzido de 7 para 2. No entanto somente está mostrando que a filtragem do sinal neural está funcionando, mas não fala nada sobre como opera com os dados que não foram utilizados durante o treinamento. O resultado obtido no período de teste (01/01/2009 - 28/30/2009) é mostrado abaixo:


O resultado obtido a partir dos dados de teste com a filtragem neural ligada.

O número de negócios realizados é bastante baixo e é difícil dizer a qualidade desta estratégia, pois eu não estou mostrando-lhe como escrever o melhor EA rentável, mas sim explicando como você poderia usar redes neurais no seu código MQL4. O efeito real do uso de redes neurais neste caso somente será visto quando comparados os resultados do EA com dados do teste e com o NeuroFilter ligado ou desligado. Abaixo está o resultado obtido a partir dos testes do período de dados sem filtragem dos sinais neurais:


Os resultados a apartir dos testes de dados sem filtragem neural.

A diferença é bastante óbvia. Como você pode ver a filtragem do sinal neural tornou um EA perdedor num EA rentável!



Conclusão

Espero que você tenha aprendido com este artigo como usar redes neurais no MetaTrader. Com a ajuda do pacote simples, livre e código aberto do Fann2MQL, você pode facilmente adicionar uma camada de rede neural em praticamente qualquer Expert Advisor, ou começar a escrever o seu próprio, totalmente ou parcialmente baseado em redes neurais. A capacidade única de multileitura pode acelerar o processamento muitas vezes, dependendo do número de seus núcleos da CPU, especialmente quando otimizar certos parâmetros. Em um caso, eu abreviei uma otimização de processamento num EA de Aprendizado por Reforço de 4 dias para "apenas" 28 horas numa CPU 4 core Intel.

Durante o desenvolvimento deste artigo eu decidi publicar o EA Fann2MQL no seu próprio site: http://fann2mql.wordpress.com/. Você encontrará no site a última versão do Fann2MQL e possivelmente todas as versões futuras, bem como a documentação de todas as funções. Comprometo-me a manter este software sob licença GPL para todas as versões. Se houver um retorno nos comentários, as solicitações de recursos ou patches interessantes serão lançados nas próximas atualizações.

Por favor observe que este artigo mostra apenas o uso básico da Fann2MQL. Como este pacote não é muito mais do que FANN, você pode usar todas as ferramentas projetadas para gerenciar redes FANN, tais como:

E há muito mais sobre FANN na homepage Fast Artificial Neural Network Library: http://leenissen.dk/fann/!


Post Scriptum

Depois de escrever este artigo, eu encontrei um erro insignificante no EA NeuroMACD.mq4. A função OrderClose() para posição vendida foi alimentado com o número do tick da posição comprada. Isso resultou numa estratégia enviesada que era mais provável manter as posições vendidas e fechar posições compradas:

/* Manter a posição vendida */
OrderSelect(ShortTicket,SELECT_BY_TICKET);
if(OrderCloseTime()==0) 
  {
// A ordem é aberta
   if(BuySignal && OrderProfit()>0) 
     {
      OrderClose(LongTicket,Lots,Bid,3);
     }
  }

Na versão correta do script , eu corrigi este erro e removi a estratégia OrderClose() em tudo. Isso não mudou o quadro geral da influência de filtragem neural sobre a EA e ainda a forma um curva de balanço bem diferente. Você pode encontrar as duas versões deste EA anexado a este artigo.