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
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:
- Entendendo a arquitetura da faixa de cotações com rolagem contínua
- Implementação em MQL5
- Backtesting
- 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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
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
Criação de EAs que se auto-otimizam em MQL5 (Parte 9): Cruzamento duplo de médias móveis
Repensando as estratégias clássicas (Parte 14): Análise de múltiplas estratégias
Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Não está funcionando
Você ao menos leu o artigo?