Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 08): Perceptrons
Introdução
A classe de sinais do Assistente MQL5 contém muitos exemplos na pasta Include\Expert\Signal. Cada um deles pode ser usado independentemente ou combinado com outros ao criar um EA no Assistente. Neste artigo, vamos criar e usar um desses arquivos no EA. Essa abordagem, além de minimizar os esforços de pré-codificação, permite testar mais de um sinal em um EA, atribuindo um peso a cada sinal usado.
As classes de perceptron Alglib são apresentadas como interfaces de rede extensas e inter-relacionadas no arquivo Include\Math\Alglib\dataanalysis.mqh. À primeira vista, pode ser fácil se perder, mas vamos discutir algumas classes importantes que esperamos que facilitem a orientação dentro dessa área.
O objetivo principal de usar essas classes Alglib para desenvolver um EA é o mesmo que usar o Assistente MQL5, que é testar ideias. Como posso determinar brevemente se a ideia x ou o conjunto de dados de entrada y valem meus esforços para o desenvolvimento adicional do sistema de trading? Este artigo pode ajudar a responder a essa pergunta.
Antes de começarmos, talvez fosse útil explicar mais amplamente por que os perceptrons e, talvez, as redes neurais em geral estão ganhando popularidade. Se nos concentrarmos em finanças e mercados, veremos que existem muitos desafios na previsão das ações do mercado, relacionados às limitações da análise tradicional.
Esses problemas existem porque os mercados são sistemas muito complexos e frequentemente dinâmicos, influenciados por algo mais do que o que aparece nas notícias (informações públicas). As relações entre as diferentes variáveis de mercado na maioria das vezes não são lineares e são muito variáveis. Métodos tradicionais de análise, baseados na linearidade, podem ser incapazes de capturar e considerar efetivamente essas complexidades. Exemplos desses métodos tradicionais podem incluir abordagens como correlação, PCA ou regressão linear. Essas são todas táticas muito bem pensadas, mas cada vez mais se mostram obsoletas. Além disso, os dados de mercado são ruidosos, pois os movimentos de mercado são influenciados não apenas pelos sentimentos dos investidores, mas também pelos vieses comportamentais dos consumidores. Assim, a análise técnica tradicional se torna limitada porque se baseia em dados históricos sem considerar devidamente toda a dinâmica do mercado. Por sua vez, a análise fundamental, que leva em conta a perspectiva de longo prazo, é propensa ao risco de curto prazo, especialmente no que diz respeito ao movimento dos preços. Embora a alavancagem normalmente não seja utilizada por aqueles que confiam na análise fundamental, a maioria concorda que ela é um componente importante para nivelar os ativos sob gestão e, portanto, o risco a longo prazo. No entanto, a alavancagem não pode ser usada sem considerar o movimento de preços de curto prazo.
Novas alternativas a essas duas abordagens tradicionais incluem a economia comportamental e métodos de inteligência artificial baseados em redes neurais. Vamos nos concentrar nesta última opção.
Recentemente, houve uma revolução nos mercados financeiros devido à introdução de tecnologias de inteligência artificial, como o lançamento do ChatGPT. Por exemplo, várias grandes empresas entraram na competição. A este respeito, podemos mencionar o BloombergGPT e o EinsteinGPT da Salesforce. No entanto, neste artigo, não falaremos sobre GPT, mas sim sobre sua versão muito simplificada: os perceptrons.
O crescente interesse nos métodos de inteligência artificial para previsão é parcialmente explicado pelos extensos dados financeiros que agora são coletados e armazenados em volumes crescentes. Lembra-se dos tempos em que os analistas técnicos só se preocupavam com o preço de fechamento diário dos títulos? Hoje, todos estão cientes dos preços OHLC de barras de um minuto, sem falar nas frequências dos ticks, que variam de corretora para corretora.
Essa sobrecarga de dados ocorre simultaneamente com o aumento da capacidade computacional, graças à saudável concorrência entre os fornecedores de chips. Ontem foi anunciado que a NVIDIA em breve se tornará o maior fornecedor de chips do mundo, em grande parte devido à crescente demanda por GPUs, que estão na moda com a disseminação dos GPT. Assim, o aumento da capacidade de armazenamento de dados e o crescimento das capacidades computacionais levam a uma negociação mais algorítmica. E, embora o trading algorítmico possa ser realizado com análise técnica e fundamental tradicional, os métodos de inteligência artificial que usam redes neurais estão atraindo cada vez mais atenção.
As redes neurais geralmente lidam melhor com grandes volumes de dados e identificação de padrões complexos e não lineares. Elas conseguem isso adaptando-se ao ambiente em constante mudança por meio de aprendizado profundo - uma rede de múltiplos níveis, onde camadas ocultas específicas se especializam em tarefas particulares, tornando-as ideais para previsões em condições tipicamente turbulentas/variáveis. Além das finanças, elas podem analisar dados não estruturados, como artigos de notícias ou postagens em redes sociais, avaliar sentimentos do mercado, eficácia de ensaios clínicos de medicamentos, e serem aplicadas em muitas outras áreas.
Visão geral das classes de perceptron Alglib
Como mencionado anteriormente, a hierarquia de classes de perceptron Alglib é uma extensa biblioteca de classes que implementam redes neurais, desde simples perceptrons, que estamos discutindo neste artigo, até conjuntos, que, sendo sinônimos de transformadores, são pilhas de redes neurais. Como estamos discutindo apenas a rede neural mais simples, chamada perceptron, vamos considerar apenas as classes CMLPBase, CMLPTrain, CMLPTrainer e CMultilayerPerceptron. Também usaremos outras classes auxiliares secundárias, como a classe que lida com relatórios ou a classe que ajuda a normalizar conjuntos de dados, mas destacaremos as principais.
A classe CMLPBase é usada para inicializar a rede, definindo o número de camadas ocultas que a rede terá, bem como o número de neurônios em cada camada. A classe CMLPTrain inicializa a classe do treinador, estabelecendo o número de entradas que a rede aceitará, bem como o número de suas saídas. Além disso, ele preenche o treinador com um conjunto de dados de treinamento que deve estar em forma matricial, onde as primeiras colunas contêm variáveis independentes e a última coluna contém o regressor ou classificador, dependendo do tipo de rede usada. No nosso caso, será um classificador, já que os perceptrons geralmente produzem um resultado lógico. A classe CMLPTrainer é usada no treinamento ao chamar a função MLPTrainNetwork da classe CMLPTrain. Existem métodos de treinamento alternativos muito interessantes, como a agregação de inicialização (boot-strap-aggregating), invocada pela função MLPEBaggedLM, mas eles só podem ser usados com conjuntos (pilhas de redes). Além disso, algoritmos como parada precoce, LBFGS e Levenberg-Marquardt podem ser usados para treinar a rede.
Os métodos usados por essas classes abrangem a típica rota das redes neurais, desde o carregamento dos dados de treinamento até a realização do treinamento efetivo e, finalmente, a transição para o conjunto de dados atual para previsão.
Assim, as classes são escritas da maneira que a rede neural opera. Durante a operação, os dados de entrada são passados pela rede, começando com o primeiro nível, que nessas classes é chamado de camada de entrada, em seguida, para as camadas ocultas e, finalmente, para o nível de saída. Além disso, a ativação dos valores geralmente ocorre em cada neurônio, e é essa ativação que permite que as redes processem relações complexas, que vão além das lineares, agindo como um filtro que permite que valores selecionados passem para o próximo nível. Esse processo é iterativo, mas relativamente simples, pois quase sempre representa um caso de multiplicação e adição, onde o resultado da camada de saída é influenciado principalmente pelos pesos e desvios em cada nível. Por isso, são esses pesos e desvios que compõem a essência das redes neurais, e o processo de ajustá-los não só requer recursos computacionais significativos, mas também levou ao desenvolvimento de várias abordagens, uma vez que não é tão simples quanto uma propagação. Este método funciona melhor em vários tipos de redes, já que as redes neurais possuem múltiplas aplicações.
Assim, a função de propagação direta para redes em AlgLib é chamada de MLPProcess. Ela possui variações, mas o princípio de operação é o mesmo: aceita dados de entrada em um vetor ou matriz e fornece os valores da camada de saída, geralmente também em um vetor ou array. Existem redes com um único neurônio na camada de saída, e nesses casos ocorre uma sobrecarga dessa função, que retorna um único valor, não um array.
É importante notar que, mesmo codificando e usando um perceptron de camada oculta, nossa classe padrão é chamada de perceptron multicamadas, pois é escalável, já que o número de camadas ocultas para qualquer rede inicializada pode ser definido durante a execução, variando de 0 a 2.
Se tentarmos aproximar um pouco o trabalho da propagação típica, podemos olhar para a função MLPInternalProcessVector. Uma das primeiras ações para essa função é a normalização da linha de dados de entrada, para que todos os valores desse array de entrada estejam mais interrelacionados.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints, const int nvars,int &info,CRowDouble &means, CRowDouble &sigmas) { //--- function call DSNormalizeC(xy,npoints,nvars,info,means,sigmas); //--- calculation for(int j=0; j<nvars; j++) { //--- change values for(int i=0; i<npoints; i++) xy.Set(i,j,(xy.Get(i,j)-means[j])/sigmas[j]); } }
Para isso, é necessário usar as médias e o desvio padrão (sigmas) de cada coluna do vetor de entrada para obter valores no intervalo de 0 a 1. Portanto, as médias e sigmas devem ser determinados manualmente a partir de conjuntos de dados de treino e então atribuídos à rede. Já existem funções que podem realizar tais cálculos no mesmo arquivo Alglib DSNormalize, conforme mostrado abaixo:
//+------------------------------------------------------------------+ //| Normalize | //+------------------------------------------------------------------+ void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints, const int nvars,int &info,double &means[], double &sigmas[]) { CRowDouble Means,Sigmas; DSNormalize(xy,npoints,nvars,info,Means,Sigmas); Means.ToArray(means); Sigmas.ToArray(sigmas); }
Também vale a pena mencionar o array m_structinfo, que é usado para armazenar informações chave sobre a rede, como o número total de neurônios, o tipo de ativação usada, o número total de pesos, o número de neurônios na camada de entrada e o número de neurônios na camada de saída.
Após a normalização, os dados são passados através da rede, e cada neurônio em cada nível pode ter sua própria função de ativação. Essa configuração pode ser definida com a função MLPSetNeuronInfo, que pode ser facilmente usada como uma vantagem na construção da rede.
A entrada direta de um perceptron é relativamente simples em comparação com o treinamento e o ajuste dos pesos da rede. O Alglib oferece principalmente dois métodos de treinamento, o algoritmo de Levenberg-Marquardt e o LBFGS.
Na busca por uma solução não linear pelo método dos mínimos quadrados, o algoritmo de Levenberg-Marquardt combina a rapidez do algoritmo de Gauss-Newton e a flexibilidade do algoritmo de descida de coordenadas em pontos de alta curvatura da solução. Ele utiliza a matriz de Hess para registrar a curvatura da superfície como uma estimativa de proximidade à solução. Suas aplicações são principalmente em redes neurais, onde é eficaz no tratamento de superfícies de erro não convexas, especialmente em situações onde estão envolvidos pequenos conjuntos de dados com uma arquitetura de rede relativamente simples, pois o cálculo da matriz de Hess é bastante trabalhoso.
O LBFGS (algoritmo de Broyden-Fletcher-Goldfarb-Shanno com memória limitada), em vez de calcular a matriz de Hess, aproxima-a utilizando memória limitada, registrando as mais recentes atualizações de peso da rede, o que o torna geralmente muito eficiente em cálculos e gerenciamento de memória. Por isso, ele é mais adequado para situações com grandes conjuntos de dados e arquiteturas de rede relativamente complexas.
A comparação das propriedades de convergência desses dois métodos sugere uma preferência pelo algoritmo de Levenberg-Marquardt, já que ele pode encontrar rapidamente a solução exata mesmo em situações onde a suposição inicial estava longe da verdade (por exemplo, quando a rede é inicializada com pesos aleatórios). Além disso, ao contrário da descida de coordenadas, ele é menos propenso a ficar preso em mínimos locais. Isso o torna um pouco mais confiável, em parte devido ao uso do coeficiente lambda de amortecimento. Por outro lado, o LBFGS é mais influenciado pela suposição inicial (neste caso, os pesos iniciais da rede) e tende a convergir mais lentamente ou ficar preso em mínimos locais.
Instância da classe de sinais do Expert Advisor
Mais informações adicionais sobre perceptrons podem ser encontradas aqui. Vamos considerar a implementação de uma instância no código. A criação de um Expert Advisor usando o Assistente MQL5 requer conhecimento de três classes típicas que definem o Expert Advisor baseado no Assistente, nomeadamente a classe de sinal, que é o foco deste artigo, a classe de trailing, que define como os stop-losses são configurados para posições abertas, e a classe de gestão de capital, que ajuda a definir os tamanhos dos lotes de negociação. Isso já foi discutido em artigos anteriores. Todas as três classes precisam ser definidas e selecionadas no Assistente durante a compilação. Embora a classe de gestão de capital ofereça um volume de negociação otimizado consoante o tamanho, poderíamos usar uma quarta classe do Assistente, que considera o risco - quão seguro é para o Expert Advisor colocar vários pedidos dentro de uma posição, com base no histórico de negociação ou dados de algum indicador, mas isso não é o foco deste artigo.
Para implementar uma instância de classes de perceptrons Alglib como um perceptron de camada única, devemos começar declarando instâncias de nossas classes-chave na interface de nossa classe de sinais personalizada do Expert Advisor. Nos arquivos de classes de sinais, sempre há funções LongCondition e ShortCondition. Outro método importante que precisaremos, além das funções de inicialização e verificação, é uma função para calcular ou processar o sinal do perceptron.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CSignalPerceptron : public CExpertSignal { protected: int m_hidden; // int m_features; // int m_hidden_1_size; // int m_hidden_2_size; // int m_training_points; // int m_training_restarts; // int m_activation_type; // double m_hidden_1_bias; // double m_hidden_2_bias; // double m_output_bias; // public: ... protected: CBdSS m_norm; CMLPBase m_base; CMLPTrain m_train; CMatrixDouble m_xy; CMLPReport m_report; CMLPTrainer m_trainer; CMultilayerPerceptron m_network; bool m_in_training; int m_inputs; int m_outputs; double m_last_long_y; double m_last_short_y; bool ReadWeights(); bool WriteWeights(CRowDouble &Export); void Process(double &Y); };
A função de verificação de facto serve como nossa função de inicialização nesta instância da classe de sinais do Expert Advisor. Temos uma função de inicialização embutida, mas é melhor recorrer à validação. Primeiramente, definimos o número de entradas e saídas para nosso perceptron. O número de entradas pode ser otimizado, por isso é lido a partir do parâmetro, mas o número de saídas, como se trata de uma classificação e não de uma regressão, deve ser de pelo menos 2.
Por questões de simplicidade, definiremos o número de saídas como 2. Em seguida, ajustamos o tamanho da matriz de dados de treinamento para que tenha linhas correspondentes ao número de pontos de treinamento que consideraremos em cada coluna ao processar a direção. Suas colunas devem corresponder à soma do número de entradas e saídas. As saídas, sendo 2, representam o treinamento de dois pesos para as direções de alta e de baixa, e o passo para frente efetivamente retornará duas probabilidades: uma para a posição longa e outra para a curta, ambas somando um total de um. Depois disso, criamos o treinador, definindo o número de entradas e saídas para ele.
m_train.MLPCreateTrainerCls(m_inputs,m_outputs,m_trainer);
A seguir, vem a criação da rede, e dependendo do número de camadas ocultas selecionadas, usaremos diferentes funções para isso, permitindo definir o número de neurônios de entrada, o número de neurônios em cada camada oculta (se usadas) e, finalmente, o número de neurônios na camada de saída.
if(m_hidden==0) { m_base.MLPCreateC0(m_inputs,m_outputs,m_network); } else if(m_hidden==1) { m_base.MLPCreateC1(m_inputs,m_hidden_1_size,m_outputs,m_network); } else if(m_hidden==2) { m_base.MLPCreateC2(m_inputs,m_hidden_1_size,m_hidden_2_size,m_outputs,m_network); } else if(m_hidden>2||m_hidden<0) { printf(__FUNCSIG__+" invalid number of hidden layers should be 0, 1, or 2. "); return(false); }
Finalmente, configuramos as funções de ativação das camadas oculta e de saída, assim como os vieses das camadas. As classes Alglib são suficientemente versáteis, de modo que as funções de ativação e os vieses podem ser configurados não apenas para cada camada, mas também para cada neurônio. No entanto, neste artigo, consideraremos algo simplificado.
Além da inicialização e verificação de nossa rede, precisamos ter os meios necessários para aprender os pesos ideais da rede através de um sistema de armazenamento e leitura deles, quando necessário. Aqui, vários métodos diferentes podem ser considerados, mas usaremos simplesmente a gravação do arquivo no array de pesos da rede após passar o teste, quando o critério de teste do Expert Advisor superar o teste anterior. Na próxima execução, nossa rede é inicializada lendo esses pesos, e com cada treinamento subsequente, eles são melhorados. A gravação e leitura dos pesos são realizadas pelas funções WriteWeights e ReadWeights, respectivamente.
Por fim, a função Process é executada em cada nova barra para treinar nossa rede com novos dados e, em seguida, processar o sinal atual, que é chamado de variável Y. Aqui, algumas coisas devem ser observadas: em primeiro lugar, a matriz de dados de teste m_xy precisa ser normalizada por colunas de modo que cada valor na matriz esteja no intervalo de -1,0 a +1,0. Como mencionado anteriormente, isso pode ser feito com outras funções nas classes Alglib, e elas estão localizadas no mesmo arquivo que as classes de perceptron. Certamente, esse método pode ser ajustado para torná-lo mais adequado para uma situação específica, mas para nossos propósitos, serão usadas as funções integradas.
//normalise data
CRowDouble _means,_sigmas;
m_norm.DSNormalize(m_xy,m_training_points,m_inputs,_info,_means,_sigmas);
Em segundo lugar, o treinamento da rede é realizado por duas funções, dependendo de se estamos começando o processo de treinamento ou já executamos uma rodada de treinamento. Assim que começarmos o treinamento, podemos salvar os pesos obtidos na passagem anterior e continuar treinando com eles, para não termos que lidar constantemente com pesos aleatórios. A função de treinamento padrão sempre randomiza os pesos, e se a usássemos, randomizaríamos nossos pesos a cada nova barra!
m_train.MLPSetDataset(m_trainer,m_xy,m_training_points); // if(!m_in_training) { m_train.MLPStartTraining(m_trainer,m_network,false); m_in_training=true; } else if(m_in_training) { while(m_train.MLPContinueTraining(m_trainer,m_network)) { // } }
Graças ao assistente, a integração dessa classe de sinais completa com a classe de trailing e a classe de gestão de capital em um EA é feita sem problemas.
<imagens: 7 passos de compilação no Assistente/>
Se executarmos os 7 passos, como mostrado nas 7 imagens acima, devemos obter algo como o EA indicado abaixo:
//+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> //--- available signals #include <Expert\Signal\My\SignalPerceptron.mqh> //--- available trailing #include <Expert\Trailing\TrailingNone.mqh> //--- available money management #include <Expert\Money\MoneyFixedMargin.mqh>
Compilação e teste do EA
Montaremos o EA com base na nossa classe de sinais personalizada usando o Assistente. Aqui tudo é bastante simples (veja as capturas de tela).
Se testarmos nosso EA compilado antes de qualquer otimização, obteremos o seguinte relatório:
Se realizarmos a otimização do nosso EA com uma janela de forward test, obteremos os seguintes resultados:
Treinamos nosso perceptron e exportamos seus pesos com base nos critérios de otimização do nosso EA. Uma maneira mais breve de fazer isso é usar os recursos embutidos de validação cruzada ou até algo mais simples, como o valor do erro quadrático médio do relatório, se o bagging não for utilizado. Em qualquer cenário, mantemos os pesos que provavelmente serão adequados aos classificadores de treinamento. A julgar pelos nossos testes, a rede mostra resultados promissores, mas, como sempre, deve-se lembrar de testar com mais rigor em períodos mais longos usando os dados tick da sua corretora.
Considerações finais
Examinamos como os perceptrons podem ser implementados com uma quantidade mínima de código graças às classes de código do Alglib. Destacamos algumas etapas preliminares, como a normalização do conjunto de dados, que devem ser realizadas antes que os perceptrons estejam prontos para serem testados e explorados. Além disso, mostramos medidas adicionais a serem consideradas quando você tem perceptrons prontos para serem testados. Todos esses passos e medidas adicionais, como a exportação de parâmetros personalizáveis, são realizados com a ajuda do código auxiliar das classes do Alglib.
Desse modo, as vantagens de usar as classes do Alglib residem, principalmente, na minimização da quantidade de código e do tempo necessário para criar um sistema testável. No entanto, há desvantagens, especialmente quando se trata de configurações. Por exemplo, nossos perceptrons não podem ter mais de duas camadas ocultas. Em cenários onde conjuntos de dados complexos são modelados, isso pode se tornar um gargalo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13832
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso