English Русский 中文 Deutsch 日本語 Português
preview
Algoritmo de tiro con arco - Archery Algorithm (AA)

Algoritmo de tiro con arco - Archery Algorithm (AA)

MetaTrader 5Probador |
131 2
Andrey Dik
Andrey Dik

Contenido

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


Introducción

Con tareas cada vez más complejas y recursos cada vez más escasos, la optimización se está convirtiendo no solo en una necesidad, sino en un verdadero arte algorítmico en el mundo actual. ¿Cómo encontrar la mejor solución entre las muchas posibles? ¿Cómo minimizar los costes, aumentar la eficacia y maximizar los beneficios? Estas cuestiones preocupan a especialistas de muy diversos campos, de la economía a la ingeniería, de los sistemas sociales a la ecología. Antes de proceder a resolver un problema de optimización, resulta esencial modelar correctamente el problema, identificando las variables clave y las relaciones matemáticas que reproducirán adecuadamente la realidad. La optimización se usa ampliamente en las finanzas y el trading, ayudando no solo a desarrollar nuevas estrategias de inversión, sino también a mejorar las existentes. No obstante, a pesar de la universalidad de los enfoques, los métodos de optimización pueden dividirse a grandes rasgos en dos categorías: deterministas y estocásticos.

Los métodos deterministas, como el descenso de gradiente, ofrecen soluciones rigurosas y predecibles usando derivadas matemáticas para encontrar el óptimo, lo cual permite modelar eficazmente diversos escenarios; sin embargo, una vez que los problemas se vuelven no lineales o multidimensionales, su eficacia puede disminuir drásticamente. En estos casos, los métodos estocásticos acuden al rescate, usando como base procesos aleatorios para encontrar soluciones aceptables en entornos complejos, lo cual los hace especialmente útiles en mercados volátiles.

Las combinaciones de métodos deterministas y estocásticos juegan un papel fundamental en los enfoques modernos de la optimización. Combinando estos dos enfoques, los analistas pueden crear modelos más flexibles y adaptables que consideren tanto las condiciones estables como las cambiantes. Esto no solo mejora la calidad de las previsiones, sino que también minimiza los riesgos, algo fundamental para el éxito de la gestión de las inversiones.

En este artículo, presentaremos un nuevo enfoque para resolver problemas de optimización, el Algoritmo de Tiro con Arco (AA). El algoritmo fue desarrollado por Fatemeh Ahmadi Zeidabadi y sus colegas y se publicó en febrero de 2022. Este método, inspirado en el arte del tiro con arco, ofrece soluciones casi óptimas actualizando la posición de los miembros de la población en el espacio de búsqueda partiendo de un elemento seleccionado al azar. Hoy comprobaremos la eficacia de AA en funciones objetivo estándar y compararemos los resultados con algoritmos que ya conocemos. Profundizando en los detalles, desvelaremos cómo este enfoque innovador puede cambiar nuestra forma de pensar sobre la optimización y abrir nuevos horizontes para resolver problemas complejos.


Implementación del algoritmo

El Algoritmo de Tiro con Arco (AA) es un método de optimización estocástica completamente nuevo desarrollado para encontrar soluciones óptimas en problemas de optimización e inspirado en el comportamiento de un arquero que apunta a una diana.  El AA simula el proceso de disparo de flechas a una diana. Cada miembro de la población representa una solución potencial al problema de optimización, y sus posiciones en el espacio de búsqueda se actualizan según el rendimiento de un miembro "objetivo" elegido aleatoriamente, proceso similar a la forma en que un tirador ajusta su mira según dónde quiere disparar.

La población se representa como una matriz en la que cada fila corresponde a un miembro (solución) y cada columna a una dimensión del problema. Esto permite evaluar y actualizar de forma estructurada las soluciones según sus valores de función objetivo. El rendimiento de cada miembro se evalúa usando una función objetivo que cuantifica la calidad de la solución encontrada. Los resultados se almacenan en un vector, lo cual permite al algoritmo comparar el rendimiento de distintas soluciones.

El objetivo se divide en secciones, y la anchura de cada sección se corresponde con la productividad de los miembros de la población. Luego se calcula una función de verosimilitud para determinar la probabilidad de que cada miembro sea seleccionado según su valor de función objetivo, teniendo los arqueros más eficientes una mayor probabilidad de ser seleccionados. Después se selecciona aleatoriamente un miembro de la población según la probabilidad acumulada, imitando la selección del objetivo del tirador. Dicha selección afecta al modo en que se actualizan las posiciones de los demás miembros. El algoritmo actualiza la posición de cada flecha en el espacio de búsqueda usando determinadas ecuaciones. La actualización dependerá de si el arquero seleccionado tiene un valor de función objetivo mejor o peor que el actual. Este proceso incorpora la aleatoriedad para explorar el espacio de búsqueda. El AA funciona de forma iterativa, actualizando la población hasta que se alcanza una condición de parada (número máximo de iteraciones). Durante este proceso, el algoritmo efectúa un seguimiento de la mejor solución encontrada.

