English Русский Português
preview
Optimización por Comunidad de Científicos: Teoría

Optimización por Comunidad de Científicos: Teoría

MetaTrader 5Trading |
28 0
Andrey Dik
Andrey Dik

Contenido

  1. Introducción
  2. Implementación del algoritmo
  3. Conclusión


Introducción

Continuando con el tema del aprendizaje, en este artículo analizaremos otro enfoque para resolver problemas de optimización: el algoritmo CoSO (Community of Scientist Optimization), basado en la modelización de los mecanismos de funcionamiento de la comunidad científica. A diferencia de los algoritmos clásicos inspirados en la biología, el CoSO reproduce las características únicas de la actividad científica: la publicación de resultados en revistas, la competencia por subvenciones, la formación de grupos de investigación y el equilibrio entre la exploración en profundidad de líneas de investigación conocidas y la búsqueda de soluciones fundamentalmente nuevas. El algoritmo CoSO fue desarrollado y publicado en 2012 por dos científicos, A. Milani y V. Santucci.

Una característica interesante de este enfoque es la autoorganización natural del proceso de búsqueda. Al igual que una auténtica comunidad científica, el algoritmo concentra los recursos en áreas prometedoras, preserva y difunde las mejores soluciones a través del mecanismo de las revistas científicas y mantiene la diversidad necesaria financiando a "agentes marginales". El cambio dinámico del tamaño de la población permite que el algoritmo se adapte a las particularidades de un problema específico sin necesidad de ajustar los parámetros, lo cual supone otra innovación, ya que normalmente utilizamos una población constante. En este artículo, analizaremos con detalle los fundamentos matemáticos del algoritmo, sus componentes clave y los mecanismos de interacción entre ellos. 


Implementación del algoritmo

El algoritmo de población CoSO se basa en la idea de modelar el comportamiento de los investigadores que buscan soluciones óptimas en un espacio multidimensional, intercambian conocimientos a través de publicaciones en revistas y compiten por recursos limitados (es decir, por subvenciones). Cada investigador representa un punto en el espacio de búsqueda. Tiene: una posición (donde está buscando), una dirección de movimiento, dinero para la investigación y, si se le acaba el dinero, el investigador queda inactivo.

Cada investigador está representado por un conjunto de parámetros: la posición en el espacio de búsqueda x, la dirección de movimiento v, el mejor resultado personal b, la cantidad de fondos m, la estrategia de gestión de fondos s y las probabilidades de publicación en diferentes revistas ρ.

El movimiento de los investigadores viene determinado por una fórmula para actualizar la dirección que combina tres componentes: la inercia (la tendencia a seguir moviéndose en la misma dirección), un componente cognitivo (el deseo de obtener los mejores resultados personales) y un componente social (la influencia de los mejores resultados de las revistas): 

v {i, t} = ω · v {i, t-1} + φ₁ · β₁ · (b {i, t-1} - x {i, t-1}) + φ₂ · β₂ · Σⱼ [ρᵢⱼ · (J {j,c} - x {i, t-1})], 

donde ω = 0,7298 es el coeficiente de inercia, φ₁ = φ₂ = 1,49618 son los coeficientes de aceleración, β₁ y β₂ son números aleatorios de [0,1], ρᵢⱼ es la probabilidad de que el investigador i seleccione la revista j, J {j,c} es un artículo aleatorio de la revista j.

Después de actualizar la dirección, la posición del investigador cambia según una fórmula simple: x {i, t} = x {i, t-1} + v {i, t}

Las revistas almacenan las mejores soluciones encontradas. Los investigadores leen las revistas y se desplazan hacia buenas soluciones, y solo los mejores resultados se publican en estas. Las revistas del algoritmo sirven como memoria colectiva, almacenando las k mejores soluciones encontradas por todos los investigadores. Cuando un investigador obtiene un nuevo resultado, lo envía a una de las revistas según su distribución de probabilidad ρ, que admite el resultado solo si es mejor que el peor de los ya publicados. Esto garantiza una mejora continua en la calidad de la información disponible para la comunidad.

Los investigadores que tienen éxito obtienen más financiación. Parte del dinero se destina a los "agentes marginales": nuevos investigadores en lugares aleatorios. Esto ayuda a evitar el atasco en óptimos locales. El mecanismo de distribución de fondos imita la financiación competitiva utilizada en la ciencia. En cada iteración, los investigadores gastan una unidad de fondos, mientras que la cantidad total de dinero gastado se redistribuye según la selección mediante la ruleta. La probabilidad de recibir fondos para un investigador en la posición i de la clasificación global se determina usando la fórmula:

P (i) = (N - i + 1) / (N · (N + 1) / 2),

donde N es el número de investigadores.

Parte de los fondos Ω se reserva para la creación de "agentes marginales": nuevos investigadores en posiciones aleatorias. El parámetro Ω se modifica de forma adaptativa para mantener un equilibrio entre exploración y explotación. Cuando la desviación estándar de los valores de aptitud de la población σ pasa a ser inferior que la σ₀ inicial, esto revela la convergencia y la proporción de agentes marginales aumenta:

Ω {t} = Ω {t-1} + (Ω {max} - Ω {min}) / 2 · ε⁺ con σ < σ₀, de lo contrario Ω {t} = Ω {t-1} - (Ω {max} - Ω {min}) / 2 · ε⁻,

donde Ω {min} = 0.2, Ω {max} = 0.5, ε⁺ = 0.2, ε⁻ = 0.1.

Los científicos de éxito pueden contratar asistentes que inicien su búsqueda cerca de su supervisor. Esto ayuda a explorar en detalle áreas prometedoras. Los investigadores exitosos con fondos m > 1 pueden contratar a nuevos investigadores. En este caso, el investigador se queda con una parte de los fondos, según su estrategia, gastando el resto en la contratación de personal.

Los nuevos investigadores heredan las características del supervisor con pequeñas perturbaciones: la posición se inicializa como x {new} = x {supervisor} + N (0, σv), la estrategia como s {new} = N (s {supervisor}, σs), las probabilidades de las revistas como ρ {new, j} = N (ρ {supervisor, j},  σρ) seguidas de la normalización.

