English Русский 中文 Deutsch 日本語
preview
Creación de interfaces gráficas dinámicas MQL5 mediante el escalado de imágenes basado en recursos con interpolación bicúbica en gráficos de trading

Creación de interfaces gráficas dinámicas MQL5 mediante el escalado de imágenes basado en recursos con interpolación bicúbica en gráficos de trading

MetaTrader 5Trading |
35 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

Personalizar los gráficos bursátiles con elementos visuales dinámicos puede mejorar nuestra forma de analizar los mercados, pero para conseguir una representación de imágenes de alta calidad es necesario un programación bien pensada en MetaQuotes Language 5 (MQL5). En este artículo presentamos una potente herramienta que permite crear interfaces gráficas dinámicas, aprovechando el escalado de imágenes basado en recursos con interpolación bicúbica para obtener gráficos nítidos y adaptables. Exploraremos el proceso a través de estos pasos:

  1. Descripción general de los gráficos de imágenes dinámicos basados en recursos
  2. Implementación en MQL5
  3. Pruebas y validación
  4. Conclusión

Al final, tendrás una herramienta sólida para transformar tus gráficos comerciales con gráficos de imágenes profesionales controlados por el usuario.


Descripción general de los gráficos de imágenes dinámicos basados en recursos

Nuestro objetivo es crear una herramienta MQL5 que incruste y escale imágenes en los gráficos de MetaTrader 5, creando interfaces gráficas dinámicas y controladas por el usuario. Cargaremos una imagen bitmap como recurso, la escalaremos para ajustarla a las dimensiones del gráfico utilizando interpolación bicúbica y la posicionaremos en función de las entradas del usuario, como el anclaje a las esquinas o el centrado dinámico. Esto nos permitirá superponer elementos visuales personalizados (logotipos o patrones) mientras mantenemos las relaciones de aspecto y alternamos la visualización entre el fondo y el primer plano, todo optimizado para un rendimiento en tiempo real. De esta manera, los gráficos serán más interesantes y atractivos.

Para escalar, utilizaremos interpolación bicúbica sobre el vecino más cercano y métodos bilineales. El vecino más cercano produce resultados pixelados y desenfoca los detalles. Aquí hay una visualización detallada de por qué elegimos la interpolación bicúbica en lugar de los otros métodos.

La visualización gráfica.

GRÁFICOS

Visualización de pixelación resultante.

PIXELACIÓN

Bicúbico, aprovechando un vecindario de píxeles 4x4 y polinomios cúbicos, garantiza gradientes más suaves y bordes más nítidos. Elegimos bicúbico por su claridad y eficiencia superiores, ideal para cambiar el tamaño de forma dinámica en distintos tamaños de gráficos, ofreciendo imágenes nítidas que mejoran las decisiones comerciales. A continuación se muestra un ejemplo de lo que pretendemos, utilizando una imagen MQL5 personalizada.

RESUMEN DE LA IMAGEN BICÚBICA MQL5


Implementación en MQL5

Para crear el programa en MQL5, primero necesitaremos tener las imágenes en el formato correcto, que es Bitmap (BMP), que son imágenes de mapa de bits sin comprimir y con alta resolución, en comparación con el formato Joint Photographic Experts Group (JPEG). Por lo tanto, si tienes una imagen relevante que necesitas utilizar y está en cualquier otro formato, puedes convertirla utilizando recursos gratuitos en línea. En cuanto a nosotros, estas son las imágenes que utilizaremos, ya que las tenemos preparadas.

Archivo de mapa de bits

Después de obtener los archivos en los formatos respectivos, debemos moverlos a la carpeta de imágenes. Puede simplemente abrir el Navegador, ubicar la carpeta Imágenes, hacer clic derecho sobre ella y seleccionar "Abrir carpeta". Esto abrirá la carpeta predeterminada con dos archivos de imágenes en dólares y euros de forma predeterminada. Puedes pegar tus archivos de imagen allí. A continuación se muestra una ilustración visual.

CARPETA DE IMÁGENES EN MQL5

Una vez hecho esto, ya estamos listos para empezar. Lo primero que haremos será agregar la imagen como recurso.

//+------------------------------------------------------------------+
//|                   Image Resource Cubic Interpolation Scaling.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"

#resource "\\Images\\mql5-circuit.bmp" //--- Include the image file as a resource
#define ORIGINAL_IMAGE_RESOURCE "::Images\\mql5-circuit.bmp" //--- Define the resource path for the original image
#define CHART_IMAGE_OBJECT_NAME "ChartImage" //--- Define the name of the chart image object

Aquí establecemos las bases para incrustar y mostrar nuestra imagen en los gráficos. Utilizamos la directiva #resource para incorporar un archivo de mapa de bits ubicado en «\Images\mql5-circuit.bmp» como recurso, asegurándonos de que esté fácilmente disponible para que nuestro programa pueda acceder a él.

A continuación, definimos la macro «ORIGINAL_IMAGE_RESOURCE», establecida en «::Images\mql5-circuit.bmp», para crear una referencia estandarizada a la ruta del recurso de la imagen, que utilizaremos para cargar los datos de la imagen. Además, definimos la macro «CHART_IMAGE_OBJECT_NAME» como «ChartImage», que utilizaremos como identificador único para el objeto gráfico que muestra la imagen, lo que nos permitirá gestionarlo controlando su apariencia en el gráfico.

Lo siguiente que necesitaremos es definir algunas variables globales y entradas que utilizaremos a lo largo del programa.

// Enum for selecting anchor corner
enum ENUM_ANCHOR_CORNER {
   TOP_LEFT = 0,     // Top-Left
   TOP_RIGHT = 1,    // Top-Right
   BOTTOM_LEFT = 2,  // Bottom-Left
   BOTTOM_RIGHT = 3  // Bottom-Right
};

// Input parameters for user customization
input bool LimitToOriginalSize = true; // Image scaling limited to original size
input bool ImageInBackground = true; // Image displayed in background (true) or foreground (false)
input bool CenterImageDynamically = true; // Image centered dynamically (true) or positioned manually (false)
input ENUM_ANCHOR_CORNER AnchorCorner = TOP_LEFT; // Anchor corner for manual positioning
input int XOffsetFromCorner = 100; // x-offset in pixels from the chosen corner
input int YOffsetFromCorner = 100; // y-offset in pixels from the chosen corner

// Counter for generating unique resource names for scaled images
int scaled_resource_counter = 0; //--- Initialize a counter for creating unique resource names

Para configurar los controles de usuario para la colocación de imágenes, definimos la «ENUM_ANCHOR_CORNER» enumeración con las opciones «TOP_LEFT», «TOP_RIGHT», «BOTTOM_LEFT» y «BOTTOM_RIGHT» para seleccionar una esquina del gráfico.

Establecemos los parámetros de entrada: « LimitToOriginalSize» (verdadero) para limitar el escalado, «ImageInBackground» (verdadero) para la visualización del fondo/primer plano, «CenterImageDynamically» (verdadero) para el posicionamiento automático o manual, «AnchorCorner» (TOP_LEFT) para la selección de la esquina y «XOffsetFromCorner» e «YOffsetFromCorner» (100 píxeles) para los desplazamientos manuales. También inicializamos «scaled_resource_counter» a 0 para nombres de imágenes escaladas únicos.

Para mostrar la imagen en el gráfico, utilizaremos una función personalizada que albergará toda la lógica.

//+------------------------------------------------------------------+
//| Display the image on the chart                                   |
//+------------------------------------------------------------------+
bool DisplayImageOnChart() {
   // Load the original image from the resource
   uint image_pixels[]; //--- Declare an array to store image pixel data
   uint original_image_width, original_image_height; //--- Declare variables for original image dimensions
   
   if (!ResourceReadImage(ORIGINAL_IMAGE_RESOURCE, image_pixels, original_image_width, original_image_height)) { //--- Read the image resource into the pixel array
      Print("Error: Failed to read original image data from resource."); //--- Log an error if reading the image fails
      return false; //--- Return false to indicate failure
   }
   
   // Get chart dimensions
   int chart_pixel_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Retrieve the chart width in pixels
   int chart_pixel_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Retrieve the chart height in pixels
   
   // Calculate scaled dimensions while preserving aspect ratio
   double image_aspect_ratio = (double)original_image_width / original_image_height; //--- Calculate the aspect ratio of the original image
   double chart_aspect_ratio = (double)chart_pixel_width / chart_pixel_height; //--- Calculate the aspect ratio of the chart
   int scaled_image_width, scaled_image_height; //--- Declare variables for scaled image dimensions
   
   if (image_aspect_ratio > chart_aspect_ratio) { //--- Check if the image is wider relative to the chart
      scaled_image_width = chart_pixel_width; //--- Set scaled width to match chart width
      scaled_image_height = (int)(chart_pixel_width / image_aspect_ratio); //--- Calculate scaled height to maintain aspect ratio
   } else {
      scaled_image_height = chart_pixel_height; //--- Set scaled height to match chart height
      scaled_image_width = (int)(chart_pixel_height * image_aspect_ratio); //--- Calculate scaled width to maintain aspect ratio
   }
   
   // Limit scaling to original size if enabled
   if (LimitToOriginalSize) { //--- Check if the user has enabled limiting to original size
      scaled_image_width = MathMin(scaled_image_width, (int)original_image_width); //--- Restrict width to original width
      scaled_image_height = MathMin(scaled_image_height, (int)original_image_height); //--- Restrict height to original height
      // Recalculate one dimension to maintain aspect ratio
      if (scaled_image_width < scaled_image_height * image_aspect_ratio) { //--- Check if width is the limiting factor
         scaled_image_height = (int)(scaled_image_width / image_aspect_ratio); //--- Adjust height to maintain aspect ratio
      } else {
         scaled_image_width = (int)(scaled_image_height * image_aspect_ratio); //--- Adjust width to maintain aspect ratio
      }
   }
   
   // Log dimensions for debugging
   PrintFormat(
      "Original: %dx%d, Chart: %dx%d, Scaled: %dx%d",
      original_image_width, original_image_height,
      chart_pixel_width, chart_pixel_height,
      scaled_image_width, scaled_image_height
   ); //--- Log the original, chart, and scaled dimensions for debugging

   return true;

}

Comenzamos definiendo la función «DisplayImageOnChart», que utilizaremos para representar nuestra imagen en el gráfico. Comenzamos declarando la matriz «image_pixels» para almacenar los datos de píxeles de la imagen y las variables «original_image_width» y «original_image_height» para almacenar las dimensiones de la imagen original. Mediante la función ResourceReadImage, cargamos los datos de la imagen desde la macro «ORIGINAL_IMAGE_RESOURCE» en la matriz «image_pixels», capturando su anchura y altura. Si esta operación falla, registramos un mensaje de error con la función Print y devolvemos false para indicar el fallo, deteniendo el procesamiento posterior.

A continuación, recuperamos las dimensiones del gráfico para asegurarnos de que la imagen se ajuste correctamente. Utilizamos la función ChartGetInteger para obtener el ancho y el alto del gráfico, y los almacenamos en «chart_pixel_width» y «chart_pixel_height» después de convertirlos a números enteros. Para mantener las proporciones de la imagen durante el escalado, calculamos la "image_aspect_ratio" dividiendo "original_image_width" por "original_image_height" y la "chart_aspect_ratio" dividiendo "chart_pixel_width" por "chart_pixel_height". Estas proporciones guiarán nuestra lógica de escalamiento.

Luego declaramos "scaled_image_width" y "scaled_image_height" para almacenar las dimensiones de la imagen escalada. Para preservar la relación de aspecto, comparamos la "image_aspect_ratio" con la "chart_aspect_ratio". Si la imagen es más ancha en relación con el gráfico (es decir, "image_aspect_ratio" excede "chart_aspect_ratio"), establecemos "scaled_image_width" en "chart_pixel_width" y calculamos "scaled_image_height" dividiendo "chart_pixel_width" por "image_aspect_ratio". De lo contrario, establecemos "scaled_image_height" en "chart_pixel_height" y calculamos "scaled_image_width" multiplicando "chart_pixel_height" por "image_aspect_ratio". Esto garantiza que la imagen se ajuste al gráfico sin distorsión.

Si la entrada "LimitToOriginalSize" es verdadera, restringimos la escala a las dimensiones de la imagen original para evitar el aumento de escala. Nosotros usamos elMatemáticasMinfunción para limitar "scaled_image_width" a "original_image_width" y "scaled_image_height" a "original_image_height". Para mantener la relación de aspecto después de esta restricción, verificamos si "scaled_image_width" es menor que "scaled_image_height" multiplicado por "image_aspect_ratio". Si es así, recalculamos "scaled_image_height" dividiendo "scaled_image_width" por "image_aspect_ratio"; de lo contrario, recalculamos "scaled_image_width" multiplicando "scaled_image_height" por "image_aspect_ratio".

Por último, registramos las dimensiones originales, del gráfico y escaladas utilizando la función PrintFormat. Usaremos esto para rastrear cambios dinámicos en el gráfico como se muestra a continuación.

CAMBIOS EN EL GRÁFICO

Desde la imagen podemos ver que ya leímos el gráfico y las dimensiones de la imagen. Lo que necesitamos hacer ahora es escalar nuestra imagen dinámicamente para que encaje en el gráfico. Necesitaremos una función personalizada para ayudar a lograrlo.

//+------------------------------------------------------------------+
//| Scale the image using bicubic interpolation                      |
//+------------------------------------------------------------------+
void ScaleImage(
   uint &pixels[], int original_width, int original_height,
   int new_width, int new_height
) {
   uint scaled_pixels[]; //--- Declare an array for scaled pixel data
   ArrayResize(scaled_pixels, new_width * new_height); //--- Resize the array to fit the scaled image
   
   for (int y = 0; y < new_height; y++) { //--- Iterate over each row of the scaled image
      for (int x = 0; x < new_width; x++) { //--- Iterate over each column of the scaled image
         // Map to original image coordinates
         double original_x = (double)x * original_width / new_width; //--- Calculate the corresponding x-coordinate in the original image
         double original_y = (double)y * original_height / new_height; //--- Calculate the corresponding y-coordinate in the original image
         
         // Apply bicubic interpolation
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate the pixel value
         scaled_pixels[y * new_width + x] = pixel; //--- Store the interpolated pixel in the scaled array
      }
   }
   
   ArrayResize(pixels, new_width * new_height); //--- Resize the original pixel array to the new dimensions
   ArrayCopy(pixels, scaled_pixels); //--- Copy the scaled pixels back to the original array
}

Aquí, definimos la función "ScaleImage" para cambiar el tamaño de una imagen utilizando interpolación bicúbica. Declaramos la matriz «scaled_pixels» y utilizamos la función ArrayResize para ajustar su tamaño a «new_width» * «new_height» píxeles. Recorremos cada píxel, asignando coordenadas a la imagen original con «original_x» y «original_y».

Para cada uno, llamamos a la función «BicubicInterpolate», que explicaremos más adelante, para calcular el valor del píxel, almacenándolo en «scaled_pixels» en «y» * «new_width» + «x». Por último, cambiamos el tamaño de «pixels» con ArrayResize y copiamos «scaled_pixels» en él utilizando ArrayCopy para su posterior procesamiento en el gráfico.

Aquí está la función responsable de calcular el valor del píxel a través de la interpolación bicúbica. Primero definamos una función para calcular la interpolación de un solo elemento de color, luego podemos usarla para calcular la interpolación de píxeles individuales.

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single color component       |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   // Calculate cubic interpolation weights for x
   double weights_x[4]; //--- Declare an array for x interpolation weights
   double t = fractional_x; //--- Store the fractional x value
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t);        //--- Calculate weight for x-1
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1);         //--- Calculate weight for x
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);    //--- Calculate weight for x+1
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t);             //--- Calculate weight for x+2
   
   // Interpolate in x for each y
   double y_values[4]; //--- Declare an array for intermediate y values
   for (int j = 0; j < 4; j++) { //--- Iterate over rows of the neighborhood
      y_values[j] =
         weights_x[0] * components[j * 4 + 0] +
         weights_x[1] * components[j * 4 + 1] +
         weights_x[2] * components[j * 4 + 2] +
         weights_x[3] * components[j * 4 + 3]; //--- Perform interpolation in x for each y
   }
   
   // Calculate cubic interpolation weights for y
   double weights_y[4]; //--- Declare an array for y interpolation weights
   t = fractional_y; //--- Store the fractional y value
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate weight for y-1
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate weight for y
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate weight for y+1
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate weight for y+2
   
   // Interpolate in y
   double result =
      weights_y[0] * y_values[0] +
      weights_y[1] * y_values[1] +
      weights_y[2] * y_values[2] +
      weights_y[3] * y_values[3]; //--- Perform interpolation in y to get the final value
   
   // Clamp the result to valid color range [0, 255]
   return MathMax(0, MathMin(255, result)); //--- Clamp the interpolated value to the valid color range
}

Aquí, implementamos la función "BicubicInterpolateComponent" para realizar una interpolación bicúbica para un solo componente de color en nuestro proceso de escalado de imágenes. Declaramos la matriz "weights_x" para almacenar los pesos de interpolación para el eje x y establecemos "t" en "fractional_x", la parte fraccionaria de la coordenada x.

Calculamos cuatro pesos en "weights_x" utilizando fórmulas polinomiales cúbicas para las posiciones x-1, x, x+1 y x+2, lo que permite una interpolación suave. A continuación, declaramos la matriz «y_values» array para almacenar los resultados intermedios y iteramos sobre cuatro filas de un vecindario de píxeles de 4x4. Para cada fila «j», calculamos «y_values[j]» multiplicando los valores «weights_x» por los componentes de color correspondientes de la matriz «components» en los índices «j * 4 + 0» a «j * 4 + 3», realizando una interpolación del eje x.

A continuación, declaramos la matriz «weights_y» y establecemos «t» en «fractional_y» para los pesos del eje y, calculándolos con las mismas fórmulas cúbicas para y-1, y, y+1 y y+2. Interpolamos en el eje y calculando el "result" como la suma ponderada de "y_values" utilizando "weights_y". Finalmente, fijamos "result" al rango de color válido [0, 255] usando MathMax y MathMin funciones que garantizan que la salida sea adecuada para la representación del color de los píxeles. También necesitaremos componentes del canal de color para cada píxel, así que tengamos una función para realizar la extracción del canal de color.

//+------------------------------------------------------------------+
//| Extract ARGB components from a pixel                             |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF); //--- Extract the alpha channel from the pixel
   red = (uchar)((pixel >> 16) & 0xFF);   //--- Extract the red channel from the pixel
   green = (uchar)((pixel >> 8) & 0xFF);  //--- Extract the green channel from the pixel
   blue = (uchar)(pixel & 0xFF);          //--- Extract the blue channel from the pixel
}

