preview
Indicador Customizado: Traçar os Pontos de Entradas Parciais em contas Netting

Indicador Customizado: Traçar os Pontos de Entradas Parciais em contas Netting

MetaTrader 5Exemplos | 24 julho 2024, 09:49
13 0
Daniel Santos
Daniel Santos

Conteúdo

  1. Introdução
  2. O que é uma Conta Netting?
  3. Trabalhando com os eventos de negociação
  4. Exemplo prático de utilização - introdução
  5. Propriedades dos indicadores
  6. Descrição do algoritmo
  7. Outro exemplo prático
  8. Integração com um Expert Advisor
  9. Conclusão


1. Introdução

Quando se trata de indicadores, podemos imaginar diversas funcionalidades: desenhar gráficos, como histogramas, linhas de tendência, setas ou barras; calcular dados com base na movimentação de preços e volumes; ou observar padrões estatísticos em suas transações. Estes são todos importantes aliados para decifrar possíveis cenários no mercado.Neste artigo, porém, veremos uma forma diferente de construir um indicador em MQL5, voltado para o gerenciamento de nossas próprias posições: entradas, saídas parciais e muito mais. Usaremos intensivamente arrays dinâmicos e algumas funções de negociação (Trade) relacionadas a histórico de transações e a posições abertas.


2. O que é uma Conta Netting?

Conforme o título deste artigo sugere, este indicador só fará sentido em uma conta com sistema de registro Netting - também conhecido como sistema de compensação. Nesse sistema, só é permitido ter uma única posição de um mesmo ativo. Se realizarmos uma transação na mesma direção, ocorrerá o aumento do volume da posição. Já se a transação for na direção oposta, existem três possibilidades para a posição aberta:

  1. Volume menor -> diminuição da posição
  2. Volume idêntico ->  fechamento da posição
  3. Volume maior -> reversão da posição


Por exemplo. Em uma conta Hedge (ou sistema de cobertura), podemos efetuar duas transações de compra de 1 lote cada no par EURUSD, resultando em duas posições distintas no mesmo ativo. Por outro lado, realizar duas transações de compra de 1 lote cada do ativo EURUSD em conta Netting resultará em uma única posição de 2 lotes desse ativo, com preço médio ponderado das duas transações. Como foram dois lotes de volumes iguais, o preço da posição é a média aritmética do preço de cada transação.

Esse cálculo é feito da seguinte forma:

Cálculo do

Ou seja, temos uma média de preços (P) ponderada pelo volume (N) dos lotes de cada transação.

Para maiores detalhes sobre a diferença entre os sistemas, recomendamos a leitura so artigo escrito pela MetaQuotes -  Agora a plataforma MetaTrader 5 possui um sistema de registro de posições. Daqui em diante neste artigo, consideraremos todas as operações como sendo feitas em uma conta Netting, e caso ainda não possua uma desse tipo, é possível abrir gratuitamente uma conta de demonstração na MetaQuotes, como mostram as telas a seguir.

No MetaTrader 5, clique em Arquivo > Abra uma Conta:

Selecionar a empresa para abrir conta

Depois de escolher a abertura de uma conta demo, clique em avançar deixando desmarcada a opção "Utilizar cobertura no trading".

Finalizando abertura de conta demo


3. Trabalhando com os eventos de negociação

Trabalhar com eventos de negociação é essencial para a manipulação bem-sucedida de ordens, transações e posições. Solicitações de negociação podem ser instantâneas ou pendentes, e assim que uma ordem é executada, ela resulta em transações que podem abrir, fechar ou modificar uma posição.

Indicadores não têm permissão para usar funções como OrderSend(), mas eles conseguem interagir com a base histórica de transações e as propriedades das posições. Por meio da função OnCalculate(), o indicador pode capturar informações de preço de abertura, preço de fechamento, volume, etc. Embora a função OnTrade() seja usada primariamente em Expert Advisors, ela também é aplicável aos indicadores, pois eles são capazes de detectar eventos Trade fora do testador de estratégias, tornando a atualização dos objetos gráficos mais rápida e eficiente.


4. Exemplo prático de utilização - introdução