La versión original del algoritmo AA presentada anteriormente describe la matriz como una población, y los vectores como miembros de la población. Sin embargo, en el texto no se especifican operaciones concretas propias del trabajo con matrices. De hecho, el algoritmo implica acciones estándar del agente de búsqueda, como sucede en la mayoría de los algoritmos discutidos anteriormente.

También cabe señalar que la frase "el objetivo se divide en secciones, y la anchura de cada sección se corresponde con la productividad de los miembros de la población" implica el uso del método de la ruleta para la selección. En este caso, la probabilidad de elegir un sector es proporcional a su anchura.

De este modo, un lenguaje complejo que describe muchos conceptos puede explicarse de forma mucho más simple, lo cual puede facilitar mucho la comprensión de la idea.

Así pues, un algoritmo de tiro con arco supone un método de optimización basado en la población que utiliza los principios del tiro al blanco para guiar la búsqueda de soluciones óptimas. El algoritmo combina elementos de aleatoriedad usando una distribución normal para explorar y explotar el espacio de búsqueda. Componentes clave del algoritmo:

1. Población de agentes (arqueros)
2. Vector de probabilidades y probabilidades acumuladas
3. Mecanismo de herencia (no presente en la versión original)
4. Mecanismo de actualización de posiciones
5. Parámetro de intensidad del entrenamiento (I)

En primer lugar, presentaremos el pseudocódigo del algoritmo:

Inicialización:
    Creamos una población de agentes popSize
    Para cada agente:
        Inicializamos una posición aleatoria dentro del rango de búsqueda
        Inicializamos la posición anterior y la adaptabilidad

Ciclo principal:
    Hasta que se alcance la condición de parada:
        Para cada agente i de la población
            Calculamos el vector de probabilidades P y las probabilidades acumuladas C
            
            Para cada coordenada c:
                Seleccionamos al arquero k utilizando la probabilidad acumulada
                
                Si (número_aleatorio < probabilidad_de_herencia):
                    nueva_posición [c] = posición_arquero_k [c]
                De lo contrario:
                    I = redondeo (1 + número_aleatorio_de_0_a_1) // Parámetro de intensidad del entrenamiento
                    random_gaussian = generar_número_gaussiano (media = 0, min =-1, max = 1)
                    
                    Si (adaptación_arquero_k > adaptación_agente_i):
                        nueva_posición [c] = posición_anterior [c] + random_gaussian * (posición_arquero_k [c] - I * posición_anterior [c])
                    De lo contrario:
                        nueva_posición [c] = posición_anterior [c] + random_gaussian * (posición_anterior [c] - I * posición_arquero_k [c])
                
                Limitamos la nueva_posición [c] dentro del rango de búsqueda
            
            Actualizamos la posición del agente i
        
        Evaluamos la adaptabilidad de todos los agentes
        Actualizamos la mejor solución global
        Para cada agente:
            Si la nueva adaptación es mejor que la anterior:
                Actualizamos la posición anterior y la adaptabilidad

Retornamos la mejor solución encontrada

Peculiaridades de implementación en el código:

1. El algoritmo usa un enfoque probabilístico para seleccionar los arqueros de los que aprender.
2. El mecanismo de herencia permite a los agentes copiar directamente las posiciones de los arqueros que han tenido éxito con cierta probabilidad.
3. Las actualizaciones de posición usan una distribución gaussiana para introducir aleatoriedad en el proceso de entrenamiento de los arqueros.
4. El algoritmo almacena las mejores posiciones anteriores de los agentes, lo cual le permite tener cierta "memoria" de las buenas decisiones.
5. La aplicación incluirá un mecanismo para limitar los nuevos elementos dentro del rango de búsqueda permitido.
6. El parámetro de intensidad de aprendizaje (I) descrito por los autores se usará para regular el grado de influencia de la posición actual en la nueva posición.

El parámetro I (intensidad de entrenamiento) es una variable aleatoria que puede tomar el valor de 1 o 2. Se define del siguiente modo: I = redondeo a número entero (1 + número generado aleatoriamente entre 0 y 1). Esto significa que I será igual a 1 con una probabilidad 0,5 y 2 con la misma probabilidad. Papel del parámetro I en el algoritmo:

1. Cuando I = 1, el algoritmo realiza ajustes de posición más pequeños.
2. Cuando I = 2, el algoritmo puede realizar cambios de posición más bruscos.