Implementamos la función «GetArgb» para extraer componentes de color ARGB individuales de un píxel específico. Tomamos el valor de "pixel" y hacemos referencia a las variables "alpha", "red", "green" y "blue" para almacenar los componentes extraídos. Mediante operaciones bit a bit, aislamos cada componente: desplazamos «pixel» 24 bits a la derecha y lo enmascaramos con «0xFF» para extraer «alpha»; lo desplazamos 16 bits y lo enmascaramos para «red»; lo desplazamos 8 bits y lo enmascaramos para «green»; y enmascaramos los 8 bits más bajos para «blue». Cada resultado se convierte a uchar, lo que garantiza que los valores se ajusten al rango de 0 a 255 para la representación del color.

Utilizamos el sistema «0xFF» para garantizar que enmascara el byte deseado (8 bits) de un píxel entero sin signo de 32 bits, que representa un color en formato ARGB, lo que garantiza la extracción precisa de los valores alfa, rojo, verde o azul. Sin «& 0xFF», obtendríamos resultados inesperados porque podrían permanecer los bits más altos de otros canales. Si te estás preguntando qué son los caracteres, solo estamos hablando de 255 en formato hexadecimal. Echa un vistazo aquí.

0xFF EN HEXADECIMAL

Armados con estas funciones, ahora podemos definir una función para realizar la interpolación bicúbica de un solo píxel para escalar la imagen con precisión.

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single pixel                 |
//+------------------------------------------------------------------+
uint BicubicInterpolate(
   uint &pixels[], int width, int height,
   double x, double y
) {
   // Get integer and fractional parts
   int x0 = (int)x; //--- Extract the integer part of the x-coordinate
   int y0 = (int)y; //--- Extract the integer part of the y-coordinate
   double fractional_x = x - x0; //--- Calculate the fractional part of the x-coordinate
   double fractional_y = y - y0; //--- Calculate the fractional part of the y-coordinate
   
   // Define 4x4 neighborhood
   int x_indices[4], y_indices[4]; //--- Declare arrays for x and y indices
   for (int i = -1; i <= 2; i++) { //--- Iterate over the 4x4 neighborhood
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Calculate clamped x-index
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Calculate clamped y-index
   }
   
   // Get 16 pixels in the 4x4 neighborhood
   uint neighborhood_pixels[16]; //--- Declare an array for the 4x4 neighborhood pixels
   for (int j = 0; j < 4; j++) { //--- Iterate over rows of the neighborhood
      for (int i = 0; i < 4; i++) { //--- Iterate over columns of the neighborhood
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store the pixel value
      }
   }
   
   // Extract ARGB components
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare arrays for ARGB components
   for (int i = 0; i < 16; i++) { //--- Iterate over the neighborhood pixels
      GetArgb(
         neighborhood_pixels[i],
         alpha_components[i], red_components[i],
         green_components[i], blue_components[i]
      ); //--- Extract ARGB components for each pixel
   }
   
   // Perform bicubic interpolation for each component
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate the alpha component
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate the red component
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate the green component
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate the blue component
   
   // Combine components into a single pixel
   return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine ARGB components into a single pixel value
}

Aquí, implementamos la función "BicubicInterpolate" para calcular el color de un solo píxel utilizando el proceso de interpolación bicúbica. Comenzamos extrayendo las partes enteras de las coordenadas de entrada "x" e "y" en "x0" e "y0", y calculamos sus partes fraccionarias como "fractional_x" y "fractional_y". Definimos las matrices "x_indices" e "y_indices" para almacenar las coordenadas de un vecindario de 4x4, iterando de -1 a 2 para calcular índices alrededor de "x0" e "y0". Utilizando MathMin y MathMax, los limitamos a los rangos válidos [0, "width"-1] and [0, "height"-1].

Luego creamos la matriz "neighborhood_pixels" para contener 16 píxeles del vecindario de 4x4, y la rellenamos iterando sobre "y_indices" y "x_indices" para obtener los valores de píxeles de la matriz "pixels" en "y_indices[j] * ancho + x_indices[i]". A continuación, declaramos las matrices "alpha_components", "red_components", "green_components" y "blue_components" y usamos la función "GetArgb" para extraer valores ARGB para cada uno de los 16 píxeles en "neighborhood_pixels".

Para cada componente de color, llamamos a la función "BicubicInterpolateComponent" con "fractional_x" y "fractional_y", almacenando los resultados en "alpha_out", "red_out", "green_out" y "blue_out" como valores "uchar". Finalmente, los combinamos en un único valor de píxel mediante desplazamientos bit a bit: "alpha_out" desplazado 24 bits, "red_out" 16 bits, "green_out" 8 bits y "blue_out", devolviendo el "uint" resultante para usar en la imagen escalada. Ahora podemos llamar a la función "ScaleImage" para que tenga efecto.

