preview
Базовый класс популяционных алгоритмов как основа эффективной оптимизации

Базовый класс популяционных алгоритмов как основа эффективной оптимизации

MetaTrader 5Тестер | 29 февраля 2024, 13:03
428 2
Andrey Dik
Andrey Dik

Содержание:

1. Введение. Перспективы и возможности при наследовании от базового класса популяционных алгоритмов
2. Пишем базовый класс популяционных алгоритмов
3. Код алгоритмов - наследников
4. Код единого тестового стенда для всех алгоритмов
5. Добавим популярные известные тестовые функции
6. Построение тестовых функций 3D
7. Выводы


1. Введение. Перспективы и возможности при наследовании от базового класса популяционных алгоритмов

В мире современных вычислений и искусственного интеллекта базовый класс, спроектированный для интеграции алгоритмов оптимизации в конечные программные решения, представляет собой ключевой элемент, открывающий перед разработчиками бесконечные горизонты технических возможностей. Наследование от данного базового класса в контексте популяционных алгоритмов не только обеспечивает удобство и эффективность разработки новых методов оптимизации, но и расширяет перспективы создания гибридных алгоритмов, способных адаптироваться к самым разнообразным задачам.

Объединение алгоритмов оптимизации в рамках базового класса открывает двери для создания инновационных решений, объединяющих в себе лучшие характеристики различных методов. Гибридные алгоритмы, зародившиеся благодаря этому подходу, способны эффективно преодолевать ограничения отдельных методов и достигать новых высот в решении сложных задач оптимизации.

Кроме того, базовый класс для популяционных алгоритмов обеспечивает простоту использования и тестирования разработанных алгоритмов на стандартных наборах тестовых функций. Это позволяет исследователям и разработчикам быстро оценить эффективность новых методов оптимизации, сравнивая их производительность с уже существующими решениями.

Давайте представим себе, что мир оптимизации и поиска решений - это как удивительный кулинарный мир, где каждый метод оптимизации — это уникальный ингредиент, придающий свой неповторимый вкус блюду. Гибридизация в этом контексте — это как искусное сочетание разнообразных ингредиентов для создания новых, более вкусных и интересных блюд.

У вас есть широкий ассортимент различных методов оптимизации - генетические алгоритмы, эволюционные стратегии, муравьиные алгоритмы, оптимизация роя частиц и многие другие. Каждый из них обладает своими сильными сторонами и способностями, но также имеет свои ограничения.

Вот где на помощь приходит гибридизация! Вы можете взять лучшее из каждого метода, сочетая их в уникальные комбинации, как опытный шеф-повар. Таким образом, гибридные методы оптимизации могут объединить сильные стороны различных подходов, компенсируя их слабости и создавая более эффективные и мощные инструменты для поиска оптимальных решений.

Представьте, что сочетание генетического алгоритма с локальным поиском — это как идеальное сочетание пряного перца и сладкого меда в блюде, придающее ему глубокий и насыщенный вкус. Точно так же, гибридизация популяционных алгоритмов позволяет создавать инновационные методы, способные быстрее и точнее находить оптимальные решения в различных областях, будь то инженерные задачи, финансовая аналитика или искусственный интеллект.

Таким образом, гибридизация в оптимизации — это не просто смешивание методов, это искусство создания новых подходов, способных раскрыть потенциал каждого метода в максимальной степени и достичь выдающихся результатов. В итоге благодаря гибридизации мы можем создавать более эффективные, инновационные и мощные методы оптимизации, способные решать самые сложные задачи и приводить к новым открытиям и достижениям в различных областях.

Более того, унифицированный базовый класс позволяет интегрировать в пользовательские решения отдельные элементы каждого из алгоритмов для использования в проектировании новых, уникальных и мощных оптимизационных методов.


2. Пишем базовый класс популяционных алгоритмов

В контексте статьи о наследовании от базового класса для популяционных алгоритмов и создании гибридных методов оптимизации, можно рассмотреть в качестве примеров несколько интересных комбинаций:

  • Генетический алгоритм с усиленным локальным поиском. В данной комбинации генетический алгоритм используется для глобального поиска оптимального решения, а локальный поиск применяется для уточнения найденного решения в окрестности. Это позволяет совместить преимущества глобального и локального поиска, повышая точность и скорость сходимости алгоритма.
  • Эволюционная стратегия с муравьиным алгоритмом. Здесь эволюционная стратегия может использоваться для изменения параметров модели, а муравьиный алгоритм - для поиска оптимального пути в пространстве параметров. Такая комбинация может быть эффективной для оптимизации сложных задач, где требуется нахождение оптимальной комбинации параметров.
  • Частицы роя с генетическим программированием. В данной комбинации частицы роя могут использоваться для исследования пространства решений, а генетическое программирование - для эволюции программных структур, решающих задачу оптимизации. Это позволяет эффективно исследовать как пространство параметров, так и структуры решений.
  • Поиск симулированным отжигом с генетическим алгоритмом. Здесь симулированный отжиг может использоваться для исследования пространства решений с учетом температурного режима, а генетический алгоритм - для поиска оптимальных решений в заданном пространстве. Такая комбинация может обеспечить более глубокое исследование пространства решений и улучшить сходимость алгоритма.

Так же можно рассмотреть в качестве примера следующие направления в использовании возможностей объединённых в единый базовый класс популяционных алгоритмов:

  • Метод комбинированного метаэвристического подхода. В этом методе можно объединить несколько различных метаэвристических алгоритмов, таких как генетические алгоритмы, муравьиные алгоритмы, алгоритмы оптимизации роя частиц, симулированный отжиг и т.д. Эти алгоритмы могут работать параллельно или последовательно, обмениваясь информацией и комбинируя свои сильные стороны для более эффективного поиска оптимального решения.
  • Гибридный метод с адаптивным управлением стратегиями поиска. В этом подходе можно использовать механизмы адаптивного управления, чтобы динамически комбинировать различные стратегии оптимизации в зависимости от характеристик проблемы. Например, можно изменять веса или параметры каждого метода в зависимости от эффективности их работы на текущем этапе оптимизации.
  • Гибридный метод с искусственными нейронными сетями. В данном подходе можно использовать искусственные нейронные сети для адаптивного управления параметрами и стратегиями оптимизации популяционных алгоритмов. Нейронные сети могут обучаться на лету, адаптируясь к изменениям в пространстве поиска, и предлагать оптимальные параметры для каждого метода оптимизации.
  • Метод совместной оптимизации и обучения с подкреплением. В этом подходе можно объединить популяционные алгоритмы с методами обучения с подкреплением для создания гибридной системы, способной эффективно исследовать и оптимизировать сложные пространства решений. Агенты обучения с подкреплением могут учиться на основе результатов популяционных алгоритмов и наоборот, создавая взаимодействие между различными методами оптимизации.


