English Русский 中文 Español Deutsch 日本語
preview
Teoria das Categorias em MQL5 (Parte 17): funtores e monoides

Teoria das Categorias em MQL5 (Parte 17): funtores e monoides

MetaTrader 5Sistemas de negociação | 31 janeiro 2024, 14:47
227 0
Stephen Njuki
Stephen Njuki

Introdução

Continuamos a examinar a teoria das categorias aplicada a funtores. Até agora, vimos a aplicação da teoria das categorias na implementação de instâncias customizadas da classe Expert Trailing e da classe Expert Signal. Neste artigo, vamos considerar aplicações usando a classe Expert Money. Todas essas classes são fornecidas com o IDE Meta Editor e usadas com o assistente MQL5 para montar EAs, minimizando a escrita de código.

O tamanho da posição é uma das questões mais importantes quando se trata de projetar um sistema de trading. Os resultados obtidos no teste preliminar de qualquer EA são muito sensíveis a esse parâmetro. Muitas vezes, recomenda-se excluí-lo completamente (usar margem fixa ou tamanho de lote fixo) ou, se necessário, adicioná-lo no final, quando você já tem um sinal de entrada um sinal de entrada adequado e bem equilibrado com seus métodos de saída. Apesar disso, tentaremos estabelecer o tamanho ideal da posição com base no stop-loss previsto na instância customizada da classe Expert Money.

Os funtores são a ponte entre categorias, fixando diferenças não só entre objetos em cada categoria, mas também diferenças em seus morfismos. Conseguimos ver como as informações coletadas podem ser usadas para prever mudanças na volatilidade e tendências de mercado, considerando categorias representadas na forma de grafos e ordens lineares. Os grafos de completude (recall graphs) e ordens lineares não são categorias em si, mas foram considerados como tais porque continham axiomas-chave das categorias.

Os funtores foram implementados usando equações lineares simples nos artigos 14 e 15, onde para mapeamento era necessário apenas um coeficiente angular, bem como um deslocamento no eixo Y para definir objetos e morfismos na categoria do codomínio. A partir do artigo 16, começamos a considerar funtores como uma rede neural simples, conhecida como perceptron multicamadas. Esta rede, baseada no trabalho de Warren McCulloch e Walter Pitts, aproxima qualquer função contínua de -1 a +1, e também reproduz qualquer OU exclusivo por um tempo se for multicamada.

Neste artigo, concluiremos nosso trabalho com funtores e examinaremos como, em combinação com monoides e a pré-ordem dos preços dos ativos de negociação, podemos formular um sistema para definir tamanhos de posição ao negociar uma ação, que, neste caso, será BTCUSD (Bitcoin). Quando consideramos monoides pela última vez, sua principal aplicação no trading era a classificação de etapas de negociação de maneira que decisões pudessem ser tomadas com base nas operações de cada monoide. Lembremos que um monoide é um conjunto com uma operação binária e um elemento unitário. Por isso, criamos monoides para cada um dos passos condicionais que um trader enfrenta ao tomar a decisão de abrir uma posição.


Funtores e monoides no trading

Os objetos (chamados de domínios em artigos anteriores) são as células ou blocos de construção das categorias. Dentro de uma categoria, os objetos têm mapeamentos entre si, chamados de morfismos, e assim como os morfismos conectam objetos, funtores conectam categorias.

Assim, a conexão entre categorias fornecida por funtores provou ser útil na elaboração de previsões, pois a categoria do codomínio representa uma série temporal, cuja previsão estamos considerando aqui. Em nosso último artigo, isso informava se deveríamos abrir uma posição longa ou curta no S&P 500.

