English Русский 中文 Español 日本語 Português
preview
Erstellen eines integrierten MQL5-Telegram Expert Advisors (Teil 5): Senden von Befehlen von Telegram an MQL5 und Empfangen von Antworten in Echtzeit

Erstellen eines integrierten MQL5-Telegram Expert Advisors (Teil 5): Senden von Befehlen von Telegram an MQL5 und Empfangen von Antworten in Echtzeit

MetaTrader 5Handelssysteme |
208 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In diesem Artikel, Teil 5 unserer Serie, setzen wir die Integration von MetaQuotes Language 5 (MQL5) mit Telegram fort und konzentrieren uns auf die Verfeinerung der Interaktion zwischen MetaTrader 5 (MT5) und Telegram. In vorherigen Teil 4 der Serie haben wir die Grundlagen für das Senden komplexer Nachrichten und Chartbilder von MQL5 an Telegram gelegt und damit die Kommunikationsbrücke zwischen diesen Plattformen geschaffen. Jetzt wollen wir diese Grundlage erweitern, indem wir den Expert Advisor in die Lage versetzen, Befehle direkt von Telegram-Nutzern zu empfangen und zu interpretieren. Anstatt dass der Expert Advisor sich selbst steuert, indem er Signale generiert, Marktpositionen eröffnet und vordefinierte Nachrichten an unseren Telegram-Chat sendet, werden wir ihn vom Telegram-Chat aus steuern, indem wir Befehle an den Advisor weiterleiten, der seinerseits die Befehle entschlüsselt, sie interpretiert und intellektuelle und angemessene Anfragen und Antworten zurücksendet.

Wir beginnen mit der Einrichtung der notwendigen Umgebung, um diese Kommunikation zu erleichtern, und stellen sicher, dass alles für eine nahtlose Interaktion vorhanden ist. Der Kern dieses Artikels besteht darin, Klassen zu erstellen, die Daten im Format Object Notation (JSON) für die automatischen Chat-Updates abrufen, die in diesem Fall die Telegram-Befehle und -Anfragen sind, die es dem Expert Advisor ermöglichen, Nutzerbefehle von Telegram zu verstehen und zu verarbeiten. Dieser Schritt ist entscheidend für den Aufbau einer dynamischen Zwei-Wege-Kommunikation, bei der der Bot nicht nur Nachrichten sendet, sondern auch intelligent auf Nutzereingaben antwortet.

Außerdem werden wir uns auf die Dekodierung und Interpretation der eingehenden Daten konzentrieren, um sicherzustellen, dass der Expert Advisor verschiedene Arten von Befehlen aus dem Application Programming Interface (API). Zur Veranschaulichung dieses Prozesses haben wir einen detaillierten, visuellen Leitfaden erstellt, der den Kommunikationsfluss zwischen Telegram, MetaTrader 5 und dem MQL5-Code-Editor veranschaulicht und das Verständnis für das Zusammenspiel dieser Komponenten erleichtert.

ABLAUF DES INTEGRATIONSPROZESSES

Die mitgelieferte Abbildung sollte die Integrationskomponenten deutlich machen. Der Ablauf wird also folgendermaßen sein: Telegram sendet Befehle an das Handelsterminal, an das der Expert Advisor angeschlossen ist, der Advisor sendet die Befehle an MQL5, das die Nachrichten dekodiert, interpretiert und die entsprechenden Antworten vorbereitet, die wiederum als Antworten an das Handelsterminal und Telegram gesendet werden. Zum besseren Verständnis werden wir den gesamten Prozess wie folgt in Themen unterteilen:

  1. Einrichten der Umgebung
  2. Erstellen von Klassen zum Abrufen von Chat-Updates aus JSON
  3. Dekodierung und Parsing von Daten aus der Telegram API
  4. Umgang mit Antworten
  5. Testen der Implementierung
  6. Schlussfolgerung

Am Ende des Artikels werden wir einen vollständig integrierten Expert Advisor haben, der Befehle und Anfragen von Telegram an MQL5 sendet und überwachte Antworten als Antworten im Telegram-Chat erhält. Fangen wir also an.


Einrichten der Umgebung

Es ist von grundlegender Bedeutung, eine Umgebung zu schaffen, die es unserem Expert Advisor (EA) ermöglicht, mit Telegram zu interagieren, bevor wir mit der eigentlichen Arbeit der Erstellung von Klassen und Funktionen beginnen. Unser EA wird auf mehrere wichtige Bibliotheken zugreifen müssen, die die Verwaltung von Handelsgeschäften, Arrays und Strings in MQL5 erleichtern. Indem wir diese wichtigen Bibliotheken zur Verfügung stellen, stellen wir sicher, dass unser EA Zugang zu einer gut ausgestatteten Funktions- und Klassenbibliothek hat, die den Weg für die Implementierung unseres EA erheblich ebnet. Dies ist wie unten dargestellt:

#include <Trade/Trade.mqh>
#include <Arrays/List.mqh>
#include <Arrays/ArrayString.mqh>
    

Hier bietet die Bibliothek „<Trade/Trade.mqh>“ einen kompletten Satz von Handelsfunktionen. Diese Bibliothek ermöglicht dem EA die Ausführung von Geschäften, die Verwaltung von Positionen und die Durchführung anderer handelsbezogener Aufgaben. Es ist eine entscheidende Komponente eines jeden EA, der mit dem Markt interagieren will. Die folgenden Bibliotheken „<Arrays/List.mqh>“ und „<Arrays/ArrayString.mqh>“ sind enthalten, um die Verwaltung von Datenstrukturen zu erleichtern. Die erste dieser beiden Bibliotheken dient der Verwaltung dynamischer Listen. Die zweite ist für die Arbeit mit Arrays von Strings. Diese beiden Bibliotheken sind besonders nützlich, wenn es um die Handelssignale geht, die wir von Telegram erhalten. Wir wissen, dass das eine Menge Fachjargon war. In den folgenden Kapiteln werden wir dies etwas näher erläutern und versuchen, die Funktionen all dieser Komponenten im Detail zu erklären. Um auf die Bibliothek „Arrays“ zuzugreifen, öffnen Sie den Navigator, erweitern Sie den Ordner „Includes“ und markieren Sie einen der beiden Ordner wie unten dargestellt.

ARRAYS BIBLIOTHEK

Schließlich müssen wir die Telegram-Basis-URL, die Zeitüberschreitung und das Token des Bots wie unten gezeigt definieren.

#define TELEGRAM_BASE_URL  "https://api.telegram.org"
#define WEB_TIMEOUT        5000
//#define InpToken "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"
#define InpToken "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"

Nach dem Einbinden der Bibliotheken und dem Kompilieren Ihres Programms verfügen wir über die notwendige Umgebung, die für die Verarbeitung komplexer Datenstrukturen, die von Telegram-Befehlen empfangen werden, erforderlich ist, und wir können nun mit der Implementierung fortfahren. 


Erstellen von Klassen zum Abrufen von Chat-Updates aus JSON

In diesem Abschnitt konzentrieren wir uns auf die Entwicklung der Kernfunktionalität, die es unserem Expert Advisor (EA) ermöglicht, Updates von Telegram in Echtzeit zu empfangen. Konkret müssen wir Klassen erstellen, die die von der Telegram-API zurückgegebenen Daten JSON analysieren und die erforderlichen Informationen extrahieren, z. B. Chat-Updates und Nutzerbefehle. Dieser Schritt ist entscheidend für die Einrichtung einer reaktionsfähigen Kommunikationsschleife zwischen Telegram und MetaTrader 5. Lassen Sie uns zunächst den Prozess simulieren. Wir laden wieder die Standardfunktion zum Abrufen von Chat-Updates wie unten in unserem Browser, damit wir die Datenstruktur erhalten, die wir für die Implementierung der Klassen benötigen.

LEERE DATEN

Nach dem Laden wird true zurückgegeben, was anzeigt, dass der Prozess erfolgreich war, die Datenstruktur aber leer ist. Das liegt daran, dass in den letzten 24 Stunden keine Nachrichten aus dem Telegram-Chat gesendet wurden. Wir müssen also eine Nachricht senden, um eine Aktualisierung zu erhalten. Zu diesem Zweck senden wir eine Initialisierungsnachricht aus dem Telegram-Chat, wie unten gezeigt.

TELEGRAMM ERSTE INITIALISIERUNGSNACHRICHT

Sobald wir die Nachricht gesendet haben, haben wir eine Aktualisierung und können den Browser-Link neu laden, um die Struktur der gesendeten Daten zu erhalten.

DATENSTRUKTUR 1

Aus dem obigen Bild können wir die korrekten Details in der Datenstruktur erkennen, die aus der von uns gesendeten Nachricht aufgebaut ist. Dies sind genau die Daten, die wir in unsere Klassen kopieren und jedes Mal, wenn wir eine neue Nachrichtenaktualisierung senden, in einer Schleife durchgehen müssen. Lassen Sie uns also eine Klasse konstruieren, die alle Mitgliedsvariablen enthalten wird. Lassen Sie uns zunächst den allgemeinen Klassenentwurf konstruieren.

//+------------------------------------------------------------------+
//|        Class_Message                                             |
//+------------------------------------------------------------------+
class Class_Message : public CObject{//Defines a class named Class_Message that inherits from CObject.
   public:
      Class_Message(); // constructor
      ~Class_Message(){}; // Declares a destructor for the class, which is empty.
};

Konzentrieren wir uns auf den Klassenprototyp, den wir oben deklariert haben, damit später alles reibungslos abläuft. Um eine Klasse zu deklarieren, verwenden wir das Schlüsselwort „class“, gefolgt vom Namen der Klasse, in unserem Fall „Class_Message“. Da wir viele ähnliche Datenstrukturen erhalten werden, erben wir eine andere Klasse namens „CObject“ und machen die geerbten Mitglieder der fremden Klasse mit dem Schlüsselwort „public“ öffentlich. Dann erklären wir die ersten Mitglieder der Klasse für „public“. Bevor wir weitermachen, wollen wir im Detail erklären, was das alles bedeutet. Das Schlüsselwort ist einer der 4 Qualifizierer, die allgemein als Zugriffsspezifizierer bezeichnet werden und festlegen, wie der Compiler auf Variablen, Mitglieder von Strukturen oder Klassen zugreifen kann. Es gibt vier davon:public, protected, private, and virtual.

Wir wollen sie aufschlüsseln und einzeln erklären.

  • Public: Mitglieder, die mit der Zugriffsangabe „public“ deklariert sind, sind von jedem Teil des Codes aus zugänglich, in dem die Klasse sichtbar ist. Dies bedeutet, dass Funktionen, Variablen oder andere Objekte außerhalb der Klasse direkt auf öffentliche Mitglieder zugreifen und diese verwenden können. Sie werden häufig für Funktionen oder Variablen verwendet, auf die andere Klassen, Funktionen oder Skripte zugreifen müssen.
  • Protected: Mitglieder, die mit dem Zugriffsmerkmal „protected“ deklariert sind, sind von außerhalb der Klasse nicht zugänglich, aber sie sind innerhalb der Klasse selbst, von abgeleiteten Klassen (d. h. Unterklassen, die von dieser Klasse erben) und von „befreudeten“ Klassen/Funktionen zugänglich. Dies ist nützlich, um Daten zu kapseln, die zwar für Unterklassen, nicht aber für den Rest des Programms verfügbar sein sollen. Sie werden in der Regel verwendet, um Unterklassen den Zugriff auf bestimmte Variablen oder Funktionen der Basisklasse zu ermöglichen, während diese Mitglieder für den Rest des Programms verborgen bleiben.
  • Private: Mitglieder, die mit der Zugriffsangabe „private“ deklariert werden, sind nur innerhalb der Klasse selbst zugänglich. Weder abgeleitete Klassen noch andere Teile des Programms können direkt auf private Mitglieder zugreifen oder sie verändern. Dies ist die restriktivste Zugriffsstufe und wird normalerweise für Variablen und Hilfsfunktionen verwendet, die von außerhalb der Klasse nicht zugänglich oder veränderbar sein sollen. Sie werden üblicherweise verwendet, um Daten zu verbergen und sicherzustellen, dass der interne Zustand eines Objekts nur über genau definierte öffentliche Schnittstellen (Methoden) geändert werden kann.
  • Virtual: Gilt nur für Klassenmethoden (aber nicht für Methoden von Strukturen) und teilt dem Compiler mit, dass diese Methode in die Tabelle der virtuellen Funktionen der Klasse aufgenommen werden soll.

Von den oben genannten Syntaxen werden nur die ersten drei üblicherweise verwendet. Zurück zu unserem Klassenprototyp, lassen Sie uns aufschlüsseln, was alles passiert.

  • Deklaration der Klasse:

class Class_Message : public CObject{...};: Hier deklarieren wir eine neue Klasse namens „Class_Message“. Diese Klasse leitet sich von „CObject“ ab, einer Basisklasse in MQL5, die zur Erstellung nutzerdefinierter Objekte verwendet wird. Auf diese Weise kann „Class_Message“ die vom MQL5-Framework bereitgestellten Funktionen, wie die Speicherverwaltung und andere Vorteile der objektorientierten Programmierung, nutzen, um Nachrichten in unserem Programm bequem anzuzeigen.

  • Konstrukteur:

Class_Message();: Hier wird der Konstruktor der Klasse „Class_Message“ deklariert. Ein Konstruktor ist eine spezielle Funktion, die automatisch aufgerufen wird, wenn eine Instanz (oder ein Objekt) der Klasse erstellt wird. Die Aufgabe des Konstruktors ist es, die Mitgliedsvariablen der Klasse zu initialisieren und alle Einstellungen vorzunehmen, die bei der Erstellung des Objekts vorgenommen werden müssen. Im Fall von Class_Message werden die Mitgliedsvariablen initialisiert.

  • Destructor:

~Class_Message(){};: Die Klasse „Class_Message“ deklariert einen Destruktor. Ein Destruktor wird automatisch aufgerufen, wenn eine Instanz einer Klasse explizit gelöscht wird oder den Gültigkeitsbereich verlässt. Normalerweise wird ein Destruktor definiert, um Aufräumarbeiten durchzuführen, und ist konzeptionell das Gegenteil eines Konstruktors, der aufgerufen wird, wenn eine Instanz einer Klasse erstellt wird. In diesem Fall tut der Destruktor für die Klasse „Class_Message“ nichts (er führt keine Aufräumarbeiten durch), da dies im Moment nicht notwendig ist. 

Beachten Sie, dass sowohl der Konstruktor als auch der Destruktor den gleichen Namen wie die Basisklasse enthalten, nur dass der Destruktor ein tide (~) als Präfix hat. Damit können wir nun mit der Definition der Mitglieder unserer Klasse fortfahren. Diese Mitglieder sind dieselben, die wir in unserer Datenstruktur erhalten haben. Daher werden wir die Datenstruktur und die Mitglieder, die wir aus ihr extrahieren müssen, wie unten dargestellt.

DETAILS DER NACHRICHT LÖSCHEN

Aus dem obigen Bild können wir ersehen, dass wir mindestens 14 Mitglieder in unserer Klasse benötigen. Wir definieren sie wie folgt:

      bool              done; //A boolean member variable TO INDICATE if a message has been processed.
      long              update_id; //Store the update ID from Telegram.
      long              message_id;//Stores the message ID.
      //---
      long              from_id;//Stores the sender’s ID.
      string            from_first_name;
      string            from_last_name;
      string            from_username;
      //---
      long              chat_id;
      string            chat_first_name;
      string            chat_last_name;
      string            chat_username;
      string            chat_type;
      //---
      datetime          message_date;
      string            message_text;

Wir haben jetzt alle Mitglieder der Klasse, die wir brauchen. Die endgültige Klassenstruktur sieht wie folgt aus. Wir haben Kommentare hinzugefügt, damit alles selbsterklärend ist.

//+------------------------------------------------------------------+
//|        Class_Message                                             |
//+------------------------------------------------------------------+
class Class_Message : public CObject{//--- Defines a class named Class_Message that inherits from CObject.
   public:
      Class_Message(); //--- Constructor declaration.
      ~Class_Message(){}; //--- Declares an empty destructor for the class.
      
      //--- Member variables to track the status of the message.
      bool              done; //--- Indicates if a message has been processed.
      long              update_id; //--- Stores the update ID from Telegram.
      long              message_id; //--- Stores the message ID.

      //--- Member variables to store sender-related information.
      long              from_id; //--- Stores the sender’s ID.
      string            from_first_name; //--- Stores the sender’s first name.
      string            from_last_name; //--- Stores the sender’s last name.
      string            from_username; //--- Stores the sender’s username.

      //--- Member variables to store chat-related information.
      long              chat_id; //--- Stores the chat ID.
      string            chat_first_name; //--- Stores the chat first name.
      string            chat_last_name; //--- Stores the chat last name.
      string            chat_username; //--- Stores the chat username.
      string            chat_type; //--- Stores the chat type.

      //--- Member variables to store message-related information.
      datetime          message_date; //--- Stores the date of the message.
      string            message_text; //--- Stores the text of the message.
};

Nachdem wir die Nachrichtenklasse definiert haben, müssen wir ihre Mitglieder initialisieren, damit sie bereit sind, Daten zu empfangen. Dies geschieht durch den Aufruf des Klassenkonstruktors.

//+------------------------------------------------------------------+
//|      Constructor to initialize class members                     |
//+------------------------------------------------------------------+
Class_Message::Class_Message(void){
   //--- Initialize the boolean 'done' to false, indicating the message is not processed.
   done = false;
   
   //--- Initialize message-related IDs to zero.
   update_id = 0;
   message_id = 0;
   
   //--- Initialize sender-related information.
   from_id = 0;
   from_first_name = NULL;
   from_last_name = NULL;
   from_username = NULL;
   
   //--- Initialize chat-related information.
   chat_id = 0;
   chat_first_name = NULL;
   chat_last_name = NULL;
   chat_username = NULL;
   chat_type = NULL;
   
   //--- Initialize the message date and text.
   message_date = 0;
   message_text = NULL;
}

Wir rufen zunächst die Basisklasse auf und definieren den Konstruktor mit dem „Scope-Operator“ (::). Anschließend initialisieren wir die Mitgliedsvariablen mit ihren Standardwerten. Der Boolesche Wert „done“ ist auf „false“ gesetzt, was bedeutet, dass die Nachricht noch nicht verarbeitet wurde. Sowohl „message_id“ als auch „update_id“ werden auf 0 initialisiert, was die Standard-IDs für die Nachricht und die Aktualisierung darstellt. Für absenderbezogene Informationen wird „from_id“ auf 0 gesetzt, und die Variablen „from_first_name“, „from_last_name“ und „from_username“ werden auf NULL initialisiert, was bedeutet, dass die Details des Absenders nicht gesetzt werden. In ähnlicher Weise werden die Variablen, die sich auf den Chat beziehen, d. h. „chat_id“, „chat_first_name“, „chat_last_name“, „chat_username“ und „chat_type“, ebenfalls auf 0 oder NULL initialisiert, was bedeutet, dass noch keine Chat-Informationen verfügbar sind. Schließlich wird „message_date“ auf 0 gesetzt, und „message_text“ wird auf NULL initialisiert, was bedeutet, dass der Inhalt der Nachricht und das Datum der Nachricht noch nicht festgelegt sind. Technisch gesehen initialisieren wir Variablen vom Datentyp „Integer“ mit 0 und „Strings“ mit NULL.

In ähnlicher Weise müssen wir eine weitere Klasseninstanz definieren, die verwendet wird, um einzelne Telegram-Chats zu speichern. Wir werden diese Daten verwenden, um einen Vergleich zwischen den analysierten Daten und den von Telegram erhaltenen Daten durchzuführen. Wenn wir beispielsweise den Befehl „get Ask price“ senden, werden wir die Daten analysieren, Aktualisierungen aus dem JSON abrufen und prüfen, ob die empfangenen und im JSON gespeicherten Daten mit unserem Befehl übereinstimmen, und wenn ja, die erforderlichen Maßnahmen ergreifen. Wir hoffen, dass dies einige Dinge klärt, aber es wird noch deutlicher werden, wenn wir fortfahren. Der Codeausschnitt der Klasse lautet wie folgt:

//+------------------------------------------------------------------+
//|        Class_Chat                                                |
//+------------------------------------------------------------------+
class Class_Chat : public CObject{
   public:
      Class_Chat(){}; //Declares an empty constructor.
      ~Class_Chat(){}; // deconstructor
      long              member_id;//Stores the chat ID.
      int               member_state;//Stores the state of the chat.
      datetime          member_time;//Stores the time related to the chat.
      Class_Message     member_last;//An instance of Class_Message to store the last message.
      Class_Message     member_new_one;//An instance of Class_Message to store the new message.
};

Wir definieren eine Klasse namens „Class_Chat“, um die Informationen der einzelnen Telegram-Chats zu verwalten und zu speichern. Diese Klasse enthält einen leeren Konstruktor und Destruktor sowie mehrere Mitglieder: „member_id“ speichert die eindeutige ID des Chats; „member_state“ gibt den Zustand des Chats an; und „member_time“ enthält alle Informationen, die sich auf den Zeitpunkt des Chats beziehen. Die Klasse hat zwei Instanzen der bereits definierten Basisklasse „Class_Message“, die jeweils die letzte und die neue Nachricht enthalten. Wir brauchen diese, um die Nachrichten zu speichern und sie einzeln zu verarbeiten, wenn der Nutzer mehrere Befehle sendet. Um dies zu veranschaulichen, senden wir eine Initialisierungsnachricht wie folgt:

ZWEITE INITIALISIERUNGSNACHRICHT

Wenn wir unsere Chat-Aktualisierungen lesen, erhalten wir die folgende Datenstruktur.

DATENSTRUKTUR DER ZWEITEN NACHRICHT

Aus der zweiten empfangenen Meldungsdatenstruktur geht hervor, dass die Aktualisierungs- und Meldungskennungen für die erste Meldung 794283239 bzw. 664 lauten, während die zweite Meldung 794283240 und 665 hat, was einen Unterschied von 1 bedeutet. Wir hoffen, dass damit der Bedarf an einer anderen Klasse geklärt ist. Nun können wir mit der Erstellung der letzten Standardklasse fortfahren, die wir zur nahtlosen Steuerung des Interaktionsablaufs verwenden werden. Seine Struktur ist wie folgt.

//+------------------------------------------------------------------+
//|   Class_Bot_EA                                                    |
//+------------------------------------------------------------------+
class Class_Bot_EA{
   private:
      string            member_token;         //--- Stores the bot’s token.
      string            member_name;          //--- Stores the bot’s name.
      long              member_update_id;     //--- Stores the last update ID processed by the bot.
      CArrayString      member_users_filter;  //--- An array to filter users.
      bool              member_first_remove;  //--- A boolean to indicate if the first message should be removed.
   
   protected:
      CList             member_chats;         //--- A list to store chat objects.

   public:
      void Class_Bot_EA();   //--- Declares the constructor.
      ~Class_Bot_EA(){};    //--- Declares the destructor.
      int getChatUpdates(); //--- Declares a function to get updates from Telegram.
      void ProcessMessages(); //--- Declares a function to process incoming messages.
};

Wir definieren eine Klasse namens „Class_Bot_EA“, um die Interaktionen zwischen dem Telegram-Bot und der Umgebung MQL5 zu verwalten. Es hat mehrere private Mitglieder wie „member_token“, das das Authentifizierungs-Token für den Bot speichert, und „member_name“, das den Namen des Bots enthält. Ein weiteres Element ist die „member_update_id“, die die zuletzt durchgeführte Aktualisierung festhält. Mehrere andere Mitglieder verwalten und filtern Nutzerinteraktionen. Die Klasse hat ein geschütztes Mitglied, „member_chats“, das eine Liste von Chat-Objekten verwaltet. Unter den öffentlichen Mitgliedern sind vor allem der Konstruktor und der Destruktor zu nennen, die die notwendige Initialisierung und Bereinigung von Instanzen vornehmen. Unter den öffentlichen Mitgliedern gibt es auch zwei bemerkenswerte Funktionen: „getChatUpdates“, die Updates von Telegram abruft, und „ProcessMessages“, die die Verarbeitung eingehender Nachrichten übernimmt. Dies sind die wichtigsten Funktionen, mit denen wir die Chat-Updates abrufen und die empfangenen Befehle verarbeiten können. Wir werden diese Mitglieder in einem ähnlichen Format initialisieren, wie wir es bei der ersten Klasse getan haben (siehe unten).

void Class_Bot_EA::Class_Bot_EA(void){ //--- Constructor
   member_token=NULL; //--- Initialize the bot's token as NULL.
   member_token=getTrimmedToken(InpToken); //--- Assign the trimmed bot token from InpToken.
   member_name=NULL; //--- Initialize the bot's name as NULL.
   member_update_id=0; //--- Initialize the last update ID to 0.
   member_first_remove=true; //--- Set the flag to remove the first message to true.
   member_chats.Clear(); //--- Clear the list of chat objects.
   member_users_filter.Clear(); //--- Clear the user filter array.
}

