English Русский 中文 Português
preview
Algoritmo del átomo artificial — Artificial Atom Algorithm (A3)

Algoritmo del átomo artificial — Artificial Atom Algorithm (A3)

MetaTrader 5Sistemas comerciales |
57 7
Andrey Dik
Andrey Dik

Índice

  1. Introducción
  2. Implementación del algoritmo
  3. Resultados de las pruebas


Introducción

En el trading algorítmico, una de las tareas clave es la optimización de los parámetros de las estrategias de trading. Los tráders se enfrentan a diario a la necesidad de ajustar numerosas variables: los periodos de los indicadores, los niveles de stop-loss y take-profit, el tamaño de las posiciones, los filtros temporales y decenas de otros parámetros. Cada combinación de estos parámetros puede modificar radicalmente el rendimiento de la estrategia, y seguimos buscando algoritmos eficientes capaces de encontrar la mejor solución en un tiempo razonable.

En este artículo analizaremos otro algoritmo de optimización, concretamente el algoritmo del átomo artificial (Artificial Atom Algorithm, A3), un algoritmo metaheurístico de optimización inspirado en las reacciones químicas. Fue desarrollado por científicos turcos y presentado por primera vez a la comunidad científica en 2018.


Implementación del algoritmo

Durante el trabajo en este algoritmo, me encontré con una serie de ambigüedades en la descripción de los autores. Algunos operadores están descritos con un nivel de detalle insuficiente, lo que dificulta su implementación práctica. Por ello, presentaré la descripción original del algoritmo; sin embargo, la implementación se ha realizado según mi propio criterio, basándome en mi experiencia personal y en los principios estructurales característicos de este tipo de algoritmos. Esto supone una laguna importante en el trabajo de los autores, lo que deja al desarrollador un amplio margen de interpretación. Además, durante el trabajo se introdujeron modificaciones en la clase base, que también se analizarán una vez que se haya expuesto la implementación principal del algoritmo.

El algoritmo simula la interacción entre átomos y electrones para encontrar la solución óptima. Los componentes principales son los átomos, que representan soluciones potenciales al problema. Los electrones son variables de solución. El enlace covalente es un operador que permite conservar y replicar las mejores soluciones, mientras que el enlace iónico es un operador que permite explorar el espacio de búsqueda y alcanzar el óptimo global.

El algoritmo comienza generando aleatoriamente un conjunto de átomos; a continuación, evalúa la calidad de cada átomo mediante una función objetivo y, posteriormente, aplica operadores de enlace covalente e iónico para mejorar las soluciones (lamentablemente, no se describe cómo se hace esto). A continuación, hay que evaluar el efecto de los electrones, aunque, de nuevo, no está claro cómo hacerlo. A continuación, es necesario ordenar los electrones y los átomos. Bien, podemos ordenar los átomos (las soluciones), pero cómo se pueden ordenar los electrones es un misterio (es como intentar ordenar las extremidades de una persona: manos y pies, derechas e izquierdas; da igual, las ordenaremos). Luego se debe repetir el proceso de forma iterativa hasta que se cumpla el criterio de parada. Bueno, como suele decirse, no podemos ignorar lo que está escrito; hay que intentar construir un algoritmo a partir de lo que hay (las ideas de los autores).

Finalmente, le presento el pseudocódigo del algoritmo A3 que he reelaborado.

INICIALIZACIÓN Parámetros de entrada:

popSize: número de átomos (por defecto, 10)
covalentRate: coeficiente de enlace covalente (por defecto, 0,1)
rangeMin [], rangeMax [] — límites de búsqueda para cada variable de solución
rangeStep [] — paso de discretización

Cálculos previos:

Calcular el número de átomos de élite:
covalentCount = floor (popSize × covalentRate)
Limitar: mínimo 1, máximo (popSize - 1)

Crear una población de popSize átomos
Para cada átomo, inicializar:
Coordenadas c []
Peor solución local cW []
Aptitud f, peor valor de aptitud fW


CICLO PRINCIPAL DE OPTIMIZACIÓN
PASO 1: Primera iteración (si revision = false)
PARA cada átomo i de 0 a popSize-1:
PARA cada coordenada j de 0 a coords-1:
Generar un valor aleatorio en el rango [rangeMin [j], rangeMax [j]]
Aplicar la discretización según rangeStep [j]
Guardar en a [i].c [j]
    
