English Русский Deutsch 日本語
preview
Análise quantitativa no MQL5: implementando um algoritmo promissor

Análise quantitativa no MQL5: implementando um algoritmo promissor

MetaTrader 5Sistemas de negociação | 17 maio 2024, 10:05
153 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

O que é análise quantitativa no mercado financeiro

O que é análise quantitativa no mercado financeiro? A análise quantitativa surgiu como uma espécie de precursor do aprendizado de máquina, sendo, na verdade, um subcampo do aprendizado estatístico. Ainda na época em que os computadores estavam apenas surgindo, ocupavam uma sala inteira e funcionavam com cartões perfurados, mentes inovadoras já tentavam adaptá-los para a análise de grandes volumes de dados e estatísticas. Sim, naquela época, o conjunto de operações e funções estatísticas que podiam ser aplicadas aos preços era extremamente limitado, as próprias funções eram bastante simples e os padrões encontrados não eram complexos.

Essas pesquisas se resumiam a cálculos muito básicos para identificar certas relações nos dados, principalmente lineares.

O método mais simples e fácil de dominar para a análise quantitativa nos mercados financeiros é a análise do spread entre ativos correlacionados. Por exemplo, podemos calcular o spread entre dois ativos correlacionados e, com a análise quantitativa, identificar o desvio médio, máximo e mediano desse spread. Obtendo uma característica quantitativa dos dados, já podemos entender o quanto um ativo se desviou do outro e aproximadamente compreender o estado de equilíbrio dos dois ativos, para onde ambos certamente retornarão quando a divergência entre eles se "fechar" (ou seja, quando os ativos se moverem em direção um ao outro). No geral, a aplicação da análise quantitativa no trading de pares é um tópico muito interessante, que ainda abordaremos em um dos próximos artigos. 


Como os hedge funds utilizam a análise quantitativa

A primeira tentativa de utilizar a análise quantitativa foi realizada por Edward Thorp, que na década de 1970 aprendeu a analisar o spread entre uma ação e um warrant (garantia) dessa ação, calculando o quanto o ativo estava supervalorizado ou subvalorizado em relação ao seu warrant. O computador de Edward Thorp, naquela época, ocupava uma sala inteira e ainda funcionava com cartões perfurados. Thorp, de modo geral, foi o primeiro a aplicar a análise quantitativa computacional aos mercados financeiros. Isso foi uma revolução na época, digna de reconhecimento mundial. Thorp criou o primeiro hedge fund "quantitativo" do mundo. 

Como você pode perceber, o primeiro exemplo de análise quantitativa no mercado de ações que nos vem à mente é sua aplicação no trading de pares ou no trading de cestas. Ainda vamos analisar essas opções, mas o nosso algoritmo de análise quantitativa de hoje será baseado em outros princípios.

Como os grandes participantes do mercado também utilizam a análise quantitativa? 

A arbitragem estatística permite detectar diferenças nos preços de instrumentos financeiros em diferentes mercados ou em momentos distintos. Isso permite que os fundos identifiquem e aproveitem oportunidades para negociações lucrativas em diversos mercados correlacionados. Além disso, os modelos quantitativos ajudam os hedge funds, ou fundos de cobertura, a prever movimentos futuros do mercado com base em dados estatísticos, o que contribui para a tomada de decisões de negociação bem fundamentadas.

Gerenciamento de riscos é outra área superimportante de aplicação da análise quantitativa. Os Hedge funds utilizam modelos para avaliar e gerenciar riscos em seus portfólios. Eles otimizam a estrutura dos ativos levando em consideração o risco, para minimizar potenciais perdas. Aqui podemos citar muitos exemplos, como a otimização de portfólios pela teoria de portfólios de Markowitz (nesse caso, baseando-se no risco para que a variação do portfólio não exceda o lucro potencial) e o gerenciamento de risco pelo sistema VaR, um modelo único que permite calcular o rebaixamento que não será ultrapassado com 99% de certeza.

Claro, o mercado real às vezes é bastante difícil de descrever com matemática – também há exemplos negativos. Por exemplo, em 1998, o hedge fund LTCM calculou que suas posições não trariam grandes perdas e entrou no jogo de estreitamento do spread entre os títulos de longo e curto prazo dos EUA com base na análise quantitativa. Ocorreu o default da Rússia, que desencadeou a "crise dos tigres asiáticos", resultando, pelo efeito borboleta, em pânico no mercado de títulos do governo dos EUA. Os modelos indicavam ao fundo LTCM que o spread estava anormalmente alto, que o preço certamente "recuaria" na direção oposta e as posições do fundo seriam fechadas com lucro.

