Algoritmo do Átomo Artificial — Artificial Atom Algorithm (A3)
Conteúdo
Introdução
No trading algorítmico, uma das tarefas mais importantes é a otimização dos parâmetros das estratégias de negociação. Os traders enfrentam diariamente a necessidade de ajustar inúmeras variáveis: períodos de indicadores, níveis de stop loss e take profit, tamanhos de posição, filtros temporais e dezenas de outros parâmetros. Cada combinação desses parâmetros pode alterar radicalmente o desempenho da estratégia, e seguimos buscando algoritmos eficientes capazes de encontrar a melhor solução em um tempo aceitável.
Neste artigo, analisaremos mais um algoritmo de otimização, o Artificial Atom Algorithm (A3), um algoritmo metaheurístico de otimização inspirado em reações químicas. Ele foi desenvolvido por pesquisadores turcos e apresentado pela primeira vez à comunidade científica em 2018.
Implementação do algoritmo
Durante o desenvolvimento deste algoritmo, deparei-me com várias ambiguidades na descrição fornecida pelos autores. Alguns operadores são apresentados com um nível insuficiente de detalhamento, o que dificulta sua implementação prática. Por esse motivo, apresentarei a descrição original do algoritmo, mas sua implementação foi feita com base em meus próprios critérios, com apoio na experiência prática e nos princípios estruturais típicos de algoritmos semelhantes. Isso representa uma lacuna significativa no trabalho dos autores, deixando ampla margem para interpretação ao desenvolvedor. Além disso, durante o desenvolvimento foram feitas modificações na classe base, que serão analisadas após a apresentação da implementação principal do algoritmo.
O algoritmo modela a interação entre átomos e elétrons para encontrar a solução ótima. Os componentes principais são os átomos, que representam soluções potenciais para o problema. Os elétrons representam as variáveis da solução. A ligação covalente é um operador destinado à preservação e replicação das melhores soluções, enquanto a ligação iônica é um operador voltado à exploração do espaço de busca e ao alcance do ótimo global.
Inicialmente, o algoritmo gera aleatoriamente um conjunto de átomos, avalia a qualidade de cada um por meio da função objetivo e, em seguida, aplica os operadores de ligação covalente e iônica para aprimorar as soluções (infelizmente, não há uma descrição de como isso é feito). Depois disso, é necessário avaliar o efeito dos elétrons, novamente sem que fique claro de que maneira isso deve ser feito. Em seguida, deve-se classificar os elétrons e os átomos. Tudo bem, podemos ordenar os átomos (as soluções), mas como ordenar elétrons continua sendo um mistério (é como tentar ordenar os membros de uma pessoa, braços e pernas, direitos e esquerdos, mas não importa, vamos ordenar). O procedimento deve ser repetido iterativamente até que o critério de parada seja atingido. Como diz o ditado, não dá para mudar o que está escrito; resta tentar reconstruir o algoritmo a partir do que existe, ou seja, das ideias propostas pelos autores.
Apresento a versão final do pseudocódigo do algoritmo A3 que reformulei.
INICIALIZAÇÃO Parâmetros de entrada:
popSize: número de átomos (por padrão, 10)
covalentRate: coeficiente de ligação covalente (por padrão, 0.1)
rangeMin [], rangeMax []: limites do espaço de busca para cada variável
rangeStep []: passo de discretização
Cálculos preliminares:
Calcular o número de átomos de elite:
covalentCount = floor (popSize × covalentRate)
Restringir a: mínimo 1, máximo (popSize - 1)
Criar uma população de popSize átomos
Para cada átomo, inicializar:
Coordenadas c []
Pior solução local cW []
Fitness f, pior fitness fW
CICLO PRINCIPAL DE OTIMIZAÇÃO
PASSO 1: Primeira iteração (se revision = false)
PARA cada átomo i de 0 até popSize-1:
PARA cada coordenada j de 0 até coords-1:
Gerar um valor aleatório no intervalo [rangeMin [j], rangeMax [j]]
Aplicar a discretização conforme rangeStep [j]
Salvar em a [i].c [j]
Definir revision = true
Encerrar a iteração
PASSO 2: Atualização das posições dos átomos (Moving)
PARA cada átomo i de 0 até popSize-1:
SE i ≤ covalentCount (o átomo é de elite):
PARA cada coordenada c de 0 até coords-1:
SE random() < covalentRate (com probabilidade de 10%):
// Movimento em direção à melhor solução global
step = random() × (cB [c] - a [i].c [c]) × covalentCount
a [i].c [c] = a [i].c [c] + step
CASO CONTRÁRIO (com probabilidade de 90%):
// Geração via PowerDistribution em torno do melhor
a[i]. c[c] = PowerDistribution(centro=cB [c], min=rangeMin [c], max=rangeMax [c], expoente=20)
Aplicar a discretização a a [i].c [c]
CASO CONTRÁRIO (o átomo não é de elite):
PARA cada coordenada c de 0 até coords-1:
// Selecionar um átomo de elite aleatório como amostra
ind = inteiro_aleatório(0, covalentCount)
// Movimento da pior solução local para a posição do átomo de elite
direction = a [ind].c [c] - a [i].cW [c]
step = random() × direction × (1.0 - covalentCount)
a [i].c [c] = a [i].c [c] + step
Aplicar a discretização a a [i].c [c]
PASSO 3: Cálculo do fitness
PARA cada átomo i:
Calcular o fitness a [i].f por meio da função objetivo
PASSO 4: Atualização da memória e ordenação (Revision)
// Atualização das piores soluções locais
PARA cada átomo i de 0 até popSize-1:
SE a [i].f < a [i].fW:
a [i].fW = a [i].f
Copiar a [i].c para a [i].cW
Atualizar o pior global fW
// Ordenação da população por fitness em ordem decrescente
Ordenar o array de átomos a [] com base no campo f (do melhor para o pior)
// Atualização do melhor global
SE a [0].f > fB:
fB = a [0].f
Copiar a [0].c para cB
// Atualização do pior global
SE a [popSize-1].f < fW:
fW = a [popSize-1].f
Copiar a [popSize-1].c para cW
PASSO 5: Verificação do critério de parada
SE o número máximo de iterações foi atingido:
Encerrar o algoritmo
CASO CONTRÁRIO:
Ir para o PASSO 2
Agora podemos passar à implementação prática em código. Escreveremos uma classe que implementará o algoritmo do sistema atômico artificial, baseado na metáfora dos átomos e de suas ligações. Essa classe herda as funcionalidades básicas da classe "C_AO" e as estende no contexto do algoritmo A3.
Métodos de configuração dos parâmetros: a função "SetParams ()" lê os valores atuais do array de parâmetros e os salva nas variáveis correspondentes. Isso proporciona flexibilidade na configuração antes de iniciar o algoritmo. Inicialização: o método "Init ()" prepara os parâmetros e a estrutura para o início da execução, recebendo os intervalos de valores e os passos de alteração das variáveis, bem como o número de épocas. Operações principais do algoritmo: os métodos Moving () e Revision () implementam as rotinas de movimentação e Revision dos átomos dentro do sistema, correspondendo às etapas de busca da solução ótima. Parâmetros e variáveis:
- popSize: tamanho da população de átomos (número de elementos no conjunto).
- covalentRate: coeficiente associado às ligações entre os átomos.
- covalentCount: número interno de átomos que participam da formação das ligações (usado dentro da classe para os cálculos).
A finalidade geral da classe é modelar o comportamento de um sistema de átomos levando em conta suas ligações. Isso é implementado por meio de parâmetros e métodos de funcionamento, sobre os quais se constrói a lógica de busca e otimização.
//———————————————————————————————————————————————————————————————————— class C_AO_A3 : public C_AO { public: //---------------------------------------------------------- ~C_AO_A3 () { } C_AO_A3 () { ao_name = "A3"; ao_desc = "Artificial Atom Algorithm"; ao_link = "https://www.mql5.com/ru/articles/18958"; popSize = 10; // количество атомов (m) covalentRate = 0.1; // коэффициент ковалентной связи (β) ArrayResize (params, 2); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "covalentRate"; params [1].val = covalentRate; } void SetParams () { popSize = (int)params [0].val; covalentRate = params [1].val; } bool Init (const double &rangeMinP [], // минимальные значения const double &rangeMaxP [], // максимальные значения const double &rangeStepP [], // шаг изменения const int epochsP = 0); // количество эпох void Moving (); void Revision (); //------------------------------------------------------------------ double covalentRate; // коэффициент ковалентной связи (β) private: //--------------------------------------------------------- int covalentCount; // количество атомов для ковалентной связи }; //————————————————————————————————————————————————————————————————————
O método "Init ()" é uma etapa essencial na preparação do algoritmo para a execução. Sua principal tarefa é inicializar todos os parâmetros e estruturas necessários antes do início da otimização. O método recebe quatro parâmetros de entrada:
- rangeMinP []: valores mínimos para cada variável que o algoritmo irá otimizar.
- rangeMaxP []: valores máximos para cada variável.
- rangeStepP []: passo de variação para cada variável. Esses passos são usados para a discretização e para definir a precisão da busca.
- epochsP: número de épocas (iterações) de execução do algoritmo.
Primeiro, é chamado "StandardInit ()", método da classe base (C_AO), que executa as ações de inicialização comuns a todos os algoritmos de otimização. Se "StandardInit ()" for concluído com sucesso, o método passa aos cálculos dos parâmetros específicos do algoritmo do átomo artificial. Em particular, é calculado "covalentCount", a número de átomos que participarão das ligações covalentes. O cálculo é feito como o produto de "popSize" (quantidade total de átomos na população) por "covalentRate" (coeficiente de ligação covalente). O resultado é arredondado para baixo até o inteiro mais próximo com "MathFloor", pois a quantidade de átomos deve ser um número inteiro. Em seguida, são aplicadas verificações para garantir um valor logicamente correto de "covalentCount". Caso todas as etapas de inicialização sejam concluídas com sucesso, o método retorna "true", sinalizando que o algoritmo está pronto para ser iniciado.
Assim, o método "Init" deixa o algoritmo do átomo artificial completamente pronto para a execução, inicializando tanto os parâmetros comuns aos algoritmos de otimização quanto os parâmetros específicos deste modelo baseado em "átomos" e "ligações".
/———————————————————————————————————————————————————————————————————— //--- Инициализация bool C_AO_A3::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ covalentCount = (int)MathFloor (popSize * covalentRate); if (covalentCount < 1) covalentCount = 1; if (covalentCount >= popSize) covalentCount = popSize - 1; return true; } //————————————————————————————————————————————————————————————————————
O método "Moving ()" na classe "C_AO_A3" é uma das principais etapas do algoritmo do átomo artificial, implementando o "movimento", ou seja, a alteração da posição dos átomos no espaço de busca. Ele modela mudanças evolutivas na população de átomos voltadas à busca da solução ótima. No início de cada chamada do método "Moving ()", é feita uma verificação do estado de "revision". Esse sinalizador indica se a população já foi inicializada anteriormente.
A inicialização de partida (se "revision" for igual a "false") significa que o método "Moving ()" está sendo chamado pela primeira vez ou após a redefinição do estado, e a população de átomos ainda não foi inicializada. Nesse caso, ocorre o seguinte: um laço percorre todos os átomos, conforme "popSize", e todas as coordenadas, conforme "coords". Para cada átomo e para cada coordenada, sua posição é inicializada com um valor aleatório dentro do intervalo definido. Após a inicialização feita na primeira chamada, "revision" é definido como "true", para que, nas chamadas seguintes de "Moving ()", seja executada a rotina principal de movimentação, e não uma nova inicialização. O método é encerrado, pois a tarefa da primeira chamada foi concluída.
Na etapa principal de movimentação dos átomos (se "revision" for igual a "true"), quando a população já está inicializada, o método "Moving" passa a atualizar as posições dos átomos, simulando seu movimento, com os átomos divididos em dois grupos de acordo com o índice "i" e o valor de "covalentCount".
Grupo 1: Átomos que participam da ligação covalente (i <= covalentCount). Para cada átomo desse grupo e para cada uma de suas coordenadas, é gerado um número aleatório; se esse número for menor que o coeficiente de ligação covalente, o átomo altera sua coordenada pela fórmula que simula a atração ou interação com a melhor solução atual "cB [c]", em que "cB" contém as melhores coordenadas encontradas na população e leva em conta "covalentCount".
Se o número aleatório for maior ou igual a "covalentRate", o átomo altera sua coordenada usando a função "u.PowerDistribution". Essa função gera um valor deslocado em direção a "cB [c]", mas com certa dispersão aleatória, simulando uma busca aleatória mais ampla ou "dispersão".
Independentemente de como a coordenada foi alterada, ela é novamente processada pela função "u.SeInDiSp" para ser ajustada ao intervalo permitido e à grade discreta.
Grupo 2: Átomos que não participam da ligação covalente (i > covalentCount): para cada átomo desse grupo e para cada uma de suas coordenadas, é escolhido um índice aleatório "ind" no intervalo de "0" a "covalentCount". Isso significa que o átomo "não ligado" interagirá com um dos átomos que participam da ligação covalente.
A coordenada do átomo é alterada, simulando a interação ou repulsão em relação ao átomo "ligado" escolhido e à sua posição "melhor" anterior, e a coordenada é novamente processada por "u.SeInDiSp" para atender às restrições.
O método "Moving ()" é o núcleo da busca. Ele implementa regras estocásticas de atualização das posições dos átomos, inspiradas em um modelo físico. Os átomos que participam da "ligação covalente" se deslocam em direção à melhor solução encontrada, enquanto os demais átomos exploram o espaço interagindo com os átomos que participam da ligação covalente.
//———————————————————————————————————————————————————————————————————— //--- Основной шаг алгоритма void C_AO_A3::Moving () { // Начальная инициализация популяции if (!revision) { for (int i = 0; i < popSize; i++) { for (int j = 0; j < coords; j++) { a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]); a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]); } } revision = true; return; } //------------------------------------------------------------------ int ind = 0; for (int i = 0; i < popSize; i++) { if (i <= covalentCount) { for (int c = 0; c < coords; c++) { if (u.RNDprobab () < covalentRate) { a [i].c [c] = a [i].c [c] + u.RNDprobab () * (cB [c] - a [i].c [c]) * covalentCount;//(1.0 - covalentCount); } else { a [i].c [c] = u.PowerDistribution (cB [c], rangeMin [c], rangeMax [c], 20); } a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } else { for (int c = 0; c < coords; c++) { ind = u.RNDintInRange (0, covalentCount); a [i].c [c] = a [i].c [c] + u.RNDprobab () * (a [ind].c [c] - a [i].cW [c]) * (1.0 - covalentCount);//covalentCount; a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } } //————————————————————————————————————————————————————————————————————
O método "Revision ()" na classe "C_AO_A3" é responsável por atualizar as informações sobre as melhores e piores soluções encontradas na população de átomos na iteração atual do algoritmo. Essa é uma etapa importante, pois esses valores "melhores" e "piores" (tanto os globais de toda a população quanto os individuais de cada átomo) são usados para direcionar a busca posterior na etapa seguinte de movimentação. Em seguida, é atualizada a pior solução local individual de cada átomo. É chamada a função "u.Sorting ()", que ordena toda a população de átomos "a" com base em seu fitness "f".
Após a ordenação, "a [0]" é o melhor átomo da população atual; então "fB" é atualizado com o valor de "a [0].f". As coordenadas "a [0].c" são copiadas para "cB" (melhores coordenadas globais). O átomo "a [popSize - 1]" é o pior átomo da população atual (após a ordenação). O fitness "a [popSize - 1].f" é comparado com "fW" (o pior fitness global encontrado ao longo de toda a execução). Se "a [popSize - 1].f" for realmente pior, então "fW" é atualizado com o valor de "a [popSize - 1].f", e as coordenadas "a [popSize - 1].c" são copiadas para "cW" (piores coordenadas globais).
O método "Revision" monitora sistematicamente o progresso da otimização. Ele atualiza tanto os estados "melhores" individuais de cada agente (átomo) quanto os estados globais "melhor" e "pior" de toda a população.
//———————————————————————————————————————————————————————————————————— //--- Обновление лучшего и худшего решений void C_AO_A3::Revision () { // Обновляем локальное худшее решение for (int i = 0; i < popSize; i++) { if (a [i].f < a [i].fW) { fW = a [i].f; ArrayCopy (a [i].cW, a [i].c, 0, 0, WHOLE_ARRAY); } } static S_AO_Agent aT []; ArrayResize (aT, popSize); u.Sorting (a, aT, popSize); if (a [0].f > fB) { fB = a [0].f; ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY); } if (a [popSize - 1].f < fW) { fW = a [popSize - 1].f; ArrayCopy (cW, a [popSize - 1].c, 0, 0, WHOLE_ARRAY); } } //————————————————————————————————————————————————————————————————————
A solução ficou bastante compacta. Agora passaremos à classe base da qual herdam todos os algoritmos de otimização. Como já mencionei, ela foi alterada, então vamos descrevê-la.
Uma estrutura simples para armazenar os parâmetros do algoritmo. Permite gerenciar dinamicamente as configurações de qualquer algoritmo de otimização.
//—————————————————————————————————————————————————————————————————————————————— struct S_AlgoParam { double val; string name; }; //——————————————————————————————————————————————————————————————————————————————
A estrutura "S_AO_Agent" serve para representar um "agente" individual, que corresponde a uma solução potencial do problema de otimização e se desloca em um espaço de busca multidimensional. A estrutura "S_AO_Agent" contém uma série de campos que podem ser divididos em várias categorias: 1. Coordenadas (posição no espaço de busca):
- c: coordenadas atuais do agente. Este é o array principal que armazena a posição atual do agente.
- cP: coordenadas anteriores do agente. Usadas para acompanhar a movimentação do agente.
- cB: melhores coordenadas que esse agente específico já alcançou. Essa informação ajuda o agente a "lembrar" suas posições mais bem-sucedidas e retornar a elas, ou usá-las na busca posterior.
- cW: piores coordenadas do agente.
2. Fitness (qualidade da solução):
- f: fitness atual do agente. Este é o valor da função objetivo calculado para as coordenadas atuais "c".
- fP: fitness anterior do agente. Assim como "cP", é usado para acompanhar a variação do fitness.
- fB: melhor fitness que esse agente já alcançou. Corresponde às coordenadas "cB".
- fW: pior fitness do agente. Corresponde às coordenadas "cW".
3. Dados auxiliares: cnt: contador inteiro.
A estrutura também inclui o método público "Init ()", usado para inicializar o agente. Ele recebe um parâmetro inteiro "coords", que indica a dimensionalidade do espaço de busca (ou seja, o número de coordenadas que o agente deve ter).
Ao chamar "Init", são realizadas as seguintes ações: os arrays de coordenadas (c, cP, cB, cW) são redimensionados dinamicamente para o tamanho especificado por "coords". Isso assegura que cada agente tenha o número correto de dimensões para lidar com um problema específico. Os valores de fitness (f, fP, fB, fW) são inicializados: "f", "fP" e "fB" são definidos como menos "DBL_MAX" (o menor valor possível de "double"), enquanto "fW" é definido como "DBL_MAX" (o maior valor possível). Essa é uma prática padrão em problemas de maximização (para f, fP e fB, o valor atual quase sempre será maior que o valor inicial); já para "fW" (o pior), é usado o maior valor possível, para que qualquer primeiro valor encontrado seja melhor (menor) do que ele. O contador "cnt" é inicializado com zero.
De modo geral, "S_AO_Agent" fornece uma estrutura de dados abrangente para cada participante da população em um algoritmo de otimização evolucionário e populacional, permitindo acompanhar tanto seu estado atual quanto seu histórico de sucessos e falhas.
//—————————————————————————————————————————————————————————————————————————————— struct S_AO_Agent { double c []; //coordinates double cP []; //previous coordinates double cB []; //best coordinates double cW []; //worst coordinates double f; //fitness double fP; //previous fitness double fB; //best fitness double fW; //worst fitness int cnt; //counter void Init (int coords) { ArrayResize (c, coords); ArrayResize (cP, coords); ArrayResize (cB, coords); ArrayResize (cW, coords); f = -DBL_MAX; fP = -DBL_MAX; fB = -DBL_MAX; fW = DBL_MAX; cnt = 0; } }; //——————————————————————————————————————————————————————————————————————————————
Vamos analisar a classe base "C_AO". Estrutura geral da classe C_AO. Membros públicos (public). Estes são os campos e métodos acessíveis de fora da classe.
Dados que descrevem o estado geral da melhor e pior solução:
- cB []: array de coordenadas da melhor solução encontrada pelo algoritmo como um todo (globalmente).
- cW []: array de coordenadas da pior solução encontrada pelo algoritmo como um todo (globalmente).
- fB: valor de fitness da melhor solução global (corresponde a "cB").
- fW: valor de fitness da pior solução global (corresponde a "cW").
Observe que se trata das melhores/piores soluções globais, diferentemente de "fB" e "fW" dentro de "S_AO_Agent", que se referem a um agente específico.
Dados relacionados à população e aos parâmetros:
- a []: array de objetos "S_AO_Agent". Esta é a "população" de agentes, cada um deles uma solução potencial.
- params []: array de objetos "S_AlgoParam". Aqui são armazenados os parâmetros específicos deste algoritmo (por exemplo, coeficientes de influência, probabilidades etc.), que podem ser ajustados.
- revision: sinalizador que indica a necessidade de revisar e atualizar o estado.
- SetParams (): método para definir os parâmetros específicos do algoritmo; nas classes derivadas, ele preencherá o array "params".
- Init (): método principal de inicialização do algoritmo. Recebe os intervalos de busca (mínimo, máximo, passo) e a quantidade de épocas. Retorna "true" em caso de inicialização bem-sucedida.
- Moving (): método que implementa a lógica de "deslocamento" ou "evolução" dos agentes no espaço de busca. É o núcleo do algoritmo.
- Revision (): método para a fase de "revisão" ou "atualização" do estado do algoritmo.
- Injection (): método para "injetar" um determinado valor na coordenada de um agente específico.
Parâmetros do espaço de busca, armazenados nos arrays:
- rangeMin []: valores mínimos para cada coordenada do espaço de busca.
- rangeMax []: valores máximos para cada coordenada do espaço de busca.
- rangeStep []: passos de variação para cada coordenada (útil para espaços discretos ou para definir a granularidade da busca).
Parâmetros internos do algoritmo:
- coords: número de dimensões (coordenadas) no espaço de busca.
- popSize: tamanho da população (número de agentes) no algoritmo.
Utilitários auxiliares: "u": objeto de uma classe auxiliar que contém funções comuns, como geração de números aleatórios, operações matemáticas etc.
Método de inicialização padrão (protected): "StandardInit ()" é o método usado para executar a etapa comum de inicialização de todos os algoritmos derivados. Ele inicializa o gerador de números aleatórios usando a hora atual do sistema como semente inicial, define "fB" como infinito negativo e "fW" como infinito positivo para o rastreamento posterior da melhor e da pior solução global. Também define o sinalizador "revision" como "false", verifica e define o número de coordenadas (dimensionalidade do espaço), redimensiona os arrays internos (rangeMin, rangeMax, rangeStep, cB, cW) de acordo com a quantidade de coordenadas, redimensiona o array de agentes "a" de acordo com o tamanho da população "popSize" e, para cada agente, chama seu próprio método "Init" para inicializar os arrays internos de coordenadas. O método copia os intervalos de busca recebidos como entrada para suas variáveis internas e retorna "true" em caso de inicialização bem-sucedida, ou "false" em caso de erros (por exemplo, divergência entre os tamanhos dos arrays de intervalos de entrada).
A classe "C_AO" serve como classe base para implementações concretas de algoritmos de otimização. Cada novo algoritmo (uma implementação concreta de AO) herdará de "C_AO" e redefinirá os métodos virtuais (SetParams, Init, Moving, Revision, Injection) para implementar sua própria lógica. Já "StandardInit" ajuda a evitar duplicação de código nas etapas comuns de inicialização.
//—————————————————————————————————————————————————————————————————————————————— class C_AO { public: //-------------------------------------------------------------------- C_AO () { } ~C_AO () { } double cB []; //best coordinates double cW []; //worst coordinates double fB; //FF of the best coordinates double fW; //FF of the worst coordinates S_AO_Agent a []; //agents S_AlgoParam params []; //algorithm parameters bool revision; virtual void SetParams () { } virtual bool Init (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP [], //step search const int epochsP = 0) //number of epochs { return false;} virtual void Moving () { } virtual void Revision () { } virtual void Injection (const int popPos, const int coordPos, const double value) { } string GetName () { return ao_name;} string GetDesc () { return ao_desc;} string GetLink () { return ao_link;} string GetParams () { string str = ""; for (int i = 0; i < ArraySize (params); i++) { str += (string)params [i].val + "|"; } return str; } protected: //----------------------------------------------------------------- string ao_name; //ao name; string ao_desc; //ao description string ao_link; //ao link double rangeMin []; //minimum search range double rangeMax []; //maximum search range double rangeStep []; //step search int coords; //coordinates number int popSize; //population size C_AO_Utilities u; //auxiliary functions bool StandardInit (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP []) //step search { int seed = (int)GetTickCount64 (); MathSrand (seed); //reset of the generator fB = -DBL_MAX; fW = DBL_MAX; revision = false; coords = ArraySize (rangeMinP); if (coords == 0 || coords != ArraySize (rangeMaxP) || coords != ArraySize (rangeStepP)) return false; ArrayResize (rangeMin, coords); ArrayResize (rangeMax, coords); ArrayResize (rangeStep, coords); ArrayResize (cB, coords); ArrayResize (cW, coords); ArrayResize (a, popSize); for (int i = 0; i < popSize; i++) a [i].Init (coords); ArrayCopy (rangeMin, rangeMinP, 0, 0, WHOLE_ARRAY); ArrayCopy (rangeMax, rangeMaxP, 0, 0, WHOLE_ARRAY); ArrayCopy (rangeStep, rangeStepP, 0, 0, WHOLE_ARRAY); return true; } }; //——————————————————————————————————————————————————————————————————————————————
Resultados dos testes
Agora vejamos o desempenho do algoritmo A3. Resultados médios com uma população pequena.
A3|Artificial Atom Algorithm|30.0|0.2|
=============================
5 Hilly's; Func runs: 10000; result: 0.7074464987418227
25 Hilly's; Func runs: 10000; result: 0.49461443027863367
500 Hilly's; Func runs: 10000; result: 0.27674325929370214
=============================
5 Forest's; Func runs: 10000; result: 0.8910275237236067
25 Forest's; Func runs: 10000; result: 0.43888040941642725
500 Forest's; Func runs: 10000; result: 0.17553299655770818
=============================
5 Megacity's; Func runs: 10000; result: 0.5323076923076923
25 Megacity's; Func runs: 10000; result: 0.3270769230769231
500 Megacity's; Func runs: 10000; result: 0.11430769230769337
=============================
All score: 3.95794 (43.98%)
Na visualização, nota-se uma variação dos resultados nas funções de baixa dimensionalidade (linhas verdes), embora caiba destacar a boa cobertura do espaço pelas soluções.

