Usando Indicadores MetaTrader 5 com Estrutura de Aprendizado de Máquina ENCOG para Previsão das Séries Temporais

investeo | 14 fevereiro, 2014

Introdução

Este artigo irá apresentar MetaTrader 5 para ENCOG - rede neural avançada e estrutura de aprendizado de máquina desenvolvido por Heaton Research. Existem métodos descritos anteriormente que conheço o qual permitem MetaTrader usar técnicas de aprendizado de máquina: FANN, NeuroSolutions, Matlab e NeuroShell. Espero que ENCOG seja uma solução complementar, uma vez que é um código robusto e bem desenhado.

Por que eu escolhi ENCOG? Existem algumas razões.

  1. ENCOG é usado em dois outros pacotes de software de negociação comercial. Um é baseado em C #, o segundo em JAVA. Isto significa que já foi testado para prever dados das séries temporais financeiras.
  2. ENCOG é um software livre e de fonte aberta. Se você gostaria de ver o que está acontecendo dentro de uma rede neural você pode procurar o código-fonte. Isto é o que eu de fato fiz para entender partes do problema de previsão das séries temporais. C # é claro e fácil de entender a linguagem de programação.
  3. ENCOG é muito bem documentado. Mr Heaton, fundador do Heaton Research oferece um curso online gratuito sobre redes neurais, aprendizado de máquina e uso do ENCOG para predizer dados futuros. Eu passei por muitos de seus ensinamentos antes de escrever este artigo. Eles me ajudaram a entender muito as redes neurais artificiais. Adicionalmente, existem e-books sobre programação ENCOG em Java e C # no site Heaton Research. A documentação de ENCOG completa está disponível online.
  4. ENCOG não é um projeto extinto. A medida que escrevo este artigo, ENCOG 2.6 ainda está em desenvolvimento. O guia básico ENCOG 3.0 foi recentemente publicado.
  5. ENCOG é robusto. é bem projetado, pode usar múltiplos núcleos de CPU e multitransação para acelerar os cálculos da rede neural. Partes do código começam a ser portados para OpenCL - cálculos habilitados para GPU.
  6. Recursos suportados atualmente por ECNOG:

Tipos de Aprendizado de Máquina

Arquiteturas de Rede Neural

Técnicas de TreinamentoFunções de ativaçãoTécnicas de Randomização
  • Randomização de Faixa
  • Números Aleatórios Gaussian
  • Fan-In
  • Nguyen-Widrow

Características planejadas:

Como você pode ver esta é uma longa lista de recursos.

Este artigo introdutório concentra-se na arquitetura de Rede Neural de alimentação direta com treinamento de propagação resiliente (RPROP). Ele também aborda noções básicas de preparação de dados - janela temporal e normalização para previsão de séries temporais temporárias.

O conhecimento que permitiu-me escrever este artigo é baseado em tutoriais disponíveis no site Heaton Research e muito artigos recentes sobre previsão de séries temporais financeiras em NinjaTrader. Por favor, note que ENCOG é baseado em JAVA e C#. Este artigo não seria possível de ser escrito sem o meu trabalho anterior: Expondo o código C# para MQL5 usando exportação não administrada. Essa solução permitiu usar C # DLL como uma ponte entre o indicador Metatrader 5 e o preditor de série temporal ENCOG.


1. Utilizando Valores de Indicadores Técnicos como Entrada para uma Rede Neural

A Rede Neural Artificial é um algoritmo humano de engenharia que tenta imitar rede neural do cérebro.

Existem vários tipos de algoritmos neurais disponíveis, e existe uma variedade de arquiteturas de redes neurais. O campo de pesquisa é tão amplo que existem livros inteiros dedicados a um único tipo de rede neural. Uma vez que esses detalhes estão fora do escopo deste artigo, só posso recomendar olhar os tutoriais Heaton Research ou ler um livro sobre o assunto. Vou me concentrar em entradas e saídas da rede neural de alimentação direta e tentar descrever o exemplo prático de previsão das séries temporais financeiras.

Para começar a previsão das séries temporais financeiras temos que pensar o que devemos fornecer para a rede neural e o que podemos esperar em troca. Na maioria dos pensamentos black-box abstratos vamos conseguir lucro ou prejuízo, tomando posições longas ou curtas no contrato de um dado de segurança e fechamento do negócio depois de algum tempo.

Pela observação dos preços passados de segurança e valores dos indicadores técnicos, tentamos prever o sentimento futuro ou a direção dos preços, a fim de comprar ou vender um contrato e garantir que nossa decisão não é feita pelo lançamento de uma moeda. A situação parece mais ou menos como na figura abaixo:

Figura 1. Previsão de séries temporais financeiras usando indicadores técnicos

Figura 1. Previsão de séries temporais financeiras usando indicadores técnicos

Vamos tentar conseguir o mesmo com a inteligência artificial. A rede neural vai tentar reconhecer os valores do indicador e decidir se há uma chance de que o preço irá subir ou descer. Como vamos conseguir isso? Posteriormente, faremos previsão de séries temporais financeiras utilizando arquitetura de rede neural de alimentação direta, eu acho que nós precisamos fazer uma introdução à sua arquitetura.

A rede neural de alimentação direta é composta por neurônios que são agrupados em camadas. Tem de haver um mínimo de duas camadas: uma camada de entrada, que contém neurônios de entrada e uma camada de saída que contém neurônios de saída. Pode haver também camadas escondidas que estão entre a entrada e as camadas de saída. A camada de entrada pode ser simplesmente imaginada de uma matriz de valores duplos e uma camada de saída pode consistir de um ou mais neurônios que também formam uma matriz de valores duplos. Por favor, veja a figura abaixo:

Figura 2. Camadas de rede neural de alimentação direta

Figura 2. Camadas de rede neural de alimentação direta

As ligações entre os neurônios não foram desenhadas a fim de simplificar o desenho. Cada neurônio da camada de entrada é ligado a um neurônio na camada escondida. Cada neurônio na camada escondida é ligado a um neurônio na camada de saída.

Cada conexão tem o seu peso, que é também um valor duplo e a função de ativação com um limite, que é responsável pela ativação de um neurônio e por passar as informações para o próximo neurônio. é por isso que é chamado de rede de "alimentação direta" - informações com base em saídas de neurônios ativados é alimentado diretamente de uma camada para outra camada de neurônios. Para ver os vídeos introdutórios detalhados sobre as redes neurais de alimentação direta você pode querer visitar os seguintes links:

Depois de aprender sobre arquitetura de rede neural e seus mecanismos você ainda pode estar perplexo.

Os principais problemas são:

  1. Quais são os dados que devemos fornecer para uma rede neural?
  2. Como vamos alimentá-la?
  3. Como preparar os dados de entrada para uma rede neural?
  4. Como escolher a arquitetura de rede neural? Quantos neurônios de entrada, neurônios ocultos e neurônios de saída precisamos?
  5. Como treinar a rede?
  6. O que esperar para ser a saída?

2. Com que Dados Alimentar a Rede Neural

Uma vez que estamos lidando com as previsões financeiras baseadas na produtividade do indicador, devemos alimentar a rede com indicadores de valores de saída. Para este artigo eu escolhi Stochastic %K, Stochastic Slow %D, e Williams %R como entradas.

Figura 3. Indicadores técnicos usados para previsão

Figura 3. Indicadores técnicos usados para previsão

Para extrair valores dos indicadores podemos usar funções MQL5 iWPRe iStochastic :

double StochKArr[], StochDArr[], WilliamsRArr[];

ArraySetAsSeries(StochKArr, true);   
ArraySetAsSeries(StochDArr, true);   
ArraySetAsSeries(WilliamsRArr, true);

int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH);
int hWilliamsR = iWPR(Symbol(), Period(), 21);
   
CopyBuffer(hStochastic, 0, 0, bufSize, StochKArr);
CopyBuffer(hStochastic, 1, 0, bufSize, StochDArr);
CopyBuffer(hWilliamsR, 0, 0, bufSize, WilliamsRArr);
    

Após esse código ser executado três matrizes StochKArr, StochDArr e WilliamsRArr devem ser preenchidas com os valores de saída dos indicadores. Dependendo do tamanho da amostra de treino isso pode ter até alguns milhares de valores. Por favor, tenha em mente que esses dois indicadores foram escolhidos somente para fins educacionais.

Você é encorajado a experimentar todos os indicadores que você ache apropriado para a previsão. Você pode querer alimentar a rede com os preços de ouro e petróleo para prever índices de ações, ou você pode usar pares de moedas correlacionadas para prever outro par de moedas.

3. Dados de Entrada de Janela Temporal

Depois de ter reunido os dados de entrada de vários indicadores, precisamos executar a "janela temporal" da entrada antes de alimentá-la para a rede neural. A janela temporal é uma técnica que permite apresentar entradas para a rede, como movimento de porções de dados. Você pode imaginar uma caixa de movimento de dados de entrada se movendo direto no eixo do tempo. Existem basicamente duas etapas envolvidas neste processo:

1. Coleta de dados de entrada de cada buffer indicador. Precisamos copiar número INPUT_WINDOW de elementos a partir da posição de partida para o futuro. A janela de entrada é o número de barras utilizadas para a previsão.

Figura 4. Coletando dados da janela de entrada do buffer indicador

Figura 4. Coletando dados da janela de entrada do buffer indicador

Como você pode ver no exemplo acima que INPUT_WINDOW equivale a 4 barras e copiamos elementos na matriz I1. I1 [0] é o primeiro elemento I1 [3] é o último. Da mesma forma os dados tem que ser copiados de outros indicadores em matrizes de tamanho INPUT_WINDOW. Esta figura é válida para as matrizes de série temporal com sinalizador AS_SERIES configurado para verdadeiro.

2. Combinando matrizes INPUT_WINDOW em uma matriz que é alimentada na camada de entrada da rede neural.

Figura 5. Matrizes da janela de entrada de janela temporal

Figura 5. Matrizes da janela de entrada de janela temporal

Existem três indicadores, primeiramente, tomamos primeiro valor de cada indicador, logo, o segundo valor de cada indicador e continuamos até que a janela de entrada estivesse cheia, como na figura acima. Tal matriz combinada a partir das saídas dos indicadores podem ser alimentadas para a camada de entrada da rede neural. Quando chega uma nova barra, os dados são cortados por um elemento e todo o procedimento é repetido. Se você estiver interessado em mais detalhes sobre como preparar os dados para a previsão você também pode querer ver um vídeo sobre o assunto.

4. Normalizando Dados de Entrada

A fim de tornar a rede neural efetiva devemos normalizar os dados. Isto é necessário para o correto cálculo das funções de ativação. A normalização é um processo matemático que converte dados em intervalo de 0..1 ou -1..1. Dados normalizados pode ser desnormalizados, em outras palavras, convertidos novamente em escala original.

A desnormalização é necessária para decodificar a saída da rede neural para um formato legível. Felizmente, ENCOG toma conta da normalização e desnormalização, portanto, não há necessidade de implementá-lo. Se você está curioso para saber como ele funciona, você pode analisar o seguinte código:

