English Русский 中文 Español 日本語 Português
preview
Erstellen eines integrierten MQL5-Telegram Expert Advisors (Teil 2): Senden von Signalen von MQL5 an Telegram

Erstellen eines integrierten MQL5-Telegram Expert Advisors (Teil 2): Senden von Signalen von MQL5 an Telegram

MetaTrader 5Handelssysteme | 4 Oktober 2024, 17:14
163 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

Im ersten Teil unserer Serie über die Entwicklung eines in Telegram integrierten Expert Advisors für MQL5 haben wir die wesentlichen Schritte behandelt, die für die Verbindung von MQL5 und Telegram erforderlich sind. Die Einrichtung der eigentlichen Anwendung war der erste Schritt. Danach ging es an die Codierung. Der Grund für diese besondere Reihenfolge der Ereignisse wird hoffentlich in den nächsten Abschnitten deutlicher werden. Das Ergebnis ist, dass wir jetzt sowohl einen Bot haben, der Nachrichten empfangen kann, als auch ein Programm, das sie senden kann. Wir haben auch ein einfaches MQL5-Programm geschrieben, das zeigt, wie man eine Nachricht über den Bot an die Anwendung sendet.

Nachdem wir in Teil 1 die Grundlagen geschaffen haben, können wir nun zum nächsten Schritt übergehen: die Übertragung von Handelssignalen an Telegram mit MQL5. Unser neu entwickelter Expert Advisor leistet etwas ganz Bemerkenswertes: Es eröffnet und schließt nicht nur Handelsgeschäfte auf der Grundlage voreingestellter Bedingungen, sondern vollbringt auch das ebenso beeindruckende Kunststück, ein Signal an einen Telegram-Gruppenchat zu senden, um uns zu informieren, dass ein Handelsgeschäft ausgeführt wurde. Die Handelssignale selbst wurden ein wenig überarbeitet, um sicherzustellen, dass die Informationen, die wir an Telegram senden, so klar und präzise wie möglich sind. Unser „Chatty Trader“ kommuniziert besser mit der Gruppe in Telegram als unsere vorherige Version, und zwar in der gleichen oder einer schnelleren Geschwindigkeit als unser alter „Chatty Trader“, was bedeutet, dass wir erwarten können, Signale fast in Echtzeit zu erhalten, wenn Handelsgeschäfte eröffnet oder geschlossen werden.

Wir werden Signale auf der Grundlage des berühmten System der kreuzenden gleitenden Durchschnitte generieren und die generierten Signale weiterleiten. Außerdem, wenn Sie sich erinnern, hatten wir in Teil 1 der Serie nur eine einzige Nachricht, die ziemlich lang sein konnte, und wenn jemand der Nachricht Segmente hinzufügen wollte, führte dies zu einem Fehler. So konnte jeweils nur eine einzige Nachricht gesendet werden, und wenn es zusätzliche Segmente gab, mussten diese in verschiedenen Einzelnachrichten übermittelt werden. Wenn Sie z. B. „Ein Kaufsignal wurde generiert“ und „Eröffnen Sie einen Kaufauftrag“ senden, handelt es sich entweder um eine einzige lange Nachricht oder um zwei kurze Nachrichten. In diesem Teil werden wir sie verketten und die Nachricht so verändern, dass eine einzige Nachricht mehrere Textsegmente und Zeichen enthalten kann. Wir werden den gesamten Prozess in den folgenden Unterthemen behandeln:

  1. Überblick über die Strategie
  2. Implementation in MQL5
  3. Testen der Integration
  4. Schlussfolgerung

Am Ende werden wir einen Expert Advisor erstellt haben, der Handelsinformationen wie generierte Signale und erteilte Aufträge vom Handelsterminal an den angegebenen Telegram-Chat sendet. Fangen wir an.


Überblick über die Strategie

Wir erzeugen Handelssignale mit gleitenden Durchschnittskreuzen, einem der am häufigsten verwendeten Instrumente der technischen Analyse. Wir werden die unserer Meinung nach einfachste und klarste Methode zur Verwendung von gleitenden Durchschnittsübergängen beschreiben, um potenzielle Kauf- oder Verkaufschancen zu ermitteln. Dies beruht auf dem Signalcharakter des Kreuzens selbst, ohne dass andere Instrumente oder Indikatoren hinzugefügt werden. Der Einfachheit halber betrachten wir nur zwei gleitende Durchschnitte mit unterschiedlichen Zeiträumen: einen kurzfristigen gleitenden Durchschnitt und einen langfristigen gleitenden Durchschnitt.

Wir werden die Funktion von gleitenden Durchschnittskreuzungen untersuchen und wie sie Handelssignale liefern, auf die man reagieren kann. Gleitende Durchschnitte glätten die Preisdaten und erzeugen eine Art fließende Linie, die sich viel besser zur Trenderkennung eignet als das eigentliche Preischart. Der Grund dafür ist, dass ein Durchschnitt im Allgemeinen stromlinienförmiger und leichter zu verfolgen ist als eine gezackte Linie. Wenn Sie zwei gleitende Durchschnitte mit unterschiedlichen Periodenlängen hinzufügen, werden sie sich irgendwann kreuzen, daher der Begriff „Kreuzen“ (crossover). 

Um die Signale des Kreuzens der gleitenden Durchschnitte mit MQL5 in die Praxis umzusetzen, bestimmen wir zunächst die kurz- und langfristigen Periodenlängen des Durchschnitts, die am besten zu unserer Handelsstrategie passen. Zu diesem Zweck werden wir den Standard für die Periodenlängen wie 50 und 200 für langfristige Trends und 10 und 20 für kürzere Trends verwenden. Nach der Berechnung der gleitenden Durchschnitte vergleichen wir die Ereigniswerte des Kreuzens bei jedem neuen Tick oder Balken und wandeln diese erkannten Kreuzungs-Signale in die binären Ereignisse „Kauf“ oder „Verkauf“ um, auf die unser Expert Advisor reagieren kann. Um zu verstehen, was wir meinen, sollten wir die beiden Fälle visualisieren.

Kreuzung nach oben:

KREUZUNG NACH OBEN

Kreuzung nach unten:

KREUZUNG NACH UNTEN

Diese generierten Signale werden mit unserem derzeitigen MQL5-Telegramm-Nachrichtenrahmen kombiniert. Zu diesem Zweck wird der Code aus Teil 1 so angepasst, dass er auch die Signalerkennung und -formatierung umfasst. Wenn eine Kreuzung erkannt wird, wird eine Nachricht mit dem Namen des Vermögenswertes, der Kreuzungs-Richtung (Kauf/Verkauf) und der Signalzeit erstellt. Durch die rechtzeitige Übermittlung dieser Nachricht an einen bestimmten Telegram-Chat wird sichergestellt, dass unsere Handelsgruppe über potenzielle Handelsmöglichkeiten auf dem Laufenden gehalten wird. Abgesehen von allem anderen bedeutet die Gewissheit, eine Nachricht unmittelbar nach der Kreuzung zu erhalten, dass wir die Chance haben, einen Handel auf der Grundlage des betreffenden Signals einzuleiten oder sogar eine Marktposition zu eröffnen und die Positionsdetails zu übermitteln.


Implementation in MQL5

Zunächst werden wir sicherstellen, dass wir unsere Nachricht segmentieren und als Ganzes versenden können. Wenn wir im ersten Teil eine komplexe Nachricht senden, die Sonderzeichen wie Zeilenumbrüche enthält, erhalten wir einen Fehler, und wir können sie nur als einzelne Nachricht ohne Struktur senden. Zum Beispiel hatten wir diesen Code-Abschnitt, das das Initialisierungsereignis, den Kontostand und die verfügbare freie Marge abruft:

   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀"
                +"📊 Account Status 📊; Equity: $"
                +DoubleToString(accountEquity,2)
                +"; Free Margin: $"
                +DoubleToString(accountFreeMargin,2);

Wenn wir das als Ganzes senden, bekommen wir das hier:

LANGE NACHRICHT

Wir sehen, dass wir die Nachricht zwar senden können, ihre Struktur aber nicht ansprechend ist. Der Initialisierungssatz sollte in der ersten Zeile stehen, dann der Kontostatus in der zweiten Zeile, das Eigenkapital in der nächsten Zeile und die Informationen über die freie Marge in der letzten Zeile. Um dies zu erreichen, muss ein neues Zeilenvorschubzeichen „\n“ wie folgt berücksichtigt werden.

   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀"
                +"\n📊 Account Status 📊"
                +"\nEquity: $"
                +DoubleToString(accountEquity,2)
                +"\nFree Margin: $"
                +DoubleToString(accountFreeMargin,2);

Wenn wir das Programm jedoch ausführen, erhalten wir eine Fehlermeldung im Journal, wie gezeigt, und die Nachricht wird nicht an den Telegram-Chat gesendet:

FEHLER BEIM NEUEN ZEILENVORSCHUB

Um sicherzustellen, dass die Nachricht erfolgreich gesendet wird, müssen wir sie verschlüsseln. Unsere Integration erfordert die Kodierung unserer Nachrichten, um Sonderzeichen korrekt zu verarbeiten. Wenn unsere Nachricht z. B. ein Leerzeichen enthält oder sich wie ein Symbol verhält („&“, „?“ usw.), könnten diese von der Telegram Application Programming Interface (API) falsch gelesen werden, weil wir bei der Integration nicht vorsichtig genug waren. Wir nehmen das ernst, das ist kein Scherz. Wir haben schon andere Verwendungen der Zeichenkodierung gesehen, zum Beispiel beim Öffnen bestimmter Dokumente auf unseren Computern, wie hier gezeigt.

DOKUMENTENKODIERUNG

Die Kodierung ist der Schlüssel, um die Probleme zu vermeiden, auf die wir bisher gestoßen sind, nämlich dass die API nicht versteht, was wir ihr senden wollen, damit sie das tun kann, was wir von ihr erwarten.