A próxima figura ilustra um exemplo prático de utilização do indicador customizado que estamos desenvolvendo. Trata-se de uma posição comprada em 3 lotes, decorrente de uma operação mais longa onde houve várias entradas e saídas parciais (e até reversões). Isso explica a aparente divergência entre o preço médio mostrado no gráfico e os preços e quantidades de lotes indicados. Ao acompanharmos os eventos de negociação, é possível compreender os critérios que o algoritmo adota para a remoção das linhas e atualização na tela do número de lotes, na medida em que há saídas parciais.

Gráfico com a indicação das entradas parciais e volumes


5. Propriedades dos indicadores

As propriedades dos indicadores vão obrigatoriamente logo no início da descrição, e foram definidas da seguinte forma:

#property indicator_chart_window			// O indicador é visualizado na janela principal
#property indicator_buffers 0				// Zero buffers
#property indicator_plots   0				// Sem plotagens 
//--- plot Label1
#property indicator_label1  "Propriedades das linhas"
#property indicator_type1   DRAW_LINE			// tipo de linha para a primeira plotagem
#property indicator_color1  clrRoyalBlue		// cor da linha para a primeira plotagem
#property indicator_style1  STYLE_SOLID			// estilo da linha para a primeira plotagem
#property indicator_width1  1				// largura da linha para a primeira plotagem

Esse código se reflete na tela de carregamento do indicador, quando são pedidos os parâmetros iniciais.

Carregar indicador


6. Descrição do algoritmo

A função OnInit(), carregada no início do programa, é responsável por realizar uma inicialização de um array do tipo double intitulado "Element", que funciona como o verdadeiro buffer do indicador. O referido array é formado por três colunas e cada índice armazena: 0 - preço; 1 - volume;e 2 - número do ticket. Cada linha dessa matriz armazena uma transação do histórico. Se a inicialização for bem-sucedida, ou seja, se for constatado que a conta não é do tipo hedge, a função OnTrade() será acionada em seguida. No caso de falha na inicialização, o indicador será encerrado e removido do gráfico.

Veja:

int OnInit()
  {
   ArrayResize(Element,0,0);
   int res=INIT_SUCCEEDED;
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
      res=INIT_FAILED;
   else
      OnTrade();
   return(res);
  }

Após a inicialização, a função OnTrade() é acionada pela função OnCalculate(), e de forma nativa quando há eventos Trade. Para garantir seu acionamento único e apenas quando houver um novo candle, é implementado um filtro da função isNewBar() e da variável booleana isOldBar. Dessa forma, temos três modos de acionamento da função OnTrade(): na inicialização, quando há mudança de candle, e a cada evento de negociação. Esses processos asseguram a leitura atualizada dos eventos, o processamento dessa leitura e armazenamento no vetor Element, e o reflexo fidedigno nos objetos gráficos da tela, com linhas e textos.

A função OnTrade() é responsável por atualizar variáveis importantes no algoritmo de negociação. A começar pela variável do tipo datetime "date", que armazena o tempo inicial da seleção do histórico de ordens. Quando não há uma posição aberta no início do programa, a variável date é atualizada com o tempo de abertura da barra atual.

Ocorrendo uma negociação, a função PositionsTotal() retorna valor maior que zero, e através de um loop são filtradas as posições do ativo correspondente ao gráfico onde o indicador foi inserido. Então, é selecionado o histórico, e recuperadas todas as ordens executadas nesse identificador. A variável date recebe, assim, o tempo mais antigo dentre essas ordens, que corresponde ao tempo de início do identificador.

No caso de uma segunda posição com identificador diferente, é necessário verificar se há elementos gráficos a serem removidos pela função LimparRetângulos(), para garantir que tudo está atualizado. Então, o array Element é redimensionado para zero, eliminando os dados que contém. Quando não há posição aberta, a função também aciona a função LimparRetângulos() e zera o array Element. A variável date, então, armazena o valor da última hora conhecida do servidor, ou seja, o tempo atual. Finalmente, o valor que restou na variável date é repassada para a função ListarOrdensPosicao().