Establecer revision = true
Finalizar la iteración

PASO 2: Actualización de las posiciones de los átomos (Moving)
PARA cada átomo i de 0 a popSize-1:
    
SI i ≤ covalentCount (el átomo es de élite):
PARA cada coordenada c de 0 a coords-1:
            
SI random() < covalentRate (con una probabilidad del 10 %):
// Movimiento hacia la mejor solución global
step = random() × (cB [c] - a [i].c [c]) × covalentCount
a[i].c[c] = a[i].c[c] + step
            
EN CASO CONTRARIO (con una probabilidad del 90 %):
// Generación mediante PowerDistribution alrededor de la mejor solución
a[i].c[c] = PowerDistribution(centro=cB[c], min=rangeMin[c], max=rangeMax[c], potencia=20)
Aplicar la discretización a a [i].c [c]
    
EN CASO CONTRARIO (el átomo no es de élite):
PARA cada coordenada c de 0 a coords-1:
// Seleccionar un átomo de élite al azar como referencia
ind = entero_aleatorio(0, covalentCount)
            
// Movimiento desde la peor solución local hacia la posición del átomo de élite
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 la discretización a a [i].c [c]

PASO 3: Cálculo de la aptitud
PARA cada átomo i:
Calcular la aptitud a[i].f mediante la función objetivo

PASO 4: Actualización de la memoria y ordenación (Revision)
// Actualización de las peores soluciones locales
PARA cada átomo i de 0 a popSize-1:
SI a[i].f < a[i].fW:
a [i].fW = a [i].f
Copiar a [i].c en a [i].cW
Actualizar el peor valor global fW

// Ordenación de la población en orden descendente de aptitud
Ordenar el array de átomos a [] según el campo f (de mejor a peor)

// Actualización del mejor global
SI a [0].f > fB:
fB = a [0].f
Copiar a [0].c en cB

// Actualización del peor global
SI a [popSize-1].f < fW:
fW = a [popSize-1].f
Copiar a [popSize-1].c en cW

PASO 5: Comprobación del criterio de parada
SI se ha alcanzado el número máximo de iteraciones:
Finalizar el algoritmo
EN CASO CONTRARIO:
Ir al PASO 2

Ahora podemos pasar a la implementación práctica en el código. Escribiremos una clase que represente la implementación del algoritmo del sistema atómico artificial, basado en la metáfora de los átomos y sus enlaces. Esta clase hereda las capacidades básicas de la clase «C_AO» y las amplía en el contexto del algoritmo A3.

Métodos para configurar los parámetros: la función «SetParams()» lee los valores actuales del array de parámetros y los almacena en las variables correspondientes. Esto permite configurar el algoritmo con flexibilidad antes de ejecutarlo. Inicialización: el método «Init()» prepara los parámetros y la estructura para comenzar a funcionar, obteniendo los intervalos de valores y los pasos de variación de las variables, así como el número de épocas. Operaciones principales del algoritmo: los métodos Moving() y Revision() llevan a cabo los procesos de desplazamiento y revisión de los átomos dentro del sistema, lo que corresponde a las etapas de búsqueda de la solución óptima. Parámetros y variables:

  • popSize — tamaño de la población de átomos (número de elementos de la comunidad).
  • covalentRate — coeficiente que caracteriza la formación de enlaces entre átomos.
  • covalentCount — número interno de átomos que participan en la formación de enlaces (se utiliza dentro de la clase para realizar cálculos).

La idea general de la clase consiste en modelar el comportamiento de un sistema de átomos teniendo en cuenta sus enlaces, lo cual se realiza mediante parámetros y métodos de funcionamiento; sobre esta base se implementan la lógica de búsqueda y la optimización basadas en estos conceptos.

//————————————————————————————————————————————————————————————————————
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;    // número de átomos (m)
    covalentRate = 0.1;   // coeficiente de estado covalente (β)

    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  [],  // valores mínimos
             const double &rangeMaxP  [],  // valores máximos
             const double &rangeStepP [],  // paso de variación
             const int     epochsP = 0);   // número de épocas

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double covalentRate;       // coeficiente de enlace covalente (β)

  private: //---------------------------------------------------------
  int    covalentCount;      // número de átomos para formar enlaces covalentes
};
//————————————————————————————————————————————————————————————————————