Beispielsweise könnte eine an die API gesendete Nachricht, die ein Sonderzeichen enthält, die Struktur des Uniform Resource Locator (URL) - die Art und Weise, wie die URL von Computern „gesehen“ wird - beeinträchtigen und zu Fehlern bei der Interpretation führen. Die API könnte das Sonderzeichen als Anweisung oder einen anderen Teil des Codes interpretieren und nicht als Teil der eigentlichen Nachricht. Diese Kommunikationsstörung kann an beiden Enden auftreten: Beim Senden der Nachricht aus dem Programm oder beim Empfangen der Nachricht am anderen Ende des Programms erfüllt die Verschlüsselung nicht ihre Hauptfunktion, den ungesehenen Teil der Nachricht sicher „sichtbar“ zu machen. Außerdem bedeutet die Verwendung des Kodierungsschemas, dass wir eine Nachricht in einem Format haben, das mit der empfangenden Seite kompatibel ist - in diesem Fall die Telegram-API. Schließlich sind an dieser Geschichte mehrere verschiedene Systeme beteiligt, und jedes von ihnen hat spezifische Anforderungen an die Art und Weise, wie die Daten an sie weitergegeben werden sollen. Deshalb werden wir als Erstes eine Funktion erstellen, die unsere Nachrichten verschlüsselt.

// FUNCTION TO ENCODE A STRING FOR USE IN URL
string UrlEncode(const string text) {
    string encodedText = ""; // Initialize the encoded text as an empty string
    int textLength = StringLen(text); // Get the length of the input text

   ...

}

Hier beginnen wir mit der Erstellung einer Funktion vom Datentyp string namens „UrlEncode“, die einen einzigen Parameter oder ein Argument, text, vom Typ string annimmt und den angegebenen Text in ein URL-kodiertes Format umwandeln soll. Dann initialisieren wir eine leere Zeichenkette, „encodedText“, die verwendet wird, um das URL-kodierte Ergebnis zu erstellen, während wir den Eingabetext verarbeiten. Als Nächstes bestimmen wir die Länge der Eingabezeichenkette mit der Funktion StringLen und speichern diese Länge in der Integer-Variablen „textLength“. Dieser Schritt ist von entscheidender Bedeutung, da er uns Aufschluss darüber gibt, wie viele Zeichen wir verarbeiten müssen. Durch die Speicherung der Länge können wir jedes Zeichen der Zeichenkette in einer Schleife effizient durchlaufen und sicherstellen, dass alle Zeichen gemäß den URL-Kodierungsregeln korrekt kodiert sind. Für den Iterationsprozess müssen wir eine Schleife verwenden.

    // Loop through each character in the input string
    for (int i = 0; i < textLength; i++) {
        ushort character = StringGetCharacter(text, i); // Get the character at the current position
   
        ...

    }

Hier wird eine for-Schleife gestartet, die alle Zeichen der Eingabemeldung oder des Textes durchläuft, beginnend mit dem ersten Zeichen bei Index 0. Wir erhalten den Wert des ausgewählten Symbols mit der Funktion StringGetCharacter, die normalerweise den Wert eines Symbols an der angegebenen Position in einer Zeichenkette zurückgibt. Die Position wird durch den Index „i“ definiert. Wir speichern das Zeichen in einer ushort-Variablen namens „character“.

        // Check if the character is alphanumeric or one of the unreserved characters
        if ((character >= 48 && character <= 57) ||  // Check if character is a digit (0-9)
            (character >= 65 && character <= 90) ||  // Check if character is an uppercase letter (A-Z)
            (character >= 97 && character <= 122) || // Check if character is a lowercase letter (a-z)
            character == '!' || character == '\'' || character == '(' ||
            character == ')' || character == '*' || character == '-' ||
            character == '.' || character == '_' || character == '~') {

            // Append the character to the encoded string without encoding
            encodedText += ShortToString(character);
        }

Hier wird geprüft, ob ein bestimmtes Zeichen entweder alphanumerisch oder eines der in URLs häufig verwendeten nicht reservierten Zeichen ist. Ziel ist es, festzustellen, ob das Zeichen kodiert werden muss oder direkt an die kodierte Zeichenfolge angehängt werden kann. Zunächst wird geprüft, ob es sich bei dem Zeichen um eine Ziffer handelt, indem überprüft wird, ob der ASCII-Wert zwischen 48 und 57 liegt. Als Nächstes prüfen wir, ob das Zeichen ein Großbuchstabe ist, indem wir sehen, ob sein ASCII-Wert zwischen 65 und 90 liegt. In ähnlicher Weise prüfen wir, ob das Zeichen ein Kleinbuchstabe ist, indem wir feststellen, ob sein ASCII-Wert zwischen 97 und 122 liegt. Diese Werte können in der „ASCII-Tabelle“ bestätigt werden.

Ziffernzeichen - 48 bis 57:

ZAHLEN

Großbuchstaben - 65 bis 90:

GROSSBUCHSTABEN

Kleinbuchstaben - 97 bis 122:

KLEINBUCHSTABEN

Zusätzlich zu diesen alphanumerischen Zeichen prüfen wir auch auf bestimmte nicht reservierte Zeichen, die in URLs verwendet werden. Dazu gehören ‚!‘, ‚‘‘, ‚(‘, ‚)‘, ‚*‘, ‚-‘, ‚.‘, ‚_‘, und ‚~‘. Wenn das Zeichen einem dieser Kriterien entspricht, bedeutet dies, dass es sich entweder um ein alphanumerisches Zeichen oder eines der nicht reservierten Zeichen handelt.

Wenn das Zeichen eine dieser Bedingungen erfüllt, wird es an die Zeichenkette „encodedText“ angehängt, ohne es zu kodieren. Dazu wird das Zeichen mit der Funktion ShortToString in seine Zeichenkettendarstellung umgewandelt, wodurch sichergestellt wird, dass das Zeichen der kodierten Zeichenkette in seiner ursprünglichen Form hinzugefügt wird. Wenn keine dieser Bedingungen erfüllt ist, wird nach Leerzeichen gesucht.

        // Check if the character is a space
        else if (character == ' ') {
            // Encode space as '+'
            encodedText += ShortToString('+');
        }

Hier verwenden wir eine else if-Anweisung, um zu prüfen, ob das Zeichen ein Leerzeichen ist, indem wir es mit dem Leerzeichen vergleichen. Wenn das Zeichen tatsächlich ein Leerzeichen ist, müssen wir es so kodieren, dass es für URLs geeignet ist. Anstatt die typische Prozentkodierung für Leerzeichen (%20) zu verwenden, wie wir es bei Computerdokumenten gesehen haben, haben wir uns dafür entschieden, Leerzeichen als das Pluszeichen „+“ zu kodieren, was eine weitere gängige Methode zur Darstellung von Leerzeichen in URLs ist, insbesondere in der Abfragekomponente. Daher wandeln wir das Pluszeichen „+“ mit der Funktion ShortToString in seine String-Darstellung um und fügen es dann an den String „encodedText“ an.

Wenn wir bis zu diesem Punkt unverschlüsselte Zeichen haben, bedeutet das, dass wir ein Problem haben, weil es sich um komplexe Zeichen wie Emojis handelt. Daher müssen wir alle Zeichen, die nicht alphanumerisch, nicht reserviert oder Leerzeichen sind, mit dem Unicode Transformation Format-8 (UTF-8) kodieren, um sicherzustellen, dass jedes Zeichen, das nicht in die zuvor geprüften Kategorien fällt, sicher für die Aufnahme in eine URL kodiert wird.

        // For all other characters, encode them using UTF-8
        else {
            uchar utf8Bytes[]; // Array to hold the UTF-8 bytes
            int utf8Length = ShortToUtf8(character, utf8Bytes); // Convert the character to UTF-8
            for (int j = 0; j < utf8Length; j++) {
                // Convert each byte to its hexadecimal representation prefixed with '%'
                encodedText += StringFormat("%%%02X", utf8Bytes[j]);
            }
        }

Zunächst deklarieren wir ein Array „utf8Bytes“, das die Unicode Transformation Format-8 (UTF-8) Byte-Darstellung des Zeichens enthält. Anschließend rufen wir die Funktion „ShortToUtf8“ auf und übergeben das „Zeichen“ und das Array „utf8Bytes“ als Argumente. Wir werden die Funktion in Kürze erklären, aber jetzt müssen Sie nur wissen, dass die Funktion das Zeichen in seine UTF-8-Darstellung umwandelt und die Anzahl der für die Umwandlung verwendeten Bytes zurückgibt, wobei diese Bytes im Array „utf8Bytes“ gespeichert werden.

Als Nächstes verwenden wir eine „for-Schleife“, um jedes Byte im Array „utf8Bytes“ zu durchlaufen. Wir konvertieren jedes Byte in seine hexadezimale Darstellung mit dem vorangestellten „%“-Zeichen, das die Standardmethode für die prozentuale Kodierung von Zeichen in URLs ist. Wir verwenden die Funktion „StringFormat“, um jedes Byte als zweistellige Hexadezimalzahl mit einem „%“-Präfix zu formatieren. Schließlich wird diese kodierte Darstellung an die Zeichenkette „encodedText“ angehängt. Am Ende geben wir nur die Ergebnisse zurück.

    return encodedText; // Return the URL-encoded string

Der Codeausschnitt der vollständigen Funktion lautet wie folgt:

