English Deutsch 日本語
preview
Торговые инструменты на MQL5 (Часть 5): Создание бегущей тикерной строки для мониторинга символов в реальном времени

Торговые инструменты на MQL5 (Часть 5): Создание бегущей тикерной строки для мониторинга символов в реальном времени

MetaTrader 5Трейдинг |
107 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 4) мы улучшили панель сканера по нескольким таймфреймам функциями динамического позиционирования и переключения на MetaQuotes Language 5 (MQL5), реализовав подвижные и сворачиваемые окна для повышения удобства использования. В Части 5 мы создаем бегущую тикерную строку для мониторинга нескольких инструментов в режиме реального времени, в которой отображаются в режиме прокрутки цены Bid, спреды, ежедневные процентные изменения и настраиваемые визуальные эффекты, позволяющие трейдерам моментально быть в курсе событий. В статье рассмотрим следующие темы:

  1. Понимание архитектуры бегущей тикерной строки
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет универсальный инструмент для работы с тикерами на MQL5, готовый к настройке и интеграции в вашу торговую систему. Давайте начнём!


Понимание архитектуры бегущей тикерной строки

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

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

TICKER TAPE PLAN


Реализация средствами MQL5

Чтобы создать программу на MQL5, нам нужно будет определить метаданные программы, а затем определить некоторые входные параметры, которые позволят легко изменять работу программы, не вмешиваясь непосредственно в код.

//+------------------------------------------------------------------+
//|                                      ROLLING TICKER TIMER EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Arrays\ArrayString.mqh> //--- Include ArrayString library for string array operations

//--- Input parameters
input string Symbols = "EURUSDm,GBPUSDm,USDJPYm,USDCHFm,AUDUSDm,BTCUSDm,TSLAm"; // Symbols to display
input int UpdateInterval = 50;                     // Update interval (milliseconds)
input int SymbolFontSize = 10;                     // Symbol font size (first line)
input string SymbolFont = "Arial Bold";            // Symbol font
input int AskFontSize = 10;                        // Ask font size (second line)
input string AskFont = "Arial";                    // Ask font
input int SpreadFontSize = 10;                     // Spread font size (third line)
input string SpreadFont = "Calibri";               // Spread font
input int SectionFontSize = 10;                    // Section currency, bid, and percent change font size
input string SectionFont = "Arial";                // Section currency, bid, and percent change font
input color FontColor = clrWhite;                  // Base font color
input color UpColor = clrLime;                     // Color for price increase (Bid text and positive % change)
input color DownColor = clrRed;                    // Color for price decrease (Bid text and negative % change)
input color ArrowUpColor = clrBlue;                // Color for up arrow
input color ArrowDownColor = clrRed;               // Color for down arrow
input int Y_Position = 30;                         // Starting Y position (pixels)
input int SymbolHorizontalSpacing = 160;           // Horizontal spacing for Symbol line (pixels)
input int AskHorizontalSpacing = 150;              // Horizontal spacing for Ask line (pixels)
input int SpreadHorizontalSpacing = 200;           // Horizontal spacing for Spread line (pixels)
input int SectionHorizontalSpacing = 170;          // Horizontal spacing for Section line (pixels)
input double SymbolScrollSpeed = 3.0;              // Symbol line scroll speed (pixels per update)
input double AskScrollSpeed = 1.3;                 // Ask line scroll speed (pixels per update)
input double SpreadScrollSpeed = 10.0;             // Spread line scroll speed (pixels per update)
input double SectionScrollSpeed = 2.7;             // Section scroll speed (pixels per update)
input bool ShowSpread = true;                      // Show spread line
input color BackgroundColor = clrBlack;            // Background rectangle color
input int BackgroundOpacity = 100;                 // Background opacity (0-255, limited effect)

Здесь мы начинаем реализацию нашей бегущей тикерной строки для мониторинга инструментов в режиме реального времени на MQL5, включая библиотеку "<Arrays\ArrayString.mqh>" и определяя входные параметры для пользовательской настройки. Мы включили "<Arrays\ArrayString.mqh>", чтобы обеспечить эффективные операции с массивами строк, необходимые для обработки и разделения списка отображаемых инструментов. Входной параметр "Symbols" - это строка, установленная на "EURUSDm, GBPUSDm, USDJPYm, USDCHFm, AUDUSDm, BTCUSDm, TSLAm", чтобы указать инструменты для мониторинга, что позволяет настроить, какие активы отображаются в тикере. Мы установили параметр "UpdateInterval" равным 50 миллисекундам для частоты обновления, стремясь к балансу между быстродействием и производительностью.

Для визуальной настройки определяем "SymbolFontSize" как 10, "SymbolFont" как "Arial Bold" для строки инструментов, "AskFontSize" как 10, "AskFont" как "Arial" для строки цен ask, "SpreadFontSize" как 10, "SpreadFont" как "Calibri" для строки спреда, "SectionFontSize" - как 10 и "SectionFont" как "Arial" для раздела с валютой, предложением цены и процентным изменением.

Используем "FontColor" как clrWhite для основного текста, "UpColor" как "clrLime" и "DownColor" как "clrRed" для изменения цены, "ArrowUpColor" как "clrBlue" и "ArrowDownColor" как "clrRed" для стрелок направления. Входные параметры для позиционирования и интервалов включают "Y_Position" (30 пикселей) для начального вертикального размещения, "SymbolHorizontalSpacing" (160 пикселей), "AskHorizontalSpacing" (150 пикселей), "SpreadHorizontalSpacing" (200 пикселей) и "SectionHorizontalSpacing" (170 пикселей) для управления раскладкой.

Скорость прокрутки задается следующим образом: "SymbolScrollSpeed" - 3,0 пикселя за обновление, "AskScrollSpeed" - 1,3, "SpreadScrollSpeed" - 10,0 и "SectionScrollSpeed" - 2,7 для независимого перемещения строк. Устанавливаем для параметра "ShowSpread" значение true, чтобы включить строку спреда, для параметра "BackgroundColor" значение "clrBlack", а для параметра "BackgroundOpacity" значение 100 для фонового прямоугольника. Эти параметры позволяют настроить внешний вид и поведение тикера для оптимального мониторинга в режиме реального времени. После компиляции получаем следующие наборы входных параметров.

INPUT SETS

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

//--- Global variables
string symbolArray[];                              //--- Array to store symbol names
int totalSymbols;                                  //--- Total number of symbols
struct SymbolData                                  //--- Structure to hold symbol price data
{
   double bid;                                     //--- Current bid price
   double ask;                                     //--- Current ask price
   double spread;                                  //--- Current spread
   double prev_bid;                                //--- Previous bid price
   double daily_open;                              //--- Daily opening price
   color bid_color;                                //--- Color for bid price display
   double percent_change;                          //--- Daily percentage change
   color percent_color;                            //--- Color for percentage change
   string arrow_char;                              //--- Arrow character for price direction
   color arrow_color;                              //--- Color for arrow
};
SymbolData prices[];                               //--- Array of symbol data structures
string dashboardName = "TickerDashboard";          //--- Name for dashboard objects
string backgroundName = "TickerBackground";        //--- Name for background object
CArrayString objManager;                           //--- Object manager for text and image objects
datetime lastDay = 0;                              //--- Track last day for daily open update

Здесь мы определяем глобальные переменные и структуру для управления данными по инструментам и элементами панели. Объявляем "symbolArray" как массив строк для хранения имен инструментов из входного параметра "Symbols". Целое число "totalSymbols" будет отслеживать количество инструментов после разделения входной строки. Определяем структуру "SymbolData" для хранения информации о ценах по каждому инструменту, включая "bid" для текущей цены bid, "ask" для текущей цены ask, "spread" для рассчитанного спреда, "prev_bid" для предыдущей цены bid для обнаружения изменений, "daily_open" для дневной цены открытия, "bid_color" - для цвета отображения цены bid, "percent_change" - для дневного процентного сдвига, "percent_color" - для окраски изменения, "arrow_char" - для стрелок направления и "arrow_color" - для цвета стрелок.

Создаем "prices" в виде массива структур "SymbolData" для хранения данных по всем инструментам. Для строки "dashboardName" задано значение "TickerDashboard" для именования объектов информационной панели, а для "backgroundName" - значение "TickerBackground" для фонового прямоугольника. Для упрощения очистки используем "CArrayString objManager" для управления именами всех текстовых и графических объектов. Наконец, параметр "lastDay" в формате datetime будет отслеживать последний день для обновления данных о ежедневных ценах открытия. Эти глобальные переменные организуют данные инструментов и управление объектами, обеспечивая эффективное обновление в реальном времени и прокрутку. Далее определим несколько глобальных служебных функций для создания основной панели тикеров следующим образом.

//+------------------------------------------------------------------+
//| Utility Functions                                                |
//+------------------------------------------------------------------+
void LogError(string message)                      // Log error messages
{
   Print(message);                                 //--- Output message to log
}

//+------------------------------------------------------------------+
//| Create Text Label Function                                       |
//+------------------------------------------------------------------+
bool createText(string objName, string text, int x, int y, color clrTxt, int fontsize, string font)
{
   ResetLastError();                               //--- Clear last error code
   if(ObjectFind(0, objName) < 0)                  //--- Check if object does not exist
   {
      if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) //--- Create text label object
      {
         LogError(__FUNCTION__ + ": Failed to create label: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log creation failure
         return false;                             //--- Return failure
      }
      objManager.Add(objName);                     //--- Add object name to manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x);       //--- Set x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y);       //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner alignment
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text content
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);      //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontsize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font type
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Disable background
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Disable selection
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, 0);          //--- Set z-order
   return true;                                              //--- Return success
}

//+------------------------------------------------------------------+
//| Create Panel Function                                            |
//+------------------------------------------------------------------+
bool createPanel(string objName, int y, int width, int height, color clr)
{
   ResetLastError();                               //--- Clear last error code
   if(ObjectFind(0, objName) < 0)                  //--- Check if panel does not exist
   {
      if(!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) //--- Create rectangle panel
      {
         LogError(__FUNCTION__ + ": Failed to create panel: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log creation failure
         return false;                             //--- Return failure
      }
      objManager.Add(objName);                     //--- Add panel to object manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 0);  //--- Set x-coordinate to 0
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y);  //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width);  //--- Set panel width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set panel height
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clr);  //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_FILL, true);    //--- Enable fill
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);    //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID); //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);      //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);   //--- Enable background drawing
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, -1);    //--- Set z-order behind other objects
   return true;                                         //--- Return success
}

Реализуем служебные функции для обработки регистрации ошибок, создания текстовых меток и настройки панели, обеспечивая надежную работу элементов пользовательского интерфейса (UI) и отладку. Начнём с функции "LogError", которая принимает строку "message" и выводит её в лог с помощью функции Print. Далее создаём функцию "createText" для генерации текстовых меток для отображения бегущей строки. В качестве параметров она принимает "objName", "text", "x", "y", "clrTxt", "fontsize" и "font".

Сбрасываем последнюю ошибку с помощью функции ResetLastError и проверяем, существует ли объект, используя функцию ObjectFind. В противном случае создаём метку с помощью функции ObjectCreate под именем OBJ_LABEL, выводя ошибки и возвращаем значение false. Для управления добавляем "objName" в "objManager", затем устанавливаем свойства с помощью ObjectSetInteger для OBJPROP_XDISTANCE и всех остальных целочисленных свойств, а также с помощью "ObjectSetString" для "OBJPROP_TEXT" и "OBJPROP_FONT". Эта функция обеспечит единообразное отображение текста для инструментов, цен и изменений.

Затем определяем функцию "createPanel" для создания фоновой панели. Она принимает "objName", "y", "width", "height", и "clr" в качестве параметров и использует ту же структуру, что и функция "createText", которая предоставляет настраиваемый фон для бегущей строки, поддерживая эффекты, подобные непрозрачности, с помощью выбора цвета. Теперь можем перейти к созданию панели тикера, но сначала давайте упорядочим необходимые данные, что включает в себя разделение строки инструментов на отдельные независимые инструменты, которые мы можем использовать, и инициализацию данных о ценах и цветах. Мы сделаем это в обработчике OnInit. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Split symbols string into array
   totalSymbols = StringSplit(Symbols, ',', symbolArray); //--- Split input symbols into array
   ArrayResize(prices, totalSymbols);            //--- Resize prices array to match symbol count
   
   //--- Verify symbols exist and initialize data
   for(int i = 0; i < totalSymbols; i++)         //--- Iterate through all symbols
   {
      if(!SymbolSelect(symbolArray[i], true))    //--- Select symbol for market watch
      {
         LogError("OnInit: Symbol " + symbolArray[i] + " not found"); //--- Log symbol not found
         return(INIT_FAILED);                    //--- Return initialization failure
      }
      prices[i].bid = 0;                         //--- Initialize bid price
      prices[i].ask = 0;                         //--- Initialize ask price
      prices[i].spread = 0;                      //--- Initialize spread
      prices[i].prev_bid = 0;                    //--- Initialize previous bid
      prices[i].daily_open = iOpen(symbolArray[i], PERIOD_D1, 0); //--- Set daily opening price
      prices[i].bid_color = FontColor;           //--- Set initial bid color
      prices[i].percent_change = 0;              //--- Initialize percentage change
      prices[i].percent_color = FontColor;       //--- Set initial percent color
      prices[i].arrow_char = CharToString(236);  //--- Set default up arrow
      prices[i].arrow_color = FontColor;         //--- Set initial arrow color
   }
   ArrayPrint(symbolArray);
   ArrayPrint(prices);
}

В обработчике OnInit инициализируем нашу программу, устанавливая инструменты и структуры данных. Начнем с разделения входной строки "Symbols" на "symbolArray" с помощью StringSplit с разделителем-запятой, сохраняя количество инструментов в "totalSymbols". Если вы указали какой-либо другой разделитель, просто используйте его здесь. Затем изменяем размер массива "prices" на "totalSymbols" с помощью функции ArrayResize, чтобы он соответствовал количеству инструментов. Затем перебираем каждый инструмент в массиве "symbolArray", выбирая его для отслеживания рынка с помощью функции SymbolSelect и выводя ошибку с помощью функции "LogError", если это не удается, возвращая "INIT_FAILED".

Для каждого инструмента инициализируем "prices[i].bid", "prices[i].ask", "prices[i].spread" и "prices[i].prev_bid" значением 0, устанавливаем "prices[i].daily_open" равным цене открытия дня с помощью iOpen на "PERIOD_D1" и присваиваем начальные цвета и значения для "prices[i].bid_color", "prices[i].percent_change", "prices[i].percent_color", "prices[i].arrow_char" (используя CharToString для стрелки вверх) и "prices[i].arrow_color". Для отладки выводим значения "symbolArray" и "prices" с помощью функции ArrayPrint. Это гарантирует корректность всех инструментов и подготовку данных для обновления в режиме реального времени. После компиляции получаем следующий результат.

INITIALIZATION OUTPUT

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

//+------------------------------------------------------------------+
//| Create background function                                       |
//+------------------------------------------------------------------+
void CreateBackground()
{
   int width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
   int height = (ShowSpread ? 4 : 3) * (MathMax(MathMax(MathMax(SymbolFontSize, AskFontSize), SpreadFontSize), SectionFontSize) + 2) + 40; //--- Calculate panel height
   createPanel(backgroundName, Y_Position - 5, width, height, BackgroundColor); //--- Create background panel
}

Здесь мы реализуем функцию "CreateBackground" и настраиваем фоновую панель для отображения бегущей строки. Начинаем с получения ширины графика с помощью функции ChartGetInteger, используя CHART_WIDTH_IN_PIXELS, и преобразуем ее в целое число в переменной «width». Вычисляем высоту панели в переменной "height", используя тернарный оператор на "ShowSpread", чтобы определить, имеется 4 строки или 3, умножая на максимальный размер шрифта из переменных "SymbolFontSize", "AskFontSize", "SpreadFontSize" и "SectionFontSize" (плюс 2 на заполнение) и добавляя 40 на дополнительное пространство. Наконец, вызываем функцию "createPanel" с параметрами "backgroundName", "Y_Position - 5" для вертикального выравнивания, "width", "height" и "BackgroundColor" для отрисовки прямоугольника фона, обеспечивая единообразную основу для прокручивания текстовых элементов. При вызове функции после инициализации получаем следующий результат.

PANEL BACKGROUND

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

//+------------------------------------------------------------------+
//| Create dashboard function                                        |
//+------------------------------------------------------------------+
void CreateDashboard()
{
   //--- Create text and image objects for each symbol
   for(int i = 0; i < totalSymbols; i++)         //--- Iterate through all symbols
   {
      // Determine image based on symbol
      string imageFile;                          //--- Variable for image file path
      if(symbolArray[i] == "EURUSDm")            //--- Check for EURUSDm
         imageFile = "\\Images\\euro.bmp";       //--- Set EURUSD image
      else if(symbolArray[i] == "GBPUSDm")       //--- Check for GBPUSDm
         imageFile = "\\Images\\gbpusd.bmp";     //--- Set GBPUSD image
      else if(symbolArray[i] == "USDJPYm")       //--- Check for USDJPYm
         imageFile = "\\Images\\usdjpy.bmp";     //--- Set USDJPY image
      else if(symbolArray[i] == "USDCHFm")       //--- Check for USDCHFm
         imageFile = "\\Images\\usdchf.bmp";     //--- Set USDCHF image
      else if(symbolArray[i] == "AUDUSDm")       //--- Check for AUDUSDm
         imageFile = "\\Images\\audusd.bmp";     //--- Set AUDUSD image
      else if(symbolArray[i] == "BTCUSDm")       //--- Check for BTCUSDm
         imageFile = "\\Images\\btcusd.bmp";     //--- Set BTCUSD image
      else if(symbolArray[i] == "TSLAm")         //--- Check for TSLAm
         imageFile = "\\Images\\tesla.bmp";      //--- Set Tesla image
      else
         imageFile = "\\Images\\euro.bmp";       //--- Set default image
      
      // Symbol line (first line)
      createText(dashboardName + "_Symbol_" + IntegerToString(i), "", (i * SymbolHorizontalSpacing), Y_Position, FontColor, SymbolFontSize, SymbolFont); //--- Create symbol text label
      
      // Ask line (second line)
      createText(dashboardName + "_Ask_" + IntegerToString(i), "", (i * AskHorizontalSpacing), Y_Position + SymbolFontSize + 2, FontColor, AskFontSize, AskFont); //--- Create ask price text label
      
      // Spread line (third line, if enabled)
      if(ShowSpread)                             //--- Check if spread display is enabled
      {
         createText(dashboardName + "_Spread_" + IntegerToString(i), "", (i * SpreadHorizontalSpacing), Y_Position + SymbolFontSize + 2 + AskFontSize + 2, FontColor, SpreadFontSize, SpreadFont); //--- Create spread text label
      }
      
      // Section: Image (left)
      string imageName = dashboardName + "_Image_" + IntegerToString(i); //--- Define image object name
      if(ObjectFind(0, imageName) < 0)           //--- Check if image object does not exist
      {
         if(!ObjectCreate(0, imageName, OBJ_BITMAP_LABEL, 0, 0, 0)) //--- Create image object
         {
            LogError("CreateDashboard: Failed to create image: " + imageName + ", Error: " + IntegerToString(GetLastError())); //--- Log image creation failure
            return;                              //--- Exit function
         }
         objManager.Add(imageName);              //--- Add image to object manager
      }
      ObjectSetInteger(0, imageName, OBJPROP_XDISTANCE, (i * SectionHorizontalSpacing)); //--- Set image x-coordinate
      ObjectSetInteger(0, imageName, OBJPROP_YDISTANCE, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14)); //--- Set image y-coordinate
      ObjectSetString(0, imageName, OBJPROP_BMPFILE, imageFile); //--- Set image file
      ObjectSetInteger(0, imageName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set image corner alignment
      
      // Section: Currency (top, right of image)
      string currencyName = dashboardName + "_Currency_" + IntegerToString(i); //--- Define currency text object name
      createText(currencyName, StringFormat("%-10s", symbolArray[i]), (i * SectionHorizontalSpacing) + 35, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14), FontColor, SectionFontSize, SectionFont); //--- Create currency text label
      
      // Section: Percent Change (next to currency, horizontal)
      string percentChangeName = dashboardName + "_PercentChange_" + IntegerToString(i); //--- Define percent change object name
      string percentText = prices[i].percent_change >= 0 ? StringFormat("+%.2f%%", prices[i].percent_change) : StringFormat("%.2f%%", prices[i].percent_change); //--- Format percent change text
      createText(percentChangeName, percentText, (i * SectionHorizontalSpacing) + 105, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14), prices[i].percent_color, SectionFontSize, SectionFont); //--- Create percent change text label
      
      // Section: Arrow (below currency, right of image, Wingdings)
      string arrowName = dashboardName + "_Arrow_" + IntegerToString(i); //--- Define arrow object name
      createText(arrowName, prices[i].arrow_char, (i * SectionHorizontalSpacing) + 35, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14) + SectionFontSize + 2, prices[i].arrow_color, SectionFontSize, "Wingdings"); //--- Create arrow text label
      
      // Section: Bid Price (next to arrow, horizontal)
      string bidName = dashboardName + "_Bid_" + IntegerToString(i); //--- Define bid price object name
      createText(bidName, StringFormat("%.5f", prices[i].bid), (i * SectionHorizontalSpacing) + 50, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14) + SectionFontSize + 2, prices[i].bid_color, SectionFontSize, SectionFont); //--- Create bid price text label
   }
}

Здесь мы реализуем функцию "CreateDashboard" для настройки визуальных элементов отображения бегущей строки, включая текстовые метки и изображения для каждого инструмента. Начинаем с перебора "totalSymbols" и определения "imageFile" на основе "symbolArray[i]" с условиями if-else, назначая конкретные файлы Bitmap (BMP) для таких инструментов, как "EURUSDm", или по умолчанию для других. Создаем текст строки инструментов с помощью "createText" для "dashboardName + "Symbol" + IntegerToString(i)", расположенного в "(i * SymbolHorizontalSpacing)" и "Y_Position".

Для линии ask создаем другую текстовую метку с помощью "createText" для "dashboardName + 'Ask' + IntegerToString(i)", расположенную в "(i * AskHorizontalSpacing)" и "Y_Position + SymbolFontSize + 2". Если "ShowSpread" равно true, добавляем текст линии спреда с помощью функции "createText" для "dashboardName + "Spread" + IntegerToString(i)", расположенный соответствующим образом.

Для этого раздела создаем объект изображения с помощью ObjectCreate как OBJ_BITMAP_LABEL, если он не существует, добавляем его в "objManager", задаем его положение с помощью "ObjectSetInteger" и присваиваем "imageFile" с помощью "ObjectSetString". Обратите внимание, что вам нужны файлы изображений в формате BMP. Мы использовали каталог по умолчанию следующим образом, но вы можете использовать собственный каталог.

IMAGE FILES DIR

Затем создаем текст валюты с помощью "createText" для "dashboardName + "Currency" + IntegerToString(i)", отформатированный с помощью StringFormat. Для изменения процента форматируем "percentText" на основе "prices[i].percent_change" и создаем текст с помощью "createText". Добавляем метку со стрелкой с надписью "createText", используя "prices[i].arrow_char" и шрифт "Wingdings". Наконец, создаем текст цены bid с помощью "createText", используя "StringFormat" для "prices[i].bid". Эта функция позволит создать многострочный макет бегущей строки с изображениями и динамическим текстом для прокрутки данных в режиме реального времени. Теперь просто вызовем функцию при инициализации, и вот какой результат мы получим.

STATIC DASHBOARD

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

//+------------------------------------------------------------------+
//| Update background function                                       |
//+------------------------------------------------------------------+
void UpdateBackground()
{
   int width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get current chart width
   int height = (ShowSpread ? 4 : 3) * (MathMax(MathMax(MathMax(SymbolFontSize, AskFontSize), SpreadFontSize), SectionFontSize) + 2) + 40; //--- Recalculate panel height
   ObjectSetInteger(0, backgroundName, OBJPROP_XSIZE, width);  //--- Update panel width
   ObjectSetInteger(0, backgroundName, OBJPROP_YSIZE, height); //--- Update panel height
}

//+------------------------------------------------------------------+
//| Update dashboard function                                        |
//+------------------------------------------------------------------+
void UpdateDashboard()
{
   static double symbolOffset = 0;                //--- Track symbol line offset
   static double askOffset = 0;                   //--- Track ask line offset
   static double spreadOffset = 0;                //--- Track spread line offset
   static double sectionOffset = 0;               //--- Track section offset
   int totalWidthSymbol = totalSymbols * SymbolHorizontalSpacing;   //--- Calculate total symbol line width
   int totalWidthAsk = totalSymbols * AskHorizontalSpacing;         //--- Calculate total ask line width
   int totalWidthSpread = totalSymbols * SpreadHorizontalSpacing;   //--- Calculate total spread line width
   int totalWidthSection = totalSymbols * SectionHorizontalSpacing; //--- Calculate total section width
   int rightEdge = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);  //--- Get chart right boundary
   
   //--- Update text and image objects
   for(int i = 0; i < totalSymbols; i++)         //--- Iterate through all symbols
   {
      // Symbol line (first line)
      string symbolName = dashboardName + "_Symbol_" + IntegerToString(i); //--- Define symbol object name
      double symbolXPos = (i * SymbolHorizontalSpacing) - symbolOffset; //--- Calculate symbol x-position
      if(symbolXPos < -SymbolHorizontalSpacing) symbolXPos += totalWidthSymbol; //--- Wrap around if off-screen
      createText(symbolName, StringFormat("%-10s", symbolArray[i]), (int)symbolXPos, Y_Position, FontColor, SymbolFontSize, SymbolFont); //--- Update symbol text
      ObjectSetInteger(0, symbolName, OBJPROP_HIDDEN, symbolXPos > rightEdge || symbolXPos < 0); //--- Hide if off-screen
      
      // Ask line (second line)
      string askName = dashboardName + "_Ask_" + IntegerToString(i); //--- Define ask object name
      double askXPos = (i * AskHorizontalSpacing) - askOffset;       //--- Calculate ask x-position
      if(askXPos < -AskHorizontalSpacing) askXPos += totalWidthAsk;  //--- Wrap around if off-screen
      createText(askName, StringFormat("%.5f", prices[i].ask), (int)askXPos, Y_Position + SymbolFontSize + 2, clrMagenta, AskFontSize, AskFont); //--- Update ask text
      ObjectSetInteger(0, askName, OBJPROP_HIDDEN, askXPos > rightEdge || askXPos < 0); //--- Hide if off-screen
      
      // Spread line (third line)
      if(ShowSpread)                             //--- Check if spread display is enabled
      {
         string spreadName = dashboardName + "_Spread_" + IntegerToString(i);      //--- Define spread object name
         double spreadXPos = (i * SpreadHorizontalSpacing) - spreadOffset;         //--- Calculate spread x-position
         if(spreadXPos < -SpreadHorizontalSpacing) spreadXPos += totalWidthSpread; //--- Wrap around if off-screen
         createText(spreadName, StringFormat("%.1f", prices[i].spread), (int)spreadXPos, Y_Position + SymbolFontSize + 2 + AskFontSize + 2, clrAqua, SpreadFontSize, SpreadFont); //--- Update spread text
         ObjectSetInteger(0, spreadName, OBJPROP_HIDDEN, spreadXPos > rightEdge || spreadXPos < 0); //--- Hide if off-screen
      }
      
      // Section (Image, Currency, Percent Change, Arrow, Bid Price)
      double sectionXPos = (i * SectionHorizontalSpacing) - sectionOffset;          //--- Calculate section x-position
      if(sectionXPos < -SectionHorizontalSpacing) sectionXPos += totalWidthSection; //--- Wrap around if off-screen
      
      // Image (left)
      string imageName = dashboardName + "_Image_" + IntegerToString(i); //--- Define image object name
      ObjectSetInteger(0, imageName, OBJPROP_XDISTANCE, (int)sectionXPos); //--- Update image x-coordinate
      ObjectSetInteger(0, imageName, OBJPROP_YDISTANCE, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14)); //--- Update image y-coordinate
      ObjectSetInteger(0, imageName, OBJPROP_HIDDEN, sectionXPos > rightEdge || sectionXPos < 0); //--- Hide if off-screen
      
      // Currency (top, right of image)
      string currencyName = dashboardName + "_Currency_" + IntegerToString(i); //--- Define currency object name
      createText(currencyName, StringFormat("%-10s", symbolArray[i]), (int)sectionXPos + 35, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14), FontColor, SectionFontSize, "Arial Bold"); //--- Update currency text
      
      // Percent Change (next to currency, horizontal)
      string percentChangeName = dashboardName + "_PercentChange_" + IntegerToString(i); //--- Define percent change object name
      string percentText = prices[i].percent_change >= 0 ? StringFormat("+%.2f%%", prices[i].percent_change) : StringFormat("%.2f%%", prices[i].percent_change); //--- Format percent change
      createText(percentChangeName, percentText, (int)sectionXPos + 105, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14), prices[i].percent_color, SectionFontSize, SectionFont); //--- Update percent change text
      
      // Arrow (below currency, right of image, Wingdings)
      string arrowName = dashboardName + "_Arrow_" + IntegerToString(i); //--- Define arrow object name
      createText(arrowName, prices[i].arrow_char, (int)sectionXPos + 35, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14) + SectionFontSize + 2, prices[i].arrow_color, SectionFontSize, "Wingdings"); //--- Update arrow text
      
      // Bid Price (next to arrow, horizontal)
      string bidName = dashboardName + "_Bid_" + IntegerToString(i); //--- Define bid object name
      createText(bidName, StringFormat("%.5f", prices[i].bid), (int)sectionXPos + 50, Y_Position + (ShowSpread ? SymbolFontSize + 2 + AskFontSize + 2 + SpreadFontSize + 14 : SymbolFontSize + 2 + AskFontSize + 14) + SectionFontSize + 2, prices[i].bid_color, SectionFontSize, SectionFont); //--- Update bid price text
   }
   
   //--- Increment offsets for scrolling effect
   symbolOffset = fmod(symbolOffset + SymbolScrollSpeed, totalWidthSymbol);     //--- Update symbol line offset
   askOffset = fmod(askOffset + AskScrollSpeed, totalWidthAsk);                 //--- Update ask line offset
   spreadOffset = fmod(spreadOffset + SpreadScrollSpeed, totalWidthSpread);     //--- Update spread line offset
   sectionOffset = fmod(sectionOffset + SectionScrollSpeed, totalWidthSection); //--- Update section offset
   
   //--- Redraw chart
   ChartRedraw();                                //--- Refresh chart display
}

Здесь реализуем функцию "UpdateBackground" для настройки фоновой панели при изменении размера графика. Получаем текущую ширину графика с помощью ChartGetInteger, используя CHART_WIDTH_IN_PIXELS, и преобразуем ее в целое число в переменной «width». Пересчитываем высоту панели в "height", используя троичный оператор в "ShowSpread", чтобы определить, имеется ли 4 или 3 строки, умножая на максимальный размер шрифта из "SymbolFontSize", "AskFontSize", "SpreadFontSize" и "SectionFontSize" (плюс 2 для заполнения) и добавляя 40 для дополнительного пространства. Наконец, обновляем размеры панели с помощью ObjectSetInteger для "OBJPROP_XSIZE" и OBJPROP_YSIZE для "backgroundName".

Далее реализуем функцию "UpdateDashboard" для обработки прокрутки и обновлений текстовых и графических объектов. Для отслеживания позиций строк определяем статические смещения "symbolOffset", "askOffset", "spreadOffset" и "sectionOffset". Вычисляем общую ширину "totalWidthSymbol", "totalWidthAsk", "totalWidthSpread" и "totalWidthSection", умножая "totalSymbols" на соответствующие значения интервалов. Правый край графика получаем с помощью "ChartGetInteger", используя значение "CHART_WIDTH_IN_PIXELS". Перебираем "totalSymbols", обновляя положение каждого инструмента с помощью "symbolXPos", скорректированного с помощью "symbolOffset", перенося по модулю, если он находится за пределами экрана, и вызываем "createText" для обновления текста, скрывая его с помощью "ObjectSetInteger" для "OBJPROP_HIDDEN", если он находится за пределами "rightEdge".

Мы делаем аналогичные обновления для ask, spread (если "ShowSpread") и элементов раздела, включая изображения с "ObjectSetInteger" для OBJPROP_XDISTANCE и "OBJPROP_YDISTANCE", валюты, процентного изменения (в формате StringFormat), стрелки (с использованием "prices[i].arrow_char") и текста bid. Увеличиваем смещения с помощью "fmod", используя скорость прокрутки, и вызываем ChartRedraw для обновления отображения. Эти функции гарантируют, что тикер адаптируется к изменениям и плавно прокручивается для мониторинга в режиме реального времени. Затем можем вызвать обработчик OnTimer, но сначала нужно будет установить интервал таймера. Это необходимо.

//--- Set timer
EventSetMillisecondTimer(UpdateInterval);     //--- Set timer for updates

//--- Initialize last day
lastDay = TimeCurrent() / 86400;              //--- Set current day for daily open tracking

Здесь просто устанавливаем интервал таймера, вызвав EventSetMillisecondTimer и передавая определенный интервал обновления, и, наконец, инициализируем переменную last day для отслеживания нового дня. Теперь можем определить логику таймера.

//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
   //--- Check for new day to update daily open
   datetime currentDay = TimeCurrent() / 86400//--- Calculate current day
   if(currentDay > lastDay)                      //--- Check if new day
   {
      for(int i = 0; i < totalSymbols; i++)      //--- Iterate through symbols
      {
         prices[i].daily_open = iOpen(symbolArray[i], PERIOD_D1, 0); //--- Update daily open price
      }
      lastDay = currentDay;                      //--- Update last day
   }
   
   //--- Update background size in case chart is resized
   UpdateBackground();                           //--- Update background dimensions
   
   //--- Update dashboard display
   UpdateDashboard();                            //--- Update dashboard visuals
}

Здесь мы реализуем обработчик OnTimer для управления периодическими обновлениями в нашей бегущей тикерной строке для мониторинга инструментов в режиме реального времени, запускаемыми с интервалом, заданным "UpdateInterval". Начинаем с вычисления "currentDay" как TimeCurrent, деленного на 86400, чтобы получить день в секундах, который равен 1 дню * 24 часам * 60 минутам * 60 секундам. Если значение "currentDay" больше, чем "lastDay", перебираем "totalSymbols" и обновляем "prices[i].daily_open" для каждого инструмента, используя iOpen на "PERIOD_D1" со сдвигом 0, затем устанавливаем для "lastDay" значение "currentDay", чтобы отслеживать новый день. Это гарантирует корректный сброс ежедневных процентных изменений в полночь.

Затем вызываем "UpdateBackground", чтобы настроить фоновую панель при изменении размера графика. Наконец, вызываем "UpdateDashboard", чтобы обновить все текстовые и графические объекты с текущими данными и позициями прокрутки, сохраняя бегущую строку динамичной и реагирующей на изменения во времени. Получаем следующий результат.

STATIC EFFECTIVE TAPE

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

//+------------------------------------------------------------------+
//| Update prices function                                           |
//+------------------------------------------------------------------+
void UpdatePrices()
{
   for(int i = 0; i < totalSymbols; i++)         //--- Iterate through all symbols
   {
      double bid = SymbolInfoDouble(symbolArray[i], SYMBOL_BID); //--- Retrieve current bid price
      double ask = SymbolInfoDouble(symbolArray[i], SYMBOL_ASK); //--- Retrieve current ask price
      
      //--- Validate prices
      if(bid == 0 || ask == 0)                   //--- Check for invalid prices
      {
         LogError("UpdatePrices: Failed to retrieve prices for " + symbolArray[i]); //--- Log price retrieval failure
         continue;                               //--- Skip to next symbol
      }
      
      //--- Update color and arrow based on price change (tick-to-tick for bid and arrow)
      if(bid > prices[i].prev_bid && prices[i].prev_bid != 0) //--- Check if bid increased
      {
         prices[i].bid_color = UpColor;            //--- Set bid color to up color
         prices[i].arrow_char = CharToString(236); //--- Set up arrow character
         prices[i].arrow_color = ArrowUpColor;     //--- Set arrow to up color
      }
      else if(bid < prices[i].prev_bid && prices[i].prev_bid != 0) //--- Check if bid decreased
      {
         prices[i].bid_color = DownColor;          //--- Set bid color to down color
         prices[i].arrow_char = CharToString(238); //--- Set down arrow character
         prices[i].arrow_color = ArrowDownColor;   //--- Set arrow to down color
      }
      else                                         //--- Handle no change or first tick
      {
         prices[i].bid_color = FontColor;          //--- Set bid color to default
         prices[i].arrow_char = CharToString(236); //--- Set default up arrow
         prices[i].arrow_color = FontColor;        //--- Set arrow to default color
      }
      
      //--- Calculate daily percentage change
      prices[i].percent_change = prices[i].daily_open != 0 ? ((bid - prices[i].daily_open) / prices[i].daily_open) * 100 : 0; //--- Compute percentage change
      prices[i].percent_color = prices[i].percent_change >= 0 ? UpColor : DownColor; //--- Set percent color based on change
      
      //--- Update data
      prices[i].bid = bid;                       //--- Store current bid
      prices[i].ask = ask;                       //--- Store current ask
      prices[i].spread = (ask - bid) * MathPow(10, SymbolInfoInteger(symbolArray[i], SYMBOL_DIGITS)); //--- Calculate spread
      prices[i].prev_bid = bid;                  //--- Update previous bid
   }
}

Реализуем функцию "UpdatePrices" для обновления данных об инструментах. Перебираем "totalSymbols" и извлекаем "bid" и "ask" для каждого "symbolArray[i]", используя функцию "SymbolInfoDouble" с SYMBOL_BID и "SYMBOL_ASK". Если "bid" или "ask" равно 0, выводим ошибку с помощью "LogError" и переходим к следующему инструменту. Обновляем "bid_color", "arrow_char" (используя функцию CharToString для стрелок вверх или вниз) и "arrow_color" в зависимости от того, является ли "bid" больше, меньше или равно "prev_bid" (игнорируя начальный 0). Стрелки находятся в структуре Wingdings MQL5 по умолчанию, которая выглядит следующим образом.

MQL5 WINGDINGS

Однако вы можете использовать код со стрелкой, который вам больше нравится. Затем вычисляем "percent_change", используя "daily_open", и устанавливаем "percent_color" с помощью троичного оператора для движения вверх или вниз. Наконец, обновляем "prices[i].bid", "prices[i].ask", "spread" (рассчитывается с помощью MathPow и SymbolInfoInteger для "SYMBOL_DIGITS") и "prev_bid", обеспечивая текущие данные для отображения и изменений, поддерживая цены и индикаторы бегущей строки в актуальном состоянии на каждом тике. Теперь вызываем эту функцию на каждом тике, чтобы обрабатывать изменения цены, или также можно вызвать ее в функции on-timer. Опять же, выбор за вами.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   //--- Update prices on every tick for live changes
   UpdatePrices();                               //--- Update symbol prices
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   //--- Clean up objects
   for(int i = objManager.Total() - 1; i >= 0; i--) //--- Iterate through all managed objects
   {
      string name = objManager.At(i);               //--- Get object name
      if(ObjectFind(0, name) >= 0)                  //--- Check if object exists
      {
         if(!ObjectDelete(0, name))                 //--- Delete object
            LogError("OnDeinit: Failed to delete object: " + name + ", Error: " + IntegerToString(GetLastError())); //--- Log deletion failure
      }
      objManager.Delete(i);                        //--- Remove object from manager
   }
   EventKillTimer();                               //--- Stop timer
}

В обработчике OnTick вызываем "UpdatePrices", чтобы обновить данные о Bid, ask, спредах и изменениях для всех инструментов, гарантируя, что бегущая строка будет оперативно отражать текущие рыночные движения. Далее реализуем функцию OnDeinit для очистки при удалении программы. Выполняем обратный цикл через "objManager", используя "Total", получая "name" каждого объекта с помощью "At". Если объект существует посредством ObjectFind, удаляем его с помощью ObjectDelete, регистрируя сбои с помощью "LogError" в случае неудачи. Удаляем имя из "objManager" с помощью оператора Delete. Наконец, останавливаем таймер с помощью EventKillTimer, чтобы завершить периодические обновления. Это необходимо. Это гарантирует, что все объекты будут правильно очищены, предотвращая появление оставшихся элементов на графике. При запуске программы получаем следующий результат.

FINAL OUTCOME

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


Тестирование на истории

Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

ТЕСТИРОВАНИЕ НА ИСТОРИИ


Заключение

В заключение, мы разработали бегущую тикерную строку на MQL5 для мониторинга инструментов в реальном времени, отображающую цены Bid, спреды и ежедневные изменения с настраиваемыми шрифтами, цветами и скоростью, что позволяет эффективно выделять движения рынка. Мы продемонстрировали архитектуру и реализацию, начиная от структур данных, таких как "SymbolData", и заканчивая функциями, такими как "UpdateDashboard" и "UpdatePrices", обеспечивая плавную прокрутку и точные обновления для эффективного анализа торговых данных. Вы можете настроить эту бегущую строку в соответствии со своими потребностями, что значительно расширит ваши возможности по отслеживанию нескольких инструментов и реагированию на ценовые тенденции в режиме реального времени.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Edson Kennedy
Edson Kennedy | 22 июл. 2025 в 13:11
Не работает
Allan Munene Mutiiria
Allan Munene Mutiiria | 22 июл. 2025 в 13:12
Edson Kennedy #:
Не работает

Вы вообще читали статью?

Трейдинг с экономическим календарем MQL5 (Часть 9): Расширение интерактивности с новостями через динамический скроллбар и улучшенное отображение Трейдинг с экономическим календарем MQL5 (Часть 9): Расширение интерактивности с новостями через динамический скроллбар и улучшенное отображение
В этой статье мы улучшаем экономический календарь MQL5, добавляя динамическую полосу прокрутки для интуитивно понятной навигации по новостям. События будут отображаться плавно, а информация будет обновляться при необходимости. Конечно же, проверим адаптивность полосы прокрутки и качество панели управления во время тестирования.
Нейросети в трейдинге: Адаптивная факторная токенизация (Основные компоненты) Нейросети в трейдинге: Адаптивная факторная токенизация (Основные компоненты)
Продолжаем перенос современных подходов, предложенных авторами фреймворка MTmixATT, на задачи финансовых временных рядов. Представлены практические реализации модулей Multi-Mix Attention и разреженного выбора эксперта, позволяющие структурировать признаки и формировать динамически адаптивных экспертов на основе текущих рыночных данных. Особое внимание уделено оригинальности подхода и его потенциалу для адаптивного структурного анализа рынка.
Пользовательские инструменты отладки и профилирования для разработки на MQL5 (Часть I): Расширенное логирование Пользовательские инструменты отладки и профилирования для разработки на MQL5 (Часть I): Расширенное логирование
Узнайте, как реализовать мощный пользовательский фреймворк для логирования в MQL5, который выходит за рамки простых операторов Print() за счет поддержки уровней серьезности, множества обработчиков вывода и автоматической ротации файлов — и все это с возможностью настройки «на лету». Интегрируйте синглтон CLogger с ConsoleLogHandler и FileLogHandler для захвата контекстных журналов с метками времени как на вкладке «Эксперты», так и в постоянных файлах. Оптимизируйте отладку и трассировку производительности в ваших советниках с помощью понятных, настраиваемых форматов журналов и централизованного управления.
Возможности Мастера MQL5, которые вам нужно знать (Часть 66): Использование паттернов FrAMA и индекса силы с ядром скалярного произведения Возможности Мастера MQL5, которые вам нужно знать (Часть 66): Использование паттернов FrAMA и индекса силы с ядром скалярного произведения
Индикатор FrAMA и осциллятор индекса силы (Force Index) — инструменты анализа тренда и объема, которые можно использовать в паре при разработке советника. В продолжение нашей предыдущей статьи, в которой мы представили эту пару, рассмотрим применимость к ней машинного обучения. Мы используем сверточную нейронную сеть (convolution neural network), которая применяет ядро скалярного произведения (dot-product kernel) для построения прогнозов на основе входных данных этих индикаторов. Это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.