El método «Init()» es un paso clave en la preparación del algoritmo para su ejecución. Su función principal es inicializar todos los parámetros y estructuras necesarios antes de iniciar el proceso de optimización. El método recibe cuatro parámetros de entrada:

  1. rangeMinP [] — valores mínimos para cada variable de solución que el algoritmo va a optimizar.
  2. rangeMaxP [] — valores máximos para cada variable de solución.
  3. rangeStepP [] — paso de variación para cada variable. Estos pasos se utilizan para la discretización y para determinar la precisión de la búsqueda.
  4. epochsP — número de épocas (iteraciones) de ejecución del algoritmo.

Lo primero que se hace es llamar a «StandardInit()», un método de la clase básica (C_AO) que lleva a cabo las acciones de inicialización comunes a todos los algoritmos de optimización. Si «StandardInit()» se ha completado con éxito, el método procede a calcular los parámetros específicos del algoritmo del átomo artificial. En concreto, se calcula «covalentCount», es decir, el número de átomos que participarán en enlaces covalentes. El cálculo se realiza multiplicando «popSize» (número total de átomos de la población) por «covalentRate» (coeficiente de enlace covalente). El resultado se redondea hacia abajo al entero inferior más cercano mediante «MathFloor»; el número de átomos debe ser un número entero. A continuación, se realizan comprobaciones para garantizar que el valor de «covalentCount» sea lógicamente correcto. Si se completan con éxito todos los pasos de inicialización, el método devuelve «true», lo que indica que el algoritmo está listo para ejecutarse.

De este modo, el método «Init» garantiza que el algoritmo del átomo artificial esté totalmente listo para su ejecución, inicializando tanto los parámetros comunes a los algoritmos de optimización como los específicos de este modelo de «átomos» y «enlaces».

/————————————————————————————————————————————————————————————————————
//--- Inicialización
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;
}
//————————————————————————————————————————————————————————————————————

El método «Moving()» de la clase «C_AO_A3» es uno de los pasos fundamentales del algoritmo del átomo artificial, ya que lleva a cabo el proceso de «movimiento» o cambio de posición de los átomos en el espacio de búsqueda. Modela los cambios evolutivos en una población de átomos con el objetivo de encontrar la solución óptima. Al inicio de cada llamada al método «Moving()», se comprueba el estado «revision». Este indicador señala si la población se ha inicializado previamente.

La inicialización inicial (si «revision» es «false») significa que el método «Moving()» se llama por primera vez o tras un restablecimiento del estado, y que la población de átomos aún no se ha inicializado. En este caso ocurre lo siguiente: se itera por todos los átomos («popSize») y por todas las coordenadas («coords», dimensiones del espacio de búsqueda). Para cada átomo y para cada coordenada, su posición se inicializa con un valor aleatorio dentro del rango especificado. Tras la inicialización en la primera llamada, «revision» se establece en «true», de modo que en las llamadas posteriores a «Moving()» se ejecute ya el proceso principal de desplazamiento, en lugar de una nueva inicialización. El método finaliza, ya que se ha completado la tarea de la primera llamada.

Proceso principal de desplazamiento de los átomos (si «revision» es «true»): cuando la población ya está inicializada, el método «Moving» comienza a actualizar las posiciones de los átomos, imitando su movimiento; para ello, los átomos se dividen en dos grupos según su índice «i» y el valor de «covalentCount».

Grupo 1: Átomos que participan en un enlace covalente (i &lt;= covalentCount). Para cada átomo de este grupo y para cada una de sus coordenadas se genera un número aleatorio; si dicho número es menor que el coeficiente de enlace covalente, el átomo modifica su coordenada según la fórmula que imita la atracción o la interacción con la mejor solución actual «cB[c]», donde «cB» representa las mejores coordenadas encontradas en la población; en la fórmula también interviene «covalentCount».

Si el número aleatorio es mayor o igual que «covalentRate», el átomo cambia su coordenada utilizando la función «u.PowerDistribution». Esta función genera un valor desplazado hacia «cB [c]», pero con cierta dispersión aleatoria, imitando una búsqueda aleatoria más amplia o una «dispersión».