No entanto, diferentemente do artigo anterior, onde funtores conectavam grafos a ordens lineares, agora teremos monoides como a categoria do domínio. Como mencionado anteriormente, usamos monoides como pontos de decisão em cada etapa da realização de uma negociação. Esses passos, que naturalmente diferem dos usados por outros traders devido a diferenças de estratégia, incluem a escolha do timeframe, o período de análise retrospectiva, a escolha do preço aplicável e a escolha do indicador, que incorpora o timeframe escolhido, a análise retrospectiva e o preço aplicado. A escolha final era feita para a ação de negociação, ou seja, se seguimos o valor do indicador de acordo com a tendência indicada ou abrimos uma posição oposta. Por isso, os monoides eram codificados para cada uma dessas decisões, e a operação binária de cada monoide era responsável pela escolha do valor apropriado de cada conjunto após a iteração de todos os valores do conjunto.


Funtores como perceptrons multicamadas (MLP)

O papel do perceptron multicamadas no trading é inestimável. O enorme volume de artigos sobre redes neurais serve como um claro testemunho de que ele está se tornando cada vez mais indispensável. A maioria dos traders o utiliza para prever os próximos movimentos de preço, o que é bastante justificado. No entanto, na minha opinião, eles frequentemente perdem de vista a escolha (e, possivelmente, a regularização) dos dados de entrada para a rede. Suponha que você queira prever uma série temporal, mas em qual fluxo de dados você baseará sua previsão e por quê? Isso pode parecer menos importante, mas pode ser uma das razões pelas quais muitas redes, treinadas em grandes volumes de dados, não funcionam tão bem quanto em testes.

Assim, como usamos monoides para tomar decisões em cada etapa do nosso sistema de trading básico no artigo 9 e em vários outros, faremos o mesmo neste artigo, com a diferença de que a última etapa será omitida. Assim, restarão quatro etapas. A quinta etapa omitida, na qual o monoide decide se segue a tendência ou negocia contra ela, não é relevante aqui. Nosso perceptron multicamadas (MLP) então usará cada um dos quatro valores de saída restantes do monoide como dados de entrada. O resultado alvo para o qual nosso MLP será treinado será o ponto ideal de stop-loss para uma posição aberta. O tamanho dessa margem de stop-loss é inversamente proporcional aos lotes negociados na posição, portanto, servirá como um indicador chave do tamanho da nossa posição.

Então, é assim que determinaremos o tamanho da posição com base na margem de stop-loss prevista:

         //output from MLP forecast
         double _stoploss_gap=_y_output[0];
         
         //printf(__FUNCSIG__+" ct call: "+DoubleToString(_stoploss_gap));
      
         sl=m_symbol.Ask()+fabs(_stoploss_gap);
         
         //--- select lot size
         double _ct_1_lot_loss=(_stoploss_gap/m_symbol.TickSize())*m_symbol.TickValue();
         double lot=((m_percent/100.0)*m_account.FreeMargin())/_ct_1_lot_loss;
         
         //--- calculate margin requirements for 1 lot
         if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_SELL,lot,m_symbol.Bid())<0.0)
         {
            printf(__FUNCSIG__" insufficient margin for sl lot! ");
            lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,m_symbol.Bid(),m_percent);
         }
         
         //--- return trading volume
         return(Optimize(lot));

Primeiro, calculamos a perda por lote ou o valor em dólares do rebaixamento incorrido quando uma posição de um lote é aberta com prejuízo sobre a margem de stop-loss prevista. Essa margem, dividida pelo tamanho do tick, é multiplicada pelo valor do tick. Se tomarmos a distribuição percentual de margem m_percent, normalmente alocada para uma nova posição, como o máximo rebaixamento percentual permitido para a posição aberta, então nossos lotes representarão esse percentual, dividido por 100, multiplicado pela nossa margem livre e dividido pela perda por lote. Em outras palavras, quanto de perda por lote podemos suportar com nossa máxima queda permitida?


Operações monoides para definir o tamanho das posições

