English Русский 中文 Deutsch 日本語
preview
Herramientas de trading de MQL5 (Parte 5): Creación de una cinta de cotizaciones deslizante para el seguimiento en tiempo real de símbolos

Herramientas de trading de MQL5 (Parte 5): Creación de una cinta de cotizaciones deslizante para el seguimiento en tiempo real de símbolos

MetaTrader 5Trading |
87 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior (Parte 4), mejoramos el panel de control del escáner de múltiples marcos temporales con funciones de posicionamiento dinámico y opciones para mostrar u ocultar elementos en MetaQuotes Language 5 (MQL5), lo que permite elementos visuales movibles y minimizables para una mayor facilidad de uso. En la parte 5, creamos una cinta de cotizaciones deslizante para el seguimiento en tiempo real de múltiples símbolos, que muestra los precios Bid, los spreads y las variaciones porcentuales diarias, además de elementos visuales personalizables para que los operadores estén informados rápidamente. Abordaremos los siguientes temas:

  1. Comprender la arquitectura de la cinta de cotizaciones deslizante
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

Al final, tendrás una herramienta de ticker versátil en MQL5 lista para personalizarla e integrarla en tu entorno de trading. ¡Entremos en materia!


Comprender la arquitectura de la cinta de cotizaciones deslizante

La cinta de cotizaciones que estamos creando es una herramienta para mostrar datos en tiempo real de varios símbolos en formato de desplazamiento, en la que se muestran los precio Bid (oferta), los spreads y las variaciones porcentuales diarias, lo que nos permite estar al día de un solo vistazo. Esta función es importante porque ofrece una visión compacta y dinámica de los movimientos del mercado, destacando las tendencias de los precios y la volatilidad sin sobrecargar el gráfico, lo cual es esencial para tomar decisiones rápidas en un entorno de trading dinámico.

Lo conseguiremos estructurando la visualización en líneas de desplazamiento independientes para los símbolos, los precios, los spreads y las variaciones, utilizando velocidades y colores personalizables para indicar los movimientos al alza o a la baja. Tenemos previsto utilizar arrays para los datos de los símbolos y temporizadores para un desplazamiento fluido, lo que garantizará que el ticker se adapte a las preferencias del usuario y, al mismo tiempo, funcione eficientemente en segundo plano. ¡Pasemos a ver cómo lo ponemos en práctica! En pocas palabras, a continuación se muestra una visualización de lo que queremos conseguir.

PLAN «TICKER TAPE»


Implementación en MQL5

Para crear el programa en MQL5, tendremos que definir los metadatos del programa y, a continuación, definir algunas entradas que nos permitan modificar fácilmente el funcionamiento del programa sin interferir directamente en el 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)

Aquí comenzamos a implementar nuestra cinta de cotizaciones deslizante para la supervisión de símbolos en tiempo real en MQL5, incluyendo la biblioteca «<Arrays\ArrayString.mqh>» y definiendo los parámetros de entrada para su personalización. Incluimos «<Arrays\ArrayString.mqh>» para permitir operaciones eficientes con arrays de cadenas, algo esencial para gestionar y dividir la lista de símbolos que se van a mostrar. El campo de entrada «Symbols» es una cadena de caracteres con el valor «EURUSDm, GBPUSDm, USDJPYm, USDCHFm, AUDUSDm, BTCUSDm, TSLAm» que especifica los símbolos que se van a supervisar, lo que nos permite configurar qué activos aparecen en el ticker. Hemos establecido «UpdateInterval» en 50 milisegundos como frecuencia de actualización, buscando un equilibrio entre la capacidad de respuesta y el rendimiento.

Para la personalización visual, definimos «SymbolFontSize» en 10, «SymbolFont» como «Arial Bold» para la línea de símbolos, «AskFontSize» en 10, «AskFont» como «Arial» para la línea del precio Ask (demanda), «SpreadFontSize» en 10, «SpreadFont» como «Calibri» para la línea del spread, «SectionFontSize» en 10 y «SectionFont» como «Arial» para la sección del símbolo, Bid y variación porcentual.