Различное количество внешних параметров в каждом алгоритме оптимизации может создавать проблемы при наследовании и унифицированном применении. Для решения этой проблемы было принято решение прописывать внешние параметры алгоритмов по умолчанию в конструкторе, основываясь на результатах обширных тестирований. При этом сохраняется возможность изменения этих параметров перед инициализацией алгоритмов. Таким образом, объект каждого алгоритма будет представлять собой окончательное решение готовое к использованию. Ранее алгоритмы и их параметры представляли собой отдельные сущности.

Итак, начнём с параметров алгоритмов. Каждый параметр удобно описать структурой "S_AlgoParam", которая содержит имя и значение параметра. Соответственно, массив объектов этой структуры будет представлять набор внешних параметров алгоритмов.

struct S_AlgoParam
{
    double val;
    string name;
};

В каждом алгоритме оптимизации имеется поисковый агент — это элементарная единица и непременный участник поисковой стратегии, будь то нежное создание - светлячок в светлячковом алгоритме, или усердная пчела в пчелином, трудолюбивый муравей в муравьином и т.д. Они — неповторимые художники в лабиринте оптимизации, раскрывающие великолепие искусства поиска и открытия оптимальных путей к успеху. Их усилия и стремления, как магия, преображают хаос данных в гармонию решений, освещая путь к новым горизонтам идеальной оптимизации.

Ммм, о чем это я... ах да, про агентов оптимизации. :)

Таким образом, каждый агент представляет конкретное решение задачи оптимизации и имеет два обязательных минимально необходимых свойства: координаты в пространстве поиска (оптимизируемые параметры) и качество решения (приспособленность или фитнес-функция). Для возможности расширения функциональности и возможностей и реализации специфических свойств алгоритмов, оформим агента в виде класса "C_AO_Agent", который впоследствии сможем наследовать.

class C_AO_Agent
{
  public:
  ~C_AO_Agent () { }

  double c []; //coordinates
  double f;    //fitness
};

Большинство логических операций в алгоритмах оптимизации повторяются и могут быть оформлены обособленно в виде набора функций класса "C_AO_Utilities", объект которого в свою очередь может быть использован как в классах алгоритмов, так и в агентах.

Класс "C_AO_Utilities" содержит следующие методы:

  • Scale: Перегруженный метод, который выполняет масштабирование входного значения "In" из диапазона [InMIN, InMAX] в диапазон [OutMIN, OutMAX]. Возможно также выполнение обратного масштабирования, если параметр "revers" установлен в "true".
  • RNDfromCI: Генерирует случайное вещественное число в пределах заданного диапазона [min, max].
  • RNDintInRange: Генерирует случайное целое число в пределах заданного диапазона [min, max].
  • RNDbool: Генерирует случайное логическое значение (true/false).
  • RNDprobab: Генерирует случайную вероятность (вещественное число от 0 до 1).
  • SeInDiSp: Рассчитывает значение с учетом заданного шага "Step" внутри диапазона [InMin, InMax].
  • Методы "DecimalToGray", "IntegerToBinary", "GrayToDecimal", "BinaryToInteger", "GetMaxDecimalFromGray": Выполняют преобразования между десятичными числами, двоичными числами и кодами Грея.
  • Методы "GaussDistribution" и "PowerDistribution": Выполняют вычисления для нормального распределения и степенного распределения соответственно.
  • Метод "Sorting" (шаблонный метод): Сортирует массив "p" типа "T" в порядке убывания.
  • Структура "S_Roulette": Содержит поля "start" и "end" для представления диапазона.
  • Методы "PreCalcRoulette" и "SpinRoulette": "PreCalcRoulette" вычисляет диапазоны для объектов типа "T" и сохраняет их в массиве "roulette". "SpinRoulette" выполняет операцию "кручения" рулетки на основе размера популяции "aPopSize".
//——————————————————————————————————————————————————————————————————————————————
class C_AO_Utilities
{
  public: //--------------------------------------------------------------------
  double Scale                 (double In, double InMIN, double InMAX, double OutMIN, double OutMAX);
  double Scale                 (double In, double InMIN, double InMAX, double OutMIN, double OutMAX,  bool revers);
  double RNDfromCI             (double min, double max);
  int    RNDintInRange         (int min, int max);
  bool   RNDbool               ();
  double RNDprobab             ();
  double SeInDiSp              (double In, double InMin, double InMax, double Step);
  void   DecimalToGray         (ulong decimalNumber, char &array []);
  void   IntegerToBinary       (ulong number, char &array []);
  ulong  GrayToDecimal         (const char &grayCode [], int startInd, int endInd);
  ulong  BinaryToInteger       (const char &binaryStr [], const int startInd, const int endInd);
  ulong  GetMaxDecimalFromGray (int digitsInGrayCode);
  double GaussDistribution     (const double In, const double outMin, const double outMax, const double sigma);
  double PowerDistribution     (const double In, const double outMin, const double outMax, const double p);

