English 中文 Español Deutsch 日本語 Português
preview
Сделайте торговые графики лучше с интерактивным графическим интерфейсом на основе MQL5 (Часть II): Перемещаемый интерфейс (II)

Сделайте торговые графики лучше с интерактивным графическим интерфейсом на основе MQL5 (Часть II): Перемещаемый интерфейс (II)

MetaTrader 5Торговые системы | 2 октября 2023, 13:27
854 0
Kailash Bai Mina
Kailash Bai Mina

Введение

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

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

К счастью, мы можем обратиться к файлам .mqh, чтобы упростить этот процесс.

Вот что мы рассмотрим в этой статье:

  1. Понятие классов
  2. Создание панели с помощью файла .mqh.
  3. Установка двух панелей на одном графике с помощью файла .mqh.


Понятие классов

Прежде всего рассмотрим понятие классов. Тема обширная и сложная, но нам сейчас нужны лишь основы.

Говоря простыми словами, класс — это сложный тип данных, похожий на int, string и другие, но несколько сложнее.

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

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

Это определение естественным образом приводит к четырем фундаментальным вопросам:

  1. Где мы их создаем?
  2. Как мы их объявляем?
  3. Как их написать?
  4. Как их использовать?

Ответ на вопрос, зачем нам вообще нужны классы, ответ довольно прост. Классы упрощают процесс написания и управления кодом.

  1. Где мы их создаем?

    Выбор типа файла для создания классов — будь то .mq5 или .mqh — является гибким. Однако обычно мы выбираем отдельные файлы .mqh.

    Разница между созданием классов в .mq5 и .mqh довольно примечательна. Если вы разрабатываете свои классы в файле .mqh, вам необходимо импортировать его в .mq5. Это связано с тем, что создание советника/индикатора предназначено исключительно для файлов .mq5. Однако если вы установите класс непосредственно в файле .mq5, никакой процесс импорта не понадобится.

    Обычно мы предпочитаем отдельные файлы .mqh, поскольку они улучшают управляемость кода. Процесс импорта прост — для него требуется всего лишь одна строка кода. Здесь мы будем использовать отдельный файл .mqh.


  2. Как мы их объявляем?

    Объявление класса делается довольно просто. Ниже приведен пример объявления простого пустого класса:

    class YourClassName
      {
      };
    В приведенном выше фрагменте кода YourClassName является заполнителем. Замените YourClassName фактическим именем, которое вы хотите назначить своему классу.



  3. Как их написать?

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

    Предположим, вы хотите объявить две переменные: одну типа int, а другую — типа bool. Вы можете сделать это следующим образом:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1;
       bool var2;
      };
    //+------------------------------------------------------------------+
    

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

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1 = "int var";
       bool var2 = true;
      };
    //+------------------------------------------------------------------+
    
    Ошибка будет следующая:
    '=' - illegal assignment use
    '=' - illegal assignment use

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

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

    Например, функция OnInit() в нашем коде ведет себя как конструктор, а функция OnDeinit() — как деструктор. Класс здесь скрыт в фоновом режиме для обеспечения простоты. Такое поведение распространено во многих языках, включая Java, который всегда включает класс по умолчанию.

    Ниже мы рассмотрим, что означает "экземпляр" (instance).

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

    Я настоятельно рекомендую использовать конструктор.

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

    Это предположение связано с тем, что во многих языках программирования (даже в MQL4) переменные без явного определения по умолчанию принимают какое-то значение, не вызывая несоответствий в коде. Однако в MQL5 вы можете столкнуться с несоответствиями в своем коде, если не определите переменные явно.

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

    Тип Код Предполагаемое значение по умолчанию
     int  int test; 0
     double  double test; 0.0
     bool  bool test; false
     string  string test; NULL
     datetime  datetime test;   1970.01.01 00:00:00

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

    Наш скрипт для тестирования выглядит так:

    void OnStart()
      {
       type test;
       if(test == presumedDefaultValue)
         {
          Alert("Yes, This is the default value of the test variable.");
         }
       else
         {
          Alert("No, This is NOT the default value of the test variable.");
         }
      }

    Замените type типом переменной, а presumedDefaultValue — значением, которое, по вашему мнению, будет значением по умолчанию.

    Вы увидите, что для bool и string все работает отлично и появляется сообщение "Yes, This is the default value of the test variable" (да, это значение тестовой переменной по умолчанию). Однако для int, double и datetime всё не так просто. Вы получите сообщение "No, This is NOT the default value of the test variable" (нет, это НЕ значение тестовой переменной по умолчанию). Этот неожиданный результат может вызвать логические проблемы.

    Теперь, когда мы понимаем важность конструктора, давайте посмотрим, как его создать:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name();
                        ~name();
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+
    

    Здесь мы включили в класс публичные и приватные поля, хотя в настоящее время приватное поле пустое. Очень важно объявить конструктор с модификатором публичного доступа, чтобы использовать его в других файлах. Для лучшего понимания обсудим модификаторы (или спецификаторы) доступа (Access Modifiers или Access Specifiers).

    Модификаторы доступа определяют, как компилятор может получить доступ к переменным, членам структур или классам.

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

    В этой статье для нас актуальны только публичные и приватные модификаторы. Остальные будут рассмотрены в последующих частях серии.

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

    Зачем они вообще нужны? Есть много причин, таких как скрытие данных, абстракция, сопровождаемость, возможность повторного использования. 

    Код нашего конструктора выглядит так:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+

    Конструктор/деструктор не имеет возвращаемого типа.

    Здесь мы просто присваиваем значение неинициализированным переменным, потому что иногда нам может понадобиться, чтобы для переменной bool (var2 в данном случае) было установлено значение true в качестве начального.

    Есть альтернативный способ сделать это:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name() : var1(0), var2(true)
      {
      }
    //+------------------------------------------------------------------+

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

    Или вы можете указать следующее в списке членов:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name() : var1(0), var2(true) {}
                        ~name();
      };
    //+------------------------------------------------------------------+



    Теперь, когда мы рассмотрели, как объявлять переменные, конструкторы и деструкторы, создание функции не составит труда.

    Если вы хотите создать функцию с именем functionName(), которая принимает один параметр в качестве строковой переменной и просто печатает переменную, это будет выглядеть примерно так: 

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class className
      {
    public:
       void              functionName(string printThis)
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void className::functionName(string printThis)
      {
       Print(printThis);
      }
    //+------------------------------------------------------------------+


    Мы объявляем функцию в списке членов класса с именем className с модификатором открытого доступа, чтобы мы могли использовать эту функцию где угодно. Затем запишем тело функции.

    Обратите внимание, что объявление функции в классе, то есть

    functionName(string printThis)

    должно точно совпадать при написании тела функции.

    На этом мы завершаем наше базовое введение в написание классов на MQL5.


  4. Как их использовать?


    Чтобы лучше это понять, давайте посмотрим на структуру наших папок:

    • Test Project
      • mainFile.mq5
      • includeFile.mqh

    Во-первых, давайте посмотрим полный код нашего класса, который мы написали в includeFile.mqh:

    В этом примере мы объявили класс className, который включает конструктор, деструктор, три переменные (одну приватную и две публичные) и публичную функцию.

    • Конструктор: мы инициализируем переменные var0, var1 и var2 значениями 10, 0 и true соответственно.
    • Деструктор: в настоящее время пуст и поэтому ничего не делает.
    • var0: приватная целочисленная переменная, инициализированная значением 10 и используемая в функции (functionName).
    • var1: публичная целочисленная переменная, инициализированная значением 0 и также используемая в функции (functionName).
    • functionName: пустая функция functionName принимает целое число printThisNumber и печатает сумму printThisNumber, var0 и var1.

    Далее рассмотрим mainFile.mq5:

    #include "includeFile.mqh"
    className classInstance;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       classInstance.functionName(5)//This line will print (5+10+0) = 15
       classInstance.var1 = 50;//This will change the value of var1 to 50
       classInstance.functionName(5)//Now, this line will print (5+10+50) = 65
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+

    Сначала включаем файл includeFile.mqh file в mainFile.mq5. Затем создаем экземпляр класса, используя

    className classInstance;

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

    Мы использовали "" вместо <> для поиска файла .mqh, поскольку <> ищет файл .mqh в папке include, а "" ищет файл .mqh в текущем каталоге.


    Создание панели с помощью файла .mqh

    Теперь создадим панель с нуля, используя файл .mqh. При необходимости позаимствуем фрагменты нашего предыдущего кода. Чтобы эффективно организовать наши файлы, создадим новую папку Movable Dashboard MQL5.

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

    Для начала создадим белую панель 200x200, используя метод Object Create в нашем основном файле .mq5 (Movable_Dashboard_MQL5.mq5) в OnInit():

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //---
       //Set the name of the rectangle as "TestRectangle"
       string name = "TestRectangle";
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
       ChartRedraw();
    //---
       return(INIT_SUCCEEDED);
      }

    Результат: 

    Рис. 1. Базовая панель

    Рис. 1. Базовая панель


    Почему мы создаем панель в нашем основном файле .mq5 (Movable_Dashboard_MQL5.mq5), а не в .mqh (GUI_Movable.mqh). Это решение принято главным образом ради простоты и может зависеть от ваших конкретных целей. Мы воспользуемся этим подходом в следующем разделе.

    Обратим внимание на файл .mqh (GUI_Movable.mqh), который на данный момент выглядит так:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
       
      };
    //+------------------------------------------------------------------+

    Здесь мы просто объявили класс без явного определения конструктора и деструктора.

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

    Как нам этого добиться? Ниже приведен код, который должен сделать панель подвижной в нашем предыдущем файле .mq5 (Movable_Dashboard_MQL5.mq5):

    //Declare some global variable that will be used in the OnChartEvent() function
    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
          
          string name = "TestRectangle";
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    

    Теперь будем действовать следующим образом: 

    1. Напишем код для класса GUI_Movable.
    2. Создадим экземпляр класса в основном файле .mq5.
    3. Присвоим этому экземпляру имя
    4. Используем методы класса GUI_Movable, чтобы сделать панель подвижной.

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

    1. Напишем код для класса GUI_Movable:

      Нам нужно спланировать компоненты нашего класса. Вот разбивка:

      1. Нам нужно объявить шесть переменных с приватными модификаторами (previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance, movingState). Эти переменные включают пять целых чисел и одно логическое значение.
      2. Мы должны объявить седьмую публичную переменную, в которой будет храниться имя информационной панели. Нам нужно сделать эту переменную публичной, поскольку нам нужно будет изменить ее из нашего основного файла .mq5.
      3. Нам нужно найти способ использовать функцию OnChartEvent в файле .mqh, поскольку все наши объявленные переменные расположены там, и нам нужны эти переменные внутри функции OnChartEvent.


      1. Начнем с объявления шести приватных переменных в классе, пять из которых являются целыми числами и одна логическая. Мы будем использовать конструктор для инициализации этих значений:

        //+------------------------------------------------------------------+
        //| Class GUI_Movable                                                |
        //+------------------------------------------------------------------+
        class GUI_Movable
          {
        private:
           int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
           bool              movingState;
        public:
                             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false) {};
          };
        //+------------------------------------------------------------------+

        Нам нужно установить 0 для всех int и false для bool в качестве наших начальных значений, поэтому мы использовали конструктор для их инициализации.

      2. Далее объявим публичную переменную для хранения имени панели. Переменная должна быть доступна из нашего основного файла .mq5.

        public: 
           string Name;

        Начальное значение для него, конечно, будет NULL, но для формальности мы инициализируем его значением NULL и изменим наш конструктор на (это именно формальность, потому что string не вызывает несоответствий)

        GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
        
      3. Этот шаг может показаться немного сложным, но, если вы его поймете, всё станет проще.

        Создадим общедоступную функцию с именем OnEvent, которая будет принимать следующие входные данные: id, lparam, dparam и sparam. Так как OnChartEvent() ничего не возвращает (void), также сделаем OnEvent() пустым.

        Функция OnEvent будет делать все, для чего предназначена функция OnChartEvent(), но она будет делать это в файле .mqh. Мы будем использовать OnEvent() в фактической функции OnChartEvent() в основном файле.

        Чтобы избежать ошибок, вызванных объявлением OnChartEvent() как в .mqh, так и в основном файлах, мы создали отдельную функцию с именем OnEvent(). Объявим ее:

        public:
           string            Name;
           void              OnEvent(int id, long lparam, double dparam, string sparam);

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

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           
          }
        //+------------------------------------------------------------------+

        Мы поместили эту функцию в глобальную область видимости. Теперь мы можем просто поместить сюда тот же код, и он будет иметь доступ к переменным, объявленным в классе.

        Полный код функции будет выглядеть так:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
           if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //define X, Y, XDistance, YDistance, XSize, YSize
              int X = (int)lparam;
              int Y = (int)dparam;
              int MouseState = (int)sparam;
        
              string name = Name;
              int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
              int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
              int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
              int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
        
              if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
                {
                 mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
                 mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
                 mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
                 mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
        
                 if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                   {
                    movingState = true; //If yes the set movingState to True
                   }
        
                }
        
              if(movingState)//if movingState is true, Update the Dashboard position
                {
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
                 ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
                 ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
                 ChartRedraw(0); //Redraw Chart
                }
        
              if(MouseState == 0)//Check if MLB is not pressed
                {
                 movingState = false;//set movingState again to false
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
                }
        
              previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
             }
          }
        //+------------------------------------------------------------------+
        

        Единственное, что мы меняем, это 

        string name = "TestRectangle";

        на

        string name = Name;

        поскольку нам нужно использовать переменную Name, которую мы установили в основном файле .mq5.


    2. Создадим экземпляр класса в основном файле .mq5: 

      Это можно очень просто сделать следующим образом:

      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;

      Здесь мы включили файл .mqh, выбрав "" вместо <>, чтобы указать местоположение файла. <> ищет файл .mqh в папке include, а "" ищет файл .mqh в текущем каталоге, которым в данном случае является папка Movable Dashboard MQL5. Затем мы объявляем экземпляр класса GUI_Movable и присваиваем ему имя Dashboard. Это имя позволяет нам использовать код, который мы написали в файле .mqh.

    3. Дадим имя этому экземпляру:

      Это можно легко сделать с помощью функции OnInit(). Вот как должна выглядеть наша функция OnInit():

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }

      В конце используем
      //Give dashboard's name to the class instance
      Dashboard.Name = name;

      чтобы назначить переменную Name в экземпляре Dashboard класса GUI_Movable. Это будет использовано позже в функции OnEvent() внутри экземпляра. Очень важно установить для свойства CHART_EVENT_MOUSE_MOVE значение true. Это позволяет обнаруживать события мыши. Мы повторим этот шаг в конструкторе позже. Пока нет смысла усложнять задачу.

    4. Используем методы класса GUI_Movable, чтобы сделать панель мониторинга подвижной: 

      Несмотря на несколько сложное название, этот шаг прост.

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }

      На этом этапе помещаем функцию OnEvent() в OnChartEvent(), чтобы использовать функционал OnChartEvent() в файле .mqh.

    Наконец, вот наш полный код:

    Структура папок:

    • Movable Dashboard MQL5
      • Movable_Dashboard_MQL5.mq5
      • GUI_Movable.mqh

    1. Movable_Dashboard_MQL5.mq5
      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }
      //+------------------------------------------------------------------+
      
    2. GUI_Movable.mqh
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
        {
         //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
         if(id == CHARTEVENT_MOUSE_MOVE)
           {
            //define X, Y, XDistance, YDistance, XSize, YSize
            int X = (int)lparam;
            int Y = (int)dparam;
            int MouseState = (int)sparam;
      
            string name = Name;
            int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
            int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
            int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
            int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
      
            if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
              {
               mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
               mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
               mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
               mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
      
               if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                 {
                  movingState = true; //If yes the set movingState to True
                 }
      
              }
      
            if(movingState)//if movingState is true, Update the Dashboard position
              {
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
               ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
               ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
               ChartRedraw(0); //Redraw Chart
              }
      
            if(MouseState == 0)//Check if MLB is not pressed
              {
               movingState = false;//set movingState again to false
               ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
              }
      
            previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
           }
        }
      //+------------------------------------------------------------------+
      

    Сначала скомпилируйте файл .mqh, затем файл .mq5. Будет создан файл .ex5, который можно запустить на графике.

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


    Результат:

    Рис. 2. Простая перемещаемая панель

    Рис. 2. Простая перемещаемая панель



    Установка двух панелей на одном графике с помощью файла .mqh

    Теперь вместо того, чтобы создавать панель мониторинга с помощью ObjectCreate в нашем основном файле .mq5, мы сделаем это в нашем файле .mqh. Вы увидите, насколько проще всё станет впоследствии.

    Давайте углубимся в изменения, которые мы внесем в наш файл .mqh:

    1. Нам нужно изменить модификатор строковой переменной Name с публичного на приватный. Name не требуется в нашем основном файле — переменная нужна только в файле .mqh. Сделаем ее приватной. Этого можно добиться так:

      Из:
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      	             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      В: 

      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
         string            Name;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      Мы просто изменили местоположение

      string            Name;
      Это изменило модификатор переменных с публичного на приватный.

    2. Далее добавляем публичный метод с именем CreateDashboard(). Метод будет принимать следующие входные параметры: name (string), xDis (int), yDis (int), xSize (int), ySize (int).

      Сначала добавляем всё это в список членов класса:

      public:
         void              OnEvent(int id, long lparam, double dparam, string sparam);
         void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);

      Теперь давайте определим эту функцию в глобальном пространстве, скопировав код из нашего основного файла следующим образом:

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Name = name;
         //Redraw Chart
         ChartRedraw();
      }
      //+------------------------------------------------------------------+
      


    После этого нам нужно изменить наш файл .mq5:

    #include "GUI_Movable.mqh"
    GUI_Movable Dashboard1;
    GUI_Movable Dashboard2;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       Dashboard1.CreateDashboard("Dashboard1", 100, 100, 200, 200);
       Dashboard2.CreateDashboard("Dashboard2", 100, 350, 200, 200);
    //---
       return(INIT_SUCCEEDED);
      }
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       Dashboard1.OnEvent(id, lparam, dparam, sparam);
       Dashboard2.OnEvent(id, lparam, dparam, sparam);
      }
    //+------------------------------------------------------------------+
    

    Разберем этот код:

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

    Далее мы объявляем два экземпляра класса GUI_Movable - Dashboard1 и Dashboard2. Эти экземпляры представляют две панели мониторинга, которые мы будем создавать и контролировать в нашей программе.

    В функции OnInit(), которая автоматически вызывается при запуске советника, создаем две панели мониторинга, вызывая метод CreateDashboard() в наших двух экземплярах. Передаем имя информационной панели, ее положение и размер (в пикселях) в качестве параметров этому методу. Затем функция возвращает INIT_SUCCEEDED, указывая, что инициализация прошла успешно.

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

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

    Полный код файла .mqh:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
    private:
       int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
       bool              movingState;
       string            Name;
    public:
    		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
       void              OnEvent(int id, long lparam, double dparam, string sparam);
       void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = Name;
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
       //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
       ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
       //Give dashboard's name to the class instance
       Name = name;
       //Redraw Chart
       ChartRedraw();
    }
    //+------------------------------------------------------------------+
    

    Результат: 

    Рис. 3. Две перемещаемые панели на одном графике

    Рис. 3. Две перемещаемые панели на одном графике


    Говоря об иерархии панелей, та, что создана ранее, будет находиться на более низкой позиции по сравнению с панелью, созданной позже.


    Заключение

    Для тех, кто хочет сделать свою уже существующую панель перемещаемой, процесс довольно прост. Изучив раздел "Создание панели с помощью файла .mqh", вы убедитесь, что можете сделать любую панель перемещаемой с помощью нескольких строк кода в существующем советнике/индикаторе. Все, что для этого требуется, — это подключить файл GUI_Movable.mqh и создать экземпляр класса с присвоенным этому экземпляру именем панели. Благодаря этим простым шагам ваша панель управления станет интерактивной и ее можно будет легко перемещать с помощью мыши.


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

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

    Я искренне надеюсь, что эта статья оказалась для вас в той или иной степени полезной.

    Удачного программирования и удачной торговли!


    Перевод с английского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/en/articles/12880

    Прикрепленные файлы |
    MQL5.zip (5.01 KB)
    Может ли Heiken Ashi давать хорошие сигналы в сочетании со скользящими средними? Может ли Heiken Ashi давать хорошие сигналы в сочетании со скользящими средними?
    Комбинации стратегий могут повысить эффективность торговли. Мы можем комбинировать индикаторы и паттерны, чтобы получать дополнительные подтверждения. Скользящие средние помогают нам подтвердить тренд и следовать ему. Это самые известный технический индикатор, что объясняется его простотой и доказанной эффективностью анализа.
    Разработка системы репликации - Моделирование рынка (Часть 15): Появление СИМУЛЯТОРА (V) - СЛУЧАЙНОЕ БЛУЖДАНИЕ Разработка системы репликации - Моделирование рынка (Часть 15): Появление СИМУЛЯТОРА (V) - СЛУЧАЙНОЕ БЛУЖДАНИЕ
    В этой статье мы завершим разработку симулятора для нашей системы. Основной целью здесь будет настройка алгоритма, рассмотренного в предыдущей статье. Этот алгоритм направлен на создание движения СЛУЧАЙНОГО БЛУЖДАНИЯ. Поэтому, для понимания сегодняшнего материала, необходимо понять содержание предыдущих статей. Если вы не следили за развитием симулятора, советую посмотреть эту последовательность с самого начала. В противном случае вы можете запутаться в том, что будет здесь объяснено.
    Разработка системы репликации - Моделирование рынка (Часть 16): Новая система классов Разработка системы репликации - Моделирование рынка (Часть 16): Новая система классов
    Нам нужно лучше организовать свою работу. Код растёт, и если этого не сделать сейчас, потом это станет невозможным. Давайте разделять и властвовать. То, что MQL5 позволяет нам использовать классы, поможет нам в этой задаче, но для этого нам нужно иметь некоторые знания о некоторых моментах, связанных с классами. Наверное, новичков больше всего смущает наследование. В этой статье мы рассмотрим практичным и простым способом, как использовать данные механизмы.
    Популяционные алгоритмы оптимизации: Алгоритм эволюции разума (Mind Evolutionary Computation, MEC) Популяционные алгоритмы оптимизации: Алгоритм эволюции разума (Mind Evolutionary Computation, MEC)
    В данной статье рассматривается алгоритм семейства MEC, называемый простым алгоритмом эволюции разума (Simple MEC, SMEC). Алгоритм отличается красотой заложенной идеи и простотой реализации.