Como resultado, o fundo aplicou a média, tomou uma posição extremamente agressiva com alavancagem de cem vezes, acumulando ativos em dívida, e simplesmente quebrou, apesar de os laureados com o Nobel na equipe da empresa afirmarem que tal resultado era impossível. Então, um modelo de análise quantitativa VaR quase destruiu todo o mercado dos EUA, forçando o presidente do Federal Reserve, Alan Greenspan, a convocar urgentemente os diretores dos maiores bancos dos EUA para que comprassem as posições de margem do fundo, caso contrário, a venda de um volume tão grande de ativos "no mercado" causaria o colapso instantâneo do mercado de ações dos EUA e uma pânico pior que a Grande Depressão.

Por isso, ao aplicar a análise quantitativa e a média de alguns indicadores, é importante lembrar dos extremos da distribuição normal de probabilidades. A curva em forma de sino de probabilidades no caso dos mercados financeiros possui "caudas" que refletem eventos de vários tipos de cisnes negros. Por um lado, eles são estatisticamente altamente improváveis, mas, por outro, sua magnitude e força, a força desses eventos, destroem portfólios de investidores, portfólios de hedge funds, liquidam posições de margem, derrubam mercados e os transformam a cada novo ciclo. Vimos isso em 1998, vimos em 2008, vimos em 2020, em 2022 e ainda veremos muitas vezes.

No final, a análise quantitativa oferece muito aos hedge funds, ou fundos de cobertura, e é constantemente utilizada em suas operações diárias. Mas é importante lembrar que nenhuma função jamais calculará as decisões de milhões de pessoas, sua pânico e reação a determinados eventos. Também é importante estar ciente das caudas da distribuição normal, que podem zerar o saldo ao usar táticas de negociação agressivas. 


Base do algoritmo: calculando ondas de movimento

A base da nossa ideia foi primeiramente proposta pelo trader Artem Zvezdin: ele considera a magnitude das ondas de movimento do preço em sua prática para entender o quanto um ativo está supervalorizado ou subvalorizado em relação a si mesmo. Por exemplo, calculamos as ondas de alta e de baixa dos últimos 500-5000 períodos para entender quanto o preço se movimentou em cada pequeno ciclo. Cada ciclo de movimento de preço representa as posições de alguém, o dinheiro de alguém, decisões de compra e venda. Cada novo ciclo é um novo nascimento e morte do mercado, e vamos usar a ideia de análise de movimentos de preços sem retrocessos, do topo até o fundo. Este é um conjunto distinto de participantes que agem de forma mais ou menos igual, então supomos que a duração dos ciclos será sempre mais ou menos a mesma. Calcularemos o movimento médio do preço usando o indicador ZigZag, que vem de fábrica em qualquer terminal MetaTrader 5.

Então, vamos examinar o Expert Advisor que criei para este artigo. Primeiro, vejamos a parte principal do EA. As configurações, em geral, não devem causar dúvidas. Para negociação, usamos a biblioteca padrão Trade. Nas configurações de tamanho de lote, pode-se especificar ou o lote fixo ou o risco calculado com base no saldo. Se especificarmos lucro de fechamento maior que 0, o EA fechará as negociações com base no lucro total. O stop e o take profit são calculados com base no ATR, ou seja, eles "dançam" de acordo com a volatilidade atual do instrumento. As configurações do ZigZag para os cálculos do EA são basicamente padrão, então não vamos nos deter nelas. Gostaria de destacar que nosso EA é multimoeda, capaz de operar em vários ativos. Isso é necessário para reduzir o risco geral através do trading de cestas de ativos correlacionados em versões futuras do EA. A versão atual 0.90 operará apenas em um símbolo.

//+------------------------------------------------------------------+
//|                                          QuantAnalysisSample.mq5 |
//|                                                   Copyright 2023 |
//|                                                Evgeniy Koshtenko |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2023, Evgeniy Koshtenko"
#property link        "https://www.mql5.com"
#property version     "0.90"
#property strict

#include <Trade\Trade.mqh>
#include <Graphics\Graphic.mqh>
#include <Math\Stat\Normal.mqh>
#include <Math\Stat\Math.mqh>
CTrade trade;
//--- Входы
input double Lots       = 0.1;      // лот
input double Risk       = 0.1;     // риск
input double Profit     = 0;     // профит
input int StopLoss      = 0;        // стоп ATR
input int TakeProfit    = 0;        // тейк ATR
input string Symbol1    = "EURUSD";
input int    Magic      = 777;    // магик
//--- iВходные параметры
input uint   InpDepth       =  120;   // ZigZag Depth
input uint   InpDeviation   =  50;    // ZigZag Deviation
input uint   InpBackstep    =  30;    // ZigZag Backstep
input uchar  InpPivotPoint  =  1;    // ZigZag pivot point
datetime t=0;
double last=0;
double countMovements;
double currentMovement;
// Глобальная переменная для хранения дескриптора индикатора
int zigzagHandle;