El proceso de búsqueda en el algoritmo se puede comparar con un grupo de personas recogiendo setas en el bosque: cada uno busca en su propio lugar, y cuando alguien encuentra un sitio con setas, avisa a los demás, entonces una parte del grupo acude a ese lugar, mientras que el resto continúa buscando nuevos sitios. Quienes no encuentran nada durante mucho tiempo se van a casa, pero los recolectores más exitosos llevan a sus amigos a los lugares donde se pueden encontrar más setas.

El rendimiento del algoritmo se basa en una combinación de mecanismos: las revistas facilitan el intercambio de información y orientan las búsquedas hacia áreas prometedoras, la financiación competitiva crea presión selectiva al eliminar a los investigadores ineficaces, la contratación de asistentes posibilita la búsqueda local en torno a buenas soluciones y los agentes marginales evitan que la convergencia se produzca de forma prematura.

El cambio dinámico del tamaño de la población permite que el algoritmo se adapte a la complejidad del problema, aumentando el número de investigadores en áreas prometedoras y reduciéndolo en aquellas que no lo son. Todos estos mecanismos trabajan de manera conjunta para crear un sistema autoorganizado capaz de resolver problemas en espacios multidimensionales complejos. 

coso

Figura 1. Ilustración del algoritmo CoSO en funcionamiento 

Qué nos muestra la ilustración:

  1. En el espacio de búsqueda, donde los investigadores (círculos verdes) se mueven hacia el óptimo, se muestran sus vectores de movimiento.
  2. Las revistas científicas almacenan las mejores soluciones encontradas, los investigadores las leen y publican sus resultados.
  3. La distribución de fondos (Funds Distribution) es un mecanismo de financiación con un parámetro adaptativo Ω para agentes marginales en espacios multidimensionales.
  4. La fórmula para actualizar la dirección supone la base matemática del movimiento de los investigadores.
  5. Los mecanismos clave se muestran en la leyenda, con tipos de investigadores: activos (con fondos), inactivos (sin fondos), nuevos empleados y agentes marginales al grupo con posiciones aleatorias.

Las flechas indican los flujos de información (gris), movimiento (azul) y fondos (naranja). Vamos a escribir ahora el pseudocódigo del algoritmo.

1. INICIALIZACIÓN:
   - Se crean 10 investigadores en posiciones aleatorias.
   - Se entrega a cada uno 15 unidades de fondos (150 / 10)
   - Todos reciben:
     * Una estrategia de ahorro aleatoria (de 0 a 1)
     * Preferencias aleatorias de revistas
     * Una dirección inicial del movimiento (pequeña aleatoriedad)
   - Se crean 3 revistas vacías
   - Se registra la dispersión inicial de la población

2. CICLO PRINCIPAL (se repite el número de veces especificado):
   
   2.1. EVALUACIÓN DE RESULTADOS:
        Para cada investigador activo:
        - Se calcula la calidad de la posición actual f(x)
        - Si el resultado es mejor que el récord:
          * Se actualiza el récord personal

   2.2. PUBLICACIONES EN REVISTAS:
        Para cada investigador activo:
        - Se elige una revista según las preferencias personales.
        - Se intenta publicar el resultado:
          * La revista acepta trabajos cuyos resultados se encuentren entre los 10 primeros.
          * El artículo con peor resultado queda desplazado.

   2.3. REGISTRO DE COSTOS E INFORMES:
        - Se recogen todos los fondos gastados (1 de cada uno)
        - Se elabora una clasificación de investigadores activos
        - Se marca como "muertos" a aquellos que se han quedado sin dinero.

   2.4. DISTRIBUCIÓN DE FONDOS:
        - Se determina la participación de los agentes marginales (20-50%)
        - Se reparte el resto entre los existentes:
          * Cuanto más alta sea la posición en el ranking, mayores serán las probabilidades
          * Se utiliza una ruleta ponderada



   2.5. CREANDO AGENTES MARGINALES:
        - Se gastan los fondos asignados en 1-5 novatos.
        - Se los coloca en lugares aleatorios.
        - Se otorgan partes iguales del presupuesto asignado.

   2.6. CONTRATACIÓN DE ASISTENTES:
        Para cada investigador adinerado (fondos > 1):
        - Se guarda una parte según la estrategia.
        - Para el resto, se contratan de 1 a 3 asistentes:
          * Se los ubica cerca del investigador
          * Se comparte la experiencia con pequeños cambios

   2.7. SELECCIÓN DE LA DIRECCIÓN DEL MOVIMIENTO:
        Para cada investigador activo, se calcula:
        
        Nueva_dirección = 
        0.7 × Antigua_dirección +                                    // Inercia
        1,5 × Aleatoriedad × (Récord personal - Posición) + // A su mejor
        1,5 × Aleatoriedad × Suma_por_revistas // A la mejor
        (Peso_de_la_revista × (Artículo_de_la_revista - Posición))

   2.8. MOVIMIENTO:
        Para cada investigador activo:
        - Se da un paso: Nueva_posición = Antigua_posición + Dirección
        - Si se cruzan los límites, se regresa al borde.

   2.9. CONTROL DE LA DIVERSIDAD:
        - Se mide la dispersión actual de la población
        - Si la dispersión ha disminuido (convergido):
          * Se aumenta la proporción de agentes marginales en un 10%
        - De lo contrario:
          * Se reduce la proporción de agentes marginales en un 5%

   2.10. GESTIÓN DEL TAMAÑO DE LA POBLACIÓN:
         - Si hay más del 25% de muertos o más de 200 en total:
           * Se eliminan los muertos del array
         - Si quedan más de 150:
           * Se conservan solo los 150 mejores

3. RESULTADO:
   - Se devuelve la mejor solución encontrada

Ahora podemos pasar a la implementación práctica del algoritmo CoSO. Vamos a escribir tres estructuras de datos. La primera estructura, "S_Journal_Entry", representa una única entrada de revista que contiene:

  • fitness — valor numérico que refleja la "calidad" o "idoneidad" de una entrada determinada. 
  • decision[] —  array dinámico que almacena los parámetros asociados a esta entrada.
Para la inicialización se usa el método "Init", estableciendo el tamaño del array "decision" y asignando a "fitness" un valor mínimo inicial. La segunda estructura, "S_Journal", representa la revista en sí misma: una colección ordenada de registros caracterizada por:
  • entries [] — array dinámico de entradas "S_Journal_Entry".
  • length — número actual de entradas en la revista.
  • maxLength — número máximo de entradas que puede almacenar la revista.

El método "Init" inicializa la revista estableciendo la longitud máxima, asignando memoria para los registros e inicializando cada registro con un valor mínimo de aptitud. El método "Add" se usa para añadir un nuevo registro a la revista. La particularidad de este método reside en que mantiene la revista ordenada según su valor de aptitud. Si una nueva entrada es peor que la peor entrada en una revista que ya está llena, no se agrega. De lo contrario, se busca espacio para la nueva entrada mediante una búsqueda binaria, luego se desplazan las entradas existentes para dejar espacio y se inserta la nueva entrada; si la revista no está ya llena, se aumenta su "longitud".

La tercera estructura, "S_Researcher", describe al "investigador" o "agente". Contiene un conjunto de parámetros:

  • x [] — posición actual del investigador (array de coordenadas)
  • v [] — vector de dirección o velocidad del movimiento del investigador
  • b [] — mejor posición encontrada individualmente por el investigador
  • rho [] — array de probabilidades de publicación en diferentes revistas. El investigador interactúa con varias "revistas" o fuentes de información.
  • s — estrategia de gestión de fondos
  • m — cantidad de fondos (número entero)
  • f — aptitud de la posición actual del investigador
  • fb — aptitud de la mejor posición encontrada por el investigador
  • alive — bandera que indica la actividad del investigador
El método "Init" inicializa el investigador estableciendo los tamaños de los arrays de posiciones, velocidades, mejores posiciones y probabilidades de publicación, y asigna valores iniciales a los parámetros restantes. Las estructuras de datos presentadas son componentes para implementar un método en el que los "investigadores" buscan soluciones óptimas utilizando "revistas" para almacenar y compartir las mejores soluciones encontradas. 
//————————————————————————————————————————————————————————————————————
// Structure for the journal
struct S_Journal_Entry
{
    double fitness;
    double decision [];

    void Init (int coords)
    {
      ArrayResize (decision, coords);
      fitness = -DBL_MAX;
    }
};

// Journal structure
struct S_Journal
{
    S_Journal_Entry entries [];
    int length;
    int maxLength;

    void Init (int maxLen, int coords)
    {
      maxLength = maxLen;
      length = 0;

      ArrayResize (entries, maxLen);

      for (int i = 0; i < maxLen; i++)
      {
        entries [i].Init (coords);
        entries [i].fitness = -DBL_MAX; // Initialize with the minimum value
      }
    }

    void Add (double fit, const double &coord [])
    {
      // Quick check - if it is the worst and the journal is full, do not add 
      if (length >= maxLength && fit <= entries [length - 1].fitness) return;

      int insertPos = length;

      // Find the insertion position (binary search)
      if (length > 0)
      {
        int left = 0;
        int right = length - 1;

        while (left <= right)
        {
          int mid = (left + right) / 2;
          if (entries [mid].fitness < fit) right = mid - 1;
          else left = mid + 1;
        }
        insertPos = left;
      }

      // If we insert at the end and the journal is full
      if (insertPos >= maxLength) return;

      // Shift the elements
      if (length < maxLength) length++;

      for (int i = length - 1; i > insertPos; i--)
      {
        entries [i].fitness = entries [i - 1].fitness;
        ArrayCopy (entries [i].decision, entries [i - 1].decision, 0, 0, WHOLE_ARRAY);
      }

      // Insert a new element
      entries [insertPos].fitness = fit;
      ArrayCopy (entries [insertPos].decision, coord, 0, 0, WHOLE_ARRAY);
    }
};

// Extended researcher structure
struct S_Researcher
{
    double x   [];   // current position
    double v   [];   // movement direction
    double b   [];   // personal best result
    double rho [];   // probabilities of publication in journals
    double s;        // funds management strategy
    int    m;        // amount of funds
    double f;        // fitness of the current position
    double fb;       // fitness of the best position
    bool   alive;    // activity flag

    void Init (int coords, int journalsNum)
    {
      if (ArraySize (x) != coords)
      {
        ArrayResize (x, coords);
        ArrayResize (v, coords);
        ArrayResize (b, coords);
      }
      if (ArraySize (rho) != journalsNum)
      {
        ArrayResize (rho, 0);
        ArrayResize (rho, journalsNum);
      }

      f  = -DBL_MAX;
      fb = -DBL_MAX;
      m  = 0;
      s  = 0.5;
      alive = true;
    }
};

Vamos a escribir la clase "C_AO_CoSO", que supone una implementación del algoritmo de optimización "Community of Scientist Optimization" (CoSO). La clase "C_AO_CoSO" hereda de la clase básica "C_AO" y presupone la presencia de propiedades y métodos comunes para diversos algoritmos de optimización.

Establece valores predeterminados para varios parámetros de control de CoSO:

  1. el tamaño inicial de la población de "investigadores";
  2. la cantidad total de "fondos" (recursos) que se pueden asignar (en la metáfora de los científicos, esto podría ser las subvenciones que usan para su "trabajo" (búsqueda de una solución));
  3. el número de "revistas" en las que los investigadores pueden publicar sus "descubrimientos" (mejores soluciones);
  4. el número máximo de artículos/descubrimientos que puede contener una "revista";
  5. el parámetro de "inercia", que controla la influencia de la dirección de movimiento anterior.

  • phi1 = 1,5; — parámetro cognitivo que refleja la influencia individual en el movimiento del investigador (el deseo de realizar los mejores descubrimientos propios);
  • phi2 = 1,5; — parámetro social que determina la intensidad con la que el investigador se centra en las publicaciones en revistas;
  • omegaMin = 0,2; — porcentaje mínimo de "agentes marginales";
  • omegaMax = 0,5; — porcentaje máximo de "agentes marginales";
  • epsilonPlus = 0,2; — paso de aumento de la diversidad;
  • epsilonMinus = 0,1; — paso de disminución de la diversidad.
SetParams() — método para establecer los parámetros internos de una clase en función de los valores almacenados en el array externo "params". 
Init() — método de inicialización del algoritmo. Admite rangos de búsqueda (valores mínimo y máximo) y pasos para cada parámetro, así como el número de épocas "epochsP".
Moving() — este método se encarga de "mover" (actualizar las posiciones) de los investigadores en el espacio de búsqueda en función de sus "velocidades" y "direcciones".
Revision() — método responsable de actualizar la mejor solución global encontrada por todos los investigadores hasta el momento actual.