  //----------------------------------------------------------------------------
  template<typename T>
  void Sorting (T &p [], T &pTemp [], int size)
  {
    int    cnt = 1;
    int    t0  = 0;
    double t1  = 0.0;
    int    ind [];
    double val [];

    ArrayResize (ind, size);
    ArrayResize (val, size);

    for (int i = 0; i < size; i++)
    {
      ind [i] = i;
      val [i] = p [i].f;
    }

    while (cnt > 0)
    {
      cnt = 0;
      for (int i = 0; i < size - 1; i++)
      {
        if (val [i] < val [i + 1])
        {
          t0 = ind [i + 1];
          t1 = val [i + 1];
          ind [i + 1] = ind [i];
          val [i + 1] = val [i];
          ind [i] = t0;
          val [i] = t1;
          cnt++;
        }
      }
    }

    for (int u = 0; u < size; u++) pTemp [u] = p [ind [u]];
    for (int u = 0; u < size; u++) p [u] = pTemp [u];
  }

  //----------------------------------------------------------------------------
  struct S_Roulette
  {
      double start;
      double end;
  };
  S_Roulette roulette [];

  template<typename T>
  void PreCalcRoulette (T &agents [])
  {
    int aPopSize = ArraySize (agents);
    roulette [0].start = agents [0].f;
    roulette [0].end   = roulette [0].start + (agents [0].f - agents [aPopSize - 1].f);

    for (int s = 1; s < aPopSize; s++)
    {
      if (s != aPopSize - 1)
      {
        roulette [s].start = roulette [s - 1].end;
        roulette [s].end   = roulette [s].start + (agents [s].f - agents [aPopSize - 1].f);
      }
      else
      {
        roulette [s].start = roulette [s - 1].end;
        roulette [s].end   = roulette [s].start + (agents [s - 1].f - agents [s].f) * 0.1;
      }
    }
  }
  int  SpinRoulette (int aPopSize);
};
//——————————————————————————————————————————————————————————————————————————————

Поскольку стохастические алгоритмы оптимизации базируются на генерации случайных чисел, данная операция может выполняться сотнями или даже тысячами раз для получения каждого решения. Поэтому целесообразно оптимизировать процесс генерации случайных чисел путем разделения специфических задач. Учитывая, что стандартный генератор осуществляет генерацию целых чисел, существует потенциал для ускорения данного процесса.

Мы уже встречались ранее с методом "RNDfromCI", который генерирует случайное вещественное число в пределах заданного диапазона ["min", "max"]:

double C_AO_Utilities ::RNDfromCI (double min, double max)
{
  if (min == max) return min;
  if (min > max)
  {
    double temp = min;
    min = max;
    max = temp;
  }
  return min + ((max - min) * rand () / 32767.0);
}

Очень часто возникает необходимость в генерации случайного целого числа, например для случайного выбора агента в популяции. В этом нам поможет метод "RNDintInRange".

int C_AO_Utilities :: RNDintInRange (int min, int max)
{
  if (min == max) return min;
  if (min > max)
  {
    int temp = min;
    min = max;
    max = temp;
  }
  return min + rand () % (max - min + 1);
}

Получить случайную булеву величину с помощью метода "RNDbool" можно очень быстро, по сравнению с двумя методами выше, именно поэтому имел смысл разделения случайных величин на отдельные методы в зависимости от задачи.

bool C_AO_Utilities :: RNDbool ()
{
  return rand () % 2 == 0;
}

И ещё один метод "RNDprobab", позволяющий получить случайное вещественное число в диапазоне [0.0, 1.0]. Он прекрасно подходит для исполнения вероятности выполнения определённых операций, таких как вероятность кроссовера в генетическом алгоритме. Такие операции так же совершаются достаточно часто.

double C_AO_Utilities :: RNDprobab ()
{
  return (double)rand () / 32767;
}
Теперь рассмотрим базовый класс "C_AO" популяционных алгоритмов оптимизации. Этот класс описывает обязательные атрибуты всех популяционных алгоритмов, такие как:

  • Методы и свойства класса "C_AO":
- SetParams: виртуальный метод для установки параметров алгоритма.
- Init: виртуальный метод для инициализации алгоритма с передачей минимального и максимального диапазона поиска, шага и количества эпох.
- Moving: виртуальный метод для выполнения шага алгоритма.
- Revision: виртуальный метод для проведения ревизии алгоритма.
- GetName: метод для получения имени алгоритма.
- GetDesc: метод для получения описания алгоритма.
- GetParams: метод для получения параметров алгоритма в виде строки.
  • Защищенные свойства класса "C_AO":
- ao_name: имя алгоритма.
- "ao_desc: описание алгоритма.
- rangeMin, rangeMax, rangeStep: массивы для хранения минимального и максимального диапазона поиска, а также шага.
- coords: количество координат.
- popSize: размер популяции.
- revision: флаг для ревизии.
- u: объект класса "C_AO_Utilities" для выполнения вспомогательных функций.
#include "#C_AO_Utilities.mqh"

//——————————————————————————————————————————————————————————————————————————————
class C_AO
{
  public: //--------------------------------------------------------------------
  C_AO () { }
  ~C_AO () { for (int i = 0; i < ArraySize (a); i++) delete a [i];}

  double      cB     []; //best coordinates
  double      fB;        //FF of the best coordinates
  C_AO_Agent *a      []; //agents
  S_AlgoParam params []; //algorithm parameters

  virtual void SetParams () { }
  virtual bool Init (const double &rangeMinP  [], //minimum search range
                     const double &rangeMaxP  [], //maximum search range
                     const double &rangeStepP [], //step search
                     const int     epochsP = 0)   //number of epochs
  { return false;}

  virtual void Moving   () { }
  virtual void Revision () { }

  string GetName   () { return ao_name;}
  string GetDesc   () { return ao_desc;}
  string GetParams ()
  {
    string str = "";
    for (int i = 0; i < ArraySize (params); i++)
    {
      str += (string)params [i].val + "|";
    }
    return str;
  }


  protected: //-----------------------------------------------------------------
  string ao_name;      //ao name;
  string ao_desc;      //ao description

  double rangeMin  []; //minimum search range
  double rangeMax  []; //maximum search range
  double rangeStep []; //step search

  int    coords;       //coordinates number
  int    popSize;      //population size
  bool   revision;

  C_AO_Utilities u;     //auxiliary functions

