Algoritmo del átomo artificial — Artificial Atom Algorithm (A3)
Índice
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:
- rangeMinP [] — valores mínimos para cada variable de solución que el algoritmo va a optimizar.
- rangeMaxP [] — valores máximos para cada variable de solución.
- 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.
- 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 <= 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 > 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.
- 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.

A3 en la función de prueba Hilly

A3 en la función de prueba Forest

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.

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

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:
- Rápido.
- Un número reducido de parámetros.
Desventajas:
- 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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Características del Wizard MQL5 que debe conocer (Parte 74): Uso de patrones de Ichimoku y del ADX-Wilder con aprendizaje supervisado
Particularidades del trabajo con números del tipo double en MQL4
Características del Wizard MQL5 que debe conocer (Parte 73): Uso de patrones de Ichimoku y del ADX-Wilder
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
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.
Menudo concepto tan alocado. Por favor, escribe la próxima vez sobre el concepto de optimización de la teoría del Big Bang
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í:
Cada vez que un niño intenta salir del parque, el algoritmo dice:
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:
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