Hier rufen wir den Konstruktor für die Klasse „Class_Bot_EA“ auf und initialisieren die Mitgliedsvariablen, um die Umgebung des Bots festzulegen. Anfänglich wird der „member_token“ als Platzhalter auf NULL gesetzt. Dann weisen wir ihm die gekürzte Version von „InpToken“ zu. Dieser Wert ist sehr wichtig, da er die Authentifizierung des Bots bestimmt. Wenn der abgeschnittene Platzhalter im Code verbleibt, funktioniert der Bot einfach nicht. Der „member_name“ wird ebenfalls auf NULL initialisiert, und die „member_update_id“ wird auf 0 gesetzt, was bedeutet, dass noch keine Aktualisierungen verarbeitet wurden. Die Variable „member_first_remove“ wird auf true gesetzt. Das bedeutet, dass der Bot so konfiguriert ist, dass er die erste Nachricht, die er verarbeitet, entfernt. Schließlich werden sowohl „member_chats“ als auch „member_users_filter“ geleert, um sicherzustellen, dass sie zu Beginn leer sind. Sie haben vielleicht bemerkt, dass wir eine andere Funktion verwendet haben, um das Token des Bots zu erhalten. Die Funktion ist wie folgt.

//+------------------------------------------------------------------+
//|        Function to get the Trimmed Bot's Token                   |
//+------------------------------------------------------------------+
string getTrimmedToken(const string bot_token){
   string token=getTrimmedString(bot_token); //--- Trim the bot_token using getTrimmedString function.
   if(token==""){ //--- Check if the trimmed token is empty.
      Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty.
      return("NULL"); //--- Return "NULL" if the token is empty.
   }
   return(token); //--- Return the trimmed token.
}

//+------------------------------------------------------------------+
//|        Function to get a Trimmed string                          |
//+------------------------------------------------------------------+
string getTrimmedString(string text){
   StringTrimLeft(text); //--- Remove leading whitespace from the string.
   StringTrimRight(text); //--- Remove trailing whitespace from the string.
   return(text); //--- Return the trimmed string.
}

Hier definieren wir zwei Funktionen, die Hand in Hand arbeiten, um den Token-String des Bots zu bereinigen und zu validieren. Die erste Funktion, „getTrimmedToken“, greift auf den „bot_token“ als Eingabe zu. Anschließend wird eine weitere Funktion, „getTrimmedString“, aufgerufen, um alle führenden und nachfolgenden Leerzeichen aus dem Token zu entfernen. Nach dem Trimmen prüft die Funktion, ob das Token leer ist. Wenn das Token nach dem Trimmen leer ist, wird eine Fehlermeldung ausgegeben, und die Funktion gibt „NULL“ zurück, um anzuzeigen, dass der Bot mit diesem Token nicht weiterkommt. Ist das Token hingegen nicht leer, wird es als gültiges, gekürztes Token zurückgegeben.

Die zweite Funktion, „getTrimmedString“, erledigt die eigentliche Arbeit des Abschneidens der Leerzeichen an beiden Enden einer gegebenen Zeichenkette. Es verwendet StringTrimLeft, um führende Leerzeichen zu entfernen, und StringTrimRight, um nachfolgende Leerzeichen zu entfernen, und gibt dann die getrimmte Zeichenfolge als Token zurück, das den Gültigkeitstest besteht.

Bis zu diesem Punkt haben wir bereits die notwendigen Datenstrukturen, um die empfangenen Metadaten zu organisieren. Anschließend müssen wir die Chat-Updates abrufen und gleichzeitig verarbeiten. Um eine klare Kommunikation zu gewährleisten, werden wir zunächst die Klassenfunktionen aufrufen. Um auf die Klassenmitglieder zugreifen zu können, müssen wir zunächst ein Objekt erstellen, das auf der Klasse basiert und uns den erforderlichen Zugriff ermöglicht. Dies wird wie folgt erreicht:

Class_Bot_EA obj_bot; //--- Create an instance of the Class_Bot_EA class

Nachdem wir das Klassenobjekt als „obj_bot“ deklariert haben, können wir mit dem Punktoperator auf die Mitglieder der Klasse zugreifen. Wir müssen nach Aktualisierungen suchen und die Nachrichten in einem bestimmten Zeitintervall verarbeiten. Anstelle des von OnTick, was zu zeitaufwendig wäre, um die Anzahl der Ticks zu zählen, und nur Computerressourcen beanspruchen könnte, entscheiden wir uns für die Funktion OnTimer, die das Zählen automatisch für uns übernimmt. Um die Funktion zu verwenden, müssen wir ihn in OnInit wie unten beschrieben einstellen und initialisieren.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   EventSetMillisecondTimer(3000); //--- Set a timer event to trigger every 3000 milliseconds (3 seconds)
   OnTimer(); //--- Call OnTimer() immediately to get the first update
   return(INIT_SUCCEEDED); //--- Return initialization success
}

Hier initialisieren wir den Expert Advisor, indem wir mit der Funktion EventSetMillisecondTimer ein Timer-Ereignis einrichten, das alle 3000 Millisekunden (3 Sekunden) ausgelöst wird. Dadurch wird sichergestellt, dass der Expert Advisor in regelmäßigen Abständen nach Aktualisierungen sucht. Wir rufen dann sofort OnTimer auf, um die erste Aktualisierung direkt nach der Initialisierung zu erhalten und sicherzustellen, dass der Prozess ohne Verzögerung beginnt. Schließlich geben wir „INIT_SUCCEEDED“ zurück, um anzuzeigen, dass die Initialisierung erfolgreich war. Da wir den Timer gesetzt haben, müssen wir, sobald das Programm deinitialisiert ist, den gesetzten Timer zerstören, um auch die Computerressourcen freizugeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   EventKillTimer(); //--- Kill the timer event to stop further triggering
   ChartRedraw(); //--- Redraw the chart to reflect any changes
}

Wenn der Expert Advisor entfernt oder angehalten wird, ist das erste, was wir in OnDeinit tun, das Timer-Ereignis zu stoppen. Dies geschieht mit der Funktion EventKillTimer, die das logische Gegenstück zu EventSetMillisecondTimer ist. Wir möchten nicht, dass der Timer weiterläuft, wenn der Expert Advisor nicht mehr funktioniert. Nach dem Anhalten des Timers rufen wir die Funktion ChartRedraw auf. Der Aufruf dieser Funktion ist nicht unbedingt erforderlich, kann aber unter bestimmten Umständen hilfreich sein, wenn Sie das Chart aktualisieren müssen, damit die vorgenommenen Änderungen wirksam werden. Schließlich rufen wir den Timer auf, um den Zählvorgang zu erledigen.

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(){
   obj_bot.getChatUpdates(); //--- Call the function to get chat updates from Telegram
   obj_bot.ProcessMessages(); //--- Call the function to process incoming messages
}

Schließlich rufen wir den OnTimer auf. Darin rufen wir unsere beiden wichtigen Funktionen auf, die notwendig sind, um die Chartaktualisierungen zu erhalten bzw. die Nachrichten zu verarbeiten, indem wir das Objekt „obj_bot“ verwenden, das wir erstellt haben, und den „Punktoperator“ nutzen, um Zugriff auf die Klassenfunktionen zu erhalten. Bis zu diesem Punkt ist alles ein Erfolg und wir können uns nun auf die Funktionen konzentrieren. Dies wird in den nächsten Abschnitten geschehen.


Dekodierung und Parsing von Daten aus der Telegram API

Als Erstes müssen wir die Chat-Updates abrufen, die wir dann mit dem von Telegram empfangenen Text vergleichen und bei Übereinstimmung die notwendige Antwort geben. Daher werden wir dies in der Funktion tun, die für den Erhalt der Aktualisierungen verantwortlich ist.

//+------------------------------------------------------------------+
int Class_Bot_EA::getChatUpdates(void){

//--- ....

}

Nach dem Aufruf der Funktion stellen wir zunächst sicher, dass wir ein gültiges Token haben. Ist dies nicht der Fall, geben wir eine Fehlermeldung in das Protokoll ein und geben -1 zurück, was bedeutet, dass wir ohne das Token nicht weitermachen können. Dies ist wie unten dargestellt.

   //--- Check if the bot token is NULL
   if(member_token==NULL){
      Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty
      return(-1); //--- Return with an error code
   }

Wenn das Token nicht leer ist, können wir eine Anfrage vorbereiten, die an die Telegram-API gesendet wird, um Updates aus einem bestimmten Chat abzurufen.

   string out; //--- Variable to store the response from the request
   string url=TELEGRAM_BASE_URL+"/bot"+member_token+"/getUpdates"; //--- Construct the URL for the Telegram API request
   string params="offset="+IntegerToString(member_update_id); //--- Set the offset parameter to get updates after the last processed ID
   
   //--- Send a POST request to get updates from Telegram
   int res=postRequest(out, url, params, WEB_TIMEOUT);

Zunächst deklarieren wir eine Variable mit dem Namen „out“, die die von der API-Anforderung zurückgegebene Antwort enthält. Um die URL für die Anfrage zu erstellen, kombinieren wir die Basis-API-URL („TELEGRAM_BASE_URL“), das Token des Bots („member_token“) und die Methode, die wir aufrufen wollen („/getUpdates“). Diese Methode ruft die von den Nutzern an den Bot gesendeten Aktualisierungen ab, sodass wir sehen können, was seit der letzten Überprüfung auf Aktualisierungen geschehen ist. Wir fügen dann einen einzigen Parameter in unsere Anfrage ein. Der Parameter „offset“ stellt sicher, dass wir nur Aktualisierungen erhalten, die nach der letzten abgerufenen Aktualisierung erfolgt sind. Schließlich stellen wir eine POST-Anfrage an die API, wobei das Ergebnis der Anfrage in der Variablen „out“ gespeichert und durch das Feld „res“ in der Antwort angezeigt wird. Wir haben eine nutzerdefinierte Funktion „postRequest“ verwendet. Hier ist der Codeausschnitt und die Aufschlüsselung. Es ähnelt dem, was wir in den vorherigen Teilen gemacht haben, aber wir haben Kommentare hinzugefügt, um die verwendeten Variablen zu erklären.

//+------------------------------------------------------------------+
//| Function to send a POST request and get the response             |
//+------------------------------------------------------------------+
int postRequest(string &response, const string url, const string params,
                const int timeout=5000){
   char data[]; //--- Array to store the data to be sent in the request
   int data_size=StringLen(params); //--- Get the length of the parameters
   StringToCharArray(params, data, 0, data_size); //--- Convert the parameters string to a char array

   uchar result[]; //--- Array to store the response data
   string result_headers; //--- Variable to store the response headers

   //--- Send a POST request to the specified URL with the given parameters and timeout
   int response_code=WebRequest("POST", url, NULL, NULL, timeout, data, data_size, result, result_headers);
   if(response_code==200){ //--- If the response code is 200 (OK)
      //--- Remove Byte Order Mark (BOM) if present
      int start_index=0; //--- Initialize the starting index for the response
      int size=ArraySize(result); //--- Get the size of the response data array
      // Loop through the first 8 bytes of the 'result' array or the entire array if it's smaller
      for(int i=0; i<fmin(size,8); i++){
         // Check if the current byte is part of the BOM
         if(result[i]==0xef || result[i]==0xbb || result[i]==0xbf){
            // Set 'start_index' to the byte after the BOM
            start_index=i+1;
         }
         else {break;}
      }
      //--- Convert the response data from char array to string, skipping the BOM
      response=CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
      //Print(response); //--- Optionally print the response for debugging

      return(0); //--- Return 0 to indicate success
   }
   else{
      if(response_code==-1){ //--- If there was an error with the WebRequest
         return(_LastError); //--- Return the last error code
      }
      else{
         //--- Handle HTTP errors
         if(response_code>=100 && response_code<=511){
            response=CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert the result to string
            Print(response); //--- Print the response for debugging
            Print("ERR: HTTP"); //--- Print an error message indicating an HTTP error
            return(-1); //--- Return -1 to indicate an HTTP error
         }
         return(response_code); //--- Return the response code for other errors
      }
   }
   return(0); //--- Return 0 in case of an unexpected error
}

Hier kümmern wir uns um das Senden einer POST-Anfrage und die Verarbeitung der Antwort. Zunächst werden die Eingabeparameter in eine Form umgewandelt, in der sie gesendet werden können, indem mit StringToCharArray ein Zeichenarray aus der Parameterzeichenkette erstellt wird. Anschließend definieren wir zwei Arrays, die die Antwortdaten und die Antwortkopfzeilen erfassen. Schließlich verwenden wir die Funktion WebRequest, um die POST-Anfrage mit den zu verwendenden Parametern und einer Timeout-Einstellung an die URL zu senden, an die sie gehen soll.

Wenn unsere Anfrage erfolgreich war (was wir anhand des Empfangs eines 200-Antwortcodes feststellen), stellen wir sicher, dass es nichts gibt, was die Verarbeitung am Anfang unserer Antwortdaten stören könnte. Insbesondere prüfen wir, ob die Byte Order Mark (BOM) vorhanden ist. Wenn wir eine finden, behandeln wir sie wie eine Teilzeichenkette, die dort nicht sein sollte, und wir unternehmen Schritte, um zu vermeiden, dass sie in die Daten aufgenommen wird, die wir schließlich verwenden. Danach wandeln wir die Daten von einem Zeichenarray in eine Zeichenkette um. Wenn wir all diese Schritte durchlaufen haben, ohne auf ein Problem zu stoßen, geben wir eine 0 zurück, um anzuzeigen, dass alles reibungslos verlaufen ist.