// FUNCTION TO ENCODE A STRING FOR USE IN URL
string UrlEncode(const string text) {
    string encodedText = ""; // Initialize the encoded text as an empty string
    int textLength = StringLen(text); // Get the length of the input text

    // Loop through each character in the input string
    for (int i = 0; i < textLength; i++) {
        ushort character = StringGetCharacter(text, i); // Get the character at the current position

        // Check if the character is alphanumeric or one of the unreserved characters
        if ((character >= 48 && character <= 57) ||  // Check if character is a digit (0-9)
            (character >= 65 && character <= 90) ||  // Check if character is an uppercase letter (A-Z)
            (character >= 97 && character <= 122) || // Check if character is a lowercase letter (a-z)
            character == '!' || character == '\'' || character == '(' ||
            character == ')' || character == '*' || character == '-' ||
            character == '.' || character == '_' || character == '~') {

            // Append the character to the encoded string without encoding
            encodedText += ShortToString(character);
        }
        // Check if the character is a space
        else if (character == ' ') {
            // Encode space as '+'
            encodedText += ShortToString('+');
        }
        // For all other characters, encode them using UTF-8
        else {
            uchar utf8Bytes[]; // Array to hold the UTF-8 bytes
            int utf8Length = ShortToUtf8(character, utf8Bytes); // Convert the character to UTF-8
            for (int j = 0; j < utf8Length; j++) {
                // Convert each byte to its hexadecimal representation prefixed with '%'
                encodedText += StringFormat("%%%02X", utf8Bytes[j]);
            }
        }
    }
    return encodedText; // Return the URL-encoded string
}

Schauen wir uns nun die Funktion an, die für die Umwandlung von Zeichen in ihre UTF-8-Darstellung zuständig ist.

//+-----------------------------------------------------------------------+
//| Function to convert a ushort character to its UTF-8 representation    |
//+-----------------------------------------------------------------------+
int ShortToUtf8(const ushort character, uchar &utf8Output[]) {

   ...

}

Die Funktion ist vom Datentyp integer und benötigt zwei Eingabeparameter, den Zeichenwert und das Ausgabefeld. 

Zunächst werden Einzelbyte-Zeichen umgewandelt.

    // Handle single byte characters (0x00 to 0x7F)
    if (character < 0x80) {
        ArrayResize(utf8Output, 1); // Resize the array to hold one byte
        utf8Output[0] = (uchar)character; // Store the character in the array
        return 1; // Return the length of the UTF-8 representation
    }

Die Umwandlung von Einzelbyte-Zeichen, die Werte im Bereich von 0x00 bis 0x7F haben, ist einfach, da sie in UTF-8 direkt in einem einzigen Byte dargestellt werden. Zuerst wird geprüft, ob das Zeichen kleiner als 0x80 ist. Ist dies der Fall, wird das Array „utf8Output“ mit der Funktion ArrayResize auf ein Byte verkleinert. Dies ermöglicht es uns, die richtige Größe für die UTF-8-Ausgabe zu erhalten. Wir fügen das Zeichen dann in das erste Element des Arrays ein, indem wir das Zeichen in ein uchar umwandeln, eine Übung, die man Typecasting nennt. Dies wäre dasselbe wie das Kopieren des Zeichenwerts in das Array. Wir geben 1 zurück, was bedeutet, dass die UTF-8-Darstellung eine Länge von einem Byte hat. Dieses Verfahren wandelt jedes Ein-Byte-Zeichen effizient in seine UTF-8-Form um, unabhängig vom Betriebssystem.

Sie würden sich wie folgt darstellen.

0x00, UTF-8:

0x00 UTF-8

0x7F, UTF-8:

0x7F UTF-8

Sie können sehen, dass die Dezimaldarstellung der Zahlen von 0 bis 127 reicht. Sie können wieder feststellen, dass diese Zeichen mit den ursprünglichen Unicode-Zeichen identisch sind. Wahrscheinlich fragen Sie sich, was das alles soll. Lassen Sie uns innehalten und einen genaueren Blick darauf werfen. In hexadezimaler Schreibweise stehen 0x80 und 0x7F für bestimmte Werte, die zum besseren Verständnis in dezimale Werte umgewandelt werden können. Die hexadezimale Zahl 0x80 entspricht 128 in Dezimalzahlen. Das liegt daran, dass Hexadezimal ein Zahlensystem zur Basis 16 ist, bei dem jede Ziffer eine Potenz von 16 darstellt. In 0x80 steht die „8“ für 8 mal 16^1 (was 128 ergibt) und die „0“ für 0 mal 16^0 (was 0 ergibt), was insgesamt 128 ergibt.

Andererseits entspricht 0x7F der Zahl 127 in Dezimalzahlen. In hexadezimaler Darstellung bedeutet „7F“ 7 mal 16^1 plus 15 mal 16^0. Das ergibt 7 mal 16 (also 112) plus F (also 15), also insgesamt 127. Siehe die nachstehende Darstellung von A-F. Das dezimale unter dem hexadezimalen F ist gleich 15.

HEX, A-F

0x80 ist also 128 dezimal, und 0x7F ist 127 dezimal. Das bedeutet, dass 0x80 nur ein Zeichen mehr ist als 0x7F und damit die Grenze, an der die Ein-Byte-Darstellung in der UTF-8-Kodierung in eine Mehr-Byte-Darstellung übergeht.

Wir wollten nur sicherstellen, dass diese Erklärungen detailliert sind und dass Sie sich nicht über die fortlaufenden Formate wundern und wie alles Sinn macht. Jetzt wissen Sie es. Kommen wir nun zu den 2-Byte-Zeichen.

    // Handle two-byte characters (0x80 to 0x7FF)
    if (character < 0x800) {
        ArrayResize(utf8Output, 2); // Resize the array to hold two bytes
        utf8Output[0] = (uchar)((character >> 6) | 0xC0); // Store the first byte
        utf8Output[1] = (uchar)((character & 0x3F) | 0x80); // Store the second byte
        return 2; // Return the length of the UTF-8 representation
    }


Hier kümmern wir uns um die Konvertierung von Zeichen, die zwei Bytes in ihrer UTF-8-Darstellung benötigen, d. h. Zeichen, deren Werte zwischen 0x80 und 0x7FF liegen. Dazu testen wir zunächst, ob das betreffende Zeichen kleiner als 0x800 (2048 in Dezimalzahlen) ist, was garantiert, dass es tatsächlich in diesem Bereich liegt. Wenn diese Bedingung erfüllt ist, wird die Größe des Arrays „utf8Output“ so angepasst, dass es zwei Bytes enthält (da zwei Bytes für die Darstellung des Zeichens in UTF-8 erforderlich sind). Anschließend wird die tatsächliche UTF-8-Darstellung berechnet.

Das erste Byte erhält man, indem man das Zeichen nimmt, es um 6 Bits nach rechts verschiebt und dann mit 0xC0 mit der logischen ODER-Operation kombiniert. Diese Berechnung setzt die höchstwertigen Bits des ersten Bytes auf das UTF-8-Präfix für ein Zwei-Byte-Zeichen. Das zweite Byte wird berechnet, indem das Zeichen mit 0x3F maskiert wird, um die unteren 6 Bits zu erhalten, und diese dann mit 0x80 kombiniert werden. Dieser Vorgang stellt sicher, dass das zweite Byte das richtige UTF-8-Präfix hat.

Am Ende legen wir diese beiden Bytes in das Array „utf8Output“ und melden 2 an den Aufrufer zurück, was bedeutet, dass das Zeichen zwei Bytes in seiner UTF-8-Darstellung benötigt. Dies ist die notwendige und korrekte Kodierung für ein Zeichen, das die doppelte Anzahl von Bits im Vergleich zu einem Ein-Byte-Zeichen verwendet. Dann haben wir die 3-Byte-Zeichen.

    // Handle three-byte characters (0x800 to 0xFFFF)
    if (character < 0xFFFF) {

        ...

    }

Sie wissen jetzt, was das bedeutet. Hier wird die hexadezimale Zahl „0xFFFF“ in 65.535 dezimal umgerechnet. Wir wissen, dass jede hexadezimale Ziffer eine Potenz von 16 darstellt. Bei „0xFFFF“ ist jede Ziffer ein „F“, was dezimal 15 ist - das hatten wir schon gesehen. Um den Dezimalwert zu berechnen, wird der Beitrag jeder Ziffer anhand ihrer Position bewertet. Wir beginnen mit dem höchsten Stellenwert, nämlich (15 * 16^3), was uns (15 * 4096 = 61.440) ergibt. Als Nächstes berechnen wir (15 * 16^2), was (15 * 256 = 3.840) entspricht. Dann ergibt sich (15 * 16^1) in (15 * 16 = 240). Schließlich ist (15 * 16^0) gleich (15 * 1 = 15). Addiert man diese Ergebnisse, erhält man 61.440 + 3.840 + 240 + 15, also insgesamt 65.535. Somit ist „0xFFFF“ 65.535 in Dezimalzahlen. Vor diesem Hintergrund könnte es drei Instanzen der 3-Byte-Zeichen geben. Werfen wir einen Blick auf den ersten Fall.

        if (character >= 0xD800 && character <= 0xDFFF) { // Ill-formed characters
            ArrayResize(utf8Output, 1); // Resize the array to hold one byte
            utf8Output[0] = ' '; // Replace with a space character
            return 1; // Return the length of the UTF-8 representation
        }

Hier behandeln wir Zeichen, die in den Unicode-Bereich 0xD800 bis 0xDFFF fallen, die als Surrogat-Hälften bekannt sind und nicht als eigenständige Zeichen gültig sind. Zunächst wird geprüft, ob das Zeichen innerhalb dieses Bereichs liegt. Wenn wir auf ein solches schlecht geformtes Zeichen stoßen, ändern wir zunächst die Größe des Arrays „utf8Output“, um nur ein Byte zu speichern.