Independientemente de cómo haya cambiado la coordenada, esta se procesa de nuevo mediante la función «u.SeInDiSp» para ajustarla al rango admisible y a la malla discreta.

Grupo 2: Átomos que no participan en un enlace covalente (i &gt; covalentCount): para cada átomo de este grupo y para cada una de sus coordenadas, se elige un índice aleatorio «ind» dentro del intervalo comprendido entre «0» y «covalentCount». Esto significa que un átomo «no enlazado» interactuará con uno de los átomos que participan en el enlace covalente.

La coordenada del átomo cambia, simulando la interacción o el alejamiento respecto del átomo enlazado seleccionado y de su peor posición local previa, y la coordenada se procesa de nuevo con «u.SeInDiSp» para que se ajuste a las restricciones.

El método «Moving()» constituye el núcleo del proceso de búsqueda. Implementa reglas estocásticas para la actualización de las posiciones de los átomos, inspiradas en un modelo físico. Los átomos que participan en un «enlace covalente» tienden hacia la mejor solución encontrada, mientras que los demás átomos exploran el espacio interactuando con los átomos enlazados covalentemente.

//————————————————————————————————————————————————————————————————————
//--- Paso principal del algoritmo
void C_AO_A3::Moving ()
{
  // Inicialización de la población
  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]);
      }
    }
  }
}
//————————————————————————————————————————————————————————————————————

El método «Revision()» de la clase «C_AO_A3» se encarga de actualizar la información sobre la mejor y la peor solución encontradas en la población de átomos en la iteración actual del algoritmo. Se trata de una etapa importante, ya que estos valores «mejores» y «peores» (tanto los comunes a toda la población como los individuales de cada átomo) se utilizan para orientar la búsqueda posterior en el siguiente paso de movimiento. Actualización de la peor solución local individual para cada átomo. Se llama a la función «u.Sorting()», que ordena toda la población de átomos «a» en función de su aptitud «f».

Tras la ordenación, el átomo «a [0]» es el mejor átomo de la población actual, por lo que «fB» se actualiza con el valor «a [0].f». Las coordenadas «a [0].c» se copian en «cB» (las mejores coordenadas globales). El átomo «a [popSize - 1]» es el peor átomo de la población actual (tras la ordenación). Se compara el valor de aptitud «a [popSize - 1].f» con «fW» (el peor valor de aptitud global encontrado hasta el momento). Si «a [popSize - 1].f» es realmente peor, entonces «fW» se actualiza con el valor «a [popSize - 1].f» y las coordenadas «a [popSize - 1].c» se copian en «cW» (las peores coordenadas globales).

El método «Revision» realiza un seguimiento sistemático del progreso de la optimización. Actualiza los estados individuales relevantes de cada agente —en esta implementación, sobre todo los peores estados locales— así como los estados globales “mejor” y “peor” de toda la población.

//————————————————————————————————————————————————————————————————————
//--- Actualización de las mejores y peores soluciones
void C_AO_A3::Revision ()
{
  // Actualizamos la peor solución local
  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);
  }
}
//————————————————————————————————————————————————————————————————————

La solución ha quedado muy compacta. Ahora pasemos a la clase básica de la que heredan todos los algoritmos de optimización; como ya he mencionado anteriormente, se han producido cambios en ella, así que describiremos la clase.

Una estructura sencilla para almacenar los parámetros del algoritmo. Permite gestionar de forma dinámica los ajustes de cualquier algoritmo de optimización.

//——————————————————————————————————————————————————————————————————————————————
struct S_AlgoParam
{
    double val;
    string name;
};
//——————————————————————————————————————————————————————————————————————————————

La estructura «S_AO_Agent» está diseñada para representar a un «agente» individual, que constituye una posible solución a un problema de optimización y se desplaza por un espacio de búsqueda multidimensional. La estructura «S_AO_Agent» contiene una serie de campos que pueden clasificarse en varias categorías: 1. Coordenadas (posición en el espacio de búsqueda):

  • c — coordenadas actuales del agente. Es el array principal que almacena la posición actual del agente.
  • cP — coordenadas anteriores del agente. Se utiliza para realizar un seguimiento del movimiento del agente.
  • cB — mejores coordenadas que ha alcanzado este agente concreto. Esta información ayuda al agente a «recordar» sus posiciones más exitosas y a volver a ellas, o a utilizarlas para continuar la búsqueda.
  • cW — peores coordenadas del agente.

