Русский Português
preview
Algoritmo de optimización de neuroboides 2 — Neuroboids Optimization Algorithm 2 (NOA2)

Algoritmo de optimización de neuroboides 2 — Neuroboids Optimization Algorithm 2 (NOA2)

MetaTrader 5Probador |
15 0
Andrey Dik
Andrey Dik

Contenido

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


Introducción

Al sumergirme cada vez más en los algoritmos de optimización a lo largo de los años, con frecuencia me atraían dos inspiraciones paralelas, como la autoorganización de los enjambres biológicos y la capacidad de aprendizaje adaptativo de las redes neuronales. La síntesis de estos dos paradigmas me llevó a desarrollar un algoritmo de enfoque híbrido que combina la inteligencia espacial de los Boids de Craig Reynolds con la capacidad de aprendizaje adaptativo que poseen las redes neuronales.

Mi viaje comenzó con la observación de que los algoritmos de enjambre tradicionales, aunque adecuados para explorar espacios de búsqueda complejos a través de un comportamiento colectivo, a menudo carecen de la capacidad de aprender de la historia de sus exploraciones. Y al contrario, las redes neuronales son excelentes en el aprendizaje de patrones complejos, pero pueden tener dificultades con la exploración espacial eficiente cuando se aplican directamente a problemas de optimización. La pregunta que impulsó mi investigación resultaba aparentemente sencilla: ¿y si cada agente del enjambre pudiera aprender a mejorar sus estrategias de movimiento usando una red neuronal específica?

El algoritmo resultante utiliza agentes que siguen las reglas boid clásicas de cohesión, separación y alineación, lo cual les permite autoorganizarse y explorar el espacio de búsqueda con eficacia. No obstante, a diferencia de las implementaciones boid tradicionales, cada agente está equipado con una red neuronal multicapa que aprende continuamente de la experiencia del agente adaptando sus estrategias de movimiento a las características específicas del paisaje de adaptabilidad. Este nivel de control neuronal influye gradualmente en el comportamiento de los boids, pasando de estrategias dominadas por la exploración en las primeras iteraciones a movimientos impulsados por la explotación a medida que se identifican regiones prometedoras.

Lo que más me fascinó durante el desarrollo fue observar cómo las redes neuronales evolucionaban con estrategias distintas según su posición en el espacio de búsqueda. Los agentes situados cerca de zonas prometedoras desarrollaron patrones neuronales que potenciaron la explotación local, mientras que los situados en zonas poco pobladas favorecieron el comportamiento exploratorio. Dicha especialización emergente surgió de forma natural a partir de procesos de aprendizaje individuales, creando un enjambre con un comportamiento heterogéneo y dependiente del contexto, sin coordinación global aparente.

En este artículo, presentaremos la arquitectura, los detalles de implementación y el análisis de rendimiento del algoritmo NOA2, así como una demostración de sus capacidades en varios parámetros de referencia. 


Implementación del algoritmo

Puede que me esté repitiendo, la idea básica del algoritmo de neuroboids consiste en combinar dos paradigmas: la inteligencia colectiva de los algoritmos de enjambre y el aprendizaje adaptativo de las redes neuronales.

En el algoritmo tradicional de boids que propuso Craig Reynolds, los agentes siguen tres reglas sencillas: convergencia (moverse hacia el centro del grupo), separación (evitar colisiones) y alineación (igualar la velocidad con los vecinos). Estas reglas crean un comportamiento de grupo realista, similar al de los pájaros en bandada. Los neuroboids amplían este concepto dotando a cada agente de una red neuronal individual que aprende de la experiencia del agente conforme explora el espacio de búsqueda. Esta red neuronal realiza dos funciones clave:

  1. Control de movimiento adaptativo: ajusta la velocidad del agente según el estado actual y la historia de movimientos.
  2. Modificación de las reglas estándar de boids: ajusta dinámicamente el impacto de las reglas de convergencia, separación y alineación en función del contexto.

El resultado es un algoritmo híbrido en el que cada agente mantiene el comportamiento social necesario para explorar el espacio de forma eficiente, pero se adapta individualmente al paisaje de la función de aptitud mediante el aprendizaje. Esto crea un equilibrio autorregulado entre exploración y explotación.

Las principales ventajas de este enfoque son que los agentes aprenden de manera autónoma estrategias de movimiento óptimas, como consecuencia de lo cual el algoritmo se adapta automáticamente a distintos tipos de paisajes de optimización, al tiempo que se preserva la exploración del espacio mediante un comportamiento colectivo sin control centralizado. Por poner una analogía sencilla: imagine una bandada de pájaros volando por el cielo. Se mueven con coherencia: ninguno choca, se mantienen unidos y vuelan en la misma dirección. Este comportamiento puede describirse con tres sencillas reglas: permanecer cerca de los vecinos (no separarse de la bandada), no chocar con ellos (mantener la distancia) y volar en la misma dirección (mantener un rumbo común).

Esta es la base del algoritmo llamado "boids" (de "bird-oid", "semejante a un pájaro"). Cada ave de la bandada no se limita a seguir estas reglas, sino que es capaz de aprender de sus experiencias. El pájaro recuerda qué acciones le llevaron al éxito (por ejemplo, encontrar más comida) y cuáles de ellas no. Con el tiempo, se vuelve más inteligente y toma mejores decisiones sobre cómo volar. Esta es la esencia del algoritmo de neuroboids: combinar las sencillas reglas del movimiento en grupo con la capacidad de cada participante para aprender de su propia experiencia. El "neuro" del nombre se refiere exactamente a la capacidad de aprender. Este enfoque resulta especialmente interesante porque combina el poder de la investigación colaborativa (nadie se pierde un área importante) con los beneficios del aprendizaje individualizado (cada uno se convierte en el mejor en su área de búsqueda).

NOA2-illustration

Figura 1. Ilustración del funcionamiento del algoritmo NOA2  

La ilustración muestra los siguientes elementos clave: el paisaje de optimización, en el que la región amarilla y morada representa el óptimo global, las regiones naranja y morada representan los óptimos locales, y las curvas de nivel muestran la "altura" del paisaje de la función de aptitud. 

Diferentes grupos de agentes: los agentes azules exploran la región del óptimo global, los agentes morados se concentran en torno al primer óptimo local, los agentes verdes exploran el segundo óptimo local y los agentes rojos exploran el espacio de forma aleatoria. Las flechas muestran la dirección de movimiento de los boids. El punto rojo marca la mejor solución encontrada actualmente.

NOA2-diagram

Figura 2. Esquema de funcionamiento del algoritmo NOA2  

El esquema incluye: un bloque de inicialización (rojo), una red neuronal de boid (rosa), iteraciones del algoritmo con subbloques (azul), mecanismos adaptativos (verde) y visualización de la arquitectura de la red neuronal con conexiones de ejemplo. La parte inferior muestra esquemas en miniatura en forma de estructura de red neuronal, la visualización de las reglas de movimiento de los boids y el entre investigación y explotación.

Una vez entendido el concepto básico, podemos empezar a describir el pseudocódigo del algoritmo.

INICIALIZACIÓN:

  • Establecemos los parámetros de búsqueda y creamos una población de agentes.
  • Para cada agente: inicializamos una posición aleatoria, una velocidad pequeña, una red neuronal (entrada → oculta → capas de salida) y una memoria de experiencia vacía.
  • Establecemos la mejor adaptabilidad global en infinito negativo y el contador de estancamiento en cero.

CICLO GENERAL:

  • Para cada iteración:

    EVALUACIÓN:

    • Calculamos la adaptabilidad de todos los agentes.
    • Actualizamos las primeras posiciones personales y globales.
    • Guardamos los experimentos en los búferes de memoria.

    GESTIÓN DEL ESTANCAMIENTO:

    • Si no hay mejora: aumentamos la investigación, reiniciamos los agentes débiles de vez en cuando.
    • En caso contrario: reducimos gradualmente el nivel de investigación.

    PROCESAMIENTO NEURONAL:

    • Para cada agente:
      • Pasada directa: entradas (posición, velocidad, distancia al mejor, adaptabilidad) → capas ocultas → salidas.
      • Si se acumula suficiente experiencia y se detecta una mejora: actualizamos los pesos neuronales.

    ACTUALIZACIÓN DEL MOVIMIENTO:

    • Para cada agente:
      • Calculamos las fuerzas estándar de boids (cohesión, separación, nivelación).
      • Aplicamos fuerzas modificadas por neuronas y correcciones neuronales directas.
      • Añadimos un componente de investigación aleatoria con probabilidad.
      • Hacemos cumplir los límites de velocidad y consideramos los límites.
      • Actualizamos la posición según la velocidad final.