Campos privados: array dinámico de estructuras "S_Researcher". Cada elemento de esta estructura representa un "investigador" y contiene información sobre su posición actual, su velocidad, su mejor solución encontrada, su "estado de actividad" (alive), "fitness", etc. Un array dinámico de estructuras "S_Journal", donde cada estructura almacena un conjunto de las mejores soluciones publicadas por investigadores en una "revista" determinada. Porcentaje actual de "agentes marginales". Desviación estándar inicial. Tamaño actual real de la población. Puede cambiar durante la ejecución del algoritmo. Tamaño máximo permitido de la población. 

La estructura S_GlobalReport es una estructura anidada para almacenar información sobre la mejor solución global: "fitness" (el valor de la función objetivo) e "index" (el índice del investigador que encontró esta solución).

S_GlobalReport globalReport [] — array para almacenar informes globales. 

Métodos privados del algoritmo:

  • UpdateDirection() — actualiza la dirección de movimiento (o "velocidad") para un investigador específico con índice "idx".
  • SubmitToJournal() — permite a un investigador con un índice "publicar" su mejor solución actual en una de las "revistas".
  • AssignFunds() — método para distribuir "fondos" entre los investigadores. En el CoSO, las subvenciones pueden influir en el comportamiento o la capacidad de los investigadores para realizar búsquedas.
  • HireResearchers() — el método crea nuevos investigadores.
  • CreateOutsiders() — crea "agentes marginales", investigadores especiales que pueden realizar búsquedas más agresivas o aleatorias para evitar quedarse atascados en óptimos locales.
  • ComputeStdDev() — calcula la desviación estándar de una población, y sirve como indicador de la "diversidad" o "convergencia" de la misma.
  • UpdateOmega() — actualiza el parámetro "omegaCurrent" actual.
  • SelectJournal() — selecciona una revista para publicar en función de las probabilidades dadas.
  • NormalizeProbabilities() — normaliza un array de probabilidades de modo que su suma sea igual a uno.
  • CompactPopulation() — método para "compactar" una población, eliminando a los investigadores menos efectivos, es decir, para gestionar el tamaño de la población.
//————————————————————————————————————————————————————————————————————
class C_AO_CoSO : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_CoSO () { }
  C_AO_CoSO ()
  {
    ao_name = "CoSO";
    ao_desc = "Community of Scientist Optimization";
    ao_link = "https://www.mql5.com/en/articles/18886";

    popSize      = 30;     // initial population size
    totalFunds   = 150;    // total amount of funds
    journalsNum  = 3;      // number of journals
    journalLen   = 10;     // journal length
    omega        = 0.7;    // inertia parameter

    ArrayResize (params, 5);

    params [0].name = "popSize";     params [0].val = popSize;
    params [1].name = "totalFunds";  params [1].val = totalFunds;
    params [2].name = "journalsNum"; params [2].val = journalsNum;
    params [3].name = "journalLen";  params [3].val = journalLen;
    params [4].name = "omega";       params [4].val = omega;

    //----------------------------------------------------------------
    phi1         = 1.5;    // cognitive parameter
    phi2         = 1.5;    // social parameter
    omegaMin     = 0.2;    // minimum percentage of outsiders
    omegaMax     = 0.5;    // maximum percentage of outsiders
    epsilonPlus  = 0.2;    // diversity increase step 
    epsilonMinus = 0.1;    // diversity reduction step
  }

  void SetParams ()
  {
    popSize     = (int)params [0].val;
    totalFunds  = (int)params [1].val;
    journalsNum = (int)params [2].val;
    journalLen  = (int)params [3].val;
    omega       = params      [4].val;
  }

  bool Init (const double &rangeMinP  [],
             const double &rangeMaxP  [],
             const double &rangeStepP [],
             const int     epochsP = 0);

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    totalFunds;   // total amount of funds
  int    journalsNum;  // number of journals
  int    journalLen;   // journal length
  double omega;        // inertia parameter
  double phi1;         // cognitive parameter
  double phi2;         // social parameter
  double omegaMin;     // minimum percentage of outsiders
  double omegaMax;     // maximum percentage of outsiders
  double epsilonPlus;  // diversity increase step
  double epsilonMinus; // diversity reduction step

  private: //---------------------------------------------------------
  S_Researcher researchers [];  // array of researchers
  S_Journal    journals    [];  // array of journals
  double       omegaCurrent;    // current percentage of outsiders
  double       sigma0;          // initial standard deviation
  int          actualPopSize;   // current population size
  int          maxPopSize;      // maximum population size
  double       socialComponent []; // cache for the social component

  struct S_GlobalReport
  {
      double fitness;
      int    index;
  };

  S_GlobalReport globalReport [];

  // Algorithm methods
  void   UpdateDirection   (int idx);
  void   SubmitToJournal   (int idx);
  void   AssignFunds       (int availableFunds);
  void   HireResearchers   (int idx);
  void   CreateOutsiders   (int outsiderFunds);
  double ComputeStdDev     ();
  void   UpdateOmega       ();
  int    SelectJournal     (const double &probs []);
  void   NormalizeProbabilities (double &probs []);
  void   CompactPopulation ();
};
//————————————————————————————————————————————————————————————————————

El método "Init" forma parte de la clase "C_AO_CoSO" y es responsable de preparar todas las estructuras y parámetros necesarios antes de que comience la ejecución del algoritmo. En primer lugar, se produce una inicialización estándar, relacionada con los rangos de los parámetros de búsqueda (valores mínimo, máximo y paso), con el número de épocas (epochsP) establecido. Si esta inicialización estándar falla, la función retorna "false". A continuación, se inicializan las variables de estado internas del algoritmo:

  • actualPopSize — tamaño actual de la población de investigadores,
  • maxPopSize — tamaño máximo de población permitido (o la memoria asignada para los investigadores),
  • sigma0 — valor inicial de la desviación estándar, que se calculará más adelante,
  • omegaCurrent — parámetro "omega" actual, inicializado como la media aritmética entre "omegaMin" y "omegaMax".