Als Nächstes ersetzen wir das ungültige Zeichen durch ein Leerzeichen, indem wir das erste Element des Arrays „utf8Output“ auf ein Leerzeichen setzen. Diese Option ist ein Platzhalter, um ungültige Eingaben korrekt zu behandeln. Schließlich wird eine 1 zurückgegeben, die anzeigt, dass die UTF-8-Darstellung dieses schlecht geformten Zeichens ein Byte lang ist. Als Nächstes wird nach Emoji-Zeichen gesucht. Das bedeutet, dass wir mit Zeichen arbeiten, die im Unicode-Spektrum von 0xE000 bis 0xF8FF liegen. Zu diesen Zeichen gehören Emojis und andere erweiterte Symbole.

        else if (character >= 0xE000 && character <= 0xF8FF) { // Emoji characters
            int extendedCharacter = 0x10000 | character; // Extend the character to four bytes
            ArrayResize(utf8Output, 4); // Resize the array to hold four bytes
            utf8Output[0] = (uchar)(0xF0 | (extendedCharacter >> 18)); // Store the first byte
            utf8Output[1] = (uchar)(0x80 | ((extendedCharacter >> 12) & 0x3F)); // Store the second byte
            utf8Output[2] = (uchar)(0x80 | ((extendedCharacter >> 6) & 0x3F)); // Store the third byte
            utf8Output[3] = (uchar)(0x80 | (extendedCharacter & 0x3F)); // Store the fourth byte
            return 4; // Return the length of the UTF-8 representation
        }

Zunächst wird festgestellt, ob das Zeichen in diesen Emoji-Bereich fällt. Da Zeichen, die in diesem Bereich liegen, eine Vier-Byte-Darstellung in UTF-8 erfordern, erweitern wir den Zeichenwert zunächst durch ein bitweises ODER mit 0x10000. Dieser Schritt ermöglicht es uns, Zeichen aus den zusätzlichen Ebenen korrekt zu verarbeiten.

Anschließend wird die Größe des Arrays „utf8Output“ auf vier Bytes geändert. Dies garantiert, dass genügend Platz vorhanden ist, um die gesamte UTF-8-Kodierung im Array zu speichern. Die Berechnung der UTF-8-Darstellung basiert also auf der Ableitung und Kombination der vier Teile (der vier Bytes). Für das erste Byte nehmen wir das „extendedCharacter“ und verschieben es um 18 Bit nach rechts. Dann kombinieren wir diesen Wert logisch (mit der bitweisen ODER-Operation oder |) mit 0xF0, um die entsprechenden „hohen“ Bits für das erste Byte zu erhalten. Für das zweite Byte verschieben wir das „extendedCharacter“ um 12 Bit nach rechts und verwenden eine ähnliche Technik, um den nächsten Teil zu erhalten.

In ähnlicher Weise wird das dritte Byte durch Rechtsverschiebung des erweiterten Zeichens um 6 Bits und Maskierung der nächsten 6 Bits berechnet. Wir kombinieren dies mit 0x80, um den ersten Teil des dritten Bytes zu erhalten. Um den zweiten Teil zu erhalten, maskieren wir das erweiterte Zeichen mit 0x3F (was uns die letzten 6 Bits des erweiterten Zeichens liefert) und kombinieren dies mit 0x80. Nachdem wir diese beiden Bytes berechnet und im Array „utf8Output“ gespeichert haben, geben wir 4 zurück, was bedeutet, dass das Zeichen 4 Bytes in UTF-8 benötigt. Wir könnten zum Beispiel ein Emoji-Zeichen 1F4B0 haben. Das ist das Emoji des Geldbeutels.

GELD EMOJIS

Um die dezimale Darstellung zu berechnen, wandeln wir zunächst die hexadezimalen Ziffern in dezimale Werte um. Die Ziffer 1 an der 16^4-Stelle ergibt 1×65.536=65.536. Die Ziffer F, die im Dezimalsystem 15 ist, trägt an der 16^3-Stelle 15×4.096=61.440 bei. Die Ziffer 4 an der 16^2-Stelle ergibt 4×256=1.024. Die Ziffer B, die im Dezimalsystem 11 ist, trägt an der 16^1-Stelle 11×16=176 bei. Die Ziffer 0 an der 16^0-Stelle schließlich trägt 0×1=0 bei.

Addiert man diese Beiträge, erhält man 65.536+61.440+1.024+176+0=128.176. Daher wird 0x1F4B0 zu 128.176 in Dezimalzahlen umgerechnet. Sie können dies auf dem beigefügten Bild sehen.

Schließlich befassen wir uns mit Zeichen, die außerhalb der zuvor behandelten spezifischen Bereiche liegen und eine Drei-Byte-UTF-8-Darstellung benötigen.

        else {
            ArrayResize(utf8Output, 3); // Resize the array to hold three bytes
            utf8Output[0] = (uchar)((character >> 12) | 0xE0); // Store the first byte
            utf8Output[1] = (uchar)(((character >> 6) & 0x3F) | 0x80); // Store the second byte
            utf8Output[2] = (uchar)((character & 0x3F) | 0x80); // Store the third byte
            return 3; // Return the length of the UTF-8 representation
        }

Wir beginnen damit, die Größe des Arrays „utf8Output“ zu ändern, damit es die erforderlichen drei Bytes enthalten kann. Jedes Byte hat eine Größe von 8. Um drei Bytes zu speichern, benötigen wir also Platz für 24 Bits. Wir berechnen dann byteweise jedes der drei Bytes der UTF-8-Kodierung. Das erste Byte wird aus dem oberen Teil des Zeichens ermittelt. Um das zweite Byte zu berechnen, verschieben wir das Zeichen um 6 Bits nach rechts, maskieren den resultierenden Wert, um die nächsten 6 Bits zu erhalten, und kombinieren diesen mit 0x80, um die Fortsetzungsbits zu setzen. Die Gewinnung des dritten Bytes ist vom Konzept her dasselbe, nur dass wir keine Verschiebung vornehmen. Stattdessen maskieren wir die letzten 6 Bits und kombinieren sie mit 0x80. Nach der Ermittlung der drei Bytes - die im Array „utf8Output“ gespeichert sind - geben wir 3 zurück, was bedeutet, dass die Darstellung drei Bytes umfasst.

Schließlich müssen wir Fälle behandeln, in denen das Zeichen ungültig ist oder nicht richtig kodiert werden kann, indem wir es durch das Unicode-Ersatzzeichen U+FFFD ersetzen.

    // Handle invalid characters by replacing with the Unicode replacement character (U+FFFD)
    ArrayResize(utf8Output, 3); // Resize the array to hold three bytes
    utf8Output[0] = 0xEF; // Store the first byte
    utf8Output[1] = 0xBF; // Store the second byte
    utf8Output[2] = 0xBD; // Store the third byte
    return 3; // Return the length of the UTF-8 representation

Wir beginnen damit, dass wir die Größe des Arrays „utf8Output“ auf drei Bytes ändern, was garantiert, dass wir genügend Platz für das zu ersetzende Zeichen haben. Als Nächstes setzen wir die Bytes des Arrays „utf8Output“ auf die UTF-8-Darstellung von U+FFFD. Dieses Zeichen erscheint in UTF-8 als die Bytefolge 0xEF, 0xBF und 0xBD, die direkt dem „utf8Output“ zugewiesen sind, wobei 0xEF das erste Byte, 0xBF das zweite Byte und 0xBD das dritte Byte ist. Schließlich wird 3 zurückgegeben, was bedeutet, dass die UTF-8-Darstellung des Ersatzzeichens drei Bytes belegt. Dies ist die vollständige Funktion, die sicherstellt, dass wir ein Zeichen in die UTF-8-Darstellung konvertieren können. Man könnte auch UFT-16 verwenden, was fortgeschrittener ist, aber da dies die Aufgabe der Website erledigt, wollen wir alles einfach halten. Der vollständige Code für die Funktion lautet also wie folgt:

//+-----------------------------------------------------------------------+
//| Function to convert a ushort character to its UTF-8 representation    |
//+-----------------------------------------------------------------------+
int ShortToUtf8(const ushort character, uchar &utf8Output[]) {
    // Handle single byte characters (0x00 to 0x7F)
    if (character < 0x80) {
        ArrayResize(utf8Output, 1); // Resize the array to hold one byte
        utf8Output[0] = (uchar)character; // Store the character in the array
        return 1; // Return the length of the UTF-8 representation
    }
    // Handle two-byte characters (0x80 to 0x7FF)
    if (character < 0x800) {
        ArrayResize(utf8Output, 2); // Resize the array to hold two bytes
        utf8Output[0] = (uchar)((character >> 6) | 0xC0); // Store the first byte
        utf8Output[1] = (uchar)((character & 0x3F) | 0x80); // Store the second byte
        return 2; // Return the length of the UTF-8 representation
    }
    // Handle three-byte characters (0x800 to 0xFFFF)
    if (character < 0xFFFF) {
        if (character >= 0xD800 && character <= 0xDFFF) { // Ill-formed characters
            ArrayResize(utf8Output, 1); // Resize the array to hold one byte
            utf8Output[0] = ' '; // Replace with a space character
            return 1; // Return the length of the UTF-8 representation
        }
        else if (character >= 0xE000 && character <= 0xF8FF) { // Emoji characters
            int extendedCharacter = 0x10000 | character; // Extend the character to four bytes
            ArrayResize(utf8Output, 4); // Resize the array to hold four bytes
            utf8Output[0] = (uchar)(0xF0 | (extendedCharacter >> 18)); // Store the first byte
            utf8Output[1] = (uchar)(0x80 | ((extendedCharacter >> 12) & 0x3F)); // Store the second byte
            utf8Output[2] = (uchar)(0x80 | ((extendedCharacter >> 6) & 0x3F)); // Store the third byte
            utf8Output[3] = (uchar)(0x80 | (extendedCharacter & 0x3F)); // Store the fourth byte
            return 4; // Return the length of the UTF-8 representation
        }
        else {
            ArrayResize(utf8Output, 3); // Resize the array to hold three bytes
            utf8Output[0] = (uchar)((character >> 12) | 0xE0); // Store the first byte
            utf8Output[1] = (uchar)(((character >> 6) & 0x3F) | 0x80); // Store the second byte
            utf8Output[2] = (uchar)((character & 0x3F) | 0x80); // Store the third byte
            return 3; // Return the length of the UTF-8 representation
        }
    }
    // Handle invalid characters by replacing with the Unicode replacement character (U+FFFD)
    ArrayResize(utf8Output, 3); // Resize the array to hold three bytes
    utf8Output[0] = 0xEF; // Store the first byte
    utf8Output[1] = 0xBF; // Store the second byte
    utf8Output[2] = 0xBD; // Store the third byte
    return 3; // Return the length of the UTF-8 representation
}