2. Aptitud (calidad de la solución):

  • f — aptitud actual del agente. Es el valor de la función objetivo calculado para las coordenadas actuales «c».
  • fP — aptitud anterior del agente. Al igual que «cP», se utiliza para hacer un seguimiento de los cambios en la aptitud.
  • fB — mejor aptitud que ha alcanzado este agente. Corresponde a las coordenadas «cB».
  • fW — peor aptitud del agente. Corresponde a las coordenadas «cW».

3. Datos auxiliares: cnt — contador entero.

La estructura también incluye el método público «Init()», que sirve para inicializar el agente. Acepta un único parámetro de tipo entero, «coords», que indica la dimensionalidad del espacio de búsqueda (es decir, el número de coordenadas que debe tener el agente).

Al llamar a «Init», se producen las siguientes acciones: los arrays de coordenadas (c, cP, cB, cW) se redimensionan dinámicamente al tamaño «coords» especificado. Esto garantiza que cada agente disponga del número adecuado de dimensiones para trabajar con un problema concreto. Los valores de aptitud (f, fP, fB, fW) se inicializan: «f», «fP» y «fB» se establecen en «-DBL_MAX» (el menor valor posible de «double»), mientras que «fW» se establece en «DBL_MAX» (el mayor valor posible). Es una práctica habitual al buscar el máximo (para f, fP y fB, el valor actual será casi siempre mayor que el inicial), mientras que para «fW» (el peor) se establece el valor más alto, de modo que cualquier primer valor encontrado sea mejor (menor) que este. El contador «cnt» se inicializa a cero.

En general, «S_AO_Agent» proporciona una estructura de datos completa para cada individuo de la población en un algoritmo de optimización evolutivo basado en poblaciones, lo que permite hacer un seguimiento tanto de su estado actual como de su historial de éxitos y fracasos.

//——————————————————————————————————————————————————————————————————————————————
struct S_AO_Agent
{
    double c  []; //coordenadas
    double cP []; //coordenadas anteriores
    double cB []; //mejores coordenadas
    double cW []; //peores coordenadas

    double f;     //aptitud
    double fP;    //aptitud anterior
    double fB;    //mejor valor de aptitud
    double fW;    //peor aptitud

    int    cnt;   //contador

    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;
    }
};
//——————————————————————————————————————————————————————————————————————————————

Analicemos la clase base «C_AO». Estructura general de la clase C_AO. Miembros públicos (public). Estos son los campos y métodos a los que se puede acceder desde fuera de la clase.

Datos que describen el estado general de la mejor/peor solución:

  • cB [] — array de coordenadas de la mejor solución encontrada por el algoritmo completo (globalmente).
  • cW [] — array de coordenadas de la peor solución encontrada por el algoritmo completo (globalmente).
  • fB — valor de aptitud de la mejor solución global (corresponde a «cB»).
  • fW — valor de aptitud de la peor solución global (corresponde a «cW»).

Tenga en cuenta que se trata de las mejores y peores soluciones globales, a diferencia de «fB» y «fW» dentro de «S_AO_Agent», que se refieren a un agente concreto.

Datos relacionados con la población y los parámetros:

  • a [] — array de objetos «S_AO_Agent». Se trata de una «población» de agentes, cada uno de los cuales representa una posible solución.
  • params [] — array de objetos «S_AlgoParam». Aquí se almacenan los parámetros específicos de este algoritmo (por ejemplo, coeficientes de influencia, probabilidades, etc.), que se pueden configurar.
  • revision — indicador que señala la necesidad de revisar y actualizar el estado.
Métodos virtuales (requieren implementación en las clases derivadas). Estos métodos se declaran como «virtual»; pueden sobrescribirse en las clases derivadas para implementar la lógica específica de una versión concreta del algoritmo.
  • SetParams () — método para configurar parámetros específicos del algoritmo; en las clases derivadas, rellenará el array «params».
  • Init () — método principal de inicialización del algoritmo. Recibe los rangos de búsqueda (mínimo, máximo y paso) y el número de épocas. Devuelve «true» si la inicialización se realiza correctamente.
  • Moving () — método que implementa la lógica de «desplazamiento» o «evolución» de los agentes en el espacio de búsqueda. Este es el núcleo del algoritmo.
  • Revision () — método correspondiente a la fase de «revisión» o «actualización» del estado del algoritmo.
  • Injection () — método para «inyectar» un valor determinado en la coordenada de un agente concreto.