Assim, nos artigos anteriores, a tomada de decisão por cada monoide era realizada por uma função chamada Operate, e, dependendo do método de operação para o monoide de entrada, ela envolvia a seleção de valores do conjunto dos monoides. Os métodos de operação usados no nosso caso eram muito elementares. Dos 6, utilizamos apenas 4, pois para adição e multiplicação era necessário que zero e um sempre estivessem presentes nos conjuntos dos monoides, o que não era possível no nosso caso. O código que define esta enumeração e função é o seguinte:

//+------------------------------------------------------------------+
//| Enumeration for Monoid Operations                                |
//+------------------------------------------------------------------+
enum EOperations
  {
      OP_FURTHEST=5,
      OP_CLOSEST=4,
      OP_MOST=3,
      OP_LEAST=2,
      OP_MULTIPLY=1,
      OP_ADD=0
  };


//+------------------------------------------------------------------+
//|   Operate function for executing monoid binary operations        |
//+------------------------------------------------------------------+
void CMoneyCT::Operate(CMonoid<double> &M,EOperations &O,int IdenityIndex,int &OutputIndex)
   {
      OutputIndex=-1;
      //
      double _values[];
      ArrayResize(_values,M.Cardinality());ArrayInitialize(_values,0.0);
      //
      for(int i=0;i<M.Cardinality();i++)
      {
         m_element.Let();
         if(M.Get(i,m_element))
         {
            if(!m_element.Get(0,_values[i]))
            {
               printf(__FUNCSIG__+" Failed to get double for 1 at: "+IntegerToString(i+1));
            }
         }
         else{ printf(__FUNCSIG__+" Failed to get element for 1 at: "+IntegerToString(i+1)); }
      }
      
      //
      
      if(O==OP_LEAST)
      {
         ...
      }
      else if(O==OP_MOST)
      {
         ...
      }
      else if(O==OP_CLOSEST)
      {
         ...
      }
      else if(O==OP_FURTHEST)
      {
         ...
      }
   }

Agora, como estamos interessados no tamanho da posição, e não na volatilidade de previsão de tendências de mercado, o monoide final de "ação" e sua decisão podem ser substituídos por um funtor. Então, os primeiros quatro monoides serão executados para ajudar a determinar o valor do indicador e definir o tamanho da posição. Vamos nos ater aos indicadores RSI e às bandas de Bollinger regularizadas para obter um valor semelhante ao RSI, variando de 0 a 100. Assim, embora este valor do indicador seja o resultado dos três resultados anteriores do monoide, ele será combinado com eles para criar um conjunto de quatro valores que formam os dados de entrada do nosso perceptron multicamadas. Nesse sentido, os dados de entrada sobre o timeframe e o preço aplicável precisarão ser convertidos em um formato numérico que possa ser processado pelo MLP, como fizemos no artigo anterior.

Assim, repito, o monoide, que é simplesmente um conjunto, uma operação binária e um elemento unitário, simplesmente permite escolher um elemento deste conjunto, conforme definido pela operação binária. Tendo escolhido o timeframe, o período de análise retrospectiva e o preço aplicável, obtemos os dados de entrada para o nosso indicador, cujo valor normalizado (0–100) servirá como o quarto valor de entrada no MLP.

Os detalhes dos passos relacionados à escolha do timeframe, período de análise retrospectiva, preço aplicável e indicador usado foram descritos em um artigo anterior. O código atualizado está anexado. No entanto, abaixo é mostrado como obtemos os resultados de cada uma das funções correspondentes:

         ENUM_TIMEFRAMES _timeframe_0=SetTimeframe(m_timeframe,0);
         int _lookback_0=SetLookback(m_lookback,_timeframe_0,0);
         ENUM_APPLIED_PRICE _appliedprice_0=SetAppliedprice(m_appliedprice,_timeframe_0,_lookback_0,0);
         double _indicator_0=SetIndicator(_timeframe_0,_lookback_0,_appliedprice_0,0);


Integração de funtores e monoides para definição complexa do tamanho da posição