/**
         * Normalize the specified value.
         * @param value The value to normalize.
         * @return The normalized value.
         */
        public static double normalize(final int value) {
                return ((value - INPUT_LOW) 
                                / (INPUT_HIGH - INPUT_LOW))
                                * (OUTPUT_HIGH - OUTPUT_LOW) + OUTPUT_LOW;
        }
        
        /**
         * De-normalize the specified value.
         * @param value The value to denormalize.
         * @return The denormalized value.
         */
        public static double deNormalize(final double data) {
                double result = ((INPUT_LOW - INPUT_HIGH) * data - OUTPUT_HIGH
                                * INPUT_LOW + INPUT_HIGH * OUTPUT_LOW)
                                / (OUTPUT_LOW - OUTPUT_HIGH);
                return result;
        }
      

e ler um artigo sobre normalização para mais detalhes.

5. Escolhendo a arquitetura de rede e o número de neurônios

Para um novato no assunto, escolher a arquitetura de rede correta é uma tarefa difícil. Neste artigo eu estou restringindo a arquitetura de rede neural de alimentação direta para três camadas: uma camada de entrada, uma camada oculta e uma camada de saída. Você é livre para experimentar com maior número de camadas.

Para a camada de entrada e de saída, seremos capazes de contar com precisão o número de neurônios necessários. Para uma camada oculta estaremos tentando minimizar o erro de rede neural, usando um algoritmo de seleção direto. Você é encorajado a usar outros métodos, pode haver alguns algoritmos genéticos para o cálculo do número de neurônios.

Outro método utilizado por ENCOG é chamado algoritmo de seleção retroativo ou corte, basicamente, ele avalia as conexões entre as camadas e remove neurônios ocultos com zero conexões sobrecarregadas, você também pode querer experimentá-lo.

5,1. Camada de neurônios de entrada

Devido a janela temporal o número de neurônios na camada de entrada deve ser igual ao número de indicadores vezes as barras utilizadas para prever a barra seguinte. Se usarmos três indicadores como entradas e o tamanho da janela de entrada for igual a 6 barras, a camada de entrada será composta de 18 neurônios. A camada de entrada é alimentada com dados elaborados pela janela temporal.

5,2. Camada de neurônios ocultos

O número de redes ocultas deve ser estimado com base no desempenho da rede neural treinada. Não há equação matemática simples para um número de neurônios ocultos. Antes de escrever o artigo eu usei várias abordagens de tentativa e erro e achei um algorítimo no site de Heaton Research que ajuda a entender o algoritmo de seleção direto:

Figura 6. Algorítimo de seleção progressivo para números de neurônios ocultos

Figura 6. Algorítimo de seleção progressivo para números de neurônios ocultos

5,3. Camada de neurônios de saída

Para os nossos propósitos, o número de neurônios de saída é o número de barras que estamos tentando prever. Por favor, lembre-se que quanto maior o número de neurônios ocultos e de saída, maior tempo a rede leva para treinar. Neste artigo eu estou tentando prever uma barra no futuro, pois a camada de saída consiste de um neurônio.

6. Exportando Dados de Treinamento de MetaTrader 5 para ENCOG

Encog aceita arquivos CSV para treinamento de rede neural.

Olhei para o formato de arquivo exportado de outro software de negociação para ENCOG e o script MQL5 implementado que prepara o mesmo formato de arquivo para o treinamento. Vou apresentar no primeiro indicador de exportação e continuar mais tarde com vários indicadores.

A primeira linha de dados é um cabeçalho separados por vírgulas:

DATE,TIME,CLOSE,Indicator_Name1,Indicator_Name2,Indicator_Name3

As três primeiras colunas contêm data, horário e valores próximos, e as seguintes colunas contêm nomes de indicadores. As próximas linhas do arquivo de treinamento devem conter os dados separados por vírgula, e os valores do indicador devem ser escritos em formato científico:

20110103,0000,0.93377000,-7.8970208860e-002

Por favor, observe o script já pronto para um indicador abaixo.

//+------------------------------------------------------------------+
//|                                                ExportToEncog.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

// Export Indicator values for NN training by ENCOG
extern string IndExportFileName = "mt5export.csv";
extern int  trainSize = 400;
extern int  maPeriod = 210;

MqlRates srcArr[];
double expBullsArr[];

void OnStart()
  {
//---
   ArraySetAsSeries(srcArr, true);   
   ArraySetAsSeries(expBullsArr, true);      
         
   int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr);
   
   if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; }
   
   int hBullsPower = iBullsPower(Symbol(), Period(), maPeriod);
   
   CopyBuffer(hBullsPower, 0, 0, trainSize, expBullsArr);
   
   int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP);
   
   FileWriteString(hFile, "DATE,TIME,CLOSE,BullsPower\n");
   
   Print("Exporting indicator data to " + IndExportFileName);
   
   for (int i=trainSize-1; i>=0; i--)
      {
         string candleDate = TimeToString(srcArr[i].time, TIME_DATE);
         StringReplace(candleDate,".","");
         string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES);
         StringReplace(candleTime,":","");
         FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), DoubleToString(expBullsArr[i], -10));
      }
      
   FileClose(hFile);   
     
   Print("Indicator data exported."); 
  }
//+------------------------------------------------------------------+
    

O arquivo de resultado que pode ser usado para o treinamento deverá ser parecido com a seguinte produção:

//+------------------------------------------------------------------+
//|                                                ExportToEncog.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

// Export Indicator values for NN training by ENCOG
extern string IndExportFileName = "mt5export.csv";
extern int  trainSize = 2000;

MqlRates srcArr[];
double StochKArr[], StochDArr[], WilliamsRArr[];