Wenn unsere Anfrage nicht erfolgreich war, wird der Fehler durch Überprüfung des Codes, der mit der Antwort zurückkam, behoben. Wenn das Problem bei der WebRequest-Funktion liegt, teilen wir dem Nutzer mit, welcher Fehlercode zuletzt gesetzt wurde - nur so können wir herausfinden, was das Problem ist. Wenn es sich um einen HTTP-Fehler handelt, tun wir unser Bestes, um die Fehlermeldung zu interpretieren, die mit der HTTP-Antwort kam, und wir teilen dem Nutzer mit, was wir gefunden haben. Bei allen anderen Antwortcodes, die wir erhalten, geben wir einfach den Code zurück.

Bevor wir fortfahren, können wir die gesendeten Daten überprüfen, indem wir die Antwort überprüfen und die Daten drucken. Wir erreichen dies, indem wir die folgende Logik anwenden.

   //--- If the request was successful
   if(res==0){
      Print(out); //--- Optionally print the response
   }

Hier wird geprüft, ob das Ergebnis des Postings gleich Null ist, und wenn ja, werden die Daten zur Fehlersuche und Überprüfung ausgedruckt. Nach der Ausführung erhalten wir die folgenden Ergebnisse.

ANTWORTDATEN

Hier können wir sehen, dass die Antwort wahr ist, was bedeutet, dass der Prozess zum Abrufen der Aktualisierungen erfolgreich war. Jetzt müssen wir die Datenantwort abrufen, und um sie abzurufen, müssen wir ein JSON-Parsing verwenden. Wir werden nicht zu tief in den Code eindringen, der für die Analyse verantwortlich ist, aber wir werden ihn als Datei einbinden und in den globalen Bereich unseres Programms aufnehmen. Nach dem Hinzufügen wird ein JSON-Objekt wie folgt erstellt.

      //--- Create a JSON object to parse the response
      CJSONValue obj_json(NULL, jv_UNDEF);

Nachdem wir das Objekt erstellt haben, verwenden wir es, um die Antwort wie folgt zu deserialisieren. 

      //--- Deserialize the JSON response
      bool done=obj_json.Deserialize(out);

Wir deklarieren eine boolesche Variable „done“, um die Ergebnisse zu speichern. Hier werden die Flags gespeichert, die angeben, ob die Antwort korrekt geparst wurde oder nicht. Wir können sie zu Debugging-Zwecken wie folgt ausdrucken.

      Print(done);

Nach dem Ausdruck erhalten wir die folgende Antwort.

DESERIALISIERUNGS-ANTWORT

Hier können wir sehen, dass wir die Antwort korrekt analysiert haben. Damit wir fortfahren können, muss die Antwort wahr sein. Ist die Antwort die letztere, müssen wir den Prozess anhalten und zurückkehren, da wir keinen Zugriff auf die restlichen Aktualisierungen der Nachricht haben werden. Aus diesem Grund stellen wir sicher, dass wir den Prozess beenden, wenn die Antwort negativ ausfällt.

      if(!done){
         Print("ERR: JSON PARSING"); //--- Print an error message if parsing fails
         return(-1); //--- Return with an error code
      }

Hier wird geprüft, ob das JSON-Parsing erfolgreich war, indem die boolesche Variable „done“ ausgewertet wird. Wenn das Parsing fehlschlägt (d.h. „done“ ist falsch), wird eine Fehlermeldung „ERR: JSON PARSING“, um anzuzeigen, dass es ein Problem bei der Interpretation der JSON-Antwort gab. Danach geben wir -1 zurück, um zu signalisieren, dass beim JSON-Parsing ein Fehler aufgetreten ist. Als Nächstes stellen wir durch die folgende Logik sicher, dass die Antwort erfolgreich verarbeitet wird.

      //--- Check if the 'ok' field in the JSON is true
      bool ok=obj_json["ok"].ToBool();
      //--- If 'ok' is false, there was an error in the response
      if(!ok){
         Print("ERR: JSON NOT OK"); //--- Print an error message if 'ok' is false
         return(-1); //--- Return with an error code
      }

Zunächst überprüfen wir den Wert des Feldes „ok“ in der JSON-Datei, die aus der Antwort abgerufen wird. So wissen wir, ob die Anfrage erfolgreich bearbeitet wurde. Wir extrahieren dieses Feld und speichern es in einem booleschen Wert namens „ok“. Wenn der Wert von „ok“ falsch ist, bedeutet dies, dass ein Fehler oder ein anderes Problem mit der Antwort aufgetreten ist, obwohl die Anfrage selbst erfolgreich war. In diesem Fall drucken wir „ERR: JSON NOT OK“, um zu signalisieren, dass ein Problem aufgetreten ist, und -1 zurückzugeben, um anzuzeigen, dass auch bei der Verarbeitung der JSON-Antwort ein Problem aufgetreten ist. Wenn alles erfolgreich war, bedeutet dies, dass wir Nachrichtenaktualisierungen haben und sie abrufen können. Daher müssen wir ein Objekt deklarieren, das auf der Nachrichtenklasse basiert, und zwar wie folgt:

      //--- Create a message object to store message details
      Class_Message obj_msg;

Wir können nun eine Schleife über alle Nachrichtenaktualisierungen laufen lassen und sie mit Hilfe des erstellten Objekts in der Klasse speichern. Zunächst müssen wir die Gesamtzahl der Aktualisierungen ermitteln, was durch die folgende Logik erreicht wird.

      //--- Get the total number of updates in the JSON array 'result'
      int total=ArraySize(obj_json["result"].m_elements);
      //--- Loop through each update
      for(int i=0; i<total; i++){

      }

Bei jeder Iteration müssen wir ein einzelnes Aktualisierungselement aus der JSON-Antwort abrufen, für das wir arbeiten.

         //--- Get the individual update item as a JSON object
         CJSONValue obj_item=obj_json["result"].m_elements[i];

Wir können dann die einzelnen Chat-Updates abrufen. Zunächst sollten die Nachrichten aktualisiert werden.

         //--- Extract message details from the JSON object
         obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Get the update ID
         obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Get the message ID
         obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Get the message date
         
         obj_msg.message_text=obj_item["message"]["text"].ToStr(); //--- Get the message text
         obj_msg.message_text=decodeStringCharacters(obj_msg.message_text); //--- Decode any HTML entities in the message text

Hier werden die Details der einzelnen Nachricht aus der mit „obj_item“ bezeichneten Aktualisierungsposition entnommen. Wir beginnen damit, die Aktualisierungs-ID aus dem JSON-Objekt zu ziehen und sie in „obj_msg.update_id“ zu speichern. Danach ziehen wir die Nachrichten-ID und parken sie in „obj_msg.message_id“. Das Datum der Nachricht, das in einem nicht ganz so menschenlesbaren Format vorliegt, ist ebenfalls in der Nachricht enthalten, und wir speichern es als „datetime“-Objekt in „obj_msg.message_date“, das wir in ein menschenlesbares Format umwandeln. Dann schauen wir uns den Text der Nachricht an. In den meisten Fällen können wir einfach den Text nehmen und ihn in „obj_msg.message_text“ einfügen. Manchmal sind die HTML-Entities jedoch kodiert, ein anderes Mal enthalten sie Sonderzeichen, die ebenfalls kodiert sind. Für diese Fälle gibt es eine Funktion namens „decodeStringCharacters“, die sie behandelt. Dies ist eine Funktion, die wir schon früher erklärt hatten, wir rufen sie einfach auf, um ihre Aufgabe zu erfüllen. Dann extrahieren wir in einem ähnlichen Format die Absenderangaben.

         //--- Extract sender details from the JSON object
         obj_msg.from_id=obj_item["message"]["from"]["id"].ToInt(); //--- Get the sender's ID
         obj_msg.from_first_name=obj_item["message"]["from"]["first_name"].ToStr(); //--- Get the sender's first name
         obj_msg.from_first_name=decodeStringCharacters(obj_msg.from_first_name); //--- Decode the first name
         obj_msg.from_last_name=obj_item["message"]["from"]["last_name"].ToStr(); //--- Get the sender's last name
         obj_msg.from_last_name=decodeStringCharacters(obj_msg.from_last_name); //--- Decode the last name
         obj_msg.from_username=obj_item["message"]["from"]["username"].ToStr(); //--- Get the sender's username
         obj_msg.from_username=decodeStringCharacters(obj_msg.from_username); //--- Decode the username

Nachdem wir die Absenderdaten extrahiert haben, extrahieren wir auf ähnliche Weise auch die Chatdaten.

         //--- Extract chat details from the JSON object
         obj_msg.chat_id=obj_item["message"]["chat"]["id"].ToInt(); //--- Get the chat ID
         obj_msg.chat_first_name=obj_item["message"]["chat"]["first_name"].ToStr(); //--- Get the chat's first name
         obj_msg.chat_first_name=decodeStringCharacters(obj_msg.chat_first_name); //--- Decode the first name
         obj_msg.chat_last_name=obj_item["message"]["chat"]["last_name"].ToStr(); //--- Get the chat's last name
         obj_msg.chat_last_name=decodeStringCharacters(obj_msg.chat_last_name); //--- Decode the last name
         obj_msg.chat_username=obj_item["message"]["chat"]["username"].ToStr(); //--- Get the chat's username
         obj_msg.chat_username=decodeStringCharacters(obj_msg.chat_username); //--- Decode the username
         obj_msg.chat_type=obj_item["message"]["chat"]["type"].ToStr(); //--- Get the chat type

Bis zu diesem Punkt sollten Sie bemerkt haben, dass die Struktur genau die gleiche ist wie die, die wir in der Datenstruktur des Browsers angegeben haben. Dann können wir die Update-ID aktualisieren, um sicherzustellen, dass die nächste Anfrage nach Updates von Telegram an der richtigen Stelle beginnt.

         //--- Update the ID for the next request
         member_update_id=obj_msg.update_id+1;

Hier aktualisieren wir die „member_update_id“, um sicherzustellen, dass die nächste Anfrage nach Updates von Telegram an der richtigen Stelle beginnt. Indem wir den Wert „obj_msg.update_id + 1“ zuweisen, stellen wir den Offset so ein, dass die nächste Anfrage nicht die aktuelle Aktualisierung enthält und nur neue Aktualisierungen erhält, die nach dieser ID erfolgen. Das ist wichtig, weil wir dieselbe Aktualisierung nicht mehr als einmal durchführen wollen, und wir wollen auch, dass der Bot so schnell wie möglich reagiert. Als Nächstes wird nach neuen Updates gesucht.

         //--- If it's the first update, skip processing
         if(member_first_remove){
            continue;
         }