Para usar corretamente nosso MLP, precisamos treiná-lo adequadamente. No último artigo, o treinamento foi realizado durante a inicialização, e os pesos mais vantajosos da rede (se disponíveis para as configurações selecionadas da rede) eram carregados e usados como ponto de partida para o ajuste dos pesos. Neste artigo, o pré-treinamento antes ou durante a inicialização não é realizado. Em vez disso, a rede é treinada a cada nova barra. Isso não significa que este seja o método correto, mas é simplesmente uma demonstração das muitas opções disponíveis para o usuário nesse caso, quando se trata de treinar o MLP ou a rede. No entanto, isso implica que, como os pesos iniciais são sempre aleatórios, as mesmas configurações do EA inevitavelmente darão resultados muito diferentes em diferentes execuções de teste. Para contornar esse problema, os pesos de uma execução de teste lucrativa serão gravados em um arquivo, e no início da próxima execução com configurações de rede semelhantes (número de elementos na camada oculta), esses pesos serão carregados e servirão como pesos iniciais no modo de teste. Visrto que as funções de leitura e gravação de rede são proprietárias, aqui são fornecidas apenas referências à biblioteca. O leitor deve implementar sua própria.

Bem, aqui é apresentada a sinergia entre monoides e MLP. Qualquer um deles por si só pode criar previsões sobre a distância do stop. Idealmente, isso exigirá um controle para teste, ou seja, precisaremos de EAs separados que implementem apenas monoides e apenas MLP e comparem todos os três conjuntos de resultados. Infelizmente, isso não é possível para este artigo, no entanto, o código-fonte demonstrando ambas as ideias está anexado, então o leitor pode estudar e verificar (ou refutar?) essa ideia acerca da sinergia.

Assim, o código que combina estes dois componentes é o seguinte:

      m_open.Refresh(-1);
      m_high.Refresh(-1);
      m_close.Refresh(-1);
      
      CMLPTrain _train;
      
      int _info=0;
      CMLPReport _report;
      CMatrixDouble _xy;_xy.Resize(1,__INPUTS+__OUTPUTS);
      
      _xy[0].Set(0,RegularizeTimeframe(_timeframe_1));
      _xy[0].Set(1,_lookback_1);
      _xy[0].Set(2,RegularizeAppliedprice(_appliedprice_1));
      _xy[0].Set(3,_indicator_1);
      //
      int _x=StartIndex()+1;
      
      double _sl_1=m_high.GetData(_x)-m_low.GetData(_x);
      
      if(m_open.GetData(_x)>m_close.GetData(_x))
      {
         _sl_1=m_high.GetData(_x)-m_open.GetData(_x);
      }
      
      double _stops=(2.0*(m_symbol.Ask()-m_symbol.Bid()))+((m_symbol.StopsLevel()+m_symbol.FreezeLevel())*m_symbol.Point());
      
      _xy[0].Set(__INPUTS,fmax(_stops,_sl_1));
      
      _train.MLPTrainLM(m_mlp,_xy,1,m_decay,m_restarts,_info,_report);


Exemplo: Aplicação prática e testes históricos

Para analisar nosso método para determinar o tamanho de uma posição composta, usaremos Bitcoin (BTCUSD) como ativo de teste. Os testes com otimização serão realizados no período de 1º de janeiro de 2020 a 1º de agosto de 2023, em um timeframe diário. Além da otimização para alcançar o número ideal de pesos na camada oculta (já que estamos usando um MLP com apenas uma camada oculta), também tentaremos ajustar precisamente os quatro monoides que usamos para obter o tamanho da posição. Isso significa que tentaremos estabelecer o elemento de identificação ideal e o tipo de operação para cada um dos quatro monoides usados na determinação do tamanho da nossa posição. Nossa análise se concentrará exclusivamente no tamanho da posição, o que significa que o sinal do EA usado deve ser um dos fornecidos na biblioteca MQL5. Para isso, usaremos a classe de sinais de experts RSI. Nenhum trailing stop será implementado e, como sempre, como em todos os testes anteriores, os valores de take-profit ou stop-loss não serão usados, consequentemente, os parâmetros de take level e stop level serão iguais a zero. No entanto, nosso EA estará aberto para o uso de ordens pendentes, então o parâmetro price level também será otimizado, como antes.