Vamos a pasar directamente a escribir el código del algoritmo. La estructura "arquero", "S_AA_Agent", representa un agente en el algoritmo de optimización con un conjunto de coordenadas en el espacio de decisión y contiene información sobre su rendimiento según la aptitud. 

  • cPrev [] - el array almacena las coordenadas anteriores del agente.
  • fPrev - esta variable almacena el anterior valor de aptitud del agente.   

El método "Init" prepara al agente para el funcionamiento estableciendo los valores iniciales para sus coordenadas y aptitud. A continuación, establecemos el valor de "fPrev" en el valor mínimo posible para el tipo "double" porque aún no se ha calculado la adaptabilidad.

//——————————————————————————————————————————————————————————————————————————————
struct S_AA_Agent
{
    double cPrev []; // previous coordinates
    double fPrev;    // previous fitness

    void Init (int coords)
    {
      ArrayResize (cPrev, coords);
      fPrev = -DBL_MAX;
    }
};
//——————————————————————————————————————————————————————————————————————————————

Luego analizamos la clase "C_AO_AAm", que implementa el algoritmo propiamente dicho y hereda de la clase "C_AO". 

  • popSize - indica el tamaño de la población.
  • inhProbab - representa el valor de la probabilidad de heredar una característica de otro arquero.

A continuación, se inicializa un array "params" de tamaño 2, donde se almacenan los parámetros del algoritmo: el tamaño de la población y la probabilidad de herencia.

  • SetParams - el método establece los parámetros del algoritmo basándose en los valores almacenados en el array "params". Después extraemos los valores "popSize" y "inhProbab", convirtiéndolos a sus respectivos tipos.
  • Init - el método inicializa el algoritmo tomando los límites de búsqueda mínimos y máximos, el paso de búsqueda y el número de épocas.
  • Moving y Revision - los métodos son responsables de la lógica de movimiento de los agentes en el espacio de soluciones y de su revisión (actualización). 

S_AA_Agent agent [] - array de agentes que serán usados para realizar la optimización.

La clase "C_AO_AAm" implementa el algoritmo de optimización, mientras que los métodos "SetParams", "Init", "Moving" y "Revision" controlan la configuración y el comportamiento del algoritmo mientras se ejecuta. 

//——————————————————————————————————————————————————————————————————————————————
class C_AO_AAm : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_AAm () { }
  C_AO_AAm ()
  {
    ao_name = "AAm";
    ao_desc = "Archery Algorithm M";
    ao_link = "https://www.mql5.com/es/articles/15782";

    popSize   = 50;    // population size
    inhProbab = 0.3;

    ArrayResize (params, 2);

    params [0].name = "popSize";   params [0].val = popSize;
    params [1].name = "inhProbab"; params [1].val = inhProbab;
  }

  void SetParams ()
  {
    popSize   = (int)params [0].val;
    inhProbab = params      [1].val;
  }

  bool Init (const double &rangeMinP  [], // minimum search range
             const double &rangeMaxP  [], // maximum search range
             const double &rangeStepP [], // step search
             const int     epochsP = 0);  // number of epochs

  void Moving ();
  void Revision ();

  //----------------------------------------------------------------------------
  double  inhProbab; //probability of inheritance

  S_AA_Agent agent [];

  private: //-------------------------------------------------------------------
};
//——————————————————————————————————————————————————————————————————————————————

El método "Init" de la clase "C_AO_AAm" se ocupa de inicializar el algoritmo de optimización. Toma cuatro parámetros: los arrays para los límites de búsqueda mínimos y máximos, el paso de búsqueda y el número de épocas, que por defecto es cero.

  • Si la inicialización estándar tiene éxito, el método redimensionará el array "agent" para que coincida con el tamaño de población establecido "popSize". Esto permitirá crear el número necesario de agentes que se utilizarán en el algoritmo.
  • En el ciclo "for", cada agente del array se inicializa usando el método "Init", que establece las coordenadas iniciales de cada agente.

Al final, el método retorna "true", indicando que la inicialización del algoritmo se ha completado con éxito. Así, el método "Init" se ocupa de preparar el algoritmo para su funcionamiento, configurando los parámetros necesarios y creando los agentes que participarán en el proceso de optimización.

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_AAm::Init (const double &rangeMinP  [],
                     const double &rangeMaxP  [],
                     const double &rangeStepP [],
                     const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  ArrayResize (agent, popSize);
  for (int i = 0; i < popSize; i++) agent [i].Init (coords);

  return true;
}
//——————————————————————————————————————————————————————————————————————————————

El método "Moving" de la clase "C_AO_AAm" se encarga de desplazar los agentes en el espacio de soluciones según sus posiciones actuales y los valores de la función que están optimizando. Vamos a desglosarlo pieza por pieza:

  • Si llamamos al método por primera vez ("revision" es "false"), se inicializará un valor aleatorio para cada agente y cada coordenada dentro de los límites "rangeMin" y "rangeMax" dados.
  • A continuación, este valor se ajusta mediante el método "SeInDiSp", que garantiza que el valor coincida con el paso indicado.

