English Русский 日本語
preview
Erstellen von dynamischen MQL5-Grafikschnittstellen durch ressourcengesteuerte Bildskalierung mit bikubischer Interpolation auf Handelscharts

Erstellen von dynamischen MQL5-Grafikschnittstellen durch ressourcengesteuerte Bildskalierung mit bikubischer Interpolation auf Handelscharts

MetaTrader 5Handel |
92 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

Das Anpassen von Handelscharts mit dynamischen Grafiken kann die Art und Weise, wie wir Märkte analysieren, verbessern, aber das Erreichen einer hochwertigen Bildwiedergabe erfordert eine gut durchdachte MetaQuotes Language 5 (MQL5) Programmierung. In diesem Artikel stellen wir ein leistungsfähiges Tool vor, das dynamische grafische Oberflächen ermöglicht, indem es ressourcengesteuerte Bildskalierung mit bikubische Interpolation für scharfe, anpassungsfähige Charts nutzt. Wir werden den Prozess anhand dieser Schritte untersuchen:

  1. Überblick über dynamische ressourcengesteuerte Bildgrafiken
  2. MQL5-Implementierung
  3. Tests und Validierung
  4. Schlussfolgerung

Am Ende des Kurses verfügen Sie über ein robustes Werkzeug, mit dem Sie Ihre Handelscharte mit professionellen, nutzergesteuerten Bildgrafiken versehen können.


Überblick über dynamische ressourcengesteuerte Bildgrafiken

Unser Ziel ist es, ein MQL5-Tool zu entwickeln, das Bilder in die Charts des MetaTrader 5 einbettet und skaliert und so dynamische, nutzergesteuerte grafische Schnittstellen schafft. Wir laden ein Bitmap-Bild als Ressource, skalieren es mit Hilfe der bikubische Interpolation, um es an die Abmessungen des Charts anzupassen, und positionieren es auf der Grundlage von Nutzereingaben wie der Verankerung an den Ecken oder der dynamischen Zentrierung. So können wir nutzerdefinierte Grafiken - Logos oder Muster - überlagern und dabei die Seitenverhältnisse beibehalten und die Anzeige von Hintergrund und Vordergrund umschalten, alles optimiert für Echtzeitleistung. Auf diese Weise werden die Charts interessanter und ansprechender.

Zur Skalierung verwenden wir die bikubische Interpolation über den nächsten Nachbarn und bilineare Methoden. Der nächste Nachbar erzeugt verpixelte Ergebnisse, und die bilineare Methode verwischt Details. Hier finden Sie eine detaillierte Visualisierung, warum wir uns für die bikubische Interpolation entschieden haben.

Die Visualisierung der Grafik

GRAFIK

Visualisierung der resultierenden Verpixelung.

VERPIXELUNG

Bikubisch, das eine 4x4-Pixel-Nachbarschaft und kubische Polynome nutzt, sorgt für glattere Verläufe und schärfere Kanten. Wir haben uns für die bikubische Darstellung entschieden, weil sie besonders klar und effizient ist. Sie eignet sich ideal für die dynamische Größenanpassung von Charts und liefert gestochen scharfe Bilder, die Handelsentscheidungen erleichtern. Hier ist ein Beispiel dafür, was wir mit einem nutzerdefinierten MQL5-Bild erreichen wollen.

ÜBERSICHT MQL5 BIKUBISCHES BILD


MQL5-Implementierung

Um das Programm in MQL5 zu erstellen, müssen wir zunächst die Bilder im richtigen Format haben, nämlich im Bitmap (BMP)-Format, bei dem es sich um unkomprimierte Bitmap-Bilder mit hoher Auflösung handelt, verglichen mit der Joint Photographic Experts Group (JPEG). Wenn Sie also ein relevantes Bild in einem anderen Format benötigen, können Sie es mit kostenlosen Online-Ressourcen konvertieren. Was uns betrifft, so sind dies die Bilder, die wir verwenden werden, da wir sie bereits vorbereitet haben.

BITMAP-DATEI