CÁLCULOS DE SOPORTE:

  • Cálculo de la masa: normalizamos los valores de adaptabilidad para asignar una masa a cada agente.
  • Cohesión: nos desplazamos hacia el centro de masa ponderado de los agentes más próximos.
  • Separación: evitamos la aglomeración de vecinos.
  • Alineación: coordinamos la velocidad con los agentes cercanos.
  • Actualización neuronal: retropropagación simplificada basada en la mejora de la adaptabilidad.

RETORNO: mejor posición global y adaptabilidad.

Ahora todo está preparado para escribir el código del algoritmo. Asignaremos la estructura "S_NeuroBoids_Agent", que será un agente neuroboid basado en una arquitectura de red neuronal para realizar tareas de optimización. En esta implementación, el agente tendrá los siguientes componentes y funciones clave:

Coordenadas y velocidades:

  • x [] — coordenadas actuales del agente.
  • dx [] — velocidades actuales del agente.
Red neuronal:
  • inputs [] — valores de entrada para las neuronas (coordenadas, velocidades, distancia a la mejor posición conocida, etc.).
  • outputs [] — valores de salida de la red neuronal (corrección de velocidad y parámetros de adaptación).
  • steps [] — pesos de los enlaces en la red neuronal.
  • biases [] — sesgos para las neuronas.
Memoria:
  • memory [] — array para almacenar las posiciones anteriores y sus valores de aptitud.
  • memory_size — tamaño máximo de la memoria para el almacenamiento.
  • memory_index — índice actual en la memoria.
Aptitud y masa:
  • best_local_fitness — mejor agente local de aptitud.
  • m — masa del agente.
  • cB [] — coordenadas de la mejor posición encontrada por el agente.
  • fB — valor de la función de aptitud para la mejor posición.

Inicialización: (Init) todas los arrays y variables se inicializan con ceros o valores aleatorios. Se fijan los tamaños de los arrays y se inicializan las influencias (pesos y desplazamientos) con pequeños valores aleatorios.

Funciones de activación — Tanh, ReLU, Sigmoid; diferentes funciones de activación que se aplican en la red neuronal.

Activación de los datos de entrada (UpdateInputs) — rellena un array con las coordenadas actuales, las velocidades, la distancia a la mejor posición conocida y el valor de aptitud actual.

Pasada directa (ForwardPass) — calcula las salidas de la red neuronal basándose en las entradas, los pesos y los desplazamientos aplicando funciones de activación.

Aprendizaje basado en la experiencia (Learn) — actualiza los pesos y desplazamientos según la experiencia si la experiencia actual es mejor que la anterior.

Memorización de la experiencia (MemorizeExperience) — guarda en la memoria las coordenadas actuales y la forma física del agente.

Actualización de la mejor posición (UpdateBestPosition) — si el valor de aptitud actual es mejor que el encontrado anteriormente, actualiza la mejor posición.

//——————————————————————————————————————————————————————————————————————————————
// Структура нейро-боидного агента
//——————————————————————————————————————————————————————————————————————————————
struct S_NeuroBoids_Agent
{
    double x       [];            // текущие координаты
    double dx      [];            // текущие скорости
    double inputs  [];            // входы нейрона
    double outputs [];            // выходы нейрона
    double weights [];            // веса нейрона
    double biases  [];            // смещения нейрона
    double memory  [];            // память предыдущих позиций и их фитнесов
    int    memory_size;           // размер памяти для накопления опыта
    int    memory_index;          // текущий индекс в памяти
    double best_local_fitness;    // лучший локальный фитнес агента
    double m;                     // масса бойда
    double cB [];                 // координаты лучшей позиции
    double fB;                    // значение фитнес-функции

    // Инициализация агента
    void Init (int coords, int neuron_size)
    {
      ArrayResize (x, coords);
      ArrayResize (dx, coords);
      ArrayInitialize (x, 0.0);
      ArrayInitialize (dx, 0.0);

      // Инициализируем структуру лучшей позиции
      ArrayResize     (cB, coords);
      ArrayInitialize (cB, 0.0);
      fB = -DBL_MAX;

      // Входы: координаты, скорости, расстояние до лучшего и т.д.
      int input_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness
      ArrayResize (inputs, input_size);
      ArrayInitialize (inputs, 0.0);

      // Выходы: коррекция скоростей и адаптивные факторы для правил стаи
      int output_size = coords + 3; // dx_correction + 3 адаптивных параметра
      ArrayResize (outputs, output_size);
      ArrayInitialize (outputs, 0.0);

      // Веса и смещения для каждого выхода
      ArrayResize (weights, input_size * output_size);
      ArrayInitialize (weights, 0.0);
      ArrayResize (biases, output_size);
      ArrayInitialize (biases, 0.0);

      // Инициализация весов и смещений малыми случайными значениями
      for (int i = 0; i < ArraySize (weights); i++)
      {
        weights [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0);
      }

      for (int i = 0; i < ArraySize (biases); i++)
      {
        biases [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0);
      }

      // Инициализация памяти для накопления опыта
      memory_size = 10;
      ArrayResize (memory, memory_size * (coords + 1)); // координаты + фитнес
      ArrayInitialize (memory, 0.0);
      memory_index = 0;
      best_local_fitness = -DBL_MAX;

      // Инициализация массы
      m = 0.5;
    }

    // Функция активации - гиперболический тангенс
    double Tanh (double input_val)
    {
      return MathTanh (input_val);
    }

    // Функция активации ReLU
    double ReLU (double input_val)
    {
      return (input_val > 0.0) ? input_val : 0.0;
    }

    // Функция активации Sigmoid
    double Sigmoid (double input_val)
    {
      return 1.0 / (1.0 + MathExp (-input_val));
    }

    // Обновление входов нейронной сети - исправленная версия
    void UpdateInputs (double &global_best [], double current_fitness, int coords_count)
    {
      int input_index = 0;

      // Координаты
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = x [c];
      }

      // Скорости
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = dx [c];
      }

      // Расстояние до лучшего глобального решения
      double dist_to_best = 0;
      for (int c = 0; c < coords_count; c++)
      {
        dist_to_best += MathPow (x [c] - global_best [c], 2);
      }
      dist_to_best = MathSqrt (dist_to_best);
      inputs [input_index++] = dist_to_best;

      // Текущая фитнес-функция
      inputs [input_index++] = current_fitness;
    }

    // Прямое распространение по сети
    void ForwardPass (int coords_count)
    {
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // Для каждого выхода вычисляем взвешенную сумму входов + смещение
      for (int o = 0; o < output_size; o++)
      {
        double sum = biases [o];

        for (int i = 0; i < input_size; i++)
        {
          sum += inputs [i] * weights [o * input_size + i];
        }

        // Применяем разные функции активации в зависимости от выхода
        if (o < coords_count) // Для коррекции скорости используем гиперболический тангенс
        {
          outputs [o] = Tanh (sum);
        }
        else // Для адаптивных параметров используем сигмоиду
        {
          outputs [o] = Sigmoid (sum);
        }
      }
    }

    // Обучение на основе накопленного опыта
    void Learn (double learning_rate, int coords_count)
    {
      if (memory_index < memory_size) return; // Недостаточно опыта

      // Находим лучший опыт в памяти
      int best_index = 0;
      double best_fitness = memory [coords_count]; // Первая фитнес-функция в памяти

      for (int i = 1; i < memory_size; i++)
      {
        double fitness = memory [i * (coords_count + 1) + coords_count];
        if (fitness > best_fitness)
        {
          best_fitness = fitness;
          best_index = i;
        }
      }

      // Если текущий опыт не лучше предыдущего, не обновляем веса
      if (best_fitness <= best_local_fitness) return;

      best_local_fitness = best_fitness;

      // Простой метод обновления весов
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // Простая форма градиентного обновления
      for (int o = 0; o < output_size; o++)
      {
        for (int i = 0; i < input_size; i++)
        {
          int weight_index = o * input_size + i;
          if (weight_index < ArraySize (weights))
          {
            weights [weight_index] += learning_rate * outputs [o] * inputs [i];
          }
        }

        // Обновляем смещения
        biases [o] += learning_rate * outputs [o];
      }
    }

    // Запоминаем текущий опыт (координаты и фитнес)
    void MemorizeExperience (double fitness, int coords_count)
    {
      int offset = memory_index * (coords_count + 1);

      // Запоминаем координаты
      for (int c = 0; c < coords_count; c++)
      {
        memory [offset + c] = x [c];
      }

      // Запоминаем фитнес-функцию
      memory [offset + coords_count] = fitness;

      // Обновляем индекс памяти
      memory_index = (memory_index + 1) % memory_size;
    }

    // Обновление лучшей позиции агента
    void UpdateBestPosition (double current_fitness, int coords_count)
    {
      if (current_fitness > fB)
      {
        fB = current_fitness;
        ArrayCopy (cB, x, 0, 0, WHOLE_ARRAY);
      }
    }
};