Hier stellen wir fest, ob die aktuelle Aktualisierung die erste Aktualisierung nach der Initialisierung ist, die verarbeitet wird, indem wir das Flag „member_first_remove“ überprüfen. Wenn „member_first_remove“ wahr ist, bedeutet dies, dass wir die erste Aktualisierung - die erste Aktualisierung - verarbeiten, nachdem alles initialisiert worden ist. Wir überspringen dann die Verarbeitung dieser Aktualisierung, indem wir einfach mit der nächsten fortfahren. Schließlich filtern und verwalten wir Chat-Nachrichten, je nachdem, ob ein Nutzernamens-Filter angewendet wird.

         //--- Filter messages based on username
         if(member_users_filter.Total()==0 || //--- If no filter is applied, process all messages
            (member_users_filter.Total()>0 && //--- If a filter is applied, check if the username is in the filter
            member_users_filter.SearchLinear(obj_msg.from_username)>=0)){

            //--- Find the chat in the list of chats
            int index=-1;
            for(int j=0; j<member_chats.Total(); j++){
               Class_Chat *chat=member_chats.GetNodeAtIndex(j);
               if(chat.member_id==obj_msg.chat_id){ //--- Check if the chat ID matches
                  index=j;
                  break;
               }
            }

            //--- If the chat is not found, add a new chat to the list
            if(index==-1){
               member_chats.Add(new Class_Chat); //--- Add a new chat to the list
               Class_Chat *chat=member_chats.GetLastNode();
               chat.member_id=obj_msg.chat_id; //--- Set the chat ID
               chat.member_time=TimeLocal(); //--- Set the current time for the chat
               chat.member_state=0; //--- Initialize the chat state
               chat.member_new_one.message_text=obj_msg.message_text; //--- Set the new message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
            //--- If the chat is found, update the chat message
            else{
               Class_Chat *chat=member_chats.GetNodeAtIndex(index);
               chat.member_time=TimeLocal(); //--- Update the chat time
               chat.member_new_one.message_text=obj_msg.message_text; //--- Update the message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
         }

Zunächst stellen wir fest, ob ein Nutzernamens-Filter aktiv ist, indem wir die „member_users_filter.Total()“ überprüfen. Wenn kein Filter vorhanden ist („Total() == 0“), werden alle Meldungen wie üblich behandelt. Wenn es einen Filter gibt („Total() > 0“), stellen wir mit „member_users_filter.SearchLinear()“ fest, ob der Nutzername des Absenders („obj_msg.from_username“) im Filter enthalten ist. Wenn wir den Nutzernamen finden, bearbeiten wir die Nachricht.

Wir suchen dann nach dem Chat in der Liste „member_chats“, indem wir sie durchgehen und die Chat-ID vergleichen („obj_msg.chat_id“). Wenn der Chat nicht gefunden wird (index == -1), fügen wir ein neues „Class_Chat“-Objekt zur Liste hinzu. Wir initialisieren das Objekt mit der Chat-ID, der aktuellen Uhrzeit, einem Anfangszustand von 0 und dem Text der neuen Nachricht. Wir kennzeichnen die neue Nachricht auch als nicht erledigt (erledigt = false).

Befindet sich der Chat bereits in der Liste, wird das bestehende Chat-Objekt mit dem neuen Text der Nachricht und der aktuellen Uhrzeit aktualisiert und die Nachricht als unbearbeitet markiert. Dadurch wird gewährleistet, dass die letzte Nachricht in jedem Chat aufgezeichnet und ordnungsgemäß aktualisiert wird. Nachdem alles erledigt ist, setzen wir das erste Aktualisierungsflag auf false.

      //--- After the first update, set the flag to false
      member_first_remove=false;

Schließlich geben wir das Ergebnis der Postanforderung zurück.

   //--- Return the result of the POST request
   return(res);

Mit dieser Funktion können wir sicher sein, dass wir die Chat-Aktualisierungen in jedem festgelegten Zeitintervall abrufen und speichern und sie somit bei Bedarf verarbeiten können. Die Verarbeitung der Meldungen erfolgt im nächsten Abschnitt.


Umgang mit Antworten

Nachdem wir die Chat-Aktualisierungen erhalten haben, können wir auf die abgerufenen Nachrichten zugreifen, Vergleiche anstellen und Antworten an Telegram zurücksenden. Dies wird durch die Verwendung der Funktion „ProcessMessages“ der Klasse erreicht.

void Class_Bot_EA::ProcessMessages(void){

//---

}

Als Erstes müssen wir die einzelnen Chats verarbeiten.

   //--- Loop through all chats
   for(int i=0; i<member_chats.Total(); i++){
      Class_Chat *chat=member_chats.GetNodeAtIndex(i); //--- Get the current chat
      if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet
         chat.member_new_one.done=true; //--- Mark the message as processed
         string text=chat.member_new_one.message_text; //--- Get the message text

         //---

      }
   }

Hier wird die Sammlung „member_chats“ durchlaufen und das entsprechende Chat-Objekt für jeden Chat mithilfe der Indexvariablen „i“ aus „member_chats“ abgerufen. Für jeden Chat wird die zugehörige Nachricht des aktuellen Chats überprüft, um festzustellen, ob sie bereits bearbeitet wurde, indem das „done“-Flag in der Struktur „member_new_one“ ausgewertet wird. Wenn die Nachricht noch nicht bearbeitet wurde, setzen wir dieses Flag auf true und kennzeichnen die Nachricht als bearbeitet, um eine doppelte Bearbeitung zu verhindern. Schließlich extrahieren wir den Text der Nachricht aus der Struktur „member_new_one“. Wir verwenden den Text, um festzustellen, welche Art von Reaktion oder Maßnahme gegebenenfalls auf der Grundlage des Inhalts der Nachricht ergriffen werden sollte. Definieren wir zunächst eine Instanz, in der der Nutzer einen Begrüßungstext „Hallo“ von Telegram sendet.

         //--- Process the command based on the message text
         
         //--- If the message is "Hello"
         if(text=="Hello"){
            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            
         }

Hier wird überprüft, ob der Nachrichtentext „Hallo“ lautet. Wenn dies der Fall ist, wird eine Antwort erstellt, die dem Nutzer mitteilt, dass das System den Text „Hallo“ empfangen und verarbeitet hat. Diese Antwort dient als Bestätigung, dass die Eingabe durch den MQL5-Code korrekt verarbeitet wurde. Anschließend senden wir diese Bestätigung an den Nutzer zurück, um ihm mitzuteilen, dass seine Eingabe erfolgreich verarbeitet wurde. Um die Antwort zu senden, müssen wir eine weitere Funktion erstellen, um die Antworten zu verarbeiten.

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram(const long chat_id,const string text,
                const string reply_markup=NULL){
   string output; //--- Variable to store the response from the request
   string url=TELEGRAM_BASE_URL+"/bot"+getTrimmedToken(InpToken)+"/sendMessage"; //--- Construct the URL for the Telegram API request

   //--- Construct parameters for the API request
   string params="chat_id="+IntegerToString(chat_id)+"&text="+UrlEncode(text); //--- Set chat ID and message text
   if(reply_markup!=NULL){ //--- If a reply markup is provided
      params+="&reply_markup="+reply_markup; //--- Add reply markup to parameters
   }
   params+="&parse_mode=HTML"; //--- Set parse mode to HTML (can also be Markdown)
   params+="&disable_web_page_preview=true"; //--- Disable web page preview in the message

   //--- Send a POST request to the Telegram API
   int res=postRequest(output,url,params,WEB_TIMEOUT); //--- Call postRequest to send the message
   return(res); //--- Return the response code from the request
}

Hier definieren wir die Funktion „sendMessageToTelegram“, die eine Nachricht an einen bestimmten Telegram-Chat unter Verwendung der Telegram Bot API sendet. Zunächst konstruieren wir die URL für die API-Anfrage, indem wir die Basis-URL für Telegram, das Bot-Token (das mit „getTrimmedToken“ abgerufen wird) und die spezifische Methode zum Senden von Nachrichten („sendMessage“) kombinieren. Diese URL ist wichtig, um die API-Anfrage an den richtigen Endpunkt zu leiten. Als Nächstes erstellen wir die Abfrageparameter für die Anfrage. Diese Parameter umfassen:

  • chat_id: Die ID des Chats, an den die Nachricht gesendet werden soll.
  • text: Der Inhalt der Nachricht, der URL-kodiert ist, um eine korrekte Übertragung zu gewährleisten.

Wenn eine nutzerdefinierte Beschriftung der Antworttaste („reply_markup“) angegeben wird, wird sie an die Parameter angehängt. Dies ermöglicht interaktive Schaltflächen in der Nachricht. Weitere Parameter sind:

  • parse_mode=HTML: Gibt an, dass die Nachricht als HTML interpretiert werden soll, sodass formatierter Text möglich ist.
  • disable_web_page_preview=true: Stellt sicher, dass alle Webseitenvorschauen in der Nachricht deaktiviert sind.

Schließlich sendet die Funktion die Anfrage mit der Funktion „postRequest“, die die eigentliche Kommunikation mit der Telegram-API übernimmt. Der Antwortcode dieser Anfrage wird zurückgegeben, um anzuzeigen, ob die Nachricht erfolgreich gesendet wurde oder ein Fehler aufgetreten ist.

Wir können diese Funktion dann mit den entsprechenden Parametern wie folgt aufrufen, um die Antwort zu senden.

            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;

Hier verwenden wir zunächst die Funktion „sendMessageToTelegram“, um die Antwortnachricht an den entsprechenden Telegram-Chat zu senden. Wir rufen die Funktion mit der „chat.member_id“ auf, die den richtigen Chat für die richtige Inhaltsnachricht anvisiert. Der Parameter „reply_markup“ ist auf NULL gesetzt, was bedeutet, dass die gesendete Nachricht keine Tastatur- oder interaktiven Elemente enthält. Nach dem Senden der Nachricht wird die Anweisung „continue“ verwendet. Das überspringt jeden verbleibenden Code in der Schleife, die gerade verarbeitet wird, und geht zur nächsten Iteration dieser Schleife über. Die Logik ist hier ganz einfach: Wir bearbeiten und leiten die Antwort auf die aktuelle Nachricht weiter. Danach geht es weiter, ohne weiteren Code für den aktuellen Chat oder die aktuelle Nachricht in der aktuellen Iteration zu verarbeiten. Nach dem Kompilieren ergibt sich folgendes Bild.

HALLO WORLD

Wir können sehen, dass die Nachricht innerhalb von Sekunden empfangen und verarbeitet wurde. Anschließend fügen wir eine nutzerdefinierte Antworttastatur zu unserer Funktion hinzu.

//+------------------------------------------------------------------+
//| Create a custom reply keyboard markup for Telegram               |
//+------------------------------------------------------------------+
string customReplyKeyboardMarkup(const string keyboard, const bool resize,
                           const bool one_time){
   // Construct the JSON string for the custom reply keyboard markup.
   // 'keyboard' specifies the layout of the custom keyboard.
   // 'resize' determines whether the keyboard should be resized to fit the screen.
   // 'one_time' specifies if the keyboard should disappear after being used once.
   
   // 'resize' > true: Resize the keyboard to fit the screen.
   // 'one_time' > true: The keyboard will disappear after the user has used it once.
   // 'selective' > false: The keyboard will be shown to all users, not just specific ones.
   
   string result = "{"
                   "\"keyboard\": " + UrlEncode(keyboard) + ", " //--- Encode and set the keyboard layout
                   "\"one_time_keyboard\": " + convertBoolToString(one_time) + ", " //--- Set whether the keyboard should disappear after use
                   "\"resize_keyboard\": " + convertBoolToString(resize) + ", " //--- Set whether the keyboard should be resized to fit the screen
                   "\"selective\": false" //--- Keyboard will be shown to all users
                   "}";
   
   return(result); //--- Return the JSON string for the custom reply keyboard
}

Hier definieren wir die Funktion „customReplyKeyboardMarkup“, die eine eigene Antworttastatur für Telegram erstellt. Diese Funktion benötigt drei Parameter: keyboard, resize und one_time. Der Parameter keyboard gibt das Layout der nutzerdefinierten Tastatur im JSON-Format an. Der Parameter resize bestimmt, ob die Größe der Tastatur an den Bildschirm des Nutzers angepasst wird. Wenn der Parameter resize auf true gesetzt ist, wird die Größe der Tastatur an den Bildschirm des Nutzers angepasst. Der Parameter one_time gibt an, ob die Tastatur eine „einmalige“ Tastatur sein soll, die verschwindet, nachdem der Nutzer mit ihr interagiert hat.

Innerhalb der Funktion wird eine JSON-Zeichenfolge erstellt, die das nutzerdefinierte Tastaturlayout für die Antwort darstellt. Um sicherzustellen, dass der Tastaturparameter für die API-Anfrage richtig formatiert ist, verwenden wir die Funktion „UrlEncode“, um ihn zu kodieren. Als Nächstes verwenden wir die Funktion „convertBoolToString“, um die booleschen Werte für „resize“ und „one_time“ (die festlegen, ob diese Werte als „true“ oder „false“ zu betrachten sind) in ihre String-Darstellung zu ändern. Schließlich wird der konstruierte Text von der Funktion zurückgegeben und kann in API-Anfragen an Telegram verwendet werden. Die nutzerdefinierte Funktion, die wir verwenden, lautet wie folgt.

//+------------------------------------------------------------------+
//| Convert boolean value to string                                 |
//+------------------------------------------------------------------+
string convertBoolToString(const bool _value){
   if(_value)
      return("true"); //--- Return "true" if the boolean value is true
   return("false"); //--- Return "false" if the boolean value is false
}

Um schließlich die nutzerdefinierten Tastaturen auszublenden und eine Antwort zu erzwingen, verwenden wir die folgenden Funktionen.

//+------------------------------------------------------------------+
//| Create JSON for hiding custom reply keyboard                    |
//+------------------------------------------------------------------+
string hideCustomReplyKeyboard(){
   return("{\"hide_keyboard\": true}"); //--- JSON to hide the custom reply keyboard
}

//+------------------------------------------------------------------+
//| Create JSON for forcing a reply to a message                    |
//+------------------------------------------------------------------+
string forceReplyCustomKeyboard(){
   return("{\"force_reply\": true}"); //--- JSON to force a reply to the message
}

Hier erzeugen die Funktionen „hideCustomReplyKeyboard“ und „forceReplyCustomKeyboard“ JSON-Strings, die bestimmte Aktionen festlegen, die von der nutzerdefinierten Tastaturfunktion von Telegram ausgeführt werden sollen.

Für die Funktion „hideCustomReplyKeyboard“ lautet die von ihr erzeugte JSON-Zeichenfolge: „{\"hide_keyboard\": true}“. Diese JSON-Konfiguration weist Telegram an, die Antworttastatur auszublenden, nachdem der Nutzer eine Nachricht gesendet hat. Im Wesentlichen dient diese Funktion dazu, die Tastatur verschwinden zu lassen, sobald eine Nachricht gesendet wurde.

Für die Funktion „forceReplyCustomKeyboard“ lautet die von ihr erzeugte JSON-Zeichenfolge: „{\"force_reply\": true}“. Diese Zeichenfolge weist Telegram an, eine Antwort vom Nutzer zu verlangen, bevor dieser mit einem anderen UI-Element im Chat interagieren kann. Diese Zeichenfolge dient dazu, dass der Nutzer nur mit der soeben gesendeten Nachricht interagiert.

Mit der Funktion der nutzerdefinierten Antworttastatur bewaffnet, rufen wir nun die Funktion auf, um die Antworttastatur in Telegram zu erstellen.

         //--- If the message is "Hello"
         if(text=="Hello"){
            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup("[[\"Hello\"]]",false,false));
            continue;
         }

Wenn wir die Nachricht in Telegram senden, erhalten wir das folgende Ergebnis.

NUTZERDEFINIERTE ANTWORT MIT TASTATUR: HALLO

Wir sehen, dass das ein Erfolg war. Jetzt können wir die Nachricht durch einfaches Anklicken der Schaltfläche versenden. Sie ist jedoch ziemlich groß. Wir können jetzt mehrere Schaltflächen hinzufügen. Fügen wir zunächst Schaltflächen im Zeilenformat hinzu.

            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            string buttons_rows = "[[\"Hello 1\"],[\"Hello 2\"],[\"Hello 3\"]]";
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,false,false));
            continue;