Después, la bandera "revision" se establece en "true" y el método finalizará.

  • A continuación, se crean dos arrays: "P" para las probabilidades y "C" para las probabilidades acumuladas.
  • Luego se encuentra el peor valor de la función "F_worst" para normalizar los valores de la función de aptitud de los agentes.
  • A continuación, calculamos las probabilidades de cada agente, que se normalizan para que su suma sea igual a 1.
  • Las probabilidades acumuladas "C" se calculan a partir de las probabilidades "P".
  • Para cada agente y cada coordenada, se selecciona un tirador compañero, un agente según la probabilidad acumulada.
  • Si el valor aleatorio es menor que la probabilidad de herencia dada "inhProbab", el agente tomará la coordenada del agente seleccionado (asegurando la herencia de características con la probabilidad dada).
  • En caso contrario, el agente actualiza su posición basándose en una fórmula que considera la posición actual, un valor aleatorio y la posición del tirador compañero.
  • Por último, el nuevo valor de coordenadas también se corrige usando el método "SeInDiSp".

El método Moving ejecuta el movimiento de los agentes en el espacio de decisión dadas sus posiciones actuales y los valores de las funciones y utiliza métodos probabilísticos para seleccionar las direcciones de movimiento y actualizar las posiciones.

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

  //-------------------------------------------------------------------------
  // Calculate probability vector P and cumulative probability C
  double P [], C [];
  ArrayResize (P, popSize);
  ArrayResize (C, popSize);
  double F_worst = DBL_MAX;
  double sum = 0;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < F_worst) F_worst = a [i].f;
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] =  a [i].f - F_worst;
    sum += P [i];
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] /= sum;
    C [i] = (i == 0) ? P [i] : C [i - 1] + P [i];
  }

  double x;

  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      // Select archer (k) using cumulative probability
      int k = 0;
      double r = u.RNDprobab ();
      while (k < popSize - 1 && C [k] < r) k++;

      if (u.RNDbool () < inhProbab)
      {
        x = a [k].c [c];
      }
      else
      {
        // Update position using Eq. (5) and (6)
        double I   = MathRound (1 + u.RNDprobab ());
        double rnd = u.GaussDistribution (0, -1, 1, 8);

        if (a [k].f > a [i].f)
        {
          x = agent [i].cPrev [c] + rnd * (a [k].c [c] - I * agent [i].cPrev [c]);
        }
        else
        {
          x = agent [i].cPrev [c] + rnd * (agent [i].cPrev [c] - I * a [k].c [c]);
        }
      }

      a [i].c [c] = u.SeInDiSp (x, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

El método "Revision" de la clase "C_AO_AAm" se ocupa de actualizar la información sobre los mejores agentes de la población. El método efectúa las siguientes acciones:

  • La variable "ind" se inicializará con el valor "-1". Se usará para almacenar el índice del agente con el mejor valor de característica.
  • El ciclo "for" itera todos los agentes de la población "popSize" y si el valor de la característica del agente actual "a[i].f" es mayor que el valor de la mejor característica actual "fB":
    • se actualiza "fB" al nuevo mejor valor "a[i].f".
    • el índice de este agente se almacena en la variable "ind".
Una vez finalizado el ciclo, si "ind" no es igual a "-1", se llamará la función "ArrayCopy", que copiará las coordenadas del mejor agente del array "a" al array "cB". También se ejecutará un segundo ciclo "for" en todos los agentes de la población:

  • Si el valor de la función del agente actual "a[i].f" es mayor que su anterior valor de la función de aptitud "agente[i].fPrev":
    • actualizamos el valor "fPrev" anterior de este agente.
    • las coordenadas actuales del agente se copian en el array "cPrev" usando "ArrayCopy".

El métodoRevision se utiliza para actualizar la información sobre la mejor solución global y para actualizar las mejores posiciones de los agentes.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_AAm::Revision ()
{
  //----------------------------------------------------------------------------
  int ind = -1;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }

  if (ind != -1) ArrayCopy (cB, a [ind].c, 0, 0, WHOLE_ARRAY);

  //----------------------------------------------------------------------------
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > agent [i].fPrev)
    {
      agent [i].fPrev = a [i].f;
      ArrayCopy (agent [i].cPrev, a [i].c, 0, 0, WHOLE_ARRAY);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————


Resultados de las pruebas

Hemos modificado ligeramente este algoritmo. El algoritmo original de los autores no intercambia directamente información entre los arqueros. El intercambio se realiza indirectamente mediante la interacción de coordenadas a través de la distribución normal, por lo que me ha parecido necesario añadir el intercambio de esta información. Para ello, hemos añadido un parámetro adicional "inhProbab" que se encarga de realizar dicho intercambio con una probabilidad determinada.

if (u.RNDbool () < inhProbab)
{
  x = a [k].c [c];
}

Los resultados presentados a continuación se corresponden con la versión original del algoritmo, tal como la concibieron los autores.

AA|Archery Algorithm|50.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.6699547926310098
25 Hilly's; Func runs: 10000; result: 0.37356238340164605
500 Hilly's; Func runs: 10000; result: 0.257542163368952
=============================
5 Forest's; Func runs: 10000; result: 0.38166669771790607
25 Forest's; Func runs: 10000; result: 0.199300365268835
500 Forest's; Func runs: 10000; result: 0.15337954055780398
=============================
5 Megacity's; Func runs: 10000; result: 0.4076923076923077
25 Megacity's; Func runs: 10000; result: 0.17907692307692308
500 Megacity's; Func runs: 10000; result: 0.10004615384615476
=============================
All score: 2.72222 (30.25%)

El algoritmo obtiene una puntuación del 30,25% en los resultados de la prueba; sin embargo, con mi modificación, el algoritmo ha mejorado su rendimiento en más de un 13%. A continuación le mostramos los resultados de la versión modificada:

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9353194829441194
25 Hilly's; Func runs: 10000; result: 0.6798262991897616
500 Hilly's; Func runs: 10000; result: 0.2596620178276653
=============================
5 Forest's; Func runs: 10000; result: 0.5735062785421186
25 Forest's; Func runs: 10000; result: 0.22007188891556378
500 Forest's; Func runs: 10000; result: 0.1486980566819649
=============================
5 Megacity's; Func runs: 10000; result: 0.6307692307692309
25 Megacity's; Func runs: 10000; result: 0.344
500 Megacity's; Func runs: 10000; result: 0.10193846153846249
=============================
All score: 3.89379 (43.26%)

Así que he decidido decantarme por un algoritmo con modificaciones y ponerlo en la tabla de clasificación. Más abajo puede ver cómo funciona el algoritmo en la visualización. Creo que es lo suficientemente bueno: claro que existe una dispersión en los resultados, sin embargo, no es sustancial y solo se da en funciones con un pequeño número de coordenadas.

Hilly

AAm en la función de prueba Hilly

Forest

AAm en la función de prueba Forest

Megacity

AAm sobre la función de prueba Megacity

La versión modificada del algoritmo ocupa el puesto 26 en cuanto a rendimiento.

AO Description HillyHilly final ForestForest final Megacity (discrete)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)
1ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
2CLAcode lock algorithm0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
3AMOmanimal migration optimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
4(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
5CTAcomet tail algorithm0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
6SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
7ESGevolution of social groups0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
8SIAsimulated isotropic annealing0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
9ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
10ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
11TSEAturtle shell evolution algorithm0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
12DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
13CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
14BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
15HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
16SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
17BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
18(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
19TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
20BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
21WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
22AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
23ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
24BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
25ABHartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974.12745,85
26AAmarchery algorithm M0,935320,679830,259661,874810,573510,220070,148700,942280,630770,344000,101941,076713,89443,26
27ASBOadaptive social behavior optimization0,763310,492530,326191,582020,795460,400350,260971,456770,264620,171690,182000,618313.65740,63
28MECmind evolutionary computation0,695330,533760,326611,555690,724640,330360,071981,126980,525000,220000,041980,786983,47038,55
29IWOinvasive weed optimization0,726790,522560,331231,580580,707560,339550,074841,121960,423330,230670,046170,700173,40337,81
30Micro-AISmicro artificial immune system0,795470,519220,308611,623300,729560,368790,093981,192330,376670,158670,028020,563353,37937,54
31COAmcuckoo optimization algorithm M0,758200,486520,313691,558410,740540,280510,055991,077040,505000,174670,033800,713473,34937,21
32SDOmspiral dynamics optimization M0,746010,446230,296871,489120,702040,346780,109441,158260,428330,167670,036630,632633,28036,44
33NMmNelder-Mead method M0,738070,505980,313421,557470,636740,283020,082211,001970,446670,186670,040280,673623,23335,92
34FAmfirefly algorithm M0,586340,472280,322761,381380,684670,374390,109081,168140,286670,164670,047220,498553,04833,87
35GSAgravitational search algorithm0,647570,491970,300621,440160,539620,363530,099451,002600,326670,122000,019170,467832,91132,34
36BFObacterial foraging optimization0,611710,432700,313181,357590,544100,215110,056760,815970,421670,138000,031950,591622,76530,72
37ABCartificial bee colony0,633770,424020,308921,366710,551030,218740,056230,826000,340000,142000,031020,513022,70630,06
38BAbat algorithm0,597610,459110,352421,409150,403210,193130,071750,668100,210000,101000,035170,346172,42326,93
39AAAalgae adaptive algorithm0,500070,320400,255251,075720,370210,222840,167850,760890,278460,148000,097550,524022,36126,23
40SAsimulated annealing0,557870,421770,315491,295130,349980,152590,050230,552800,311670,100330,028830,440832,28925,43
41IWDmintelligent water drops M0,545010,378970,301241,225220,461040,147040,043690,651770,258330,097000,023080,378422,25525,06
42PSOparticle swarm Optimization0,597260,369230,299281,265770,372370,163240,070100,605720,256670,080000,021570,358232,23024,77
43Boidsboids algorithm0,433400,305810,254250,993460,357180,201600,157080,715860,278460,142770,098340,519572,22924,77
44MAmonkey algorithm0,591070,426810,318161,336040,311380,140690,066120,518190,228330,085670,027900,341902,19624,40
45SFLshuffled frog-leaping0,539250,358160,298091,195510,371410,114270,040510,526180,271670,086670,024020,382352,10423,38



Conclusiones

Hoy hemos presentado dos versiones del algoritmo: la del autor y mi versión modificada, que incluye pequeños cambios pero ofrece una mejora significativa del rendimiento. Este artículo demuestra claramente que incluso pequeños ajustes en la lógica de un algoritmo pueden dar lugar a notables aumentos de eficiencia en diversas tareas. También se pone de manifiesto que las descripciones complejas de un algoritmo pueden dificultar la comprensión del funcionamiento del mismo, lo que a su vez obstaculiza su mejora. Por el contrario, los conceptos complejos expresados en un lenguaje simple abren el camino a soluciones más eficaces.

Tab

Figura 1. Gradación por colores de los algoritmos según sus respectivas pruebas. Los resultados superiores o iguales a 0,99 aparecen resaltados en blanco.

chart

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


Cuando el artículo estaba casi listo para su publicación, se me ocurrió una idea que decidí poner a prueba. ¿Y si, siguiendo la lógica de los autores sobre las dianas y los arqueros que usan el método de la "ruleta" para la selección, cambiamos el tamaño de las propias dianas de forma inversamente proporcional a la calidad de las soluciones encontradas? Si la solución es buena, habrá que perfeccionarla y explorar su vecindad. De lo contrario, si el resultado es insignificante, deberemos ampliar el área de búsqueda para identificar nuevas zonas potencialmente prometedoras.

Goals

Figura 3. El número de flechas que alcanzan los objetivos es directamente proporcional a la calidad de los propios objetivos, mientras que el tamaño de los objetivos es inversamente proporcional a la calidad de los mismos. 

Veamos un código que usa la idea de que los objetivos aumentan de forma inversamente proporcional a su calidad.

void C_AO_AAm::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;
  }

  //-------------------------------------------------------------------------
  // Calculate probability vector P and cumulative probability C
  double P [], C [];
  ArrayResize (P, popSize);
  ArrayResize (C, popSize);
  double F_worst = DBL_MAX; // a[ArrayMaximum(a, WHOLE_ARRAY, 0, popSize)].f;
  double sum = 0;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < F_worst) F_worst = a [i].f;
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] =  a [i].f - F_worst; ////F_worst - a[i].f;
    sum += P [i];
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] /= sum;
    C [i] = (i == 0) ? P [i] : C [i - 1] + P [i];
  }

  double x;
  
  double maxFF = fB;
  double minFF = DBL_MAX;
  double prob1;
  double prob2;
  
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < minFF) minFF = a [i].f;
  } 

  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      // Select archer (k) using cumulative probability
      int k = 0;
      double r = u.RNDprobab ();
      while (k < popSize - 1 && C [k] < r) k++;

      if (u.RNDbool () < inhProbab)
      {
        x = a [k].c [c];
      }
      else
      {
        
        // Update position using Eq. (5) and (6)
        //double I   = MathRound (1 + u.RNDprobab ());
        double rnd = u.GaussDistribution (0, -1, 1, 8);
        /*
        if (a [k].f > a [i].f)
        {
          x = agent [i].cPrev [c] + rnd * (a [k].c [c] - I * agent [i].cPrev [c]);
        }
        else
        {
          x = agent [i].cPrev [c] + rnd * (agent [i].cPrev [c] - I * a [k].c [c]);
        }
        */
        
        prob1 = u.Scale (a [i].f, minFF, maxFF, 0, 1);
        prob2 = u.Scale (a [k].f, minFF, maxFF, 0, 1);
        
        x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (1 - prob1 - prob2);  
        
      }

      a [i].c [c] = u.SeInDiSp (x, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//—

1. En la sección comentada, la versión original usaba una construcción condicional "if-else" para determinar cómo actualizar la posición del agente. Esta lógica ha sido eliminada y sustituida por un nuevo cálculo.

2. Tres nuevas líneas de código:

prob1 = u.Scale(a[i].f, minFF, maxFF, 0, 1);
prob2 = u.Scale(a[k].f, minFF, maxFF, 0, 1);

x = agent[i].cPrev[c] + rnd * (a[k].c[c] - agent[i].cPrev[c]) * (1 - prob1 - prob2);

Estas líneas introducen un nuevo enfoque para calcular la posición actualizada:

(a) Se calculan dos valores de "prob1" y "prob2", usando la función "Scale", que normaliza los valores de aptitud de los agentes "i" y "k" en un diapasón de 0 a 1, usando como base los valores de aptitud máximos y mínimos, "minFF" y "maxFF".

b) A continuación, se calcula la nueva posición "x" utilizando estas probabilidades. Esta utiliza la anterior posición "i" del agente "agent [i].cPrev [c]", la posición "k" del arquero "a [k].c [c]" elegido y el factor aleatorio "rnd".