La clase "C_AO_NOA2" es una implementación del algoritmo NOA2 y se hereda de la clase básica "C_AO". Veamos más de cerca la estructura y los aspectos clave de esta clase.

Parámetros clave:
  • popSize — tamaño de la población de agentes (boids).
  • cohesionWeight, cohesionDist — peso y distancia de la regla de cohesión.
  • separationWeight, separationDist — peso y distancia de la regla de separación.
  • alignmentWeight, alignmentDist — peso y distancia de la regla de alineación.
  • maxSpeed y minSpeed — velocidades máxima y mínima de los agentes.
  • learningRate — velocidad de aprendizaje de la red neuronal.
  • neuralInfluence — influencia de la red neuronal en el movimiento de los agentes.
  • ExplorationRate — probabilidad de exploración aleatoria del espacio de soluciones.
  • m_stagnationCounter y m_prevBestFitness — contador de estancamiento y valor anterior de mejor valor de aptitud.
Métodos de clase:
  • SetParams () — método que establece los parámetros del algoritmo basándose en los valores almacenados en el array "params".
  • Init () — inicialización, acepta los parámetros para definir los rangos de valores para los agentes. Este método establece las condiciones iniciales para que se ejecute el algoritmo.
  • Moving () — se encarga del movimiento de los agentes usando como base varias reglas de interacción.
  • Revision () — se utiliza para revisar el estado de los agentes durante la ejecución del algoritmo.

Agentes: S_NeuroBoids_Agent agent [] — array de agentes que representan los boids en la población.

Métodos privados (para el uso dentro de una clase):

  • CalculateMass () — calcula la masa de los agentes.
  • Cohesión () — implementa la regla de convergencia.
  • Separation () — implementa la regla de separación.
  • Alignment () — implementa la regla de alineación.
  • LimitSpeed () — limita la velocidad del agente.
  • KeepWithinBounds () — mantiene a los agentes dentro de los límites dados.
  • Distance () — calcula la distancia entre dos agentes.
  • ApplyNeuralControl () — aplica el control de la red neuronal al agente.
  • AdaptiveExploration() — implementa la exploración adaptativa.
//——————————————————————————————————————————————————————————————————————————————
// Класс нейро-боидного алгоритма оптимизации, наследуемый от C_AO
//——————————————————————————————————————————————————————————————————————————————
class C_AO_NOA2 : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_NOA2 () { }
  C_AO_NOA2 ()
  {
    ao_name = "NOA2";
    ao_desc = "Neuroboids Optimization Algorithm 2 (joo)";
    ao_link = "https://www.mql5.com/es/articles/17497";

    popSize          = 50;     // размер популяции

    cohesionWeight   = 0.6;    // вес сближения
    cohesionDist     = 0.001;  // дистанция сближения

    separationWeight = 0.005;  // вес разделения
    separationDist   = 0.03;   // дистанция разделения

    alignmentWeight  = 0.1;    // вес выравнивания
    alignmentDist    = 0.1;    // дистанция выравнивания

    maxSpeed         = 0.001;  // максимальная скорость
    minSpeed         = 0.0001; // минимальная скорость

    learningRate     = 0.01;   // скорость обучения нейронной сети
    neuralInfluence  = 0.3;    // влияние нейронной сети на движение
    explorationRate  = 0.1;    // вероятность случайного исследования

    ArrayResize (params, 12);

    params [0].name  = "popSize";          params [0].val = popSize;

    params [1].name  = "cohesionWeight";   params [1].val  = cohesionWeight;
    params [2].name  = "cohesionDist";     params [2].val  = cohesionDist;

    params [3].name  = "separationWeight"; params [3].val  = separationWeight;
    params [4].name  = "separationDist";   params [4].val  = separationDist;

    params [5].name  = "alignmentWeight";  params [5].val  = alignmentWeight;
    params [6].name  = "alignmentDist";    params [6].val  = alignmentDist;

    params [7].name  = "maxSpeed";         params [7].val  = maxSpeed;
    params [8].name  = "minSpeed";         params [8].val  = minSpeed;

    params [9].name  = "learningRate";     params [9].val  = learningRate;
    params [10].name = "neuralInfluence";  params [10].val = neuralInfluence;
    params [11].name = "explorationRate";  params [11].val = explorationRate;

    // Инициализация счетчика стагнации и предыдущего лучшего значения фитнеса
    m_stagnationCounter = 0;
    m_prevBestFitness   = -DBL_MAX;
  }

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

    cohesionWeight   = params [1].val;
    cohesionDist     = params [2].val;

    separationWeight = params [3].val;
    separationDist   = params [4].val;

    alignmentWeight  = params [5].val;
    alignmentDist    = params [6].val;

    maxSpeed         = params [7].val;
    minSpeed         = params [8].val;

    learningRate     = params [9].val;
    neuralInfluence  = params [10].val;
    explorationRate  = params [11].val;
  }

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

  //----------------------------------------------------------------------------
  double cohesionWeight;     // вес сближения
  double cohesionDist;       // дистанция сближения

  double separationWeight;   // вес разделения
  double separationDist;     // дистанция разделения

  double alignmentWeight;    // вес выравнивания
  double alignmentDist;      // дистанция выравнивания

  double minSpeed;           // минимальная скорость
  double maxSpeed;           // максимальная скорость

  double learningRate;       // скорость обучения нейронной сети
  double neuralInfluence;    // влияние нейронной сети на движение
  double explorationRate;    // вероятность случайного исследования

  int m_stagnationCounter;   // счетчик стагнации
  double m_prevBestFitness;  // предыдущее лучшее значение фитнеса

  S_NeuroBoids_Agent agent []; // агенты (боиды)

  private: //-------------------------------------------------------------------
  double distanceMax;     // максимальная дистанция
  double speedMax [];     // максимальные скорости по измерениям

  int neuron_size;        // размер нейрона (количество входов)

  void   CalculateMass       ();                                  // расчет масс агентов
  void   Cohesion            (S_NeuroBoids_Agent &boid, int pos); // правило сближения
  void   Separation          (S_NeuroBoids_Agent &boid, int pos); // правило разделения
  void   Alignment           (S_NeuroBoids_Agent &boid, int pos); // правило выравнивания
  void   LimitSpeed          (S_NeuroBoids_Agent &boid);          // ограничение скорости
  void   KeepWithinBounds    (S_NeuroBoids_Agent &boid);          // удержание в границах
  double Distance            (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2); // расчет дистанции
  void   ApplyNeuralControl  (S_NeuroBoids_Agent &boid, int pos); // применение нейронного управления
  void   AdaptiveExploration ();                                  // адаптивное исследование
};
//——————————————————————————————————————————————————————————————————————————————

El método "Init" se encarga de inicializar el algoritmo y los parámetros del agente e inicia su trabajo con la llamada "StandardInit", transmitiéndole los arrays de valores mínimo y máximo del rango y los pasos de búsqueda. Si falla la inicialización estándar, el método retornará inmediatamente "false". Luego se establecerá el tamaño de la neurona que se utilizará en la red neuronal agente.

En este caso, el tamaño se calculará como el número de coordenadas multiplicado por 2, y se añadirán dos valores adicionales: "dist_to_best" y "current_fitness". El tamaño del array de agentes se redimensionará según el tamaño de población especificado "popSize". A continuación, para cada agente, se llamará al método "Init ", que inicializará sus parámetros, incluida la red neuronal.