void OnStart()
  {
//---
   ArraySetAsSeries(srcArr, true);   
   ArraySetAsSeries(StochKArr, true);   
   ArraySetAsSeries(StochDArr, true);   
   ArraySetAsSeries(WilliamsRArr, true);
         
   int copied = CopyRates(Symbol(), Period(), 0, trainSize, srcArr);
   
   if (copied!=trainSize) { Print("Not enough data for " + Symbol()); return; }
   
   int hStochastic = iStochastic(Symbol(), Period(), 8, 5, 5, MODE_EMA, STO_LOWHIGH);
   int hWilliamsR = iWPR(Symbol(), Period(), 21);
   
   
   CopyBuffer(hStochastic, 0, 0, trainSize, StochKArr);
   CopyBuffer(hStochastic, 1, 0, trainSize, StochDArr);
   CopyBuffer(hWilliamsR, 0, 0, trainSize, WilliamsRArr);
    
   int hFile = FileOpen(IndExportFileName, FILE_CSV | FILE_ANSI | FILE_WRITE | FILE_REWRITE, ",", CP_ACP);
   
   FileWriteString(hFile, "DATE,TIME,CLOSE,StochK,StochD,WilliamsR\n");
   
   Print("Exporting indicator data to " + IndExportFileName);
   
   for (int i=trainSize-1; i>=0; i--)
      {
         string candleDate = TimeToString(srcArr[i].time, TIME_DATE);
         StringReplace(candleDate,".","");
         string candleTime = TimeToString(srcArr[i].time, TIME_MINUTES);
         StringReplace(candleTime,":","");
         FileWrite(hFile, candleDate, candleTime, DoubleToString(srcArr[i].close), 
                                                 DoubleToString(StochKArr[i], -10),
                                                 DoubleToString(StochDArr[i], -10),
                                                 DoubleToString(WilliamsRArr[i], -10)
                                                 );
      }
      
   FileClose(hFile);   
     
   Print("Indicator data exported."); 
  }
//+------------------------------------------------------------------+
    

O arquivo de resultado deve ter todos os valores do indicador:

DATE,TIME,CLOSE,StochK,StochD,WilliamsR
20030707,0000,1.37370000,7.1743119266e+001,7.2390220187e+001,-6.2189054726e-001
20030708,0000,1.36870000,7.5140977444e+001,7.3307139273e+001,-1.2500000000e+001
20030709,0000,1.35990000,7.3831775701e+001,7.3482018082e+001,-2.2780373832e+001
20030710,0000,1.36100000,7.1421933086e+001,7.2795323083e+001,-2.1495327103e+001
20030711,0000,1.37600000,7.5398313027e+001,7.3662986398e+001,-3.9719626168e+000
20030714,0000,1.37370000,7.0955352856e+001,7.2760441884e+001,-9.6153846154e+000
20030715,0000,1.38560000,7.4975891996e+001,7.3498925255e+001,-2.3890784983e+000
20030716,0000,1.37530000,7.5354107649e+001,7.4117319386e+001,-2.2322435175e+001
20030717,0000,1.36960000,7.1775345074e+001,7.3336661282e+001,-3.0429594272e+001
20030718,0000,1.36280000,5.8474576271e+001,6.8382632945e+001,-3.9778325123e+001
20030721,0000,1.35400000,4.3498596819e+001,6.0087954237e+001,-5.4946524064e+001
20030722,0000,1.36130000,2.9036761284e+001,4.9737556586e+001,-4.5187165775e+001
20030723,0000,1.34640000,1.6979405034e+001,3.8818172735e+001,-6.5989159892e+001
20030724,0000,1.34680000,1.0634573304e+001,2.9423639592e+001,-7.1555555556e+001
20030725,0000,1.34400000,9.0909090909e+000,2.2646062758e+001,-8.7500000000e+001
20030728,0000,1.34680000,1.2264922322e+001,1.9185682613e+001,-8.2705479452e+001
20030729,0000,1.35250000,1.4960629921e+001,1.7777331716e+001,-7.2945205479e+001
20030730,0000,1.36390000,2.7553336360e+001,2.1035999930e+001,-5.3979238754e+001
20030731,0000,1.36990000,4.3307839388e+001,2.8459946416e+001,-4.3598615917e+001
20030801,0000,1.36460000,5.6996412096e+001,3.7972101643e+001,-5.2768166090e+001
20030804,0000,1.34780000,5.7070193286e+001,4.4338132191e+001,-8.1833910035e+001
20030805,0000,1.34770000,5.3512705531e+001,4.7396323304e+001,-8.2006920415e+001
20030806,0000,1.35350000,4.4481132075e+001,4.6424592894e+001,-7.1972318339e+001
20030807,0000,1.35020000,3.3740028156e+001,4.2196404648e+001,-7.7681660900e+001
20030808,0000,1.35970000,3.0395426394e+001,3.8262745230e+001,-6.1245674740e+001
20030811,0000,1.35780000,3.4155781326e+001,3.6893757262e+001,-6.4532871972e+001
20030812,0000,1.36880000,4.3488943489e+001,3.9092152671e+001,-4.5501730104e+001
20030813,0000,1.36690000,5.1160443996e+001,4.3114916446e+001,-4.8788927336e+001
20030814,0000,1.36980000,6.2467599793e+001,4.9565810895e+001,-2.5629290618e+001
20030815,0000,1.37150000,6.9668246445e+001,5.6266622745e+001,-2.1739130435e+001
20030818,0000,1.38910000,7.9908906883e+001,6.4147384124e+001,-9.2819614711e+000
    

Você pode modificar o segundo exemplo para produzir facilmente um script que irá atender as suas necessidades.


7. Treinamento de Rede Neural