A3 na função de teste Hilly

A3 na função de teste Forest

A3 na função de teste Megacity
Em nossa tabela de classificação, o algoritmo A3 é apresentado apenas para fins informativos.
| № | AO | Description | Hilly | Hilly Final | Forest | Forest Final | Megacity (discrete) | Megacity Final | Final Result | % of MAX | ||||||
| 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | ||||||||
| 1 | ANS | across neighbourhood search | 0,94948 | 0,84776 | 0,43857 | 2,23581 | 1,00000 | 0,92334 | 0,39988 | 2,32323 | 0,70923 | 0,63477 | 0,23091 | 1,57491 | 6,134 | 68,15 |
| 2 | CLA | code lock algorithm (joo) | 0,95345 | 0,87107 | 0,37590 | 2,20042 | 0,98942 | 0,91709 | 0,31642 | 2,22294 | 0,79692 | 0,69385 | 0,19303 | 1,68380 | 6,107 | 67,86 |
| 3 | AMOm | animal migration ptimization M | 0,90358 | 0,84317 | 0,46284 | 2,20959 | 0,99001 | 0,92436 | 0,46598 | 2,38034 | 0,56769 | 0,59132 | 0,23773 | 1,39675 | 5,987 | 66,52 |
| 4 | (P+O)ES | (P+O) evolution strategies | 0,92256 | 0,88101 | 0,40021 | 2,20379 | 0,97750 | 0,87490 | 0,31945 | 2,17185 | 0,67385 | 0,62985 | 0,18634 | 1,49003 | 5,866 | 65,17 |
| 5 | CTA | comet tail algorithm (joo) | 0,95346 | 0,86319 | 0,27770 | 2,09435 | 0,99794 | 0,85740 | 0,33949 | 2,19484 | 0,88769 | 0,56431 | 0,10512 | 1,55712 | 5,846 | 64,96 |
| 6 | TETA | time evolution travel algorithm (joo) | 0,91362 | 0,82349 | 0,31990 | 2,05701 | 0,97096 | 0,89532 | 0,29324 | 2,15952 | 0,73462 | 0,68569 | 0,16021 | 1,58052 | 5,797 | 64,41 |
| 7 | SDSm | stochastic diffusion search M | 0,93066 | 0,85445 | 0,39476 | 2,17988 | 0,99983 | 0,89244 | 0,19619 | 2,08846 | 0,72333 | 0,61100 | 0,10670 | 1,44103 | 5,709 | 63,44 |
| 8 | BOAm | billiards optimization algorithm M | 0,95757 | 0,82599 | 0,25235 | 2,03590 | 1,00000 | 0,90036 | 0,30502 | 2,20538 | 0,73538 | 0,52523 | 0,09563 | 1,35625 | 5,598 | 62,19 |
| 9 | AAm | archery algorithm M | 0,91744 | 0,70876 | 0,42160 | 2,04780 | 0,92527 | 0,75802 | 0,35328 | 2,03657 | 0,67385 | 0,55200 | 0,23738 | 1,46323 | 5,548 | 61,64 |
| 10 | ESG | evolution of social groups (joo) | 0,99906 | 0,79654 | 0,35056 | 2,14616 | 1,00000 | 0,82863 | 0,13102 | 1,95965 | 0,82333 | 0,55300 | 0,04725 | 1,42358 | 5,529 | 61,44 |
| 11 | SIA | simulated isotropic annealing (joo) | 0,95784 | 0,84264 | 0,41465 | 2,21513 | 0,98239 | 0,79586 | 0,20507 | 1,98332 | 0,68667 | 0,49300 | 0,09053 | 1,27020 | 5,469 | 60,76 |
| 12 | EOm | extremal_optimization_M | 0,76166 | 0,77242 | 0,31747 | 1,85155 | 0,99999 | 0,76751 | 0,23527 | 2,00277 | 0,74769 | 0,53969 | 0,14249 | 1,42987 | 5,284 | 58,71 |
| 13 | BBO | biogeography based optimization | 0,94912 | 0,69456 | 0,35031 | 1,99399 | 0,93820 | 0,67365 | 0,25682 | 1,86867 | 0,74615 | 0,48277 | 0,17369 | 1,40261 | 5,265 | 58,50 |
| 14 | ACS | artificial cooperative search | 0,75547 | 0,74744 | 0,30407 | 1,80698 | 1,00000 | 0,88861 | 0,22413 | 2,11274 | 0,69077 | 0,48185 | 0,13322 | 1,30583 | 5,226 | 58,06 |
| 15 | DA | dialectical algorithm | 0,86183 | 0,70033 | 0,33724 | 1,89940 | 0,98163 | 0,72772 | 0,28718 | 1,99653 | 0,70308 | 0,45292 | 0,16367 | 1,31967 | 5,216 | 57,95 |
| 16 | BHAm | black hole algorithm M | 0,75236 | 0,76675 | 0,34583 | 1,86493 | 0,93593 | 0,80152 | 0,27177 | 2,00923 | 0,65077 | 0,51646 | 0,15472 | 1,32195 | 5,196 | 57,73 |
| 17 | ASO | anarchy society optimization | 0,84872 | 0,74646 | 0,31465 | 1,90983 | 0,96148 | 0,79150 | 0,23803 | 1,99101 | 0,57077 | 0,54062 | 0,16614 | 1,27752 | 5,178 | 57,54 |
| 18 | RFO | royal flush optimization (joo) | 0,83361 | 0,73742 | 0,34629 | 1,91733 | 0,89424 | 0,73824 | 0,24098 | 1,87346 | 0,63154 | 0,50292 | 0,16421 | 1,29867 | 5,089 | 56,55 |
| 19 | AOSm | atomic orbital search M | 0,80232 | 0,70449 | 0,31021 | 1,81702 | 0,85660 | 0,69451 | 0,21996 | 1,77107 | 0,74615 | 0,52862 | 0,14358 | 1,41835 | 5,006 | 55,63 |
| 20 | TSEA | turtle shell evolution algorithm (joo) | 0,96798 | 0,64480 | 0,29672 | 1,90949 | 0,99449 | 0,61981 | 0,22708 | 1,84139 | 0,69077 | 0,42646 | 0,13598 | 1,25322 | 5,004 | 55,60 |
| 21 | BSA | backtracking_search_algorithm | 0,97309 | 0,54534 | 0,29098 | 1,80941 | 0,99999 | 0,58543 | 0,21747 | 1,80289 | 0,84769 | 0,36953 | 0,12978 | 1,34700 | 4,959 | 55,10 |
| 22 | DE | differential evolution | 0,95044 | 0,61674 | 0,30308 | 1,87026 | 0,95317 | 0,78896 | 0,16652 | 1,90865 | 0,78667 | 0,36033 | 0,02953 | 1,17653 | 4,955 | 55,06 |
| 23 | SRA | successful restaurateur algorithm (joo) | 0,96883 | 0,63455 | 0,29217 | 1,89555 | 0,94637 | 0,55506 | 0,19124 | 1,69267 | 0,74923 | 0,44031 | 0,12526 | 1,31480 | 4,903 | 54,48 |
| 24 | CRO | chemical reaction optimisation | 0,94629 | 0,66112 | 0,29853 | 1,90593 | 0,87906 | 0,58422 | 0,21146 | 1,67473 | 0,75846 | 0,42646 | 0,12686 | 1,31178 | 4,892 | 54,36 |
| 25 | BIO | blood inheritance optimization (joo) | 0,81568 | 0,65336 | 0,30877 | 1,77781 | 0,89937 | 0,65319 | 0,21760 | 1,77016 | 0,67846 | 0,47631 | 0,13902 | 1,29378 | 4,842 | 53,80 |
| 26 | BSA | bird swarm algorithm | 0,89306 | 0,64900 | 0,26250 | 1,80455 | 0,92420 | 0,71121 | 0,24939 | 1,88479 | 0,69385 | 0,32615 | 0,10012 | 1,12012 | 4,809 | 53,44 |
| 27 | DEA | dolphin_echolocation_algorithm | 0,75995 | 0,67572 | 0,34171 | 1,77738 | 0,89582 | 0,64223 | 0,23941 | 1,77746 | 0,61538 | 0,44031 | 0,15115 | 1,20684 | 4,762 | 52,91 |
| 28 | HS | harmony search | 0,86509 | 0,68782 | 0,32527 | 1,87818 | 0,99999 | 0,68002 | 0,09590 | 1,77592 | 0,62000 | 0,42267 | 0,05458 | 1,09725 | 4,751 | 52,79 |
| 29 | SSG | saplings sowing and growing | 0,77839 | 0,64925 | 0,39543 | 1,82308 | 0,85973 | 0,62467 | 0,17429 | 1,65869 | 0,64667 | 0,44133 | 0,10598 | 1,19398 | 4,676 | 51,95 |
| 30 | BCOm | bacterial chemotaxis optimization M | 0,75953 | 0,62268 | 0,31483 | 1,69704 | 0,89378 | 0,61339 | 0,22542 | 1,73259 | 0,65385 | 0,42092 | 0,14435 | 1,21912 | 4,649 | 51,65 |
| 31 | ABO | african buffalo optimization | 0,83337 | 0,62247 | 0,29964 | 1,75548 | 0,92170 | 0,58618 | 0,19723 | 1,70511 | 0,61000 | 0,43154 | 0,13225 | 1,17378 | 4,634 | 51,49 |
| 32 | (PO)ES | (PO) evolution strategies | 0,79025 | 0,62647 | 0,42935 | 1,84606 | 0,87616 | 0,60943 | 0,19591 | 1,68151 | 0,59000 | 0,37933 | 0,11322 | 1,08255 | 4,610 | 51,22 |
| 33 | FBA | fractal-based Algorithm | 0,79000 | 0,65134 | 0,28965 | 1,73099 | 0,87158 | 0,56823 | 0,18877 | 1,62858 | 0,61077 | 0,46062 | 0,12398 | 1,19537 | 4,555 | 50,61 |
| 34 | TSm | tabu search M | 0,87795 | 0,61431 | 0,29104 | 1,78330 | 0,92885 | 0,51844 | 0,19054 | 1,63783 | 0,61077 | 0,38215 | 0,12157 | 1,11449 | 4,536 | 50,40 |
| 35 | BSO | brain storm optimization | 0,93736 | 0,57616 | 0,29688 | 1,81041 | 0,93131 | 0,55866 | 0,23537 | 1,72534 | 0,55231 | 0,29077 | 0,11914 | 0,96222 | 4,498 | 49,98 |
| 36 | WOAm | wale optimization algorithm M | 0,84521 | 0,56298 | 0,26263 | 1,67081 | 0,93100 | 0,52278 | 0,16365 | 1,61743 | 0,66308 | 0,41138 | 0,11357 | 1,18803 | 4,476 | 49,74 |
| 37 | AEFA | artificial electric field algorithm | 0,87700 | 0,61753 | 0,25235 | 1,74688 | 0,92729 | 0,72698 | 0,18064 | 1,83490 | 0,66615 | 0,11631 | 0,09508 | 0,87754 | 4,459 | 49,55 |
| 38 | AEO | artificial ecosystem-based optimization algorithm | 0,91380 | 0,46713 | 0,26470 | 1,64563 | 0,90223 | 0,43705 | 0,21400 | 1,55327 | 0,66154 | 0,30800 | 0,28563 | 1,25517 | 4,454 | 49,49 |
| 39 | CAm | camel algorithm M | 0,78684 | 0,56042 | 0,35133 | 1,69859 | 0,82772 | 0,56041 | 0,24336 | 1,63149 | 0,64846 | 0,33092 | 0,13418 | 1,11356 | 4,444 | 49,37 |
| 40 | ACOm | ant colony optimization M | 0,88190 | 0,66127 | 0,30377 | 1,84693 | 0,85873 | 0,58680 | 0,15051 | 1,59604 | 0,59667 | 0,37333 | 0,02472 | 0,99472 | 4,438 | 49,31 |
| 41 | CMAES | covariance_matrix_adaptation_evolution_strategy | 0,76258 | 0,72089 | 0,00000 | 1,48347 | 0,82056 | 0,79616 | 0,00000 | 1,61672 | 0,75846 | 0,49077 | 0,00000 | 1,24923 | 4,349 | 48,33 |
| 42 | BFO-GA | bacterial foraging optimization - ga | 0,89150 | 0,55111 | 0,31529 | 1,75790 | 0,96982 | 0,39612 | 0,06305 | 1,42899 | 0,72667 | 0,27500 | 0,03525 | 1,03692 | 4,224 | 46,93 |
| 43 | SOA | simple optimization algorithm | 0,91520 | 0,46976 | 0,27089 | 1,65585 | 0,89675 | 0,37401 | 0,16984 | 1,44060 | 0,69538 | 0,28031 | 0,10852 | 1,08422 | 4,181 | 46,45 |
| 44 | ABHA | artificial bee hive algorithm | 0,84131 | 0,54227 | 0,26304 | 1,64663 | 0,87858 | 0,47779 | 0,17181 | 1,52818 | 0,50923 | 0,33877 | 0,10397 | 0,95197 | 4,127 | 45,85 |
| 45 | ACMO | atmospheric cloud model optimization | 0,90321 | 0,48546 | 0,30403 | 1,69270 | 0,80268 | 0,37857 | 0,19178 | 1,37303 | 0,62308 | 0,24400 | 0,10795 | 0,97503 | 4,041 | 44,90 |
| A3 | artificial_atom_algorithm | 0,70744 | 0,49461 | 0,27674 | 1,47879 | 0,89102 | 0,43888 | 0,17553 | 1,50543 | 0,53230 | 0,32707 | 0,11431 | 0,97368 | 3,958 | 43,98 | |
| RW | random walk | 0,48754 | 0,32159 | 0,25781 | 1,06694 | 0,37554 | 0,21944 | 0,15877 | 0,75375 | 0,27969 | 0,14917 | 0,09847 | 0,52734 | 2,348 | 26,09 | |
Conclusões
Neste artigo, foi apresentada a implementação em MQL5 do algoritmo do átomo artificial A3 para a otimização de estratégias de negociação. Embora a descrição teórica apresentada pelos autores no trabalho original seja incompleta, foi possível criar uma implementação prática funcional. Os testes realizados mostraram que o A3 apresenta uma boa relação entre velocidade de execução e qualidade das soluções obtidas. Apenas dois parâmetros ajustáveis simplificam significativamente a configuração do algoritmo para uma tarefa concreta, o que é fundamental para traders que não são especialistas em otimização.
O algoritmo A3 pode ocupar um nicho próprio entre os métodos de otimização. O algoritmo do átomo artificial mostra que mesmo metaheurísticas relativamente simples podem ser bastante eficientes quando adaptadas corretamente a um domínio de aplicação específico. A combinação de simplicidade conceitual, eficiência computacional e qualidade satisfatória das soluções faz do A3 uma boa ferramenta no arsenal do trader algorítmico.
O código-fonte do algoritmo e exemplos de uso estão disponíveis no anexo do artigo, permitindo que os interessados avaliem por conta própria a eficiência do método e experimentem.