// Scale the image using bicubic interpolation
ScaleImage(image_pixels, original_image_width, original_image_height, scaled_image_width, scaled_image_height); //--- Scale the image to the calculated dimensions

Después de llamar a la función, ahora podemos crear un nuevo recurso con la imagen escalada.

// Create a unique resource name for the scaled image
string scaled_resource_name = "::ScaledImage" + IntegerToString(scaled_resource_counter++); //--- Generate a unique resource name using the counter

// Create a new resource with the scaled image
if (!ResourceCreate(
   scaled_resource_name, image_pixels, scaled_image_width, scaled_image_height,
   0, 0, scaled_image_width, COLOR_FORMAT_ARGB_NORMALIZE
)) { //--- Create a new resource for the scaled image
   Print("Error: Failed to create resource for scaled image: ", scaled_resource_name); //--- Log an error if resource creation fails
   return false; //--- Return false to indicate failure
}

Para generar y almacenar una imagen escalada como recurso, creamos un nombre de recurso único definiendo la cadena «scaled_resource_name», concatenando «::ScaledImage» con el resultado de la función IntegerToString aplicada a «scaled_resource_counter», que luego incrementamos para garantizar la unicidad de los recursos posteriores.

A continuación, utilizamos la función ResourceCreate para crear un nuevo recurso con «scaled_resource_name», utilizando la matriz «image_pixels», «scaled_image_width», "scaled_image_height" y especificando desplazamientos de 0, un ancho de "scaled_image_width" y el formato COLOR_FORMAT_ARGB_NORMALIZE. Si «ResourceCreate» falla, registramos un error con la función Print, incluyendo «scaled_resource_name», y devolvemos false para indicar el fallo, deteniendo el procesamiento posterior.

Si pasamos por aquí, solo tenemos que posicionar y visualizar la imagen en el gráfico, así que primero definamos las coordenadas de posicionamiento.

// Determine image position based on user input
int x_offset, y_offset; //--- Declare variables for x and y offsets
if (CenterImageDynamically) { //--- Check if the user wants to center the image dynamically
   x_offset = (chart_pixel_width - scaled_image_width) / 2; //--- Calculate horizontal offset to center the image
   y_offset = (chart_pixel_height - scaled_image_height) / 2; //--- Calculate vertical offset to center the image
} else {
   // Set base position based on chosen anchor corner
   switch (AnchorCorner) { //--- Select the anchor corner based on user input
      case TOP_LEFT: //--- Handle Top-Left corner
         x_offset = XOffsetFromCorner; //--- Use user-defined x-offset from top-left
         y_offset = YOffsetFromCorner; //--- Use user-defined y-offset from top-left
         break;
      case TOP_RIGHT: //--- Handle Top-Right corner
         x_offset = chart_pixel_width - scaled_image_width - XOffsetFromCorner; //--- Calculate x-offset from right edge
         y_offset = YOffsetFromCorner; //--- Use user-defined y-offset from top
         break;
      case BOTTOM_LEFT: //--- Handle Bottom-Left corner
         x_offset = XOffsetFromCorner; //--- Use user-defined x-offset from left
         y_offset = chart_pixel_height - scaled_image_height - YOffsetFromCorner; //--- Calculate y-offset from bottom
         break;
      case BOTTOM_RIGHT: //--- Handle Bottom-Right corner
         x_offset = chart_pixel_width - scaled_image_width - XOffsetFromCorner; //--- Calculate x-offset from right edge
         y_offset = chart_pixel_height - scaled_image_height - YOffsetFromCorner; //--- Calculate y-offset from bottom
         break;
      default: //--- Handle unexpected case
         x_offset = XOffsetFromCorner; //--- Default to top-left x-offset
         y_offset = YOffsetFromCorner; //--- Default to top-left y-offset
   }
}

En cuanto al posicionamiento, declaramos «x_offset» y «y_offset». Si «CenterImageDynamically» es verdadero, centramos la imagen con «x_offset» como («chart_pixel_width» - «scaled_image_width») / 2 y «y_offset» como («chart_pixel_height» - «scaled_image_height») / 2.

De lo contrario, un switch en «AnchorCorner» establece los desplazamientos: «TOP_LEFT» utiliza «XOffsetFromCorner» y «YOffsetFromCorner»; «TOP_RIGHT» establece «x_offset» en «chart_pixel_width» - «scaled_image_width» - «XOffsetFromCorner»; «BOTTOM_LEFT» establece «y_offset» en «chart_pixel_height» - «scaled_image_height» - «YOffsetFromCorner»; «BOTTOM_RIGHT» combina ambos. Por defecto se utiliza «XOffsetFromCorner» y «YOffsetFromCorner».