void
 OnTrade()
  {
//---
   static datetime date=0;
   if(date==0)
      date=lastTime;
   long positionId=-1,numberOfPositions=0;
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(m_position.SelectByIndex(i))
         if(m_position.Symbol()==ativo000)
           {
            numberOfPositions++;
            positionId=m_position.Identifier();
            oldPositionId=positionId;
           }
   if(numberOfPositions!=0)
     {
      //Print("PositionId: "+positionId);
      HistorySelectByPosition(positionId);
      date=TimeCurrent();
      for(int j=0; j<HistoryDealsTotal(); j++)
        {
         ulong ticket = HistoryDealGetTicket(j);
         if(ticket > 0)
            if(HistoryDealGetInteger(ticket,DEAL_TIME)<date)
               date=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
        }
      if(HistoryDealsTotal()==1 && (ArraySize(Element)/3)>1)
         if(LimparRetangulos())
            ArrayResize(Element,0,0);
     }
   else
     {
      bool isClean=LimparRetangulos();
      ArrayResize(Element,0,0);
      if(isClean)
        date=TimeCurrent();        // O array não será manipulado enquanto não houver nova posição aberta
      ArrayPrint(Element);         // Se não houver erros, essa função não será chamado aqui: o array com tamanho zero
     }
   ListarOrdensPosicao(date);
  }

A função ListaOrdensPosicao() desempenha um papel importante, pois é a responsável pelo acionamento de funções para a adição e remoção de entradas no array Element por meio das funções AddValue() e RemoveValue(). Ao receber o parâmetro dateInicio, do tipo int, existem duas possibilidades. Se o histórico estiver ausente das transações no período passado à função HistorySelect(inicio, fim), a função irá diretamente para seu fim, acionando a função PlotarRetangulos(), atualizando assim os objetos na tela de acordo com o array Element. Por outro lado, se houver transações no histórico, a função HistoryDealsTotal() retornará um valor não nulo. Dessa forma, haverá uma nova verificação, cujo objetivo é inspecionar cada transação encontrada, classificando-a pelo tipo de entrada, coletando preço, volume e número do ticket. Essas transações podem ser: DEAL_ENTRY_IN (entrada), DEAL_ENTRY_OUT (saída) ou DEAL_ENTRY_INOUT (reversão).

Se a transação for de entrada, é acionada a função AddValue(), e RemoveValue() se for de saída, passando os parâmetros de preço, volume e ticket coletados anteriormente. Se for uma reversão, a função AddVolume() também será acionada, levando em consideração que o número do ticket não tenha anteriormente sido inserido no array, além de passar os parâmetros de preço e volume, calculando-se o último como a diferença entre o volume coletado e o volume das transações passadas ainda presentes no array.

Esse processo simula uma reconstituição da posição, sendo que, ao chegar à transação de reversão, a posição histórica é revertida e inserida no array como se fosse uma nova entrada, ajustando-se a quantidade de lotes. A linhas presentes na tela até o momento também são apagadas. A função Sort() faz o ordenamento ascendente do array Element com base na coluna preço, além de apagar os objetos no gráfico de acordo com os valores zerados na coluna 1 (volume) do array. Por fim, essa função verifica eventuais inconsistências, removendo linhas do array com índices 0 e 1 iguais a zero (preço e volumes nulos).

void ListarOrdensPosicao(datetime dateInicio)
  {
//Obtenção do Histórico
   datetime inicio=dateInicio,fim=TimeCurrent();
   if(inicio==0)
      return;
   HistorySelect(inicio, fim);
   double deal_price=0, volume=0,newVolume;
   bool encontrouTicket;
   uint tamanhoElement=0;
   for(int j=0; j<HistoryDealsTotal(); j++)
     {
      ulong ticket = HistoryDealGetTicket(j);
      if(ticket <= 0)
         return;
      if(HistoryDealGetString(ticket, DEAL_SYMBOL)==_Symbol)
        {
         encontrouTicket=false;
         newVolume=0;            // Necessário zerar a cada laço do for
         volume=HistoryDealGetDouble(ticket,DEAL_VOLUME);
         deal_price=HistoryDealGetDouble(ticket,DEAL_PRICE);
         double auxArray[1][3] = {deal_price,volume,(double)ticket};
         if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_IN)
            AddValue(deal_price,volume,(double)ticket);
         if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT)
            RemoveValue(deal_price,volume,(double)ticket);
         if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_INOUT)
           {
            tamanhoElement = ArraySize(Element)/3; //Checar tamanho do array sempre, pode variar com as funções Add/RemoveValue())
            for(uint i=0; i<tamanhoElement; i++)
               if(Element[i][2]==ticket)
                 {
                  encontrouTicket=true;
                  break;
                 }
            if(!encontrouTicket) // Se fizer toda a varredura anterior e não encontrar no array menção ao ticket
              {
               for(uint i=0; i<tamanhoElement; i++)
                 {
                  newVolume+=Element[i][1];
                  Element[i][1]=0;
                 }
               newVolume=volume-newVolume;
               AddValue(deal_price,newVolume,double(ticket));
              }
           }
        }
     }
   PlotarRetangulos();
  }