Hier definieren wir ein nutzerdefiniertes Antwort-Tastaturlayout mit der Variablen „buttons_rows“. Diese Zeichenfolge „[[\"Hallo 1\"],[\"Hallo 2\"],[\"Hallo 3\"]]“ stellt eine Tastatur mit drei Tasten dar, die jeweils mit „Hallo 1“, „Hallo 2“ und „Hallo 3“ beschriftet sind. Das Format dieser Zeichenkette ist JSON, das von Telegram zur Darstellung der Tastatur verwendet wird. Nach der Ausführung erhalten wir die folgenden Ergebnisse.

ZEILEN LAYOUT

Um die Tastaturbelegung im Spaltenformat zu visualisieren, implementieren wir die folgende Logik.

            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            string buttons_rows = "[[\"Hello 1\",\"Hello 2\",\"Hello 3\"]]";
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,false,false));

Nach der Ausführung des Programms erhalten wir die folgende Ausgabe.

SPALTEN LAYOUT

Wir können sehen, dass das erhaltene Layout ein Spaltenformat hat, was bedeutet, dass der Vorgang erfolgreich war. Wir können nun mit der Erstellung komplexerer Befehle fortfahren. Als Erstes wollen wir eine nutzerdefinierte Liste von Befehlen erstellen, die der Nutzer schnell bearbeiten kann.

         //--- If the message is "/start", "/help", "Start", or "Help"
         if(text=="/start" || text=="/help" || text=="Start" || text=="Help"){
            //chat.member_state=0; //--- Reset the chat state
            string message="I am a BOT \xF680 and I work with your MT5 Forex trading account.\n";
            message+="You can control me by sending these commands \xF648 :\n";
            message+="\nInformation\n";
            message+="/name - get EA name\n";
            message+="/info - get account information\n";
            message+="/quotes - get quotes\n";
            message+="/screenshot - get chart screenshot\n";
            message+="\nTrading Operations\n";
            message+="/buy - open buy position\n";
            message+="/close - close a position\n";
            message+="\nMore Options\n";
            message+="/contact - contact developer\n";
            message+="/join - join our MQL5 community\n";
            
            //--- Send the response message with the main keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            continue;
         }

Hier wird überprüft, ob die eingehende Nachricht zu den vorgegebenen Befehlen „/start“, „/help“, „Start“ und „Hilfe“ gehört. Wenn es sich um einen dieser Befehle handelt, bereiten wir eine Begrüßungsnachricht vor, die den Bot dem Nutzer vorstellt und eine Liste von Befehlen enthält, die an den Bot gesendet werden können, um mit ihm zu interagieren. Wir lassen Teile dieser Liste weg und kategorisieren andere Teile, um dem Nutzer einen Überblick darüber zu geben, was er mit dem Bot tun kann. Schließlich senden wir diese Nachricht zusammen mit einer nutzerdefinierten Tastatur an den Nutzer zurück, die für die Interaktion mit dem Bot besser geeignet ist als die Befehlszeile. Wir haben auch die nutzerdefinierte Tastatur wie folgt definiert.

   #define EMOJI_CANCEL "\x274C" //--- Cross mark emoji
   #define KEYB_MAIN    "[[\"Name\"],[\"Account Info\"],[\"Quotes\"],[\"More\",\"Screenshot\",\""+EMOJI_CANCEL+"\"]]" //--- Main keyboard layout

Wir verwenden #define, um zwei Makro-Elemente zu definieren, die in der Nutzeroberfläche des Telegram-Bots verwendet werden sollen. Zunächst definieren wir „EMOJI_CANCEL“ als ein Emoji mit Kreuzzeichen unter Verwendung seiner Unicode-Darstellung „\x274C“. Wir werden dieses Emoji in der Tastaturbelegung verwenden, um die Option „Abbrechen“ anzuzeigen. Die Unicode-Darstellung des Emojis ist wie unten dargestellt:

KREUZZEICHEN UNICODE

Als Nächstes definieren wir „KEYB_MAIN“, das die Haupttastaturbelegung für den Bot darstellt. Die Tastatur ist als JSON-Array mit Reihen von Schaltflächen strukturiert. Das Layout enthält Optionen, die in der Befehlsliste enthalten sind, nämlich „Name“, „Kontoinformationen“, „Zitate“ und eine Zeile mit „Mehr“, „Screenshot“ und die Schaltfläche „Abbrechen“, die durch „EMOJI_CANCEL“ dargestellt wird. Diese Tastatur wird dem Nutzer angezeigt, sodass er mit dem Bot interagieren kann, indem er diese Tasten drückt, anstatt die Befehle manuell einzugeben. Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe.

TELEGRAMM JSON UI 1

Wir haben jetzt die JSON-formatierte nutzerdefinierte Tastatur und die Liste der Befehle, die wir an den Bot senden können. Was jetzt noch bleibt, ist die Erstellung der jeweiligen Antworten gemäß den empfangenen Befehlen von Telegram. Wir beginnen mit der Beantwortung des Befehls „/name“. 

         //--- If the message is "/name" or "Name"
         if (text=="/name" || text=="Name"){
            string message = "The file name of the EA that I control is:\n";
            message += "\xF50B"+__FILE__+" Enjoy.\n";
            sendMessageToTelegram(chat.member_id,message,NULL);
         }

Hier wird überprüft, ob die vom Nutzer empfangene Nachricht entweder „/name“ oder „Name“ lautet. Wenn diese Prüfung zu einem positiven Ergebnis führt, erstellen wir eine Antwort an den Nutzer, die den Namen der aktuell verwendeten Expert Advisor (EA)-Datei enthält. Wir initialisieren eine String-Variable namens „message“, die mit dem Text „The file name of the EA that I control is:\n“ beginnt. Nach dieser ersten Erklärung folgen ein Buch-Emoji (dargestellt durch den Code „\xF50B“) und der Name der EA-Datei.

Wir verwenden das eingebaute MQL5-Makro „__FILE__“, um den Namen der Datei zu erhalten. Das Makro gibt den Namen und den Pfad der Datei zurück. Anschließend erstellen wir eine Nachricht, die an den Nutzer gesendet wird. Die Nachricht besteht aus dem Namen der EA-Datei und dem Pfad zu ihr. Wir senden die konstruierte Nachricht mit der Funktion „sendMessageToTelegram“. Diese Funktion benötigt drei Parameter: Der erste ist die Chat-ID des Nutzers, an den die Nachricht gesendet werden soll; der zweite ist die Nachricht selbst; und der dritte Parameter, der auf „NULL“ gesetzt ist, zeigt an, dass wir keine nutzerdefinierten Tastatur- oder Tastenbefehle zusammen mit unserer Nachricht senden. Dies ist wichtig, da wir keine zusätzliche Tastatur erstellen wollen. Wenn wir entweder auf den Befehl „/name“ oder auf dessen Schaltfläche klicken, erhalten wir die entsprechende Antwort wie unten dargestellt.

NAMEN-BEFEHL

