Algoritmo do mercado acionário: Exchange Market Algorithm (EMA)
Conteúdo
Introdução
Na área de otimização numérica, novos algoritmos vêm sendo desenvolvidos continuamente com o objetivo de resolver de forma eficiente problemas complexos em condições de incerteza e alta dimensionalidade. Entre eles, um lugar de destaque é ocupado pelas meta-heurísticas populacionais que, ao imitarem processos naturais ou sociais, demonstram capacidade impressionante de encontrar ótimos globais.
No entanto, nem todo algoritmo novo consegue alcançar um nível competitivo de desempenho. Este artigo é dedicado a uma análise detalhada do algoritmo Exchange Market Algorithm (EMA), representante da classe de métodos mencionada acima, inspirado no comportamento de traders no mercado acionário. O algoritmo modela o processo de negociação de ações, em que participantes do mercado com diferentes níveis de sucesso aplicam estratégias variadas para maximizar o lucro.
Implementação do algoritmo
O algoritmo inicia seu funcionamento formando uma população de participantes do mercado (traders), e todos os participantes são divididos em três grupos iguais. Cada trader recebe aleatoriamente uma posição inicial no espaço de busca, que representa sua carteira atual de ações. Essas posições são distribuídas uniformemente por toda a faixa de valores permitida.
Após a avaliação inicial do sucesso de cada trader (cálculo do valor da função objetivo), toda a população é classificada em ordem decrescente de sucesso e dividida em três grupos:
- o primeiro grupo é a elite. São os participantes do mercado mais bem-sucedidos, que encontraram as posições mais lucrativas. Uma característica importante é que os traders de elite mantêm suas posições inalteradas durante toda a sessão de negociação, servindo de referência para os demais.
- o segundo grupo é a classe média. São participantes moderadamente bem-sucedidos, que buscam melhorar suas posições aprendendo com a elite, mas ao mesmo tempo estão dispostos a assumir risco moderado.
- o terceiro grupo é o dos iniciantes. São os participantes menos bem-sucedidos, dispostos a assumir alto risco na tentativa de melhorar radicalmente sua situação.
Cada iteração do algoritmo representa uma sessão de negociação composta por duas fases, correspondentes a diferentes estados do mercado.
Fase 1: Mercado equilibrado (negociação estável)
Em condições de mercado estável, os traders aplicam estratégias conservadoras baseadas na imitação de participantes mais bem-sucedidos.
Comportamento da classe média. Cada trader do segundo grupo escolhe aleatoriamente um mentor da elite e desloca gradualmente sua posição na direção dele. O grau desse deslocamento é determinado pelo coeficiente de absorção (r1 = 1.5), que diminui de forma adaptativa à medida que o algoritmo avança. O movimento não ocorre de forma determinística, pois é usado um fator aleatório que simula a incerteza das decisões de mercado.
Comportamento dos iniciantes. Os traders do terceiro grupo ficam sob a influência tanto da elite quanto da classe média. Cada iniciante escolhe um mentor do primeiro grupo e um do segundo grupo, e sua nova posição é formada como uma combinação de movimentos na direção de ambos os mentores. Isso reflete a tentativa de aprender ao mesmo tempo com os mais bem-sucedidos e com aqueles que melhoraram suas posições recentemente.
Fase 2: Mercado oscilante (negociação volátil)
Em condições de mercado instável, os traders aplicam estratégias mais arriscadas para buscar novas oportunidades.
Comportamento exploratório da classe média. Os traders do segundo grupo aplicam uma estratégia dupla. Com probabilidade de 50%, eles podem copiar integralmente a posição da melhor solução global (o trader mais bem-sucedido de toda a história). Nos demais casos, exploram a região ao redor do centro de massa do grupo de elite, adicionando um ruído aleatório controlado a essa posição. A magnitude do ruído é determinada pelo fator de risco (riskAlpha = 0.3) e diminui com o tempo.
Comportamento agressivo dos iniciantes. O terceiro grupo aplica as estratégias mais arriscadas:
- com probabilidade igual ao fator de risco (30%), o trader esquece completamente sua posição atual e recomeça a partir de um ponto aleatório;
- em 35% dos casos, é realizada uma busca ampla ao redor da posição atual com um raio grande;
- nos casos restantes, é aplicado o aprendizado por oposição: o trader se move na direção oposta ao centro das piores soluções, tentando se afastar das regiões malsucedidas.
A principal característica do EMA é sua adaptabilidade. À medida que as iterações avançam, é calculado um coeficiente de atenuação com base em uma função sigmoide. Esse coeficiente garante uma transição suave da diversificação (no início da execução) para a intensificação das boas soluções encontradas (no final).
Depois da execução de ambas as fases, ocorre uma etapa importante: as posições de todos os traders, exceto os da elite, são atualizadas de acordo com os novos valores calculados. A elite mantém suas posições, garantindo a preservação das melhores soluções encontradas.
Em seguida, é calculado o novo sucesso de todos os traders (valor da função objetivo), a população é classificada novamente, e a nova melhor solução global é determinada. Agora vamos ver como as fórmulas do algoritmo aparecem na imagem apresentada abaixo.