Ahora necesitamos una función para posicionar la imagen mediante las coordenadas definidas.

//+------------------------------------------------------------------+
//| Create and position the chart image object                       |
//+------------------------------------------------------------------+
void CreateFullChartImage(
   string object_name, string resource_name,
   int x_size, int y_size,
   int x_offset, int y_offset,
   bool is_background
) {
   // Create the bitmap label object if it doesn't exist
   if (ObjectFind(0, object_name) < 0) { //--- Check if the object already exists
      ObjectCreate(0, object_name, OBJ_BITMAP_LABEL, 0, 0, 0); //--- Create a new bitmap label object
   }
   
   // Set object properties
   ObjectSetString(0, object_name, OBJPROP_BMPFILE, resource_name); //--- Set the resource file for the bitmap
   ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Set the width of the bitmap
   ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Set the height of the bitmap
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_offset); //--- Set the horizontal position of the bitmap
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_offset); //--- Set the vertical position of the bitmap
   ObjectSetInteger(0, object_name, OBJPROP_BACK, is_background); //--- Set whether the bitmap is in the background based on input
   
   // Redraw the chart to update the display
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aquí, implementamos la función "CreateFullChartImage" para crear y posicionar un objeto de imagen en el gráfico. Primero comprobamos si el objeto denominado «object_name» existe utilizando la función ObjectFind. Si no es así (result < 0), creamos un nuevo objeto de etiqueta de mapa de bits con la función ObjectCreate, especificando «object_name», OBJ_BITMAP_LABEL y las coordenadas predeterminadas del gráfico.

A continuación, configuramos las propiedades del objeto: utilizando ObjectSetString, vinculamos «resource_name» a OBJPROP_BMPFILE para la fuente de la imagen; con ObjectSetInteger, configuramos «OBJPROP_XSIZE» como «x_size», «OBJPROP_YSIZE» a «y_size», «OBJPROP_XDISTANCE» a «x_offset» y «OBJPROP_YDISTANCE» a «y_offset» para el tamaño y la posición; y OBJPROP_BACK a «is_background» para alternar entre la visualización en primer plano y en segundo plano. Por último, llamamos a la función ChartRedraw para actualizar la visualización del gráfico con el objeto de imagen nuevo o modificado. A continuación, llamamos a esta función para visualizar la imagen.

CreateFullChartImage(
   CHART_IMAGE_OBJECT_NAME, scaled_resource_name,
   scaled_image_width, scaled_image_height,
   x_offset, y_offset, ImageInBackground
); //--- Create and position the chart image object, using user-specified background setting

Después de llamar a la función, ya estamos listos y podemos llamar a esta función principal ahora en el controlador de eventos OnInit para crear la primera imagen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Display the image on the chart during initialization
   if (!DisplayImageOnChart()) { //--- Attempt to display the image on the chart
      Print("Error: Failed to display the initial image."); //--- Log an error if image display fails
      return (INIT_FAILED); //--- Return failure status if initialization fails
   }
   return (INIT_SUCCEEDED); //--- Return success status if initialization succeeds
}

En el controlador de eventos OnInit, llamamos a la función «DisplayImageOnChart» para renderizar la imagen durante la inicialización. Si «DisplayImageOnChart» devuelve falso, lo que indica un error al mostrar la imagen, registramos un mensaje de error y devolvemos INIT_FAILED para indicar que la inicialización ha fallado, deteniendo el programa. Si la visualización se realiza correctamente, devolvemos INIT_SUCCEEDED para indicar que la inicialización se ha completado con éxito, lo que permite que el programa continúe. Tras la compilación, esto es lo que tenemos.

IMAGEN INICIAL

Desde la imagen, podemos ver que podemos mapear la imagen del recurso en la ejecución inicial. Sin embargo, necesitamos hacer que la imagen responda cuando cambian las dimensiones del gráfico. Por lo tanto, necesitaremos el controlador de eventos OnChartEvent.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(
   const int event_id,         // Event ID
   const long &long_param,     // Long type event parameter
   const double &double_param, // Double type event parameter
   const string &string_param  // String type event parameter
) {
   // Handle chart resize events to update the image
   if (event_id == CHARTEVENT_CHART_CHANGE) { //--- Check if the event is a chart change (e.g., resize)
      if (!DisplayImageOnChart()) { //--- Attempt to update the image on the chart
         Print("Error: Failed to update image on chart resize."); //--- Log an error if image update fails
      }
   }
}