Nachdem wir die Dateien in den entsprechenden Formaten erhalten haben, müssen wir sie in den Ordner „images“ verschieben. Sie können einfach den Navigator öffnen, den Ordner der Bilder suchen, mit der rechten Maustaste darauf klicken und „Ordner öffnen“ wählen. Dadurch wird der Standardordner mit zwei Dollar- und Euro-Bilddateien standardmäßig geöffnet. Dort können Sie Ihre Bilddateien einfügen. Hier ist eine visuelle Veranschaulichung.

BILDORDNER IN MQL5

Wenn das erledigt ist, können wir loslegen. Als erstes werden wir das Bild als Ressource hinzufügen.

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

Hier schaffen wir die Grundlage für die Einbettung und Darstellung unseres Bildes auf den Charts. Wir verwenden die Direktive #resource, um eine Bitmap-Datei mit dem Namen „\Images\mql5-circuit.bmp“ als Ressource einzubinden und sicherzustellen, dass sie für unser Programm leicht zugänglich ist.

Anschließend definieren wir das Makro „ORIGINAL_IMAGE_RESOURCE“, das auf „::Images\mql5-circuit.bmp“ gesetzt wird, um einen standardisierten Verweis auf den Ressourcenpfad des Bildes zu erstellen, den wir zum Laden der Bilddaten verwenden werden. Zusätzlich definieren wir das Makro „CHART_IMAGE_OBJECT_NAME“ als „ChartImage“, das wir als eindeutigen Bezeichner für das Chartobjekt, das das Bild anzeigt, verwenden werden, um es zu verwalten, indem wir sein Aussehen im Chart steuern.

Als Nächstes müssen wir einige globale Variablen und Eingaben definieren, die wir im gesamten Programm verwenden werden.

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

Um die Nutzersteuerung für die Bildplatzierung zu konfigurieren, definieren wir die Enumeration „ENUM_ANCHOR_CORNER“ mit den Optionen „TOP_LEFT“, „TOP_RIGHT“, „BOTTOM_LEFT“ und „BOTTOM_RIGHT“ zur Auswahl einer Chart-Ecke.

Wir legen folgende Eingabeparameter fest: “LimitToOriginalSize“ (true) zur Begrenzung der Skalierung, „ImageInBackground“ (true) für die Anzeige im Hintergrund/Vordergrund, „CenterImageDynamically“ (true) für die automatische oder manuelle Positionierung, „AnchorCorner“ (TOP_LEFT) für die Auswahl der Ecken und „XOffsetFromCorner“ und „YOffsetFromCorner“ (100 Pixel) für manuelle Offsets. Außerdem initialisieren wir „scaled_resource_counter“ auf 0 für eindeutige skalierte Bildnamen.

Um das Bild im Chart anzuzeigen, verwenden wir eine nutzerdefinierte Funktion, die die gesamte Logik enthält.

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

}

Wir beginnen mit der Definition der Funktion „DisplayImageOnChart“, mit der wir unser Bild auf dem Chart darstellen werden. Zunächst deklarieren wir das Array „image_pixels“, das die Pixeldaten des Bildes enthält, und die Variablen „original_image_width“ und „original_image_height“, die die Abmessungen des Originalbildes speichern. Mit der Funktion ResourceReadImage laden wir die Bilddaten aus dem Makro „ORIGINAL_IMAGE_RESOURCE“ in das Array „image_pixels“, wobei wir die Breite und Höhe erfassen. Wenn dieser Vorgang fehlschlägt, wird mit der Funktion Print eine Fehlermeldung protokolliert und false zurückgegeben, um den Fehler anzuzeigen und die weitere Verarbeitung zu stoppen.

Als Nächstes werden die Abmessungen des Charts abgerufen, um sicherzustellen, dass das Bild in die richtige Größe passt. Wir verwenden die Funktion ChartGetInteger, um die Breite und Höhe des Charts zu erhalten und speichern sie in „chart_pixel_width“ und „chart_pixel_height“, nachdem wir sie in Ganzzahlen umgewandelt haben. Um die Proportionen des Bildes bei der Skalierung beizubehalten, berechnen wir das „image_aspect_ratio“, indem wir „original_image_width“ durch „original_image_height“ dividieren, und das „chart_aspect_ratio“, indem wir „chart_pixel_width“ durch „chart_pixel_height“ dividieren. Diese Verhältnisse werden unsere Skalierungslogik leiten.

