
Técnicas do MQL5 Wizard que você deve conhecer (Parte 34): Embedding de Preços com um RBM Não Convencional
Introdução
Continuamos essas séries que exploram diversos setups de trade e ideias, graças ao rápido desenvolvimento e ambiente de prototipagem do MetaTrader-5 com o MQL5 wizard. Esses artigos, em princípio, buscam explorar como os traders podem se destacar da concorrência, explorando ideias que podem não ser tão comuns e que poderiam oferecer uma vantagem ao trader interessado, dependendo de como ele escolhe explorá-las. Portanto, estamos explorando aqui, não necessariamente explorando, e a razão pela qual uma vantagem é muito importante é que muitas ideias de trade que estão disponíveis tendem a se correlacionar de forma muito positiva entre si.
Isso é ótimo quando as tendências são altistas e todos estão no lucro, no entanto, como muitos concordariam, a diversificação é o que mitigaria as perdas quando as tendências se invertem e, no entanto, simplesmente encontrar valores inversamente correlacionados é muito mais difícil do que parece no papel. É por isso que as entradas e saídas de trades que são específicas para um trader podem ser um refúgio melhor do que simplesmente confiar em setups comumente usados. Com isso, este artigo examina as Máquinas de Boltzmann Restritas (RBMs) quando implementadas com Backpropagation, ao invés de suas implementações tradicionais com Gibbs Sampling e Divergência Contrastiva.
Argumenta-se que a razão pela qual esses métodos foram usados inicialmente foi porque, na metade da década de 80 (cerca de 1986, quando os RBMs foram introduzidos sob o nome de Harmonium), os custos computacionais de implementar o backpropagation nas Máquinas de Boltzmann simplesmente não eram viáveis. As Máquinas de Boltzmann, das quais os RBMs derivam o nome, são ainda mais complexas, pois não usam um grafo bipartido como os RBMs, mas, em vez disso, têm conexões intra-neurônio dentro de uma camada. Essas complexidades de conexão são o que fez até uma implementação mais simples das Máquinas de Boltzmann, como os RBMs, depender de modelos baseados em energia Energy Based Models (EBMs) para o ajuste fino dos parâmetros (pesos e vieses) da rede, ao invés de ter que processar ou lidar com cada parâmetro individualmente como é prática em outras redes neurais e no Backpropagation.
Conforme o Deep-AI:
Divergência Contrastiva aborda o desafio computacional apresentado pela função de partição nos modelos baseados em energia. A chave da Divergência Contrastiva é que é possível treinar esses modelos sem ter que calcular a função de partição completa. Em vez disso, o CD foca em ajustar os parâmetros do modelo de forma que a probabilidade dos dados observados aumente, enquanto a probabilidade das amostras geradas pelo modelo diminua.
Para alcançar isso, a Divergência Contrastiva executa um procedimento de Gibbs sampling começando pelos dados de treinamento para produzir amostras que o modelo acredita serem prováveis. Em seguida, usa essas amostras para estimar o gradiente do logaritmo da verossimilhança dos dados de treinamento em relação aos parâmetros do modelo. Esse gradiente é utilizado para atualizar os parâmetros em uma direção que melhore a representação do modelo dos dados.
Portanto, para evitar cálculos para cada probabilidade, Gibbs sampling e divergência contrastiva foram uma bênção que claramente fez sentido. Avançando para hoje, e quando apresentado com os mesmos problemas, Backpropagation certamente pode ser uma opção viável, especialmente quando se considera que a maior parte da carga de trabalho de IA não é mais suportada por CPUs, mas por GPUs, que estão assumindo essas tarefas a uma velocidade acelerada. Essa escolha de hardware ao lidar com redes semelhantes às Máquinas de Boltzmann (como o RBM) é fundamental porque, embora tenham apenas 2 camadas, essas camadas tendem a ser muito profundas e, portanto, como os pesos de cada neurônio são ajustados precisa ser devidamente considerado.
Então, o que realmente são os RBMs, dado o limite de 2 camadas? A resposta curta é um classificador, que reduz as dimensões de seus dados de entrada para revelar propriedades ocultas dos dados em menos dimensões do que a entrada. Essa é uma definição excessivamente simplificada, já que uma definição mais diligente mencionaria que são redes neurais estocásticas generativas treinadas em configurações não supervisionadas para aprender a distribuição de probabilidade de seus dados de entrada. Os resultados dos dados de entrada, que são registrados na camada oculta da rede, podem então ser usados em classificação, agrupamento ou como entrada para outra rede.
Para este artigo, estamos explorando o último, tomando os valores da camada oculta dos nossos RBMs como entrada para um Multi-Layer-Perceptron. A estrutura geral do artigo seguirá, em princípio, o formato que temos nos acostumado ao longo dessas séries.
Price-Embedding
Price-Embedding é usado no contexto deste artigo como um processo muito semelhante ao word embedding; e isso, como alguns leitores podem saber, é a etapa pré-requisita para redes transformer de grandes modelos de linguagem. O word embedding, que pode ser definido como a numeração das palavras, quando emparelhado com self-attention, ajuda a converter grande parte do material escrito disponível online em um formato que as redes neurais podem entender. Estamos de forma similar pegando uma folha dessa abordagem, presumindo que, por padrão, os dados de preços de segurança (mesmo sendo numéricos) não podem ser facilmente ‘entendidos’ pelas redes neurais diretamente. E nossa abordagem para tornar isso mais compreensível é usando um RBM treinado com backpropagation.
Agora, a conversão de palavras em números não é simplesmente sobre atribuir um número a uma palavra ou letra, mas sim um processo intricado que envolve self-attention, como já mencionado acima. Paralelos disso, acredito, podem ser traçados para os RBMs quando se considera seu design de grafo bipartido.
Embora não haja conexões diretas de neurônio para neurônio dentro de uma camada de um RBM, essas conexões, que podem ser chave para capturar o componente de self-attention de qualquer dado de entrada, são feitas através da camada oculta. Com essa tese, a camada oculta não apenas registra o que cada neurônio poderia ser redesenhado, mas também qual a significância de suas relações com os outros neurônios.
Como sempre, no que diz respeito aos traders, a prova está no resultado e, portanto, os benefícios desse price-embedding só podem ser comprovados pelos resultados do trading. E vamos chegar à primeira parte desse processo, no entanto, pode ser interessante destacar que a escala de recompensas que obtemos do embedding de palavra para número não pode ser comparada com as que estamos olhando no embedding de número para número, isso porque o que estamos fazendo aqui não é nem de perto tão transformacional. Com isso, vamos agora considerar como reconstruímos um RBM com backpropagation.
Máquinas de Boltzmann Restritas (RBMs)
Os RBMs operam em dois ciclos, frequentemente chamados de fase positiva e fase negativa. Como já mencionado, eles são frequentemente não supervisionados (embora algumas versões supervisionadas tenham sido implementadas), e isso significa que, ao começarmos e passarmos pelo processo de treinamento, não sabemos quais propriedades ocultas de cada ponto de dados testado deveriam ser. Os dados de treinamento não têm rótulos (ou valores de alvo).
A maneira como os RBMs mantêm a pontuação, portanto, é reconstruindo os valores da camada oculta para coincidir com os valores da camada de entrada na fase negativa. Portanto, existem 2 fases, a fase positiva, que é semelhante à propagação direta de um MLP regular, fornece valores para a camada oculta. Esses valores da camada oculta são o nosso objetivo, pois são uma representação com dimensionalidade reduzida dos dados de entrada que capturam as propriedades que buscamos.
Ao propagar-se em direção à camada oculta (a fase positiva), uma matriz de pesos é usada, como ocorre nas redes neurais; o que torna os RBMs especiais, no entanto, é que, na fase negativa, os mesmos pesos são usados para reconstruir os dados de entrada. Essa reconstrução, como mencionado, é como o RBM treina de forma não supervisionada ou mantém a pontuação, e como estamos implementando o Backpropagation, os dados de entrada servem como o rótulo de fato (ou alvo) para si mesmos.
Eu mantenho que isso ainda é não supervisionado porque nenhum dado adicional fora dos dados de entrada de treinamento é necessário para treinar esse RBM. Em casos onde o treinamento supervisionado foi realizado com RBMs, rótulos dos valores ideais da camada oculta foram usados, e isso não é o que estamos fazendo aqui. Portanto, para começar, precisaríamos reconstruir as funções típicas dos RBMs de fase positiva e fase negativa. Isso é o seguinte:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void C_u_rbm::GetPositive(void) { vector _positive = weights[0].MatMul(inputs), _output; _positive += biases[0]; _positive.Activation(_output, THIS.activation); output = _output; }
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void C_u_rbm::GetNegative(void) { vector _negative = output.MatMul(weights[0]), _output; _negative += biases[1]; _negative.Activation(_output, THIS.activation); label = _output; }
Nossa classe MQL5, a ‘C_u_rbm’, herda de outra classe ‘Cmlp’ que temos utilizado em artigos recentes. A construção da ‘C_u_rbm’, embora herde de ‘Cmlp’, precisa ser personalizada para garantir que o número apropriado de camadas esteja em vigor e que as etapas de validação relevantes sejam cobertas. Executamos a construção dessa classe da seguinte forma, como indicado em sua interface:
#include <My\Cmlp-.mqh> //+------------------------------------------------------------------+ //| Unconventional RBM that uses: | //| reconstruction-error instead of free-energy | //| and back-propagation instead of contrastive divergence | //+------------------------------------------------------------------+ class C_u_rbm : public Cmlp { protected: public: void GetPositive(); void GetNegative(); void BackPropagate(double LearningRate = 0.1); double Get(ENUM_REGRESSION_METRIC R) { return(label.RegressionMetric(inputs, R)); } void C_u_rbm(Smlp &MLP) : Cmlp(MLP) { validated = false; int _layers = ArraySize(MLP.arch); if(_layers == 2 && MLP.arch[0] > MLP.arch[1]) { ArrayResize(biases, _layers); // ArrayResize(gradients, _layers); ArrayResize(gradients_1st_moment, _layers); ArrayResize(gradients_2nd_moment, _layers); ArrayResize(sum_gradients, _layers); ArrayResize(sum_gradients_update, _layers); // ArrayResize(deltas, _layers); ArrayResize(deltas_1st_moment, _layers); ArrayResize(deltas_2nd_moment, _layers); ArrayResize(sum_deltas, _layers); ArrayResize(sum_deltas_update, _layers); // hidden_layers = 0; bool _norm_validated = true; for(int i = 0; i < _layers; i++) { int _rows = MLP.arch[_layers - 1 - i], _columns = MLP.arch[i]; // biases[i].Init(_rows); biases[i].Fill(MLP.initial_bias); // gradients[i].Init(_rows, _columns); gradients[i].Fill(0.0); // gradients_1st_moment[i].Init(_rows, _columns); gradients_1st_moment[i].Fill(0.0); gradients_2nd_moment[i].Init(_rows, _columns); gradients_2nd_moment[i].Fill(0.0); // sum_gradients[i].Init(_rows, _columns); sum_gradients[i].Fill(0.0); sum_gradients_update[i].Init(_rows, _columns); sum_gradients_update[i].Fill(0.0); // deltas[i].Init(_rows); deltas[i].Fill(0.0); deltas_1st_moment[i].Init(_rows); deltas_1st_moment[i].Fill(0.0); deltas_2nd_moment[i].Init(_rows); deltas_2nd_moment[i].Fill(0.0); sum_deltas[i].Init(_rows); sum_deltas[i].Fill(0.0); sum_deltas_update[i].Init(_rows); sum_deltas_update[i].Fill(0.0); } validated = true; } else { printf(__FUNCSIG__ + " invalid network arch! Settings size is: %i, Max layer size is: %i, Min layer size is: %i, and activation is %s ", _layers, MLP.arch[ArrayMaximum(MLP.arch)], MLP.arch[ArrayMinimum(MLP.arch)], EnumToString(MLP.activation) ); } }; void ~C_u_rbm(void) { }; };
Ao personalizar o construtor para nossa classe, omitimos o array da matriz de pesos, pois seu tamanho é sempre o número total de camadas menos um, e é isso que temos aqui. O construtor, portanto, lidou com o que é diferente; em primeiro lugar, temos dois vetores de viés, embora as camadas sejam apenas duas. Isso implica que teremos dois vetores delta também, com um para cada vetor de viés. Outra personalização necessária é no número de matrizes de gradiente. Apesar de termos apenas uma matriz de pesos, temos duas matrizes de gradiente porque nosso backpropagation será para os dois ciclos de teste: a fase positiva e a fase negativa.
Isso também implica que nossa única matriz de pesos será atualizada duas vezes em cada backpropagation. Como sempre, o backpropagation envolve o cálculo dos deltas, depois os gradientes, e depois a atualização dos pesos e viés com esses valores. Realizamos nosso backpropagation da seguinte forma:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void C_u_rbm::BackPropagate(double LearningRate = 0.1) { //COMPUTE DELTAS vector _loss = label.LossGradient(inputs, THIS.loss); // vector _negative = output.MatMul(weights[0]), _negative_derivative; _negative.Derivative(_negative_derivative, THIS.activation); deltas[1] = Hadamard(_loss, _negative_derivative); // vector _positive = weights[0].MatMul(inputs), _positive_derivative; _positive.Derivative(_positive_derivative, THIS.activation); matrix _weights; _weights.Copy(weights[0]); _weights.Transpose(); vector _product = _weights.MatMul(deltas[1]); deltas[0] = Hadamard(_product, _positive_derivative); //COMPUTE GRADIENTS gradients[0] = TransposeCol(deltas[0]).MatMul(TransposeRow(inputs)); gradients[1] = TransposeCol(deltas[1]).MatMul(TransposeRow(output)); // UPDATE WEIGHTS AND BIASES for(int h = 1; h >= 0; h--) { matrix _gradients; _gradients.Copy(gradients[h]); if(h == 1) { _gradients = _gradients.Transpose(); } weights[0] -= LearningRate * _gradients; biases[h] -= LearningRate * deltas[h]; } }
Essa função, portanto, resume nossa classe que herda de ‘Cmlp’, com todas as funções não sobrescritas da classe base ainda em vigor. Só para esclarecer por que temos dois vetores de viés, dois vetores delta, duas matrizes de gradientes e apenas uma matriz de pesos: na fase positiva encontramos pela primeira vez a matriz de pesos e o produto dela precisa ser adicionado ao primeiro vetor de viés. Esse produto também implica que uma matriz de gradiente precisa ser capturada para atualizar corretamente os pesos sobre esse produto. Na segunda fase, o mesmo processo se repete com a principal diferença sendo, como já mencionado, que usamos os mesmos pesos da fase positiva.
Apesar de usarmos os mesmos pesos, um novo (diferente) vetor de viés é adicionado ao produto da segunda fase e isso produz nossa reconstrução dos dados de entrada. A diferença entre esses dados reconstruídos e os dados de entrada originais define nossos deltas, e isso alimenta o backpropagation.
RBM na Classe de Sinal Personalizado
Para construir a classe de sinal personalizada, precisaríamos fazer referência à nossa classe RBM personalizada criada acima em uma nova instância de uma classe de sinal personalizada que montamos em um Expert Advisor através do MQL5 wizard. Existem guias aqui e aqui sobre isso para os leitores que são novos no assunto. E uma vez que a referenciamos e a configuramos, teríamos apenas metade do nosso sistema de trade, porque, como mencionado na introdução, temos os valores da camada oculta do RBM dos dados de entrada servindo como entrada para outra rede neural na forma de um multi-layer-perceptron (MLP). A função executada pelo RBM é o embedding das mudanças de preço para desmascarar sua equivalência em uma dimensão menor. Para nossos testes, nosso RBM é uma rede 8-4, onde os números são os tamanhos das camadas visíveis e ocultas. A tarefa do RBM, na minha opinião, tende mais para a classificação do que para a regressão e, portanto, a função de perda e ativação será a entropia cruzada categórica e soft-sign. Essas configurações ainda podem ser ajustadas para funcionar como classificadores, como abordamos em artigos recentes, no entanto, estamos usando essas configurações como constantes e não são otimizáveis.
As funções executadas pelo RBM, como mencionado, estão mais alinhadas com embedding, e assim a função responsável por realizar isso em nossa classe de sinal personalizada é chamada de ‘Embedder’. O código-fonte dela é compartilhado abaixo:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSignalEmbedding::Embedder(vector &Extraction) { m_learning.rate = m_learning_rate; for(int i = 1; i <= m_epochs; i++) { U_RBM.LearningType(m_learning, i); for(int ii = m_train_set; ii >= 0; ii--) { vector _in, _in_new, _in_old, _out, _out_new, _out_old; if ( _in_new.Init(__RBM_VISIBLE) && _in_new.CopyRates(m_symbol.Name(), m_period, 8, ii + __MLP_OUTPUTS, __RBM_VISIBLE) && _in_new.Size() == __RBM_VISIBLE && _in_old.Init(__RBM_VISIBLE) && _in_old.CopyRates(m_symbol.Name(), m_period, 8, ii + __MLP_OUTPUTS + 1, __RBM_VISIBLE) && _in_old.Size() == __RBM_VISIBLE && _out_new.Init(__MLP_OUTPUTS) && _out_new.CopyRates(m_symbol.Name(), m_period, 8, ii, __MLP_OUTPUTS) && _out_new.Size() == __MLP_OUTPUTS && _out_old.Init(__MLP_OUTPUTS) && _out_old.CopyRates(m_symbol.Name(), m_period, 8, ii + __MLP_OUTPUTS, __MLP_OUTPUTS) && _out_old.Size() == __MLP_OUTPUTS ) { _in = _in_new - _in_old; _out = _out_new - _out_old; U_RBM.Set(_in); U_RBM.GetPositive(); U_RBM.GetNegative(); U_RBM.BackPropagate(m_learning.rate); Extraction = Extractor(U_RBM.output, _out, ii > 0); } } } }
Nossa preparação de dados é muito semelhante ao que fizemos em artigos anteriores. Na maioria dos Expert Advisors que são codificados manualmente e não via o MQL5 wizard, etapas extras precisam ser tomadas para garantir que os dados de preço consultados pelo Expert Advisor estejam realmente disponíveis no servidor do corretor antes que os cálculos do sinal sejam realizados. Geralmente, quando um wizard monta o Expert Advisor, isso pode ser evitado, mas no nosso caso, simplesmente usamos a cláusula if para garantir que os dados que buscamos sejam realmente copiados para os nossos vetores pretendidos.
Ao copiar dados para vetores, também é importante ter em mente que os dados não são classificados como uma série, o que implica que o índice mais alto no vetor copiado tenha o valor mais recente. Há mais aqui sobre esse assunto. Como temos uma configuração de cadeia onde o RBM fornece entrada para o MLP, optamos por treinar as duas redes quase simultaneamente, onde, em cada ponto do conjunto de treinamento do RBM, treinamos o RBM e, em seguida, seguimos com o treinamento do MLP. Isso é diferente de primeiro esgotar a sessão de treinamento do RBM antes de treinar o MLP.
Essa configuração pode certamente ser ajustada pelo leitor, dado que o código completo para isso está anexado no final, mas adotamos essa abordagem porque pode ser mais eficiente dado a necessidade de treinar duas redes para nossos fins de teste. Devido ao treinamento quase simultâneo, precisamos obter a entrada do RBM e os rótulos do MLP ao mesmo tempo, e é por isso que nossas cláusulas if são bem longas.
Integrando o RBM com um MLP
Assim, através do processo de treinamento, atualizamos os pesos do RBM, o que nos permite processar mais dados de entrada e obter seus valores ocultos (também conhecidos como sua distribuição de probabilidade). Essa distribuição de probabilidade, que estamos nos referindo como o price-embedding, é então alimentada para o MLP, e não as mudanças de preço brutas anteriores. O uso desse embedding está na função ‘Extractor’, que simplesmente repassa o price embedding fornecido pelo RBM através do MLP. À medida que ele passa, também treina o MLP se um rótulo (valor alvo) for fornecido. Como o treinamento é realizado até as mudanças de preço mais recentes para as quais não há rótulo, precisamos acompanhar isso. O rastreador é o terceiro parâmetro de entrada na função ‘Extractor’, que é o booleano ‘Train’. Por padrão, ele é verdadeiro, no entanto, uma vez que chegamos ao final do conjunto de treinamento e não temos rótulo, ele é atribuído como falso. O código para a função ‘Extractor’ é compartilhado abaixo:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ vector CSignalEmbedding::Extractor(vector &Embedding, vector &Labels, bool Train = true) { vector _extraction; _extraction.Init(MLP.output.Size()); m_learning.rate = m_learning_rate; for(int i = 1; i <= m_epochs; i++) { MLP.LearningType(m_learning, i); MLP.Set(Embedding); MLP.Forward(); _extraction = MLP.output; if(Train) { MLP.Get(Labels); MLP.Backward(m_learning, i); } } return(_extraction); }
Ela retorna um vetor que chamamos de ‘_extraction’, mas no nosso caso, estamos interessados apenas em um valor, a próxima mudança de preço, então é realmente um vetor de um tamanho. Embora o RBM seja estruturado como um classificador pela sua função de ativação e função de perda, o MLP provavelmente funcionará melhor quando estruturado como um regressor. Isso ocorre devido à sua saída singular de dados flutuantes que pode ser negativa. Como regressor, usamos a ativação soft-sign e a função de perda é Huber. Razões para isso ao lidar com redes regressoras foram abordadas em artigos recentes, como aqui, então os leitores novos podem conferir.
Tanto o RBM quanto o MLP não estão adequadamente ajustados para um desempenho ideal porque, por exemplo, eles usam os mesmos pesos e viés iniciais, além do treinamento ser simultâneo, o que essencialmente significa que uma sessão de treinamento é usada para treinar ambas as redes. Alternativas a isso incluem ter sessões de treinamento de tamanhos diferentes para o RBM e o MLP, onde o treinamento do MLP é feito somente depois que o treinamento do RBM for concluído. Além disso, a taxa de aprendizado é fixa para ambas as redes e formatos diferentes que incluem aprendizado adaptativo não foram explorados. Muitas dessas ajustes adicionais e mais ficam a cargo do leitor para investigar.
Teste de Retrocesso e Otimização
Fazemos uma otimização ao longo do ano de 2023 para o par GBPUSD no gráfico diário, buscando os pesos e viés iniciais ideais para a rede. Lembre-se, temos um único parâmetro de entrada para ambos esses valores para as duas redes, o que não é necessariamente ideal. Além disso, estamos buscando os limiares de abertura e fechamento para as funções ‘LongCondition’ e ‘ShortCondition’ da nossa classe de sinal personalizada, além de um nível de take profit em pontos. Realizamos algumas execuções que não são exaustivas, e delas obtemos os seguintes resultados:
Nosso Expert Advisor montado pelo wizard pode negociar, no entanto, como sempre, mais diligência é necessária em termos de testes ao longo de períodos mais longos de história e seguindo as otimizações com testes de caminhada para frente. Além disso, vale a pena mencionar que, por ser um sinal baseado em rede neural, o registro e a salvaguarda dos pesos e viés da rede (há 2 aqui) é algo que o usuário sempre deve planejar.
Conclusão
Fornecemos um RBM como um price embedder para um MLP com seus resultados otimizados de trade ao longo de um único ano, mas como alguém poderia avaliar a eficácia do ‘price-embedding’ no contexto dentro do qual ele é usado neste artigo? Bem, a resposta curta é: se tivermos outro sinal puramente MLP que toma como entradas as mudanças de preço brutas anteriores e também é treinado como o MLP deste artigo para projetar a próxima mudança de preço. Configurar isso também é relativamente fácil, pois as classes de ancoragem para o MLP são semelhantes ao que temos usado, então resultados comparativos podem ser facilmente obtidos.
Também emparelhamos um RBM que alimenta um MLP e não o contrário. Isso, eu acho, é uma maneira melhor para esses dois tipos de redes neurais trabalharem juntas (um classificador e um regressor). Regressores muitas vezes, mas nem sempre, estão produzindo um único valor de ponto flutuante (que pode ser negativo) e ao tomar entradas ‘classificadas’, esse emparelhamento poderia ser justificado. Seguindo em frente, essa configuração pode ser expandida, não empilhando os RBMs, já que eles exploram profundidade, mas colocando-os em paralelo como entradas para redes regressoras ativadas por transformer. Os classificadores com sua profundidade, eu sinto, são melhores construídos para ‘especialização’ do que redes regressoras.
Além disso, como nota final, simplesmente olhamos para classificar mudanças de preço como o ‘price-embedding’, no entanto diferentes dados financeiros e séries temporais também podem ser considerados ao buscar dados de ‘price-embedding’ para um MLP. Esses podem incluir padrões de preços de velas, valores de indicadores de preços, dados de notícias do calendário econômico, etc. O desempenho e os resultados dos testes para cada um deles certamente variarão muito, como se espera, e cabe ao leitor encontrar e personalizar o que funcionaria melhor com a maneira como eles veem os mercados.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15652





- 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