c) Ahora el movimiento se ve afectado por la suma de la aptitud de ambos agentes restada de 1. Este factor sirve como factor de escalado, permitiendo que la diana se ensanche o estreche de forma inversamente proporcional a la aptitud de los arqueros seleccionados. Cuanto menos experimentados sean los arqueros, mayor será la dispersión de las flechas, pero la distribución de las probabilidades de dar en el blanco seguirá una distribución normal.

Veamos ahora los resultados:

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9174358826544864
25 Hilly's; Func runs: 10000; result: 0.7087620527831496
500 Hilly's; Func runs: 10000; result: 0.42160091427958263
=============================
5 Forest's; Func runs: 10000; result: 0.9252690259821034
25 Forest's; Func runs: 10000; result: 0.7580206359203926
500 Forest's; Func runs: 10000; result: 0.353277934084795
=============================
5 Megacity's; Func runs: 10000; result: 0.6738461538461538
25 Megacity's; Func runs: 10000; result: 0.552
500 Megacity's; Func runs: 10000; result: 0.23738461538461658
=============================
All score: 5.54760 (61.64%)

No podemos sino estar contentos con los resultados: el rendimiento del algoritmo ha mejorado notablemente. En la siguiente visualización podemos ver la convergencia segura del algoritmo y la identificación de zonas significativas de la superficie de la función.