Realizamos testes com o funtor de objetos e o funtor de morfismos. Os resultados são mostrados abaixo:

r_1


r_2

Para quaisquer valores de entrada dados, como os acima com execução com morfismos de objetos, os resultados não são necessariamente reproduzíveis, pois a cada inicialização do MLP, são atribuídos pesos aleatórios. Nos ajuda o fato de que podemos carregar pesos de uma execução lucrativa anterior durante a inicialização, para não começar sempre do zero, mas mesmo assim, como o treinamento acontece a cada nova barra, você acabará ajustando os pesos de um MLP lucrativo. Isso significa que você não obterá resultados idênticos. Daí a sugestão de que o leitor que escreva um método adaptado à sua estratégia, que registre e leia cuidadosamente os pesos de seus melhores testes.

Se, como controle, executarmos o EA com o mesmo sinal RSI, com o mesmo sinal sem trailing stop, mas com outro método para determinar o tamanho da posição, usando margem fixa, obteremos os seguintes resultados:

r_ctrl

Pelos resultados, nossos sistemas tendem a apresentar melhores resultados mesmo sem testes exaustivos (a otimização foi interrompida, já que o objetivo era apenas demonstrar o potencial). No entanto, como mencionado no início do artigo, normalmente para a maioria dos traders, os aspectos do sistema relacionados à determinação do tamanho da posição são de menor prioridade, pois têm um enorme impacto nos resultados dos testes, mas, estritamente falando, não significam nada se o sistema usar um sinal de entrada confiável.


Limitações e nuances

Vamos destacar algumas das potenciais questões e limitações do tamanho da posição com base em funtores. Em primeiro lugar, obter conjuntos de monoides que funcionem com a estratégia do usuário e criar modelos de treinamento correspondentes para o MLP requerem um longo período de treinamento. Ambos são cruciais para a implementação da estratégia que apresentamos aqui e requerem um bom tempo para serem dominados.

Em segundo lugar, os gaps de preço ou os ativos cujos dados OHLC de um minuto não correspondem aos dados reais de tick não produzem configurações confiáveis de monoides ou pesos de MLP para negociação em uma conta real (considerando o movimento para frente). As razões são óbvias, mas este é o principal obstáculo na implementação das ideias apresentadas aqui.

Em terceiro lugar, o problema do sobreajuste e generalização é outra questão, particularmente característica do MLP. Eu já havia aludido a isso anteriormente, destacando a importância do conjunto de dados da camada de entrada e, na minha opinião, a solução está no uso de dados regulares e significativos. "Significativos" no sentido de que existem aspectos fundamentais confiáveis do conjunto de dados da camada de entrada que se espera que influenciem a previsão de interesse.

Além disso, existe o problema de ajuste de parâmetros, que alguns podem dizer que está relacionado ao ponto anterior, mas eu diria que vai além, se considerarmos a quantidade de recursos computacionais envolvidos e o tempo necessário para alcançar nossos parâmetros alvo.

A fraca interpretabilidade e transparência de qualquer sistema desenvolvido usando MLP é outra preocupação que os programadores devem estar cientes. Se você tem um sistema funcional e deseja começar a atrair investidores, muitas vezes eles exigem mais informações sobre como seu sistema funciona com o MLP. Dependendo dos níveis da sua rede e sua complexidade, isso pode ser uma tarefa desafiadora. Há outros aspectos, como o pré-processamento de dados, que é uma espécie de requisito para o MLP, já que eles sempre precisam carregar pesos, mudanças no regime de mercado, atualização e manutenção de modelos e assim por diante. Todos estes factores devem ser tidos em conta e devem ser desenvolvidas medidas adequadas para os resolver à medida que surgem.