Parámetros del espacio de búsqueda; arrays que almacenan:

  • rangeMin [] — valores mínimos para cada coordenada del espacio de búsqueda.
  • rangeMax [] — valores máximos para cada coordenada del espacio de búsqueda.
  • rangeStep [] — pasos de discretización para cada coordenada (útil para espacios discretos o para definir la granularidad de la búsqueda).

Parámetros internos del algoritmo:

  • coords — número de dimensiones (coordenadas) en el espacio de búsqueda.
  • popSize — tamaño de la población (número de agentes) en el algoritmo.

Utilidades auxiliares: «u» — objeto de una clase auxiliar que contiene funciones generales, como la generación de números aleatorios, operaciones matemáticas, etc.

Método de inicialización estándar (protected): «StandardInit ()» — este método sirve para ejecutar la parte general y estándar de la inicialización para todos los algoritmos derivados, inicializa el generador de números aleatorios utilizando la hora actual del sistema como semilla inicial, establece «fB» en infinito negativo y «fW» en infinito positivo para el seguimiento posterior de la mejor solución global y la peor solución global. Además, establece el indicador «revision» en «false», comprueba y establece el número de coordenadas (dimensionalidad del espacio), modifica el tamaño de los arrays internos (rangeMin, rangeMax, rangeStep, cB, cW) de acuerdo con el número de coordenadas, modifica el tamaño del array de agentes «a» según el tamaño de la población «popSize» y, para cada agente, invoca su propio método «Init» para inicializar los arrays internos de coordenadas del agente. El método copia los rangos de búsqueda de entrada en sus variables internas, devuelve «true» si la inicialización se realiza correctamente y «false» en caso de errores (por ejemplo, si los tamaños de los arrays de rangos de entrada no coinciden).

La clase «C_AO» sirve como clase básica para implementaciones concretas de algoritmos de optimización. Cada nuevo algoritmo (una versión concreta de AO) heredará de «C_AO» y sobrescribirá los métodos virtuales (SetParams, Init, Moving, Revision, Injection) para implementar su lógica específica, mientras que «StandardInit» ayuda a evitar la duplicación de código en los pasos comunes de inicialización.

//——————————————————————————————————————————————————————————————————————————————
class C_AO
{
  public: //--------------------------------------------------------------------
  C_AO () { }
  ~C_AO () { }

  double      cB     []; //mejores coordenadas
  double      cW     []; //peores coordenadas
  double      fB;        //FF de las mejores coordenadas
  double      fW;        //FF de las peores coordenadas
  S_AO_Agent  a      []; //agentes
  S_AlgoParam params []; //parámetros del algoritmo
  bool        revision;

