
Erstellen eines integrierten MQL5-Telegram-Expertenberaters (Teil 3): Senden von Screenshots des Charts mit einer Legende von MQL5 an Telegram
Einführung
Im vorangegangenen Artikel, dem zweiten Teil unserer Serie, haben wir den Prozess der Verschmelzung von MetaQuotes Language 5 (MQL5) mit Telegram zur Signalerzeugung und -weiterleitung genau untersucht. Das Ergebnis war klar: Es ermöglichte uns, Handelssignale an Telegram zu senden und natürlich die Notwendigkeit, die Handelssignale zu nutzen, damit sich die ganze Sache lohnt. Warum also müssen wir den nächsten Schritt im Integrationsprozess tun? Was wir in diesem dritten Teil der Serie tun, ist ein „nächster Schritt“ bei der Veranschaulichung des Potenzials der Verschmelzung von MQL5 mit Telegram im Hinblick auf das Senden von Handelssignalen. Anstatt jedoch nur den Textteil des Handelssignals zu senden, senden wir einen Screenshot des Charts des Handelssignals. Manchmal ist es besser, nicht nur ein Signal zu erhalten, auf das man reagieren kann, sondern auch das Signal-Setup wie Preisaktions-Setups auf dem Chart in einer visuellen Darstellung zu sehen, in diesem Fall dem Chart-Screenshot.
In diesem Artikel werden wir uns daher auf die Besonderheiten der Konvertierung von Bilddaten in ein kompatibles Format für die Einbettung in Hypertext Transfer Protocol Secure (HTTP) Anfragen konzentrieren. Diese Umwandlung muss erfolgen, wenn wir Bilder in unseren Telegram-Bot aufnehmen wollen. Wir werden die Details des Prozesses durcharbeiten, der uns von einem Chart in MQL5 über das Handelsterminal MetaTrader 5 zu einer kunstvoll gestalteten Bot-Nachricht mit einer Legende und einem Chart-Bild als dem visuell beeindruckenden Teil unserer Handelsbenachrichtigung führt. Dieser Artikel gliedert sich in vier Teile.
Zu Beginn geben wir einen grundlegenden Überblick über die Funktionsweise der Bildkodierung und -übertragung über HTTPS. In diesem ersten Abschnitt erläutern wir die grundlegenden Konzepte und die zur Erfüllung dieser Aufgabe verwendeten Techniken. Als Nächstes werden wir uns mit dem Implementierungsprozess in MQL5 befassen, der Programmiersprache, die zum Schreiben von Handelsprogrammen für die Plattform MetaTrader5 verwendet wird. Wir werden im Detail erläutern, wie die Bildkodierungs- und -übertragungsmethoden anzuwenden sind. Danach werden wir das implementierte Programm testen, um zu überprüfen, ob es korrekt funktioniert. Wir werden den Artikel dann mit einer Zusammenfassung abschließen, um noch einmal auf die wichtigsten Punkte einzugehen und die Vorteile dieser Arbeit in Handelssystemen zu beschreiben. Hier sind die Themen, die wir verfolgen werden, um den Expert Advisor (EA) zu erstellen:
- Überblick über die Bildkodierung und -übertragung über HTTPS
- Implementation in MQL5
- Testen der Integration
- Schlussfolgerung
Am Ende werden wir einen Expert Advisor erstellt haben, der Chart-Screenshots und Bilder mit Handelsinformationen wie generierte Signale und erteilte Aufträge vom Handelsterminal an den angegebenen Telegram-Chat sendet. Fangen wir an.
Überblick über die Bildkodierung und -übertragung über HTTPS
Das Versenden von Bildern über das Internet und insbesondere die Integration mit Anwendungsprogrammierschnittstellen (APIs) oder Messaging-Plattformen erfordert, dass die Bilddaten zunächst kodiert und dann ohne unangemessene Verzögerung oder Beeinträchtigung der Wirkung oder Sicherheit versendet werden. Eine direkte Bilddatei sendet viel zu viele Bits und Bytes, um die Befehle, mit denen ein Internetnutzer auf eine bestimmte Website, Plattform oder einen Dienst zugreifen kann, reibungslos auszuführen. Für eine API wie Telegram, die als Vermittler zwischen einem Internetnutzer und einem bestimmten Dienst (wie einer webbasierten Schnittstelle für verschiedene Aufgaben) fungiert, erfordert das Senden eines Bildes, dass die Bilddatei zuerst kodiert und dann als Teil der Nutzlast eines Befehls vom Nutzer zum Dienst oder umgekehrt gesendet wird, und dies wird insbesondere durch Protokolle wie HTTP oder HTTPS erreicht.
Die häufigste Methode zur Konvertierung von Bildern für den Versand ist die Umwandlung des Bildes in eine Base64 kodierte Zeichenfolge. Bei der Base64-Kodierung werden die Binärdaten eines Bildes in eine Textdarstellung umgewandelt. Dabei werden Zeichen aus einem bestimmten Datensatz verwendet, der dafür sorgt, dass das so genannte „verschlüsselte Bild“ bei der Übertragung über Textprotokolle ordnungsgemäß funktioniert. Um das Base64-kodierte Bild zu erstellen, werden die Rohdaten (die eigentliche Datei, vor allen „Lese“-Operationen) Byte für Byte gelesen. Die gelesenen Bytes werden dann durch Base64-Symbole dargestellt. Sobald dies geschehen ist, kann die Datei über ein Textprotokoll versendet werden.
Nachdem die Bilddaten verschlüsselt wurden, werden sie über HTTPS versendet, eine sichere Form von HTTP. Im Gegensatz zu HTTP, das Daten im Klartext sendet, verwendet HTTPS das Verschlüsselungsprotokoll Secure Socket Layer (SSL)/Transport Layer Security (TLS), um sicherzustellen, dass die Daten, die zu und von einem Server übertragen werden, privat und sicher bleiben. Die Bedeutung von HTTPS für den Schutz von Handelssignalen und anderer finanzbezogener Kommunikation kann kaum überschätzt werden. Ein skrupelloser Dritter, der in den Besitz von Handelssignalen gelangt, kann diese Informationen beispielsweise dazu nutzen, Geschäfte zu tätigen und den Markt zum Nachteil der unschuldigen Opfer der Handelssignale und zum Vorteil der Partei, die das Signal abgefangen hat, zu manipulieren. Die Prozessvisualisierung sieht folgendermaßen aus:
Zusammenfassend lässt sich sagen, dass die Bildkodierungs- und Sendemethode Bilddateien in ein textbasiertes Format umwandelt, das für die Webkommunikation geeignet ist. Außerdem gewährleistet es eine sichere Zustellung über HTTPS. Es ist wichtig, diese beiden Konzepte zu verstehen, wenn man Bilddaten in Anwendungen integrieren will. Ein offensichtliches Beispiel sind Handelssysteme, die Benachrichtigungen über die Plattform Telegram automatisieren, einer Plattform, die Nachrichten schnell und zuverlässig zustellen kann.
Implementation in MQL5
Die Implementierung von Image Relay in MQL5 beginnt mit dem Prozess der Aufnahme eines Screenshots des aktuellen Handelscharts innerhalb eines MQL5 Expert Advisors (EA). Wir verschlüsseln den Screenshot und senden ihn über Telegram. Wir implementieren diese Funktionalität hauptsächlich in OnInit, das ausgeführt wird, wenn der EA initialisiert wird. Wie bereits erwähnt, besteht der Zweck von OnInit darin, die notwendigen Komponenten des EA vorzubereiten und einzurichten und sicherzustellen, dass alles korrekt konfiguriert ist, bevor die Hauptlogik der Handelsoperation ausgeführt wird. Zunächst legen wir den Namen der Bilddatei für den Screenshot fest.
//--- Get ready to take a chart screenshot of the current chart #define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.jpg"
Hier machen wir den ersten Schritt im Arbeitsablauf, indem wir eine Konstante für den Namen der Screenshot-Datei festlegen. Wir erreichen dies mit der Direktive #define, die es uns ermöglicht, einen konstanten Wert zuzuweisen, auf den im gesamten Code Bezug genommen werden kann. Hier erstellen wir eine Konstante namens „SCREENSHOT_FILE_NAME“, die den Wert „Our Chart ScreenShot.jpg“ speichert. Und wir tun dies aus einem sehr guten Grund: Wenn wir jemals den Dateinamen benötigen, um etwas zu laden oder zu speichern, können wir einfach diese Konstante verwenden. Wenn wir den Dateinamen oder das Format ändern müssen, brauchen wir dies nur an dieser einen Stelle zu tun. Sie können erkennen, dass wir als Bildtyp ein Joint Photographic Experts Group (JPEG) gewählt haben. Sie können jedes Format wählen, das Sie für geeignet halten, z. B. Portable Network Graphics (PNG). Sie sollten jedoch bedenken, dass es erhebliche Unterschiede zwischen den Bildformaten gibt. JPG beispielsweise verwendet einen verlustbehafteten Komprimierungsalgorithmus, was bedeutet, dass ein Teil der Bilddaten verloren geht, die Bildgröße aber reduziert wird. Ein Beispiel für die Formate, die Sie verwenden können, ist unten abgebildet:
Wir integrieren die Screenshot-Funktion in den OnInit. Dadurch wird sichergestellt, dass das System den Status des Charts erfasst und speichert, sobald der Expert Advisor gestartet wird. Wir haben eine Konstante „SCREENSHOT_FILE_NAME“ deklariert, die als Ersatz für den tatsächlichen Namen der Chart-Bilddatei dient. Mit diesem Platzhalter vermeiden wir (meistens) den Fall, dass wir versuchen, zwei Dateien mit demselben Namen ungefähr zur gleichen Zeit zu speichern. Mit diesem Schritt stellen wir sicher, dass die Chart-Bilddatei die gleiche Grundstruktur hat, die sie benötigt, wenn wir das Bild zu diesem Zeitpunkt aktiv kodieren und übertragen.
Dieser Schritt ist wichtig, da er die Dateinamenskonvention festlegt und gewährleistet, dass wir das Bild des Charts bei der ersten Initialisierung des EA abrufen können. Von diesem Moment an können wir uns darauf konzentrieren, die Daten aus dem Chart zu erfassen, sie in eine Form zu bringen, die für menschliche Augen geeignet ist, und sie an den von uns gewählten Telegram-Kanal zu senden.
Um zu verhindern, dass wir versuchen, eine Datei mit einem ähnlichen Namen zu überschreiben und zu erstellen, müssen wir als Nächstes die vorhandene Datei löschen, sofern vorhanden, und eine neue Datei erstellen. Dies wird durch den folgenden Codeabschnitt erreicht.
//--- First delete an instance of the screenshot file if it already exists if(FileIsExist(SCREENSHOT_FILE_NAME)){ FileDelete(SCREENSHOT_FILE_NAME); Print("Chart Screenshot was found and deleted."); ChartRedraw(0); }
Hier stellen wir zunächst sicher, dass keine Instanzen der Screenshot-Datei vorhanden sind, bevor wir einen neuen Screenshot erstellen. Dies ist wichtig, da wir eine Verwechslung zwischen dem aktuellen Zustand des Charts und zuvor gespeicherten Screenshots vermeiden wollen. Zu diesem Zweck wird geprüft, ob eine Datei mit dem in der Konstante „SCREENSHOT_FILE_NAME“ gespeicherten Namen auf dem System existiert. Dies geschieht mit der Funktion FileIsExist, die das angegebene Verzeichnis überprüft und true zurückgibt, wenn die Datei vorhanden ist. Wenn die Datei existiert, wird sie mit der Funktion FileDelete gelöscht. Indem wir sicherstellen, dass das angegebene Verzeichnis frei von unserem alten Screenshot ist, schaffen wir Platz für den neuen Screenshot, den wir später im Prozess erstellen werden.
Nach dem Löschen senden wir mit der Druckfunktion eine Nachricht an das Terminal, um anzuzeigen, dass der Screenshot des Charts gefunden und erfolgreich gelöscht wurde. Diese kleine Rückmeldung kann bei der Fehlersuche sehr nützlich sein, da sie gleichzeitig eine Bestätigung dafür ist, dass das System die Löschung früherer Screenshots korrekt vornimmt. Schließlich wollen wir uns nicht angewöhnen, nicht existierende Dateien zu „löschen“. Wir zeichnen das Chart auch sofort neu (wir rufen die Funktion ChartRedraw auf), um sicherzustellen, dass wir mit einem aktuellen, visuellen Status des Charts arbeiten. Nach dieser Bereinigung können wir nun mit der Aufnahme des Screenshots fortfahren.
ChartScreenShot(0,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT);
In diesem Fall wird der Bildschirmausdruck des Charts mit der Funktion ChartScreenShot aufgenommen, die einen Schnappschuss des angegebenen Charts erstellt und als Bilddatei speichert. In unserem Fall übergeben wir die Parameter „0“, „SCREENSHOT_FILE_NAME“, „1366“, „768“ und „ALIGN_RIGHT“ an die Funktion, um zu steuern, wie das Bildschirmfoto aufgenommen und gespeichert wird.
- Der erste Parameter, „0“, gibt die ID des Charts an, von dem wir den Screenshot machen wollen. Ein Wert von 0 bezieht sich auf die derzeit aktive Chart. Wenn wir ein anderes Chart erfassen wollten, müssten wir die spezifische Chart-ID übergeben.
- Der zweite Parameter, „SCREENSHOT_FILE_NAME“, ist der Name der Datei, in der das Bildschirmfoto gespeichert werden soll. In unserem Fall ist dies die Konstante „Our Chart ScreenShot.jpg“. Diese Datei wird im Verzeichnis „Files“ des Terminals erstellt. Wenn sie noch nicht vorhanden ist, wird sie nach der Aufnahme des Screenshots erstellt.
- Der dritte und vierte Parameter, 1366 und 768, definieren die Abmessungen des Screenshots in Pixeln. Hier steht 1366 für die Breite des Screenshots und 768 für die Höhe. Diese Werte können je nach den Präferenzen des Nutzers oder der Größe des zu erfassenden Bildschirms angepasst werden.
- Der letzte Parameter, ALIGN_RIGHT, gibt an, wie das Bildschirmfoto im Chartfenster ausgerichtet werden soll. Mit ALIGN_RIGHT richten wir den Screenshot an der rechten Seite des Charts aus. Andere Ausrichtungsoptionen, wie ALIGN_LEFT oder ALIGN_CENTER, können je nach gewünschtem Ergebnis verwendet werden.
Aus irgendwelchen Gründen, die allerdings sehr unbedeutend sind, konnte der Screenshot nicht gespeichert werden. Um also keinen Stein auf dem anderen zu lassen, müssen wir eine Iteration einleiten, bei der wir einige Sekunden warten können, bis der Screenshot gespeichert wird.
// Wait for 30 secs to save screenshot if not yet saved int wait_loops = 60; while(!FileIsExist(SCREENSHOT_FILE_NAME) && --wait_loops > 0){ Sleep(500); }
Hier wird eine while-Schleife implementiert, die darauf wartet, dass die Screenshot-Datei erfolgreich gespeichert wird, und sicherstellt, dass sie am richtigen Ort und unter dem richtigen Namen gespeichert wurde, bevor wir fortfahren. Die Wartezeit selbst ist lang genug, dass die Screenshot-Datei unter normalen Umständen leicht im Dateisystem zu finden sein sollte (wenn sie tatsächlich während des Tests gespeichert werden sollte). Wir beginnen mit der ganzzahligen Variablen „wait_loops“, die auf 60 initialisiert ist. Jede Iteration der Schleife führt, wenn die Datei nicht gefunden wird, zu einer Wartezeit von einer halben Sekunde (500 Millisekunden (ms)) - insgesamt 30 Sekunden (60 Iterationen * 500 ms) vom Beginn der Schleife bis zu ihrem Ende, wenn die Datei nicht gefunden wird.
Bei jeder Iteration wird auch der Zähler „wait_loops“ dekrementiert, um zu verhindern, dass die Schleife endlos läuft, wenn die Datei nicht in der angegebenen Zeit erstellt wird. Außerdem verwenden wir die Funktion Sleep, um eine Verzögerung von 500 Millisekunden zwischen den einzelnen Prüfungen zu erzeugen. Dadurch wird verhindert, dass wir zu häufig prüfen und unser System mit zu vielen Anfragen zum Vorhandensein von Dateien überfordern.
Schließlich müssen wir prüfen, ob die Datei vorhanden ist, und wenn sie nicht vorhanden ist, hat es keinen Sinn, weiterzumachen, da unser gesamter Algorithmus und unsere Logik von der Bilddatei abhängen. Wenn dies der Fall ist, können wir mit den nächsten Schritten fortfahren.
if(!FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!"); return (INIT_FAILED); }
Hier definieren wir einen Mechanismus zur Fehlerbehandlung, um zu prüfen, ob die Screenshot-Datei erfolgreich gespeichert wurde. Nachdem wir einige Zeit gewartet haben, bis die Datei erstellt wurde, prüfen wir mit der Funktion FileIsExist, ob die Datei vorhanden ist. Wenn die Prüfung „false“ ergibt, d.h. die Datei nicht vorhanden ist, wird folgende Meldung ausgegeben: „THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!“ Diese Meldung zeigt an, dass wir die Screenshot-Datei nicht speichern konnten. Nach der Ausgabe dieser Fehlermeldung kann das Programm nicht fortgesetzt werden, da wir die Bilddatei vollständig als Grundlage für die Programmlogik benötigen. Daher beenden wir das Programm mit einem Rückgabewert von „INIT_FAILED“, was bedeutet, dass die Initialisierung nicht erfolgreich abgeschlossen werden konnte. Wenn der Screenshot gespeichert wurde, informieren wir auch über die Instanz.
else if(FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE."); }
Wenn Sie den Code ausführen, erhalten Sie folgende Ergebnisse:
Hier sehen Sie, dass es uns gelungen ist, eine erste Existenz der Bilddatei zu löschen und eine weitere zu speichern. Um auf das Bild auf Ihrem Computer zuzugreifen, klicken Sie mit der rechten Maustaste auf den Dateinamen, wählen Sie „Ordner öffnen“, und suchen Sie die Datei im Ordner „Dateien“.
Alternativ können Sie auch direkt auf die Bilddatei zugreifen, indem Sie den Navigator öffnen, ihn erweitern, mit der rechten Maustaste auf die Registerkarte „Dateien“ klicken und „Ordner öffnen“ wählen.
Dadurch wird der Dateiordner geöffnet, in dem die Bilddatei registriert wurde.
Hier können Sie sehen, dass der genaue Bildname registriert ist. Abschließend überprüfen wir die Dateigröße und den Dateityp, um festzustellen, ob die korrekten Angaben berücksichtigt wurden.
Wir können sehen, dass der Dateityp JPG ist und die Breite und die Höhe des Screenshots jeweils 1366 x 768 Pixel beträgt, wie angegeben. Wenn man z. B. einen anderen Dateityp, z. B. PNG, haben möchte, muss nur der Dateityp wie unten beschrieben geändert werden:
#define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.png"
Wenn wir diesen Codeabschnitt kompilieren und ausführen, erstellen wir ein weiteres Bild des Typs PNG, wie unten in einem Graphics Interchange Format (GIF)-Bildformat dargestellt:
Bis zu diesem Punkt ist es offensichtlich, dass wir den Snapshot des Charts erfolgreich aufgenommen und direkt in den Dateien gespeichert haben. Wir können also mit der Verschlüsselung der Bilddatei fortfahren, sodass sie über HTTPS übertragen werden kann. Zunächst müssen wir die Bilddatei öffnen und lesen.
int screenshot_Handle = INVALID_HANDLE; screenshot_Handle = FileOpen(SCREENSHOT_FILE_NAME,FILE_READ|FILE_BIN); if(screenshot_Handle == INVALID_HANDLE){ Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!"); return(INIT_FAILED); }
Im obigen Codeabschnitt konzentrieren wir uns auf Dateioperationen in MQL5, um eine Screenshot-Datei zu bearbeiten, die wir zuvor gespeichert haben. Wir deklarieren eine ganzzahlige Variable namens „screenshot_Handle“ und initialisieren sie mit dem Wert „INVALID_HANDLE“. Der Wert „screenshot_Handle“ dient als Verweis auf die Datei, und der Wert „INVALID_HANDLE“ dient als Platzhalter, der uns mitteilt, dass noch keine gültige Datei geöffnet wurde. Die Beibehaltung dieses Wertes stellt sicher, dass wir eine Datei über ihr Handle referenzieren können und dass wir alle Fehler behandeln können, die aus den Dateioperationen entstehen, falls etwas schief geht.
Als Nächstes versuchen wir, mit der Funktion FileOpen unser gespeichertes Bildschirmfoto zu öffnen. Wir geben ihm den Namen des Screenshots, der den Pfad zur Screenshot-Datei enthält. Wir geben ihm auch zwei Flags: FILE_READ und FILE_BIN. Das erste Flag sagt dem System, dass wir die Datei lesen wollen. Das zweite Flag, das wahrscheinlich das wichtigere von beiden ist, teilt dem System mit, dass die Datei Binärdaten enthält (was nicht damit zu verwechseln ist, dass das Bildschirmfoto eine Reihe von Einsen und Nullen ist). Da es sich bei dem Screenshot um ein Bild handelt und das Bild ein einigermaßen standardisiertes Format hat (wenn man dieses Format in ein wirklich „standardisiertes“, „einfaches“ oder „natürliches“ Format umwandelt, wird das Bild zu einer Reihe von Einsen und Nullen - keine Formatierung, keine Struktur, nur reine Mathematik -, eine andere Reihe von Einsen und Nullen, und das Bild sieht völlig anders aus, aber das ist hier nicht unser Anliegen), erwarten wir, dass wir eine Reihe von Bytes finden, die irgendwie dem Bild entsprechen.
Die Funktion „FileOpen“ gibt entweder ein gültiges Handle oder „INVALID_HANDLE“ zurück, nachdem wir versucht haben, die Datei zu öffnen. Wir überprüfen die Gültigkeit des Handles mit einer if-Anweisung. Ein ungültiger Handle bedeutet, dass die Datei nicht erfolgreich geöffnet wurde. Wir geben eine Fehlermeldung aus, die besagt, dass das Handle für den Screenshot ungültig ist. Entweder wurde der Screenshot nicht gespeichert oder es kann nicht darauf zugegriffen werden, was uns signalisiert, dass das Programm gegen eine Wand gestoßen ist. Wir machen keine weiteren Schritte und geben stattdessen „INIT_FAILED“ zurück, da es keinen Sinn hat, weiterzumachen, wenn wir die Bilddatei nicht lesen können. Wenn die Handle-ID tatsächlich gültig ist, informieren wir den Nutzer über den Erfolg.
else if (screenshot_Handle != INVALID_HANDLE){ Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING."); Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING."); }
Hier fügen wir einen weiteren Überprüfungsschritt hinzu, um sicherzustellen, dass die Screenshot-Datei korrekt geöffnet wurde. Nachdem wir geprüft haben, ob „screenshot_Handle“ gültig ist (nicht gleich „INVALID_HANDLE“), geben wir eine Reihe von Meldungen aus, die anzeigen, dass die Datei ordnungsgemäß geöffnet wurde. Dies ist nur eine weitere Möglichkeit, um zu bestätigen, dass „screenshot_Handle“ gut ist und dass wir bereit sind, weiterzumachen. Wir verwenden die Funktion Print für die erste Meldung, die dasselbe aussagt wie die zweite Meldung: dass der Screenshot erfolgreich gespeichert und zum Lesen geöffnet wurde. Beide Aussagen dienen dem Zweck, den erfolgreichen Abschluss des aktuellen Schritts in unserem Arbeitsablauf zu bestätigen.
Anschließend wird die Handle-ID angezeigt, die die Datei eindeutig identifiziert und es ermöglicht, die nachfolgenden Operationen (Lesen, Schreiben und Verschlüsseln) mit der Datei durchzuführen. Die Handle-ID ist auch für die Fehlersuche nützlich. Sie bestätigt, dass das System Ressourcen für die Verwaltung dieser Datei erhalten und zugewiesen hat. Wir schließen mit einer Druckanweisung, die uns darüber informiert, dass das System nun bereit ist, den nächsten Vorgang auszuführen, nämlich den Screenshot zu verschlüsseln, damit er über das Netzwerk mit dem HTTPS-Protokoll übertragen werden kann.
Als Nächstes können wir überprüfen, ob der Handle tatsächlich aufgezeichnet und gespeichert wurde und ob er einen gültigen Inhalt hat.
int screenshot_Handle_Size = (int)FileSize(screenshot_Handle); if (screenshot_Handle_Size > 0){ Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size); }
Hier erhalten und überprüfen wir die Größe der zuvor geöffneten Screenshot-Datei mit ihrem Handle. Wir rufen die Funktion FileSize mit dem Screenshot-Handle auf, die die Größe der Datei in Bytes zurückgibt. Diesen Wert weisen wir dann einer ganzzahligen Variablen namens „screenshot_Handle_Size“ zu. Wenn die Größe größer als Null ist, was darauf hindeutet, dass die Datei Daten enthält, wird die Dateigröße in das Protokoll geschrieben. Dieser Schritt ist sehr nützlich, denn er gibt uns die Gewissheit, dass der Screenshot richtig gespeichert wurde und einen gültigen Inhalt hat, bevor wir die Datei kodieren und über HTTP senden.
Wenn das Handle tatsächlich einen gültigen Inhalt hat, bedeutet dies, dass wir die richtige Datei geöffnet haben, und wir können uns darauf vorbereiten, die Binärdaten der Screenshot-Datei in ein Array zur Dekodierung einzulesen.
uchar photoArr_Data[]; ArrayResize(photoArr_Data,screenshot_Handle_Size); FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size); if (ArraySize(photoArr_Data) > 0){ Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data)); }
Zunächst deklarieren wir ein uchar-Array mit dem Namen „photoArr_Data“, das die Binärdaten aufnehmen wird. Anschließend wird die Größe dieses Arrays durch Aufruf der Funktion ArrayResize an die Größe der Screenshot-Datei angepasst. Als Nächstes lesen wir den Inhalt der Screenshot-Datei in das „photoArr_Data-Array“, beginnend bei Index 0 und bis zum Ende der Datei (der screenshot_Handle_Size) mit Hilfe der Funktion FileReadArray. Anschließend wird die Größe des Arrays „photoArr_Data“ nach dem Laden überprüft, und wenn sie größer als 0 ist, d. h. wenn es nicht leer ist, wird seine Größe protokolliert. In der Regel ist dies der Teil des Codes, der das Lesen und Verarbeiten der Screenshot-Datei übernimmt, sodass sie für die Kodierung und Übertragung verwendet werden kann.
Nachdem wir den Inhalt der Datei gelesen und gespeichert haben, müssen wir nun die Bilddatei schließen. Dies geschieht durch seinen Handle.
FileClose(screenshot_Handle);
Hier schließen wir schließlich die Screenshot-Datei, nachdem wir ihre Daten erfolgreich in das Speicher-Array eingelesen haben. Wir rufen die Funktion FileClose auf, um den mit der Screenshot-Datei verbundenen Handle freizugeben. Dadurch werden Systemressourcen, die beim Öffnen der Datei zugewiesen wurden, wieder freigegeben. Es muss unbedingt sichergestellt werden, dass die Datei geschlossen wird, bevor wir versuchen, andere Operationen mit der Datei durchzuführen, wie z. B. auf sie zuzugreifen, sie zu lesen oder zu schreiben. Die Funktion signalisiert, dass alle Zugriffsoperationen auf die Datei abgeschlossen sind und wir nun zur nächsten Phase des Prozesses übergehen: der Kodierung der Bildschirmdaten und ihrer Vorbereitung für die Übertragung. Sobald wir dies ausführen, erhalten wir das folgende Ergebnis:
Wie Sie sehen, werden die binären Bilddaten korrekt gelesen und im Speicherfeld gespeichert. Um die Daten zu sehen, können wir sie mit der Funktion ArrayPrint wie folgt in das Protokoll drucken:
ArrayPrint(photoArr_Data);
Nach dem Drucken erhalten wir diese Daten:
Es ist offensichtlich, dass wir die gesamten Daten, d. h. bis zu 320894, lesen, kopieren und speichern.
Als Nächstes müssen wir die Fotodaten für die Übertragung über HTTP vorbereiten, indem wir sie im Base64-Format kodieren. Da Binärdaten wie Bilder nicht direkt über HTTP übertragen werden können, müssen wir die Binärdaten mithilfe der Base64-Kodierung in einen Zeichenkette im Format ASCII umwandeln. Dadurch wird sichergestellt, dass die Daten sicher in die HTTP-Anfrage aufgenommen werden können. Dies wird durch den folgenden Codeabschnitt erreicht.
//--- create boundary: (data -> base64 -> 1024 bytes -> md5) //Encodes the photo data into base64 format //This is part of preparing the data for transmission over HTTP. uchar base64[]; uchar key[]; CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64); if (ArraySize(base64) > 0){ Print("Transformed BASE-64 data = ",ArraySize(base64)); //Print("The whole data is as below:"); //ArrayPrint(base64); }
Zunächst richten wir zwei Arrays ein. Die erste ist „base64“. Dieser enthält die verschlüsselten Daten. Das zweite Feld ist „key“. Wir verwenden den Begriff „key" in diesem Zusammenhang nie, aber die Verschlüsselungsfunktion erfordert ihn. Die Funktion, die die Base64-Kodierung vornimmt, heißt CryptEncode. Dazu werden vier Parameter benötigt: die Art der Kodierung („CRYPT_BASE64“), die binären Quelldaten („photoArr_Data“), der Verschlüsselungsschlüssel („key“) und das Ausgabe-Array („base64“). Die Funktion CryptEncode wandelt die Binärdaten in das Base64-Format um und speichert das Ergebnis in dem Array „base64“. Wenn wir die Größe von „base64“ mit der Funktion ArraySize überprüfen, bedeutet dies, dass die Kodierung erfolgreich war, wenn „base64“ überhaupt Elemente enthält, d. h. wenn sie größer als 0 ist.
Um diese Daten in das Journal zu drucken, verwenden wir die Funktion ArrayPrint.
Print("Transformed BASE-64 data = ",ArraySize(base64)); Print("The whole data is as below:"); ArrayPrint(base64);
Wir erhalten das folgende Ergebnis:
Es ist zu erkennen, dass es eine erhebliche Abweichung zwischen den ursprünglichen binären Daten der Größe 320894 und den neu konvertierten Daten der Größe 427860 gibt. Diese Abweichung ist eine Folge der Datenumwandlung und -kodierung.
Als Nächstes müssen wir eine Teilmenge der Base64-kodierten Daten vorbereiten, um sicherzustellen, dass wir einen überschaubaren Teil davon für die nächsten Schritte in unserem Prozess verarbeiten können. Konkret müssen wir uns darauf konzentrieren, die ersten 1024 Bytes der kodierten Daten in ein temporäres Array zur weiteren Verwendung zu kopieren.
//Copy the first 1024 bytes of the base64-encoded data into a temporary array uchar temporaryArr[1024]= {0}; ArrayCopy(temporaryArr,base64,0,0,1024);
Zunächst wird ein temporäres Array, „temporaryArr", mit einer Größe von 1024 Bytes angelegt. Wir initialisieren alle seine Werte mit Null. Wir verwenden dieses Array, um das erste Segment der Base64-kodierten Daten zu speichern. Da der Initialisierungswert Null ist, vermeiden wir mögliche Probleme mit Restinformationen im Speicher, in dem das temporäre Array gespeichert ist.
Dann verwenden wir die Funktion ArrayCopy, um die ersten 1024 Bytes von „base64“ nach „temporaryArr“ zu verschieben. Dadurch wird der Kopiervorgang sauber und effizient durchgeführt. Die Gründe dafür und die Einzelheiten des Kopiervorgangs sind eine eigene Geschichte, aber ich möchte nur einige Dinge erwähnen. Die Initialisierung hat den Nebeneffekt, dass wir uns keine Sorgen mehr über den ersten Teil der Base64-kodierten Daten machen müssen, wenn wir uns diesen als eine Art zufälliges Kauderwelsch vorstellen. Schreiben wir das leere temporäre Array. Wir erreichen dies durch den folgenden Code.
Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:"); ArrayPrint(temporaryArr);
Nach dem Kompilieren ergibt sich folgendes Bild:
Wir sehen, dass das temporäre Array nur mit Nullen gefüllt ist. Diese Nullen werden dann durch die ersten 1024 Werte der ursprünglich formatierten Daten ersetzt. Wir können diese Daten über eine ähnliche Logik wieder anzeigen.
Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:"); ArrayPrint(temporaryArr);
Die ausgefüllten, vorläufigen Daten werden wie folgt dargestellt:
Nachdem wir diese temporären Daten erhalten haben, müssen wir aus den ersten 1024 Bytes der Base64-kodierten Daten einen Hash des Message-Digest-Algorithmus 5 (MD5) erzeugen. Dieser MD5-Hash wird als Teil der Begrenzung in einer multipart/form-data-Struktur verwendet, die häufig in HTTP-POST-Anfragen für das Hochladen von Dateien verwendet wird.
//Create an MD5 hash of the temporary array //This hash will be used as part of the boundary in the multipart/form-data uchar md5[]; CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5); if (ArraySize(md5) > 0){ Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5)); Print("MD5 HASH boundary in multipart/form-data is as follows:"); ArrayPrint(md5); }
Zunächst deklarieren wir ein Array namens „md5", um das Ergebnis des MD5-Hashes zu speichern. Der MD5-Algorithmus (wobei „MD" für „Message Digest" steht) ist eine kryptografische Hash-Funktion, die einen 128-Bit-Hash-Wert erzeugt. Der Hash wird in der Regel als eine Zeichenkette aus 32 hexadezimalen Ziffern dargestellt.
In diesem Fall verwenden wir die in MQL5 eingebaute Funktion CryptEncode mit dem Parameter CRYPT_HASH_MD5, um den Hash zu berechnen. Wir übergeben der Funktion ein temporäres Array namens „temporaryArr", das die ersten 1024 Bytes der Base64-kodierten Daten enthält. Der Parameter „key" wird normalerweise für zusätzliche kryptografische Operationen verwendet, ist aber für MD5 nicht erforderlich und wird in diesem Zusammenhang auf ein leeres Array gesetzt. Das Ergebnis der Hashing-Operation wird in dem Array „md5" gespeichert.
Nach der Berechnung des Hashwerts wird das „md5“-Array auf Nicht-Leerheit geprüft, indem die Anzahl der Elemente im Array mit der Funktion ArraySize überprüft wird. Wenn das Array Elemente enthält, wird zunächst die Größe des MD5-Hashes und dann der tatsächliche Hash-Wert protokolliert. Dieser Hash-Wert wird verwendet, um eine Begrenzungszeichenkette im Format multipart/form-data zu erstellen, mit deren Hilfe die verschiedenen Teile der übertragenen HTTP-Anfrage voneinander getrennt werden können. Der MD5-Algorithmus wird hier ausschließlich aufgrund seiner Allgemeingültigkeit und des eindeutigen Wertes verwendet, nicht weil er der beste oder sicherste Algorithmus ist. Wenn wir dies ausführen, erhalten wir die folgenden Daten:
Hier können wir sehen, dass wir die MD5-Hash-Daten in Zahlenform erhalten. Daher muss der MD5-Hash in eine hexadezimale Zeichenkette umgewandelt und dann so gekürzt werden, dass er eine bestimmte Längenanforderung für die Verwendung als Begrenzung in multipart/form-data-HTTP-Anfragen erfüllt, die normalerweise 16 beträgt.
//Format MD5 hash as a hexadecimal string & //truncate it to 16 characters to create the boundary. string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash int total=ArraySize(md5); for(int i=0; i<total; i++){ HexaDecimal_Hash+=StringFormat("%02X",md5[i]); } Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash); HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters //done to comply with a specific length requirement for the boundary //in the multipart/form-data of the HTTP request. Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash);
Zunächst deklarieren wir eine String-Variable, „HexaDecimal_Hash“, die die hexadezimale Form des MD5-Hashes enthält. Diese Zeichenkette dient als Grenzmarkierung, um verschiedene Teile unserer HTTP-Anfrage-Nutzdaten zu trennen.
Als Nächstes durchlaufen wir in einer Schleife jedes Byte des im md5-Array gespeicherten Hashes. Wir wandeln jedes Byte in eine zweistellige Zeichenkette um, indem wir den Formatbezeichner „%02X“ verwenden. Der Teil „%0“ des Bezeichners zeigt an, dass die Zeichenfolge gegebenenfalls mit führenden Nullen aufgefüllt werden sollte, um sicherzustellen, dass jedes Byte mit zwei Zeichen dargestellt wird. Die „02“ gibt an, dass die Darstellung aus mindestens zwei Zeichen besteht; das „X“ zeigt an, dass es sich bei den Zeichen um hexadezimale Ziffern (ggf. mit Großbuchstaben) handeln muss.
Auch hier werden diese hexadezimalen Zeichen an die Zeichenkette „HexaDecimal_Hash“ angehängt. Schließlich geben wir den Inhalt der Zeichenkette in das Protokoll ein, um zu überprüfen, ob sie korrekt gebildet wurde. Ein Durchlauf des Programms ergibt folgende Informationen:
Dies war ein Erfolg. Als Nächstes müssen wir die Dateidaten für eine multipart/form-data HTTP POST-Anfrage erstellen und vorbereiten, mit der das Foto über die Telegram API an den Telegram-Chat gesendet wird. Dazu gehört die Vorbereitung des Hauptteil der Anfrage, um sowohl die Formularfelder als auch die Dateidaten in einem Format zu enthalten, das der Server korrekt verarbeiten kann. Dies erreichen wir mit dem folgenden Codeabschnitt.
//--- WebRequest char DATA[]; string URL = NULL; URL = TG_API_URL+"/bot"+botTkn+"/sendPhoto"; //--- add chart_id //Append a carriage return and newline character sequence to the DATA array. //In the context of HTTP, \r\n is used to denote the end of a line //and is often required to separate different parts of an HTTP request. ArrayAdd(DATA,"\r\n"); //Append a boundary marker to the DATA array. //Typically, the boundary marker is composed of two hyphens (--) //followed by a unique hash string and then a newline sequence. //In multipart/form-data requests, boundaries are used to separate //different pieces of data. ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); //Add a Content-Disposition header for a form-data part named chat_id. //The Content-Disposition header is used to indicate that the following data //is a form field with the name chat_id. ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n"); //Again, append a newline sequence to the DATA array to end the header section //before the value of the chat_id is added. ArrayAdd(DATA,"\r\n"); //Append the actual chat ID value to the DATA array. ArrayAdd(DATA,chatID); //Finally, Append another newline sequence to the DATA array to signify //the end of the chat_id form-data part. ArrayAdd(DATA,"\r\n");
Wir beginnen mit der Einrichtung des Arrays „DATA“ und der „URL“ für die HTTP-Anfrage. Die „URL“ besteht aus drei Teilen: der Basis-URL für die API („TG_API_URL“), dem Token für den Bot, der den Bot gegenüber der API identifiziert („botTkn“), und dem Endpunkt zum Senden eines Fotos an einen Chat („/sendPhoto“). Diese URL gibt an, an welchen „entfernten Server“ wir unsere „Nutzlast“ senden - das Foto, das wir senden wollen, und die Informationen, die wir an das Foto anhängen wollen. Die Endpunkt-URL ändert sich nicht; sie ist bei jeder Anfrage die gleiche. Unsere Anfragen werden an dieselbe Stelle weitergeleitet, unabhängig davon, ob wir ein Foto oder mehrere senden, ob wir Fotos an verschiedene Chats senden usw.
Danach fügen wir eine Begrenzungsmarkierung an den Rand unseres Datenblocks. Er besteht aus zwei Bindestrichen (--) und unserem eindeutigen Grenzhash („HexaDecimal_Hash“). Insgesamt erscheint er so: „--HexaDecimal_Hash“. Diese Begrenzungsmarkierung erscheint am Anfang des Datenblocks für den nächsten Teil der Anfrage, der ein Formularfeld „chart_id“ ist. Die Kopfzeile Content-Disposition gibt an, dass der nächste Teil (der nächste Datenblock) der multipart/form-data-Anfrage ein Formularfeld ist, und nennt den Namen dieses Feldes („chart_id“).
Wir fügen diese Kopfzeile und ein Zeilenumbruchzeichen („/r/n“) hinzu, um das Ende des Kopfzeilenabschnitts anzuzeigen. Nach dem Kopfteil fügen wir den Wert „chartID“ in das DATA-Array ein, gefolgt von einem weiteren Zeilenumbruch („/r/n“), um das Ende des Formular-Datenteils „chart_id“ anzuzeigen. Dieser Prozess garantiert, dass das Formularfeld korrekt formatiert und von den anderen Teilen der Anfrage getrennt ist, um sicherzustellen, dass die API von Telegram die Daten korrekt empfängt und verarbeitet.
Sie haben vielleicht bemerkt, dass wir im Code zwei „Überladungsfunktionen“ verwendet haben. Im Folgenden finden Sie einen Codeausschnitt.
//+------------------------------------------------------------------+ // ArrayAdd for uchar Array void ArrayAdd(uchar &destinationArr[],const uchar &sourceArr[]){ int sourceArr_size=ArraySize(sourceArr);//get size of source array if(sourceArr_size==0){ return;//if source array is empty, exit the function } int destinationArr_size=ArraySize(destinationArr); //resize destination array to fit new data ArrayResize(destinationArr,destinationArr_size+sourceArr_size,500); // Copy the source array to the end of the destination array. ArrayCopy(destinationArr,sourceArr,destinationArr_size,0,sourceArr_size); } //+------------------------------------------------------------------+ // ArrayAdd for strings void ArrayAdd(char &destinationArr[],const string text){ int length = StringLen(text);// get the length of the input text if(length > 0){ uchar sourceArr[]; //define an array to hold the UTF-8 encoded characters for(int i=0; i<length; i++){ // Get the character code of the current character ushort character = StringGetCharacter(text,i); uchar array[];//define an array to hold the UTF-8 encoded character //Convert the character to UTF-8 & get size of the encoded character int total = ShortToUtf8(character,array); //Print("text @ ",i," > "text); // @ "B", IN ASCII TABLE = 66 (CHARACTER) //Print("character = ",character); //ArrayPrint(array); //Print("bytes = ",total) // bytes of the character int sourceArr_size = ArraySize(sourceArr); //Resize the source array to accommodate the new character ArrayResize(sourceArr,sourceArr_size+total); //Copy the encoded character to the source array ArrayCopy(sourceArr,array,sourceArr_size,0,total); } //Append the source array to the destination array ArrayAdd(destinationArr,sourceArr); } }
Hier definieren wir zwei nutzerdefinierte Funktionen für das Hinzufügen von Daten zu Arrays in MQL5, die speziell für den Umgang mit den Typen uchar und string entwickelt wurden. Diese Funktionen erleichtern den Aufbau von HTTP-Anfragedaten, indem sie verschiedene Informationen an ein bestehendes Array anhängen und sicherstellen, dass das endgültige Datenformat korrekt und für die Übertragung geeignet ist. Zum besseren Verständnis haben wir die Funktionen mit Kommentaren versehen, aber lassen Sie uns noch einmal kurz den Code durchgehen.
Die erste Funktion, „ArrayAdd“, gilt für Arrays von Zeichen ohne Vorzeichen (uchar). Es ist so eingerichtet, dass es Daten aus einem Quell-Array an ein Ziel-Array anhängt. Zunächst wird ermittelt, wie viele Elemente sich in der Ausgangsmatrix befinden. Dies wird durch den Aufruf der einfachen Funktion ArraySize für das Quell-Array erreicht. Mit dieser Information prüfen wir, ob das Quell-Array Daten enthält. Wenn dies nicht der Fall ist, vermeiden wir die überflüssige Fortsetzung, indem wir die Funktion vorzeitig beenden. Wenn es Daten enthält, gehen wir zum nächsten Schritt über, der darin besteht, die Größe des Zielfeldes zu ändern, um diese Daten aufzunehmen. Wir tun dies, indem wir die Funktion ArrayResize auf dem Ziel-Array aufrufen, die wir jetzt jedoch mit Zuversicht aufrufen können, da wir wissen, dass sie korrekt funktionieren wird.
Die andere Funktion, die Zeichenketten zu einem char-Array hinzufügt, funktioniert wie folgt: Sie berechnet die Länge der eingegebenen Zeichenkette. Wenn die Eingabezeichenkette nicht leer ist, wird jedes Zeichen der Zeichenkette entnommen, sein Code ermittelt und an das Zielarray angehängt, wobei es in UTF-8 konvertiert wird. Um die Zeichenkette zu konvertieren und an das Zielarray anzuhängen, ändert diese Funktion die Größe des Quellarrays für die Zwischenspeicherung der hinzuzufügenden Zeichenkettendaten. Sie stellt sicher, dass die UTF-8-Darstellung der Zeichenkette sowie die Zeichenkette selbst korrekt in dem endgültigen Datenarray gespeichert werden, das für die Konstruktion von HTTP-Anfragekörpern oder anderen Datenstrukturen verwendet wird.
Um zu sehen, was wir getan haben, lassen Sie uns eine Logik implementieren, die die resultierenden Daten in Bezug auf die Chat-ID, die in der HTTP-Anfrage gesendet wird, drucken wird.
Print("CHAT ID DATA:"); ArrayPrint(DATA); string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8); Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data);
Zunächst verwenden wir die Funktion ArrayPrint, um das Rohdatenfeld darzustellen. Die Funktion gibt den Inhalt des Arrays für uns aus. Anschließend erfolgt die Umwandlung des Arrays „DATA“ von einem Zeichenarray in eine Zeichenkette. Die Funktion, die wir verwenden, ist CharArrayToString, die die rohen Byte-Daten in „DATA“ in eine UTF-8-kodierte Zeichenkette umwandelt. Die hier verwendeten Parameter geben an, dass das gesamte Array („WHOLE_ARRAY“) konvertiert werden soll und dass die Zeichenkodierung UTF-8 (CP_UTF8) ist. Diese Konvertierung ist notwendig, weil die HTTP-Anfrage die Daten als Zeichenketten verlangt.
Abschließend haben wir eine Zeichenfolge „chatID_Data“, deren endgültiges Format so beschaffen ist, dass sie in die HTTP-Anfrage aufgenommen wird. Mit der Funktion Print können wir sehen, wie die Ausgabe in der Anfrage aussehen wird.
Wir können sehen, dass wir die korrekten Chat-ID-Daten in das Array einfügen können. Mit der gleichen Logik können wir auch die Bilddaten hinzufügen, um den multipart/form-data-Anfragekörper für das Senden des Fotos über HTTP an die Telegram-API zu konstruieren.
ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,photoArr_Data); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n");
Zunächst fügen wir die Begrenzungsmarkierung für den Fototeil der Multipart/Formular-Daten ein. Wir tun dies mit der Zeile ArrayAdd(DATA,“--“+HexaDecimal_Hash+“\r\n“). Die Begrenzungsmarke, die aus zwei Bindestrichen und der „HexaDecimal_Hash“ besteht, dient zur Trennung der verschiedenen Teile der mehrteiligen Anfrage. Die „HexaDecimal_Hash“, ein eindeutiger Bezeichner für die Grenze, ist eine Garantie dafür, dass jeder Teil des Antrags unverwechselbar vom nächsten getrennt ist.
Dann fügen wir den Content-Disposition-Header für den Fototeil der Formulardaten ein. Wir fügen sie mit der Funktion ArrayAdd wie folgt hinzu: ArrayAdd(DATA, "Content-Disposition: form-data; name=\" photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n"). Diese Kopfzeile gibt an, dass es sich bei den folgenden Daten um eine Datei handelt, und zwar um die Datei „Upload_ScreenShot.jpg“. Da wir über den Teil name=\" photo\" der Kopfzeile angegeben haben, dass das Formulardatenfeld, mit dem wir gerade arbeiten, den Namen \" photo\" hat, weiß der Server, dass er die Datei „Upload_ScreenShot.jpg“ als Teil dieses Feldes erwartet, wenn er die eingehende Anfrage verarbeitet. Die Datei ist nur ein Bezeichner, den Sie nach Belieben ändern können.
Danach wird mit ArrayAdd(DATA, "\r\n") eine Zeilenumbruchsequenz an die Kopfzeilen der Anfrage angehängt. Dies zeigt das Ende des Kopfteils und den Beginn der eigentlichen Dateidaten an. Anschließend werden mit ArrayAdd(DATA, photoArr_Data) die eigentlichen Fotodaten an das Array DATA angehängt. Diese Codezeile fügt die Binärdaten des Screenshots (zuvor base64-kodiert) an den Anfragekörper an. Die multipart/form-data-Nutzdaten enthalten nun die Fotodaten.
Schließlich fügen wir mit ArrayAdd(DATA, "\r\n") eine weitere Zeilenumbruchsequenz und mit ArrayAdd(DATA, "--" + HexaDecimal_Hash + "--\r\n") die Begrenzungsmarkierung zum Abschluss des Fototeils hinzu. Das -- am Ende des Begrenzungszeichens zeigt das Ende des mehrteiligen Abschnitts an. Diese letzte Begrenzung stellt sicher, dass der Server das Ende des Fotodatenabschnitts innerhalb der Anfrage korrekt erkennt. Um die gesendeten Daten zu sehen, drucken wir sie wieder über eine ähnliche Funktion wie die vorherige in den Log-Bereich.
Print("FINAL FULL PHOTO DATA BEING SENT:"); ArrayPrint(DATA); string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP); Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data);
Das sind die Ergebnisse, die wir erhalten:
Schließlich erstellen wir die HTTP-Anfrage-Header, die für das Senden einer multipart/form-data-Anfrage an die Telegram-API benötigt werden.
string HEADERS = NULL; HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n";
Wir beginnen mit der Definition einer Zeichenkette „HEADERS“, die mit „NULL“ initialisiert wird. Diese Zeichenfolge enthält die HTTP-Kopfzeilen, die für die Anfrage festgelegt werden müssen. Der Header, den wir unbedingt setzen müssen, ist Content-Type. Der Content-Type-Header gibt an, welche Art von Daten gesendet wird und wie sie formatiert sind.
Wir weisen der Zeichenfolge den richtigen Content-Type-Wert zu. Der entscheidende Teil ist die Zeichenkette „HEADERS“ selbst. Wir müssen das „Format“ der HTTP-Anfrage verstehen, um zu begreifen, warum diese besondere Zuordnung zum „HEADERS“-String notwendig ist. Das Format der Anfrage besagt, dass die Anfrage mit der Kopfzeile „Content-Type: multipart/form-data“ gesendet wird. Nachdem wir all dies getan haben, können wir nun die Web-Anfrage starten. Zunächst sollten wir den Nutzer informieren, indem wir ihm den unten stehenden Antrag zusenden.
Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.");
Aus dem ursprünglichen Code kommentieren wir die unnötigen Parameter des WebRequest aus und wechseln zu den letzten.
//char data[]; // Array to hold data to be sent in the web request (empty in this case) char res[]; // Array to hold the response data from the web request string resHeaders; // String to hold the response headers from the web request //string msg = "EA INITIALIZED ON CHART " + _Symbol; // Message to send, including the chart symbol //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID + // "&text=" + msg; // Send the web request to the Telegram API int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);
In diesem Fall werden nur die neue URL, die Kopfzeilen und die Daten der Bilddatei an die Funktion angehängt, die gesendet werden sollen. Die Antwortlogik bleibt intakt und unverändert wie unten dargestellt:
// Check the response status of the web request if (send_res == 200) { // If the response status is 200 (OK), print a success message Print("TELEGRAM MESSAGE SENT SUCCESSFULLY"); } else if (send_res == -1) { // If the response status is -1 (error), check the specific error code if (GetLastError() == 4014) { // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL"); } // Print a general error message if the request fails Print("UNABLE TO SEND THE TELEGRAM MESSAGE"); } else if (send_res != 200) { // If the response status is not 200 or -1, print the unexpected response code and error code Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError()); }
Wenn wir das Programm ausführen, erhalten wir folgendes Ergebnis:
Auf MetaTrader 5:
In Telegram:
Jetzt ist es offensichtlich, dass wir die Bilddatei erfolgreich vom MetaTrader 5-Handelsterminal an den Telegram-Chat gesendet haben. Wir haben jedoch nur einen leeren Screenshot übermittelt. Um der Bilddatei eine Legende hinzuzufügen, implementieren wir die folgende Logik, die der multipart/form-data-Anfrage eine optionale Legende hinzufügt, die zusammen mit dem Chart-Screenshot an die Telegram-API gesendet wird.
//--- Caption string CAPTION = NULL; CAPTION = "Screenshot of Symbol: "+Symbol()+ " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ ") @ Time: "+TimeToString(TimeCurrent()); if(StringLen(CAPTION) > 0){ ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,CAPTION); ArrayAdd(DATA,"\r\n"); } //---
Zunächst wird die Zeichenkette „CAPTION“ als „NULL“ initialisiert und dann mit den relevanten Details konstruiert. Die Legende enthält das Handelssymbol, den Zeitrahmen des Charts und die aktuelle Uhrzeit, formatiert als Zeichenkette. Dann wird geprüft, ob die Zeichenkette von „CAPTION“ eine Länge größer als Null hat. Ist dies der Fall, wird die Legende dem Array „DATA“ hinzugefügt, das zum Aufbau der mehrteiligen Formulardaten verwendet wird. Dazu gehört das Anfügen einer Begrenzungsmarke, die Angabe des Formulardatenteils als Legende und die Aufnahme des Legendeninhalts selbst. Wenn wir dies ausführen, erhalten wir die folgenden Ergebnisse:
Das war ein Erfolg. Wir sehen, dass wir nicht nur die Bilddatei erhalten, sondern auch eine beschreibende Legende, die den Namen des Symbols, den Zeitraum und die Zeit des betreffenden Charts angibt.
Bis zu diesem Punkt erhalten wir den Screenshot des Charts, an das das Programm angehängt ist. Für den Fall, dass man ein anderes Chart öffnen und ändern möchte, müssen wir dafür eine andere Logik implementieren.
long chart_id=ChartOpen(_Symbol,_Period); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); // update chart int wait=60; while(--wait>0){//decrease the value of wait by 1 before loop condition check if(SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)){ break; // if prices up to date, terminate the loop and proceed } } ChartRedraw(chart_id); ChartSetInteger(chart_id,CHART_SHOW_GRID,false); ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue); ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon); ChartScreenShot(chart_id,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); //Sleep(10000); // sleep for 10 secs to see the opened chart ChartClose(chart_id); //---
Hier beginnen wir mit dem Öffnen eines neuen Charts für das angegebene Symbol und den angegebenen Zeitrahmen mit Hilfe der Funktion ChartOpen, wobei wir die vordefinierten Variablen _Symbol und _Period verwenden. Wir weisen die ID des neuen Charts der Variablen „chart_id“ zu. Wir verwenden dann „chart_id“, um sicherzustellen, dass der neue Chart im Vordergrund der MetaTrader-Umgebung sichtbar ist und nicht von früheren Charts verdeckt wird.
Danach starten wir eine Schleife mit maximal 60 Iterationen. Innerhalb dieser Schleife wird immer wieder überprüft, ob das Chart synchronisiert ist. Um die Synchronisation zu testen, verwenden wir die Funktion SeriesInfoInteger mit den Parametern _Symbol, _Period und SERIES_SYNCHRONIZED. Wenn wir feststellen, dass das Chart synchronisiert ist, brechen wir die Schleife ab. Sobald wir bestätigen, dass das Chart synchronisiert ist, verwenden wir die Funktion ChartRedraw mit dem Parameter „chart_id“, um das Chart zu aktualisieren.
Wir passen verschiedene Charteinstellungen an, um das Chart nach unseren Wünschen zu gestalten. Wir verwenden die Funktion ChartSetInteger, um die Farben für den Hintergrund des Charts sowie für Auf- und Abwärts-Kerzen einzustellen. Die von uns festgelegten Farben sorgen für visuelle Klarheit und helfen uns, die verschiedenen Elemente des Charts leicht zu unterscheiden. Wir machen das Chart auch optisch weniger unübersichtlich, indem wir das Gitter und die Punkttrennzeichen deaktivieren. Sie können Ihre Tabelle an dieser Stelle nach eigenem Ermessen ändern. Zum Schluss machen wir einen Screenshot des Charts zur Verwendung bei der Übertragung. Wir wollen nicht, dass das Chart unnötig offen bleibt, also schließen wir es nach der Aufnahme des Screenshots. Hierfür verwenden wir die Funktion ChartClose. Wenn wir das Programm ausführen, erhalten wir die folgenden Ergebnisse:
Es ist klar, dass wir ein Chart öffnen, es nach unseren Wünschen verändern und es am Ende schließen, nachdem wir einen Schnappschuss davon gemacht haben. Um den Prozess des Öffnens und Schließens des Charts zu veranschaulichen, sollten wir den Chart mit einer Verzögerung von 10 Sekunden anzeigen.
Sleep(10000); // sleep for 10 secs to see the opened chart
Hier lassen wir das Chart einfach 10 Sekunden lang offen, damit wir sehen können, was im Hintergrund unseres Programms vor sich geht. Nach dem Kompilieren ergibt sich folgendes Bild:
Der vollständige Quellcode, der für die Aufnahme von Screenshots, die Verschlüsselung und das Senden vom Handelsterminal an den Telegram-Chat verantwortlich ist, lautet wie folgt:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Get ready to take a chart screenshot of the current chart #define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.jpg" //--- First delete an instance of the screenshot file if it already exists if(FileIsExist(SCREENSHOT_FILE_NAME)){ FileDelete(SCREENSHOT_FILE_NAME); Print("Chart Screenshot was found and deleted."); ChartRedraw(0); } //--- long chart_id=ChartOpen(_Symbol,_Period); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); // update chart int wait=60; while(--wait>0){//decrease the value of wait by 1 before loop condition check if(SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)){ break; // if prices up to date, terminate the loop and proceed } } ChartRedraw(chart_id); ChartSetInteger(chart_id,CHART_SHOW_GRID,false); ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue); ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon); ChartScreenShot(chart_id,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); Print("OPENED CHART PAUSED FOR 10 SECONDS TO TAKE SCREENSHOT.") Sleep(10000); // sleep for 10 secs to see the opened chart ChartClose(chart_id); //--- //ChartScreenShot(0,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); // Wait for 30 secs to save screenshot if not yet saved int wait_loops = 60; while(!FileIsExist(SCREENSHOT_FILE_NAME) && --wait_loops > 0){ Sleep(500); } if(!FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!"); return (INIT_FAILED); } else if(FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE."); } int screenshot_Handle = INVALID_HANDLE; screenshot_Handle = FileOpen(SCREENSHOT_FILE_NAME,FILE_READ|FILE_BIN); if(screenshot_Handle == INVALID_HANDLE){ Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!"); return(INIT_FAILED); } else if (screenshot_Handle != INVALID_HANDLE){ Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING."); Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING."); } int screenshot_Handle_Size = (int)FileSize(screenshot_Handle); if (screenshot_Handle_Size > 0){ Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size); } uchar photoArr_Data[]; ArrayResize(photoArr_Data,screenshot_Handle_Size); FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size); if (ArraySize(photoArr_Data) > 0){ Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data)); } FileClose(screenshot_Handle); //ArrayPrint(photoArr_Data); //--- create boundary: (data -> base64 -> 1024 bytes -> md5) //Encodes the photo data into base64 format //This is part of preparing the data for transmission over HTTP. uchar base64[]; uchar key[]; CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64); if (ArraySize(base64) > 0){ Print("Transformed BASE-64 data = ",ArraySize(base64)); //Print("The whole data is as below:"); //ArrayPrint(base64); } //Copy the first 1024 bytes of the base64-encoded data into a temporary array uchar temporaryArr[1024]= {0}; //Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:"); //ArrayPrint(temporaryArr); ArrayCopy(temporaryArr,base64,0,0,1024); //Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:"); //ArrayPrint(temporaryArr); //Create an MD5 hash of the temporary array //This hash will be used as part of the boundary in the multipart/form-data uchar md5[]; CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5); if (ArraySize(md5) > 0){ Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5)); Print("MD5 HASH boundary in multipart/form-data is as follows:"); ArrayPrint(md5); } //Format MD5 hash as a hexadecimal string & //truncate it to 16 characters to create the boundary. string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash int total=ArraySize(md5); for(int i=0; i<total; i++){ HexaDecimal_Hash+=StringFormat("%02X",md5[i]); } Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash); HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters //done to comply with a specific length requirement for the boundary //in the multipart/form-data of the HTTP request. Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash); //--- WebRequest char DATA[]; string URL = NULL; URL = TG_API_URL+"/bot"+botTkn+"/sendPhoto"; //--- add chart_id //Append a carriage return and newline character sequence to the DATA array. //In the context of HTTP, \r\n is used to denote the end of a line //and is often required to separate different parts of an HTTP request. ArrayAdd(DATA,"\r\n"); //Append a boundary marker to the DATA array. //Typically, the boundary marker is composed of two hyphens (--) //followed by a unique hash string and then a newline sequence. //In multipart/form-data requests, boundaries are used to separate //different pieces of data. ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); //Add a Content-Disposition header for a form-data part named chat_id. //The Content-Disposition header is used to indicate that the following data //is a form field with the name chat_id. ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n"); //Again, append a newline sequence to the DATA array to end the header section //before the value of the chat_id is added. ArrayAdd(DATA,"\r\n"); //Append the actual chat ID value to the DATA array. ArrayAdd(DATA,chatID); //Finally, Append another newline sequence to the DATA array to signify //the end of the chat_id form-data part. ArrayAdd(DATA,"\r\n"); // EXAMPLE OF USING CONVERSIONS //uchar array[] = { 72, 101, 108, 108, 111, 0 }; // "Hello" in ASCII //string output = CharArrayToString(array,0,WHOLE_ARRAY,CP_ACP); //Print("EXAMPLE OUTPUT OF CONVERSION = ",output); // Hello Print("CHAT ID DATA:"); ArrayPrint(DATA); string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8); Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data); //--- Caption string CAPTION = NULL; CAPTION = "Screenshot of Symbol: "+Symbol()+ " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ ") @ Time: "+TimeToString(TimeCurrent()); if(StringLen(CAPTION) > 0){ ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,CAPTION); ArrayAdd(DATA,"\r\n"); } //--- ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,photoArr_Data); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n"); Print("FINAL FULL PHOTO DATA BEING SENT:"); ArrayPrint(DATA); string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP); Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data); string HEADERS = NULL; HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n"; Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY."); //char data[]; // Array to hold data to be sent in the web request (empty in this case) char res[]; // Array to hold the response data from the web request string resHeaders; // String to hold the response headers from the web request //string msg = "EA INITIALIZED ON CHART " + _Symbol; // Message to send, including the chart symbol //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID + // "&text=" + msg; // Send the web request to the Telegram API int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders); // Check the response status of the web request if (send_res == 200) { // If the response status is 200 (OK), print a success message Print("TELEGRAM MESSAGE SENT SUCCESSFULLY"); } else if (send_res == -1) { // If the response status is -1 (error), check the specific error code if (GetLastError() == 4014) { // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL"); } // Print a general error message if the request fails Print("UNABLE TO SEND THE TELEGRAM MESSAGE"); } else if (send_res != 200) { // If the response status is not 200 or -1, print the unexpected response code and error code Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError()); } return(INIT_SUCCEEDED); // Return initialization success status }
Jetzt ist klar, dass wir unser drittes Ziel erreicht haben, nämlich das Senden von Chart-Screenshot-Bilddateien mit Legenden vom Handelsterminal an den Telegram-Chat oder die Telegram-Gruppe. Das ist ein Erfolg und ein Hoch auf uns! Jetzt müssen wir die Integration testen, um sicherzustellen, dass sie ordnungsgemäß funktioniert, und um etwaige Probleme zu erkennen. Dies geschieht im nächsten Abschnitt.
Testen der Integration
Um sicherzustellen, dass unser Expert Advisor (EA) korrekt Screenshots vom Handelsterminal MetaTrader 5 an Telegram sendet, müssen wir die Integration gründlich testen. Um die Dinge zu verbinden, sollten wir die Testlogik in einem GIF-Format haben.
Im obigen GIF demonstrieren wir die nahtlose Interaktion zwischen MetaTrader 5 und Telegram, indem wir den Prozess des Versendens eines Chart-Screenshots zeigen. Das GIF zeigt zunächst die Plattform MetaTrader 5, auf der ein Chart-Fenster geöffnet und in den Vordergrund gebracht wird. Anschließend wird das Fenster für 10 Sekunden angehalten, um Zeit für letzte Anpassungen zu haben. Während dieser Pause werden auf der Registerkarte „Journal“ in MetaTrader 5 Meldungen über den Fortschritt des Vorgangs protokolliert, z. B. dass das Chart neu gezeichnet und der Screenshot aufgenommen wird. Das Chart wird dann automatisch geschlossen, und der Screenshot wird verpackt und an Telegram gesendet. Auf der Telegram-Seite sehen wir, dass der Screenshot im Chat ankommt, was bestätigt, dass die Integration wie vorgesehen funktioniert. Dieses GIF veranschaulicht, wie das automatisierte System in Echtzeit arbeitet, von der Vorbereitung des Charts bis zur erfolgreichen Bereitstellung des Bildes auf Telegram.
Schlussfolgerung
Abschließend haben wir in diesem Artikel Schritt für Schritt beschrieben, wie man einen Chart-Screenshot von der MetaTrader 5 Handelsplattform an einen Chat auf Telegram sendet. Wir haben den Screenshot des Charts zunächst mit MetaTrader 5 erstellt. Wir haben die Einstellungen des Charts konfiguriert, um sicherzustellen, dass es übersichtlich ist, und es dann mit der Funktion ChartScreenShot erfasst, um das Bild in einer Datei zu speichern. Nachdem wir die Datei auf unserem Computer gespeichert hatten, öffneten wir die Datei und lasen ihren binären Inhalt. Dann haben wir das Chart im Base64-Format an eine HTTP-Anfrage geschickt, die die API von Telegram verstehen konnte. Auf diese Weise konnten wir das Bild in Echtzeit in einen Chat auf Telegram übertragen.
Bei der Verschlüsselung des Bildes für die Übertragung wurden die Schwierigkeiten deutlich, die mit dem Senden von binären Rohdaten über das HTTP-Protokoll verbunden sind, insbesondere wenn das Ziel eine Nachrichtenplattform wie Telegram ist. Zunächst einmal muss man verstehen, dass die direkte Übertragung von Binärdaten nicht möglich ist. Stattdessen verlangt Telegram (wie viele andere Dienste auch), dass die Daten in einem Textformat gesendet werden. Wir haben diese Anforderung problemlos erfüllt, indem wir einen weithin bekannten Algorithmus zur Konvertierung der binären Rohbilddaten in Base64 verwendet haben. Danach haben wir das Base64-Bild in eine multipart/form-data-HTTP-Anfrage eingefügt. Diese Demonstration unterstrich nicht nur die Leistungsfähigkeit der MetaTrader 5-Plattform als Mittel zur Erstellung individueller Automatisierungen, sondern zeigte auch, wie man einen externen Dienst - in diesem Fall Telegram - in eine Handelsstrategie integriert.
In Teil 4 werden wir den Code aus diesem Artikel in wiederverwendbare Komponenten umwandeln. Wir werden dies tun, um mehrere Instanzen der Telegram-Integration zu erstellen, die es uns in den nächsten Teilen des Tutorials ermöglichen werden, verschiedene Nachrichten und Screenshots nach Lust und Laune an Telegram zu senden - nicht nur, wie wir wollen, sondern auch wann und wie wir wollen -, ohne dass wir uns dabei auf einen einzigen Funktionsaufruf verlassen müssen. Indem wir den Code in Klassen unterteilen, machen wir das System modularer und skalierbarer. Wir werden dies auch tun, um den Code leichter in die verschiedenen Handelsszenarien zu integrieren, die wir in Teil 1 skizziert haben. Dies ist wichtig, weil die Integration des Telegram-Mechanismus dynamisch und flexibel mit unseren Expert Advisors funktionieren soll, sodass mehrere Strategien und Kontoszenarien eine Vielzahl von Nachrichten und Bildern zu kritischen Punkten während eines Handels oder am Ende eines Handelstages senden können. Bleiben Sie dran, denn wir werden dieses integrierte System weiter ausbauen und verfeinern.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/15616





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Ich danke Ihnen. Ich habe diese öffentliche Funktion mit dem gleichen Namen wie reguläre MQL5 finden und umbenennen, um Warnung zu löschen. Jetzt kompilieren ist klar :)
Best regards, Volker
Best regards, Volker