English Русский Español Deutsch 日本語
preview
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 08): Perceptrons

Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 08): Perceptrons

MetaTrader 5Sistemas de negociação | 15 maio 2024, 16:40
146 0
Stephen Njuki
Stephen Njuki

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.

bannr

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).

s_1


s_4


s_5


s_6


s_7


Se testarmos nosso EA compilado antes de qualquer otimização, obteremos o seguinte relatório:

init_pass

Se realizarmos a otimização do nosso EA com uma janela de forward test, obteremos os seguintes resultados:

back_pass

forward_pass

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

Arquivos anexados |
MQL5.zip (11.41 KB)
Interpretação de modelos: Compreensão mais profunda dos modelos de aprendizado de máquina Interpretação de modelos: Compreensão mais profunda dos modelos de aprendizado de máquina
O aprendizado de máquina é uma área fascinante e essencial para todos, independentemente da experiência que possuam. Neste artigo, vamos mergulhar nos detalhes dos mecanismos que fundamentam os modelos desenvolvidos, desvendaremos o intricado universo das características, das previsões e das soluções robustas, e alcançaremos uma interpretação cristalina dos modelos. Descubra como “fazer concessões”, aprimorar previsões, priorizar a importância dos parâmetros e fazer escolhas assertivas. Este texto servirá de guia para você aprimorar a eficácia dos modelos de aprendizado de máquina e maximizar os benefícios das metodologias aplicadas.
Anotação de dados na análise de série temporal (Parte 4): Decomposição da interpretabilidade usando anotação de dados Anotação de dados na análise de série temporal (Parte 4): Decomposição da interpretabilidade usando anotação de dados
Esta série de artigos apresenta várias técnicas destinadas a rotular séries temporais, técnicas essas que podem criar dados adequados à maioria dos modelos de inteligência artificial (IA). A rotulação de dados (ou anotação de dados) direcionada pode tornar o modelo de IA treinado mais alinhado aos objetivos e tarefas do usuário, melhorar a precisão do modelo e até mesmo ajudar o modelo a dar um salto qualitativo!
Algoritmos de otimização populacional: simulação de têmpera (Simulated Annealing, SA). Parte I Algoritmos de otimização populacional: simulação de têmpera (Simulated Annealing, SA). Parte I
O algoritmo de simulação de têmpera é uma metaheurística inspirada no processo de têmpera de metais. Neste artigo, realizaremos uma análise detalhada do algoritmo e mostraremos como muitas concepções comuns e mitos em torno deste método de otimização popular e amplamente conhecido podem ser equivocados e incompletos. Anúncio da segunda parte do artigo: "Conheça nosso algoritmo autoral de simulação de têmpera isotrópica (Simulated Isotropic Annealing, SIA)!"
Padrões de projeto no MQL5 (Parte 3): Padrões comportamentais 1 Padrões de projeto no MQL5 (Parte 3): Padrões comportamentais 1
Neste novo artigo da série dedicada a padrões de projeto, exploraremos os padrões comportamentais para entender como criar métodos eficazes de interação entre os objetos criados. Ao projetar esses padrões de comportamento, poderemos entender como desenvolver software reutilizável, expansível e testável.