  bool StandardInit (const double &rangeMinP  [], //minimum search range
                     const double &rangeMaxP  [], //maximum search range
                     const double &rangeStepP []) //step search
  {
    MathSrand ((int)GetMicrosecondCount ()); //reset of the generator
    fB       = -DBL_MAX;
    revision = false;

    coords  = ArraySize (rangeMinP);
    if (coords == 0 || coords != ArraySize (rangeMaxP) || coords != ArraySize (rangeStepP)) return false;

    ArrayResize (rangeMin,  coords);
    ArrayResize (rangeMax,  coords);
    ArrayResize (rangeStep, coords);
    ArrayResize (cB,        coords);

    ArrayCopy (rangeMin,  rangeMinP,  0, 0, WHOLE_ARRAY);
    ArrayCopy (rangeMax,  rangeMaxP,  0, 0, WHOLE_ARRAY);
    ArrayCopy (rangeStep, rangeStepP, 0, 0, WHOLE_ARRAY);

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

Так же в этом же файле вместе с базовым классом находится перечисление "E_AO", содержащее идентификаторы оптимизационных алгоритмов и функция "SelectAO", позволяющая создать экземпляр соответствующего алгоритма и получить его указатель.

#include "AO_BGA_Binary_Genetic_Algorithm.mqh"
#include "AO_(P_O)ES_Evolution_Strategies.mqh"
#include "AO_DE_Differential_Evolution.mqh"
#include "AO_SDSm_Stochastic_Diffusion_Search.mqh"
#include "AO_ESG_Evolution_of_Social_Groups.mqh";

//——————————————————————————————————————————————————————————————————————————————
enum E_AO
{
  AO_BGA,
  AO_P_O_ES,
  AO_SDSm,
  AO_ESG,
  AO_DE,
  AO_NONE
};
C_AO *SelectAO (E_AO a)
{
  C_AO *ao;
  switch (a)
  {
    case  AO_BGA:
      ao = new C_AO_BGA (); return (GetPointer (ao));
    case  AO_P_O_ES:
      ao = new C_AO_P_O_ES (); return (GetPointer (ao));
    case  AO_SDSm:
      ao = new C_AO_SDSm (); return (GetPointer (ao));
    case  AO_ESG:
      ao = new C_AO_ESG (); return (GetPointer (ao));
    case  AO_DE:
      ao = new C_AO_DE (); return (GetPointer (ao));

    default:
      ao = NULL; return NULL;
  }
}
//——————————————————————————————————————————————————————————————————————————————


3. Код алгоритмов - наследников

В качестве примера наследования от базового класса рассмотрим алгоритм стохастического диффузионного поиска, SDSm. Агента "C_SDS_Agent" этого алгоритма мы унаследуем от базового агента "C_AO_Agent". Заметим, что в методе инициализации агента присутствуют координаты "с" и приспособленность "f", однако в классе "C_SDS_Agent" они не объявлены. Это вполне логично, поскольку эти атрибуты являются обязательными для всех агентов алгоритмов оптимизации и наследуются от базового алгоритма, поэтому нет необходимости их повторно объявлять.

//——————————————————————————————————————————————————————————————————————————————
class C_SDS_Agent : public C_AO_Agent
{
  public: //--------------------------------------------------------------------
  ~C_SDS_Agent () { }

  int    raddr     []; //restaurant address
  int    raddrPrev []; //previous restaurant address
  double cPrev     []; //previous coordinates (dishes)
  double fPrev;        //previous fitness

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

Класс "C_AO_SDSm" алгоритма SDSm наследуется от класса "C_AO". При объявлении объекта класса в конструкторе произведём инициализацию внешних параметров алгоритма, которые впоследствии могут быть изменены пользователем при особом желании, параметры будут доступны в виде массива и нам не придется беспокоиться о совместимости с тестовым стендом.

//——————————————————————————————————————————————————————————————————————————————
class C_AO_SDSm : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_SDSm () { }
  C_AO_SDSm ()
  {
    ao_name = "SDSm";
    ao_desc = "Stochastic Diffusion Search";

    popSize    = 100; //population size

    restNumb   = 100;  //restaurants number
    probabRest = 0.05; //probability restaurant choosing

    ArrayResize (params, 3);

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

    params [1].name = "restNumb";   params [1].val  = restNumb;
    params [2].name = "probabRest"; params [2].val  = probabRest;
  }

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

    restNumb   = (int)params [1].val;
    probabRest = params      [2].val;
  }

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

  void Moving   ();
  void Revision ();

  //----------------------------------------------------------------------------
  int    restNumb;          //restaurants number
  double probabRest;        //probability restaurant choosing

  C_SDS_Agent *agent []; //candidates

  private: //-------------------------------------------------------------------
  struct S_Riverbed //русло реки
  {
      double coordOnSector []; //coordinate on the sector (количество ячеек: количество секторов на координате, значение ячеек: конкретная координата на секторе)
  };

  double restSpace [];      //restaurants space
  S_Riverbed    rb [];      //riverbed

  void Research  (const double  ko,
                  const int     raddr,
                  const double  restSpace,
                  const double  rangeMin,
                  const double  rangeStep,
                  const double  pitOld,
                  double       &pitNew);
};
//——————————————————————————————————————————————————————————————————————————————
Далее, особо следует рассмотреть метод инициализации "Init" класса "C_AO_SDSm". По шагам метод выполняет следующее:

1. С самого начала необходимо вызвать метод базового класса "StandardInit" и передать в него "rangeMinP", "rangeMaxP", "rangeStepP". Если метод возвращает "false", функция "Init" также возвращает "false", сообщая о неудачной инициализации алгоритма.
2. Удаление агентов с помощью "delete". Это необходимо при многократном использовании объекта алгоритма.
3. Затем массивам "agent" алгоритма SDSm и "a" базового класса изменяем размер до "popSize" и производим приведение типов.
4. Далее выполняются действия, аналогичные алгоритму, описанному ранее в статье о SDSm.
//——————————————————————————————————————————————————————————————————————————————
bool C_AO_SDSm::Init (const double &rangeMinP  [], //minimum search range
                      const double &rangeMaxP  [], //maximum search range
                      const double &rangeStepP [], //step search
                      const int     epochsP = 0)   //number of epochs
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  for (int i = 0; i < ArraySize (agent); i++) delete agent [i];