Wir deklarieren dann „scaled_image_width“ und „scaled_image_height“, um die Abmessungen des skalierten Bildes zu speichern. Um das Seitenverhältnis beizubehalten, vergleichen wir das „image_aspect_ratio“ mit dem „chart_aspect_ratio“. Wenn das Bild im Verhältnis zum Chart breiter ist (d. h., „image_aspect_ratio“ ist größer als „chart_aspect_ratio“), wird „scaled_image_width“ auf „chart_pixel_width“ gesetzt und „scaled_image_height“ durch Division von „chart_pixel_width“ durch „image_aspect_ratio“ berechnet. Andernfalls setzen wir „scaled_image_height“ auf „chart_pixel_height“ und berechnen „scaled_image_width“ durch Multiplikation von „chart_pixel_height“ mit „image_aspect_ratio“. Dadurch wird sichergestellt, dass das Bild ohne Verzerrung in das Chart passt.

Wenn die Eingabe „LimitToOriginalSize“ wahr ist, beschränken wir die Skalierung auf die ursprünglichen Bildabmessungen, um eine Hochskalierung zu verhindern. Wir verwenden die MathMin-Funktion, um „scaled_image_width“ auf „original_image_width“ und „scaled_image_height“ auf „original_image_height“ zu begrenzen. Um das Seitenverhältnis nach dieser Einschränkung beizubehalten, wird geprüft, ob „scaled_image_width“ kleiner ist als „scaled_image_height“ multipliziert mit „image_aspect_ratio“. Wenn dies der Fall ist, wird „scaled_image_height“ neu berechnet, indem „scaled_image_width“ durch „image_aspect_ratio“ dividiert wird; andernfalls wird „scaled_image_width“ durch Multiplikation von „scaled_image_height“ mit „image_aspect_ratio“ neu berechnet.

Schließlich protokollieren wir die Original-, Chart- und skalierten Abmessungen mit der Funktion PrintFormat. Damit können wir dynamische Änderungen im Chart verfolgen, wie unten gezeigt.

CHART-ÄNDERUNGEN

Aus dem Bild können wir ersehen, dass wir bereits die Abmessungen des Charts und des Bildes gelesen haben. Jetzt müssen wir unser Bild dynamisch skalieren, damit es in das Chart passt. Um dies zu erreichen, benötigen wir eine nutzerdefinierte Funktion.

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

Hier definieren wir die Funktion „ScaleImage“ zur Größenänderung eines Bildes durch bikubische Interpolation. Wir deklarieren das Array „scaled_pixels“ und verwenden die Funktion ArrayResize, um es für „new_width“ * „new_height“ Pixel zu vergrößern. Wir gehen in einer Schleife durch jedes Pixel und ordnen die Koordinaten dem Originalbild mit „original_x“ und „original_y“ zu.

Für jedes Pixel rufen wir die Funktion „BicubicInterpolate“ auf, die wir weiter unten erklären werden, um den Pixelwert zu berechnen und ihn in „scaled_pixels“ bei „y“ * „new_width“ + „x“ zu speichern. Schließlich ändern wir die Größe von „pixels“ mit ArrayResize und kopieren „scaled_pixels“ mit ArrayCopy für die weitere Bearbeitung des Charts hinein.

Dies ist die Funktion, die für die Berechnung des Pixelwertes durch bikubische Interpolation zuständig ist. Definieren wir zunächst eine Funktion zur Berechnung der Interpolation für ein einzelnes Farbelement, dann können wir sie zur Berechnung der Interpolation für einzelne Pixel verwenden.

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

Hier implementieren wir die Funktion „BicubicInterpolateComponent“, um eine bikubische Interpolation für eine einzelne Farbkomponente in unserem Bildskalierungsprozess durchzuführen. Wir deklarieren das Array „weights_x“, um Interpolationsgewichte für die x-Achse zu speichern, und setzen „t“ auf „fractional_x“, den gebrochenen Teil der x-Koordinate.