A ilustração contém todas as fórmulas-chave de atualização de posições para cada grupo em ambos os regimes de mercado. Com base na descrição e nas fórmulas apresentadas, vamos montar o pseudocódigo do algoritmo EMA.
1. Inicialização
- Criar uma população de N = 60 traders (N múltiplo de 3)
- Definir os parâmetros: r₁ = 1.5, r₂ = 0.8, α = 0.3 (fator de risco)
- Inicializar cada trader com uma posição aleatória
- Definir o tamanho do grupo: m = N/3 = 20
2. Laço principal
Para cada iteração t = 1, 2, ..., T executar:
2.1 Classificação
- Avaliar o fitness de todos os traders
- Classificar em ordem decrescente de fitness
- Dividir em grupos:
- G₁ = {1, ..., m} elite
- G₂ = {m+1, ..., 2m} classe média
- G₃ = {2m+1, ..., N} iniciantes
2.2 Adaptação dos parâmetros
- progress = t / T
- decay = 1 / (1 + exp(-10(progress - 0.5)))
- r₁ᵃᵈᵃᵖᵗ = r₁ × (1 - decay × 0.5)
- r₂ᵃᵈᵃᵖᵗ = r₂ × (1 - decay × 0.3)
2.3 Mercado equilibrado
- G₁: as posições são mantidas
- G₂: para cada i ∈ G₂
- escolher um líder aleatório k ∈ G₁
- xᵢ = xᵢ + rand() × r₁ᵃᵈᵃᵖᵗ × (xₖ - xᵢ)
- G₃: para cada i ∈ G₃
- escolher o líder k₁ ∈ G₁ e k₂ ∈ G₂
- xᵢ = xᵢ + rand() × r₁ᵃᵈᵃᵖᵗ × (xₖ₁ - xᵢ) + rand() × r₂ᵃᵈᵃᵖᵗ × (xₖ₂ - xᵢ)
2.4 Mercado oscilante
- G₂: para cada i ∈ G₂
- se rand() < 0.5: xᵢ = xᵇᵉˢᵗ
- caso contrário: xᵢ = centro(G₁) + ruído × α × (1 - decay × 0.5)
- G₃: para cada i ∈ G₃
- se rand() < α: xᵢ = posição_aleatória()
- caso contrário, se rand() < 0.5 + α/2: xᵢ = xᵢ + grande_perturbação
- caso contrário: xᵢ = 2xᵢ - centro(piores) + pequeno_ruído
2.5 Atualização
- Aplicar as novas posições para G₂ e G₃
- Atualizar a melhor solução global
3 Retorno do resultado
Retornar a melhor solução encontrada xᵇᵉˢᵗ
Agora podemos escrever a classe "C_AO_EMA", que será derivada da classe "C_AO" e implementará nosso algoritmo de otimização EMA. Os componentes principais são: popSize, tamanho da população (quantidade de agentes), r1, r2, coeficientes de absorção para diferentes grupos de agentes, riskAlpha, fator de risco. Em seguida, os parâmetros de configuração do algoritmo são inicializados e recebem valores padrão.
Funções:
- SetParams () atualiza os valores dos parâmetros do algoritmo a partir do array "params", além de verificar a validade desses parâmetros para garantir que estejam dentro dos limites permitidos.
- Init () inicializa o algoritmo antes de seu uso, recebendo parâmetros como os limites de busca (rangeMinP, rangeMaxP), o passo de variação dos parâmetros (rangeStepP) e o número de épocas (epochsP).
- Moving () é responsável pela movimentação dos agentes no espaço de busca e representa a lógica principal do algoritmo EMA para atualizar as posições dos agentes.
- Revision() realiza a revisão (revision) das estratégias dos agentes e inclui mecanismos para melhorar e alterar as posições atuais dos agentes.
- GetDecayRate() retorna o coeficiente de atenuação (decay rate).
Variáveis:
- r1, coeficiente de absorção para um grupo de agentes. Quanto maior o valor, mais fortemente os agentes desse grupo são "atraídos" por outros agentes ou pelas "melhores" soluções.
- r2, coeficiente de absorção para outro grupo de agentes.
- riskAlpha, fator de risco, ele determina o quanto os agentes estão expostos ao risco ao tomar decisões.
- groupSize, tamanho de cada um dos três grupos em que a população é dividida (popSize / 3).
- currentEpoch, número da iteração atual do algoritmo.
- totalEpochs, número total de iterações que serão executadas pelo algoritmo.
- tempPop, array temporário para armazenar as novas posições dos agentes durante sua movimentação. O tipo "S_AO_Agent" define a estrutura de um agente individual (solução).
A classe "C_AO_EMA" fornece uma implementação do algoritmo EMA para otimização numérica. O algoritmo divide a população em grupos com estratégias diferentes (definidas pelos coeficientes r1 e r2) e usa o fator de risco (riskAlpha) para equilibrar entre a diversificação do espaço de busca e a intensificação das "boas" soluções encontradas. As funções "Moving ()" e "Revision ()" contêm a lógica principal de otimização, "Init ()" inicializa o algoritmo, e "SetParams ()" permite alterar os valores dos parâmetros.
//———————————————————————————————————————————————————————————————————— class C_AO_EMA : public C_AO { public: //---------------------------------------------------------- ~C_AO_EMA () { } C_AO_EMA () { ao_name = "EMA"; ao_desc = "Exchange Market Algorithm"; ao_link = "https://www.mql5.com/ru/articles/18605"; popSize = 60; // Размер популяции r1 = 1.5; // Коэффициент поглощения для группы 2 r2 = 0.8; // Коэффициент поглощения для группы 3 riskAlpha = 0.3; // Фактор риска ArrayResize (params, 4); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "r1"; params [1].val = r1; params [2].name = "r2"; params [2].val = r2; params [3].name = "riskAlpha"; params [3].val = riskAlpha; } void SetParams () { popSize = (int)params [0].val; r1 = params [1].val; r2 = params [2].val; riskAlpha = params [3].val; // Проверка корректности параметров if (popSize < 6) popSize = 6; if (popSize % 3 != 0) popSize = ((popSize / 3) + 1) * 3; // Кратно 3 if (r1 < 0.0) r1 = 0.0; if (r2 < 0.0) r2 = 0.0; if (riskAlpha < 0.0) riskAlpha = 0.0; if (riskAlpha > 1.0) riskAlpha = 1.0; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving (); void Revision (); //------------------------------------------------------------------ double r1; // Коэффициент поглощения 1 double r2; // Коэффициент поглощения 2 double riskAlpha; // Фактор риска private: //--------------------------------------------------------- int groupSize; // размер каждой группы (popSize/3) int currentEpoch; // текущая эпоха int totalEpochs; // общее количество эпох S_AO_Agent tempPop []; // временная популяция для хранения новых позиций double GetDecayRate (); }; //————————————————————————————————————————————————————————————————————
O método de inicialização "Init" foi projetado para preparar o algoritmo para a execução. Ele recebe arrays de valores mínimos, valores máximos e passos para os parâmetros de busca, além do número de épocas (iterações). Primeiro, é chamada a função de inicialização padrão, responsável por configurar os intervalos de busca e verificar sua correção. Se ela retornar um resultado negativo, a inicialização do método também é interrompida.
Em seguida, é calculado o tamanho de cada grupo de agentes, já que a população é dividida em três partes iguais em número de elementos (divisão do tamanho total da população por 3). O contador da época atual é inicializado, e o número total de épocas para as quais o algoritmo foi definido é armazenado. Depois disso, é alocada memória para a população temporária, um array de agentes com o mesmo tamanho da população principal. Em um laço, cada agente desse array temporário é inicializado com o auxílio das coordenadas definidas no array "coords".
Quando todas essas etapas são concluídas com sucesso, é retornado o valor "true", o que indica que a preparação para a execução do algoritmo foi bem-sucedida.
//———————————————————————————————————————————————————————————————————— //--- Инициализация bool C_AO_EMA::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ groupSize = popSize / 3; currentEpoch = 0; totalEpochs = epochsP; // Инициализация временной популяции ArrayResize (tempPop, popSize); for (int i = 0; i < popSize; i++) { tempPop [i].Init (coords); } return true; } //————————————————————————————————————————————————————————————————————
O método "Moving" é a função principal que implementa a evolução dos agentes no algoritmo EMA. Ele é responsável por alterar as posições dos agentes no espaço de busca. Inicialização inicial (executada apenas uma vez):
- Se esta for a primeira execução do algoritmo, as coordenadas de todos os agentes são inicializadas aleatoriamente dentro dos intervalos definidos (rangeMin, rangeMax).
- As coordenadas de cada agente são ajustadas levando em conta o passo de discretização (rangeStep).
- A variável "revision" é definida como verdadeira, para que nas próximas chamadas desse método a inicialização não se repita.
Atualização da época e do coeficiente de atenuação:
- O contador da época atual (currentEpoch) é incrementado.
- É calculado o "decayRate" (coeficiente de atenuação), que diminui a cada época, influenciando o comportamento adaptativo do algoritmo.
Cópia da população atual. As coordenadas e os valores da função objetivo "f" de todos os agentes da população atual "a2 são copiados para a população temporária "tempPop", o que é feito para que as alterações em "tempPop" não afetem "a" até a conclusão de todos os cálculos na iteração atual.
FASE 1: Mercado equilibrado (Operadores de absorção). Esta fase modela o comportamento de agentes que "absorvem" informação dos melhores agentes. A população é dividida em três grupos:
- Grupo 1 (Elite): esses agentes (com índices de 0 a groupSize - 1) não são alterados nesta etapa, são os melhores agentes, e suas posições são mantidas.
- Grupo 2 (Operador de absorção 1): os agentes desse grupo (com índices de "groupSize" a "2*groupSize - 1") atualizam suas posições.
- Para cada agente desse grupo, um "líder" da Grupo 1 é escolhido aleatoriamente.
- As coordenadas do agente do Grupo 2 são deslocadas na direção das coordenadas do líder escolhido da Grupo 1. O deslocamento depende do coeficiente adaptativo "r1", que diminui levando em conta o "decayRate".
- As coordenadas são ajustadas aos intervalos e ao passo.
- Grupo 3 (Operador de absorção 2): os agentes desse grupo (com índices de "2 * groupSize" a "popSize - 1") também atualizam suas posições, mas de forma mais ativa.
- Para cada agente desse grupo, dois líderes são escolhidos aleatoriamente: um da Grupo 1 e outro da Grupo 2.
- As coordenadas do agente do Grupo 3 são deslocadas na direção de ambos os líderes escolhidos, usando o mesmo coeficiente adaptativo "r1".
- As coordenadas são ajustadas aos intervalos e ao passo.
FASE 2: Mercado oscilante (Operadores de busca). Esta fase modela o comportamento exploratório dos agentes, permitindo que eles procurem novas áreas no espaço.
- Grupo 2 (Operador de busca 1, risco moderado):
- Cada coordenada do agente desse grupo é atualizada.
- Com probabilidade de 50%, a coordenada é totalmente substituída pelo valor de "cB".
- Caso contrário, a coordenada é deslocada em relação ao "centro da elite" (valor médio das coordenadas dos agentes do Grupo 1) com adição de ruído. O ruído depende de "riskAlpha", da faixa das coordenadas e de "decayRate".
- As coordenadas são ajustadas aos intervalos e ao passo.
- Grupo 3 (Operador de busca 2, alto risco):
- Cada coordenada do agente desse grupo é atualizada.
- Com probabilidade igual a "riskAlpha", a coordenada é reinicializada aleatoriamente dentro do intervalo. Isso modela uma diversificação muito arriscada e em larga escala.
- Caso contrário, se o segundo número aleatório for menor que "0.5 + riskAlpha / 2.0", ocorre uma "busca ampla": a coordenada é deslocada por uma quantidade aleatória dentro de um determinado raio, que depende de "riskAlpha" e de "decayRate".
- Caso contrário, é aplicado o "aprendizado por oposição": a coordenada é deslocada na direção oposta. Um pequeno ruído aleatório é adicionado.
- As coordenadas são ajustadas aos intervalos e ao passo.
Cópia das alterações. Por fim, após todos os cálculos, as coordenadas atualizadas dos agentes de "tempPop" (para os Grupos 2 e 3) são copiadas de volta para a população principal "a". As coordenadas do Grupo 1 permanecem inalteradas (inicialmente, elas não foram copiadas de volta).
De modo geral, o método "Moving" implementa um processo iterativo no qual os agentes da população alteram suas posições, "aprendendo" com os melhores agentes e explorando o espaço de busca com diferentes níveis de risco.
//———————————————————————————————————————————————————————————————————— //--- Основной цикл алгоритма void C_AO_EMA::Moving () { // Начальная инициализация if (!revision) { for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; return; } currentEpoch++; double decayRate = GetDecayRate (); // Копирование текущей популяции во временную for (int i = 0; i < popSize; i++) { ArrayCopy (tempPop [i].c, a [i].c, 0, 0, WHOLE_ARRAY); tempPop [i].f = a [i].f; } // ФАЗА 1: Сбалансированный рынок (Поглощающие операторы) // Группа 1 (элита) - не изменяется // Индексы: 0 ... groupSize-1 // Группа 2 - поглощающий оператор 1 // Индексы: groupSize ... 2*groupSize-1 double adaptiveR1 = r1 * (1.0 - decayRate * 0.5); for (int i = groupSize; i < 2 * groupSize; i++) { // Каждый агент группы 2 выбирает случайного лидера из группы 1 int leaderIdx = u.RNDminusOne (groupSize); for (int c = 0; c < coords; c++) { tempPop [i].c [c] = a [i].c [c] + u.RNDprobab () * adaptiveR1 * (a [leaderIdx].c [c] - a [i].c [c]); tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } // Группа 3 - поглощающий оператор 2 // Индексы: 2*groupSize ... popSize-1 adaptiveR1 = r1 * (1.0 - decayRate * 0.3); for (int i = 2 * groupSize; i < popSize; i++) { // Выбор лидеров из групп 1 и 2 int leader1Idx = u.RNDminusOne (groupSize); int leader2Idx = groupSize + u.RNDminusOne (groupSize); for (int c = 0; c < coords; c++) { tempPop [i].c [c] = a [i].c [c] + u.RNDprobab () * adaptiveR1 * (a [leader1Idx].c [c] - a [i].c [c]) + u.RNDprobab () * adaptiveR1 * (a [leader2Idx].c [c] - a [i].c [c]); tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } // ФАЗА 2: Колеблющийся рынок (Поисковые операторы) // Группа 2 - поисковый оператор 1 (умеренный риск) for (int i = groupSize; i < 2 * groupSize; i++) { for (int c = 0; c < coords; c++) { double range = rangeMax [c] - rangeMin [c]; if (u.RNDprobab () < 0.5) { tempPop [i].c [c] = cB [c];// tempPop [i].c [c] + delta; } else { // Поиск вокруг центра элиты double eliteCenter = 0.0; for (int j = 0; j < groupSize; j++) { eliteCenter += a [j].c [c]; } eliteCenter /= (double)groupSize; double noise = riskAlpha * range * u.RNDfromCI (-0.5, 0.5) * (1.0 - decayRate * 0.5); tempPop [i].c [c] = eliteCenter + noise; } // Проверка границ tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } // Группа 3 - поисковый оператор 2 (высокий риск) for (int i = 2 * groupSize; i < popSize; i++) { for (int c = 0; c < coords; c++) { double range = rangeMax [c] - rangeMin [c]; if (u.RNDprobab () < riskAlpha) { // Полная реинициализация tempPop [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); } else if (u.RNDprobab () < 0.5 + riskAlpha / 2.0) { // Широкий поиск double searchRadius = 2.0 * riskAlpha * range * (1.0 - decayRate * 0.3); double delta = u.RNDfromCI (-searchRadius, searchRadius); tempPop [i].c [c] = tempPop [i].c [c] + delta; } else { // Оппозиционное обучение double worstCenter = 0.0; int worstCount = groupSize / 2; for (int j = popSize - worstCount; j < popSize; j++) { worstCenter += a [j].c [c]; } worstCenter /= (double)worstCount; // Движение в противоположном направлении от худших tempPop [i].c [c] = 2.0 * tempPop [i].c [c] - worstCenter; // Добавляем небольшой шум double noise = riskAlpha * range * u.RNDfromCI (-0.1, 0.1); tempPop [i].c [c] += noise; } // Проверка границ tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } // Копирование из временной популяции в основную (кроме группы 1) for (int i = groupSize; i < popSize; i++) { ArrayCopy (a [i].c, tempPop [i].c, 0, 0, WHOLE_ARRAY); } } //————————————————————————————————————————————————————————————————————
O método "GetDecayRate" foi projetado para calcular o coeficiente de atenuação, que controla o grau de mudança no comportamento do algoritmo ao longo da execução. Primeiro, ele verifica se o número total de épocas "totalEpochs" é igual a "0" ou negativo; nesse caso, retorna zero para evitar divisão por zero e garantir uma execução segura. Em seguida, calcula o progresso da busca como a fração de épocas transcorridas em relação ao total, "progress". O valor de "progress" cresce de "0" para "1" à medida que o algoritmo avança.
Usando "progress", o método retorna um valor baseado em uma função sigmoide de forma exponencial. Essa função garante uma transição suave do coeficiente de atenuação de "0" para "1", fazendo com que no início da busca (quando "progress" está próximo de 0) o valor fique perto de "0" e, no final (quando "progress" se aproxima de 1), se aproxime de 1. Essa transição não linear ajuda a equilibrar o balanço entre a diversificação do espaço e a intensificação das soluções já encontradas.
Especificamente, é usada uma função sigmoide na forma: 1 / (1 + exp(-10 * (progress - 0.5))). Esse formato garante uma transição mais acentuada perto do meio do processo, "progress ≈ 0.5", o que favorece uma mudança mais dinâmica do coeficiente durante a execução do algoritmo.
//———————————————————————————————————————————————————————————————————— //--- Получение коэффициента затухания double C_AO_EMA::GetDecayRate () { if (totalEpochs <= 0) return 0.0; // Нелинейное затухание для лучшего баланса эксплуатации/исследования double progress = (double)currentEpoch / (double)totalEpochs; // Использование сигмоидной функции для плавного перехода return 1.0 / (1.0 + MathExp (-10.0 * (progress - 0.5))); } //————————————————————————————————————————————————————————————————————
O método "Revision", no contexto do algoritmo EMA, é responsável por atualizar as melhores soluções encontradas (ótimo global) em cada iteração. É criado um array estático temporário "aT" do tipo "S_AO_Agent", e "static" significa que esse array será criado uma única vez na primeira chamada da função e manterá seu estado entre as chamadas. "ArrayResize (aT, popSize)"; altera o tamanho desse array temporário para que ele possa armazenar "popSize" (tamanho da população) agentes. Esse array será usado para a classificação. É chamada a função "Sorting" do objeto auxiliar "u". Essa função classifica a população principal de agentes "a" de acordo com sua aptidão (valor da função objetivo "f").
Importante: após essa operação, o array "a" será classificado de tal forma que o agente com o melhor valor da função objetivo "f" ficará na primeira posição (índice "0"). Em algoritmos de minimização, esse será o agente com o menor "f"; em algoritmos de maximização, com o maior "f". As coordenadas do melhor agente da população atual, que agora está em "a [0]" após a classificação, são copiadas para o array "cB", que armazena as coordenadas da melhor solução encontrada pelo algoritmo até o momento. O valor da função objetivo do melhor agente é atribuído à variável "fB". Essa variável armazena o valor da função objetivo correspondente às melhores coordenadas "cB".
//———————————————————————————————————————————————————————————————————— //--- Обновление лучших решений void C_AO_EMA::Revision () { static S_AO_Agent aT []; ArrayResize (aT, popSize); u.Sorting (a, aT, popSize); ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY); fB = a [0].f; } //————————————————————————————————————————————————————————————————————
Resultados dos testes
Os resultados após a execução dos testes poderiam ser melhores, no entanto o algoritmo funciona e apresenta certas capacidades de busca.
EMA|Exchange Market Algorithm|60.0|1.5|0.8|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.6706604188712635
25 Hilly's; Func runs: 10000; result: 0.42759923501764946
500 Hilly's; Func runs: 10000; result: 0.252217676777693
=============================
5 Forest's; Func runs: 10000; result: 0.7419215403847332
25 Forest's; Func runs: 10000; result: 0.38137087707323236
500 Forest's; Func runs: 10000; result: 0.19454127467011006
=============================
5 Megacity's; Func runs: 10000; result: 0.38769230769230767
25 Megacity's; Func runs: 10000; result: 0.21323076923076928
500 Megacity's; Func runs: 10000; result: 0.09672307692307769
=============================
All score: 3.36596 (37.40%)
Na visualização, é possível notar dispersão nos resultados em funções de baixa dimensionalidade.