Hilly2

AAm en la función de prueba Hilly

Hagamos otro pequeño experimento. Los resultados anteriores se obtienen restando a la unidad la suma de las probabilidades de los arqueros.

//x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (1 - prob1 - prob2); 
 x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (2 - prob1 - prob2);  

El principal cambio consiste en restar la suma no a la unidad, sino a dos. Veamos cómo una acción tan simple puede influir en el comportamiento del algoritmo:

  • en la versión anterior, el resultado de dicha operación puede ser negativo si la adaptabilidad de ambos arqueros es alta, provocando un efecto de "mutación" en las coordenadas resultantes del nuevo arquero.
  • en la nueva versión, el multiplicador dará un valor entre 0 y 2.

Este cambio provoca un movimiento más disperso de los agentes, además da una exploración más agresiva del espacio de soluciones, ya que los agentes darán pasos más largos cada vez que se actualice una posición.

De esta forma, como podemos ver en la impresión de los resultados del algoritmo, este cambio ha mejorado la convergencia del algoritmo en funciones de dimensionalidad media, pero también ha provocado un deterioro en funciones de dimensionalidad alta (marcadas en amarillo), aunque en general el algoritmo ha obtenido una puntuación global más alta.

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9053229410164233
25 Hilly's; Func runs: 10000; result: 0.8259118221071665
500 Hilly's; Func runs: 10000; result: 0.2631661675236262
=============================
5 Forest's; Func runs: 10000; result: 0.9714247249319152
25 Forest's; Func runs: 10000; result: 0.9091052022399436
500 Forest's; Func runs: 10000; result: 0.2847632249786224
=============================
5 Megacity's; Func runs: 10000; result: 0.7169230769230768
25 Megacity's; Func runs: 10000; result: 0.6378461538461538
500 Megacity's; Func runs: 10000; result: 0.10473846153846252
=============================
All score: 5.61920 (62.44%)