A continuación, se procede a comprobar la exactitud de los parámetros de entrada:

  • totalFunds — monto total de "fondos" no debe ser inferior al tamaño de la población; a cada investigador se le debe asignar una cierta cantidad de fondos o subvenciones;
  • journalsNum — el número de "revistas" debe ser al menos uno;
  • journalLen — la longitud de cada "revista" también debe ser de al menos uno;
  • omega (parámetro global) — debe estar en el rango de 0,0 a 1,0.

Tras comprobar los parámetros, se produce un "borrado completo de las ejecuciones anteriores", lo que implica restablecer a cero los tamaños de los arrays dinámicos "researchers", "journals", "globalReport" y "socialComponent"; el algoritmo siempre parte de un estado limpio. Las variables de estado ya mencionadas (actualPopSize, maxPopSize, sigma0) se restablecen nuevamente.

Luego se inicializan las "revistas": el array "journals" se redimensiona según "journalsNum". Cada revista en este array se inicializa con una longitud determinada "journalLen" y un número determinado de coordenadas "coords".

Inicialización de investigadores. "maxPopSize" se establece en al menos tres veces "popSize" (el tamaño de la población). Esto crea una "reserva" para posibles cambios en el tamaño de la población (el algoritmo crea nuevos investigadores). Se calcula "fundsPerResearcher" (fondos por investigador), que es la cantidad de fondos que se asignarán a cada investigador (la cantidad total de fondos se divide por el tamaño de la población). A continuación, se realiza un ciclo que itera por todos los investigadores.

Cada investigador se inicializa con una serie de coordenadas "coords" y una serie de revistas "journalsNum". El indicador "alive" se establece en "true" solo para los primeros investigadores "popSize", el resto siguen inactivos. A los investigadores activos se les asigna una cantidad calculada de fondos "fundsPerResearcher". Su "estrategia de gestión de fondos" "s" se inicializa aleatoriamente. Luego se inicializan sus posiciones "x", sus mejores posiciones personales "b" y sus vectores de movimiento "v". Las posiciones "x" y "b" se establecen aleatoriamente en el rango dado (rangeMin - rangeMax, teniendo en cuenta el paso "rangeStep"). Los vectores de movimiento "v" se inicializan con un pequeño valor aleatorio procedente de una distribución normal. Las probabilidades "rho" para cada revista se inicializan con valores aleatorios.

Después se llama a la función "NormalizeProbabilities", que normaliza estas probabilidades. Tras la inicialización de todos los investigadores, se calcula la desviación estándar inicial "sigma0", que caracteriza la dispersión de las posiciones de los investigadores en todas las coordenadas. Si "sigma0" resulta ser cero (por ejemplo, si todos los investigadores están en el mismo punto), se establece en 1,0 para evitar la división por cero. El parámetro "omegaCurrent" se establece nuevamente como el promedio entre "omegaMin" y "omegaMax".