  ArrayResize (agent, popSize);
  ArrayResize (a,     popSize);

  for (int i = 0; i < popSize; i++)
  {
    a     [i] = new C_SDS_Agent ();
    agent [i] = (C_SDS_Agent *)a [i];

    agent [i].Init (coords);
  }

  ArrayResize (restSpace, coords);
  ArrayResize (rb,        coords);
  for (int i = 0; i < coords; i++)
  {
    ArrayResize     (rb [i].coordOnSector, restNumb);
    ArrayInitialize (rb [i].coordOnSector, -DBL_MAX);
  }

  for (int i = 0; i < coords; i++)
  {
    restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb;
  }

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


4. Код единого тестового стенда для всех алгоритмов

Хотя на данный момент нет необходимости "размножать" тестовые стенды, мы все же перенесем все функции стенда в класс "C_TestStand". Это позволит удобно инкапсулировать функционал стенда. Поскольку никаких существенных изменений в функциях стенда не произошло, не будем подробно описывать их. Достаточно просто посмотреть, как теперь выглядит это "тестовое хозяйство":

#include <Canvas\Canvas.mqh>
#include <\Math\Functions.mqh>

//——————————————————————————————————————————————————————————————————————————————
class C_TestStand
{
  public: void Init (int width, int height)
  {
    W = width;  //750;
    H = height; //375;

    WscrFunc = H - 2;
    HscrFunc = H - 2;

    //creating a table ---------------------------------------------------------
    string canvasName = "AO_Test_Func_Canvas";

    if (!Canvas.CreateBitmapLabel (canvasName, 5, 30, W, H, COLOR_FORMAT_ARGB_RAW))
    {
      Print ("Error creating Canvas: ", GetLastError ());
      return;
    }

    ObjectSetInteger (0, canvasName, OBJPROP_HIDDEN, false);
    ObjectSetInteger (0, canvasName, OBJPROP_SELECTABLE, true);

    ArrayResize (FunctScrin, HscrFunc);

    for (int i = 0; i < HscrFunc; i++) ArrayResize (FunctScrin [i].clr, HscrFunc);

  }

  struct S_CLR
  {
    color clr [];
  };

  //----------------------------------------------------------------------------
  public: void CanvasErase ()
  {
    Canvas.Erase (XRGB (0, 0, 0));
    Canvas.FillRectangle (1,     1, H - 2, H - 2, COLOR2RGB (clrWhite));
    Canvas.FillRectangle (H + 1, 1, W - 2, H - 2, COLOR2RGB (clrWhite));
  }

  //----------------------------------------------------------------------------
  public: void MaxMinDr (C_Function & f)
  {
    //draw Max global-------------------------------------------------------------
    int x = (int)Scale(f.GetMaxFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 1, W/2 - 1, false);
    int y = (int)Scale(f.GetMaxFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 1, H   - 1, true);

    Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 14, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 15, COLOR2RGB(clrBlack));