7. Outro exemplo prático

Confiando que a descrição do algoritmo dada acima foi suficiente para se ter uma compreensão mais clara do funcionamento, passaremos a visualizar melhor tudo isso através desse exemplo, com as operações envolvidas e o conteúdo das variáveis mais importantes. Vamos considerar as operações sendo executadas fora do testador de estratégias - os eventos Trade serão detectados. Sabemos que numa conta Netting as transações de cada posição possuem o mesmo identificador, portanto podemos filtrar com base nesse critério. Com base nisso, trazemos na figura abaixo os eventos de uma determinada posição como exemplo:

 Time Symbol
 Deal Type
Direction
Volume
Price
2023.05.04  09:42:05
winm23
1352975
buy
in
1
104035
2023.05.04  09:43:16
winm23
1356370
sell
in/out
2
103900
2023.05.04  16:34:51
winm23
2193299
buy
out
1
103700
2023.05.04  16:35:05
winm23
2193395
buy
in
1
103690
2023.05.04  16:35:24
winm23
2193543
buy
in
1
103720
2023.05.04  16:55:00
winm23
2206914
sell
out
1
103470
2023.05.04  17:27:26
winm23
2214188
sell
in/out
2
103620
2023.05.04  17:30:21
winm23
2215738
buy
in/out
4
103675
2023.05.05  09:03:28
winm23
2229482
buy
in
1
104175
2023.05.05  09:12:27
winm23
2236503
sell
out
1
104005
2023.05.05  09:19:18
winm23
2246014
sell
out
1
103970
2023.05.05  09:22:45
winm23
2250253
buy
in
1
103950
2023.05.05  16:00:10
winm23
2854029
sell
out
1
106375
2023.05.05  16:15:40
winm23
2864767
sell
out
1
106275
2023.05.05  16:59:41
winm23
2884590
sell
out
1
106555

Independentemente de operações prévias, o array Element estará com tamanho zero nesse ponto em que também não há posições abertas. No horário 2023.05.04 09:42:05 ocorre uma transação de entrada do tipo compra (que já vai para o histórico da plataforma), 01 lote, acionando imediatamente a função OnTrade(). Considerando que o MetaTrader 5 foi iniciado no computador minutos antes (09:15h), houve tempo suficiente para atualização da variável date para 2023.05.04 09:15:00, e o valor foi mantido desde então. Em OnTrade(), será percorrida a lista de posições abertas, e no tipo de conta que estamos usando só é permitida uma posição por ativo: nesse caso WINM23. Variável numberOfPositions assume valor 1, e variável positionID recebe 1352975 - coincide com o ticket da primeira transação que por sua vez é o número do ticket da ordem que a originou. Variável date agora recebe o valor do tempo da transação, e as transações futuras até a de número 2193299 acabarão por receber esse mesmo tempo da função Identifier().

A função ListarOrdensPosicao(date) é acionada, e seleciona o período de 09:42:05 até TimeCurrent() para seleção do histórico. No laço de repetição, posto que se detecta o Entry de entrada "IN", a função AddValue() é acionada com os valores preço=104035, volume=1, e ticket=1352975. AddValue() não encontrará ticket no array vazio, e por isso inserirá a linha contendo os três valores passados. A função ArrayPrint(Element) mostrará a matriz com essa linha no terminal.

Em seguida é chamada a função PlotarRetangulos(), que armazenará os tempos do candle atual, e do 15º candle anterior, e esses serão os parâmetros de tamanho da linha a ser plotada. Com a função GetDigits(), é definido o número de dígitos do tamanho do tick do ativo (que no caso é zero), usado para compor a string que formará os nomes dos objetos em conjunto com os preços obtidos do array Element. Os objetos retângulo e texto são criados desde que correspondente volume de cada preço do array seja não-nulo, e desde que os objetos ainda não existam no gráfico. Se o objeto existir, então atributos como cor, texto, e posição são atualizados. Os retângulos na verdade funcionam como linhas na tela, posto que suas alturas são nulas. Optou-se por OBJ_RECTANGLE no início do projeto, pois se vislumbrou uma função que pudesse ser acionada na remoção do gráfico e que deletasse todos os objetos desse tipo. Tal mecanismo genérico de exclusão dos objetos não foi implementado, mas foi mantido o emprego dos retângulos de altura zero. Assim a linha contida no array referente à transação de compra aos 104035 é processada. Por ser o correspondente volume não nulo, e por ainda não existir o objeto de nome "104035text", são criados os objetos texto e retângulo associados.

No minuto seguinte, é efetuada uma transação de venda de 02 lotes. Como já estava posicionado com 01 lote na compra, configura uma reversão, ficando agora vendido em 01 lote. O MetaTrader também adicionará essa transação ao histórico imediatamente.  De maneira análoga, o fluxo se repete tal como no processamento da transação anterior, até chegar no loop de histórico de ordens. Novamente a transação de ticket=1352975 é retornada por estar no período selecionado, e é repassada para a função AddValue(). Esta, por sua vez, encontra o valor do ticket no único elemento presente até o momento no array e sai da função sem adicionar. O próximo item encontrado é do tipo " INOUT", e a única transação anterior já presente no array tem o elemento Element[0][1] armazenado em newVolume, em seguida definido para zero.

O volume de registro da transação é calculado (HistoryDealGetDouble(ticket,DEAL_PRICE) - newVolume, ou seja, newVolume = 2 - 1 = 1). Então a função AddValue(103900, 1, 135370), e creio que você já esteja compreendendo bem o mecanismo. Semelhantemente, a função PlotarRetangulos() executa outra vez o código, e o primeiro preço do array agora é o valor 103900 por ter sido feita a ordenação ascendente na função Sort(). Os objetos relacionados a esse preço não existem no gráfico, e por isso são criados. O segundo elemento do array, com preço 104035 já tem os objetos desenhados na tela, portanto tem seus atributos atualizados. O conteúdo de Element até o momento é { {103900,1,1356370},{104035,0,1352975 } }.

Seguindo para a terceira linha, tudo se processa de maneira similar, até que a transação do tipo saída é encontrada, com preço=103700, volume=1, e ticket=2193299. Transação de saída chama a função RemoveValue(), com os parâmetros recém encontrados. A função é encerrada se encontrar volume nulo, ou se encontrar no array uma linha com o mesmo ticket. Essas condições não são satisfeitas, e a RemoveValue() prossegue para a busca do preço a remover usando a função ArrayBsearch(). Ela é uma busca binária, e exige que o array esteja ordenado (conforme já atendido anteriormente pelo método Sort()). O índice mais próximo do preço 103700 é o primeiro do array. O volume dessa primeira linha também é um, e resulta sendo zerado, com o acionamento da função RemoverRetangulo() correspondente. Essa ação resulta no apagamento da tela dos objetos ligados ao preço 103900. Em seguida, a função AddValue() insere no array a linha {103700,0,219299}, que posteriormente não sofrerá modificação pela função Sort(). A posição foi encerrada.  O conteúdo de Element até o momento é {{103700,0,219299}, {103900,0,1356370},{104035,0,1352975 } }.

Com a posição totalmente zerada, a variável numberOfPositions é igual a zero, e a execução com sucesso de LimparRetângulos() insere na variável isClean o valor true. O array é definido para zero elementos, e a variável date receberá o tempo corrente. Isso faz com que não existam ordens a serem retornadas no novo período definido. O sistema fica aguardando uma nova transação para continuar a alimentar o array e processar as ações subsequentes.  O conteúdo de Element se tornou: { }. Voltamos para a situação semelhante à descrita no início deste exemplo, e o mesmo raciocínio pode ser empregado para a compreensão do funcionamento do indicador diante das transações seguintes. É no preço 103690 do exemplo atual que começa a operação mencionada no item "5. Exemplo Prático de Utilização". Seguindo passo-a-passo as operações, será possível compreender o questionamento lançado naquele primeiro exemplo. Mas adianto que a explicação está ligada aos preços das transações de saída, e à forma que o algoritmo emprega para apagar sequencialmente as linhas cujos preços são mais próximos aos das transações do tipo "DEAL_ENTRY_OUT".


8. Integração com um Expert Advisors 

Existem duas maneiras para se utilizar indicadores customizados tais como esse no testador de estratégias. A primeira delas seria a compilação de um EA ou mesmo outro indicador que acionasse o nosso indicador customizado. Bastaria garantir a presença do arquivo compilado "Plotagem de Entradas Parciais.ex5" dentro da pasta "Indicators", e inserir as linhas de código abaixo na função OnInit() do programa acionador, por assim dizer. Lembrando de antes declarar a variável global "handlePlotagemEntradasParciais" do tipo int:

   iCustom(_Symbol,PERIOD_CURRENT,"Plotagem de Entradas Parciais");
//--- se o manipulador não é criado
   if(handlePlotagemEntradasParciais ==INVALID_HANDLE)
     {
      //--- mensagem sobre a falha e a saída do código de erro
      PrintFormat("Falha ao criar o manipulador do indicador para o símbolo %s/%s, código de erro %d",
                  _Symbol,
                  EnumToString(_Period),
                  GetLastError());
      //--- o indicador é interrompido precocemente
      return(INIT_FAILED);
     }

A outra forma evita a edição dessas linhas no EA, e pode ser empregada preferencialmente para realizar os testes, dada a sua simplicidade. Consiste em carregar o indicador no gráfico da forma convencional, e salvando o template com o nome Tester.tpl (pode sobrescrever, caso já existe outro com mesmo nome). Assim, a cada teste do seu EA, o indicador também será carregado. Lembrando que só faz sentido essa testagem no "modo visual com exibição de gráficos" ativado.


9. Conclusão

O indicador de Plotagem de Entradas Parciais foi criado para explorar novas possibilidades de criar e utilizar indicadores na linguagem MQL5, em uma das plataformas de negociação mais modernas e avançadas que é o MetaTrader 5. 


Inferência causal em problemas de classificação de séries temporais Inferência causal em problemas de classificação de séries temporais
Neste artigo, examinaremos a teoria da inferência causal usando aprendizado de máquina, bem como a implementação de uma abordagem personalizada em Python. A inferência causal e o pensamento causal têm suas raízes na filosofia e psicologia e desempenham um papel importante na nossa compreensão da realidade.
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 11): Paredes numéricas Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 11): Paredes numéricas
As paredes numéricas (Number Walls) são uma variante do registrador de deslocamento com realimentação linear (Linear Shift Back Registers), que avalia previamente sequências para previsibilidade verificando a convergência. Vamos ver como essas ideias podem ser usadas no MQL5.
Ciência de Dados e Aprendizado de Máquina (Parte 19): Supercharge Seus Modelos de IA com AdaBoost Ciência de Dados e Aprendizado de Máquina (Parte 19): Supercharge Seus Modelos de IA com AdaBoost
AdaBoost, um poderoso algoritmo de boosting projetado para elevar o desempenho dos seus modelos de IA. AdaBoost, abreviação de Adaptive Boosting, é uma técnica sofisticada de aprendizado em conjunto que integra perfeitamente aprendizes fracos, aprimorando sua força preditiva coletiva.
Redes neurais de maneira fácil (Parte 77): Cross-Covariance Transformer (XCiT) Redes neurais de maneira fácil (Parte 77): Cross-Covariance Transformer (XCiT)
Em nossos modelos, frequentemente usamos vários algoritmos de atenção. E, provavelmente, usamos Transformadores com mais frequência. A principal desvantagem deles é a exigência de recursos. Neste artigo, quero apresentar um algoritmo que ajuda a reduzir os custos computacionais sem perda de qualidade.