Das war ein Erfolg. In ähnlicher Weise gestalten wir die jeweiligen Antworten auf die Befehle für Kontoinformationen und Preisangaben. Dies wird durch den folgenden Codeabschnitt erreicht.

         //--- If the message is "/info" or "Account Info"
         ushort MONEYBAG = 0xF4B0; //--- Define money bag emoji
         string MONEYBAGcode = ShortToString(MONEYBAG); //--- Convert emoji to string
         if(text=="/info" || text=="Account Info"){
            string currency=AccountInfoString(ACCOUNT_CURRENCY); //--- Get the account currency
            string message="\x2733\Account No: "+(string)AccountInfoInteger(ACCOUNT_LOGIN)+"\n";
            message+="\x23F0\Account Server: "+AccountInfoString(ACCOUNT_SERVER)+"\n";
            message+=MONEYBAGcode+"Balance: "+(string)AccountInfoDouble(ACCOUNT_BALANCE)+" "+currency+"\n";
            message+="\x2705\Profit: "+(string)AccountInfoDouble(ACCOUNT_PROFIT)+" "+currency+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

         //--- If the message is "/quotes" or "Quotes"
         if(text=="/quotes" || text=="Quotes"){
            double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Get the current ask price
            double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Get the current bid price
            string message="\xF170 Ask: "+(string)Ask+"\n";
            message+="\xF171 Bid: "+(string)Bid+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Für die Befehle der Handelsoperationen, insbesondere die Eröffnung einer Kaufposition, verwenden wir die folgende Logik.

         //--- If the message is "/buy" or "Buy"
         if (text=="/buy" || text=="Buy"){
            CTrade obj_trade; //--- Create a trade object
            double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Get the current ask price
            double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Get the current bid price
            obj_trade.Buy(0.01,NULL,0,Bid-300*_Point,Bid+300*_Point); //--- Open a buy position
            double entry=0,sl=0,tp=0,vol=0;
            ulong ticket = obj_trade.ResultOrder(); //--- Get the ticket number of the new order
            if (ticket > 0){
               if (PositionSelectByTicket(ticket)){ //--- Select the position by ticket
                  entry=PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the entry price
                  sl=PositionGetDouble(POSITION_SL); //--- Get the stop loss price
                  tp=PositionGetDouble(POSITION_TP); //--- Get the take profit price
                  vol=PositionGetDouble(POSITION_VOLUME); //--- Get the volume
               }
            }
            string message="\xF340\Opened BUY Position:\n";
            message+="Ticket: "+(string)ticket+"\n";
            message+="Open Price: "+(string)entry+"\n";
            message+="Lots: "+(string)vol+"\n";
            message+="SL: "+(string)sl+"\n";
            message+="TP: "+(string)tp+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Hier behandeln wir ein Szenario, in dem der Nutzer die Nachricht „/buy“ oder „Buy“ sendet. Unser erster Schritt besteht darin, ein CTrade-Objekt mit dem Namen „obj_trade“ zu erstellen, das wir für die Durchführung der Handelsoperationen verwenden werden. Anschließend erhalten wir die aktuellen Geld- und Briefkurse (bid & ask), indem wir die Funktion SymbolInfoDouble aufrufen. Um unsere Kaufposition zu eröffnen, verwenden wir die Funktion Buy des CTrade-Objekts. Wir haben das Handelsvolumen auf 0,01 Lots festgelegt. Für unseren SL (Stop Loss) und TP (Take Profit) setzen wir den Geldkurs (bid) minus 300 Punkte bzw. den Geldkurs plus 300 Punkte.

Sobald die Position eröffnet ist, ermitteln wir die Ticketnummer des neuen Auftrags über die Funktion „ResultOrder“. Mit dem Ticket in der Hand verwenden wir die Funktion PositionGetInteger, um die Position nach Ticket auszuwählen. Wir rufen dann wichtige Statistiken wie Einstiegskurs, Volumen, Stop Loss und Take Profit ab. Anhand dieser Zahlen erstellen wir eine Nachricht, die den Nutzer darüber informiert, dass er eine Kaufposition eröffnet hat. Für die Positionsschließung und den Kontaktbefehl verwenden wir die folgende ähnliche Logik.

         //--- If the message is "/close" or "Close"
         if (text=="/close" || text=="Close"){
            CTrade obj_trade; //--- Create a trade object
            int totalOpenBefore = PositionsTotal(); //--- Get the total number of open positions before closing
            obj_trade.PositionClose(_Symbol); //--- Close the position for the symbol
            int totalOpenAfter = PositionsTotal(); //--- Get the total number of open positions after closing
            string message="\xF62F\Closed Position:\n";
            message+="Total Positions (Before): "+(string)totalOpenBefore+"\n";
            message+="Total Positions (After): "+(string)totalOpenAfter+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

         //--- If the message is "/contact" or "Contact"
         if (text=="/contact" || text=="Contact"){
            string message="Contact the developer via link below:\n";
            message+="https://t.me/Forex_Algo_Trader";
            
            //--- Send the contact message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Es ist nun klar, dass wir auf Befehle reagieren können, die von Telegram gesendet werden. Bis zu diesem Punkt senden wir nur einfache Textnachrichten. Seien wir etwas ausgefallener und formatieren wir unsere Textnachrichten mit Hypertext Markup Language (HTML), die auch ein Markdown sein könnte. Sie haben die Wahl!

         //--- If the message is "/join" or "Join"
         if (text=="/join" || text=="Join"){
            string message="You want to be part of our MQL5 Community?\n";
            message+="Welcome! <a href=\"https://t.me/forexalgo_trading\">Click me</a> to join.\n";
            message+="<s>Civil Engineering</s> Forex AlgoTrading\n";//strikethrough
            message+="<pre>This is a sample of our MQL5 code</pre>\n";//preformat
            message+="<u><i>Remember to follow community guidelines!\xF64F\</i></u>\n";//italic, underline
            message+="<b>Happy Trading!</b>\n";//bold
            
            //--- Send the join message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Hier reagieren wir auf den Nutzer, wenn er die Nachricht „/join“ oder „Join“ sendet. Wir beginnen damit, eine Nachricht zu verfassen, die den Nutzer einlädt, der MQL5-Community beizutreten. Die Nachricht enthält einen Hyperlink, auf den die Nutzer klicken können, um der Community beizutreten, sowie mehrere Beispiele dafür, wie der Text mit HTML-Tags in Telegram formatiert werden kann:

  • Durchgestrichener Text: Wir verwenden den <s>-Tag, um die Worte „Civil Engineering“ zu durchstreichen und zu betonen, dass wir uns auf „Forex AlgoTrading“ konzentrieren.
  • Vorformatierter Text: Der <pre>-Tag wird verwendet, um ein Beispiel für MQL5-Code in einem vorformatierten Textblock anzuzeigen.
  • Kursiver und unterstrichener Text: Die <u>- und <i>-Tags werden kombiniert, um eine Erinnerung an die Einhaltung der Community-Richtlinien zu unterstreichen und kursiv zu setzen, wobei ein Unicode-Emoji zur Betonung hinzugefügt wird.
  • Fettgedruckter Text: Der <b>-Tag wird verwendet, um die abschließende Aussage „Happy Trading!“ fett darzustellen.

Schließlich senden wir diese formatierte Nachricht mit der Funktion „sendMessageToTelegram“ über Telegram an den Nutzer und stellen so sicher, dass der Nutzer eine gut formatierte und ansprechende Einladung erhält, der MQL5 Community beizutreten. Nach der Ausführung erhalten wir die folgende Ausgabe.

HTML-ENTITÄT

Nachdem wir nun die Befehlslisten geleert haben, fahren wir damit fort, die Antworttastatur zu ändern und eine neue zu erzeugen, sobald die Schaltfläche „mehr“ angeklickt wird. Die folgende Logik ist implementiert.

         //--- If the message is "more" or "More"
         if (text=="more" || text=="More"){
            chat.member_state=1; //--- Update chat state to show more options
            string message="Choose More Options Below:";
            
            //--- Send the more options message with the more options keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MORE,false,true));
            continue;
         }

Wenn wir die Nachricht „more“ oder „More“ vom Nutzer erhalten, nehmen wir dies als Signal, den Kontext der aktuellen Unterhaltung zu aktualisieren. In der Welt der Chatbots zeigt die Nachricht an, dass der Nutzer mit der aktuellen Anzahl der Optionen nicht zufrieden ist oder bisher nicht gefunden hat, wonach er sucht. Unsere Antwort an den Nutzer muss daher eine andere Vielfalt an Auswahlmöglichkeiten bieten. In der Praxis bedeutet dies, dass wir dem Nutzer eine neue Nachricht mit einem neuen Tastaturlayout schicken. Der „KEYB_MORE“ ist wie unten dargestellt:

   #define EMOJI_UP    "\x2B06" //--- Upwards arrow emoji
   #define KEYB_MORE "[[\""+EMOJI_UP+"\"],[\"Buy\",\"Close\",\"Next\"]]" //--- More options keyboard layout

Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe.

MEHR GIF

Das war ein Erfolg. Die anderen Befehle können wir ähnlich handhaben.

         //--- If the message is the up emoji
         if(text==EMOJI_UP){
            chat.member_state=0; //--- Reset chat state
            string message="Choose a menu item:";
            
            //--- Send the message with the main keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            continue;
         }

         //--- If the message is "next" or "Next"
         if(text=="next" || text=="Next"){
            chat.member_state=2; //--- Update chat state to show next options
            string message="Choose Still More Options Below:";
            
            //--- Send the next options message with the next options keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_NEXT,false,true));
            continue;
         }

         //--- If the message is the pistol emoji
         if (text==EMOJI_PISTOL){
            if (chat.member_state==2){
               chat.member_state=1; //--- Change state to show more options
               string message="Choose More Options Below:";
               
               //--- Send the message with the more options keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MORE,false,true));
            }
            else {
               chat.member_state=0; //--- Reset chat state
               string message="Choose a menu item:";
               
               //--- Send the message with the main keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            }
            continue;
         }

         //--- If the message is the cancel emoji
         if (text==EMOJI_CANCEL){
            chat.member_state=0; //--- Reset chat state
            string message="Choose /start or /help to begin.";
            
            //--- Send the cancel message with hidden custom reply keyboard
            sendMessageToTelegram(chat.member_id,message,hideCustomReplyKeyboard());
            continue;
         }

Hier geht es um diverse Nutzermeldungen zur Steuerung der Chat-Schnittstelle. Wenn ein Nutzer das Aufwärts-Emoji sendet, werten wir das als Signal und setzen den Chat-Status auf 0 zurück, sodass der Nutzer erneut einen Menüpunkt auswählen kann, begleitet vom Haupttastaturlayout. Wenn ein Nutzer „next“ oder „Next“ sendet, aktualisieren wir den Chat-Status auf 2 und weisen den Nutzer erneut an, einen Menüpunkt auszuwählen, diesmal über ein Tastaturlayout, das zusätzliche Optionen bietet.

Für das Pistolen-Emoji passen wir den Chat-Status auf der Grundlage seines aktuellen Werts an: Wenn der Status 2 ist, schalten wir ihn auf 1 und zeigen die Tastatur für weitere Optionen an; wenn der Status anders ist, schalten wir ihn auf 0 und zeigen die Tastatur für das Hauptmenü an. Für das Abbruch-Emoji setzen wir den Chat-Status auf 0 zurück und senden dem Nutzer eine Nachricht, die ihn auffordert, entweder „/start“ oder „/help“ zu wählen, um zu beginnen. Wir senden diese Nachricht mit der versteckten nutzerdefinierten Antworttastatur, um alle aktiven nutzerdefinierten Tastaturen für den Nutzer zu löschen. Die zusätzlich verwendeten nutzerdefinierten Layouts sind wie folgt:

   #define EMOJI_PISTOL   "\xF52B" //--- Pistol emoji
   #define KEYB_NEXT "[[\""+EMOJI_UP+"\",\"Contact\",\"Join\",\""+EMOJI_PISTOL+"\"]]" //--- Next options keyboard layout

Bis zu diesem Punkt ist alles vollständig. Wir müssen nur noch die Befehle für den Screenshot bearbeiten und das war's. Die folgende Logik ist implementiert, um die Art des Empfangs der Chartbilder zu behandeln. Zu diesem Zweck wird das Tastaturlayout verwendet, sodass wir nicht mehr manuell tippen müssen.

         //--- If the message is "/screenshot" or "Screenshot"
         static string symbol = _Symbol; //--- Default symbol
         static ENUM_TIMEFRAMES period = _Period; //--- Default period
         if (text=="/screenshot" || text=="Screenshot"){
            chat.member_state = 10; //--- Set state to screenshot request
            string message="Provide a symbol like 'AUDUSDm'";
            
            //--- Send the message with the symbols keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
            continue;
         }

         //--- Handle state 10 (symbol selection for screenshot)
         if (chat.member_state==10){
            string user_symbol = text; //--- Get the user-provided symbol
            if (SymbolSelect(user_symbol,true)){ //--- Check if the symbol is valid
               chat.member_state = 11; //--- Update state to period request
               string message = "CORRECT: Symbol is found\n";
               message += "Now provide a Period like 'H1'";
               symbol = user_symbol; //--- Update symbol
               
               //--- Send the message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
            }
            else {
               string message = "WRONG: Symbol is invalid\n";
               message += "Provide a correct symbol name like 'AUDUSDm' to proceed.";
               
               //--- Send the invalid symbol message with the symbols keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
            }
            continue;
         }

         //--- Handle state 11 (period selection for screenshot)
         if (chat.member_state==11){
            bool found=false; //--- Flag to check if period is valid
            int total=ArraySize(periods); //--- Get the number of defined periods
            for(int k=0; k<total; k++){
               string str_tf=StringSubstr(EnumToString(periods[k]),7); //--- Convert period enum to string
               if(StringCompare(str_tf,text,false)==0){ //--- Check if period matches
                  ENUM_TIMEFRAMES user_period=periods[k]; //--- Set user-selected period
                  period = user_period; //--- Update period
                  found=true;
                  break;
               }
            }
            if (found){
               string message = "CORRECT: Period is valid\n";
               message += "Screenshot sending process initiated \xF60E";
               
               //--- Send the valid period message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
               string caption = "Screenshot of Symbol: "+symbol+
                                " ("+EnumToString(ENUM_TIMEFRAMES(period))+
                                ") @ Time: "+TimeToString(TimeCurrent());
               
               //--- Send the screenshot to Telegram
               sendScreenshotToTelegram(chat.member_id,symbol,period,caption);
            }
            else {
               string message = "WRONG: Period is invalid\n";
               message += "Provide a correct period like 'H1' to proceed.";
               
               //--- Send the invalid period message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
            }
            continue;
         }

In diesem Fall werden die Anfragen des Nutzers nach einem Screenshot eines Charts bearbeitet, indem die verschiedenen Zustände des Chatverlaufs verwaltet werden. Wenn der Nutzer den Befehl „/screenshot“ oder „Screenshot“ sendet, setzen wir den Chatstatus auf 10 und fordern den Nutzer zur Eingabe eines Symbols auf, indem wir eine Tastatur mit den verfügbaren Symbolen anzeigen. Dabei ist zu beachten, dass der Chatstatus eine beliebige Zahl sein kann, sogar 1000. Er dient lediglich als Bezeichner oder Quantifizierer, um den Zustand zu speichern, an den wir uns während der Antwortverarbeitung erinnern. Wenn der Nutzer ein Symbol angibt, prüfen wir dessen Gültigkeit. Wenn sie gültig ist, fragen wir den Nutzer nach einem Zeitraum (einer gültigen „Zeit“ für das Chart), indem wir eine Tastatur mit den verfügbaren Optionen für Zeiträume anzeigen. Wenn der Nutzer ein ungültiges Symbol angibt, benachrichtigen wir ihn und fordern ihn auf, uns ein gültiges Symbol zu geben.

Wenn der Nutzer einen Zeitrahmen eingibt, prüfen wir, ob dieser gültig ist. Handelt es sich bei dem Zeitrahmen um eine der vordefinierten gültigen Optionen, aktualisieren wir den Chat-Status und leiten die Anfrage des Nutzers nach einem Screenshot des in der letzten gültigen Beschriftung angegebenen Symbols mit den für die implizite „Wenn-Dann“-Anweisung, mit der wir begonnen haben, erforderlichen Details zur rechtzeitigen Erfüllung weiter - und initiieren den Screenshot-Prozess in unserem Backend. Wenn der Nutzer hingegen einen Zeitrahmen angibt, der nicht mit einer der gültigen vordefinierten Optionen übereinstimmt, teilen wir dem Nutzer lediglich mit, dass die Eingabe fehlerhaft war, und wiederholen die gültigen Optionen, die wir entsprechend der ursprünglichen Eingabeaufforderung angezeigt haben. Die nutzerdefinierten Antworttastaturen für die Symbole und Punkte sowie das von uns verwendete Zeitrahmen-Array sind unten definiert.

   #define KEYB_SYMBOLS "[[\""+EMOJI_UP+"\",\"AUDUSDm\",\"AUDCADm\"],[\"EURJPYm\",\"EURCHFm\",\"EURUSDm\"],[\"USDCHFm\",\"USDCADm\",\""+EMOJI_PISTOL+"\"]]" //--- Symbol selection keyboard layout
   #define KEYB_PERIODS "[[\""+EMOJI_UP+"\",\"M1\",\"M15\",\"M30\"],[\""+EMOJI_CANCEL+"\",\"H1\",\"H4\",\"D1\"]]" //--- Period selection keyboard layout

   //--- Define timeframes array for screenshot requests
   const ENUM_TIMEFRAMES periods[] = {PERIOD_M1,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1};

Bis zu diesem Punkt sind wir nun mit unserer vollständig angepassten Tastatur und den Antworten fertig. Um dies festzustellen, führen wir das Programm aus. Hier sind die Ausgabeergebnisse, die wir erhalten.

SCREENSHOT GIF

Hier können wir sehen, dass der Screenshot-Versand eingeleitet und durchgeführt wird. Ungültige Befehle oder Eingaben werden so behandelt, dass sichergestellt ist, dass nur gültige Befehle vom Nutzer gesendet werden. Um sicherzustellen, dass alles wie vorgesehen funktioniert, und um etwaige Einschränkungen zu ermitteln, müssen wir die Umsetzung gründlich testen. Dies geschieht im nächsten Abschnitt.


Testen der Implementierung

Das Testen ist eine entscheidende Phase, um zu überprüfen, ob das von uns erstellte Programm wie vorgesehen funktioniert. Wir müssen also überprüfen, ob es richtig funktioniert. Als Erstes aktivieren wir die Webseitenvorschau in unseren Linkantworten. Durch das Zulassen von Webseitenvorschauen in Links können die Nutzer einen Blick auf den Inhalt werfen, bevor sie ihn anklicken. Sie sehen einen Titel und ein Bild, die oft einen guten Eindruck davon vermitteln, worum es auf der verlinkten Seite geht. Dies ist aus Sicht der Nutzerfreundlichkeit großartig, vor allem, wenn man bedenkt, dass es oft schwierig ist, die Qualität eines Links nur anhand des Textes des Links selbst zu beurteilen. Wir werden also die deaktivierte Vorschau wie folgt auf false setzen.

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram( ... ){
   
   //--- ...

   params+="&disable_web_page_preview=false"; //--- Enable web page preview in the message

   //--- ...
   
}

Sobald wir dies ausführen, erhalten wir die folgende Ausgabe.

WEBSEITENVORSCHAU AKTIVIERT

Wir können nun die Webseitenvorschau wie gezeigt empfangen. Das war ein Erfolg. Anschließend können wir die Formatierungsentität oder den Parse-Modus von Hypertext Markup Language (HTML) auf Markdown wie folgt ändern:

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram( ... ){

   //--- ...

   params+="&parse_mode=Markdown"; //--- Set parse mode to Markdown (can also be HTML)

   //--- ...

}

Im Markdown-Parsing-Modus müssen wir die gesamte Formatierungsstruktur unseres ursprünglichen Codes mit Markdown-Entities ändern. Die korrekte Form ist die folgende. 

      //--- If the message is "/join" or "Join"
      if (text=="/join" || text=="Join"){
         string message = "You want to be part of our MQL5 Community?\n";
         message += "Welcome! [Click me](https://t.me/forexalgo_trading) to join.\n"; // Link
         message += "~Civil Engineering~ Forex AlgoTrading\n"; // Strikethrough
         message += "```\nThis is a sample of our MQL5 code\n```"; // Preformatted text
         message += "*_Remember to follow community guidelines! \xF64F_*"; // Italic and underline
         message += "**Happy Trading!**\n"; // Bold
      
         //--- Send the join message
         sendMessageToTelegram(chat.member_id, message, NULL);
         continue;
      }

Wir haben folgende Änderungen vorgenommen:

  • Link: In Markdown werden Links mit [text](URL) anstelle von <a href="URL">text</a> erstellt.
  • Durchgestrichen: Verwenden Sie ~text~ zum Durchstreichen anstelle von <s>text</s>.
  • Vorformatierter Text: Verwenden Sie dreifache Backticks (```) zur Formatierung von vorformatiertem Text anstelle von <pre>Text</pre>.
  • Kursiv und unterstrichen: Markdown unterstützt von Haus aus keine Unterstreichungen. Am ehesten können Sie kursiv mit *Text* oder _Text_ schreiben. Der Unterstreichungseffekt von HTML wird in Markdown nicht direkt unterstützt, daher wird er bei Bedarf mit einem Platzhalter eingefügt.
  • Fett: Doppelte Sternchen **text** für Fettdruck anstelle von <b>text</b> verwenden.

Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe.

MARKDOWN-AUSGABE

Zur Veranschaulichung des Testverfahrens haben wir ein Video vorbereitet, das das Programm in Aktion zeigt. Dieses Video veranschaulicht die verschiedenen Testfälle, die wir durchgeführt haben, und zeigt, wie das Programm auf verschiedene Eingaben reagierte und wie gut es seine notwendigen Aufgaben erfüllte. Wenn Sie sich dieses Video ansehen, erhalten Sie ein sehr klares Bild vom Testprozess und können zweifelsfrei feststellen, dass die Implementierung die erwarteten Anforderungen erfüllt. Das Video ist unten zu sehen.

Im Ganzen können wir feststellen, dass die erfolgreiche Durchführung und Überprüfung der Implementierung, wie im beigefügten Video gezeigt, bestätigen, dass das Programm wie vorgesehen funktioniert.


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass der von uns entwickelte Expert Advisor die Sprache MetaQuotes Language 5 (MQL5) - zusammen mit der Handelsplattform MetaTrader 5 - die Messaging-App Telegram integriert, sodass die Nutzer buchstäblich mit ihren Handelsrobotern sprechen können. Und warum nicht? Telegram hat sich als leistungsstarke und nutzerfreundliche Methode zur Steuerung automatisierter Handelssysteme erwiesen. Damit kann man in Echtzeit Befehle senden und Antworten vom System erhalten.

In unserem Fall haben wir dafür gesorgt, dass wir nicht darauf warten, dass der Expert Advisor mit dem Telegram Bot kommuniziert, der die Kommunikation an den Nutzer weiterleitet, sondern wir verbinden die beiden Bots und kommunizieren mit dem Expert Advisor, wann immer wir wollen, ohne auf eine Signalgenerierung warten zu müssen. Wir haben eine Reihe von Konversationen zwischen dem Nutzer und dem Bot aufgebaut. Wir haben sichergestellt, dass die MQL5-Befehle, die der Nutzer über Telegram sendet, korrekt interpretiert werden. Nach zahlreichen Tests können wir mit Zuversicht sagen, dass unser Expert Advisor sowohl zuverlässig als auch robust ist.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (6)
Allan Munene Mutiiria
Allan Munene Mutiiria | 2 Okt. 2024 in 16:29
Extratimber Alpha #:

Sehr beeindruckende Arbeit!!

Damit können die folgenden Funktionen realisiert werden:

Tradingview-Alarm an Telegramm

Telenachricht an MQL5

THX!

@Extratimber Alpha vielen Dank für das freundliche Feedback. Wir sind froh, dass Sie es hilfreich fanden.
Oluwatosin Michael Akinyemi
Oluwatosin Michael Akinyemi | 23 März 2025 in 08:35
obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Abrufen der Aktualisierungs-ID
         obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Abrufen der Nachrichten-ID
         obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Abrufen des Nachrichtendatums

Hallo Allan, danke für diesen tollen Artikel.

Leider scheint der Code ab Zeile 1384 beim Extrahieren von Nachrichtendetails aus dem JSON-Objekt defekt zu sein. Der erste Code in Zeile 1383

obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Abrufen der Aktualisierungs-ID

funktioniert gut, wenn er in das Journal gedruckt wird. Die Update-ID gibt eine gültige ID zurück, aber die message_id, message_date und alle anderen Instanzen geben einen leeren Wert zurück. Aufgrund dieser Probleme scheint im Code nichts so zu funktionieren, wie es erwartet werden sollte.

Können Sie bitte helfen, diese Probleme zu lösen?

Nochmals vielen Dank, dass Sie sich die Zeit genommen haben, diesen Artikel zu schreiben.

Oluwatosin Michael Akinyemi
Oluwatosin Michael Akinyemi | 25 März 2025 in 11:38
Oluwatosin Michael Akinyemi #:

Hallo Allan, danke für diesen tollen Artikel.

Leider scheint der Code ab Zeile 1384 beim Extrahieren von Nachrichtendetails aus dem JSON-Objekt defekt zu sein. Der erste Code in Zeile 1383

funktioniert gut, wenn er in das Journal gedruckt wird. Die Update-ID gibt eine gültige ID zurück, aber die message_id, message_date und alle anderen Instanzen geben einen leeren Wert zurück. Aufgrund dieser Probleme scheint im Code nichts so zu funktionieren, wie es erwartet werden sollte.

Können Sie bitte helfen, diese Probleme zu lösen?

Nochmals vielen Dank, dass Sie sich die Zeit genommen haben, diesen Artikel zu schreiben.

Hallo Allan, ich habe endlich herausgefunden, dass das Problem auf meiner Seite liegt. Vielen Dank für diesen hervorragenden Artikel!

Allan Munene Mutiiria
Allan Munene Mutiiria | 25 März 2025 in 20:24
Oluwatosin Michael Akinyemi #:

Hallo Allan, ich habe endlich herausgefunden, dass das Problem auf meiner Seite liegt. Vielen Dank für diesen hervorragenden Artikel!

@Oluwatosin Michael Akinyemi danke für den Hinweis. Herzlich willkommen.
Tai Tran
Tai Tran | 30 Juni 2025 in 15:18
Hallo Allan, danke für das hilfreiche Tutorial.

Wenn ich kompiliere, erhalte ich folgende Fehlermeldung:


----------------------------------------------------------------------------------------------------------------------------

'ArrayAdd' - keine der Überladungen kann auf den Funktionsaufruf angewendet werden TELEGRAM_MQL5_COMMANDS_PART5.mq5 1151 4

könnte eine von 2 Funktion(en) sein TELEGRAM_MQL5_COMMANDS_PART5.mq5 1151 4

void ArrayAdd(uchar&[],const uchar&[]) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1186 6

void ArrayAdd(char&[],const string) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1200 6


'ArrayAdd' - keine der Überladungen kann auf den Funktionsaufruf angewendet werden TELEGRAM_MQL5_COMMANDS_PART5.mq5 1223 7

könnte eine von 2 Funktion(en) sein TELEGRAM_MQL5_COMMANDS_PART5.mq5 1223 7

void ArrayAdd(uchar&[],const uchar&[]) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1186 6

void ArrayAdd(char&[],const string) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1200 6


2 Fehler, 0 Warnungen 2 0

----------------------------------------------------------------------------------------------------------------------------


Können Sie mir helfen, dies zu beheben

Vielen Dank im Voraus!
Neuinterpretation klassischer Strategien in MQL5 (Teil II): FTSE100 und britische Staatsanleihen Neuinterpretation klassischer Strategien in MQL5 (Teil II): FTSE100 und britische Staatsanleihen
In dieser Artikelserie untersuchen wir beliebte Handelsstrategien und versuchen, sie mithilfe von KI zu verbessern. Im heutigen Artikel greifen wir die klassische Handelsstrategie wieder auf, die auf der Beziehung zwischen dem Aktien- und dem Anleihemarkt basiert.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 37): Gaußsche Prozessregression mit linearen und Matérn-Kernel MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 37): Gaußsche Prozessregression mit linearen und Matérn-Kernel
Lineare Kernel sind die einfachste Matrix ihrer Art, die beim maschinellen Lernen für lineare Regression und Support Vector Machines verwendet wird. Der Matérn-Kernel hingegen ist eine vielseitigere Version der Radialbasisfunktion, die wir in einem früheren Artikel besprochen haben, und er eignet sich für die Abbildung von Funktionen, die nicht so glatt sind, wie es die RBF annehmen würde. Wir erstellen eine nutzerdefinierte Signalklasse, die beide Kernel für die Vorhersage von Long- und Short-Bedingungen verwendet.
Selbstoptimierender Expert Advisor mit MQL5 und Python (Teil III): Den Boom-1000-Algorithmus knacken Selbstoptimierender Expert Advisor mit MQL5 und Python (Teil III): Den Boom-1000-Algorithmus knacken
In dieser Artikelserie erörtern wir, wie wir Expert Advisors entwickeln können, die sich selbständig an dynamische Marktbedingungen anpassen. Im heutigen Artikel werden wir versuchen, ein tiefes neuronales Netz auf die synthetischen Märkte von Derivativen abzustimmen.
Einführung in MQL5 (Teil 9): Verstehen und Verwenden von Objekten in MQL5 Einführung in MQL5 (Teil 9): Verstehen und Verwenden von Objekten in MQL5
Lernen Sie, wie Sie Chart-Objekte in MQL5 mit aktuellen und historischen Daten erstellen und anpassen. Dieser projektbasierte Leitfaden hilft Ihnen bei der Visualisierung von Handelsgeschäften und der praktischen Anwendung von MQL5-Konzepten, was die Erstellung von Tools, die auf Ihre Handelsanforderungen zugeschnitten sind, erleichtert.