Establecemos «FontColor» como clrWhite para el texto base, «UpColor» como «clrLime» y «DownColor» como «clrRed» para los cambios de precio, y «ArrowUpColor» como «clrBlue» y «ArrowDownColor» como «clrRed» para las flechas de dirección. Los parámetros de posicionamiento y espaciado incluyen «Y_Position» con un valor de 30 píxeles para la posición vertical inicial, «SymbolHorizontalSpacing» con un valor de 160 píxeles, «AskHorizontalSpacing» con un valor de 150 píxeles, «SpreadHorizontalSpacing» con un valor de 200 píxeles y «SectionHorizontalSpacing» con un valor de 170 píxeles para controlar la maquetación.

Las velocidades de desplazamiento se configuran con «SymbolScrollSpeed» a 3,0 píxeles por actualización, «AskScrollSpeed» a 1,3, «SpreadScrollSpeed» a 10,0 y «SectionScrollSpeed» a 2,7 para un movimiento independiente de las líneas. Establecemos «ShowSpread» en «true» para activar la línea del spread, «BackgroundColor» en «clrBlack» y «BackgroundOpacity» en 100 para el rectángulo de fondo. Estos parámetros de entrada nos permitirán personalizar el aspecto, el funcionamiento y el contenido del ticker para lograr un seguimiento en tiempo real óptimo. Tras la compilación, disponemos de los siguientes conjuntos de datos de entrada.

CONJUNTOS DE ENTRADAS

Una vez definidas las entradas, podemos continuar definiendo algunas variables globales y estructuras que utilizaremos a lo largo del programa y que servirán para almacenar información recurrente para la cinta de cotizaciones, respectivamente.

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

Aquí definimos variables globales y una estructura para gestionar los datos de los símbolos y los elementos del panel de control. Declaramos «symbolArray» como un array de cadenas para almacenar los nombres de los símbolos de la entrada «Symbols». El entero «totalSymbols» registrará el número de símbolos tras dividir la cadena de entrada. Definimos la estructura «SymbolData» para almacenar la información de precios de cada símbolo, incluyendo «bid» para el precio de compra actual, «ask» para el precio Ask (demanda) actual, «spread» para el spread calculado, «prev_bid» para la oferta (precio Bid) anterior con el fin de detectar cambios, «daily_open» para el precio de apertura del día, «bid_color» para aplicar un color a la visualización del precio de oferta (Bid), «percent_change» para la variación porcentual diaria, «percent_color» para aplicar un color a la variación, «arrow_char» para las flechas de dirección y «arrow_color» para aplicar un color a las flechas.

Creamos «prices» como una matriz de «SymbolData» estructuras para almacenar los datos de todos los símbolos. La cadena «dashboardName» se establece en «TickerDashboard» para nombrar los objetos del panel de control, y «backgroundName» en «TickerBackground» para el rectángulo de fondo. Utilizamos «CArrayString objManager» para gestionar todos los nombres de los objetos de texto e imagen, lo que facilita su limpieza. Por último, «lastDay», como fecha y hora, registrará el último día para actualizar las aperturas diarias. Estas variables globales organizan los datos de los símbolos y la gestión de objetos, lo que permite actualizaciones y desplazamientos eficientes en tiempo real. A continuación, definiremos algunas funciones de utilidad globales para crear el panel principal de cotizaciones, tal y como se muestra a continuación.

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

Implementamos funciones de utilidad para gestionar el registro de errores, la creación de etiquetas de texto y la configuración de paneles, lo que garantiza la fiabilidad de los elementos de la interfaz de usuario (UI) y la depuración. Empezamos con la función «LogError», que toma una cadena de texto «message» y la envía al registro mediante la función Print. A continuación, creamos la función «createText» para generar etiquetas de texto para la visualización del teletipo. Acepta como parámetros «objName», «text», «x», «y», «clrTxt», «fontsize» y «font».