Wir berechnen vier Gewichte in „weights_x“ mit Hilfe von kubischen Polynomformeln für die Positionen x-1, x, x+1 und x+2, was eine reibungslose Interpolation ermöglicht. Als Nächstes deklarieren wir das Array „y_values“, um Zwischenergebnisse zu speichern, und iterieren über vier Zeilen einer 4x4-Pixel-Nachbarschaft. Für jede Zeile „j“ wird „y_values[j]“ durch Multiplikation der „weights_x“-Werte mit den entsprechenden Farbkomponenten aus dem Array „components“ bei den Indizes „j * 4 + 0“ bis „j * 4 + 3“ berechnet, wobei eine Interpolation auf der x-Achse erfolgt.

Dann deklarieren wir das Array „weights_y“ und setzen „t“ auf „fractional_y“ für die Gewichte der y-Achse und berechnen sie mit denselben kubischen Formeln für y-1, y, y+1 und y+2. Wir interpolieren auf der y-Achse, indem wir „result“ als gewichtete Summe der „y_values“ mit „weights_y“ berechnen. Schließlich wird das „Ergebnis“ mit den Funktionen MathMax und MathMin auf den gültigen Farbbereich [0, 255] begrenzt, um sicherzustellen, dass die Ausgabe für die Pixelfarbdarstellung geeignet ist. Wir benötigen auch Farbkanalkomponenten für jedes Pixel, also brauchen wir eine Funktion für die Extraktion der Farbkanäle.

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

Wir implementieren die Funktion „GetArgb“, um einzelne ARGB-Farbkomponenten aus einem bestimmten Pixel zu extrahieren. Wir nehmen einen „Pixel“-Wert und referenzieren die Variablen „Alpha“, „Rot“, „Grün“ und „Blau“, um die extrahierten Komponenten zu speichern. Mit bitweisen Operationen isolieren wir jede Komponente: Wir verschieben „Pixel“ um 24 Bits nach rechts und maskieren mit 0xFF, um „Alpha“ zu extrahieren; verschieben um 16 Bits und maskieren für „Rot“; verschieben um 8 Bits und maskieren für „Grün“; und maskieren die untersten 8 Bits für „Blau“. Jedes Ergebnis wird in uchar umgewandelt, um sicherzustellen, dass die Werte in den Bereich 0-255 für die Farbdarstellung passen.

Wir haben das System „0xFF“ verwendet, um sicherzustellen, dass das gewünschte Byte (8 Bits) von einem 32-Bit-Pixel ohne Vorzeichen maskiert wird, das eine Farbe im ARGB-Format darstellt, wodurch eine genaue Extraktion der Alpha-, Rot-, Grün- oder Blauwerte gewährleistet wird. Ohne „& 0xFF“ würden wir unerwartete Ergebnisse erhalten, weil die höheren Bits von anderen Kanälen übrig bleiben könnten. Wenn Sie sich fragen, welche Zeichen das sind, so handelt es sich um 255 im Hexadezimalformat. Schauen Sie hier nach.

0xFF IN HEXADECIMAL

Mit diesen Funktionen können wir nun eine Funktion definieren, die die bikubische Interpolation für ein einzelnes Pixel durchführt, um das Bild präzise zu skalieren.

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

Hier implementieren wir die Funktion „BicubicInterpolate“, um die Farbe eines einzelnen Pixels mit Hilfe des bikubischen Interpolationsverfahrens zu berechnen. Wir beginnen mit der Extraktion der ganzzahligen Teile der Eingabekoordinaten „x“ und „y“ in „x0“ und „y0“ und berechnen ihre gebrochenen Teile als „fractional_x“ und „fractional_y“. Wir definieren die Arrays „x_indices“ und „y_indices“, um die Koordinaten einer 4x4-Nachbarschaft zu speichern, wobei wir von -1 bis 2 iterieren, um Indizes um „x0“ und „y0“ zu berechnen. Mithilfe von MathMin und MathMax werden diese auf gültige Bereiche [0, „Breite“-1] und [0, „Höhe“-1] festgelegt.