O treinamento de rede já foi preparado em C# pela Heaton Research. ENCOG 2.6 implementa Encog.App.Quant namespace que é uma base para a previsão de séries temporais financeiras. O script de treinamento é muito flexível, pode ser facilmente ajustado a qualquer número de indicadores de entrada. Você só deve mudar o local diretório de MetaTrader 5 em constante de DIRETóRIO.

A arquitetura de rede e os parâmetros de treinamento podem ser facilmente personalizados mudando as seguintes variáveis:

        /// <summary>
        /// The size of the input window.  This is the number of bars used to predict the next bar.
        /// </summary>
        public const int INPUT_WINDOW = 6;        

        /// <summary>
        /// The number of bars forward we are trying to predict.  This is usually just 1 bar.  The future indicator used in step 1 may
        /// well look more forward into the future. 
        /// </summary>
        public const int PREDICT_WINDOW = 1;

        /// <summary>
        /// The number of bars forward to look for the best result.
        /// </summary>
        public const int RESULT_WINDOW = 5;

        /// <summary>
        /// The number of neurons in the first hidden layer.
        /// </summary>
        public const int HIDDEN1_NEURONS = 12;

        /// <summary>
        /// The target error to train to.
        /// </summary>
        public const double TARGET_ERROR = 0.01;

O código é bastante auto-explicativo, portanto, o melhor é lê-lo com atenção:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Encog.App.Quant.Normalize;
using Encog.Util.CSV;
using Encog.App.Quant.Indicators;
using Encog.App.Quant.Indicators.Predictive;
using Encog.App.Quant.Temporal;
using Encog.Neural.NeuralData;
using Encog.Neural.Data.Basic;
using Encog.Util.Simple;
using Encog.Neural.Networks;
using Encog.Neural.Networks.Layers;
using Encog.Engine.Network.Activation;
using Encog.Persist;

namespace NetworkTrainer
{
    public class Program
    {
        /// <summary>
        /// The directory that all of the files will be stored in.
        /// </summary>
        public const String DIRECTORY = "d:\\mt5\\MQL5\\Files\\";

        /// <summary>
        /// The input file that starts the whole process.  This file should be downloaded from NinjaTrader using the EncogStreamWriter object.
        /// </summary>
        public const String STEP1_FILENAME = DIRECTORY + "mt5export.csv";

        /// <summary>
        /// We apply a predictive future indicator and generate a second file, with the additional predictive field added.
        /// </summary>
        public const String STEP2_FILENAME = DIRECTORY + "step2_future.csv";

        /// <summary>
        /// Next the entire file is normalized and stored into this file.
        /// </summary>
        public const String STEP3_FILENAME = DIRECTORY + "step3_norm.csv";

        /// <summary>
        /// The file is time-boxed to create training data.
        /// </summary>
        public const String STEP4_FILENAME = DIRECTORY + "step4_train.csv";

        /// <summary>
        /// Finally, the trained neural network is written to this file.
        /// </summary>
        public const String STEP5_FILENAME = DIRECTORY + "step5_network.eg";
       
        /// <summary>
        /// The size of the input window.  This is the number of bars used to predict the next bar.
        /// </summary>
        public const int INPUT_WINDOW = 6;        

        /// <summary>
        /// The number of bars forward we are trying to predict.  This is usually just 1 bar.  The future indicator used in step 1 may
        /// well look more forward into the future. 
        /// </summary>
        public const int PREDICT_WINDOW = 1;

        /// <summary>
        /// The number of bars forward to look for the best result.
        /// </summary>
        public const int RESULT_WINDOW = 5;

        /// <summary>
        /// The number of neurons in the first hidden layer.
        /// </summary>
        public const int HIDDEN1_NEURONS = 12;

        /// <summary>
        /// The target error to train to.
        /// </summary>
        public const double TARGET_ERROR = 0.01;

        static void Main(string[] args)
        {
            // Step 1: Create future indicators
            Console.WriteLine("Step 1: Analyze MT5 Export & Create Future Indicators");
            ProcessIndicators ind = new ProcessIndicators();
            ind.Analyze(STEP1_FILENAME, true, CSVFormat.DECIMAL_POINT);
            int externalIndicatorCount = ind.Columns.Count - 3;
            ind.AddColumn(new BestReturn(RESULT_WINDOW,true)); 
            ind.Process(STEP2_FILENAME);          
            Console.WriteLine("External indicators found: " + externalIndicatorCount);
            //Console.ReadKey();

            // Step 2: Normalize
            Console.WriteLine("Step 2: Create Future Indicators");
            EncogNormalize norm = new EncogNormalize();
            norm.Analyze(STEP2_FILENAME, true, CSVFormat.ENGLISH);
            norm.Stats[0].Action = NormalizationDesired.PassThrough; // Date
            norm.Stats[1].Action = NormalizationDesired.PassThrough; // Time
            
            norm.Stats[2].Action = NormalizationDesired.Normalize; // Close
            norm.Stats[3].Action = NormalizationDesired.Normalize; // Stoch K
            norm.Stats[4].Action = NormalizationDesired.Normalize; // Stoch Dd
            norm.Stats[5].Action = NormalizationDesired.Normalize; // WilliamsR
       
            norm.Stats[6].Action = NormalizationDesired.Normalize; // best return [RESULT_WINDOW]

            norm.Normalize(STEP3_FILENAME);

            // neuron counts
            int inputNeurons = INPUT_WINDOW * externalIndicatorCount;
            int outputNeurons = PREDICT_WINDOW;

            // Step 3: Time-box
            Console.WriteLine("Step 3: Timebox");
            //Console.ReadKey();
            TemporalWindow window = new TemporalWindow();
            window.Analyze(STEP3_FILENAME, true, CSVFormat.ENGLISH);
            window.InputWindow = INPUT_WINDOW;
            window.PredictWindow = PREDICT_WINDOW;
            int index = 0;
            window.Fields[index++].Action = TemporalType.Ignore; // date
            window.Fields[index++].Action = TemporalType.Ignore; // time
            window.Fields[index++].Action = TemporalType.Ignore; // close
            for(int i=0;i<externalIndicatorCount;i++)
                window.Fields[index++].Action = TemporalType.Input; // external indicators
            window.Fields[index++].Action = TemporalType.Predict; // PredictBestReturn

            window.Process(STEP4_FILENAME);

            // Step 4: Train neural network
            Console.WriteLine("Step 4: Train");
            Console.ReadKey();
            INeuralDataSet training = (BasicNeuralDataSet)EncogUtility.LoadCSV2Memory(STEP4_FILENAME, inputNeurons, 
                                                                                      outputNeurons, true, CSVFormat.ENGLISH);

            BasicNetwork network = new BasicNetwork();
            network.AddLayer(new BasicLayer(new ActivationTANH(), true, inputNeurons));
            network.AddLayer(new BasicLayer(new ActivationTANH(), true, HIDDEN1_NEURONS));
            network.AddLayer(new BasicLayer(new ActivationLinear(), true, outputNeurons));
            network.Structure.FinalizeStructure();
            network.Reset();

            //EncogUtility.TrainToError(network, training, TARGET_ERROR);
            EncogUtility.TrainConsole(network, training, 3);

            // Step 5: Save neural network and stats
            EncogMemoryCollection encog = new EncogMemoryCollection();
            encog.Add("network", network);
            encog.Add("stat", norm.Stats);
            encog.Save(STEP5_FILENAME);
            Console.ReadKey();
        }
    }
}