Borramos el último error con ResetLastError y comprobamos si el objeto existe mediante la función ObjectFind. Si no es así, creamos una etiqueta con la función ObjectCreate como OBJ_LABEL, registramos los errores y devolvemos «false». Añadimos «objName» a «objManager» para su gestión y, a continuación, configuramos las propiedades mediante ObjectSetInteger para OBJPROP_XDISTANCE y todas las demás propiedades enteras, y mediante «ObjectSetString» para «OBJPROP_TEXT» y «OBJPROP_FONT». Esta función garantizará una visualización coherente del texto para símbolos, precios y variaciones.

A continuación, definimos la función «createPanel» para crear el panel de fondo. Toma como parámetros «objName», «y», «width», «height» y «clr», y utiliza la misma estructura que la función «createText», que proporciona un fondo personalizable para el ticker y permite crear efectos similares a la opacidad mediante la elección del color. Ya podemos pasar a la creación del panel del ticker, pero primero vamos a organizar los datos necesarios, lo que implica dividir la cadena de símbolos en símbolos independientes que podamos utilizar e inicializar los datos de precios y colores. Lo haremos en el controlador de eventos 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);
}

En el controlador de eventos OnInit, inicializamos nuestro programa, configurando símbolos y estructuras de datos. Empezamos dividiendo la cadena de entrada «Symbols» en «symbolArray» mediante la función StringSplit con una coma como delimitador, y almacenamos el número de símbolos en «totalSymbols». Si hubieras definido cualquier otro delimitador, utilízalo aquí. A continuación, ajustamos el tamaño de «prices» a «totalSymbols» utilizando ArrayResize para que se adapte al número de símbolos. A continuación, recorremos cada símbolo de «symbolArray», lo seleccionamos en la ventana Market Watch con SymbolSelect y, si falla, registramos un error con «LogError» y devolvemos «INIT_FAILED».

Para cada símbolo, inicializamos «prices[i].bid», «prices[i].ask», «prices[i].spread», y «prices[i].prev_bid» a 0, establecemos «prices[i].daily_open» en el precio de apertura diario utilizando iOpen en «PERIOD_D1», y asignamos colores y valores iniciales a «prices[i].bid_color», «prices[i].percent_change», «prices[i].percent_color», «prices[i].arrow_char» (utilizando CharToString para una flecha hacia arriba) y «prices[i].arrow_color». Imprimimos «symbolArray» y «prices» con la función ArrayPrint para depurar el código. De este modo se garantiza que todos los símbolos sean válidos y que los datos estén preparados para las actualizaciones en tiempo real. Tras la compilación, obtenemos el siguiente resultado.

RESULTADO DE LA INICIALIZACIÓN

En la imagen podemos ver que hemos inicializado correctamente todos los símbolos y estructuras de datos, lo que significa que ya está todo listo. Ahora podemos crear el fondo del panel de control.

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

Aquí implementamos la función «CreateBackground» y configuramos el panel de fondo para la visualización del ticker. Empezamos obteniendo el ancho del gráfico con la función ChartGetInteger, utilizando CHART_WIDTH_IN_PIXELS, y convirtiéndolo a un entero en «width». Calculamos la altura del panel en «height» utilizando un operador ternario sobre «ShowSpread» para determinar si hay 4 o 3 líneas, multiplicando por el tamaño máximo de fuente de «SymbolFontSize», «AskFontSize», «SpreadFontSize» y «SectionFontSize» (más 2 para el relleno) y sumando 40 para el espacio adicional. Por último, llamamos a la función «createPanel» con «backgroundName», «Y_Position - 5» para la alineación vertical, «width», «height» y «BackgroundColor» para dibujar el rectángulo de fondo, lo que proporciona una base uniforme para los elementos de texto que se desplazan. Cuando llamamos a la función durante la inicialización, obtenemos el siguiente resultado.

FONDO DEL PANEL

Una vez creado el fondo, podemos continuar creando los demás elementos del panel de control. Creamos una función para albergar todo como se muestra a continuación.

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

Aquí implementamos la función «CreateDashboard» para configurar los elementos visuales del ticker, incluidas las etiquetas de texto y las imágenes correspondientes a cada símbolo. Empezamos recorriendo el bucle de «totalSymbols» y determinando «imageFile» en función de «symbolArray[i]» mediante condiciones «if-else», asignando archivos Bitmap (BMP) específicos a símbolos como «EURUSDm» o un valor por defecto para los demás. Creamos el texto de la línea del símbolo con «createText» para «dashboardName + «Symbol» + IntegerToString(i)», situado en «(i * SymbolHorizontalSpacing)» y «Y_Position».