  virtual void SetParams () { }
  virtual bool Init (const double &rangeMinP  [], //límite mínimo del rango de búsqueda
                     const double &rangeMaxP  [], //límite máximo del rango de búsqueda
                     const double &rangeStepP [], //paso de búsqueda
                     const int     epochsP = 0)   //número de épocas
  { 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;      //nombre ao;
  string ao_desc;      //descripción ao
  string ao_link;      //enlace ao

  double rangeMin  []; //límite mínimo del rango de búsqueda
  double rangeMax  []; //límite máximo del rango de búsqueda
  double rangeStep []; //paso de búsqueda

  int    coords;       //número de coordenadas
  int    popSize;      //tamaño de la población

  C_AO_Utilities u;     //funciones auxiliares

  bool StandardInit (const double &rangeMinP  [], //límite mínimo del rango de búsqueda
                     const double &rangeMaxP  [], //límite máximo del rango de búsqueda
                     const double &rangeStepP []) //paso de búsqueda
  {
    int seed = (int)GetTickCount64 ();
    MathSrand (seed); //reinicio del generador

    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 de las pruebas

Ahora veamos cómo funciona el algoritmo A3. Resultados medios con una población pequeña.

A3|Artificial Atom Algorithm|30.0|0.2|
=============================
5 Hilly's; Func runs: 10 000; result: 0,7074464987418227
25 Hilly's; Func runs: 10 000; 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%)

En la visualización se aprecia una dispersión de los resultados en las funciones de baja dimensionalidad (líneas verdes), aunque cabe destacar la buena cobertura del espacio por parte de las soluciones.

Hilly

A3 en la función de prueba Hilly

Forest

A3 en la función de prueba Forest

Megacity

A3 en la función de prueba Megacity

En nuestra tabla de clasificación, el algoritmo A3 se presenta a título informativo.

N.º AO Descripción Hilly Hilly
Final
Forest Forest
Final
Megacity (discreta) Megacity
Final
Final
Result
% de
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 optimization 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


Conclusiones

En este artículo se ha presentado la implementación del algoritmo del átomo artificial A3 en el lenguaje MQL5 para resolver problemas de optimización de estrategias de trading. Aunque los autores no ofrecieron una descripción teórica completa en el trabajo original, se logró crear una implementación práctica funcional. Las pruebas realizadas han demostrado que A3 ofrece un buen equilibrio entre la velocidad de ejecución y la calidad de las soluciones obtenidas. Solo dos parámetros ajustables simplifican considerablemente el proceso de adaptar el algoritmo a un problema concreto, lo cual es fundamental para los tráders que no son especialistas en optimización.

El algoritmo A3 podría ocupar un nicho específico entre los métodos de optimización. El algoritmo del átomo artificial demuestra que incluso las metaheurísticas relativamente sencillas pueden resultar muy eficaces si se adaptan adecuadamente a un dominio específico. La combinación de simplicidad conceptual, eficiencia computacional y una calidad suficiente de las soluciones convierte a A3 en una buena herramienta en el arsenal de un tráder algorítmico.

El código fuente del algoritmo y los ejemplos de su aplicación están disponibles en el anexo del artículo, lo que permitirá a quienes lo deseen evaluar por sí mismos la eficacia del método y experimentar con él.

tab

Figura 1. Gradación de colores de los algoritmos según las pruebas correspondientes

gráfico

Figura 2. Histograma de los resultados de las pruebas de los algoritmos (en una escala de 0 a 100; cuanto mayor sea el valor, mejor, donde 100 es el resultado teórico máximo posible; en el archivo comprimido se incluye el script para calcular la tabla de clasificación)

Ventajas y desventajas del algoritmo A3:

Ventajas:

  1. Rápido.
  2. Un número reducido de parámetros.

Desventajas:

  1. Baja precisión de convergencia.

Se adjunta al artículo un archivo comprimido con las versiones actualizadas de los códigos de los algoritmos. El autor del artículo no se hace responsable de la exactitud absoluta en la descripción de los algoritmos canónicos, ya que en muchos de ellos se han introducido modificaciones para mejorar las capacidades de búsqueda. Las conclusiones y consideraciones que figuran en los artículos se basan en los resultados de los experimentos realizados.

Programas utilizados en el artículo

# Nombre Tipo Descripción
1 #C_AO.mqh
Archivo de inclusión
Clase padre de los algoritmos de optimización poblacional
2 #C_AO_enum.mqh
Archivo de inclusión
Enumeración de algoritmos de optimización poblacional
3 TestFunctions.mqh
Archivo de inclusión
Biblioteca de funciones de prueba
4
TestStandFunctions.mqh
Archivo de inclusión
Biblioteca de funciones del banco de pruebas
5
Utilities.mqh
Archivo de inclusión
Biblioteca de funciones auxiliares
6
CalculationTestResults.mqh
Archivo de inclusión
Script para calcular los resultados para la tabla comparativa
7
Testing AOs.mq5
Script Banco de pruebas unificado para todos los algoritmos de optimización poblacional
8
Simple use of population optimization algorithms.mq5
Script
Ejemplo sencillo de uso de algoritmos de optimización poblacional sin visualización
9
Test_AO_A3.mq5
Script Banco de pruebas para el A3

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/18958

Archivos adjuntos |
A3.ZIP (326.77 KB)
Auvoria Prime
Auvoria Prime | 26 jun 2026 en 16:54

A mí me pasa lo mismo, la primera lectura no fue fácil, jeje. La idea suena interesante, pero cuando las reglas sobre los enlaces covalentes e iónicos y la distribución de electrones no están muy claras, cuesta evaluar el algoritmo adecuadamente.

Para mí, lo más útil sería un ejemplo sencillo paso a paso con, quizá, dos átomos y unas cuantas variables, y que luego se mostrara exactamente cómo una iteración cambia la solución. Sin eso, parece más un concepto que algo que los operadores puedan entender o probar de verdad.

Chacha Ian Maroa
Chacha Ian Maroa | 29 jun 2026 en 18:04
Qué idea tan descabellada. Por favor, escribe a continuación sobre el concepto de optimización de la teoría del Big Bang.
Ryan L Johnson
Ryan L Johnson | 29 jun 2026 en 21:25
Chacha Ian Maroa #:
Menudo concepto tan alocado. Por favor, escribe la próxima vez sobre el concepto de optimización de la teoría del Big Bang
El tío se hartó de Roscosmos y acabó en mql5.com.
Robson Bruno Teixeira De Sousa
Robson Bruno Teixeira De Sousa | 2 jul 2026 en 00:15

Mi valoración

Imagina un grupo de 100 niños buscando un juguete escondido en un parque.

Cada niño empieza en un lugar diferente.

El algoritmo funciona así:

  1. En la primera ronda, distribuye a todos los niños por el parque en lugares aleatorios.
  2. Después, en cada ronda:
    • Algunos niños miran quién está más cerca del juguete y caminan un poco en esa dirección.
    • Otros niños siguen a un niño elegido al azar de entre los que van bien.
    • De vez en cuando, un niño decide explorar un lugar completamente nuevo, para ver si encuentra un camino mejor.
  3. Cada vez que un niño intenta salir del parque, el algoritmo dice:

    «¡No! Quédate dentro de los límites del parque».

  4. Este proceso se repite varias veces.

Al final, como todas van aprendiendo de quienes encuentran mejores caminos, todo el grupo se va acercando cada vez más al juguete.

En una frase:

El algoritmo hace que varias «personas» intenten resolver un problema, aprendiendo de las mejores soluciones y, a veces, explorando nuevos lugares para encontrar una solución aún mejor.


Nota: El código aún está en fase intermedia y necesita algunos ajustes.


Andrey Dik
Andrey Dik | 14 jul 2026 en 10:43
Chacha Ian Maroa #:
Menudo concepto más alucinante. Por favor, la próxima vez escribe sobre el concepto de optimización de la teoría del Big Bang

;D

https://www.mql5.com/es/articles/16963

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Características del Wizard MQL5 que debe conocer (Parte 74): Uso de patrones de Ichimoku y del ADX-Wilder con aprendizaje supervisado Características del Wizard MQL5 que debe conocer (Parte 74): Uso de patrones de Ichimoku y del ADX-Wilder con aprendizaje supervisado
Retomamos el tema de nuestro último artículo, donde presentamos el par de indicadores Ichimoku y ADX, analizando cómo se podría mejorar este dúo con el aprendizaje supervisado. Ichimoku y ADX forman una combinación complementaria de soporte/resistencia y tendencia. Nuestro enfoque de aprendizaje supervisado utiliza una red neuronal que emplea el núcleo de mezcla espectral profunda para ajustar con precisión las predicciones de este par de indicadores. Como de costumbre, esto se realiza en un archivo de clase de señal personalizado que funciona con el asistente MQL5 para ensamblar un Asesor Experto.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Características del Wizard MQL5 que debe conocer (Parte 73): Uso de patrones de Ichimoku y del ADX-Wilder Características del Wizard MQL5 que debe conocer (Parte 73): Uso de patrones de Ichimoku y del ADX-Wilder
El indicador Ichimoku Kinko Hyo y el oscilador ADX-Wilder son una combinación que podría utilizarse de forma complementaria en un asesor experto de MQL5. El Ichimoku tiene múltiples facetas; sin embargo, en este artículo nos centraremos principalmente en su capacidad para definir los niveles de soporte y resistencia. Por otra parte, también utilizamos el ADX para definir nuestra tendencia. Como de costumbre, utilizamos el asistente de MQL5 para desarrollar y probar todo el potencial que estos dos puedan tener.