Você pode notar que eu comentei uma linha e mudei a função de treinamento de EncogUtility.TrainToError() para EncogUtility.TrainConsole()

EncogUtility.TrainConsole(network, training, 3);

O método TrainConsole especifica um número de minutos para treinar a rede. No exemplo, eu treinei a rede por três minutos. Dependendo da complexidade da rede e o tamanho dos dados de treinamento, treinar a rede pode levar alguns minutos, horas ou até mesmo dias. Eu recomendo ler mais sobre cálculo de erros e algoritmos de treinamento no site Heaton Research ou qualquer outro livro sobre o assunto.

Os métodos EncogUtility.TrainToError() param o treinamento da rede após que um erro de rede objetivo é alcançado. Você pode comentar EncongUtiliy.TrainConsole() e excluir as barras de comentário EncogUtility.TrainToError() para treinar a rede até um erro desejado, como no exemplo original

EncogUtility.TrainToError(network, training, TARGET_ERROR);
    

Por favor, note que algumas vezes a rede não pode ser treinada para um determinado erro pois o número de neurônios pode ser muito pequeno.


8. Usando Rede Neural Treinada para Construir o Indicador Neural MetaTrader 5

A rede treinada pode ser usada por um indicador de rede neural que vai tentar prever o melhor retorno sobre o investimento.

O indicador neural ENCOG para MetaTrader 5 consiste de duas partes. Uma parte está escrita em MQL5 e basicamente tem os mesmos indicadores, como os da rede que foi treinada, e alimenta a rede com valores do indicador da janela de entrada. A segunda parte é escrita em C# e seus dados de entrada da janela temporal e a saída da rede neural retorna para MQL5. Parte do indicador C# é baseado em meu artigo anterior sobre expor código C# para MQL5.

using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
using Encog.Neural.Networks;
using Encog.Persist;
using Encog.App.Quant.Normalize;
using Encog.Neural.Data;
using Encog.Neural.Data.Basic;

namespace EncogNeuralIndicatorMT5DLL
{

    public class NeuralNET
    {
        private EncogMemoryCollection encog;
        public BasicNetwork network;
        public NormalizationStats stats;

        public NeuralNET(string nnPath)
        {
            initializeNN(nnPath);
        }

        public void initializeNN(string nnPath)
        {
            try
            {
                encog = new EncogMemoryCollection();
                encog.Load(nnPath);
                network = (BasicNetwork)encog.Find("network");
                stats = (NormalizationStats)encog.Find("stat");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
        }
    };

   class UnmanagedExports
   {

      static NeuralNET neuralnet; 

      [DllExport("initializeTrainedNN", CallingConvention = CallingConvention.StdCall)]
      static int initializeTrainedNN([MarshalAs(UnmanagedType.LPWStr)]string nnPath)
      {
          neuralnet = new NeuralNET(nnPath);

          if (neuralnet.network != null) return 0;
          else return -1;
      }