Agora vamos examinar as demais funções do EA. As funções de inicialização e desinicialização são simples e fáceis de entender: carregam o número mágico do EA, um identificador único que permitirá ao EA distinguir suas ordens das de outros. Carregamos o handle em uma função adicional personalizada, pois se carregarmos o handle multimoeda diretamente pelo OnInit, o EA gerará um erro. É por isso que aplicamos essa solução simples e fácil.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert initialization function custom                            |
//+------------------------------------------------------------------+
int OnIniti(string symb)
  {// Загрузка индикатора ZigZag
   zigzagHandle = iCustom(symb, _Period, "ZigZag", InpDepth, InpDeviation, InpBackstep, InpPivotPoint);
   if (zigzagHandle == INVALID_HANDLE)
     {
      Print("Ошибка при загрузке индикатора ZigZag: ", GetLastError());
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }

Vamos examinar outras funções do EA. Vamos descer no código.  A seguir, temos funções de cálculo do lucro total de todas as posições e a função de fechamento de todas as ordens por completo:

//+------------------------------------------------------------------+
//|  Position Profit                                                 |
//+------------------------------------------------------------------+
double AllProfit(int type=-1)
  {
   double p=0;

    for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetInteger(POSITION_MAGIC)==Magic)
           {
            if(PositionGetInteger(POSITION_TYPE)==type || type==-1)
               p+=PositionGetDouble(POSITION_PROFIT);
           }
        }
     }

   return(p);
  }
//+------------------------------------------------------------------+
//|   CloseAll                                                       |
//+------------------------------------------------------------------+
void CloseAll(int type=-1)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetInteger(POSITION_MAGIC)==Magic)
           {
            if(PositionGetInteger(POSITION_TYPE)==type || type==-1)
               trade.PositionClose(PositionGetTicket(i));
           }
        }
     }
  }

Em seguida, temos a função de cálculo do lote e a função de cálculo da quantidade de posições abertas:

//+------------------------------------------------------------------+
//|     CountTrades                                                  |
//+------------------------------------------------------------------+
int CountTrades(string symb)
  {
   int count=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            count++;
           }
        }
     }
   return(count);
  }
//+------------------------------------------------------------------+
//|     Lot                                                          |
//+------------------------------------------------------------------+  
double Lot()
  {
   double lot=Lots;

   if(Risk>0)
      lot=AccountInfoDouble(ACCOUNT_BALANCE)*Risk/100000;

   return(NormalizeDouble(lot,2));
  }

Mencionamos também as funções de cálculo do último preço de negociação para compras e vendas (que usaremos mais tarde) e a função de determinação da direção da posição.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double FindLastBuyPrice(string symb)
  {
   double pr=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==0)
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            pr=PositionGetDouble(POSITION_PRICE_OPEN);
            break;
           }
        }
     }
   return(pr);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double FindLastSellPrice(string symb)
  {
   double pr=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==1)
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            pr=PositionGetDouble(POSITION_PRICE_OPEN);
            break;
           }
        }
     }
   return(pr);
  }
//+------------------------------------------------------------------+
//|  PositionType                                                    |
//+------------------------------------------------------------------+
int PositionType(string symb)
  {
   int type=8;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            type=(int)PositionGetInteger(POSITION_TYPE);
            break;
           }
        }
     }
   return(type);
  }

E, claro, nossa função principal - a função de cálculo do movimento médio e atual. Ela é calculada não em pontos, mas na magnitude do movimento da unidade de preço para maior conveniência. É simples: chamamos nossa "inicialização" personalizada, copiamos os buffers e no loop for calculamos a magnitude do movimento do preço do topo do ZigZag até seu último extremo. A saída da função fornece o movimento atual em unidades de movimento de preço e o movimento médio. 