Cálculo de la distancia y la velocidad máximas:

  • La variable "distanceMax" se inicializará con cero.
  • Para cada coordenada, el valor máximo de velocidad se calculará partiendo de la diferencia entre los valores máximo y mínimo del intervalo.
  • La distancia máxima también se calculará como la raíz cuadrada de la suma de los cuadrados de las velocidades máximas.

El contador de estancamiento (m_stagnationCounter) se pondrá a cero, y la variable que almacenará el valor anterior de mejor forma física (m_prevBestFitness) se establecerá en el valor más bajo posible. El método establecerá diversas variables globales, como la convergencia, los pesos de separación y alineación, las velocidades máxima y mínima, la tasa de aprendizaje, la influencia de la red neuronal y la probabilidad de exploración. Si todos los pasos se han realizado correctamente, el método retornará "true", lo cual confirmará que la inicialización se ha realizado correctamente.

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_NOA2::Init (const double &rangeMinP  [],  // минимальный диапазон поиска
                      const double &rangeMaxP  [],  // максимальный диапазон поиска
                      const double &rangeStepP [],  // шаг поиска
                      const int    epochsP)         // количество эпох
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  // Определение размера нейрона
  neuron_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness

  // Инициализация агентов
  ArrayResize (agent, popSize);
  for (int i = 0; i < popSize; i++)
  {
    agent [i].Init (coords, neuron_size);
  }

  distanceMax = 0;
  ArrayResize (speedMax, coords);

  for (int c = 0; c < coords; c++)
  {
    speedMax [c] = rangeMax [c] - rangeMin [c];
    distanceMax += MathPow (speedMax [c], 2);
  }

  distanceMax = MathSqrt (distanceMax);

  // Сброс счетчика стагнации и предыдущего лучшего значения фитнеса
  m_stagnationCounter = 0;
  m_prevBestFitness   = -DBL_MAX;

  GlobalVariableSet ("#reset", 1.0);

  GlobalVariableSet ("1cohesionWeight",   params [1].val);
  GlobalVariableSet ("2cohesionDist",     params [2].val);

  GlobalVariableSet ("3separationWeight", params [3].val);
  GlobalVariableSet ("4separationDist",   params [4].val);

  GlobalVariableSet ("5alignmentWeight",  params [5].val);
  GlobalVariableSet ("6alignmentDist",    params [6].val);

  GlobalVariableSet ("7maxSpeed",         params [7].val);
  GlobalVariableSet ("8minSpeed",         params [8].val);

  GlobalVariableSet ("9learningRate",     params [9].val);
  GlobalVariableSet ("10neuralInfluence", params [10].val);
  GlobalVariableSet ("11explorationRate", params [11].val);

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

El método Moving se encargará de desplazar a los agentes en el entorno según los datos obtenidos y las interacciones entre ellos. En primer lugar, el método comprobará el valor de la variable global "#reset", si es igual a 1,0, se restablecerán los parámetros (la variable "revision" se pondrá en "false" y luego volverá a "0,0"). De las variables globales se extraerán los valores de diversos parámetros, como la convergencia, los pesos de separación y alineación, las velocidades mínima y máxima, la tasa de aprendizaje, la influencia de la red neuronal y la relación de exploración. Estos parámetros pueden configurarse para modificar el comportamiento de los agentes.

Si la variable "revisión" es "false", se inicializarán las posiciones "x" y velocidades "dx" de los agentes. Luego se aplicarán valores aleatorios de un rango determinado a cada coordenada, y las velocidades se fijarán en pequeños valores aleatorios. Cada coordenada almacenará igualmente el estado del agente según su posición actual. El método "AdaptiveExploration ()" se llamará para la exploración adaptativa, que puede cambiar el comportamiento de los agentes según el estado de estancamiento.

Ciclo de movimiento básico de los agentes (para cada uno):

  • Primero se guardará la experiencia actual del agente.
  • Luego se actualizará la mejor posición que haya encontrado el agente.
  • Después se actualizarán los datos de entrada de la red neuronal del agente.
  • Y se realizará una propagación directa de los datos a través de la red neuronal.
  • Los agentes se entrenarán a través de la experiencia.