Mit der Kodierungsfunktion können wir nun unsere Nachricht kodieren und erneut senden.

   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string msg = "🚀EA INITIALIZED ON CHART " + _Symbol + " 🚀"
                +"\n📊Account Status 📊"
                +"\nEquity: $"
                +DoubleToString(accountEquity,2)
                +"\nFree Margin: $"
                +DoubleToString(accountFreeMargin,2);
   
   string encloded_msg = UrlEncode(msg);
   msg = encloded_msg;

Hier deklarieren wir einfach eine String-Variable mit dem Namen „encoded_msg“, die unsere URL-kodierte Nachricht speichert, und wir fügen das Ergebnis schließlich an die ursprüngliche Nachricht an, was technisch gesehen ihren Inhalt überschreibt, anstatt einfach eine weitere Variable zu deklarieren. Wenn wir dies ausführen, erhalten wir das folgende Ergebnis:

NACHRICHT OHNE EMOJIS

Wir können sehen, dass dies ein Erfolg war. Wir haben die Botschaft auf strukturierte Weise erhalten. Die ursprünglich in der Nachricht enthaltenen Emoji-Zeichen werden jedoch verworfen. Das liegt daran, dass wir sie verschlüsselt haben, und jetzt müssen wir ihre jeweiligen Formate eingeben, um sie zurückzubekommen. Wenn Sie sie nicht entfernen müssen, bedeutet das, dass Sie sie hart kodieren und daher den Emoji-Schnipsel in der Funktion einfach ignorieren. Wir wollen sie in ihrem jeweiligen Format haben, damit sie automatisch kodiert werden können.

   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
                +"\n\xF4CA Account Status \xF4CA"
                +"\nEquity: $"
                +DoubleToString(accountEquity,2)
                +"\nFree Margin: $"
                +DoubleToString(accountFreeMargin,2);
   
   string encloded_msg = UrlEncode(msg);
   msg = encloded_msg;

Hier stellen wir das Zeichen im Format „\xF***“ dar. Wenn Sie ein Wort haben, das auf die Darstellung folgt, achten Sie darauf, zur Unterscheidung ein Leerzeichen oder einen Backslash „\“ zu verwenden, also „\xF123 „ oder „\xF123\“. Wenn wir dies ausführen, erhalten wir das folgende Ergebnis:

ENDGÜLTIGE EMOJI-EINBINDUNG

Wie wir sehen, haben wir jetzt das richtige Nachrichtenformat mit allen korrekt kodierten Zeichen. Das ist ein Erfolg! Nun können wir mit der Erzeugung echter Signale fortfahren.

Da die Funktion WebRequest im Strategietester nicht funktioniert und das Warten auf eine Signalerzeugung auf der Grundlage der Kreuzungs-Strategie mit gleitendem Durchschnitt einige Zeit in Anspruch nehmen wird, um auf die Bestätigung zu warten, sollten wir eine andere schnelle Strategie entwickeln, auch wenn wir die Strategie mit gleitendem Durchschnitt später noch verwenden werden, um sie bei der Programminitialisierung zu verwenden. Bei der Initialisierung bewerten wir den vorherigen Balken, und wenn es sich um einen Aufwärtsbalken (bullish) handelt, eröffnen wir einen Kaufauftrag. Andernfalls, wenn es sich um einen Abwärtsbalken (bearish) oder einen Balken ohne Richtung handelt, eröffnen wir einen Verkaufsauftrag. Dies wird im Folgenden veranschaulicht:

AUF- UND ABWÄRTSKERZEN

Der für die Logik verwendete Code-Abschnitt lautet wie folgt:

   double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   
   double Price_Open = iOpen(_Symbol,_Period,1);
   double Price_Close = iClose(_Symbol,_Period,1);
   
   bool isBuySignal = Price_Open < Price_Close;
   bool isSellSignal = Price_Open >= Price_Close;
   

Hier legen wir die Preisnotierungen fest, d. h. den Brief- und den Geldkurs (ask & bid). Dann erhalten wir den Eröffnungskurs für den vorherigen Balken bei Index 1 mithilfe der Funktion iOpen, die drei Argumente oder Parameter benötigt, nämlich das Warensymbol, den Zeitraum und den Index des Balkens, für den der Wert ermittelt werden soll. Um den Schlusskurs zu ermitteln, wird die Funktion iClose verwendet. Dann definieren wir boolesche Variablen „isBuySignal“ und „isSellSignal“, die die Werte des Eröffnungs- und des Schlusskurses vergleichen, und wenn der Eröffnungskurs kleiner als der Schlusskurs oder der Eröffnungskurs größer oder gleich dem Schlusskurs ist, speichern wir die Kauf- bzw. Verkaufssignalflags in den Variablen.

Um die Aufträge zu öffnen, benötigen wir eine Methode.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Im globalen Bereich, vorzugsweise am Anfang des Codes, binden wir die Handelsklasse mit dem Schlüsselwort #include ein. Dadurch erhalten wir Zugriff auf die Klasse CTrade, mit der wir ein Handelsobjekt erstellen werden. Dies ist von entscheidender Bedeutung, da wir sie zur Eröffnung von Geschäften benötigen.

DIE KLASSE CTRADE

Der Präprozessor wird die Zeile #include <Trade/Trade.mqh> durch den Inhalt der Datei Trade.mqh ersetzen. Die spitzen Klammern zeigen an, dass die Datei Trade.mqh aus dem Standardverzeichnis entnommen wird (normalerweise ist es das Terminal-Installationsverzeichnis\MQL5\Include). Das aktuelle Verzeichnis wird bei der Suche nicht berücksichtigt. Die Zeile kann an beliebiger Stelle im Programm platziert werden, aber in der Regel werden alle Einschlüsse am Anfang des Quellcodes platziert, um den Code besser zu strukturieren und die Referenz zu erleichtern. Die Deklaration des Objekts obj_Trade der Klasse CTrade ermöglicht uns dank der MQL5-Entwickler einen einfachen Zugriff auf die in dieser Klasse enthaltenen Methoden.

Mit diesen können wir nun Positionen eröffnen.

   double lotSize = 0, openPrice = 0,stopLoss = 0,takeProfit = 0;
   
   if (isBuySignal == true){
      lotSize = 0.01;
      openPrice = Ask;
      stopLoss = Bid-1000*_Point;
      takeProfit = Bid+1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }
   else if (isSellSignal == true){
      lotSize = 0.01;
      openPrice = Bid;
      stopLoss = Ask+1000*_Point;
      takeProfit = Ask-1000*_Point;
      obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }

Wir definieren double Variablen, um das Handelsvolumen, den Eröffnungskurs der Aufträge, die Stop-Loss- und Take-Profit-Levels zu speichern, und initialisieren sie mit Null. Um die Positionen zu eröffnen, prüfen wir zunächst, ob das „isBuySignal“ eine „true“-Flag enthält, was bedeutet, dass der vorhergehende Balken tatsächlich ein Bulle war, und eröffnen dann die Kaufposition. Die Losgröße wird auf 0,01 initialisiert, der Eröffnungskurs ist der Briefkurs (ask), die Stop-Loss- und Take-Profit-Niveaus werden aus dem Geldkurs (bid) berechnet, und die Ergebnisse werden zur Eröffnung der Kaufposition verwendet. In ähnlicher Weise werden die Werte für die Eröffnung der Verkaufsposition berechnet und in der Funktion verwendet.

Sobald die Positionen eröffnet sind, können wir nun die Informationen über das erzeugte Signal und die eröffnete Position in einer einzigen Nachricht zusammenfassen und an Telegram weiterleiten.

   string position_type = isBuySignal ? "Buy" : "Sell";
   
   ushort MONEYBAG = 0xF4B0;
   string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
   string msg =  "\xF680 OPENED "+position_type+" POSITION."
          +"\n===================="
          +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
          +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
          +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
          +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
          +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
          +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
          +"\n_________________________"
          +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
          +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
          ;
   string encloded_msg = UrlEncode(msg);
   msg = encloded_msg;
   

Hier erstellen wir eine klare und präzise Nachricht, die die Informationen in Bezug auf das Handelssignal enthält. Wir formatieren die Nachricht mit Emojis und anderen relevanten Daten, von denen wir glauben, dass sie die Informationen für die Empfänger leicht verdaulich machen. Wir beginnen mit der Bestimmung, ob es sich um ein Kauf- oder Verkaufssignal handelt, was durch die Verwendung eines ternären Operators erreicht wird. Dann gestalten wir die Nachricht, einschließlich einer Emoji-Darstellung eines Geldstapels, der unserer Meinung nach für ein „Kauf“- oder „Verkauf“-Signal geeignet ist. Wir haben die tatsächlichen Emoji-Darstellungszeichen im „ushort“-Format verwendet und den Zeichencode später mit der Funktion „ShortToString“ in eine String-Variable umgewandelt, um einfach zu zeigen, dass man nicht immer die String-Formate verwenden muss. Sie sehen jedoch, dass der Konvertierungsprozess etwas Zeit und Platz in Anspruch nimmt, aber wenn Sie den jeweiligen Zeichen Namen geben wollen, ist dies die beste Methode.

Anschließend fügen wir die Informationen über die offene Handelsposition in einem String zusammen. Diese in eine Nachricht umgewandelte Zeichenkette enthält die Einzelheiten des Handels - um welche Art von Handel es sich handelt, wie hoch der Eröffnungskurs war, wie die Handelszeit war, wie die aktuelle Zeit ist, wie groß das Los ist, wie hoch der Stop-Loss ist, wie hoch der Take-Profit ist, usw. Wir tun dies auf eine Art und Weise, die die Botschaft visuell ansprechend und leicht zu interpretieren macht.