Na verdade, pode-se argumentar que o mencionado sobre mudança no regime de mercado reforça a vantagem das abordagens tradicionais de trading, como a negociação manual. Eu acredito que ainda não há uma resposta definitiva, já que os sistemas de hoje são desenvolvidos e testados em extensos conjuntos de dados históricos, cobrindo uma ampla gama de regimes de mercado.


Conclusão e perspectivas

Demonstramos como uma nova visão do monoide como categoria pode ser implementada na linguagem MQL5. Mostramos que essa implementação pode ser útil para determinar o tamanho da posição de um sistema de trading, que, no nosso caso, dependia do indicador RSI para sinais de entrada e saída.

A importância do uso de funtores e monoides como ferramentas para determinar o tamanho da posição sugere que o mesmo pode ser feito para outros aspectos de um sistema de trading, como o sinal de entrada ou, como frequentemente ocorre nesta série, o posicionamento e ajuste do trailing stop.

Como observado no final, ainda há trabalho a ser feito e obstáculos a serem superados antes que os traders possam utilizar plenamente as ideias apresentadas aqui, por isso os leitores são encorajados a explorar e experimentar abordagens baseadas em funtores ao desenvolver seus próprios sistemas de trading.


Referências

O trabalho inclui hiperlinks necessários para artigos da Wikipedia.


Apêndice: Fragmentos de Código MQL5

Coloque o arquivo MoneyCT_17_.mqh na pasta MQL5\include\Expert\Money\ e ct_9.mqh na pasta Include.

Você pode achar úteis as recomendações fornecidas aqui sobre como compilar um EA usando o assistente MQL5. Conforme mencionado no artigo, usei o oscilador RSI como sinal de entrada. Trailing stop não foi utilizado. Como sempre, o objetivo do artigo não é apresentar um Santo Graal, mas sim oferecer uma ideia que você pode adaptar à sua própria estratégia. Os arquivos *.*mq5 foram compilados no assistente MQL5. Você pode compilá-los ou criar os seus próprios. O arquivo ct_17_control foi compilado com margem fixa.


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13156

Arquivos anexados |
ct_17.mq5 (9.25 KB)
ct_9.mqh (65.06 KB)
MoneyCT_17_.mqh (36.29 KB)
Testes de permutação de Monte Carlo no MetaTrader 5 Testes de permutação de Monte Carlo no MetaTrader 5
Este artigo explora o uso de testes de permutação, aplicando-os a qualquer Expert Advisor através da reorganização de dados de ticks, recorrendo exclusivamente aos recursos disponíveis no MetaTrader 5.
Redes neurais de maneira fácil (Parte 54): usando o codificador aleatório para exploração eficiente (RE3) Redes neurais de maneira fácil (Parte 54): usando o codificador aleatório para exploração eficiente (RE3)
A cada vez que consideramos métodos de aprendizado por reforço, nos deparamos com a questão da exploração eficiente do ambiente. A solução deste problema frequentemente leva à complexificação do algoritmo e ao treinamento de modelos adicionais. Neste artigo, vamos considerar uma abordagem alternativa para resolver esse problema.
GIT: Mas que coisa é esta ? GIT: Mas que coisa é esta ?
Neste artigo apresentarei uma ferramenta de suma importância para quem desenvolve programas. Se você não conhece GIT, veja este artigo para ter uma noção do que se trata, tal ferramenta. E como usá-la junto ao MQL5.
Testando o conteúdo informativo de diferentes tipos de médias móveis Testando o conteúdo informativo de diferentes tipos de médias móveis
Todos conhecemos a importância da média móvel para muitos traders. Existem diferentes tipos de médias móveis que podem ser úteis no trading. Vamos examiná-las e realizar uma simples comparação para ver qual delas pode apresentar os melhores resultados.