Dann erstellen wir das Array „neighborhood_pixels“, um 16 Pixel aus der 4x4-Nachbarschaft zu speichern, und füllen es durch eine Iteration über „y_indices“ und „x_indices“, um Pixelwerte aus dem Array „pixels“ bei „y_indices[j] * width + x_indices[i]“ zu holen. Als Nächstes deklarieren wir die Arrays „alpha_components“, „red_components“, „green_components“ und „blue_components“ und verwenden die Funktion „GetArgb“, um ARGB-Werte für jedes der 16 Pixel in „neighborhood_pixels“ zu extrahieren.

Für jede Farbkomponente rufen wir die Funktion „BicubicInterpolateComponent“ mit „fractional_x“ und „fractional_y“ auf und speichern die Ergebnisse in „alpha_out“, „red_out“, „green_out“ und „blue_out“ als „uchar“ Werte. Schließlich kombinieren wir diese zu einem einzigen Pixelwert, indem wir sie bitweise verschieben: „alpha_out“ um 24 Bits, „red_out“ um 16 Bits, „green_out“ um 8 Bits und „blue_out“ um den resultierenden „uint“ zur Verwendung im skalierten Bild. Jetzt können wir die Funktion „ScaleImage“ aufrufen, damit sie wirksam wird.

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

Nach dem Aufruf der Funktion können wir nun eine neue Ressource mit dem skalierten Bild erstellen.

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

Um ein skaliertes Bild als Ressource zu erzeugen und zu speichern, erstellen wir einen eindeutigen Ressourcennamen, indem wir die Zeichenkette „scaled_resource_name“ definieren und „::ScaledImage“ mit dem Ergebnis der Funktion IntegerToString, die auf „scaled_resource_counter“ angewendet wird, verketten, das wir dann inkrementieren, um die Eindeutigkeit für nachfolgende Ressourcen sicherzustellen.

Als Nächstes verwenden wir die Funktion ResourceCreate, um eine neue Ressource mit dem Namen „scaled_resource_name“ zu erstellen. Dabei verwenden wir das Array „image_pixels“, „scaled_image_width“, „scaled_image_height“ und geben einen Offset von 0, eine Breite von „scaled_image_width“ und das Format COLOR_FORMAT_ARGB_NORMALIZE an. Wenn „ResourceCreate“ fehlschlägt, protokollieren wir einen Fehler mit Print, einschließlich „scaled_resource_name“, und geben false zurück, um den Fehler anzuzeigen und die weitere Verarbeitung zu stoppen.

Wenn wir hier passen, müssen wir nur das Bild auf dem Chart positionieren und visualisieren, also definieren wir zuerst die Positionierungskoordinaten.

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

Für die Positionierung geben wir „x_offset“ und „y_offset“ an. Wenn „CenterImageDynamically“ true ist, wird das Bild mit „x_offset“ als (“chart_pixel_width“ - „scaled_image_width“) / 2 und „y_offset“ als (“chart_pixel_height“ - „scaled_image_height“) / 2 zentriert.

Andernfalls setzen wir mit switch auf „AnchorCorner“ die Abstände: “TOP_LEFT“ verwendet „XOffsetFromCorner“ und „YOffsetFromCorner“; „TOP_RIGHT“ setzt „x_offset“ auf „chart_pixel_width“ - „scaled_image_width“ - „XOffsetFromCorner“; „BOTTOM_LEFT“ setzt „y_offset“ auf „chart_pixel_height“ - „scaled_image_height“ - „YOffsetFromCorner“; „BOTTOM_RIGHT“ kombiniert beide. Standardmäßig werden „XOffsetFromCorner“ und „YOffsetFromCorner“ verwendet.

Wir brauchen nun eine Funktion, die das Bild über die definierten Koordinaten positioniert.

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