Nach der Zusammenstellung der Nachricht rufen wir die Funktion „UrlEncode“ auf, um die Nachricht für die sichere Übertragung an die URL zu kodieren. Wir stellen insbesondere sicher, dass alle Sonderzeichen und Emojis korrekt behandelt werden und für das Web geeignet sind. Anschließend speichern wir die verschlüsselte Nachricht in einer Variablen mit dem Namen „encloded_msg“ und überschreiben die verschlüsselte Nachricht mit der ursprünglichen Nachricht oder tauschen sie normalerweise aus. Wenn wir dies ausführen, erhalten wir das folgende Ergebnis:

MELDUNG DES ENDGÜLTIGEN INITIALISIERUNGSSIGNALS

Wie Sie sehen, haben wir die Nachricht erfolgreich verschlüsselt und in der Zielstruktur an Telegram gesendet. Der vollständige Quellcode, der für das Senden dieser Nachricht verantwortlich ist, lautet wie folgt:

//+------------------------------------------------------------------+
//|                                  TELEGRAM_MQL5_SIGNALS_PART2.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

// Define constants for Telegram API URL, bot token, and chat ID
const string TG_API_URL = "https://api.telegram.org";  // Base URL for Telegram API
const string botTkn = "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc";  // Telegram bot token
const string chatID = "-4273023945";  // Chat ID for the Telegram chat

// The following URL can be used to get updates from the bot and retrieve the chat ID
// CHAT ID = https://api.telegram.org/bot{BOT TOKEN}/getUpdates
// https://api.telegram.org/bot7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc/getUpdates


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {

   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request
   //string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol
   ////--- Simple Notification with Emoji:
   //string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀";
   ////--- Buy/Sell Signal with Emoji:
   //string msg = "📈 BUY SIGNAL GENERATED ON " + _Symbol + " 📈";
   //string msg = "📉 SELL SIGNAL GENERATED ON " + _Symbol + " 📉";
   ////--- Account Balance Notification:
   //double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   //string msg = "💰 Account Balance: $" + DoubleToString(accountBalance, 2) + " 💰";
   ////--- Trade Opened Notification:
   //string orderType = "BUY";  // or "SELL"
   //double lotSize = 0.1;  // Example lot size
   //double price = 1.12345;  // Example price
   //string msg = "🔔 " + orderType + " order opened on " + _Symbol + "; Lot size: " + DoubleToString(lotSize, 2) + "; Price: " + DoubleToString(price, 5) + " 🔔";
   ////--- Stop Loss and Take Profit Update:
   //double stopLoss = 1.12000;  // Example stop loss
   //double takeProfit = 1.13000;  // Example take profit
   //string msg = "🔄 Stop Loss and Take Profit Updated on " + _Symbol + "; Stop Loss: " + DoubleToString(stopLoss, 5) + "; Take Profit: " + DoubleToString(takeProfit, 5) + " 🔄";
   ////--- Daily Performance Summary:
   //double profitToday = 150.00;  // Example profit for the day
   //string msg = "📅 Daily Performance Summary 📅; Symbol: " + _Symbol + "; Profit Today: $" + DoubleToString(profitToday, 2);
   ////--- Trade Closed Notification:
   //string orderType = "BUY";  // or "SELL"
   //double profit = 50.00;  // Example profit
   //string msg = "❌ " + orderType + " trade closed on " + _Symbol + "; Profit: $" + DoubleToString(profit, 2) + " ❌";
   
//   ////--- Account Status Update:
//   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
//   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
//   string msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
//                +"\n\xF4CA Account Status \xF4CA"
//                +"\nEquity: $"
//                +DoubleToString(accountEquity,2)
//                +"\nFree Margin: $"
//                +DoubleToString(accountFreeMargin,2);
//   
//   string encloded_msg = UrlEncode(msg);
//   msg = encloded_msg;

   double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   
   double Price_Open = iOpen(_Symbol,_Period,1);
   double Price_Close = iClose(_Symbol,_Period,1);
   
   bool isBuySignal = Price_Open < Price_Close;
   bool isSellSignal = Price_Open >= Price_Close;
   
   double lotSize = 0, openPrice = 0,stopLoss = 0,takeProfit = 0;
   
   if (isBuySignal == true){
      lotSize = 0.01;
      openPrice = Ask;
      stopLoss = Bid-1000*_Point;
      takeProfit = Bid+1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }
   else if (isSellSignal == true){
      lotSize = 0.01;
      openPrice = Bid;
      stopLoss = Ask+1000*_Point;
      takeProfit = Ask-1000*_Point;
      obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }
   
   string position_type = isBuySignal ? "Buy" : "Sell";
   
   ushort MONEYBAG = 0xF4B0;
   string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
   string msg =  "\xF680 OPENED "+position_type+" POSITION."
          +"\n===================="
          +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
          +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
          +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
          +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
          +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
          +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
          +"\n_________________________"
          +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
          +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
          ;
   string encloded_msg = UrlEncode(msg);
   msg = encloded_msg;
   
   // Construct the URL for the Telegram API request to send a message
   // Format: https://api.telegram.org/bot{HTTP_API_TOKEN}/sendmessage?chat_id={CHAT_ID}&text={MESSAGE_TEXT}
   const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
      "&text=" + msg;

   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", 10000, data, res, resHeaders);

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   } else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   } else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
   }

   return(INIT_SUCCEEDED);  // Return initialization success status
}

Nun müssen wir die Handelssignale auf der Grundlage von Überkreuzungen des gleitenden Durchschnitts einbeziehen. Zunächst müssen wir die beiden Handles für den gleitenden Durchschnittsindikator und ihre Datenspeicher-Arrays deklarieren.

int handleFast = INVALID_HANDLE; // -1
int handleSlow = INVALID_HANDLE; // -1

double bufferFast[];
double bufferSlow[];

long magic_no = 1234567890;

Zunächst deklarieren wir ganzzahlige Variablen vom Typ „handleFast“ und „handleSlow“, um die Indikatoren für den schnellen bzw. langsamen Durchschnitt zu speichern. Wir initialisieren die Handles mit „INVALID_HANDLE“, einem Wert von -1, was bedeutet, dass sie derzeit auf keine gültige Indikatorinstanz verweisen. Dann definieren wir zwei Double-Arrays: „bufferFast“ und „bufferSlow“, in denen wir den Wert speichern, den wir von den schnellen bzw. langsamen Indikatoren abrufen. Schließlich deklarieren wir eine „long“-Variable, um die magische Zahl für die offenen Positionen zu speichern. Diese gesamte Logik ist im globalen Bereich angesiedelt.

Mit der Funktion OnInit werden die Indikator-Handles initialisiert und die Speicher-Arrays als Zeitreihen festgelegt. 

   handleFast = iMA(Symbol(),Period(),20,0,MODE_EMA,PRICE_CLOSE);
   if (handleFast == INVALID_HANDLE){
      Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!");
      return (INIT_FAILED);
   }

Hier erstellen wir einen Handle für den schnellen Mittelwerts-Indikator. Dies geschieht mit der Funktion iMA, die mit den Parametern „Symbol“, „Periode“, 20, 0, „MODE_EMA“ und „PRICE_CLOSE“ aufgerufen wird. Der erste Parameter, „Symbol()“, ist eine integrierte Funktion, die den Namen des aktuellen Instruments zurückgibt. Der zweite Parameter, „Period()“, gibt den aktuellen Zeitrahmen zurück. Der nächste Parameter, 20, ist die Anzahl der Perioden für den gleitenden Durchschnitt. Der vierte Parameter, 0, gibt an, dass der gleitende Durchschnitt auf die jüngsten Kursbalken angewendet werden soll. Der fünfte Parameter, „MODE_EMA“, gibt an, dass der Exponential Moving Average (EMA) berechnet werden soll. Der letzte Parameter ist „PRICE_CLOSE“, der anzeigt, dass wir den gleitenden Durchschnitt auf der Grundlage der Schlusskurse berechnen. Diese Funktion gibt ein Handle zurück, das diesen gleitenden Durchschnittsindikator eindeutig identifiziert, und wir weisen es „handleFast“ zu.

Nachdem wir versucht haben, den Indikator zu erstellen, überprüfen wir, ob der Handle gültig ist. Ein Ergebnis von „INVALID_HANDLE“ für „handleFast“ sagt uns, dass wir nicht in der Lage waren, den Handle für den sich schnell bewegenden Durchschnittsindikator zu erstellen. In diesem Fall wird eine Meldung mit dem Schweregrad von ERROR in das Protokoll geschrieben. Die an den Nutzer gerichtete Meldung besagt, dass das Handle nicht erstellt werden konnte: „UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!“ In der Meldung wird deutlich gemacht, dass kein Handle keinen Indikator bedeutet, was bedeutet, dass wir nicht in der Lage waren, das Indikator-Handle zu erstellen. Da es ohne diesen Indikator kein Handelssystem gibt, was das Programm nutzlos macht, ist es sinnlos, es weiter laufen zu lassen. Wir geben „INIT_FAILED“ zurück, ohne fortzufahren, da ein Fehler aufgetreten ist. Dadurch wird die weitere Ausführung des Programms gestoppt und es wird aus dem Chart entfernt.

Die gleiche Logik gilt für den langsamen Indikator.

   handleSlow = iMA(Symbol(),Period(),50,0,MODE_SMA,PRICE_CLOSE);
   if (handleSlow == INVALID_HANDLE){
      Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!");
      return (INIT_FAILED);
   }

Wenn wir diese Indikator-Handles ausdrucken, erhalten wir einen Startwert von 10, und wenn es mehr Indikator-Handles gibt, wird ihr Wert für jeden Handle um 1 erhöht. Drucken wir sie aus und sehen wir, was dabei herauskommt. Wir erreichen dies durch den folgenden Code:

   Print("HANDLE FAST MA = ",handleFast);
   Print("HANDLE SLOW MA = ",handleSlow);