El resultado anterior parece más práctico y seguirá siendo la variante principal de la versión modificada del algoritmo AAm. Aquí tenemos nuevamente la tabla de clasificación con gradiente térmico, en la que AAm ocupa ahora un respetable 7º puesto. El algoritmo puede definirse como muy equilibrado (buena convergencia en funciones de diferente dimensionalidad) y puede recomendarse para resolver diversos problemas.

Tab2

Figura 4. Gradación por colores de los algoritmos según sus respectivas pruebas. Los resultados superiores o iguales a 0,99 aparecen resaltados en blanco.

Ventajas e inconvenientes del algoritmo AAm:

Ventajas:

  1. Bastante rápido.
  2. Autoadaptativo.
  3. Solo un parámetro externo.
  4. Buena convergencia.
  5. Buena escalabilidad.
  6. Aplicación sencilla (a pesar de la compleja descripción de los autores).

Desventajas:

  1. Ligera tendencia a atascarse en funciones de baja dimensionalidad.

La adición de nuevos algoritmos a la tabla de clasificación ha empezado a causar dificultades: se ha vuelto poco legible. Por lo tanto, hemos decidido limitar el número de participantes en la comparación a 45 algoritmos, y ahora la competición tiene un formato de todos contra todos. Para facilitar y aclarar a los lectores el acceso a todos los artículos, hemos preparado un archivo HTML con una lista de todos los algoritmos anteriormente comentados, ordenados según su clasificación en una tabla. Este fichero se encuentra en el archivo anexo a los artículos desde hace algún tiempo, y para los que lo abren por primera vez, hay una pequeña sorpresa.