Hier implementieren wir die Funktion „CreateFullChartImage“, um ein Bildobjekt auf dem Chart zu erstellen und zu positionieren. Zunächst wird mit der Funktion ObjectFind geprüft, ob das Objekt mit dem Namen „object_name“ existiert. Ist dies nicht der Fall (Ergebnis < 0), wird mit der Funktion ObjectCreate ein neues Bitmap-Objekt erstellt, wobei „object_name“, OBJ_BITMAP_LABEL und die Standardkoordinaten des Charts angegeben werden.

Dann setzen wir die Eigenschaften des Objekts: Mit ObjectSetString verknüpfen wir „resource_name“ mit OBJPROP_BMPFILE für die Bildquelle, mit ObjectSetInteger setzen wir „OBJPROP_XSIZE“ auf „x_size“, „OBJPROP_YSIZE“ auf „y_size“, „OBJPROP_XDISTANCE“ auf „x_offset“ und „OBJPROP_YDISTANCE“ auf „y_offset“ für Größe und Position und OBJPROP_BACK auf „is_background“, um zwischen Hintergrund- und Vordergrundanzeige umzuschalten. Schließlich rufen wir die Funktion ChartRedraw auf, um die Chartanzeige mit dem neuen oder geänderten Bildobjekt zu aktualisieren. Anschließend rufen wir diese Funktion auf, um das Bild zu visualisieren.

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

Nachdem wir die Funktion aufgerufen haben, sind wir fertig und können diese Hauptfunktion nun in OnInit aufrufen, um das erste Bild zu erstellen.

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

Im OnInit-Ereignis-Handler rufen wir die Funktion „DisplayImageOnChart“ auf, um das Bild während der Initialisierung zu rendern. Wenn „DisplayImageOnChart“ den Wert false zurückgibt, was bedeutet, dass das Bild nicht angezeigt werden kann, wird eine Fehlermeldung protokolliert und INIT_FAILED zurückgegeben, um zu signalisieren, dass die Initialisierung fehlgeschlagen ist und das Programm angehalten wird. Wenn die Anzeige erfolgreich ist, geben wir INIT_SUCCEEDED zurück, um anzuzeigen, dass die Initialisierung erfolgreich abgeschlossen wurde und das Programm fortgesetzt werden kann. Die folgende Zusammenstellung zeigt, was wir haben.

INITIALER IMAGE-LAUF

Aus dem Bild geht hervor, dass wir das Ressourcenbild beim ersten Durchlauf zuordnen können. Wir müssen jedoch dafür sorgen, dass das Bild reagiert, wenn sich die Abmessungen des Charts ändern. Daher benötigen wir die Ereignisbehandlung durch 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
      }
   }
}

Hier definieren wir den OnChartEvent-Ereignishandler, um Ereignisse im Zusammenhang mit Charts in unserem Programm zu behandeln, wobei wir uns speziell auf die Größenänderung von Charts konzentrieren. Wir prüfen, ob „event_id“ gleich CHARTEVENT_CHART_CHANGE ist, was auf eine Änderung des Charts, z. B. eine Größenänderung, hinweist. Wenn „true“, rufen wir die Funktion „DisplayImageOnChart“ auf, um die Anzeige des Bildes zu aktualisieren, damit es den neuen Chartabmessungen entspricht. Wenn „DisplayImageOnChart“ den Wert „false“ zurückgibt, was bedeutet, dass das Bild nicht aktualisiert werden konnte, wird eine Fehlermeldung protokolliert, um den Nutzer auf das Problem hinzuweisen. Schließlich müssen wir alle Ressourcen entfernen, die wir im Chart zugeordnet haben, wenn wir das Programm entfernen.

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

Um eine saubere Beendigung zu gewährleisten, rufen wir im OnDeinit die Funktion ObjectDelete auf, um das durch „CHART_IMAGE_OBJECT_NAME“ identifizierte Chartobjekt aus dem Chart zu entfernen. Dadurch wird verhindert, dass nach Beendigung des Programms Restobjekte übrig bleiben. Beachten Sie, dass dynamische Ressourcen, wie z. B. solche, die für skalierte Bilder erstellt wurden, automatisch vom System freigegeben werden, wenn der Expert Advisor entfernt wird, und keine zusätzliche Bereinigung erforderlich ist. Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis.