Wir erhalten die folgende Ausgabe:

INDIKATOR HANDLE AUSDRUCKEN

Schließlich legen wir die Datenspeicherfelder als Zeitreihen fest und bestimmen die magische Zahl.

   ArraySetAsSeries(bufferFast,true);
   ArraySetAsSeries(bufferSlow,true);
   obj_Trade.SetExpertMagicNumber(magic_no);

Die Einstellung der Arrays als Zeitreihen wird durch die Verwendung der Funktion ArraySetAsSeries erreicht.

Bei der Funktion OnDeinit geben wir die Indikator-Handles mit Hilfe der Funktion IndicatorRelease aus dem Computerspeicher frei und geben die Speicher-Arrays mit Hilfe der Funktion ArrayFree frei. Dadurch wird der Computer von unnötigen Prozessen befreit und seine Ressourcen werden geschont.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   // Code to execute when the expert is deinitialized
   
   IndicatorRelease(handleFast);
   IndicatorRelease(handleSlow);
   ArrayFree(bufferFast);
   ArrayFree(bufferSlow);
   
}

In der Ereignisbehandlung von OnTick führen wir einen Code aus, der die Indikator-Handles verwendet und die Signalerzeugung überprüft. Dies ist eine Funktion, die bei jedem Tick, d. h. bei jeder Änderung der Kursnotierungen, aufgerufen wird, um die neuesten Kurse zu erhalten.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Code to execute on every tick event

   ...

}

Dies ist die Ereignisbehandlung, über die wir die Indikatorwerte abrufen müssen.

   if (CopyBuffer(handleFast,0,0,3,bufferFast) < 3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      return;
   }

Zunächst versuchen wir, mit Hilfe der Funktion CopyBuffer Daten aus dem Puffer für den schnellen Mittelwerts-Indikator zu erhalten. Wir rufen es mit den Parametern „handleFast“, 0, 0, 3 und „bufferFast“ auf. Der erste Parameter, „handleFast“, ist der Zielindikator, von dem wir die Indikatorwerte erhalten. Der zweite Parameter ist die Puffernummer, von der wir die Werte erhalten, die normalerweise im Datenfenster angezeigt werden, und für den gleitenden Durchschnitt ist sie immer 0. Der dritte Parameter ist die Startposition des Balkenindexes, von der aus wir die Werte erhalten, 0 bedeutet in diesem Fall den aktuellen Balken. Der vierte Parameter ist die Anzahl der abzurufenden Werte, d. h. die Balken. 3 bedeutet in diesem Fall die ersten 3 Balken ab dem aktuellen Takt. Der letzte Parameter ist „bufferFast“, das Ziel-Array, in dem wir unsere 3 abgerufenen Werte speichern.

Nun prüfen wir, ob die Funktion die angeforderten Werte erfolgreich abgerufen hat, d. h. 3. Wenn der zurückgegebene Wert kleiner als 3 ist, bedeutet dies, dass die Funktion die angeforderten Daten nicht abrufen konnte. In einem solchen Fall wird eine Fehlermeldung ausgegeben mit dem Wortlaut „UNABLE TO RETRIEVE THE REQUESTED DATA FOR FUR FURTHER ANALYSIS REVERTING.“ Dadurch wird uns mitgeteilt, dass der Datenabruf fehlgeschlagen ist und wir nicht weiter nach Signalen suchen können, da wir nicht genügend Daten für den Prozess haben. Dann kehren wir zurück, wodurch die weitere Ausführung dieses Teils des Programms gestoppt wird, und warten auf den nächsten Tick.

Das gleiche Verfahren wird angewandt, um die Daten des sich langsam bewegenden Durchschnitts abzurufen.

   if (CopyBuffer(handleSlow,0,0,3,bufferSlow) < 3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      return;
   }

Da die Funktion OnTick bei jedem Tick ausgeführt wird, müssen wir eine Logik entwickeln, die sicherstellt, dass unser Signal-Scan-Code nur einmal pro Balken ausgeführt wird. Hier ist die Logik.

   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;

Zunächst deklarieren wir eine Integer-Variable „currBars“, die die berechnete Anzahl der aktuellen Balken auf dem Chart für das angegebene Handelssymbol und die Periode bzw. den Zeitrahmen speichert, wie Sie vielleicht schon gehört haben. Dies wird durch die Verwendung der Funktion iBars erreicht, die nur zwei Argumente benötigt, nämlich das Symbol und den Zeitrahmen. 

Dann deklarieren wir eine weitere statische Integer-Variable „prevBars“, um die Gesamtzahl der vorherigen Balken im Chart zu speichern, wenn ein neuer Balken erzeugt wird, und initialisieren sie mit dem Wert der aktuellen Balken im Chart für den ersten Durchlauf der Funktion. Wir werden ihn verwenden, um die aktuelle Anzahl der Balken mit der vorherigen Anzahl der Balken zu vergleichen, um den Zeitpunkt einer neuen Balkenerzeugung im Chart zu bestimmen.

Schließlich wird mit einer bedingten Anweisung geprüft, ob die aktuelle Anzahl der Kerzen gleich der vorherigen Anzahl der Kerzen ist. Wenn sie gleich sind, bedeutet dies, dass sich kein neuer Balken gebildet hat, sodass wir die weitere Ausführung beenden und zurückkehren. Andernfalls, wenn die Zählung des aktuellen und des vorherigen Balkens nicht gleich ist, bedeutet dies, dass sich ein neuer Balken gebildet hat. In diesem Fall aktualisieren wir die Variable „previousBars“ mit dem aktuellen Balken, sodass sie beim nächsten Tick gleich der Anzahl der Balken im Chart ist, es sei denn, wir gehen zu einem neuen Balken über.

Dann definieren wir Variablen, in denen wir unsere Daten für die weitere Analyse einfach speichern können (siehe unten).

   double fastMA1 = bufferFast[1];
   double fastMA2 = bufferFast[2];
   
   double slowMA1 = bufferSlow[1];
   double slowMA2 = bufferSlow[2];

Mit diesen Variablen können wir nun auf Überschneidungen prüfen und die erforderlichen Maßnahmen ergreifen.

   if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){
      for (int i = PositionsTotal()-1; i>= 0; i--){
         ulong ticket = PositionGetTicket(i);
         if (ticket > 0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                  PositionGetInteger(POSITION_MAGIC) == magic_no){
                  if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
                     obj_Trade.PositionClose(ticket);
                  }
               }
            }
         }
      }
      double lotSize = 0.01;
      double openPrice = Ask;
      double stopLoss = Bid-1000*_Point;
      double takeProfit = Bid+1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }

Hier suchen wir nach einer bestimmten Kreuzungsbedingung: Wenn der jüngste schnelle Durchschnitt (fastMA1) größer ist als der entsprechende langsame Durchschnitt (slowMA1) und der vorherige schnelle Durchschnitt (fastMA2) kleiner oder gleich dem vorherigen langsamen Durchschnitt (slowMA2) war, dann haben wir es mit einer Aufwärts-Kreuzung zu tun, was ein potenzielles Kaufsignal darstellt. 

Wenn eine Aufwärtskreuzung identifiziert wird, überprüfen wir die aktuellen Positionen auf offene Verkaufspositionen, die einem neuen Kauf im Wege stehen. Falls erforderlich, schließen wir die Verkaufspositionen, bevor wir neue Käufe eröffnen. Wir arbeiten von der letzten Position bis zur letzten Position.

Für jede Handelsposition erhalten wir die Ticketnummer mit der Funktion PositionGetTicket. Wenn die Ticketnummer größer als 0 ist, bedeutet das, dass wir tatsächlich eine gültige Ticketnummer haben, und wir die Position mit der Funktion PositionSelectByTicket auswählen, fahren wir damit fort, zu prüfen, ob die Position gültig ist und ob sie zum aktuellen Symbol und zur magischen Nummer gehört. Wenn die Position eine Verkaufsposition ist, verwenden wir die Funktion „obj_Trade.PositionClose“, um die Position zu schließen. Nachdem wir alle bestehenden Verkaufspositionen geschlossen haben, eröffnen wir eine neue Kaufposition und legen unsere Handelsparameter fest: Losgröße, Eröffnungskurs, Stop-Loss und Take-Profit. Sobald die Position geöffnet wird, informieren wir den Nutzer über den Fall, indem wir ein Protokoll an das Journal senden.

      // BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");

Zum Schluss senden wir eine Nachricht, genau wie im Abschnitt über die Programminitialisierung.

      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Buy Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;

Für das Kreuzungssignal für einen Verkauf bleibt die gleiche Codestruktur bestehen, aber mit umgekehrten Bedingungen.

   else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){
      for (int i = PositionsTotal()-1; i>= 0; i--){
         ulong ticket = PositionGetTicket(i);
         if (ticket > 0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                  PositionGetInteger(POSITION_MAGIC) == magic_no){
                  if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
                     obj_Trade.PositionClose(ticket);
                  }
               }
            }
         }
      }
      double lotSize = 0.01;
      double openPrice = Bid;
      double stopLoss = Ask+1000*_Point;
      double takeProfit = Ask-1000*_Point;
      obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
      
      // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");

Bis zu diesem Punkt ist die Codestruktur fast vollständig. Jetzt müssen wir die Indikatoren automatisch in das Chart einfügen, sobald das Programm zur Visualisierung geladen ist. In der Ereignishandlung der Initialisierung wird also die Logik für das automatische Hinzufügen der Indikatoren wie folgt gestaltet:

   //--- Add indicators to the chart automatically
   ChartIndicatorAdd(0,0,handleFast);
   ChartIndicatorAdd(0,0,handleSlow);

Hier rufen wir einfach die Funktion ChartIndicatorAdd auf, um die Indikatoren zum Chart hinzuzufügen, wobei der erste und zweite Parameter das Chartfenster bzw. das Unterfenster angeben. Der dritte Parameter ist das Handle des Indikators, der hinzugefügt werden soll.