Adjuntamos al artículo un archivo con las versiones actuales de los códigos de los algoritmos. El autor de este artículo no se responsabiliza de la exactitud absoluta de la descripción de los algoritmos canónicos, muchos de ellos han sido modificados para mejorar las capacidades de búsqueda. Las conclusiones y juicios presentados en los artículos se basan en los resultados de los experimentos realizados.

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

Archivos adjuntos |
AAm.zip (35.89 KB)
Ilya Melamed
Ilya Melamed | 13 sept 2024 en 18:11
Gracias por su investigación. Pero tengo una pregunta muy sencilla como simple programador de Asesores Expertos en mql5 (no soy matemático). Puede parecerle una tontería, pido disculpas de antemano. Pero aún así, ¿cómo puede su investigación ayudar en la optimización de EAs? ¿Podría darme un ejemplo? Digamos que tenemos un nuevo EA, queremos optimizarlo, y ....? Gracias.
Andrey Dik
Andrey Dik | 14 sept 2024 en 18:23
Ilya Melamed optimización de EAs? ¿Podría darme un ejemplo? Digamos que tenemos un nuevo EA, queremos optimizarlo, y ....? Muchas gracias.

Gracias por su interés en mi trabajo y por su excelente pregunta.

Hay muchos escenarios para aplicar algoritmos de optimización, siempre que quieras obtener la mejor solución entre las posibles.

Por ejemplo, se puede aplicar a la auto-optimización de EAs como se describe aquí.

O se puede utilizar como parte de la gestión de optimización de un probador interno, como se describe aquí.

Uso conjunto de PSAR, Heiken Ashi y Deep Learning para el trading Uso conjunto de PSAR, Heiken Ashi y Deep Learning para el trading
Este proyecto explora la fusión del aprendizaje profundo y el análisis técnico para probar estrategias de trading en forex. Se utiliza un script en Python para experimentar rápidamente, empleando un modelo ONNX junto con indicadores tradicionales como PSAR, SMA y RSI para predecir los movimientos del EURUSD. A continuación, un script de MetaTrader 5 lleva esta estrategia a un entorno en vivo, utilizando datos históricos y análisis técnicos para tomar decisiones de negociación informadas. Los resultados de las pruebas retrospectivas indican un planteamiento prudente pero coherente, centrado en la gestión del riesgo y el crecimiento constante más que en la búsqueda agresiva de beneficios.
Soluciones sencillas para trabajar cómodamente con indicadores Soluciones sencillas para trabajar cómodamente con indicadores
En este artículo le contaremos cómo crear un panel simple para cambiar la configuración del indicador directamente desde el gráfico, y qué cambios se deberán introducir en el indicador para conectar este panel. Este artículo está dirigido exclusivamente a aquellos que acaban de empezar a familiarizarse con MQL5.
Redes neuronales en el trading: Detección de objetos con reconocimiento de escena (HyperDet3D) Redes neuronales en el trading: Detección de objetos con reconocimiento de escena (HyperDet3D)
Le proponemos que conozca un nuevo enfoque de la detección de objetos mediante hiper-redes: una hiper-red de generación de coeficientes de peso para el modelo básico que permite tener en cuenta las peculiaridades del estado actual del mercado. Este enfoque mejora la precisión de las previsiones adaptando el modelo a las distintas condiciones comerciales.
Creación de un Panel de administración de operaciones en MQL5 (Parte III): Mejora de la interfaz gráfica de usuario con estilización visual (I) Creación de un Panel de administración de operaciones en MQL5 (Parte III): Mejora de la interfaz gráfica de usuario con estilización visual (I)
En este artículo, nos centraremos en el estilo visual de la interfaz gráfica de usuario (GUI) de nuestro Panel de Administrador de Trading utilizando MQL5. Exploraremos diversas técnicas y funciones disponibles en MQL5 que permiten personalizar y optimizar la interfaz, garantizando que satisfaga las necesidades de los operadores al tiempo que mantiene una estética atractiva.