Cálculo de masas: se llamará al método "CalculateMass ()", para evaluar las interacciones entre los agentes con respecto a su masa.

    Aplicación de reglas y movimiento: el ciclo comenzará de nuevo en los agentes, con las reglas estándar del algoritmo de boids aplicadas a cada uno:

    • Cohesión: los agentes buscarán estar más cerca unos de otros.
    • Separación: los agentes evitarán acercarse unos a otros para no colisionar.
    • Alineación: los agentes intentarán moverse en la misma dirección que sus vecinos.
    Luego se llamará al método "ApplyNeuralControl ()" para el control basado en redes neuronales. Se limitará la velocidad de los agentes mediante "LimitSpeed ()" y se comprobará que los agentes se mantengan dentro de los límites de un área determinada mediante KeepWithinBounds (). Las posiciones de los agentes en cada coordenada se actualizarán, añadiendo la velocidad actual a la posición actual, y el estado.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::Moving ()
    {
      double reset = GlobalVariableGet ("#reset");
      if (reset == 1.0)
      {
        revision = false;
        GlobalVariableSet ("#reset", 0.0);
      }
    
      // Получение параметров из глобальных переменных для интерактивной настройки
      cohesionWeight   = GlobalVariableGet ("1cohesionWeight");
      cohesionDist     = GlobalVariableGet ("2cohesionDist");
    
      separationWeight = GlobalVariableGet ("3separationWeight");
      separationDist   = GlobalVariableGet ("4separationDist");
    
      alignmentWeight  = GlobalVariableGet ("5alignmentWeight");
      alignmentDist    = GlobalVariableGet ("6alignmentDist");
    
      maxSpeed         = GlobalVariableGet ("7maxSpeed");
      minSpeed         = GlobalVariableGet ("8minSpeed");
    
      learningRate     = GlobalVariableGet ("9learningRate");
      neuralInfluence  = GlobalVariableGet ("10neuralInfluence");
      explorationRate  = GlobalVariableGet ("11explorationRate");
    
      // Инициализация начальных позиций и скоростей
      if (!revision)
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001;
    
            a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
    
        revision = true;
        return;
      }
    
      // Адаптивное исследование в зависимости от стагнации
      AdaptiveExploration ();
    
      //----------------------------------------------------------------------------
      // Основной цикл движения боидов
      for (int i = 0; i < popSize; i++)
      {
        // Запоминаем текущий опыт
        agent [i].MemorizeExperience (a [i].f, coords);
    
        // Обновляем лучшую позицию агента
        agent [i].UpdateBestPosition (a [i].f, coords);
    
        // Обновляем входы нейронной сети
        agent [i].UpdateInputs (cB, a [i].f, coords);
    
        // Прямое распространение по нейронной сети
        agent [i].ForwardPass (coords);
    
        // Обучение на основе накопленного опыта
        agent [i].Learn (learningRate, coords);
      }
    
      // Расчет масс
      CalculateMass ();
    
      // Применение правил и движение
      for (int i = 0; i < popSize; i++)
      {
        // Стандартные правила алгоритма боидов
        Cohesion (agent [i], i);
        Separation (agent [i], i);
        Alignment (agent [i], i);
    
        // Применение нейронного управления
        ApplyNeuralControl (agent [i], i);
    
        // Ограничение скорости и удержание в границах
        LimitSpeed (agent [i]);
        KeepWithinBounds (agent [i]);
    
        // Обновление позиций
        for (int c = 0; c < coords; c++)
        {
          agent [i].x [c] += agent [i].dx [c];
          a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "CalculateMass" calculará la "masa" de los agentes según su aptitud (productividad) y normalizará estos valores. Las variables "maxMass" y "minMass" se inicializarán con los valores -DBL_MAX y DBL_MAX respectivamente. Estas variables se utilizarán para encontrar los valores de aptitud más altos y más bajos entre toda la población de agentes. El método comprobará si hay al menos un agente, verificando que el "popSize" (tamaño de la población) sea mayor que cero. Si no hay agentes, el método finalizará la ejecución. El primer ciclo probará todos los agentes (de 0 a popSize), y para cada uno comprobará si su aptitud (el valor de a[i].f) es mayor que "maxMass" actual o menor que "minMass".

    Como resultado de este ciclo se determinarán los valores de aptitud máximo y mínimo entre todos los agentes. En el segundo ciclo, se enumerarán de nuevo todos los agentes y, para cada uno de ellos, se calculará su "masa" (variable "m") mediante la función "u.Scale", que normalizará los valores de aptitud. 

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::CalculateMass ()
    {
      double maxMass = -DBL_MAX;
      double minMass = DBL_MAX;
    
      // Проверка на наличие данных перед вычислениями
      if (popSize <= 0) return;
    
      for (int i = 0; i < popSize; i++)
      {
        if (a [i].f > maxMass) maxMass = a [i].f;
        if (a [i].f < minMass) minMass = a [i].f;
      }
    
      for (int i = 0; i < popSize; i++)
      {
        agent [i].m = u.Scale (a [i].f, minMass, maxMass, 0.1, 1.0);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "ApplyNeuralControl" se encargará de aplicar el control basado en las salidas de la red neuronal a un agente de tipo "boid". El método iterará todas las coordenadas del agente "coords". Para cada coordenada "c", se comprobará que el índice actual no supere los límites del array de salidas "boid.outputs". Si el índice es correcto, se realizará una corrección de la velocidad "dx[c]" del agente basada en el valor correspondiente de las salidas de la red neuronal multiplicado por su influencia "neuralInfluence".

    El tamaño del array "boid.outputs" se extraerá para simplificar las comprobaciones posteriores. Para los coeficientes de cohesión, separación y alineación "cohesion_factor", "separation_factor", "alignment_factor", se comprobará si los índices correspondientes están dentro del array de salidas. Si el índice está fuera de los límites, se asignará un valor por defecto de 0,5. Estos coeficientes se utilizarán para escalar los pesos básicos a fin de considerar el impacto de la red neuronal. Estos influirán en el comportamiento de los agentes modificando los pesos de convergencia, separación y alineación:

    • local_cohesion establece el peso de convergencia.
    • local_separation establece el peso de la separación.
    • local_alignment establece el peso de la alineación.

    El método comprobará si debe realizarse una exploración aleatoria utilizando la probabilidad dada "xplorationRate", si se cumple la condición, se seleccionará una coordenada aleatoria "c" para la perturbación. El tamaño de la perturbación "perturbation_size" se calculará según la masa del agente (que es una medida de su aptitud) y del rango de valores posibles para esa coordenada. Esto permitirá controlar la magnitud de la variación aleatoria a pesar de la masa del agente. A la velocidad "dx[c]" se le añadirá una perturbación aleatoria seleccionada desde "perturbation_size" hasta "perturbation_size". El método "ApplyNeuralControl" integrará los resultados de la red neuronal en el proceso de control del movimiento del agente. Asimismo, ajustará su tasa según los datos; el método también considerará la necesidad de una investigación aleatorio.

    //——————————————————————————————————————————————————————————————————————————————
    // Применение нейронного управления к боиду
    void C_AO_NOA2::ApplyNeuralControl (S_NeuroBoids_Agent &boid, int pos)
    {
      // Используем выходы нейронной сети для коррекции скорости
      for (int c = 0; c < coords; c++)
      {
        // Проверяем, что индекс не выходит за границы массива
        if (c < ArraySize (boid.outputs))
        {
          // Применяем нейронную коррекцию скорости с заданным влиянием
          boid.dx [c] += boid.outputs [c] * neuralInfluence;
        }
      }
    
      // Используем выходы нейронной сети для адаптации параметров стаи
      // Проверяем, что индексы не выходят за границы массива
      int output_size = ArraySize (boid.outputs);
    
      double cohesion_factor   = (coords < output_size) ? boid.outputs [coords] : 0.5;
      double separation_factor = (coords + 1 < output_size) ? boid.outputs [coords + 1] : 0.5;
      double alignment_factor  = (coords + 2 < output_size) ? boid.outputs [coords + 2] : 0.5;
    
      // Масштабируем базовые веса с учетом нейронной адаптации
      // Эти переменные локальные и не изменяют глобальные параметры
      double local_cohesion   = cohesionWeight * (0.5 + cohesion_factor);
      double local_separation = separationWeight * (0.5 + separation_factor);
      double local_alignment  = alignmentWeight * (0.5 + alignment_factor);
    
      // Случайное исследование с заданной вероятностью
      if (u.RNDprobab () < explorationRate)
      {
        // Выбираем случайную координату для возмущения
        int c = (int)u.RNDfromCI (0, coords - 1);
    
        // Адаптивный размер возмущения в зависимости от массы (фитнеса)
        double perturbation_size = (1.0 - boid.m) * (rangeMax [c] - rangeMin [c]) * 0.01;
    
        // Добавляем случайное возмущение
        boid.dx [c] += u.RNDfromCI (-perturbation_size, perturbation_size);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    El método "AdaptiveExploration" de la clase "C_AO_NOA2" implementará la exploración adaptativa según la fase de estancamiento en el proceso de optimización. El método comenzará comprobando si el valor de la función objetivo "fB" ha cambiado con respecto al mejor valor anterior "m_prevBestFitness". Si la diferencia entre ellos es inferior a 0,000001, se considerará que no hay progreso y se incrementará el contador de estancamiento "m_stagnationCounter". De lo contrario, el estancamiento se detendrá, el contador se pondrá a cero y el valor actual de "fB" se almacenará como el nuevo mejor valor.

    Si el número de estancamientos es superior a 20, aumentará la probabilidad de exploración aleatoria "explorationRate", pero se limitará a un valor de "0,5" para evitar una probabilidad demasiado alta de variación aleatoria. Cada 50 iteraciones de estancamiento, se producirá un reinicio parcial: El 70% de los agentes de la población se reiniciarán con nuevos valores aleatorios dentro de los rangos especificados "rangeMin" y "rangeMax". De este modo, el resto de los agentes de primera línea mantendrán sus posiciones actuales. Tras el reinicio, se reiniciará el contador de estancamiento.

    Si hay progreso y el número de parámetros "params" es superior a 11, se establecerá la probabilidad de exploración "explorationRate" del array de parámetros. Si hay menos parámetros, se establecerá un valor por defecto de 0,1.

    //——————————————————————————————————————————————————————————————————————————————
    // Адаптивное исследование в зависимости от стагнации
    void C_AO_NOA2::AdaptiveExploration ()
    {
      // Определяем, есть ли прогресс в поиске
      if (MathAbs (fB - m_prevBestFitness) < 0.000001)
      {
        m_stagnationCounter++;
      }
      else
      {
        m_stagnationCounter = 0;
        m_prevBestFitness = fB;
      }
    
      // Увеличиваем исследование при стагнации
      if (m_stagnationCounter > 20)
      {
        // Увеличиваем вероятность случайного исследования
        explorationRate = MathMin (0.5, explorationRate * 1.5);
    
        // Каждые 50 итераций стагнации выполняем частичный рестарт
        if (m_stagnationCounter % 50 == 0)
        {
          // Перезапускаем 70% популяции, оставляя лучших агентов
          int restart_count = (int)(popSize * 0.7);
    
          for (int i = popSize - restart_count; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
              agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001;
            }
          }
    
          // Сбрасываем счетчик стагнации
          m_stagnationCounter = 0;
        }
      }
      else
      {
        // При хорошем прогрессе используем обычный уровень исследования
        if (11 < ArraySize (params))
        {
          explorationRate = params [11].val;
        }
        else
        {
          explorationRate = 0.1; // значение по умолчанию
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Cohesion" de la clase "C_AO_NOA2" se encargará de implementar el comportamiento de convergencia "cohesion" para el modelo boid basado en los agentes. Declaración de variables:

    • centerX — el array se utilizará para almacenar las coordenadas del centro de masa de los agentes vecinos. El tamaño del array se corresponderá con el número de coordenadas "coords".
    • numNeighbors — el contador Neighbours llevará la cuenta del número de agentes dentro de una distancia dada (considerando la masa).
    • sumMass — la suma de las masas de los agentes vecinos se utilizará para normalizar las coordenadas del centro de masa.

    El primer ciclo (de 0 a popSize) sumará las masas de todos los agentes de la población, excluyendo el boid (agente) actual en el índice "pos". El segundo ciclo iterará todos los agentes y comprobará si se encuentran a una distancia máxima multiplicada por el factor "cohesionDist" del boid actual. Si la distancia a un agente es inferior al máximo especificado, las coordenadas de ese agente (teniendo en cuenta su masa) se añadirán a "centreX" y el contador "numNeighbors" se incrementará.

    Una vez calculadas las sumas de coordenadas y las masas de los vecinos, se comprobará si hay vecinos (es decir, numNeighbors > 0) y la suma de las masas (sumMass > 0,0), y si se encuentran vecinos, se normalizarán las coordenadas del centro de masas (dividiendo por la suma de masas). Entonces la velocidad actual "dx" del boid se incrementará hacia el centro de masa con un peso dado "cohesionWeight", esto significará que el boid se desplazará hacia el centro de masa de sus vecinos dadas sus masas.

    El método "Cohesion" calculará el centro de masa y ajustará la velocidad del boid actual para que "converja" hacia ese centro. Este comportamiento será uno de los aspectos clave de la simulación del comportamiento de manada o grupo. Parámetros como "cohesionDist" y "cohesionWeight" permitirán controlar la distancia a la que se aplica este comportamiento y el grado en que el centro de masa influirá en el movimiento de los boids.

    //——————————————————————————————————————————————————————————————————————————————
    // Найти центр масс других боидов и скорректировать скорость в направлении центра масс
    void C_AO_NOA2::Cohesion (S_NeuroBoids_Agent &boid, int pos)
    {
      double centerX [];
      ArrayResize (centerX, coords);
      ArrayInitialize (centerX, 0.0);
    
      int numNeighbors = 0;
      double sumMass = 0;
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i) sumMass += agent [i].m;
      }
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * cohesionDist)
          {
            for (int c = 0; c < coords; c++)
            {
              centerX [c] += agent [i].x [c] * agent [i].m;
            }
    
            numNeighbors++;
          }
        }
      }
    
      if (numNeighbors > 0 && sumMass > 0.0)
      {
        for (int c = 0; c < coords; c++)
        {
          centerX [c] /= sumMass;
          boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Separation" de la clase "C_AO_NOA2" implementará un comportamiento de repulsión (separación) para agentes modelo que está diseñado para prevenir colisiones entre un boid y los agentes vecinos. "moveX" — parámetros como el array "cohesionDist" se usarán para almacenar vectores de desplazamiento para alejar al boid de otros agentes.

    El tamaño del array se corresponderá con el número de coordenadas "coords". El array "moveX" se inicializará con ceros para evitar la acumulación de valores aleatorios, luego se realizará la iteración sobre todos los agentes de la población (de 0 a popSize). A continuación, se comprobará si el agente está cerca del boid actual. Esto se logrará utilizando la función "Distance", y si la distancia entre el boid y el agente es menor que un máximo dado (multiplicado por el factor "separationDist"), entonces para cada coordenada, las coordenadas del agente se restarán de las coordenadas del boid actual para crear efectivamente un vector que apunte hacia el boid actual (es decir, lejos del agente).

    Una vez completado el ciclo del agente, la velocidad actual "dx" del boid se incrementará con el vector de desplazamiento calculado "moveX" multiplicado por el factor "separationWeight". Esto permitirá controlar la fuerza de la repulsión: cuanto mayor sea el valor de "separationWeight", más con mayor intensidad evitará el boid las colisiones.

    El método "Separation" implementará un comportamiento que hará que el boid repela a sus vecinos más cercanos, evitando colisiones. Se trata de un aspecto importante en el modelado del comportamiento de la manada, que ayuda a mantener el espacio personal de cada agente y favorece interacciones más naturales entre ellos. Parámetros como "separationDist" y "separationWeight" permiten ajustar con flexibilidad el alcance y la fuerza del efecto de repulsión, respectivamente.

    //——————————————————————————————————————————————————————————————————————————————
    // Отталкивание от других боидов, чтобы избежать столкновений
    void C_AO_NOA2::Separation (S_NeuroBoids_Agent &boid, int pos)
    {
      double moveX [];
      ArrayResize (moveX, coords);
      ArrayInitialize (moveX, 0.0);
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * separationDist)
          {
            for (int c = 0; c < coords; c++)
            {
              moveX [c] += boid.x [c] - agent [i].x [c];
            }
          }
        }
      }
    
      for (int c = 0; c < coords; c++)
      {
        boid.dx [c] += moveX [c] * separationWeight;
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Alignment" de la clase "C_AO_NOA2" implementará el comportamiento de alineación para los agentes del modelo. Este comportamiento hace que los boids coordinen sus velocidades con los agentes vecinos para crear un movimiento de grupo más armonioso y coordinado. "avgDX" es un array que se utilizará para almacenar el vector de velocidad media de los agentes vecinos. El tamaño del array se corresponde con el número de coordenadas "coords"; "numNeighbors" es un contador que llevará la cuenta del número de vecinos que se encuentran a una distancia determinada.

    El ciclo iterará todos los agentes de la población. Dentro del ciclo se comprobará si el agente actual es el mismo que el boid. Si la distancia a otro agente es menor que el máximo especificado multiplicado por el factor "alignmentDist", la velocidad actual "dx" de ese agente se añadirá a "avgDX". El contador "numNeighbors" se incrementará en 1. Una vez finalizado el ciclo, si se han encontrado vecinos (es decir, numNeighbors > 0), se calculará la velocidad media (avgDX) dividiendo el vector de velocidad sumado por el número de vecinos. La velocidad actual "dx" del boid se ajustará entonces a la velocidad media de sus vecinos teniendo en cuenta el factor "alignmentWeight". 

    El método de "Alignment" permitirá a un boid adaptar su velocidad a la de sus vecinos. Este comportamiento ayudará a los grupos de boids a moverse de forma más coherente y reducirá la probabilidad de conflictos o cambios bruscos de dirección. Parámetros como "alignmentDist" y "alignmentWeight" establecerán, respectivamente, el radio del efecto de alineación y el grado en que la velocidad media de los vecinos afectará a la velocidad del boid actual. 
    //——————————————————————————————————————————————————————————————————————————————
    // Выравнивание скорости с другими боидами
    void C_AO_NOA2::Alignment (S_NeuroBoids_Agent &boid, int pos)
    {
      double avgDX [];
      ArrayResize (avgDX, coords);
      ArrayInitialize (avgDX, 0.0);
    
      int numNeighbors = 0;
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * alignmentDist)
          {
            for (int c = 0; c < coords; c++)
            {
              avgDX [c] += agent [i].dx [c];
            }
    
            numNeighbors++;
          }
        }
      }
    
      if (numNeighbors > 0)
      {
        for (int c = 0; c < coords; c++)
        {
          avgDX [c] /= numNeighbors;
          boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "LimitSpeed" de la clase "C_AO_NOA2" está diseñado para controlar la velocidad de los agentes (boids) en el modelo para asegurarse de que las velocidades de los boids estén dentro del rango especificado, y "speed" es una variable para almacenar la velocidad actual de los boids, que será calculada como la longitud del vector de velocidad. El ciclo por coordenadas (coords) calculará el cuadrado de la velocidad para cada coordenada (sumando boid.dx [c] * boid.dx [c]) y las sumará. Esto nos permitirá calcular el cuadrado de la longitud del vector velocidad. La longitud (o velocidad real) se obtendrá entonces mediante la función "MathSqrt", que calculará la raíz cuadrada de la suma de cuadrados. Si la velocidad es mayor que un valor pequeño (1e-10), el programa continuará la ejecución, si la velocidad actual es menor que la velocidad mínima permitida (definida como speedMax[0] * minSpeed), entonces el vector de velocidad "boid.dx" será normalizado (dividido por su longitud actual) e incrementado a un valor igual al valor mínimo permitido.

    Si la velocidad actual es mayor que la velocidad máxima permitida (speedMax[0] * maxSpeed), entonces, de forma similar, el vector de velocidad se normalizará y se establecerá en el valor de la velocidad máxima, mientras que si la velocidad es casi cero (cero o muy cercana a cero), en este caso, cada coordenada del vector de velocidad se sustituirá por un pequeño valor aleatorio obtenido mediante la función "u.RNDfromCI (-1.0, 1.0)" multiplicado por la velocidad máxima.

    El método "LimitSpeed" garantizará que la velocidad de los boids se mantenga dentro de unos límites aceptables, evitando movimientos demasiado lentos o demasiado rápidos. Este comportamiento permitirá una simulación más realista de los agentes, ya que no permitirá grandes desviaciones de velocidad que podrían dar lugar a movimientos poco naturales. Los parámetros "minSpeed" y "maxSpeed" pueden configurarse para ajustar el comportamiento y la velocidad de los agentes según los objetivos de la simulación.

    //——————————————————————————————————————————————————————————————————————————————
    // Ограничение скорости
    void C_AO_NOA2::LimitSpeed (S_NeuroBoids_Agent &boid)
    {
      double speed = 0;
    
      for (int c = 0; c < coords; c++)
      {
        speed += boid.dx [c] * boid.dx [c];
      }
    
      speed = MathSqrt (speed);
    
      // Если скорость не нулевая (предотвращаем деление на ноль)
      if (speed > 1e-10)
      {
        // Если скорость слишком мала, увеличиваем её
        if (speed < speedMax [0] * minSpeed)
        {
          for (int c = 0; c < coords; c++)
          {
            boid.dx [c] = boid.dx [c] / speed * speedMax [c] * minSpeed;
          }
        }
        // Если скорость слишком велика, уменьшаем её
        else
          if (speed > speedMax [0] * maxSpeed)
          {
            for (int c = 0; c < coords; c++)
            {
              boid.dx [c] = boid.dx [c] / speed * speedMax [c] * maxSpeed;
            }
          }
      }
      else
      {
        // Если скорость почти нулевая, задаем небольшую случайную скорость
        for (int c = 0; c < coords; c++)
        {
          boid.dx [c] = u.RNDfromCI (-1.0, 1.0) * speedMax [c] * minSpeed;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "KeepWithinBounds" de la clase "C_AO_NOA2" está diseñado para mantener al agente (boid) dentro de los límites especificados. Si el boid se acerca demasiado a los bordes de la zona, este método invertirá su dirección y proporcionará un empujón definitivo hacia los límites. El método comenzará con un ciclo a través de todas las coordenadas, lo que permitirá trabajar con un espacio multidimensional.

    Para cada coordenada, comprobará si la posición del boid (boid.x[c]) está por debajo del límite mínimo (rangeMin[c]), si es así, la dirección de la velocidad (boid.dx[c]) se invertirá con "boid.dx [c] *= -1.0". Esto significará que el boid se moverá en la dirección opuesta. A continuación, se añadirá un pequeño empujón desde el límite: (rangeMax[c] - rangeMin[c]) * 0,001, que ayudará a empujar el boid hacia el interior de la región.

    Luego se realizará una comprobación similar para el límite máximo (rangeMax[c]): si la posición del boid sobrepasa el máximo, su velocidad también se invertirá y se corregirá, pero restándole algún valor, de forma similar al caso anterior.

    El método "KeepWithinBounds" restringirá eficazmente los movimientos de los boids en un área determinada, impidiendo que salgan de los límites y garantizando que vuelvan al interior. 

    //——————————————————————————————————————————————————————————————————————————————
    // Удержание боида в границах. Если он оказывается слишком близко к краю,
    // подталкиваем его обратно и меняем направление.
    void C_AO_NOA2::KeepWithinBounds (S_NeuroBoids_Agent &boid)
    {
      for (int c = 0; c < coords; c++)
      {
        if (boid.x [c] < rangeMin [c])
        {
          boid.dx [c] *= -1.0;
          // Добавляем небольшой толчок от границы
          boid.dx [c] += (rangeMax [c] - rangeMin [c]) * 0.001;
        }
        if (boid.x [c] > rangeMax [c])
        {
          boid.dx [c] *= -1.0;
          // Добавляем небольшой толчок от границы
          boid.dx [c] -= (rangeMax [c] - rangeMin [c]) * 0.001;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Distance" de la clase "C_AO_NOA2" está diseñado para calcular la distancia entre dos agentes (boids) en un espacio multidimensional. Este utilizará una fórmula para calcular la distancia euclidiana; "dist" es una variable que almacenará la suma de los cuadrados de las diferencias de las coordenadas de los dos boids.

    El método iniciará un ciclo que iterará todas las coordenadas, lo que permitirá calcular la distancia en un espacio de cualquier dimensionalidad. Para cada coordenada "c", se calculará el cuadrado de la diferencia entre las coordenadas correspondientes de ambos boids: boid1.x[c] - boid2.x[c].

    Los resultados ((boid1.x[c] - boid2.x[c])^2) se añadirán a la variable "dist". Una vez finalizado el ciclo, la variable "dist" contendrá la suma de cuadrados de las diferencias de coordenadas. Para obtener la distancia real, el método usará "MathSqrt" para calcular la raíz cuadrada de la suma de cuadrados, que se corresponderá con la fórmula de la distancia euclidiana.

    //——————————————————————————————————————————————————————————————————————————————
    // Расчет расстояния между двумя боидами
    double C_AO_NOA2::Distance (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2)
    {
      double dist = 0;
    
      for (int c = 0; c < coords; c++)
      {
        dist += MathPow (boid1.x [c] - boid2.x [c], 2);
      }
    
      return MathSqrt (dist);
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Revision" de la clase "C_AO_NOA2" se encargará de actualizar la información sobre la mejor solución encontrada durante el proceso de optimización. Este proceso implicará la actualización del valor de la función de aptitud, así como de las coordenadas correspondientes a dicho valor. El método también supervisará los progresos y adaptará los parámetros del algoritmo si se producen mejoras significativas. El método iterará toda la población representada por el array "a", que contiene "popSize" (número de agentes).

    Dentro del ciclo, se comprobará si el valor de aptitud de cada agente (a[i].f) es mayor que el mejor valor actual (fB). Si el agente actual muestra el mejor valor de aptitud, se actualizará el mejor valor de aptitud global asignando a "fB" un nuevo valor "a[i].f", luego se actualizarán las coordenadas de la mejor solución. Para cada coordenada "c", el array "cB" (que contiene las coordenadas de la mejor solución) se actualizará con los valores del agente actual. El contador de estancamiento (m_stagnationCounter) se pondrá a cero porque se ha encontrado una mejora en la solución.

    El método utilizará la variable "hasProgress" para determinar si se ha avanzado. Para ello, se calculará la diferencia absoluta entre los valores actual y anterior de la mejor función de aptitud (MathAbs(fB - m_prevBestFitness)), y si esta diferencia es superior a 0,000001, se considerará que se está avanzando. Si hay progreso, "m_prevBestFitness" se actualizará al mejor valor actual "fB".
    La tasa de exploración "explorationRate" también se adaptará: esta disminuirá si se encuentra una mejora, teniendo en cuenta algún parámetro del array "params" y el valor actual de "explorationRate".

    //——————————————————————————————————————————————————————————————————————————————
    // Обновление лучшего найденного решения
    void C_AO_NOA2::Revision ()
    {
      // Обновляем лучшие координаты и значение фитнес-функции
      for (int i = 0; i < popSize; i++)
      {
        // Обновление глобального лучшего решения
        if (a [i].f > fB)
        {
          fB = a [i].f;
          for (int c = 0; c < coords; c++)
          {
            cB [c] = a [i].c [c];
          }
    
          // Сбрасываем счетчик стагнации при нахождении лучшего решения
          m_stagnationCounter = 0;
        }
      }
    
      // Проверка наличия прогресса для адаптации параметров алгоритма
      bool hasProgress = MathAbs (fB - m_prevBestFitness) > 0.000001;
      if (hasProgress)
      {
        m_prevBestFitness = fB;
        explorationRate = MathMax (params [11].val * 0.5, explorationRate * 0.9);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————


    Resultados de las pruebas

    Los resultados de las pruebas son bastante flojos.

    NOA2|Neuroboids Optimization Algorithm 2 (joo)|50.0|0.6|0.001|0.005|0.03|0.1|0.1|0.1|0.01|0.01|0.3|0.1|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.47680799582735267
    25 Hilly's; Func runs: 10000; result: 0.30763714006051013
    500 Hilly's; Func runs: 10000; result: 0.2544737238936433
    =============================
    5 Forest's; Func runs: 10000; result: 0.3238017030688524
    25 Forest's; Func runs: 10000; result: 0.20976876473929068
    500 Forest's; Func runs: 10000; result: 0.15740101965732595
    =============================
    5 Megacity's; Func runs: 10000; result: 0.27076923076923076
    25 Megacity's; Func runs: 10000; result: 0.14676923076923082
    500 Megacity's; Func runs: 10000; result: 0.09729230769230844
    =============================
    All score: 2.24472 (24.94%)

    En la visualización podemos apreciar la modesta capacidad de búsqueda. La posibilidad de modificar los parámetros externos del algoritmo mediante variables globales permite experimentar con el comportamiento de los boids, revelando comportamientos interesantes. A continuación le mostramos algunos de los posibles comportamientos.

    Hilly

    NOA2 en la función de prueba Hilly

    Forest

    NOA2 en la función de prueba Forest

    Megacity

    NOA2 en la función de prueba Megacity

    Basándonos en los resultados de las pruebas, el algoritmo NOA2 en su versión de referencia se incluye en nuestra tabla de clasificación de algoritmos de optimización de poblaciones únicamente con fines ilustrativos.

    AO Description Hilly Hilly final Forest Forest 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 algorithm (joo)0,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 algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
    6TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
    7SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
    8BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
    9AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
    10ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
    11SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
    12ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
    13DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
    14BHAmblack hole algorithm M0,752360,766750.345831,864930.935930.801520,271772,009230.650770.516460,154721,321955.19657.73
    15ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
    16RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
    17AOSmbúsqueda de orbitales atómicos M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
    18TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
    19DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
    20SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
    21CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
    22BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
    23BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
    24HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
    25SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
    26BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
    27ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
    28(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
    29TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
    30BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
    31WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
    32AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
    33AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
    34ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
    35BFO-GAbacterial foraging optimization — ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
    36SOAsimple optimization algorithm0.915200.469760.270891,655850.896750.374010.169841,440600.695380.280310.108521,084224.18146,45
    37ABHartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974.12745,85
    38ACMOatmospheric cloud model optimization0,903210,485460,304031,692700,802680,378570,191781,373030,623080,244000,107950,975034,04144,90
    39ADAMmadaptive moment estimation M0.886350.447660,266131.600140.844970.384930.168891,398800,661540.270460.105941,037944.03744.85
    40CGOchaos game optimization0,572560,371580,320181,264320,611760,619310,621611,852670,375380,219230,190280,784903,90243,35
    41ATAmartificial tribe algorithm M0.717710.553040,252351,523100.824910.559040,204731,588670,440000,186150.094110.720263.83242.58
    42CFOcentral force optimization0,609610,549580,278311,437500,634180,468330,225411,327920,572310,234770,095860,902943,66840,76
    43ASHAartificial showering algorithm0,896860,404330,256171,557370,803600,355260,191601,350460,476920,181230,097740,755893,66440,71
    44ASBOadaptive social behavior optimization0,763310,492530,326191,582020,795460,400350,260971,456770,264620,171690,182000,618313.65740,63
    45MECmind evolutionary computation0,695330,533760,326611,555690,724640,330360,071981,126980,525000,220000,041980,786983,47038,55
    NOA2neuroboids optimization algorithm 2(joo)0,476810,307640,254471,038920,323800,209770,157400,690970,270770,146770,097290,514832,24524,94


    Conclusiones

    El Algoritmo de Optimización Neuroboidal (NOA2) que hemos desarrollado es un enfoque híbrido que combina los principios de la inteligencia de enjambre (algoritmo boid) con el control neuronal. La idea clave consiste en aplicar una red neuronal para controlar de forma adaptativa los parámetros de comportamiento de los agentes boid. La aplicación actual usa una red neuronal simple de una sola capa sin capas ocultas. Consta de una capa de entrada que admite las coordenadas actuales, las velocidades, la distancia a la mejor solución y el valor de la función de aptitud, y una capa de salida que determina las correcciones de velocidad y los parámetros adaptativos de las reglas de comportamiento del enjambre boid.

    En esta versión no hay capas intermedias. El número de entradas se define como (coords * 2 + 2), donde "coords" será el número de dimensiones del espacio de búsqueda. Los resultados incluyen una corrección para cada coordenada y tres parámetros adicionales para adaptar las reglas del enjambre. El algoritmo tiene un gran número de parámetros ajustables, lo cual hace difícil encontrar el mejor; hemos probado varias combinaciones, pero no hemos encontrado la combinación ideal que muestre los mejores resultados en las funciones de prueba.

    En su forma actual, el algoritmo sirve claramente para demostrar el concepto y, en general, supone un experimento interesante en métodos de optimización híbridos, pero no demuestra un rendimiento suficiente para ser calificado. Ha obtenido el número mínimo de puntos en las pruebas realizadas y solo puede considerarse como una ilustración del enfoque de hibridación de algoritmos. Para los investigadores y desarrolladores interesados, el código NOA2 puede servir como punto de partida para experimentar con diferentes configuraciones y parámetros, así como para crear mejores algoritmos de optimización híbridos que aprovechen tanto los métodos de población como los de aprendizaje automático.

    Tab

    Figura 3. Clasificación por colores de los algoritmos según las pruebas respectivas

    chart

    Figura 4. Histograma de los resultados de las pruebas de 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 está en el archivo)

    Ventajas y desventajas del algoritmo NOA2:

    Ventajas:

    1. Supone una idea interesante para poner en práctica.

    Desventajas:

    1. Resultados muy flojos.

    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.


    Programas usados en el artículo

    #NombreTipoDescripció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
    3TestFunctions.mqh
    Archivo de inclusión
    Biblioteca de funciones de prueba
    4TestStandFunctions.mqh
    Archivo de inclusión
    Biblioteca de funciones del banco de pruebas
    5Utilities.mqh
    Archivo de inclusión
    Biblioteca de funciones auxiliares
    6CalculationTestResults.mqh
    Archivo de inclusión
    Script para calcular los resultados en una tabla comparativa
    7Testing AOs.mq5
    ScriptBanco de pruebas único para todos los algoritmos de optimización basados en la población
    8Simple 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
    9Test_AO_NOA2.mq5
    ScriptBanco de pruebas para NOA2

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

    Archivos adjuntos |
    NOA2.ZIP (406.44 KB)
    Explorando técnicas avanzadas de aprendizaje automático en la estrategia Darvas Box Breakout Explorando técnicas avanzadas de aprendizaje automático en la estrategia Darvas Box Breakout
    La estrategia Darvas Box Breakout, creada por Nicolas Darvas, es un enfoque técnico de negociación que detecta posibles señales de compra cuando el precio de una acción sube por encima de un rango establecido, lo que sugiere un fuerte impulso alcista. En este artículo, aplicaremos este concepto estratégico como ejemplo para explorar tres técnicas avanzadas de aprendizaje automático. Entre ellas se incluyen el uso de un modelo de aprendizaje automático para generar señales en lugar de filtrar operaciones, el empleo de señales continuas en lugar de discretas y el uso de modelos entrenados en diferentes marcos temporales para confirmar las operaciones.
    Automatización de estrategias de trading en MQL5 (Parte 12): Implementación de la estrategia Mitigation Order Blocks (MOB) Automatización de estrategias de trading en MQL5 (Parte 12): Implementación de la estrategia Mitigation Order Blocks (MOB)
    En este artículo creamos un sistema de trading en MQL5 que se encarga de detectar de forma automática los "order blocks", un concepto utilizado en el método Smart Money. Describimos las reglas de la estrategia, implementamos la lógica en MQL5 e integramos la gestión de riesgos para una ejecución eficaz de las operaciones. Por último, realizamos pruebas retrospectivas del sistema para evaluar su rendimiento y perfeccionarlo con el fin de obtener resultados óptimos.
    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.
    Desarrollo de estrategias comerciales de tendencia basadas en el aprendizaje automático Desarrollo de estrategias comerciales de tendencia basadas en el aprendizaje automático
    El presente artículo propone un enfoque original para el desarrollo de estrategias de tendencia. Hoy aprenderemos a marcar ejemplos de entrenamiento y a entrenar clasificadores con ellos. El resultado serán sistemas comerciales listos para usar que se ejecutarán en el terminal MetaTrader 5.