Para la línea «Ask», creamos otra etiqueta de texto con «createText» para «dashboardName + “Ask” + IntegerToString(i)», situada en «(i * AskHorizontalSpacing)» y «Y_Position + SymbolFontSize + 2». Si «ShowSpread» es «true», añadimos una línea de texto con «createText» que indica «dashboardName + «Spread» + IntegerToString(i)», colocada en la posición correspondiente.

Para esta sección, creamos un objeto de imagen con la función ObjectCreate como OBJ_BITMAP_LABEL; si no existe, lo añadimos a «objManager», establecemos su posición con «ObjectSetInteger» y le asignamos «imageFile» mediante la función «ObjectSetString». Ten en cuenta que necesitas los archivos de imagen en formato BMP. Hemos utilizado el directorio predeterminado tal y como se indica a continuación, pero puedes utilizar el que prefieras.

DIRECTORIO DE ARCHIVOS DE IMAGEN

A continuación, creamos el texto de la divisa con «createText» utilizando la expresión «dashboardName + «Currency» + IntegerToString(i)», formateada con la función StringFormat. Para calcular la variación porcentual, damos formato a «percentText» basándonos en «prices[i].percent_change» y generamos el texto con «createText». Añadimos una etiqueta de flecha con el texto «createText» utilizando «prices[i].arrow_char» y la fuente «Wingdings». Por último, generamos el texto del precio de compra con «createText», utilizando «StringFormat» para «prices[i].bid». Esta función creará un diseño de ticker de varias líneas con imágenes y texto dinámico para mostrar datos en tiempo real que se desplazan. Ahora, basta con llamar a la función durante la inicialización, y este es el resultado que obtenemos.

PANEL DE CONTROL ESTÁTICO

Lo que obtenemos es un panel de control estático. El siguiente paso es actualizar el panel de control. Para las actualizaciones en tiempo real, no queremos depender de actualizaciones basadas en ticks, ya que eso dependería por completo de la frecuencia de los ticks del símbolo al que está vinculado el programa. Lo que queremos hacer es utilizar actualizaciones basadas en temporizadores para que las actualizaciones se realicen con frecuencia. Primero, definamos las funciones para actualizar el panel de control y el fondo cuando sea necesario.

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

Aquí implementamos la función «UpdateBackground» para ajustar el panel de fondo cuando se cambia el tamaño del gráfico. Recuperamos el ancho actual del gráfico con la función ChartGetInteger utilizando CHART_WIDTH_IN_PIXELS y lo convertimos a un entero en «width». Recalculamos la altura del panel en «height» utilizando un operador ternario sobre «ShowSpread» para determinar si hay 4 o 3 líneas, multiplicando por el tamaño máximo de fuente de «SymbolFontSize», «AskFontSize», «SpreadFontSize» y «SectionFontSize» (más 2 para el relleno) y sumando 40 para el espacio adicional. Por último, actualizamos las dimensiones del panel con ObjectSetInteger para «OBJPROP_XSIZE» y OBJPROP_YSIZE en «backgroundName».

A continuación, implementamos la función «UpdateDashboard» para gestionar el desplazamiento y las actualizaciones de los objetos de texto e imagen. Definimos los desplazamientos estáticos «symbolOffset», «askOffset», «spreadOffset» y «sectionOffset» para realizar un seguimiento de las posiciones de las líneas. Calculamos las anchuras totales «totalWidthSymbol», «totalWidthAsk», «totalWidthSpread» y «totalWidthSection» multiplicando «totalSymbols» por los valores de espaciado correspondientes. Obtenemos el borde derecho del gráfico con «ChartGetInteger» utilizando «CHART_WIDTH_IN_PIXELS». Recorremos «totalSymbols», actualizando la posición de cada símbolo con «symbolXPos» ajustada mediante «symbolOffset», aplicando el módulo si está fuera de visualización, y llamamos a «createText» para actualizar el texto, ocultándolo con «ObjectSetInteger» para «OBJPROP_HIDDEN» si está fuera de «rightEdge».