EMA na função de teste Hilly

EMA na função de teste Forest

EMA na função de teste Megacity
Após a realização dos testes, o algoritmo EMA será apresentado em nossa tabela de classificação dos algoritmos populacionais de otimização apenas para fins de ознакомления.
| № | 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 | 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 |
| 13 | 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 |
| 14 | 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 |
| 15 | 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 |
| 16 | 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 |
| 17 | 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 |
| 18 | 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 |
| 19 | 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 |
| 20 | 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 |
| 21 | 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 |
| 22 | 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 |
| 23 | 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 |
| 24 | 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 |
| 25 | 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 |
| 26 | 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 |
| 27 | 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 |
| 28 | 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 |
| 29 | 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 |
| 30 | 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 |
| 31 | (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 |
| 32 | 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 |
| 33 | 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 |
| 34 | 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 |
| 35 | 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 |
| 36 | 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 |
| 37 | 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 |
| 38 | 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 |
| 39 | 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 |
| 40 | 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 |
| 41 | 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 |
| 42 | 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 |
| 43 | 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 |
| 44 | 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 |
| 45 | ADAMm | adaptive moment estimation M | 0,88635 | 0,44766 | 0,26613 | 1,60014 | 0,84497 | 0,38493 | 0,16889 | 1,39880 | 0,66154 | 0,27046 | 0,10594 | 1,03794 | 4,037 | 44,85 |
| EMA | exchange_market_algorithm | 0,67066 | 0,42759 | 0,25221 | 1,35046 | 0,74192 | 0,38137 | 0,19454 | 1,31783 | 0,38769 | 0,21323 | 0,09672 | 0,69764 | 3,366 | 37,40 | |
| 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 | |
Considerações finais
O algoritmo EMA apresentado, embora tenha capacidades básicas de busca, não demonstra desempenho suficiente para entrar no top-45 da tabela de classificação dos algoritmos populacionais de otimização. Sua estrutura monolítica e a ausência de mecanismos explícitos para superar problemas amplamente conhecidos, relacionados ao aprisionamento em extremos locais, apontam para uma série de possíveis desvantagens.
Sem um enriquecimento substancial de sua funcionalidade e um controle adaptativo de comportamento, o EMA continuará sendo um algoritmo básico com potencial limitado para resolver problemas complexos de otimização, o que explica sua ausência no ranking das meta-heurísticas de ponta, no entanto as ideias incorporadas ao algoritmo são bastante interessantes e promissoras para a nossa coleção de ideias para aperfeiçoamentos futuros.

Figura 2. Gradação de cores dos algoritmos pelos testes correspondentes

Figura 3. Histograma dos resultados de testes dos algoritmos (na escala de 0 a 100, quanto maior, melhor, onde 100 — resultado teórico máximo possível, no arquivo compactado o script para cálculo da tabela de classificação)
Vantagens e desvantagens do algoritmo EMA:
Vantagens:
- implementação simples
Desvantagens:
- baixa precisão de convergência
Um arquivo compactado com as versões atualizadas dos códigos dos algoritmos está anexado ao artigo. O autor do artigo não se responsabiliza pela precisão absoluta na descrição dos algoritmos canônicos, pois muitos deles foram modificados para melhorar as capacidades de busca. As conclusões e julgamentos apresentados nos artigos baseiam-se nos resultados dos experimentos realizados.
Programas utilizados no artigo
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | #C_AO.mqh | Arquivo de inclusão | Classe base dos algoritmos populacionais de otimização |
| 2 | #C_AO_enum.mqh | Arquivo de inclusão | Enumeração dos algoritmos populacionais de otimização |
| 3 | TestFunctions.mqh | Arquivo de inclusão | Biblioteca de funções de teste |
| 4 | TestStandFunctions.mqh | Arquivo de inclusão | Biblioteca de funções do ambiente de testes |
| 5 | Utilities.mqh | Arquivo de inclusão | Biblioteca de funções auxiliares |
| 6 | CalculationTestResults.mqh | Arquivo de inclusão | Script para cálculo dos resultados na tabela comparativa |
| 7 | Testing AOs.mq5 | Script | Ambiente de testes unificado para todos os algoritmos populacionais de otimização |
| 8 | Simple use of population optimization algorithms.mq5 | Script | Exemplo simples de uso de algoritmos populacionais de otimização sem visualização |
| 9 | Test_AO_EMA.mq5 | Script | Ambiente de testes para EMA |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/18605
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.
Explorando modelos de regressão para inferência causal e trading
O Filtro de Kalman para Estratégias de Reversão à Média no Forex
Está chegando o novo MetaTrader 5 e MQL5
Automatizando Estratégias de Trading em MQL5 (Parte 10): Desenvolvendo a Estratégia Trend Flat Momentum
- 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