ENDGÜLTIGES ERGEBNIS

Anhand des Bildes können wir sehen, dass wir das definierte Bild erstellen, es als Ressource festlegen und es dynamisch nach den Vorgaben des Nutzers skalieren, wodurch wir unser Ziel erreichen. Jetzt muss das Programm nur noch gründlich getestet werden, und das wird im nächsten Punkt behandelt.


Tests und Validierung

Wir haben alle Tests durchgeführt und sie in einer Visualisierung als Graphical Interchange Format (GIF) zusammengestellt, die zeigt, dass sie dynamisch ist und auf Größenänderungen des Charts und Nutzereingaben reagiert, ohne dabei an Qualität zu verlieren.

MQL5 BIKUBISCHE INTERPOLATION BILDRESSOURCE


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir ein MQL5-Tool für dynamische Bildgrafiken auf MetaTrader 5-Charts entwickelt haben, das bicubic interpolation für scharfe, anpassungsfähige Grafiken verwendet. Wir haben gezeigt, wie man sie für die nutzergesteuerte Skalierung und Positionierung von Bildern implementiert und anwendet. Sie können dieses Tool anpassen, um die Funktionalität und Ästhetik Ihrer Trading-Charts zu verbessern.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17892

Datenwissenschaft und ML (Teil 36): Der Umgang mit verzerrten Finanzmärkten Datenwissenschaft und ML (Teil 36): Der Umgang mit verzerrten Finanzmärkten
Die Finanzmärkte sind nicht vollkommen ausgeglichen. Einige Märkte steigen, andere fallen, und wieder andere zeigen ein gewisses Schwankungsverhalten, das auf Unsicherheit in beide Richtungen hindeutet. Diese unausgewogenen Informationen können beim Trainieren von Machine-Learning-Modellen irreführend sein, da sich die Märkte häufig ändern. In diesem Artikel werden wir verschiedene Möglichkeiten erörtern, dieses Problem zu lösen.
Dekodierung von Intraday-Handelsstrategien des Opening Range Breakout Dekodierung von Intraday-Handelsstrategien des Opening Range Breakout
Die Strategien des Opening Range Breakout (ORB) basieren auf der Idee, dass die erste Handelsspanne, die sich kurz nach der Markteröffnung bildet, wichtige Preisniveaus widerspiegelt, bei denen sich Käufer und Verkäufer auf einen Wert einigen. Durch die Identifizierung von Ausbrüchen über oder unter einer bestimmten Spanne können Händler von der Dynamik profitieren, die oft folgt, wenn die Marktrichtung klarer wird. In diesem Artikel werden wir drei ORB-Strategien untersuchen, die von der Concretum Group übernommen wurden.
Aufbau eines nutzerdefinierten Systems zur Erkennung von Marktregimen in MQL5 (Teil 1): Der Indikator Aufbau eines nutzerdefinierten Systems zur Erkennung von Marktregimen in MQL5 (Teil 1): Der Indikator
Dieser Artikel beschreibt die Erstellung eines MQL5-Systems zur Erkennung von Marktregimen unter Verwendung statistischer Methoden wie Autokorrelation und Volatilität. Es enthält Code für Klassen zur Klassifizierung von Trend-, Spannen- und Volatilitätsbedingungen sowie einen nutzerdefinierten Indikator.
Automatisieren von Handelsstrategien in MQL5 (Teil 16): Midnight Range Breakout mit der Preisaktion Break of Structure (BoS) Automatisieren von Handelsstrategien in MQL5 (Teil 16): Midnight Range Breakout mit der Preisaktion Break of Structure (BoS)
In diesem Artikel automatisieren wir die Midnight Range Breakout mit Break of Structure Strategie in MQL5, indem wir den Code für die Breakout-Erkennung und die Handelsausführung detailliert beschreiben. Wir definieren präzise Risikoparameter für Einstieg, Stopp und Gewinn. Backtests und Optimierung sind für den praktischen Handel enthalten.