Der vollständige Code der Ereignisbehandlung von OnTick, der für die Erzeugung und Kanalisierung der Signale verantwortlich ist, lautet also wie folgt:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Code to execute on every tick event
   
   if (CopyBuffer(handleFast,0,0,3,bufferFast) < 3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      return;
   }
   if (CopyBuffer(handleSlow,0,0,3,bufferSlow) < 3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      return;
   }
   
   double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;
   
   double fastMA1 = bufferFast[1];
   double fastMA2 = bufferFast[2];
   
   double slowMA1 = bufferSlow[1];
   double slowMA2 = bufferSlow[2];
   
   if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){
      for (int i = PositionsTotal()-1; i>= 0; i--){
         ulong ticket = PositionGetTicket(i);
         if (ticket > 0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                  PositionGetInteger(POSITION_MAGIC) == magic_no){
                  if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
                     obj_Trade.PositionClose(ticket);
                  }
               }
            }
         }
      }
      double lotSize = 0.01;
      double openPrice = Ask;
      double stopLoss = Bid-1000*_Point;
      double takeProfit = Bid+1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
      
      // BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      char data[];  // Array to hold data to be sent in the web request (empty in this case)
      char res[];  // Array to hold the response data from the web request
      string resHeaders;  // String to hold the response headers from the web request
      
      
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Buy Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
   
      const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
         "&text=" + msg;
   
      // Send the web request to the Telegram API
      int send_res = WebRequest("POST", url, "", 10000, data, res, resHeaders);
   
      // Check the response status of the web request
      if (send_res == 200) {
         // If the response status is 200 (OK), print a success message
         Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      } else if (send_res == -1) {
         // If the response status is -1 (error), check the specific error code
         if (GetLastError() == 4014) {
            // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
            Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL");
         }
         // Print a general error message if the request fails
         Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      } else if (send_res != 200) {
         // If the response status is not 200 or -1, print the unexpected response code and error code
         Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      }

      
   }
   else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){
      for (int i = PositionsTotal()-1; i>= 0; i--){
         ulong ticket = PositionGetTicket(i);
         if (ticket > 0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                  PositionGetInteger(POSITION_MAGIC) == magic_no){
                  if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
                     obj_Trade.PositionClose(ticket);
                  }
               }
            }
         }
      }
      double lotSize = 0.01;
      double openPrice = Bid;
      double stopLoss = Ask+1000*_Point;
      double takeProfit = Ask-1000*_Point;
      obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
      
      // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      char data[];  // Array to hold data to be sent in the web request (empty in this case)
      char res[];  // Array to hold the response data from the web request
      string resHeaders;  // String to hold the response headers from the web request
   
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Sell Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
   
      const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
         "&text=" + msg;
   
      // Send the web request to the Telegram API
      int send_res = WebRequest("POST", url, "", 10000, data, res, resHeaders);
   
      // Check the response status of the web request
      if (send_res == 200) {
         // If the response status is 200 (OK), print a success message
         Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      } else if (send_res == -1) {
         // If the response status is -1 (error), check the specific error code
         if (GetLastError() == 4014) {
            // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
            Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL");
         }
         // Print a general error message if the request fails
         Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      } else if (send_res != 200) {
         // If the response status is not 200 or -1, print the unexpected response code and error code
         Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      }
      
   }
   
}
//+------------------------------------------------------------------+

Es ist nun klar, dass wir unser zweites Ziel erreicht haben, nämlich das Senden von Signalen vom Handelsterminal an den Telegram-Chat oder die Telegram-Gruppe. Das ist ein Erfolg und ein Hoch auf uns! Jetzt müssen wir die Integration testen, um sicherzustellen, dass sie ordnungsgemäß funktioniert, und um etwaige Probleme zu erkennen. Dies geschieht im nächsten Abschnitt.


Testen der Integration

Um die Integration zu testen, deaktivieren wir die Testlogik für die Initialisierung, indem wir sie auskommentieren, um zu verhindern, dass viele Signale geöffnet werden, wechseln zu einer niedrigeren Periode, 1 Minute, und ändern die Indikatorperioden auf 5 und 10, um schnellere Signale zu erzeugen. Hier sind die Meilenstein-Ergebnisse, die wir erhalten.

Trading-Terminal Verkaufssignal Bestätigung:

MT5 VERKAUFSSIGNAL

Telegramm Verkaufssignal Bestätigung:

TELEGRAMM VERKAUFSSIGNAL

Handels-Terminal Kaufsignal Bestätigung:

MT5 KAUFSIGNAL

Bestätigung des Telegramm-Kaufsignals:

TELEGRAMM KAUFSIGNAL

Aus den Bildern ist ersichtlich, dass die Integration erfolgreich funktioniert. Es wird ein Signal gescannt, und sobald es bestätigt ist, werden seine Details in einer einzigen Nachricht kodiert und vom Handelsterminal an den Telegramm-Gruppenchat gesendet. Damit haben wir unser Ziel erfolgreich erreicht.


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass dieser Artikel erhebliche Fortschritte bei der Weiterentwicklung unseres integrierten Expert Advisors, MQL5-Telegram, gemacht hat, indem er das Hauptziel erreicht hat, Handelssignale direkt vom Handelsterminal an einen Telegram-Chat zu senden. Wir haben uns jedoch nicht darauf beschränkt, lediglich einen Kommunikationskanal zwischen MQL5 und Telegram einzurichten, wie es in Teil 1 dieser Serie geschehen ist. Stattdessen haben wir uns auf die eigentlichen Handelssignale konzentriert und dabei das beliebte technische Analyseinstrument der gleitenden Durchschnittsübergänge verwendet. Wir haben die Logik dieser Signale und das robuste System, mit dem sie über Telegram versendet werden können, im Detail erklärt. Das Ergebnis ist ein bedeutender Fortschritt im Gesamtaufbau unseres integrierten Expert Advisors.

In diesem Artikel haben wir die technische Funktionsweise der Erzeugung und Versendung dieser Signale genau untersucht. Wir haben uns genau angeschaut, wie man Nachrichten sicher kodiert und sendet, wie man Indikator-Handles verwaltet und wie man Trades auf der Grundlage von erkannten Signalen ausführt. Wir haben den Code entwickelt und ihn in Telegram integriert, sodass wir uns sofort über Handelssignale informieren konnten, wenn wir nicht auf unserer Handelsplattform waren. Die praktischen Beispiele und detaillierten Erläuterungen in diesem Artikel sollten Ihnen eine klare Vorstellung davon vermitteln, wie Sie etwas Ähnliches mit Ihren Handelsstrategien einrichten können.

In Teil 3 unserer Serie werden wir unserer MQL5-Telegram-Integration eine weitere Ebene hinzufügen. Dieses Mal werden wir an einer Lösung arbeiten, um Screenshots von Charts an Telegram zu senden. Die Fähigkeit, den Markt und den Kontext des Handelssignals visuell zu analysieren, wird den Einblick und das Verständnis der Händler verbessern. Textliche Signale in Kombination mit visuellen Daten liefern sogar noch stärkere Signale. Und genau darum geht es uns: nicht nur um das Senden von Signalen, sondern um die Verbesserung des automatisierten Handels und des Situationsbewusstseins über den Telegram-Handelskanal. Bleiben Sie am Ball.


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

MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 31): Auswahl der Verlustfunktion MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 31): Auswahl der Verlustfunktion
Die Verlustfunktion ist die wichtigste Kennzahl für Algorithmen des maschinellen Lernens, die eine Rückmeldung für den Trainingsprozess liefert, indem sie angibt, wie gut ein bestimmter Satz von Parametern im Vergleich zum beabsichtigten Ziel funktioniert. Wir untersuchen die verschiedenen Formate dieser Funktion in einer nutzerdefinierten MQL5-Assistenten-Klasse.
Datenwissenschaft und ML (Teil 29): Wichtige Tipps für die Auswahl der besten Forex-Daten für AI-Trainingszwecke Datenwissenschaft und ML (Teil 29): Wichtige Tipps für die Auswahl der besten Forex-Daten für AI-Trainingszwecke
In diesem Artikel befassen wir uns eingehend mit den entscheidenden Aspekten der Auswahl der relevantesten und hochwertigsten Forex-Daten, um die Leistung von KI-Modellen zu verbessern.
Entwicklung eines Replay Systems (Teil 46): Chart Trade Projekt (V) Entwicklung eines Replay Systems (Teil 46): Chart Trade Projekt (V)
Sind Sie es leid, Zeit mit der Suche nach genau der Datei zu verschwenden, die Ihre Anwendung zum Funktionieren braucht? Wie wäre es, alles in die ausführbare Datei aufzunehmen? Auf diese Weise müssen Sie nicht nach den Dingen suchen. Ich weiß, dass viele Menschen diese Form der Verteilung und Speicherung nutzen, aber es gibt einen viel geeigneteren Weg. Zumindest was die Verteilung von ausführbaren Dateien und deren Speicherung betrifft. Die hier vorgestellte Methode kann sehr nützlich sein, da Sie den MetaTrader 5 selbst als hervorragenden Assistenten verwenden können, ebenso wie MQL5. Außerdem ist es nicht so schwer zu verstehen.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 30): Spotlight auf Batch-Normalisierung beim maschinellen Lernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 30): Spotlight auf Batch-Normalisierung beim maschinellen Lernen
Die Batch-Normalisierung ist die Vorverarbeitung von Daten, bevor sie in einen Algorithmus für maschinelles Lernen, z. B. ein neuronales Netz, eingespeist werden. Dies geschieht immer unter Berücksichtigung der Art der Aktivierung, die der Algorithmus verwenden soll. Wir untersuchen daher die verschiedenen Ansätze, die man mit Hilfe eines von einem Assistenten zusammengestellten Expert Advisors verfolgen kann, um die Vorteile dieses Ansatzes zu nutzen.