      [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
      public static int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t1,
                                           [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t2,
                                           [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t3, 
                                           int len, 
                                           [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result,
                                           int rates_total)
      {
          INeuralData input = new BasicNeuralData(3 * len);
          
          int index = 0;
          for (int i = 0; i <len; i++)
          {
              input[index++] = neuralnet.stats[3].Normalize(t1[i]);
              input[index++] = neuralnet.stats[4].Normalize(t2[i]);
              input[index++] = neuralnet.stats[5].Normalize(t3[i]);
          }

          INeuralData output = neuralnet.network.Compute(input);
          double d = output[0];
          d = neuralnet.stats[6].DeNormalize(d);        
          result[rates_total-1]=d;

          return 0;
      }  
   }
}
      

Se você gostaria de usar qualquer outro número de indicadores maiores que três, você precisa alterar o método computeNNIndicator() para atender às suas necessidades.

 [DllExport("computeNNIndicator", CallingConvention = CallingConvention.StdCall)]
      public static int computeNNIndicator([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t1,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t2,
                                         [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] t3, 
                                         int len, 
                                         [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] double[] result,
                                         int rates_total)

Neste caso, os três primeiros parâmetros de entrada são tabelas que contêm os valores de entrada do indicador, e o quarto parâmetro é o comprimento da janela de entrada.

SizeParamIndex = 3 pontos para a variável do comprimento da janela de entrada, como a conta das variáveis​de entrada é aumentada de 0 em diante. O quinto parâmetro é uma tabela que contém os resultados da rede neural.

Parte do indicador MQL5 precisa importar uma C# EncogNNTrainDLL.dll e usar funções de initializeTrainedNN() e computeNNIndicator() exportadas da DLL.

//+------------------------------------------------------------------+
//|                                         NeuralEncogIndicator.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property indicator_separate_window

#property indicator_plots 1
#property indicator_buffers 1
#property indicator_color1 Blue
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_width1  2

#import "EncogNNTrainDLL.dll"
   int initializeTrainedNN(string nnFile);
   int computeNNIndicator(double& ind1[], double& ind2[],double& ind3[], int size, double& result[], int rates);  
#import


int INPUT_WINDOW = 6;
int PREDICT_WINDOW = 1;

double ind1Arr[], ind2Arr[], ind3Arr[]; 
double neuralArr[];

int hStochastic;
int hWilliamsR;

int hNeuralMA;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0, neuralArr, INDICATOR_DATA);
   
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);

   ArrayResize(ind1Arr, INPUT_WINDOW);
   ArrayResize(ind2Arr, INPUT_WINDOW);
   ArrayResize(ind3Arr, INPUT_WINDOW);
     
   ArrayInitialize(neuralArr, 0.0);
   
   ArraySetAsSeries(ind1Arr, true);   
   ArraySetAsSeries(ind2Arr, true);  
   ArraySetAsSeries(ind3Arr, true);
  
   ArraySetAsSeries(neuralArr, true);   
               
   hStochastic = iStochastic(NULL, 0, 8, 5, 5, MODE_EMA, STO_LOWHIGH);
   hWilliamsR = iWPR(NULL, 0, 21);
 