Realizamos actualizaciones similares para los elementos «ask», «spread» (si se ha activado «ShowSpread») y «section», incluyendo imágenes con «ObjectSetInteger» para OBJPROP_XDISTANCE y «OBJPROP_YDISTANCE», la divisa, el porcentaje de variación (formateado con StringFormat), la flecha (utilizando «prices[i].arrow_char») y el texto del precio de compra (Bid). Incrementamos los desplazamientos con «fmod» utilizando las velocidades de desplazamiento y llamamos a ChartRedraw para actualizar la visualización. Estas funciones garantizarán que el ticker se adapte a los cambios y se desplace con fluidez para permitir un seguimiento en tiempo real. A continuación, podemos llamar al controlador de eventos OnTimer, pero antes tendremos que configurar el intervalo del temporizador. Esto es importante.

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

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

Aquí, simplemente configuramos el intervalo del temporizador llamando a la función EventSetMillisecondTimer y pasándole el intervalo de actualización definido; por último, inicializamos la variable del último día para el seguimiento del nuevo día. Ahora podemos definir la lógica del 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
}

Aquí implementamos el controlador de eventos OnTimer para gestionar las actualizaciones periódicas en nuestra cinta de cotizaciones deslizante para la supervisión de símbolos en tiempo real, que se activa en el intervalo establecido por «UpdateInterval». Empezamos calculando «currentDay» como TimeCurrent dividido entre 86 400 para obtener el día en segundos, que equivale a 1 día * 24 horas * 60 minutos * 60 segundos. Si «currentDay» es mayor que «lastDay», recorremos «totalSymbols» y actualizamos «prices[i].daily_open» para cada símbolo utilizando iOpen en «PERIOD_D1» con desplazamiento 0; a continuación, establecemos «lastDay» en «currentDay» para hacer un seguimiento del nuevo día. Esto garantiza que las variaciones porcentuales diarias se reinicien correctamente a medianoche.

A continuación, llamamos a «UpdateBackground» para ajustar el panel de fondo si se cambia el tamaño del gráfico. Por último, llamamos a «UpdateDashboard» para actualizar todos los objetos de texto e imagen con los datos actuales y las posiciones de desplazamiento, de modo que el ticker se mantenga dinámico y se adapte a los cambios que se producen con el paso del tiempo. Obtenemos el siguiente resultado.

CINTA ESTÁTICA EFICAZ

En la imagen podemos ver que tenemos una cinta que se está moviendo. Ahora tenemos que actualizar los precios, y con eso ya está todo. Pongamos también esa lógica de actualización en una función.

//+------------------------------------------------------------------+
//| 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 la función «UpdatePrices» para actualizar los datos de los símbolos. Recorremos «totalSymbols» y obtenemos los valores de «bid» y «ask» para cada «symbolArray[i]» utilizando la función «SymbolInfoDouble» con SYMBOL_BID y «SYMBOL_ASK». Si «bid» o «ask» es 0, registramos un error con «LogError» y pasamos al siguiente símbolo. Actualizamos «bid_color», «arrow_char» (utilizando la función CharToString para las flechas arriba y abajo) y «arrow_color» en función de si «bid» es mayor, menor o igual que «prev_bid» (sin tener en cuenta el 0 inicial). Las flechas siguen la estructura predeterminada de Wingdings en MQL5, que es la siguiente.

MQL5 WINGDINGS

No obstante, puedes usar el código de flecha que más te guste. A continuación, calculamos «percent_change» utilizando «daily_open» y establecemos «percent_color» mediante un operador ternario para indicar si el valor ha subido o bajado. Por último, actualizamos «prices[i].bid», «prices[i].ask» y «spread» (calculados con MathPow y SymbolInfoInteger para «SYMBOL_DIGITS») y «prev_bid», garantizando que los datos que se muestran y los cambios sean actuales, y manteniendo actualizados los precios y los indicadores del ticker en cada tick. Ahora llamamos a la función en cada tick para procesar los cambios de precio, aunque también se podría llamar desde la función «on-timer». La elección vuelve a quedar en tus manos.

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