    //draw Min global-------------------------------------------------------------
    x = (int)Scale(f.GetMinFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 0, W/2 - 1, false);
    y = (int)Scale(f.GetMinFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 0, H - 1, true);

    Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack));
  }

  //----------------------------------------------------------------------------
  public: void PointDr (double &args [], C_Function & f, int shiftX, int shiftY, int count, bool main)
  {
    double x = 0.0;
    double y = 0.0;

    double xAve = 0.0;
    double yAve = 0.0;

    int width  = 0;
    int height = 0;

    color clrF = clrNONE;

    for(int i = 0; i < count; i++)
    {
      xAve += args [i * 2];
      yAve += args [i * 2 + 1];

      x = args [i * 2];
      y = args [i * 2 + 1];

      width  = (int)Scale(x, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false);
      height = (int)Scale(y, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true);

      clrF = DoubleToColor(i, 0, count - 1, 0, 270);
      Canvas.FillCircle(width + shiftX, height + shiftY, 1, COLOR2RGB(clrF));
    }

    xAve /=(double)count;
    yAve /=(double)count;

    width  = (int)Scale(xAve, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false);
    height = (int)Scale(yAve, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true);

    if(!main)
    {
      Canvas.FillCircle(width + shiftX, height + shiftY, 3, COLOR2RGB(clrBlack));
      Canvas.FillCircle(width + shiftX, height + shiftY, 2, COLOR2RGB(clrWhite));
    }
    else
    {
      Canvas.Circle (width + shiftX, height + shiftY, 5, COLOR2RGB (clrBlack));
      Canvas.Circle (width + shiftX, height + shiftY, 6, COLOR2RGB (clrBlack));
    }
  }

  //----------------------------------------------------------------------------
  public: void SendGraphToCanvas ()
  {
    for (int w = 0; w < HscrFunc; w++)
    {
      for (int h = 0; h < HscrFunc; h++)
      {
        Canvas.PixelSet (w + 1, h + 1, COLOR2RGB (FunctScrin [w].clr [h]));
      }
    }
  }

  //----------------------------------------------------------------------------
  public: void DrawFunctionGraph (C_Function & f)
  {
    double ar [2];
    double fV;

    for (int w = 0; w < HscrFunc; w++)
    {
      ar [0] = Scale (w, 0, H, f.GetMinRangeX (), f.GetMaxRangeX (), false);
      for (int h = 0; h < HscrFunc; h++)
      {
        ar [1] = Scale (h, 0, H, f.GetMinRangeY (), f.GetMaxRangeY (), true);
        fV = f.CalcFunc (ar, 1);
        FunctScrin [w].clr [h] = DoubleToColor (fV, f.GetMinFunValue (), f.GetMaxFunValue (), 0, 270);
      }
    }
  }

  //----------------------------------------------------------------------------
  public: void Update ()
  {
    Canvas.Update ();
  }

  //----------------------------------------------------------------------------
  //Scaling a number from a range to a specified range
  public: double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX, bool Revers = false)
  {
    if (OutMIN == OutMAX) return (OutMIN);
    if (InMIN == InMAX) return ((OutMIN + OutMAX) / 2.0);
    else
    {
      if (Revers)
      {
        if (In < InMIN) return (OutMAX);
        if (In > InMAX) return (OutMIN);
        return (((InMAX - In) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
      }
      else
      {
        if (In < InMIN) return (OutMIN);
        if (In > InMAX) return (OutMAX);
        return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
      }
    }
  }

  //----------------------------------------------------------------------------
  private: color DoubleToColor (const double In,    //input value
                                const double inMin, //minimum of input values
                                const double inMax, //maximum of input values
                                const int    loH,   //lower bound of HSL range values
                                const int    upH)   //upper bound of HSL range values
  {
    int h = (int) Scale (In, inMin, inMax, loH, upH, true);
    return HSLtoRGB (h, 1.0, 0.5);
  }

  //----------------------------------------------------------------------------
  private: color HSLtoRGB (const int    h, //0   ... 360
                           const double s, //0.0 ... 1.0
                           const double l) //0.0 ... 1.0
  {
    int r;
    int g;
    int b;
    if (s == 0.0)
    {
      r = g = b = (unsigned char)(l * 255);
      return StringToColor ((string) r + "," + (string) g + "," + (string) b);
    }
    else
    {
      double v1, v2;
      double hue = (double) h / 360.0;
      v2 = (l < 0.5) ? (l * (1.0 + s)) : ((l + s) - (l * s));
      v1 = 2.0 * l - v2;
      r = (unsigned char)(255 * HueToRGB (v1, v2, hue + (1.0 / 3.0)));
      g = (unsigned char)(255 * HueToRGB (v1, v2, hue));
      b = (unsigned char)(255 * HueToRGB (v1, v2, hue - (1.0 / 3.0)));
      return StringToColor ((string) r + "," + (string) g + "," + (string) b);
    }
  }

  //----------------------------------------------------------------------------
  private: double HueToRGB (double v1, double v2, double vH)
  {
    if (vH < 0) vH += 1;
    if (vH > 1) vH -= 1;
    if ((6 * vH) < 1) return (v1 + (v2 - v1) * 6 * vH);
    if ((2 * vH) < 1) return v2;
    if ((3 * vH) < 2) return (v1 + (v2 - v1) * ((2.0f / 3) - vH) * 6);
    return v1;
  }

  //----------------------------------------------------------------------------
  public: int W; //monitor screen width
  public: int H; //monitor screen height

  private: int WscrFunc; //test function screen width
  private: int HscrFunc; //test function screen height

  public:  CCanvas Canvas;      //drawing table
  private: S_CLR FunctScrin []; //two-dimensional matrix of colors
};
//——————————————————————————————————————————————————————————————————————————————
Теперь давайте взглянем на сам код тестового стенда. При изучении входных параметров стенда становится ясно, что теперь у нас есть возможность выбирать алгоритм оптимизации и тестовые функции в настройках. Это позволяет создавать уникальные наборы тестовых функций для проверки алгоритма. Теперь можно проводить тестирование только на гладких или только на дискретных функциях. Более того, теперь можно выбирать любую комбинацию тестовых функций, наиболее подходящую для потребностей пользователя.
#include "PAO\#C_TestStandFunctions.mqh"
#include "PAO\#C_AO.mqh"

//——————————————————————————————————————————————————————————————————————————————
input string AOparam            = "----------------"; //AO parameters-----------
input E_AO   AOexactly_P        = AO_NONE;

input string TestStand_1        = "----------------"; //Test stand--------------
input double ArgumentStep_P     = 0.0;   //Argument Step

input string TestStand_2        = "----------------"; //------------------------
input int    Test1FuncRuns_P    = 5;     //Test #1: Number of functions in the test
input int    Test2FuncRuns_P    = 25;    //Test #2: Number of functions in the test
input int    Test3FuncRuns_P    = 500;   //Test #3: Number of functions in the test

input string TestStand_3        = "----------------"; //------------------------
input EFunc  Function1          = Hilly;
input EFunc  Function2          = Forest;
input EFunc  Function3          = Megacity;

input string TestStand_4        = "----------------"; //------------------------
input int    NumbTestFuncRuns_P = 10000; //Number of test function runs
input int    NumberRepetTest_P  = 10;    //Test repets number

input string TestStand_5        = "----------------"; //------------------------
input bool   Video_P            = true;  //Show video
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  C_AO *AO = SelectAO (AOexactly_P);
  if (AO == NULL)
  {
    Print ("AO is not selected...");
    return;
  }

  Print (AO.GetName (), "|", AO.GetDesc (), "|", AO.GetParams ());

  //============================================================================
  C_TestStand ST; //stand
  ST.Init (750, 375);

  double allScore = 0.0;
  double allTests = 0.0;

  C_Function *F1 = SelectFunction (Function1);
  C_Function *F2 = SelectFunction (Function2);
  C_Function *F3 = SelectFunction (Function3);

  if (F1 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();

    FuncTests (AO, ST, F1, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F1, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F1, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F1;
  }

  if (F2 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();
    FuncTests (AO, ST, F2, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F2, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F2, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F2;
  }

  if (F3 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();
    FuncTests (AO, ST, F3, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F3, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F3, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F3;
  }

  Print ("=============================");
  if (allTests > 0.0) Print ("All score: ", DoubleToString (allScore, 5), " (", DoubleToString (allScore * 100 / allTests, 2), "%)");
  delete AO;
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void FuncTests (C_AO          &ao,
                C_TestStand   &st,
                C_Function    &f,
                const  int     funcCount,
                const  color   clrConv,
                double        &allScore,
                double        &allTests)
{
  if (funcCount <= 0) return;

  allTests++;

  if (Video_P)
  {
    st.DrawFunctionGraph (f);
    st.SendGraphToCanvas ();
    st.MaxMinDr          (f);
    st.Update            ();
  }

  int    xConv      = 0.0;
  int    yConv      = 0.0;
  double aveResult  = 0.0;
  int    params     = funcCount * 2;
  int    epochCount = NumbTestFuncRuns_P / (int)ao.params [0].val;

  //----------------------------------------------------------------------------
  double rangeMin  [], rangeMax  [], rangeStep [];
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);
  ArrayResize (rangeStep, params);

  for (int i = 0; i < funcCount; i++)
  {
    rangeMax  [i * 2] = f.GetMaxRangeX ();
    rangeMin  [i * 2] = f.GetMinRangeX ();
    rangeStep [i * 2] = ArgumentStep_P;

    rangeMax  [i * 2 + 1] = f.GetMaxRangeY ();
    rangeMin  [i * 2 + 1] = f.GetMinRangeY ();
    rangeStep [i * 2 + 1] = ArgumentStep_P;
  }

  for (int test = 0; test < NumberRepetTest_P; test++)
  {
    //--------------------------------------------------------------------------
    if (!ao.Init (rangeMin, rangeMax, rangeStep)) break;

    // Optimization-------------------------------------------------------------
    for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
    {
      ao.Moving ();

      for (int set = 0; set < ArraySize (ao.a); set++)
      {
        ao.a [set].f = f.CalcFunc (ao.a [set].c, funcCount);
      }

      ao.Revision  ();

      if (Video_P)
      {
        //drawing a population--------------------------------------------------
        st.SendGraphToCanvas  ();

        for (int i = 0; i < ArraySize (ao.a); i++)
        {
          st.PointDr (ao.a [i].c, f, 1, 1, funcCount, false);
        }
        st.PointDr (ao.cB, f, 1, 1, funcCount, true);

        st.MaxMinDr (f);

        //drawing a convergence graph---------------------------------------------
        xConv = (int)st.Scale (epochCNT, 1, epochCount, st.H + 2, st.W - 3, false);
        yConv = (int)st.Scale (ao.fB, f.GetMinFunValue (), f.GetMaxFunValue (), 2, st.H - 2, true);
        st.Canvas.FillCircle (xConv, yConv, 1, COLOR2RGB (clrConv));

        st.Update ();
      }
    }

    aveResult += ao.fB;
  }

  aveResult /= (double)NumberRepetTest_P;

  double score = aveResult;

  Print (funcCount, " ", f.GetFuncName (), "'s; Func runs: ", NumbTestFuncRuns_P, "; result: ", aveResult);
  allScore += score;
}
//——————————————————————————————————————————————————————————————————————————————


5. Добавим популярные известные тестовые функции

Иногда меня спрашивают, почему я не включил в набор тестовых функций такие широко известные и часто используемые при исследованиях и разработках алгоритмов оптимизации. Причина кроется в том, что все они относятся к категории "простых" тестовых функций по моей классификации. Более половины их поверхностей сосредоточены вблизи глобального экстремума, что делает их слишком "предсказуемыми" для надлежащего тестирования. Но, несмотря на это, люди привыкли доверять им и стремятся проверять свои алгоритмы именно на таких функциях. Поэтому я решил включить в набор функции Экли, Гольдштейна-Прайса и Шафера №2. Это поможет сбалансировать выбор тестовых функций пользователям и обеспечить более всестороннее и надежное тестирование алгоритмов оптимизации, открывая новые горизонты для исследователей и способствуя более глубокому пониманию их эффективности.

Формула Экли:

f(x, y) = -(-20 * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20)

где:
- x, y - входные параметры функции,
- e - число Эйлера (приблизительно 2.71828),
- π - число пи (приблизительно 3.14159).

Код функции:

double Core (double x, double y)
{
  double res1 = -20.0 * MathExp (-0.2 * MathSqrt (0.5 * (x * x + y * y)));
  double res2 = -MathExp (0.5 * (MathCos (2.0 * M_PI * x) + MathCos (2.0 * M_PI * y)));
  double res3 = -(res1 + res2 + M_E + 20.0);

  return Scale (res3, -14.302667500265278, 0.0, 0.0, 1.0);
}

Формула Гольдштейна-Прайса :

f(x, y) = -([1 + (x + y + 1)^2 * (19 - 14x + 3x^2 - 14y + 6xy + 3y^2)] * [30 + (2x - 3y)^2 * (18 - 32x + 12x^2 + 48y - 36xy + 27y^2)])

Код функции:

double Core (double x, double y)
{
  double part1 = 1 + MathPow ((x + y + 1), 2) * (19 - 14 * x + 3 * x * x - 14 * y + 6 * x * y + 3 * y * y);
  double part2 = 30 + MathPow ((2 * x - 3 * y), 2) * (18 - 32 * x + 12 * x * x + 48 * y - 36 * x * y + 27 * y * y);

  return Scale (-part1 * part2, -1015690.2717980597, -3.0, 0.0, 1.0);
}

Формула Шафера №2:

f(x, y) = -(0.5 + ((sin(x^2 - y^2)^2 - 0.5) / (1 + 0.001 * (x^2 + y^2))^2))

Код функции:

double Core (double x, double y)
{
  double numerator   = MathPow (MathSin (x * x - y * y), 2) - 0.5;
  double denominator = MathPow (1 + 0.001 * (x * x + y * y), 2);
    
  return Scale (-(0.5 + numerator / denominator), -0.9984331449753265, 0, 0, 1.0);
}

Примечание: значения тестовых функций приведены к диапазону [0.0, 1.0].

6. Построение тестовых функций 3D

Иногда для более глубокого понимания тестовых функций и их рельефа необходимо не просто абстрагироваться от чисел и формул, а увидеть их визуально. Поэтому я решил воспользоваться возможностью построения 3D-сцен с помощью DirectX в платформе MetaTrader 5. Меня вдохновил файл "...\MQL5\Experts\Examples\Math 3D Morpher\Math 3D Morpher.mq5" от разработчиков, который входит в стандартную поставку и визуализирует функции, разработанные мной ранее (и которые я планирую добавить в список функций для тестового стенда). Таким образом, я принял решение создать визуализатор для тестовых функций.

Для этого мне пришлось расширить файл "Functions.mqh", где хранится класс тестовых функций, используемых в тестировании алгоритмов оптимизации. Дополнительные функции, которые я добавил, позволят не только изучить изменения функций визуально при их модификации если возникнет такая необходимость, но и более глубоко исследовать их свойства и особенности.

Этот процесс не только делает мое исследование более увлекательным и наглядным, но и помогает мне более полно понять поведение тестовых функций. В конечном итоге визуализация в 3D помогает мне не просто видеть числа на экране, а непосредственно взаимодействовать с формой и структурой функций, что важно при их модификации и анализе свойств.

Код дополнительных функций для построения модели для 3D-сцены:

//——————————————————————————————————————————————————————————————————————————————
//GenerateFunctionDataFixedSize
bool GenerateFunctionDataFixedSize (int x_size, int y_size, double &data [], double x_min, double x_max, double y_min, double y_max, C_Function &function)
{
  if (x_size < 2 || y_size < 2)
  {
    PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    return (false);
  }

  double dx = (x_max - x_min) / (x_size - 1);
  double dy = (y_max - y_min) / (y_size - 1);

  ArrayResize (data, x_size * y_size);

  //---
  for (int j = 0; j < y_size; j++)
  {
    for (int i = 0; i < x_size; i++)
    {
      double x = x_min + i * dx;
      double y = y_min + j * dy;

      data [j * x_size + i] = function.Core (x, y);
    }
  }
  return (true);
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
//GenerateDataFixedSize
bool GenerateDataFixedSize (int x_size, int y_size, C_Function &function, double &data [])
{
  if (x_size < 2 || y_size < 2)
  {
    PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    return (false);
  }

  return GenerateFunctionDataFixedSize (x_size, y_size, data,
                                        function.GetMinRangeX (),
                                        function.GetMaxRangeX (),
                                        function.GetMinRangeY (),
                                        function.GetMaxRangeY (),
                                        function);
}
//——————————————————————————————————————————————————————————————————————————————

Дополнительно я добавил дискретные варианты уже знакомых нам функций: SkinDiscrete и HillyDiscrete.

Hilly Discrete

Функция HillyDiscrete.

Skin Discrete

Функция SkinDiscrete.

Shaffer

Функция Shaffer #2.

7. Выводы

Мы продолжим пользоваться тестовыми функциями "Hilly", "Forest" и "Megacity", которые уже завоевали свою репутацию как настоящие испытания для алгоритмов оптимизации, помогая составить надежную рейтинговую таблицу. Однако, не стоит ограничиваться только этими функциями - ведь список тестовых функций успешно пополнился! Так что вам предоставляется свобода выбора: экспериментируйте, исследуйте, открывайте новые горизонты, ведь именно в этом заключается вся прелесть научных исследований.

В заключении нашего "кулинарного" исследования гибридных методов оптимизации через наследование от базового класса, мы видим, что создание такой отправной точки открывает двери перед бесконечными возможностями исследований. Объединение различных популяционных алгоритмов в один класс позволяет нам не только улучшить точность и скорость поиска оптимальных решений, но и создать универсальный инструмент для исследований.

Создание единого тестового стенда, который объединяет разнообразные тестовые функции — дискретные, гладкие, острые, недифференцируемые — открывает новые возможности для тестирования и сравнения различных методов оптимизации. Этот подход позволяет исследователям не только использовать стандартные наборы функций, но и создавать собственные тестовые стенды, адаптированные под конкретные задачи и требования.

Таким образом, объединение в один класс популяционных алгоритмов и создание универсального тестового стенда открывает перед нами увлекательный путь к созданию новых "гастрономических" комбинаций методов оптимизации, позволяя нам открывать новые вкусовые ноты в мире поиска оптимальных решений. Давайте продолжим этот "кулинарный" эксперимент и создадим новые шедевры на пути к совершенству и инновациям в области оптимизации!

Прикрепленные файлы |
AOs.zip (430.62 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Stanislav Korotky
Stanislav Korotky | 29 февр. 2024 в 14:02

Опечатка?

    W = width;  //750;
    H = height; //375;

    WscrFunc = H - 2; // W - 2
    HscrFunc = H - 2;
Andrey Dik
Andrey Dik | 29 февр. 2024 в 15:13
Stanislav Korotky #:

Опечатка?


Нет.
Мультибот в MetaTrader (Часть II): улучшенный динамический шаблон Мультибот в MetaTrader (Часть II): улучшенный динамический шаблон
Развивая тему предыдущей статьи про мультибота, я решил создать более гибкий и функциональный шаблон, который обладает большими возможностями и может эффективно применяться как во фрилансе, так и использоваться в виде базы для разработки мультивалютных и мультипериодных советников с возможностью интеграции с внешними решениями.
Добавляем пользовательскую LLM в торгового робота (Часть 2): Пример развертывания среды Добавляем пользовательскую LLM в торгового робота (Часть 2): Пример развертывания среды
Языковые модели (LLM) являются важной частью быстро развивающегося искусственного интеллекта, поэтому нам следует подумать о том, как интегрировать мощные LLM в нашу алгоритмическую торговлю. Большинству людей сложно настроить эти модели в соответствии со своими потребностями, развернуть их локально, а затем применить к алгоритмической торговле. В этой серии статей будет рассмотрен пошаговый подход к достижению этой цели.
Разрабатываем мультивалютный советник (Часть 4): Отложенные виртуальные ордера и сохранение состояния Разрабатываем мультивалютный советник (Часть 4): Отложенные виртуальные ордера и сохранение состояния
Приступив к разработке мультивалютного советника мы уже достигли некоторых результатов и успели провести несколько итераций улучшения кода. Однако наш советник не мог работать с отложенными ордерами и возобновлять работу после перезапуска терминала. Давайте добавим эти возможности.
Перестановка ценовых баров в MQL5 Перестановка ценовых баров в MQL5
В этой статье мы представляем алгоритм перестановки ценовых баров и подробно рассказываем, как тесты на перестановку (permutation tests) можно использовать для выявления случаев, когда эффективность стратегии была сфабрикована с целью обмануть потенциальных покупателей советников.