//+------------------------------------------------------------------+
//|     CalculateAverageMovement                                     |
//+------------------------------------------------------------------+ 
void CalculateAverageMovement(string symb, double &averageMovement, double &currentMovement) {
    const int lookback = 500; // Количество баров для анализа
    double sumMovements = 0.0;
    int countMovements = 0;
    double lastExtremePrice = 0.0;
    double zigzagArray[500]; // Массив для хранения значений ZigZag
    OnIniti(symb);
    // Копируем значения ZigZag в массив
    if (CopyBuffer(zigzagHandle, 0, 0, lookback, zigzagArray) <= 0) {
        Print("Ошибка при копировании данных индикатора");
        averageMovement = -1;
        currentMovement = -1;
        return;
    }

    // Копируем значения ZigZag в массив
    if (CopyBuffer(zigzagHandle, 0, 0, lookback, zigzagArray) <= 0) {
        Print("Ошибка при копировании данных индикатора");
        averageMovement = -1;
        currentMovement = -1;
        return;
    }

    for (int i = 0; i < lookback; i++) {
        if (zigzagArray[i] != 0 && zigzagArray[i] != lastExtremePrice) {
            if (lastExtremePrice != 0) {
                // Определяем направление движения
                double movement = zigzagArray[i] - lastExtremePrice;
                sumMovements += movement;
                countMovements++;
            }
            lastExtremePrice = zigzagArray[i];
        }
    }

    // Вычисляем текущее движение
    double lastMovement = iClose(symb, _Period, 0) - lastExtremePrice;
    currentMovement = lastMovement;

    // Вычисляем среднее движение
    averageMovement = countMovements > 0 ? sumMovements / countMovements : 0.0;

    // Выводим результаты
    Print("Среднее движение: ", averageMovement);
    Print("Текущее движение: ", currentMovement);

    // Освобождаем ресурсы
    IndicatorRelease(zigzagHandle);
}

Outra de nossas funções principais é a função de negociação multimoeda com base nos sinais de exceder o movimento atual do preço em relação ao seu valor médio. Nossos take profits e stops são baseados no ATR, e o passo da grade (média) também está em valores de ATR. As negociações são abertas em novos barras. Isso é importante para nós, para que o Expert Advisor "não pinte como Picasso" nos testes no testador. Esta função é então chamada em OnTick e opera em um ou mais símbolos. Ainda não consegui executar com sucesso em vários símbolos, como já mencionei, usarei apenas um símbolo, que deve ser especificado nas configurações do Expert Advisor. 

//+------------------------------------------------------------------+
//| Expert Trade unction                                             |
//+------------------------------------------------------------------+
void Trade(string symb)
  {
   double averageMovement = 0;
   double currentMovement = 0;
   double pr=0,sl=0,tp=0,hi=0,lo=0;
// Вызов функции для расчета
   CalculateAverageMovement(symb, averageMovement, currentMovement);

// Использование результатов
   double Ask = SymbolInfoDouble(symb, SYMBOL_ASK);
   double Bid = SymbolInfoDouble(symb, SYMBOL_BID);
   int dg=(int)SymbolInfoInteger(symb,SYMBOL_DIGITS);
   double pp=SymbolInfoDouble(symb,SYMBOL_POINT);
  
   double atr = iATR(symb, PERIOD_CURRENT, 3);
     
// Здесь определите вашу логику для покупки и продажи
   bool sell  = currentMovement > -averageMovement; // условие для покупки
   bool buy = -currentMovement > averageMovement; // условие для продажи
  
   if(AllProfit()>Profit && Profit>0)
      CloseAll();

   if(t!=iTime(symb,PERIOD_CURRENT,0))
     {
      if(buy && CountTrades(symb)<1)
        {
         if(StopLoss>0)
            sl=NormalizeDouble(Bid-(atr*StopLoss)*Point(),_Digits);
         if(TakeProfit>0)
            tp=NormalizeDouble(Bid+(atr*TakeProfit)*Point(),_Digits);
         pr=NormalizeDouble(Bid,dg);
         trade.Buy(Lot(),symb,pr,sl,tp,"");
         last=pr;
        }
      if(sell && CountTrades(symb)<1)
        {
         if(StopLoss>0)
            sl=NormalizeDouble(Ask+(atr*StopLoss)*Point(),_Digits);
         if(TakeProfit>0)
            tp=NormalizeDouble(Ask-(atr*TakeProfit)*Point(),_Digits);
         pr=NormalizeDouble(Ask,dg);
         trade.Sell(Lot(),symb,Ask,sl,tp,"");
         last=pr;
        }
      if(CountTrades(symb)>0)
        {
         if(PositionType(symb)==0 && (FindLastBuyPrice(symb)-Ask)/pp>=atr*30)
           {
            if(StopLoss>0)
               sl=NormalizeDouble(Bid-(atr*StopLoss)*Point(),_Digits);
            if(TakeProfit>0)
               tp=NormalizeDouble(Bid+(atr*TakeProfit)*Point(),_Digits);
            trade.Buy(Lot(),symb,Ask,sl,tp);
           }
         if(PositionType(symb)==1 && (Bid-FindLastSellPrice(symb))/pp>=atr*30)
           {
            if(StopLoss>0)
               sl=NormalizeDouble(Ask+(atr*StopLoss)*Point(),_Digits);
            if(TakeProfit>0)
               tp=NormalizeDouble(Ask-(atr*TakeProfit)*Point(),_Digits);
            trade.Sell(Lot(),symb,Bid,sl,tp);
           }
        }
      t=iTime(symb,0,0);
     }
  }