Figura 1. Gradação de cores dos algoritmos pelos respectivos testes

Figura 2. Histograma dos resultados dos testes dos algoritmos (em uma escala de 0 a 100, quanto maior, melhor, em que 100 é o resultado teórico máximo possível; no arquivo compactado há o script para calcular a tabela de classificação)
Prós e contras do algoritmo A3:
Prós:
- Rápido.
- Pequena quantidade de parâmetros.
Contras:
- Baixa precisão de convergência.
Ao artigo está anexado um arquivo compactado com as versões atualizadas do código dos algoritmos. O autor do artigo não se responsabiliza pela precisão absoluta na descrição dos algoritmos canônicos; muitos deles foram modificados para melhorar suas capacidades de busca. As conclusões e avaliações apresentadas nos artigos baseiam-se nos resultados dos experimentos realizados.
Programas usados no artigo
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | #C_AO.mqh | Arquivo de inclusão | Classe pai dos algoritmos de otimização populacionais |
| 2 | #C_AO_enum.mqh | Arquivo de inclusão | Enumeração dos algoritmos de otimização populacionais |
| 3 | TestFunctions.mqh | Arquivo de inclusão | Biblioteca de funções de teste |
| 4 | TestStandFunctions.mqh | Arquivo de inclusão | Biblioteca de funções da bancada de testes |
| 5 | Utilities.mqh | Arquivo de inclusão | Biblioteca de funções auxiliares |
| 6 | CalculationTestResults.mqh | Arquivo de inclusão | Script para calcular os resultados da tabela comparativa |
| 7 | Testing AOs.mq5 | Script | Bancada de testes unificada para todos os algoritmos de otimização populacionais |
| 8 | Simple use of population optimization algorithms.mq5 | Script | Exemplo simples de uso dos algoritmos de otimização populacionais sem visualização |
| 9 | Test_AO_A3.mq5 | Script | Bancada de testes para o A3 |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/18958
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Introdução ao MQL5 (Parte 14): Guia para Iniciantes na Criação de Indicadores Personalizados (III)
Está chegando o novo MetaTrader 5 e MQL5
Estratégias de Reversão à Média com RSI2 de Larry Connors para Day Trading
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso