English Русский 中文 Español Deutsch 日本語
preview
Ferramentas de trading em MQL5 (Parte 5): criação de uma faixa de cotações com rolagem contínua para monitorar símbolos em tempo real

Ferramentas de trading em MQL5 (Parte 5): criação de uma faixa de cotações com rolagem contínua para monitorar símbolos em tempo real

MetaTrader 5 — Negociação |
25 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 4), aprimoramos o painel do scanner multitimeframe em MetaQuotes Language 5 (MQL5), adicionando recursos de posicionamento dinâmico e alternância. Também implementamos janelas móveis e recolhíveis para melhorar a usabilidade. Nesta Parte 5, criaremos uma faixa de cotações com rolagem contínua para monitorar vários instrumentos em tempo real, exibindo preços Bid, spreads e variações percentuais diárias em movimento. O recurso também contará com efeitos visuais personalizáveis, permitindo que os traders acompanhem rapidamente o que acontece no mercado. Abordaremos os seguintes tópicos:

  1. Entendendo a arquitetura da faixa de cotações com rolagem contínua
  2. Implementação em MQL5
  3. Backtesting
  4. Conclusão

Ao final, você terá uma ferramenta versátil de acompanhamento de cotações em MQL5, pronta para ser personalizada e integrada ao seu sistema de trading. Vamos começar!


Entendendo a arquitetura da faixa de cotações com rolagem contínua

A faixa de cotações com rolagem contínua que desenvolveremos permitirá acompanhar dados de vários instrumentos em tempo real, exibindo preços Bid, spreads e variações percentuais diárias em uma sequência contínua de rolagem. Com isso, será possível identificar rapidamente as mudanças nas condições do mercado. Esse recurso é importante porque oferece uma visão compacta e dinâmica dos movimentos do mercado, destacando tendências de preços e volatilidade sem sobrecarregar o gráfico com informações. Essa apresentação facilita a tomada de decisões rápidas em um mercado sujeito a mudanças constantes.

Para implementar essa funcionalidade, dividiremos a área de exibição em linhas independentes de informações com rolagem contínua para os instrumentos, preços, spreads e variações percentuais. Também utilizaremos velocidades de rolagem configuráveis e cores para diferenciar movimentos de alta e de baixa. Os dados dos instrumentos serão armazenados em arrays, enquanto temporizadores garantirão uma rolagem fluida do conteúdo. Dessa forma, a faixa de cotações poderá ser ajustada às preferências do usuário e funcionar de maneira eficiente em segundo plano. Vamos ver como colocar essa ideia em prática! Antes disso, a ilustração abaixo apresenta uma visão geral do resultado que pretendemos alcançar.

TICKER TAPE PLAN


Implementação em MQL5

Para desenvolver o programa em MQL5, começaremos definindo seus metadados e, em seguida, os parâmetros de entrada que permitirão personalizar seu funcionamento sem precisar modificar diretamente o código.

//+------------------------------------------------------------------+
//|                                      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)

Aqui, começamos a implementar nossa faixa de cotações com rolagem contínua para monitorar instrumentos em tempo real no MQL5. Para isso, incluímos a biblioteca "<Arrays\ArrayString.mqh>" e definimos os parâmetros de entrada que permitirão personalizar seu funcionamento. A biblioteca "<Arrays\ArrayString.mqh>" oferece recursos para manipular arrays de strings, necessários para processar a lista de instrumentos a serem exibidos e separá-la em símbolos individuais. O parâmetro de entrada "Symbols" é uma string definida como "EURUSDm, GBPUSDm, USDJPYm, USDCHFm, AUDUSDm, BTCUSDm, TSLAm", que especifica os instrumentos monitorados e permite escolher quais ativos serão exibidos na faixa de cotações. Definimos "UpdateInterval" como 50 milissegundos para estabelecer o intervalo entre as atualizações, buscando equilibrar a rapidez de resposta e o desempenho do programa.

Para personalizar a aparência, definimos "SymbolFontSize" como 10 e "SymbolFont" como "Arial Bold" para a linha de instrumentos; "AskFontSize" como 10 e "AskFont" como "Arial" para a linha de preços Ask; "SpreadFontSize" como 10 e "SpreadFont" como "Calibri" para a linha de spreads; e "SectionFontSize" como 10 e "SectionFont" como "Arial" para a seção que reúne a moeda, o preço Ask e a variação percentual.

Utilizamos "FontColor" com o valor clrWhite para o texto principal, "UpColor" como "clrLime" e "DownColor" como "clrRed" para indicar as variações de preço, além de "ArrowUpColor" como "clrBlue" e "ArrowDownColor" como "clrRed" para as setas direcionais. Os parâmetros de posicionamento e espaçamento incluem "Y_Position" (30 pixels), que define a posição vertical inicial, "SymbolHorizontalSpacing" (160 pixels), "AskHorizontalSpacing" (150 pixels), "SpreadHorizontalSpacing" (200 pixels) e "SectionHorizontalSpacing" (170 pixels), que controlam a disposição dos elementos na tela.

As velocidades de rolagem são configuradas da seguinte forma: "SymbolScrollSpeed" como 3,0 pixels por atualização, "AskScrollSpeed" como 1,3, "SpreadScrollSpeed" como 10,0 e "SectionScrollSpeed" como 2,7, permitindo que cada linha se mova de forma independente. Definimos "ShowSpread" como true para habilitar a exibição da linha de spreads, "BackgroundColor" como "clrBlack" e "BackgroundOpacity" como 100 para definir a opacidade do retângulo de fundo. Esses parâmetros permitem ajustar a aparência e o comportamento da faixa de cotações para facilitar o monitoramento em tempo real. Após a compilação, teremos os seguintes conjuntos de parâmetros de entrada.

INPUT SETS

Com os parâmetros de entrada definidos, podemos passar às variáveis globais utilizadas ao longo do programa e às estruturas para armazenar os dados recorrentes da faixa de cotações.

//--- 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

Nesta etapa, definimos as variáveis globais e uma estrutura para gerenciar os dados dos instrumentos e os elementos do painel. Declaramos "symbolArray" como um array de strings que armazenará os nomes dos instrumentos especificados no parâmetro de entrada "Symbols". A variável inteira "totalSymbols" armazenará a quantidade de instrumentos obtida após a separação da string de entrada. Definimos a estrutura "SymbolData" para armazenar os dados de preço de cada instrumento. Ela inclui "bid" para o preço Bid atual, "ask" para o preço Ask atual, "spread" para o spread calculado, "prev_bid" para o preço Bid anterior, utilizado para detectar variações, "daily_open" para o preço de abertura do dia, "bid_color" para a cor de exibição do preço Bid, "percent_change" para a variação percentual diária, "percent_color" para a cor utilizada na exibição dessa variação, "arrow_char" para os caracteres das setas direcionais e "arrow_color" para a cor dessas setas.

Criamos "prices" como um array de estruturas "SymbolData" para armazenar os dados de todos os instrumentos. A string "dashboardName" recebe o valor "TickerDashboard", que será usado para nomear os objetos do painel, enquanto "backgroundName" recebe "TickerBackground", destinado ao retângulo de fundo. Para facilitar a remoção dos objetos, utilizamos "CArrayString objManager" para gerenciar os nomes de todos os objetos textuais e gráficos. Por fim, a variável "lastDay", do tipo datetime, registrará o último dia processado para controlar a atualização dos dados dos preços de abertura diários. Essas variáveis globais permitem estruturar os dados dos instrumentos e gerenciar os objetos, permitindo atualizações e rolagem eficientes em tempo real. A seguir, definiremos algumas funções auxiliares para construir o painel principal de cotações.

//+------------------------------------------------------------------+
//| 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
}

Implementaremos funções auxiliares para registrar erros, criar rótulos de texto e configurar o painel, garantindo o funcionamento confiável dos elementos da interface do usuário (UI) e facilitando a depuração. Começamos pela função "LogError", que recebe uma string "message" e registra seu conteúdo no log por meio da função Print. Em seguida, criamos a função "createText", responsável por gerar os rótulos de texto exibidos na faixa de cotações. Ela recebe os parâmetros "objName", "text", "x", "y", "clrTxt", "fontsize" e "font".

Primeiro, redefinimos o código do último erro com ResetLastError e verificamos se o objeto já existe usando ObjectFind. Caso não exista, criamos um rótulo do tipo OBJ_LABEL com ObjectCreate. Se a criação falhar, registramos o erro e retornamos false. Para gerenciar os objetos, adicionamos "objName" ao "objManager". Em seguida, configuramos as propriedades com ObjectSetInteger para OBJPROP_XDISTANCE e as demais propriedades de valores inteiros, além de utilizar "ObjectSetString" para definir "OBJPROP_TEXT" e "OBJPROP_FONT". Essa função permitirá manter um padrão visual consistente na exibição dos instrumentos, preços e variações.

Depois, definimos a função "createPanel" para criar o painel de fundo. Ela recebe os parâmetros "objName", "y", "width", "height" e "clr" e segue a mesma estrutura da função "createText". Com isso, podemos personalizar o fundo da faixa de cotações, inclusive criando efeitos visuais relacionados à opacidade por meio da escolha das cores. Agora já podemos avançar para a criação do painel de cotações, mas antes precisamos preparar os dados. Isso inclui dividir a string de instrumentos em símbolos individuais, que poderão ser tratados de forma independente, e inicializar os dados de preços e cores. Faremos isso no manipulador 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);
}

No manipulador OnInit, inicializamos o programa configurando os instrumentos e as estruturas de dados. Começamos dividindo a string de entrada "Symbols" em elementos do array "symbolArray" por meio de StringSplit, usando a vírgula como delimitador e armazenando a quantidade de instrumentos em "totalSymbols". Caso você tenha especificado outro delimitador, basta utilizá-lo aqui. Em seguida, redimensionamos o array "prices" com ArrayResize, utilizando "totalSymbols" para que seu tamanho corresponda à quantidade de instrumentos. Depois, percorremos cada instrumento do array "symbolArray" e utilizamos SymbolSelect para adicioná-lo à janela Observação do Mercado. Se a seleção falhar, registramos o erro por meio da função "LogError" e retornamos "INIT_FAILED".

Para cada instrumento, inicializamos "prices[i].bid", "prices[i].ask", "prices[i].spread" e "prices[i].prev_bid" com o valor 0. Definimos "prices[i].daily_open" com o preço de abertura do dia, obtido pela função iOpen no timeframe "PERIOD_D1". Também atribuímos os valores e as cores iniciais a "prices[i].bid_color", "prices[i].percent_change", "prices[i].percent_color", "prices[i].arrow_char" (utilizando CharToString para representar a seta para cima) e "prices[i].arrow_color". Para facilitar a depuração, exibimos o conteúdo de "symbolArray" e "prices" por meio da função ArrayPrint. Assim, verificamos se todos os instrumentos foram inicializados corretamente e se os dados estão preparados para as atualizações em tempo real. Após a compilação, obtemos o seguinte resultado.

INITIALIZATION OUTPUT

A imagem mostra que todos os instrumentos e as estruturas de armazenamento de dados foram inicializados com sucesso. Com essa etapa concluída, podemos criar o fundo do painel.

//+------------------------------------------------------------------+
//| 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
}

Implementamos a função "CreateBackground" para configurar o painel de fundo da faixa de cotações. Primeiro, obtemos a largura do gráfico com ChartGetInteger, utilizando a propriedade CHART_WIDTH_IN_PIXELS, e convertemos o resultado em um número inteiro, armazenado na variável "width". Em seguida, calculamos a altura do painel na variável "height". Para isso, utilizamos um operador ternário com base em "ShowSpread" para determinar se o painel terá quatro ou três linhas. Multiplicamos essa quantidade pelo maior tamanho de fonte entre "SymbolFontSize", "AskFontSize", "SpreadFontSize" e "SectionFontSize", acrescentando 2 pixels de espaçamento interno por linha e mais 40 pixels de espaço adicional. Por fim, chamamos a função "createPanel", passando "backgroundName", "Y_Position - 5" para ajustar o alinhamento vertical, "width", "height" e "BackgroundColor". Essa chamada desenha o retângulo de fundo e cria uma base visual uniforme para os elementos de texto com rolagem contínua. Ao executar a função após a inicialização, obtemos o seguinte resultado.

PANEL BACKGROUND

Com o fundo criado, podemos adicionar os demais elementos do painel. Para isso, implementamos uma função responsável por posicioná-los, conforme mostrado a seguir.

//+------------------------------------------------------------------+
//| 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
   }
}

Implementamos a função "CreateDashboard" para configurar os elementos visuais da faixa de cotações, incluindo rótulos de texto e imagens para cada instrumento. Começamos percorrendo os "totalSymbols" instrumentos e definindo "imageFile" com base em "symbolArray[i]". Utilizamos condições if-else para selecionar arquivos Bitmap (BMP) específicos para instrumentos como "EURUSDm" e um arquivo padrão para os demais. Em seguida, criamos o rótulo da linha de instrumentos por meio de "createText", utilizando a expressão dashboardName + "Symbol" + IntegerToString(i) para definir o nome do objeto. O rótulo é posicionado nas coordenadas horizontais "(i * SymbolHorizontalSpacing)" e verticais "Y_Position".

Para a linha de preços Ask, criamos outro rótulo com "createText", utilizando a expressão dashboardName + "Ask" + IntegerToString(i). Definimos sua posição horizontal como "(i * AskHorizontalSpacing)" e a vertical como "Y_Position + SymbolFontSize + 2". Se "ShowSpread" for true, adicionamos também um rótulo à linha de spreads por meio de "createText", utilizando a expressão dashboardName + "Spread" + IntegerToString(i) e posicionando-o de acordo com o layout definido.

Para esta seção, criamos um objeto de imagem com ObjectCreate do tipo OBJ_BITMAP_LABEL, caso ele ainda não exista. Em seguida, adicionamos esse objeto ao "objManager", definimos sua posição com "ObjectSetInteger" e atribuímos "imageFile" com "ObjectSetString". Vale observar que os arquivos de imagem precisam estar no formato BMP. Utilizamos o diretório padrão mostrado abaixo, mas você pode usar um diretório próprio.

IMAGE FILES DIR

Depois, criamos o texto da moeda com "createText" para "dashboardName + "Currency" + IntegerToString(i)", formatado com StringFormat. Para a variação percentual, formatamos "percentText" com base em "prices[i].percent_change" e criamos o respectivo texto com "createText". Também adicionamos um rótulo para a seta, novamente com "createText", usando "prices[i].arrow_char" e a fonte "Wingdings". Por fim, criamos o texto do preço Bid com "createText", usando StringFormat para "prices[i].bid". Essa função nos permite montar um layout multilinha para a faixa de cotações, com imagens e texto dinâmico, para exibir os dados atualizados em tempo real com rolagem contínua. Agora basta chamarmos essa função durante a inicialização, e o resultado será o seguinte.

STATIC DASHBOARD

Com isso, obtemos um painel estático. O próximo passo é atualizá-lo. Para as atualizações em tempo real, não devemos depender de eventos baseados em ticks, pois isso faria o funcionamento ficar totalmente atrelado à frequência de ticks do instrumento ao qual o programa está anexado. Em vez disso, é melhor usar atualizações controladas por temporizador, para que elas ocorram com frequência. Primeiro, definiremos as funções responsáveis por atualizar o painel e o fundo sempre que necessário.

//+------------------------------------------------------------------+
//| 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
}

Aqui, implementamos a função "UpdateBackground" para ajustar o painel de fundo quando o gráfico for redimensionado. Obtemos a largura atual do gráfico com ChartGetInteger, usando CHART_WIDTH_IN_PIXELS, e convertemos esse valor para inteiro na variável "width". Em seguida, recalculamos a altura do painel em "height", usando um operador ternário com "ShowSpread" para determinar se haverá 4 ou 3 linhas. Multiplicamos a quantidade de linhas pela soma de 2 pixels de espaçamento interno com o maior tamanho de fonte entre "SymbolFontSize", "AskFontSize", "SpreadFontSize" e "SectionFontSize"; depois, acrescentamos 40 pixels de espaço adicional. Por fim, atualizamos as dimensões do painel com ObjectSetInteger para "OBJPROP_XSIZE" e OBJPROP_YSIZE em "backgroundName".

Na sequência, implementamos a função "UpdateDashboard" para controlar a rolagem e atualizar os objetos textuais e gráficos. Para acompanhar a posição de cada linha, declaramos as variáveis estáticas de deslocamento "symbolOffset", "askOffset", "spreadOffset" e "sectionOffset". Calculamos as larguras totais "totalWidthSymbol", "totalWidthAsk", "totalWidthSpread" e "totalWidthSection", multiplicando "totalSymbols" pelos respectivos espaçamentos horizontais. A borda direita do gráfico é obtida com "ChartGetInteger", usando o valor "CHART_WIDTH_IN_PIXELS". Em seguida, percorremos "totalSymbols", atualizando a posição de cada instrumento com "symbolXPos", ajustada com "symbolOffset". Quando a posição ultrapassa a tela, recalculamos a coordenada horizontal com o operador módulo e chamamos "createText" para atualizar o texto, ocultando-o com "ObjectSetInteger" em "OBJPROP_HIDDEN" quando ele ficar além de "rightEdge".

Fazemos atualizações semelhantes nas linhas de preços Ask, spreads (quando "ShowSpread" estiver habilitado) e nos demais elementos da seção. Isso inclui o reposicionamento das imagens com ObjectSetInteger, utilizando OBJPROP_XDISTANCE e OBJPROP_YDISTANCE, além da atualização dos textos das moedas, das variações percentuais (formatadas com StringFormat), das setas direcionais (utilizando "prices[i].arrow_char") e dos preços Bid. Em seguida, atualizamos os deslocamentos de cada linha de acordo com sua velocidade de rolagem, utilizando fmod para manter o movimento cíclico, e chamamos ChartRedraw para atualizar a exibição do gráfico. Essas funções permitem que a faixa de cotações se adapte às mudanças e mantenha uma rolagem fluida durante o monitoramento em tempo real. Agora podemos utilizar o manipulador OnTimer, Essa etapa é necessária

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

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

Nesta etapa, configuramos o intervalo do temporizador chamando EventSetMillisecondTimer e passando o intervalo de atualização definido. Por fim, inicializamos a variável "lastDay" para identificar a mudança de dia. Com isso, podemos implementar a lógica do temporizador.

//+------------------------------------------------------------------+
//| 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
}

Implementamos o manipulador OnTimer para controlar as atualizações periódicas da faixa de cotações, executadas no intervalo definido por "UpdateInterval". Começamos calculando "currentDay" a partir de TimeCurrent dividido por 86400, obtendo o número do dia com base no tempo decorrido em segundos. O divisor corresponde à quantidade de segundos em um dia: 24 horas * 60 minutos * 60 segundos. Se "currentDay" for maior que "lastDay", percorremos os "totalSymbols" instrumentos e atualizamos "prices[i].daily_open" para cada um deles, utilizando iOpen no timeframe "PERIOD_D1", com deslocamento 0. Em seguida, atribuímos "currentDay" a "lastDay" para registrar o novo dia. Isso garante que o cálculo das variações percentuais diárias seja reiniciado corretamente à meia-noite.

Depois, chamamos "UpdateBackground" para ajustar o painel de fundo caso o tamanho do gráfico tenha mudado. Por fim, executamos "UpdateDashboard" para atualizar todos os objetos textuais e gráficos com os dados atuais e as novas posições de rolagem, mantendo a faixa de cotações dinâmica e atualizada ao longo do tempo. Obtemos o seguinte resultado.

STATIC EFFECTIVE TAPE

A imagem mostra que já temos uma faixa de cotações com rolagem contínua funcionando corretamente. Agora falta implementar a atualização dos preços. Para concluir essa etapa, vamos reunir essa lógica em uma função específica.

//+------------------------------------------------------------------+
//| 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
   }
}

Implementamos a função "UpdatePrices" para atualizar os dados dos instrumentos. Percorremos os "totalSymbols" instrumentos e obtemos os preços Bid e Ask de cada elemento de "symbolArray[i]", utilizando SymbolInfoDouble com as propriedades SYMBOL_BID e SYMBOL_ASK. Se o preço Bid ou Ask for igual a 0, registramos um erro por meio de "LogError" e passamos ao próximo instrumento. Em seguida, atualizamos "bid_color", "arrow_char" (utilizando CharToString para representar as setas de alta ou de baixa) e "arrow_color", conforme o preço Bid atual seja maior, menor ou igual ao valor anterior armazenado em "prev_bid", desconsiderando o valor inicial 0. As setas utilizam os caracteres da fonte Wingdings disponível no MQL5, conforme mostrado abaixo.

MQL5 WINGDINGS

No entanto, você pode utilizar o código do caractere de seta de sua preferência. Em seguida, calculamos "percent_change" com base em "daily_open" e definimos "percent_color" por meio de um operador ternário, conforme a variação seja positiva ou negativa. Por fim, atualizamos "prices[i].bid", "prices[i].ask", "spread" (calculado com MathPow e SymbolInfoInteger, utilizando a propriedade SYMBOL_DIGITS) e "prev_bid". Dessa forma, mantemos os dados necessários à exibição e à detecção das variações sempre atualizados, permitindo que a faixa de cotações reflita os preços e seus indicadores a cada tick. Agora podemos chamar essa função a cada tick para processar as mudanças de preço. Outra opção é executá-la no manipulador OnTimer. A escolha fica a seu critério.

//+------------------------------------------------------------------+
//| 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
}

No manipulador OnTick, chamamos "UpdatePrices" para atualizar os preços Bid e Ask, os spreads e as variações de todos os instrumentos, permitindo que a faixa de cotações reflita rapidamente os movimentos atuais do mercado. Em seguida, implementamos a função OnDeinit para liberar os recursos quando o programa for removido. Percorremos "objManager" em ordem inversa, utilizando Total para determinar a quantidade de objetos e At para obter o "name" de cada um. Verificamos se o objeto existe com ObjectFind e, em caso positivo, removemos esse objeto com ObjectDelete. Se a exclusão falhar, registramos o erro por meio de "LogError". Também removemos o nome correspondente de "objManager" utilizando o método Delete. Por fim, interrompemos o temporizador com EventKillTimer para encerrar as atualizações periódicas. Essa etapa é necessária para garantir a remoção correta de todos os objetos e evitar que elementos residuais permaneçam no gráfico. Ao executar o programa, obtemos o seguinte resultado.

FINAL OUTCOME

A imagem mostra que tudo está funcionando conforme o esperado, indicando que alcançamos nossos objetivos. Resta apenas testar o funcionamento do projeto, como veremos na próxima seção.


Backtesting

Realizamos os testes e apresentamos abaixo uma demonstração visual do programa compilado em um único arquivo GIF (Graphics Interchange Format).

BACKTESTING


Conclusão

Neste artigo, desenvolvemos uma faixa de cotações com rolagem contínua em MQL5 para monitorar instrumentos em tempo real, exibindo preços Bid, spreads e variações diárias. Implementamos opções de personalização de fontes, cores e velocidades de rolagem, permitindo destacar os movimentos do mercado de maneira eficiente. Apresentamos a arquitetura e a implementação da ferramenta, desde estruturas de dados como "SymbolData" até funções como "UpdateDashboard" e "UpdatePrices", responsáveis por manter a rolagem fluida e os dados atualizados com precisão para facilitar a análise das informações de trading. Você pode adaptar essa faixa de cotações às suas necessidades, ampliando significativamente sua capacidade de acompanhar vários instrumentos e reagir às tendências de preços em tempo real.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18844

Arquivos anexados |
Últimos Comentários | Ir para discussão (2)
Edson Kennedy
Edson Kennedy | 22 jul. 2025 em 13:11
Não está funcionando
Allan Munene Mutiiria
Allan Munene Mutiiria | 22 jul. 2025 em 13:12
Edson Kennedy #:
Não está funcionando

Você ao menos leu o artigo?

Desenvolvimento de ferramentas para análise de Price Action (Parte 32): Módulo de reconhecimento de padrões de candles em Python (II): Reconhecimento com TA-Lib Desenvolvimento de ferramentas para análise de Price Action (Parte 32): Módulo de reconhecimento de padrões de candles em Python (II): Reconhecimento com TA-Lib
Neste artigo, deixamos de lado a implementação manual do reconhecimento de padrões de candles em Python e passamos a utilizar a TA-Lib, uma biblioteca capaz de identificar mais de 60 padrões diferentes. Essas formações fornecem informações valiosas sobre possíveis reversões do mercado e a continuação da tendência. Continue a leitura para saber mais.
Criação de EAs que se auto-otimizam em MQL5 (Parte 9): Cruzamento duplo de médias móveis Criação de EAs que se auto-otimizam em MQL5 (Parte 9): Cruzamento duplo de médias móveis
Este artigo apresenta o desenvolvimento de uma estratégia de cruzamento duplo de médias móveis que utiliza sinais de um timeframe superior (D1) para determinar as entradas em um timeframe inferior (M15), enquanto os níveis de stop-loss são calculados com base em um timeframe intermediário (H4), utilizado para a gestão de risco. São introduzidas constantes do sistema, enumerações personalizadas e a lógica dos modos de seguimento de tendência e reversão à média, com ênfase na modularidade e na futura otimização por meio de um algoritmo genético. Essa abordagem oferece condições flexíveis de entrada e saída, buscando reduzir o atraso dos sinais e melhorar o timing das operações ao alinhar as entradas nos timeframes inferiores às tendências dos timeframes superiores.
Repensando as estratégias clássicas (Parte 14): Análise de múltiplas estratégias Repensando as estratégias clássicas (Parte 14): Análise de múltiplas estratégias
Neste artigo, continuamos a construção de um ensemble de estratégias de trading, utilizando o otimizador genético do MT5 para ajustar os parâmetros das estratégias. Hoje, analisaremos os dados em Python para verificar se esse tipo de modelo consegue prever melhor qual estratégia terá o melhor desempenho e qual delas operará com maior precisão, além de avaliar se essa abordagem é mais eficaz do que prever diretamente os retornos. Adianto que os testes do aplicativo com esse modelo estatístico revelaram uma queda acentuada no desempenho. O problema está no otimizador genético: infelizmente, ele favorece estratégias correlacionadas. Por isso, vamos reformular a abordagem, mantendo fixos os pesos dos votos e concentrando a otimização nos parâmetros dos indicadores.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes
O Awesome Oscillator de Bill Williams e o canal Envelopes (Envelopes Channel) são dois indicadores que podem se complementar em um EA desenvolvido em MQL5. Utilizamos o Awesome Oscillator para identificar tendências, enquanto o canal Envelopes ajuda a determinar níveis de suporte e resistência. Como de costume, recorremos ao Assistente MQL5 para desenvolver padrões de sinal e testar o potencial dessa combinação de indicadores.