Teste do modelo no MQL5

Então, chegou a hora da parte mais interessante, que é testar nosso modelo no mercado real. Devo dizer que o cálculo através de um loop sobrecarrega bastante o processador, por isso é mais sensato rodar o EA apenas nos preços de abertura. Vamos rodar um teste único no EUR/USD com preços de abertura no timeframe H1, de 1º de janeiro de 2020 até hoje, 6 de dezembro de 2023:


O teste único é lucrativo, mas a retração é alta. Assumir risco adicional ao negociar não interessa a muitos. Mas lembramos que temos o fechamento por lucro. E podemos fazer o teste em uma conta Netting.

Para rodar o teste com fechamento por lucro, configuramos o fechamento por lucro maior que 0. Vamos testar. Talvez obtenhamos um teste estável. Rodamos o EA no mesmo ativo com preços de abertura. O tipo de conta é Hedging. E o que vemos:


O EA é extremamente arriscado, e a culpa é da média. Vamos rodar o mesmo teste em uma conta Netting.


Novamente, uma grande retração, o lucro não justifica o risco. Vamos modificar o código novamente. Desta vez, vamos implementar o fechamento por sinal (quando o sinal de alta muda para baixa, as posições anteriores serão fechadas). Implementamos o fechamento por lucro com o seguinte código:

if (CloseSig)
   {
      if (buy)
         CloseAll(1);
      if (sell)
         CloseAll(0);
   }

E esta configuração:

input bool CloseSig     = 1;        // закрытие по сигналу

Realizamos o teste, e o resultado é novamente decepcionante:


No geral, o teste não pode ser chamado de ideal. A retração é enorme, há grandes rebaixamentos tanto em contas Netting quanto em contas Hedging. E o fechamento por sinal não traz absolutamente nenhum resultado e não é lucrativo. Isso é desanimador.


Considerações finais

Então, revisamos um exemplo simples de como criar um algoritmo básico de análise quantitativa no MQL5. Calculamos as ondas de movimento do preço, comparamos com os valores médios e, com base nesses dados, tomamos decisões de compra ou venda. Claro, no final, obtivemos um algoritmo negativo, mas a base da ideia era bastante boa. Em artigos futuros, continuaremos nossas pesquisas de análise quantitativa.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/13835

Arquivos anexados |
Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 5): Bandas de Bollinger no canal de Keltner — Sinais dos indicadores Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 5): Bandas de Bollinger no canal de Keltner — Sinais dos indicadores
Neste artigo, por EA multimoeda, entendemos um robô investidor, que pode negociar (abrir/fechar ordens, gerenciar ordens, por exemplo, do tipo trailing stop-loss e trailing profit) mais de um par de moedas em um gráfico. Neste artigo, utilizaremos sinais de dois indicadores, nomeadamente Bandas de Bollinger (Bollinger Bands®) e canal de Keltner.
Redes neurais de maneira fácil (Parte 67): Aprendendo com experiências passadas para resolver novos problemas Redes neurais de maneira fácil (Parte 67): Aprendendo com experiências passadas para resolver novos problemas
Neste artigo, continuaremos a falar sobre métodos de coleta de dados em uma amostra de treinamento. É claro que o processo de aprendizado requer constante interação com o ambiente. Mas as situações podem variar.
Rede neural na prática: Função de reta Rede neural na prática: Função de reta
Neste artigo, vamos passar rapidamente, por alguns métodos para conseguir a função que poderá representar os nossos dados no banco. Não irei me aprofundar em detalhes relacionados ao como usar estatísticas e estudos de probabilidade para interpretar os resultados. Deixo isto como dever de casa, para cada um que realmente deseja se aprofundar, na parte matemática da coisa. De qualquer forma, estudar tais coisas será primordial para que você de fato consiga compreender tudo que envolve estudos de redes neurais. Aqui irei pegar bem leve no tema.
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)!"