Al final, las posiciones de todos los investigadores inicializados (popSize) se copian en la "array de agentes estándar" "a". De este modo, la función "Init" prepara al completo el entorno para el funcionamiento del algoritmo de optimización, inicializando todos los agentes "investigadores", sus parámetros, las "revistas" para almacenar los resultados, así como los parámetros internos para controlar el algoritmo.

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

  //------------------------------------------------------------------
  // Initialize state variables
  actualPopSize = 0;
  maxPopSize    = 0;
  sigma0        = 0;
  omegaCurrent  = omegaMin + (omegaMax - omegaMin) / 2.0;

  // Check the parameters validity
  if (totalFunds < popSize) totalFunds = popSize;
  if (journalsNum < 1) journalsNum = 1;
  if (journalLen < 1) journalLen = 1;
  if (omega < 0.0) omega = 0.0;
  if (omega > 1.0) omega = 1.0;

  // Complete reset of data from previous runs
  ArrayResize (researchers,     0);
  ArrayResize (journals,        0);
  ArrayResize (globalReport,    0);
  ArrayResize (socialComponent, 0);

  // Reset parameters
  actualPopSize = 0;
  maxPopSize    = 0;
  sigma0        = 0;

  // Initialize the journals
  ArrayResize (journals, journalsNum);
  for (int i = 0; i < journalsNum; i++)
  {
    journals [i].Init (journalLen, coords);
  }

  // Initialize the array of researchers with a reserve
  maxPopSize = MathMin (popSize * 3, 300); // Limit the maximum size
  ArrayResize (researchers, maxPopSize);
  ArrayResize (socialComponent, coords);

  actualPopSize = popSize;
  int fundsPerResearcher = totalFunds / popSize;

  for (int i = 0; i < maxPopSize; i++)
  {
    researchers [i].Init (coords, journalsNum);
    researchers [i].alive = (i < popSize);

    if (i < popSize)
    {
      researchers [i].m = fundsPerResearcher;
      researchers [i].s = u.RNDprobab ();

      // Initialize a position
      for (int c = 0; c < coords; c++)
      {
        researchers [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        researchers [i].x [c] = u.SeInDiSp  (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        researchers [i].b [c] = researchers [i].x [c];
        researchers [i].v [c] = u.GaussDistribution (0.0, -0.01, 0.01, 1);
      }

      // Initialize journal probabilities
      for (int j = 0; j < journalsNum; j++)
      {
        researchers [i].rho [j] = u.RNDprobab ();
      }

      NormalizeProbabilities (researchers [i].rho);
    }
  }

  // Calculate the initial standard deviation
  sigma0 = ComputeStdDev ();

  if (sigma0 == 0) sigma0 = 1.0; // Protection against zero divide
  omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0;

  // Copy the researchers to the standard array of agents
  for (int i = 0; i < popSize; i++)
  {
    ArrayCopy (a [i].c, researchers [i].x, 0, 0, WHOLE_ARRAY);
  }

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

El método "Moving" es la parte central del proceso iterativo del algoritmo, que ejecuta un paso en la búsqueda de la solución óptima, simulando el comportamiento de los "investigadores" y su interacción. Al inicio de la función, si esta es la primera llamada después de la inicialización (revision = false), simplemente establece "revision" en "true" y sale. A continuación se describen los pasos principales del algoritmo que se realizan en cada iteración.

Actualización sobre la idoneidad de los investigadores. Para cada investigador activo (de la población actual), su valor de aptitud se toma del array general "a", que almacena los resultados de la evaluación de aptitud de todos los agentes. Luego se comprueba si la aptitud actual del investigador es mejor que su mejor aptitud personal registrada anteriormente y, de ser así, se actualiza "fb" y la mejor posición del investigador "b" también se almacena como su posición actual "x".

Envío de resultados a revistas científicas. Cada investigador activo "envía" sus resultados actuales (aptitud y posición actuales) a una o más "revistas". Los detalles de este proceso están recogidos en la función "SubmitToJournal", que selecciona una revista según las probabilidades "rho" del investigador y añade allí su resultado si es lo suficientemente bueno.

Recopilar un informe global y calcular los fondos disponibles. En esta etapa, se reevalúa la situación de los investigadores y se preparan los recursos para su distribución. Para cada investigador activo, el número de sus "fondos" (m) disminuye en uno, simbolizando los costos de la iteración actual. El importe total de los fondos disponibles "availableFunds" aumenta. Si al investigador le quedan fondos después de gastarlos (m > 0), se considera que puede continuar trabajando y será incluido en el "informe global". Si se agotan los fondos, el investigador "muere" (alive = false). Se genera un "globalReport", un array que contiene la aptitud y el índice únicamente de aquellos investigadores que permanecen activos y cuentan con fondos.

Ordenación del informe global. Los elementos de "globalReport" se ordenan por valor de "aptitud" descendente (de mejor a peor) usando un sencillo algoritmo de ordenación de burbuja. Esto permite identificar rápidamente a los investigadores más exitosos.

Distribución de fondos. La función "AssignFunds" se usa para redistribuir los "fondos disponibles" entre los investigadores activos. Los mejores investigadores (según lo determine "globalReport") reciben más fondos, lo cual incentiva su actividad continua, mientras que los investigadores menos exitosos pueden recibir menos o nada, lo que podría llevar a su "muerte" en iteraciones futuras.

Contratación de nuevos investigadores por parte de los ya existentes. Los investigadores actuales que tengan fondos suficientes (m > 1) pueden "contratar" a nuevos investigadores. Este es un mecanismo para aumentar la población o crear nuevos agentes a partir de los ya existentes que hayan tenido éxito. Encontrará más detalles en la función "HireResearchers".

Actualizando dirección y posición. Para cada investigador activo, se llama a la función "UpdateDirection", que calcula el nuevo vector de movimiento "v" del investigador según su mejor resultado personal "b", el mejor resultado de las revistas, el componente social u otros factores del algoritmo. La posición "x" del investigador se actualiza sumando el vector de movimiento "v". Luego se comprueba la posición para detectar errores fuera de rango y se ajusta si es necesario, y también se "discretiza" o "redondea" al "rangeStep" aceptable más cercano.

Parámetro de diversidad actualizado. Después se llama a la función "UpdateOmega", que ajusta el parámetro "omegaCurrent" del algoritmo. Este parámetro se usa para controlar el equilibrio entre la exploración (encontrar nuevas áreas) y la explotación (perfeccionar las soluciones encontradas), modificándolo dinámicamente durante la ejecución del algoritmo para mantener la diversidad de la población.

Compactación de la población. La función "CompactPopulation" se usa para eliminar a los investigadores inactivos (alive = false) del array "researchers", reduciendo así su tamaño y liberando memoria.

Copiado de posiciones al array de agentes. Finalmente, las posiciones actuales de todos los investigadores activos se copian en el array "a". El tamaño de "a" se actualiza según "actualPopSize". La variable global "popSize" también se actualiza al nuevo valor "actualPopSize".

    De este modo, la función "Moving" engloba el ciclo de vida completo de una iteración del algoritmo CoSO, incluyendo la evaluación, la interacción social ("revistas"), la gestión de recursos, el cambio dinámico de la población y el movimiento de los agentes en el espacio de búsqueda.

    //————————————————————————————————————————————————————————————————————
    void C_AO_CoSO::Moving ()
    {
      if (!revision)
      {
        revision = true;
        return;
      }
    
      //--- CoSO basic steps:
    
      // 1. Updating 'fitness' of researchers from the agents array
      int aSize = ArraySize (a);
      for (int i = 0, j = 0; i < actualPopSize && j < aSize; i++)
      {
        if (!researchers [i].alive) continue;
    
        researchers [i].f = a [j].f;
    
        // Update the personal best
        if (researchers [i].f > researchers [i].fb)
        {
          researchers [i].fb = researchers [i].f;
          ArrayCopy (researchers [i].b, researchers [i].x, 0, 0, WHOLE_ARRAY);
        }
        j++;
      }
    
      // 2. Submit results to journals
      for (int i = 0; i < actualPopSize; i++)
      {
        if (researchers [i].alive) SubmitToJournal (i);
      }
    
      // 3. Collect a global report and calculate available funds
      int availableFunds = 0;
      int reportSize = 0;
    
      // Preliminary calculation of the report size
      for (int i = 0; i < actualPopSize; i++)
      {
        if (!researchers [i].alive) continue;
    
        researchers [i].m--;  // Spend 1 unit of funds per iteration
        availableFunds++;
    
        if (researchers [i].m > 0) reportSize++;
        else researchers [i].alive = false;
      }
    
      // Fill out the global report
      ArrayResize (globalReport, reportSize);
      int idx = 0;
    
      for (int i = 0; i < actualPopSize && idx < reportSize; i++)
      {
        if (researchers [i].alive && researchers [i].m > 0)
        {
          globalReport [idx].fitness = researchers [i].f;
          globalReport [idx].index = i;
          idx++;
        }
      }
    
      // 4. Quick sorting of a global report
      for (int i = 0; i < reportSize - 1; i++)
      {
        for (int j = i + 1; j < reportSize; j++)
        {
          if (globalReport [i].fitness < globalReport [j].fitness)
          {
            S_GlobalReport temp = globalReport [i];
            globalReport [i] = globalReport [j];
            globalReport [j] = temp;
          }
        }
      }
    
      // 5. Distribution of funds
      AssignFunds (availableFunds);
    
      // 6. Hire new researchers by existing ones
      for (int i = 0; i < actualPopSize; i++)
      {
        if (researchers [i].alive && researchers [i].m > 1) HireResearchers (i);
      }
    
      // 7. Update the direction and position for each researcher
      for (int i = 0; i < actualPopSize; i++)
      {
        if (!researchers [i].alive) continue;
    
        UpdateDirection (i);
    
        // Update position
        for (int c = 0; c < coords; c++)
        {
          researchers [i].x [c] += researchers [i].v [c];
    
          // Boundary control
          if (researchers [i].x [c] < rangeMin [c]) researchers [i].x [c] = rangeMin [c];
          if (researchers [i].x [c] > rangeMax [c]) researchers [i].x [c] = rangeMax [c];
    
          researchers [i].x [c] = u.SeInDiSp (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    
      // 8. Update the diversity parameter
      UpdateOmega ();
    
      // 9. Population compactification
      CompactPopulation ();
    
      // 10. Copy positions to the array of agents to calculate 'fitness'
      ArrayResize (a, actualPopSize);
      idx = 0;
      for (int i = 0; i < maxPopSize && idx < actualPopSize; i++)
      {
        if (researchers [i].alive)
        {
          a [idx].Init (coords);
          ArrayCopy (a [idx].c, researchers [i].x, 0, 0, WHOLE_ARRAY);
          idx++;
        }
      }
    
      popSize = actualPopSize;  // Update the population size
    }
    //————————————————————————————————————————————————————————————————————
    

    La función "UpdateDirection" se encarga de actualizar el vector de movimiento "v" para un investigador individual en el algoritmo CoSO. Este proceso es clave para el movimiento de los agentes (investigadores) en el espacio de búsqueda y les permite investigar nuevas áreas y utilizar la información obtenida tanto personalmente como de otros agentes. La función toma un parámetro como entrada: "idx" (el índice del investigador para quien se necesita actualizar la dirección).

    Al inicio de la función, se generan dos variables aleatorias entre 0 y 1: "beta1", que se utiliza como coeficiente para el componente personal, y "beta2", que se usa como coeficiente para el componente social. A continuación, se calcula el componente social, mientras que el array "socialComponent" se reinicia a cero, lo que garantiza que esté limpio antes de cada nuevo cálculo.

    Luego el ciclo itera por todas las "revistas" disponibles para el algoritmo y, para cada uno, comprueba si contiene información (lenght > 0), ya que una revista vacía no puede proporcionar información para el componente social. Si la revista no está vacía, se selecciona aleatoriamente una entrada (entryIdx) de la misma. 

    Para cada coordenada (dimensionalidad del espacio de búsqueda), se actualiza el componente social. La contribución de una entrada de revista seleccionada se calcula como la probabilidad de que un investigador seleccione la revista (rho[j]) * (posición de la revista - posición actual del investigador). Estas contribuciones se suman en todas las revistas formando la influencia global de la información social sobre el movimiento del investigador. Después de calcular el componente social, el vector de movimiento del investigador se actualiza, el nuevo vector de movimiento (v [c]) = componente inercial + componente personal + componente social.

    Analicemos cada parte:

    Componente inercial: omega * investigadores [idx]. v [c]. Aquí, "omega" (el parámetro "omegaCurrent" establecido en la función "Init" y actualizado en "Moving") es el coeficiente de inercia. Determina en qué medida el investigador mantendrá su dirección de movimiento anterior. Un valor alto significa que el investigador continuará moviéndose en la misma dirección; un valor bajo significa que cambiará de dirección más rápidamente bajo la influencia de otros factores.

    Componente personal: phi1 * beta1 * (researchers [idx]. b [c] — researchers [idx]. x [c]), donde "phi1" es un factor de aceleración asociado con la experiencia personal y "beta1" es un factor aleatorio que agrega estocasticidad.
    (researchers [idx]. b [c] — researchers [idx]. x [c]) es un vector que apunta desde la posición actual del investigador hasta su mejor posición personal encontrada previamente (fb). Este componente hace que el investigador vuelva a sus propios ámbitos de éxito.

    Componente social: phi2 * beta2 * socialComponent[c], donde "phi2" es el factor de aceleración asociado con la experiencia social (información de otros), "beta2" es un factor aleatorio que agrega estocasticidad, y socialComponent[c] es el vector calculado anteriormente que tiene en cuenta la influencia de decisiones seleccionadas aleatoriamente de todas las revistas. Dicho componente orienta al investigador hacia las posiciones exitosas encontradas por el equipo. La combinación de estos tres componentes permite al investigador simultáneamente:
    1. mantener cierto movimiento inercial;
    2. "recordar" y volver a sus mejores hallazgos;
    3. estudiar y monitorear los resultados exitosos de otros investigadores.

    Como resultado, "UpdateDirection" calcula un nuevo vector de movimiento para el investigador, que se utilizará en el siguiente paso para actualizar su posición en el espacio de búsqueda.

    //————————————————————————————————————————————————————————————————————
    void C_AO_CoSO::UpdateDirection (int idx)
    {
      double beta1 = u.RNDprobab ();
      double beta2 = u.RNDprobab ();
    
      // Social component
      ArrayInitialize (socialComponent, 0);
    
      for (int j = 0; j < journalsNum; j++)
      {
        if (journals [j].length > 0)
        {
          int entryIdx = u.RNDminusOne (journals [j].length);
    
          for (int c = 0; c < coords; c++)
          {
            socialComponent [c] += researchers [idx].rho [j] *
                                   (journals [j].entries [entryIdx].decision [c] - researchers [idx].x [c]);
          }
        }
      }
    
      // Update direction
      for (int c = 0; c < coords; c++)
      {
        researchers [idx].v [c] = omega * researchers [idx].v [c] +
                                  phi1 * beta1 * (researchers [idx].b [c] - researchers [idx].x [c]) +
                                  phi2 * beta2 * socialComponent [c];
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    La función "SubmitToJournal" describe el proceso mediante el cual un investigador individual "publica" sus resultados actuales en una de las "revistas" disponibles. Este es un mecanismo clave para la difusión de información y el aprendizaje social dentro del algoritmo CoSO. La función requiere un parámetro: "idx", que es el índice del investigador que desea enviar sus resultados a la revista. El funcionamiento de la función consta de dos pasos principales:

    Seleccionar una revista. En primer lugar, se llama a la función auxiliar "SelectJournal". Esta función toma como entrada el array "rho" del investigador. La array "rho" (que representa probabilidades o preferencias) determina la probabilidad de que un investigador determinado envíe sus resultados a una revista en particular. La función "SelectJournal" selecciona aleatoriamente una de las revistas disponibles en función de estas probabilidades y devuelve su índice (journalIdx).

    Añadiendo una entrada a la revista. Una vez seleccionada una revista, se llama al método "Agregar" sobre ella. A este método se le pasan dos parámetros:

    • researchers [idx]. f — valor de aptitud actual del investigador. Esta es la "calidad" o el "éxito" de la solución que ha encontrado.
    • researchers [idx]. x — posición actual (coordenadas en el espacio de búsqueda) del investigador. Precisamente esta "solución" ha dado lugar a tal aptitud.

      El método "Add" de la revista seleccionada (la revista indexada por journalIdx) es el responsable de agregar realmente esta información. De este modo, la revista preserva el conocimiento colectivo o las mejores prácticas descubiertas por diversos investigadores.

      En general, "SubmitToJournal" simula el proceso de publicación de resultados científicos, donde los investigadores eligen dónde enviar su trabajo, y luego ese trabajo queda disponible para que otros lo vean. Esto permite que el algoritmo acumule y distribuya soluciones exitosas entre la población de agentes.

      //————————————————————————————————————————————————————————————————————
      void C_AO_CoSO::SubmitToJournal (int idx)
      {
        int journalIdx = SelectJournal (researchers [idx].rho);
        journals [journalIdx].Add (researchers [idx].f, researchers [idx].x);
      }
      //————————————————————————————————————————————————————————————————————
      

      La función "SelectJournal" está diseñada para seleccionar una revista de entre un conjunto de las disponibles, basándose en probabilidades específicas. Se trata de una implementación de la selección mediante ruleta o selección proporcional, donde cada revista tiene un cierto "peso" o "preferencia". La función toma un parámetro: probs[] - un array de números de punto flotante que representan las probabilidades o preferencias relativas para elegir cada revista.

      La función opera de la siguiente forma: primero, se genera un número aleatorio "rnd", distribuido uniformemente entre 0 (inclusive) y 1 (exclusivo), que se utilizará para determinar qué revista se seleccionará. La variable "cumSum" se inicializa a cero y acumulará las sumas de las probabilidades a medida que itere el array "probs".

        Iteración sobre probabilidades. La función comienza a iterar sobre los elementos del array "probs" en orden, desde el primero hasta el último. En cada iteración, la probabilidad actual "probs" se suma a "cumSum". Por ello, "cumSum" es la suma de las probabilidades desde el inicio del array hasta la posición actual "i". El número aleatorio generado "rnd" se compara con el "cumSum" actual. Si "rnd" es menor o igual que "cumSum", significa que el número aleatorio "ha caído" en el intervalo asociado con la revista actual (es decir, la revista con índice "i"). En este caso, la función devuelve inmediatamente el índice "i".

        Procesamiento de un caso extremo. Si el bucle termina y no se ha seleccionado ninguna revista (lo que teóricamente podría ocurrir debido a la precisión extremadamente baja de los cálculos, si "rnd" es 1 y la suma de todas las "probs" es exactamente 1, o si la suma de "probs" es ligeramente menor que 1), la función devuelve el índice de la última revista (probsSize - 1). Esto garantiza que la revista siempre sea seleccionada, incluso en casos límite o patológicos.

          Básicamente, esta función crea una serie de "ranuras" en la recta numérica del 0 al 1, donde la longitud de cada ranura es proporcional a la probabilidad de la revista correspondiente. Luego lanza un "dardo" (un número aleatorio "rnd") y ve en qué ranura cae. Esto garantiza que las revistas con mayores probabilidades sean seleccionadas con más frecuencia.

          //————————————————————————————————————————————————————————————————————
          int C_AO_CoSO::SelectJournal (const double &probs [])
          {
            double rnd = u.RNDprobab ();
            double cumSum = 0;
          
            int probsSize = ArraySize (probs);
            for (int i = 0; i < probsSize; i++)
            {
              cumSum += probs [i];
              if (rnd <= cumSum) return i;
            }
          
            return probsSize - 1;
          }
          //————————————————————————————————————————————————————————————————————
          


          Conclusión

          Así pues, hemos analizado en detalle la teoría del algoritmo CoSO, así como parte de la implementación de sus métodos en código. El material ha resultado significativamente superior a lo esperado al comienzo del proyecto, por lo que hemos decidido dividir el artículo en dos partes. El algoritmo en sí es sumamente interesante y multifacético, con una lógica profunda y compleja que merece una cuidadosa consideración.

          En la segunda parte, continuaremos describiendo los métodos específicos, así como probando el algoritmo y presentando conclusiones sobre su efectividad.

          Programas usados en el artículo

          # Nombre Tipo Descripción
          1 #C_AO.mqh
          Archivo de inclusión
          Clase padre de algoritmos de optimización basados en la población
          2 #C_AO_enum.mqh
          Archivo de inclusión
          Enumeración de los algoritmos de optimización basados en la población
          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 en una tabla comparativa
          7
          Testing AOs.mq5
          Script Banco de pruebas único para todos los algoritmos de optimización basados en la población
          8
          Simple use of population optimization algorithms.mq5
          Script
          Ejemplo sencillo de utilización de algoritmos de optimización basados en la población sin visualización
          9
          Test_AO_CoSO.mq5
          Script Banco de pruebas para CoSO

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

          Archivos adjuntos |
          CoSO.ZIP (325.17 KB)
          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.
          Introducción a MQL5 (Parte 25): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico (II) Introducción a MQL5 (Parte 25): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico (II)
          Este artículo explica cómo crear un Asesor Experto (EA) que interactúe con los objetos del gráfico, en particular con las líneas de tendencia, para identificar y operar con oportunidades de ruptura y reversión. Aprenderás cómo el Asesor Experto (EA) confirma las señales válidas, gestiona la frecuencia de las operaciones y mantiene la coherencia con las estrategias seleccionadas por el usuario.
          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.
          Simulación de mercado: Position View (XIV) Simulación de mercado: Position View (XIV)
          Ahora implementaremos esta solución porque MQL5 sigue el mismo principio que la programación basada en eventos. Los desarrolladores utilizan este modelo con frecuencia al crear DLL. Sé que, al principio, el funcionamiento basado en eventos parecerá confuso y carente de lógica. Pero, en este artículo, presentaré los principios de la programación basada en eventos con mayor claridad para que tú, que estás empezando, comprendas adecuadamente su funcionamiento. Entender lo que comenzaré a explicar en este artículo te ayudará en tu vida como programador.