English Deutsch 日本語
preview
Искусство ведения логов (Часть 7): Как отображать логи на графике

Искусство ведения логов (Часть 7): Как отображать логи на графике

MetaTrader 5Примеры |
191 0
joaopedrodev
joaopedrodev

Введение

Есть вещи, которые мы делаем в пылу разработки и которые, честно говоря, даже не планировали превращать в статью. Что-то, что происходит спонтанно, мелкая деталь, возникшая просто для решения надоевшей проблемы. И, знаете, эта история именно такая. Я даже думал: “Нет, это слишком просто, даже делиться не стоит...”. Но, по правде говоря, результат оказался настолько полезным и приятным, что было бы преступлением оставить это при себе.

Раз вы дочитали до этого места, вы, вероятно, уже знакомы с Logify — полноценной библиотекой для управления и хранения логов при разработке советников (Expert Advisors) на MQL5. Этот инструмент создан, чтобы раз и навсегда решить проблемы с ограничениями стандартного логирования MetaTrader 5, обеспечивая разработчикам больший контроль, организованность и профессионализм.

В первой статье этой серии, «Осваиваем работу с логами (Часть 1): Фундаментальные концепции и первые шаги в MQL5», мы сделали первые шаги в создании этой библиотеки. Мы изучили основы, обсудили, почему слепая вера в стандартные логи MetaTrader — это прямой путь к хаосу, и начали формировать надежное, настраиваемое и масштабируемое решение.

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

И тут меня осенило: "А что, если бы эти логи были там, где они действительно нужны? Прямо на графике, перед глазами трейдера, там, где живет и работает робот". И речь не о разбросанных метках, мигающих стрелках или графических объектах, которые скорее загромождают, чем помогают. Я говорю о чем-то гораздо более элегантном, ненавязчивом и функциональном: об использовании старого доброго Comment().

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

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

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


Создание нового обработчика

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

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

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

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

Вот начальный скелет нашего класса со всеми основными объявленными методами — Emit(), Flush() и Close(), — готовыми к следующей реализации:

//+------------------------------------------------------------------+
//|                                         LogifyHandlerComment.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/en/users/joaopedrodev"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "LogifyHandler.mqh"
//+------------------------------------------------------------------+
//| class : CLogifyHandlerComment                                    |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CLogifyHandlerComment                              |
//| Heritage    : CLogifyHandler                                     |
//| Description : Log handler, inserts data into chart comment.      |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyHandlerComment : public CLogifyHandler
  {
public:
                     CLogifyHandlerComment(void);
                    ~CLogifyHandlerComment(void);
   
   virtual void      Emit(MqlLogifyModel &data);         // Processes a log message and sends it to the specified destination
   virtual void      Flush(void);                        // Clears or completes any pending operations
   virtual void      Close(void);                        // Closes the handler and releases any resources
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerComment::CLogifyHandlerComment(void)
  {
   m_name = "comment";
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerComment::~CLogifyHandlerComment(void)
  {
  }
//+------------------------------------------------------------------+
//| Processes a log message and sends it to the specified destination|
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Emit(MqlLogifyModel &data)
  {
  }
//+------------------------------------------------------------------+
//| Clears or completes any pending operations                       |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Flush(void)
  {
  }
//+------------------------------------------------------------------+
//| Closes the handler and releases any resources                    |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Close(void)
  {
  }
//+------------------------------------------------------------------+



Планирование конфигурации: размер, рамка и заголовок

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

Наш обработчик визуальных логов будет предлагать четыре основных параметра конфигурации:

  • size – Определяет, сколько строк лога мы хотим видеть на графике. Другими словами, это размер видимого окна сообщений.
  • frame_style – Стиль рамки, которая окружает лог на графике. Здесь можно выбрать между: none (нет рамки, просто и напрямую), single frame (одинарная рамка) или double frame (двойная рамка).
  • direction – Направление, в котором будут отображаться сообщения: сверху вниз или снизу вверх.
  • title – Заголовок, отображаемый в верхней части рамки. Заголовок, отображаемый в верхней части рамки, например имя вашего советника.

С учетом этих параметров мы создаем структуру с именем MqlLogifyHandleCommentConfig. Она инкапсулирует все эти настройки и используется внутри нашего класса CLogifyHandlerComment. Вот сердце этой конфигурации:

//+------------------------------------------------------------------+
//| ENUMS                                                            |
//+------------------------------------------------------------------+
enum ENUM_LOG_FRAME_STYLE
  {
   LOG_FRAME_STYLE_NONE = 0,           // No rotation
   LOG_FRAME_STYLE_SINGLE,             // Rotate based on date
   LOG_FRAME_STYLE_DOUBLE,             // Rotate based on file size
  };
enum ENUM_LOG_DIRECTION
  {
   LOG_DIRECTION_UP = 0,               // Up
   LOG_DIRECTION_DOWN,                 // Down
  };
//+------------------------------------------------------------------+
//| Struct: MqlLogifyHandleComment                                   |
//+------------------------------------------------------------------+
struct MqlLogifyHandleCommentConfig
  {
   int size;                           // Space in lines that it will occupy
   ENUM_LOG_FRAME_STYLE frame_style;   // Display grid
   ENUM_LOG_DIRECTION direction;       // Direction
   string title;                       // log title
   
   //--- Default constructor
   MqlLogifyHandleCommentConfig(void)
     {
      size = 20;
      frame_style = LOG_FRAME_STYLE_SINGLE;
      direction = LOG_DIRECTION_UP;
      title = "LOGIFY";
     }
   
   //--- Destructor
   ~MqlLogifyHandleCommentConfig(void)
     {
     }

   //--- Validate configuration
   bool ValidateConfig(string &error_message)
     {
      //--- Saves the return value
      bool is_valid = true;
      
      //--- Check if size is greater than 0
      if(size <= 0)
        {
         size = 20;
         error_message = "Size must be greater than 0.";
         is_valid = false;
        }
      
      //--- Check len
      if(StringLen(title) > 40)
        {
         error_message = "Title is too long for frame. Max 40 chars.";
         is_valid = false;
        }
      
      //--- No errors found
      return(is_valid);
     }
  };

Класс CLogifyHandlerComment затем использует эту конфигурацию в качестве частного свойства (m_config). Он также предоставляет два основных метода для работы с конфигурацией: SetConfig(), проверяющий настройки, и GetConfig(), который возвращает текущие настройки.

class CLogifyHandlerComment : public CLogifyHandler
  {
private:
   
   MqlLogifyHandleCommentConfig m_config;
   
public:
                     CLogifyHandlerComment(void);
                    ~CLogifyHandlerComment(void);
   
   //--- Configuration management
   void              SetConfig(MqlLogifyHandleCommentConfig &config);
   MqlLogifyHandleCommentConfig GetConfig(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerComment::CLogifyHandlerComment(void)
  {
   m_name = "comment";
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerComment::~CLogifyHandlerComment(void)
  {
  }
//+------------------------------------------------------------------+
//| Set configuration                                                |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::SetConfig(MqlLogifyHandleCommentConfig &config)
  {
   m_config = config;
   
   //--- Validade config
   string err_msg = "";
   if(!m_config.ValidateConfig(err_msg))
     {
      Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg);
     }
   
   //--- Resize
   ArrayResize(m_logs, m_config.size);
  }
//+------------------------------------------------------------------+
//| Get configuration                                                |
//+------------------------------------------------------------------+
MqlLogifyHandleCommentConfig CLogifyHandlerComment::GetConfig(void)
  {
   return(m_config);
  }
//+------------------------------------------------------------------+


Создание каскадного сдвига

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

Представьте это так: доступное место на графике не бесконечно. Визуально вы можете разместить, скажем, только 10 строк текста, прежде чем они начнут накладываться друг на друга или исчезать с экрана. Поэтому нам нужно гарантировать, что с появлением каждого нового сообщения оно будет занимать первое место в очереди, выталкивая остальные вверх. Если мы уже достигли лимита (например, 10 строк), самое старое сообщение просто отбрасывается.

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

Как это работает на практике? Логика удивительно проста, но чрезвычайно эффективна:

  1. Мы храним массив под названием m_logs[], который работает как наша "визуальная консоль". Этот массив имеет фиксированный размер, например, 10 элементов, то есть 10 строк на графике.
  2. Когда приходит новое сообщение лога, оно должно появиться в начале списка, в позиции 0 массива.
  3. Для этого мы сдвигаем существующие элементы: те, что были в позиции 8, переходят в 9; те, что в 7, переходят в 8; и так далее... пока элемент в позиции 0 не будет сдвинут в позицию 1. 
  4. Как только это сделано, место в позиции 0 освобождается, и туда мы вставляем новое, свежее сообщение, которое будет видно в верхней части экрана.

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

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

Теперь, когда вы отлично понимаете, как работает каскадирование, давайте применим это на практике в методе Emit(). Этот метод вызывается всякий раз, когда нужно обработать и отобразить на графике новое сообщение лога. И он делает именно то, что мы обсуждали: применяет каскадный сдвиг, собирает весь отформатированный текст (включая рамку, заголовок и выравнивание) и в конце выводит все это на график с помощью встроенной функции Comment().

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

Процесс состоит из трех простых шагов:

  1. Фильтрация по уровню: Если сообщение не достигает уровня логирования, установленного в обработчике, оно отбрасывается.
  2. Каскадный сдвиг: Перемещает существующие логи в массиве m_logs[] на одну позицию вперед, освобождая нулевую позицию для нового сообщения. (следуя принципу каскадного сдвига)
  3. Сборка комментария: Использует вспомогательные функции для генерации:
  • Заголовка: Верхняя часть рамки и заголовок (если есть).
  • Тела: Список логов, упорядоченный в соответствии с настроенным направлением.
  • Нижнего колонтитула: Закрытие рамки, если она настроена.

Результат отображается с помощью собственной функции MQL5 Comment(). Для этого используются некоторые вспомогательные функции, а именно:

  • GetSideBorder() → Возвращает символ боковой границы:
    • для одинарной рамки.
    • для двойной рамки.
    • "" (пусто), если рамка отсутствует.
  • GetBorderTop() → Возвращает верхнюю линию рамки:
    • Пример: или
  • GetBorderMiddle() → Возвращает разделитель под заголовком:
    • Пример: ├───────┤ или ╠═══════╣ 
  • GetBorderBottom() → Возвращает нижнюю линию рамки:
    • Пример: Example: └──────┘ или ╚═══════╝ 
  • BuildHeader() → Собирает заголовок с названием (если настроено) и рамкой.
  • BuildFooter() → Генерирует только нижний колонтитул рамки.
  • FormatLogLines() → Форматирует все строки логов:
    • Применяет боковую границу (если она есть).
    • Учитывает направление (LOG_DIRECTION_UP или LOG_DIRECTION_DOWN).

В итоге получаем следующий код.

//+------------------------------------------------------------------+
//| Processes a log message and sends it to the specified destination|
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Emit(MqlLogifyModel &data)
  {
   //--- Check if log level is allowed
   if(data.level < this.GetLevel())
     {
      return;
     }

   //--- Shift logs to maintain history
   for(int i = m_config.size-1; i > 0; i--)
     {
      m_logs[i] = m_logs[i-1];
     }
   m_logs[0] = data;

   //--- Build the complete comment
   string comment = BuildHeader();
   comment += FormatLogLines();
   comment += BuildFooter();

   //--- Display on chart
   Comment(comment);
  }
//+------------------------------------------------------------------+
//| Returns the side border character based on frame style          |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetSideBorder()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE) return "│";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE) return "║";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the top border based on frame style                     |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderTop()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "┌───────────────────────────────────────────┐\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╔═══════════════════════════════════════════╗\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the middle separator based on frame style               |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderMiddle()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "├───────────────────────────────────────────┤\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╠═══════════════════════════════════════════╣\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the bottom border based on frame style                  |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderBottom()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "└───────────────────────────────────────────┘\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╚═══════════════════════════════════════════╝\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Builds the comment header with optional title and frame         |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::BuildHeader()
  {
   string header = "";

   if(m_config.title != "" && m_config.title != NULL)
     {
      if(m_config.frame_style == LOG_FRAME_STYLE_NONE)
        {
         header += " " + m_config.title + "\n";
         header += "─────────────────────────────────────────────\n";
        }
      else
        {
         header += GetBorderTop();
         header += GetSideBorder() + " " + m_config.title + "\n";
         header += GetBorderMiddle();
        }
     }
   else
     {
      if(m_config.frame_style != LOG_FRAME_STYLE_NONE)
        {
         header += GetBorderTop();
        }
     }

   return header;
  }
//+------------------------------------------------------------------+
//| Builds the comment footer based on frame style                  |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::BuildFooter()
  {
   if(m_config.frame_style != LOG_FRAME_STYLE_NONE)
      return GetBorderBottom();
   return "";
  }
//+------------------------------------------------------------------+
//| Formats all log lines according to direction and frame          |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::FormatLogLines()
  {
   string result = "";
   string side = GetSideBorder();

   if(m_config.direction == LOG_DIRECTION_UP)
     {
      for(int i = m_config.size-1; i >= 0; i--)
        {
         string line = m_logs[i].formated;
         if(line != "")
           {
            result += side + " " + line + "\n";
           }
         else
           {
            result += side + "\n";
           }
        }
     }
   else // LOG_DIRECTION_DOWN
     {
      for(int i = 0; i <= m_config.size-1; i++)
        {
         string line = m_logs[i].formated;
         if(line != "")
           {
            result += side + " " + line + "\n";
           }
         else
           {
            result += side + "\n";
           }
        }
     }

   return result;
  }
//+------------------------------------------------------------------+



Очистка лога в конце

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

И знаете, что самое лучшее? Это невероятно просто. В отличие от других обработчиков, которые могут, например, закрывать файлы, соединения или освобождать память, здесь наша задача — просто удалить комментарий с графика. И MetaTrader делает это очень напрямую с помощью самой функции Comment() без аргументов, то есть Comment("").

//+------------------------------------------------------------------+
//| Closes the handler and releases any resources                    |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Close(void)
  {
   //--- Clear
   Comment("");
  }
//+------------------------------------------------------------------+


Тестирование обработчика CLogifyHandlerComment на практике

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

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

//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Handler config
   MqlLogifyHandleCommentConfig m_config;
   m_config.size = 10;                                     // Max log lines
   m_config.frame_style = LOG_FRAME_STYLE_NONE;            // Frame style
   m_config.direction = LOG_DIRECTION_UP;                  // Log direction (up)
   m_config.title = "Expert name";                         // Log panel title
   
   //--- Create and configure handler
   CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment();
   handler_comment.SetConfig(m_config);
   handler_comment.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_comment.SetFormatter(new CLogifyFormatter("hh:mm:ss",
                                                      "{date_time} [{levelname}]: {msg}"));
   
   //--- Add handler to Logify
   logify.AddHandler(handler_comment);
   
   //--- Test logs
   logify.Debug("Initializing Expert Advisor...", "Init", "");
   Sleep(1500);
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   Sleep(800);
   logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   Sleep(800);
   logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678");
   Sleep(500);
   logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance");
   Sleep(100);
   logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Зачем нужен Sleep()? Sleep() не является обязательным, но используется здесь для того, чтобы немного разнести логи во времени, имитируя возникновение событий в разные моменты. А если вам кажется, что текст не передаёт всей картины, взгляните на изображение ниже, чтобы увидеть, как это выглядит на практике:

Давайте посмотрим, как выглядит каждый из других доступных нам фреймов.


На этом наш обработчик CLogifyHandlerComment протестирован, проверен и работает на 100%.


Заключение

Мы подошли к концу этой серии статей о создании Logify — полной, надежной и полностью настраиваемой библиотеки логирования для MQL5. На протяжении этого пути мы изучили всё: от основ систем логирования до структурирования архитектуры, создания обработчиков, настройки форматов и, наконец, добрались до визуального обработчика, который выводит логи прямо на график с помощью функции Comment().

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

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

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

Увидимся в следующей статье.


Название файла
Описание
Experts/Logify/LogiftTest.mq5
Файл, в котором мы тестируем возможности библиотеки, содержащий практический пример
Include/Logify/Formatter/LogifyFormatter.mqh
Класс, отвечающий за форматирование записей лога, заменяющий плейсхолдеры конкретными значениями
Include/Logify/Handlers/LogifyHandler.mqh
Базовый класс для управления обработчиками логов, включая установку уровня и отправку логов
Include/Logify/Handlers/LogifyHandlerComment.mqh
Обработчик логов, который отправляет отформатированные записи непосредственно в комментарий на графике терминала MetaTrader
Include/Logify/Handlers/LogifyHandlerConsole.mqh
Обработчик логов, который отправляет отформатированные записи непосредственно в консоль терминала MetaTrader
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
Обработчик логов, который отправляет отформатированные записи в базу данных (в настоящее время содержит только вывод, но вскоре мы будем сохранять их в реальную базу данных SQLite)
Include/Logify/Handlers/LogifyHandlerFile.mqh
Обработчик логов, который отправляет отформатированные записи в файл
Include/Logify/Utils/IntervalWatcher.mqh
Проверяет, прошел ли заданный интервал времени, позволяя создавать повторяющиеся действия внутри библиотеки
Include/Logify/Logify.mqh
Основной класс для управления логированием, объединяющий уровни, модели и форматирование
Include/Logify/LogifyLevel.mqh
Файл, определяющий уровни логирования библиотеки Logify, позволяющий осуществлять детальный контроль
Include/Logify/LogifyModel.mqh Структура, которая моделирует записи лога, включая такие детали, как уровень, сообщение, временная метка и контекст


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

Прикрепленные файлы |
Logify.zip (21.43 KB)
Разрабатываем мультивалютный советник (Часть 32): Секреты шага создания проекта оптимизации (II) Разрабатываем мультивалютный советник (Часть 32): Секреты шага создания проекта оптимизации (II)
В статье рассматриваются параметры второго этапа конвейера автоматической оптимизации мультивалютного советника. Мы анализируем критерии фильтрации проходов первого этапа и правила формирования групп торговых стратегий. Демонстрируется влияние настроек на результаты оптимизации, обсуждаются аспекты надёжности процесса и баланс между строгостью отбора и достаточностью кандидатов для алгоритма.
Автоматизация индикатора настроений рынка (индикатора сентимента) Автоматизация индикатора настроений рынка (индикатора сентимента)
В этой статье мы автоматизируем создание пользовательского индикатора рыночных настроений, который подразделяет рыночные условия на бычьи, медвежьи, склонные к риску, не склонные к риску и нейтральные. Советник предоставляет информацию о текущих настроениях в режиме реального времени, одновременно упрощая процесс анализа рыночных тенденций и направлений развития рынка.
Создание самооптимизирующихся советников на MQL5 (Часть 7): Одновременная торговля на нескольких периодах Создание самооптимизирующихся советников на MQL5 (Часть 7): Одновременная торговля на нескольких периодах
В этой серии статей мы рассмотрели несколько различных способов определения наилучшего периода для использования наших технических индикаторов. Сегодня мы покажем, как применить противоположную логику, то есть, вместо выбора единственного наиболее подходящего периода, мы покажем, как эффективно использовать все доступные периоды. Такой подход сокращает объем отбрасываемых данных и предлагает альтернативные варианты использования алгоритмов машинного обучения, выходящие за рамки обычного прогнозирования цен.
От начального до среднего уровня: Struct (VII) От начального до среднего уровня: Struct (VII)
В сегодняшней статье мы покажем, как можно подходить к решению проблем по структурированию разных элементов и созданию более простых и привлекательных решений. Хотя содержание ориентировано на обучение и, следовательно, не является настоящим кодом, необходимо очень хорошо усвоить концепции и знания, которые здесь будут рассмотрены. Таким образом, в будущем мы сможем следовать кодам, которые мы покажем.