En el controlador de eventos OnTick, llamamos a «UpdatePrices» para actualizar los datos de oferta, demanda, spread y variación de todos los símbolos, lo que garantiza que el ticker refleje con rapidez los movimientos del mercado en tiempo real. A continuación, implementamos la función OnDeinit para realizar la limpieza cuando se elimine el programa. Recorremos «objManager» hacia atrás utilizando «Total» y obtenemos el «nombre» de cada objeto con «At». Si el objeto existe mediante ObjectFind, lo eliminamos con la función ObjectDelete y, en caso de fallo, registramos el error con «LogError». Eliminamos el nombre de «objManager» con el operador Delete. Por último, detenemos el temporizador con EventKillTimer para poner fin a las actualizaciones periódicas. Esto es importante. De este modo se garantiza que todos los objetos se eliminen correctamente, evitando que queden elementos residuales en el gráfico. Al ejecutar el programa, obtenemos el siguiente resultado.

RESULTADO FINAL

En la visualización podemos ver que todo funciona según lo previsto, por lo que estamos alcanzando nuestros objetivos. Ahora solo queda comprobar el funcionamiento del proyecto, y eso se aborda en la siguiente sección.


Backtesting

Hemos realizado las pruebas y, a continuación, se muestra la visualización compilada como una imagen GIF (Graphics Interchange Format).

BACKTESTING


Conclusión

En conclusión, hemos desarrollado un Ticker Tape en MQL5 para el seguimiento en tiempo real de múltiples símbolos, que incluye líneas en desplazamiento con los precio Bid (oferta), los spreads y las variaciones diarias, con fuentes, colores y velocidades personalizables para resaltar eficazmente los movimientos del mercado. Hemos mostrado la arquitectura y la implementación, desde estructuras de datos como «SymbolData» hasta funciones como «UpdateDashboard» y «UpdatePrices», lo que garantiza un desplazamiento fluido y actualizaciones precisas para obtener información útil sobre las operaciones. Puedes personalizar este ticker para adaptarlo a tus necesidades, lo que te permitirá mejorar considerablemente tu capacidad para realizar un seguimiento de múltiples símbolos y reaccionar ante las tendencias de los precios en tiempo real.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18844

Archivos adjuntos |
Edson Kennedy
Edson Kennedy | 22 jul 2025 en 13:11
No funciona
Allan Munene Mutiiria
Allan Munene Mutiiria | 22 jul 2025 en 13:12
Edson Kennedy #:
No funciona

¿Te has molestado siquiera en leer el artículo?

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles
Este artículo describe el diseño de una estrategia de cruce de medias móviles dobles que utiliza señales de un marco temporal superior (D1) para orientar las entradas en un marco temporal inferior (M15), calculándose los niveles de stop-loss a partir de un marco temporal de riesgo intermedio (H4). Introduce constantes del sistema, enumeraciones personalizadas y la lógica para los modos de seguimiento de tendencias y de retorno a la media, al tiempo que hace hincapié en la modularidad y en la optimización futura mediante un algoritmo genético. Este enfoque permite establecer condiciones flexibles de entrada y salida, con el objetivo de reducir el retraso de la señal y mejorar la sincronización de las operaciones, alineando las entradas en los marcos temporales más cortos con las tendencias de los marcos temporales más largos.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Características del Wizard MQL5 que debe conocer (Parte 75): Uso del Awesome Oscillator y Envelopes Características del Wizard MQL5 que debe conocer (Parte 75): Uso del Awesome Oscillator y Envelopes
El «Awesome Oscillator» de Bill Williams y el canal de envolventes son una combinación que podría utilizarse de forma complementaria dentro de un asesor experto de MQL5. Utilizamos el Awesome Oscillator por su capacidad para detectar tendencias, mientras que el canal de envolventes se incorpora para definir nuestros niveles de soporte y resistencia. Al explorar esta combinación de indicadores, utilizamos el asistente MQL5 para construir y probar cualquier potencial que estos dos puedan tener.