Aquí definimos el controlador de eventos OnChartEvent para gestionar los eventos relacionados con los gráficos en nuestro programa, centrándonos específicamente en el cambio de tamaño de los gráficos. Comprobamos si «event_id» es igual a CHARTEVENT_CHART_CHANGE, lo que indica una modificación del gráfico, como un cambio de tamaño. Si es cierto, llamamos a la función «DisplayImageOnChart» para actualizar la visualización de la imagen y que coincida con las nuevas dimensiones del gráfico. Si «DisplayImageOnChart» devuelve falso, lo que indica un error al actualizar la imagen, registramos un mensaje de error para alertar al usuario del problema. Por último, debemos eliminar todos los recursos que hemos asignado en el gráfico cuando eliminemos el programa.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   // Clean up by deleting the chart image object
   ObjectDelete(0, CHART_IMAGE_OBJECT_NAME); //--- Remove the chart image object during deinitialization
   // Dynamic resources are automatically freed when the EA is removed //--- Note that dynamic resources are automatically released
}

Para garantizar una salida limpia, en el controlador de eventos OnDeinit, llamamos a la función ObjectDelete para eliminar del gráfico el objeto de imagen identificado por «CHART_IMAGE_OBJECT_NAME». Esto evita que queden objetos residuales después de que finalice el programa. Tenga en cuenta que los recursos dinámicos, como los creados para imágenes escaladas, son liberados automáticamente por el sistema cuando se elimina el Asesor Experto, sin necesidad de realizar ninguna limpieza adicional. Cuando ejecutamos el programa, obtenemos el siguiente resultado.

RESULTADO FINAL

En la imagen podemos ver que creamos la imagen definida, la establecemos como recurso y la escalamos dinámicamente según lo definido por el usuario, logrando así nuestro objetivo. Ahora solo queda probar el programa a fondo, lo cual se trata en el siguiente tema.


Pruebas y validación

Realizamos todas las pruebas y las recopilamos en una visualización en formato GIF (Graphical Interchange Format) que muestra que es dinámica y responde al cambio de tamaño de los gráficos y a las entradas del usuario, sin perder calidad en el proceso.

RECURSO DE IMAGEN DE INTERPOLACIÓN BICÚBICA MQL5


Conclusión

En conclusión, hemos creado una herramienta MQL5 para gráficos de imágenes dinámicas en los gráficos de MetaTrader 5, utilizando interpolación bicúbica para obtener imágenes nítidas y adaptables. Hemos mostrado cómo implementarlo y aplicarlo para el escalado y posicionamiento de imágenes controlado por el usuario. Puede personalizar esta herramienta para mejorar la funcionalidad y la estética de sus gráficos bursátiles.

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

Del básico al intermedio: Indicador (V) Del básico al intermedio: Indicador (V)
En este artículo, veremos cómo podemos lidiar con solicitudes del usuario para cambiar el modo de trazado del gráfico. Esto, para que podamos lograr que un indicador, orientado a usar el modo de trazado gráfico actual, no quede extraño ni diferente de lo que el usuario de MetaTrader 5 esperaría.
Simulación de mercado (Parte 20): Iniciando el SQL (III) Simulación de mercado (Parte 20): Iniciando el SQL (III)
Aunque podemos hacer cosas con una base de datos de unas 10 entradas, esto se asimila mucho mejor cuando trabajamos con un archivo que tenga más de 15 mil registros. Es decir, si tú intentaras crear eso manualmente, sería una tarea enorme. Sin embargo, es difícil encontrar una base de datos, incluso con fines didácticos, disponible para descargar. Pero, en realidad, no necesitamos recurrir a eso. Podemos usar MetaTrader 5 para crear una base de datos para nosotros. En este artículo, veremos cómo hacerlo.
Visión por computadora para el trading (Parte 1): Creamos una funcionalidad básica sencilla Visión por computadora para el trading (Parte 1): Creamos una funcionalidad básica sencilla
Sistema de previsión de EURUSD mediante visión por computadora y aprendizaje profundo. Descubra cómo las redes neuronales convolucionales pueden reconocer patrones de precios complejos en el mercado de divisas y predecir la evolución de los tipos con una precisión de hasta el 54%. El artículo revela la metodología de creación de un algoritmo que usa tecnologías de inteligencia artificial para analizar visualmente los gráficos en lugar de los indicadores técnicos tradicionales. El autor muestra el proceso de transformación de los datos de precios en "imágenes", su procesamiento por una red neuronal y una visión única de la "conciencia" de la IA a través de mapas de activación y mapas de calor de la atención. El práctico código Python que utiliza la biblioteca MetaTrader 5 permite a los lectores reproducir el sistema y aplicarlo a sus propias transacciones.
Del básico al intermedio: Herencia Del básico al intermedio: Herencia
Sin duda, se trata de un artículo al que deberás dedicarle bastante tiempo para entender cómo y por qué funcionan las cosas que se muestran aquí. Esto se debe simplemente a que todo lo que se verá y mostrará aquí está orientado originalmente a lo que sería la programación orientada a objetos. Pero, en realidad, se basa en principios de programación estructural.