   Print(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
   initializeTrainedNN(TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\Files\step5_network.eg");
      
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   int calc_limit;
   
   if(prev_calculated==0) // First execution of the OnCalculate() function after the indicator start
        calc_limit=rates_total-34; 
   else calc_limit=rates_total-prev_calculated;
    
   ArrayResize(neuralArr, rates_total);
  
   for (int i=0; i<calc_limit; i++)     
   {
      CopyBuffer(hStochastic, 0, i, INPUT_WINDOW, ind1Arr);
      CopyBuffer(hStochastic, 1, i, INPUT_WINDOW, ind2Arr);
      CopyBuffer(hWilliamsR,  0, i, INPUT_WINDOW, ind3Arr);    
      
      computeNNIndicator(ind1Arr, ind2Arr, ind3Arr, INPUT_WINDOW, neuralArr, rates_total-i); 
   }
     
  //Print("neuralArr[0] = " + neuralArr[0]);
  
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
        

Por favor, veja a saída do indicador treinada em dados diários USDCHF e indicadores estocásticos e Williams %R:

Figura 7. Indicador Encog Neural

Figura 7. Indicador Encog Neural

O indicador mostra o melhor retorno previsto sobre o investimento na próxima barra.

Você deve ter notado que eu mudei a barra do indicador no futuro:

PlotIndexSetInteger(0, PLOT_SHIFT, 1);

Isto é para indicar que o indicador é um preditivo. Desde que construímos um indicador neural, estamos prontos para construir um Expert Advisor com base no indicador.


9. Expert Advisor Basedo em um Indicador Neural

O Expert Advisor recebe a saída do indicador neural e decide se quer comprar ou vender uma garantia. Minha primeira impressão foi que ele deve comprar sempre que o indicador estiver acima de zero e vender quando estiver abaixo de zero, ou seja, comprar quando a previsão de melhor retorno em um determinado intervalo de tempo é positiva e vender quando a previsão de melhor retorno é negativa.

Depois de alguns testes iniciais descobriu-se que o desempenho poderia ser melhor, então eu apresentei "forte tendência de alta" e as variáveis ​​"forte tendência de baixa", o que significa que não há nenhuma razão para sair do comércio, quando estamos em uma tendência forte de acordo com a tendência famoso 'você é regra' amigo.

Além disso, fui aconselhado no fórum Research Heaton a usar ATR para mover perda de paradas, então eu usei o indicador Chandelier ATR eu encontrei no fórum MQL5. Ele efetivamente aumentou a conquista do patrimônio enquanto fazia o backtesting. Estou colando o código fonte do Expert Advisor abaixo.

//+------------------------------------------------------------------+
//|                                           NeuralEncogAdvisor.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

double neuralArr[];

double trend;
double Lots=0.3;

int INPUT_WINDOW=8;

int hNeural,hChandelier;

//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArrayResize(neuralArr,INPUT_WINDOW);
   ArraySetAsSeries(neuralArr,true);
   ArrayInitialize(neuralArr,0.0);

   hNeural=iCustom(Symbol(),Period(),"NeuralEncogIndicator");
   Print("hNeural = ",hNeural,"  error = ",GetLastError());

   if(hNeural<0)
     {
      Print("The creation of ENCOG indicator has failed: Runtime error =",GetLastError());
      //--- forced program termination
      return(-1);
     }
   else  Print("ENCOG indicator initialized");

   hChandelier=iCustom(Symbol(),Period(),"Chandelier");
   Print("hChandelier = ",hChandelier,"  error = ",GetLastError());

   if(hChandelier<0)
     {
      Print("The creation of Chandelier indicator has failed: Runtime error =",GetLastError());
      //--- forced program termination
      return(-1);
     }
   else  Print("Chandelier indicator initialized");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   long tickCnt[1];
   int ticks=CopyTickVolume(Symbol(),0,0,1,tickCnt);
   if(tickCnt[0]==1)
     {
      if(!CopyBuffer(hNeural,0,0,INPUT_WINDOW,neuralArr)) { Print("Copy1 error"); return; }

      // Print("neuralArr[0] = "+neuralArr[0]+"neuralArr[1] = "+neuralArr[1]+"neuralArr[2] = "+neuralArr[2]);
      trend=0;

      if(neuralArr[0]<0 && neuralArr[1]>0) trend=-1;
      if(neuralArr[0]>0 && neuralArr[1]<0) trend=1;

      Trade();
     }
  }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---

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

void Trade()
  {
   double bufChandelierUP[2];
   double bufChandelierDN[2];

   double bufMA[2];

   ArraySetAsSeries(bufChandelierUP,true);
   ArraySetAsSeries(bufChandelierUP,true);

   ArraySetAsSeries(bufMA,true);

   CopyBuffer(hChandelier,0,0,2,bufChandelierUP);
   CopyBuffer(hChandelier,1,0,2,bufChandelierDN);

   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,3,rates);

   bool strong_uptrend=neuralArr[0]>0 && neuralArr[1]>0 && neuralArr[2]>0 &&
                      neuralArr[3]>0 && neuralArr[4]>0 && neuralArr[5]>0 &&
                       neuralArr[6]>0 && neuralArr[7]>0;
   bool strong_downtrend=neuralArr[0]<0 && neuralArr[1]<0 && neuralArr[2]<0 &&
                        neuralArr[3]<0 && neuralArr[4]<0 && neuralArr[5]<0 &&
                        neuralArr[6]<0 && neuralArr[7]<0;

   if(PositionSelect(_Symbol))
     {
      long type=PositionGetInteger(POSITION_TYPE);
      bool close=false;

      if((type==POSITION_TYPE_BUY) && (trend==-1))

         if(!(strong_uptrend) || (bufChandelierUP[0]==EMPTY_VALUE)) close=true;
      if((type==POSITION_TYPE_SELL) && (trend==1))
         if(!(strong_downtrend) || (bufChandelierDN[0]==EMPTY_VALUE))
            close=true;
      if(close)
        {
         CTrade trade;
         trade.PositionClose(_Symbol);
        }
      else // adjust s/l
        {
         CTrade trade;

         if(copied>0)
           {
            if(type==POSITION_TYPE_BUY)
              {
               if(bufChandelierUP[0]!=EMPTY_VALUE)
                  trade.PositionModify(Symbol(),bufChandelierUP[0],0.0);
              }
            if(type==POSITION_TYPE_SELL)
              {
               if(bufChandelierDN[0]!=EMPTY_VALUE)
                  trade.PositionModify(Symbol(),bufChandelierDN[0],0.0);
              }
           }
        }
     }

   if((trend!=0) && (!PositionSelect(_Symbol)))
     {
      CTrade trade;
      MqlTick tick;
      MqlRates rates[];
      ArraySetAsSeries(rates,true);
      int copied=CopyRates(Symbol(),PERIOD_CURRENT,0,INPUT_WINDOW,rates);

      if(copied>0)
        {
         if(SymbolInfoTick(_Symbol,tick)==true)
           {
            if(trend>0)
              {
               trade.Buy(Lots,_Symbol,tick.ask);
               Print("Buy at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]);
              }
            if(trend<0)
              {
               trade.Sell(Lots,_Symbol,tick.bid);
               Print("Sell at "+tick.ask+" trend = "+trend+" neuralArr = "+neuralArr[0]);
              }
           }
        }
     }

  }
//+------------------------------------------------------------------+
        

O Expert Advisor foi executado em dado D1 monetário USDCHF. Cerca de 50% dos dados estavam fora da amostra do treinamento.


10. Resultados do Backtesting do Expert Advisor

Eu estou colando os resultados do backtesting abaixo. O backtest foi executado a partir de 2000.01.01 a 2011.03.26.

Figura 8. Resultados do backtesting do Expert Advisor Neural

Figura 8. Resultados do backtesting do Expert Advisor Neural

Figura 9. Gráfico de backtesting do Patrimônio/Balanço do Expert Advisor Neural

Figura 9. Gráfico de backtesting do Patrimônio/Balanço do Expert Advisor Neural

Por favor, observe que este desempenho pode ser totalmente diferente para outro período de tempo e outros valores mobiliários.

Por favor, trate esta EA como uma forma de educação e a torne um ponto de partida para futuras pesquisas. Minha opinião pessoal é de que a rede poderia ser retreinada a cada certo período de tempo para torná-la mais robusta, talvez alguém irá ou já encontrou uma boa maneira de conseguir isso. Talvez haja uma maneira melhor de fazer previsões de Compra/Venda com base em um indicador neural. Eu encorajo os leitores experimentar.


Conclusão

No artigo subsequente, apresentei uma maneira de construir um indicador preditivo neural e um expert advisor baseado nesse indicador, com a ajuda do quadro de aprendizagem de máquina ENCOG. Todos os códigos fonte, binários compilados, DLLs e uma rede treinada exemplar estão ligados ao artigo.


Por causa da "dupla DLL envolvida em. NET", os arquivos Cloo.dll, encog-core-cs.dll e log4net.dll devem estar localizados na pasta do terminal do cliente.
O arquivo EncogNNTrainDLL.dll deve estar localizado em \Terminal Data folder\MQL5\Libraries\ folder.