Die Entwicklung von grafischen Oberflächen für Expert Advisors und Indikatoren auf Basis von .Net Framework und C#

22 April 2019, 08:27
Vasiliy Sokolov
3
479

Einführung

Seit Oktober 2018 unterstützt MQL5 die native Integration mit Net Framework-Bibliotheken. Native Unterstützung bedeutet, dass Typen, Methoden und Klassen, die in der Bibliothek .Net platziert sind, nun direkt aus einem MQL5-Programm heraus zugänglich sind, ohne vorherige Deklaration der aufrufenden Funktionen und ihrer Parameter sowie ohne die komplexe Typisierung der beiden Sprachen. Dies kann in der Tat als endgültiger Durchbruch gewertet werden, da die riesige .Net Framework Codebasis und die Leistungsfähigkeit der C#-Sprache nun für alle MQL5-Anwender sofort verfügbar sind.

Die Net Framework-Funktionen sind nicht durch die Bibliothek selbst eingeschränkt. Die integrierte Entwicklungsumgebung freemium Visual Studio vereinfacht den Entwicklungsprozess erheblich. So können Sie beispielsweise eine vollwertige Windows-Anwendung im Drag-and-Drop-Modus mit all seinen Elementen entwickeln, die sich wie in jeder anderen grafischen Windows-Anwendung verhalten. Das ist es, was MQL fehlt.

Im Laufe der Sprachexistenz wurden mehrere Bibliotheken erstellt, die die grafische Anwendungsentwicklung innerhalb einer MQL-Anwendung erheblich erleichtern. Unabhängig davon, wie gut diese Bibliotheken auch sein mögen, bestehen sie immer noch aus einer Reihe von Codezeilen, die sowohl Verständnis als auch die Fähigkeit erfordert, diese in den Code von Nutzer-EAs und -Indikatoren zu integrieren. Mit anderen Worten, Nicht-Programmierer können sie kaum nutzen. Die Lücke zwischen der Einfachheit der Formularerstellung in Visual Studio und der Komplexität der Konfiguration von Grafikbibliotheken in MQL wäre auch heute noch bestehen geblieben, wenn nicht die Integration mit den Bibliotheken von .Net Framework. 

Dieser Artikel beschäftigt sich mit der Entwicklung von benutzerdefinierten grafischen Oberflächen (GUIs) für MQL5 Handels-Experten und -Indikatoren. Die GUIs sind Windows-Standardformulare, die eine Reihe von grafischen Standardelementen enthalten, wobei jedes Element eng mit der Handelslogik der EA verbunden ist.

Um eine grafische Anwendung zu entwickeln, müssen wir die Bibliotheken von .Net in ein MQL-Programm integrieren. Auch diese Aufgabe wird im Artikel ausführlich behandelt. Daher ist es nicht nur für diejenigen von Vorteil, die eine grafische Form für ihr MQL-Programm erstellen möchten, sondern auch für diejenigen, die an der Integration mit einer Drittanbieter-Codebasis interessiert sind.

Der Schwerpunkt liegt auf der Einfachheit der vorgeschlagenen Methode. Die Hauptaufgabe besteht darin, die Interaktion mit dem C#-Code so einfach wie möglich zu gestalten. Die Interaktion selbst ist so gestaltet, dass der C#-Code ohne Benutzereingriff erstellt wird! Dies ist dank der fortschrittlichen C#-Sprachwerkzeuge und der umfangreichen Funktionen von Visual Studio leicht möglich.

Somit benötigen die Leser keine Kenntnisse von C#. Die Hauptidee besteht darin, die grafischen Steuerelemente, wie Schaltflächen oder Textbeschriftungen, in den visuellen Modus zu versetzen und dann jedes Element mit der entsprechenden Logik über die MQL-Sprache zu versehen. Die Integration des Panels mit dem MQL-Programm erfolgt automatisch "hinter den Kulissen". 


Interaktion mit den GUIs von .Net. Allgemeine Grundsätze

.Net ist ein proprietärer Name der gemeinsamen Sprachplattform, die 2002 von Microsoft als Alternative zur beliebten Java-Plattform entwickelt wurde. Die Plattform basiert auf Common Language Runtime (CLR). Im Gegensatz zu einem herkömmlichen Programm, das direkt in einen Maschinencode kompiliert und direkt auf einem Computer gestartet wird, läuft eine .Net-Anwendung auf der virtuellen CLR-Maschine. .Net ist also eine Art Umgebung, die von einem Programm verwendet wird, das mit einer Hochsprache entwickelt wurde, um auf dem PC eines Benutzers ausgeführt zu werden.

C# ist eine der wichtigsten Programmiersprachen in Net. Wenn jemand über C# spricht, meint er Net und umgekehrt — Net ist eindeutig mit C# verbunden. Vereinfacht gesagt, können wir sagen, dass Net eine Ausführungsumgebung für Programme ist, die hauptsächlich in C# entwickelt wurden. Unser Artikel ist keine Ausnahme. Der gesamte im Artikel vorgestellte Code ist in C# geschrieben.

Nachdem ein Programm für die .Net-Plattform entwickelt wurde, wird es in einen Bytecode der Zwischensprache CIL (Common Intermediate Language) kompiliert, der von der virtuellen Maschine CLR ausgeführt wird. Der Code selbst ist in Standard-Windows-Programmformen verpackt: exe, die ausführbare Module oder dll, die dynamischen Bibliotheken. Der kompilierte Code für die Net Virtual Machine hat eine High-Level-Struktur, seine Eigenschaften sind leicht zu erforschen und wir können sehen, welche Arten von Daten er enthält. Diese bemerkenswerte Eigenschaft wird von den neuesten Versionen des MQL-Compilers genutzt. Während der Kompilierung lädt der Compiler die dynamische Net-Bibliothek herunter und liest die darin definierten öffentlichen statischen Methoden. Zusätzlich zu den öffentlichen statischen Methoden versteht der MQL-Compiler die C#-Grunddatentypen. Zu diesen Datentypen gehören:

  • Alle ganzzahligen Datentypen: long/ulong, int/uint, byte, short/sushort;
  • Gleitkommazahlen float/double;
  • Zeichendatentyp char (im Gegensatz zu MQL, das die Typen char und uchar kennt, wird unter С# dieser Typ zur Definition eines Symbols verwendet);
  • Zeichenketten string;
  • Einfache Strukturen, die die oben aufgeführten Grundtypen als Felder enthalten.

Zusätzlich zu den aufgeführten Typen sieht der MQL-Compiler auch C#-Arrays. Bislang ist es jedoch noch nicht möglich, mit dem Index-Operator '[]' im MQL-Programm einen Standardzugriff auf Array-Elemente zu erhalten. Ich kann mit Sicherheit sagen, dass die Unterstützung von Typen in Zukunft erweitert wird. Die heutigen Fähigkeiten reichen jedoch völlig aus, um eine vollwertige Interaktion zu gewährleisten.

In unserem Projekt werden wir Formulare mit der Windows Forms Technologie entwickeln. Dies ist ein ziemlich einfache Gruppe von APIs, die es sogar einem unvorbereiteten Benutzer ermöglicht, eine GUI schnell und einfach zu zeichnen. Sie zeichnet sich durch einen ereignisorientierten Ansatz aus. Das heißt, wenn ein Benutzer auf eine Schaltfläche klickt oder einen Text in das Eingabefenster eingibt, wird ein entsprechendes Ereignis erzeugt. Nach der Verarbeitung eines solchen Ereignisses bestimmt ein C#-Programm, dass ein bestimmtes grafisches Element des Formulars vom Benutzer geändert wurde. Die Arbeit mit Ereignissen ist ein ziemlich komplizierter Prozess für diejenigen, die mit C# nicht vertraut sind. Ein spezieller Zwischencode wird benötigt, um Ereignisse, die im Formular auftreten, zu verarbeiten und an das MQL-Programm im MetaTrader 5 Terminal weiterzugeben.

So wird unser Projekt drei unabhängige Objekte enthalten, die miteinander interagieren:

  • Ein Programm in Form eines EA oder Indikators, das in MQL (EX5-Datei) entwickelt wurde, um Ereignisse aus dem Grafikfenster zu empfangen oder mit einer speziellen Steuerung an dieses Fenster weiterzugeben;
  • Ein Controller in Form der dynamischen Net-Bibliothek (DLL-Datei), auf die sich das MQL-Programm beziehen soll;
  • Ein grafisches Fenster, das von einem Benutzer in C# als unabhängiges Programm (EXE) oder dynamische Bibliothek (DLL) entwickelt wurde, deren Ereignisse von der Steuerung analysiert werden sollen.

Alle drei Objekte werden über die Nachrichtensysteme miteinander interagieren. Ein Nachrichtensystem soll in der Interaktion zwischen der MQL-Anwendung und der Steuerung verwendet werden, während ein anderes in der Interaktion zwischen der Steuerung und dem Benutzerfenster verwendet werden soll.



Abb. 1. Interaktion zwischen einem MQL-Programm und einer C#-Grafikanwendung. Allgemeiner Aufbau

Die Struktur ist in der allgemeinsten Form dargestellt und zeigt bisher nicht die Besonderheiten der Interaktion zwischen den beschriebenen Teilen unserer zukünftigen grafischen Anwendung. Betrachtet man jedoch das vorgeschlagene Schema, so wird deutlich, dass unser System stark verteilt sein soll: Jedes Modul wird unabhängig sein und keinen Eingriff in seinen Code erfordern, wenn ein anderes Modul Änderungen erfährt. In den folgenden Abschnitten werden wir uns eingehend mit der Interaktion zwischen diesen Teilen und den Mitteln, mit denen diese Trennung umgesetzt wird, befassen.


Installation und Konfiguration von Visual Studio

Nachdem wir nun die allgemeine Durchführungsstruktur vorbereitet haben, ist es an der Zeit, mit dem Projekt fortzufahren. Um dies zu tun, sollten Sie die Arbeitsversion von Visual Studio auf Ihrem PC installiert haben. Wenn Sie dieses Programm bereits installiert haben, können Sie diesen Abschnitt überspringen. Lesen Sie weiter, wenn Sie ein Anfänger sind und sich noch nie zuvor mit dem Programm beschäftigt haben. 

Visual Studio ist eine professionelle Entwicklungsumgebung für eine Vielzahl von Programmieraufgaben. Die Software wird in mehreren Editionen vorgestellt. Wir werden mit der Edition Community arbeiten. Dies ist eine Freemium-Version. Nach dreißig Tagen Nutzung sollte es kostenlos registriert werden. Dazu sollten Sie sich dem Standard-Verifizierungsverfahren mit einem der Microsoft-Dienste unterziehen. Hier zeige ich Ihnen die grundlegenden Schritte des Herunterladens, der Installation und der Registrierung der Plattform, damit Neueinsteiger ihre Funktionalität in kürzester Zeit und ohne große Hürden nutzen können. 

Nachfolgend finden Sie eine Schritt-für-Schritt-Anleitung für die Installation von Visual Studio auf einem Computer. Screenshots für die internationale englische Version des Installationsprogramms finden Sie unten. Das spezifische Aussehen kann in Ihrem Fall je nach den regionalen Einstellungen Ihres PCs unterschiedlich sein. 

Gehen Sie zunächst auf die offizielle Visual Studio-Website visualstudio.microsoft.com und wählen Sie die entsprechende Distribution. Wählen Sie die Community-Version aus:

 

Abb. 2. Auswahl der Distribution von VisualStudio


Danach beginnt der Download des Installationsprogramms vom Visual Studio. Wenn die Website Sie zur Registrierung auffordert, überspringen Sie diesen Schritt. Das werden wir später tun.

Nach dem Start des Installationsprogramms erscheint ein Fenster, in dem Sie über die Notwendigkeit der Konfiguration des Installationsprogramms informiert werden. Klicken Sie auf Сontinue (Weiter)

Abb. 3. Klicken Sie auf Continue (Weiter), um die Installation fortzusetzen.

Der Download der notwendigen Installationsdateien beginnt als nächstes. Es kann einige Zeit dauern, abhängig von Ihrer Bandbreite. Nach Abschluss des Downloads erscheint das Konfigurationsfenster der Installation. Wählen Sie aus den vorgeschlagenen Komponenten die Option ".Net desktop development": 


Abb. 4. Auswahl der Komponenten

Klicken Sie auf Installieren (Install). Der Installationsprozess beginnt. Es kann einige Zeit dauern:

Abb. 5. Installation

Nach Abschluss der Installation wird Visual Studio automatisch gestartet. Wenn dies nicht der Fall ist, starten Sie es manuell. Beim ersten Start fordert Visual Studio Sie auf, sich bei Ihrem Konto anzumelden oder ein neues zu erstellen. Wenn Sie noch keinen Account haben, erstellen Sie ihn jetzt, indem Sie auf den Link "Create One" klicken:


Abb. 6. Erstellen eines neuen Kontos


Die Registrierung einer neuen Mailbox beginnt. Dieses Postfach ist an alle Microsoft-Dienste gebunden. Vollständige Registrierung, die die vorgeschlagenen Aktionen konsistent durchführt. Der Registrierungsprozess ist ziemlich standardisiert, so dass wir nicht im Detail darauf eingehen werden.

Wenn Sie sich nicht registrieren möchten, klicken Sie auf "Nicht jetzt, vielleicht später", um diesen Schritt zu überspringen. Beachten Sie jedoch, dass Visual Studio innerhalb von dreißig Tagen eine Registrierung erfordert. Andernfalls wird es nicht mehr funktionieren.


Erstellen des ersten Formulars. Schnellstart

Nach der Registrierung und dem Einloggen in das Konto wird Visual Studio gestartet. Lassen Sie uns unsere erste visuelle Form entwickeln und mit MetaTrader verbinden. In diesem Abschnitt erfahren Sie, wie einfach dies zu bewerkstelligt werden kann.

Erstellen Sie zunächst ein neues Projekt. Wählen Sie File -> New -> Project. Das Auswahlfenster für den Projekttyp wird eingeblendet:

Abb. 7

Wählen Sie "Windows Form App (.Net Framework)". Geben Sie den Projektnamen in das Feld Name ein. Wir ändern den Standardnamen und nennen unser Projekt GuiMT. Klicken Sie auf OK. Visual Studio zeigt einen Visual Designer mit einem automatisch erstellten Formular an:

 

Abb. 8. Erstellen eines grafischen Formulars im Visual Studio-Fenster


Das Fenster des Solution Explorers enthält die Projektstruktur. Beachten Sie Form1.cs. Dies ist eine Datei mit dem Programmcode, der die grafische Darstellung des Formulars erstellt, die wir im Fenster Form1.cs[Disign] des grafischen Editors sehen. Merken Sie sich den Namen der Datei. Wir werden ihn später brauchen.

Der Visual Designer ermöglicht es uns, die Formulargröße mit der Maus zu ändern. Sie können auch benutzerdefinierte Elemente auf dem Formular platzieren. Diese Eigenschaften sind für unsere ersten Experimente ausreichend. Öffnen Sie die Registerkarte Toolbox und wählen Sie das Element Button auf den Seitenregistern links neben dem Hauptfenster und im Abschnitt All Windows Form:

Abb. 9. Auswahl der Schaltfläche

Ziehen Sie sie mit der Maus auf die Hauptoberfläche von Form1:

Abb. 10. Das erste Formular

Die Größe der Schaltfläche kann ebenfalls geändert werden. Sie können mit der Größe des Hauptfensters und der Position der Schaltfläche experimentieren. Nachdem das Formular nun eine Schaltfläche hat, gehen wir davon aus, dass unsere erste Anwendung fertig ist. Kompilieren wir sie. Dies kann auf verschiedene Weise geschehen, aber jetzt führen wir das einfach im Debug-Modus aus. Klicken Sie dazu auf Start:

Abb. 11. Die Schaltfläche zum Ausführen einer Anwendung im Debug-Modus  

Nach dem Anklicken der Schaltfläche wird die Anwendung kompiliert und automatisch gestartet. Nachdem die Anwendung gestartet wurde, kann sie gestoppt werden, indem Sie einfach das Fenster schließen oder den Debugger in Visual Studio stoppen, indem Sie auf Stop klicken:

Abb. 11. Die Schaltfläche zum Beenden des Debuggers

Unsere erste Anwendung ist fertig. Das Letzte, was wir tun müssen, ist, den absoluten Pfad zu dem Programm herauszufinden, das wir gerade erstellt haben. Der einfachste Weg ist, sich den Pfad im Feld Projektordner des Eigenschaftenfensters anzusehen. Das GuiMT-Projekt sollte im Fenster des Solution Explorers hervorgehoben werden:

Abb. 12. Der absolute Pfad zur Anwendung in der Zeile Project Folder.

Der Pfad in diesem Fenster bezieht sich auf das Projekt selbst. Die spezifische Zusammenstellung unseres Programms ist je nach Kompilierungsmodus in einem der Unterverzeichnisse zu finden. In unserem Fall ist dies .\bin\debug\<Custom_project_name.exe>. Der vollständige Pfad zur Anwendung lautet also wie folgt: C:\User\<Benutzername>\Quelle\repos\GuiMT\GuiMT\GuiMT\bin\debug\GuiMT.exe. Nachdem wir den Pfad definiert haben, sollten wir ihn irgendwo speichern, da wir ihn später in unseren MQL-Code einfügen müssen.


Abrufen der neuesten Version der GuiController.dll. Arbeiten mit GitHub

Die an diesen Artikel angehängten Dateien enthalten die Bibliothek GuiController.dll. Legen Sie sie in das Verzeichnis \MQL5\Libraries. Es kommt jedoch häufig vor, dass die Bibliothek weiterhin aktualisiert und weiterentwickelt wird, so dass das dem Artikel beigefügte Archiv obsolet wird. Um dies und alle ähnlichen Probleme zu vermeiden, empfehle ich die Verwendung eines Versionskontrollsystems, das es ermöglicht, dass der neue Code automatisch für Benutzer verfügbar wird. Unser Projekt bildet dabei keine Ausnahme. Nutzen wir den Dienst GitHub.com zum Speichern von Open Source Codes, um die neueste Version von GuiController zu erhalten. Der Quellcode des Controllers ist in diesem Repository bereits enthalten. Alles, was wir tun müssen, ist, das Projekt herunterzuladen und den Controller in die dynamische Bibliothek zu kompilieren. Wenn Sie das System nicht nutzen können oder wollen, überspringen Sie einfach diesen Abschnitt. Kopieren Sie stattdessen die Datei GuiController.dll in das Verzeichnis MQL5\Libraries. 

Wenn Sie die aktuelle Lösung noch geöffnet haben, schließen Sie sie (Datei -> Lösung). Gehen Sie nun auf die Registerkarte Team Explorer und klicken Sie auf den Link Klonen. Geben Sie die Projektadresse in das gelbe Feld ein:

https://github.com/PublicMqlProjects/MtGuiController

Das nächste Feld gibt den lokalen Pfad an, der zum Speichern des heruntergeladenen Projekts verwendet wird. Der Pfad wird automatisch entsprechend dem Namen des heruntergeladenen Projekts ausgewählt, so dass wir ihn nicht ändern werden. Der folgende Screenshot zeigt die Werte, die in den Team Explorer eingegeben werden sollen:

Abb. 13. Verbindung zum Remote-Repository des Quellcodes herstellen

Nachdem nun alles fertig ist, klicken Sie auf Klonen. Das Projekt mit der neuesten Version von MtGuiController wird nach einiger Zeit an der angegebenen Adresse erscheinen. Öffnen Sie es über den Befehl im Menü Datei -> Öffnen -> Projekt/Lösung. Nach dem Herunterladen und Öffnen des Projekts sollte es kompiliert werden. Drücken Sie dazu F6 oder wählen Sie im Menü Build -> Build Solution. Suchen Sie die kompilierte Datei MtGuiController.dll im Ordner MtGuiController\bin\debug und kopieren Sie sie in das Verzeichnis der MetaTrader 5 Bibliotheken: MQL5\Bibliotheken.

Wenn Sie aus irgendeinem Grund nicht die neueste Version über github beziehen können, kopieren Sie den Controller aus dem unten angehängten Archiv.


Integration der ersten Anwendung mit MetaTrader 5

Nun, da wir die erste Anwendung und die Steuerung haben, die grafische Fenstersignale an MetaTrader sendet, müssen wir den letzten Teil durchführen: Schreiben Sie ein MQL-Programm als EA, das Ereignisse aus dem Fenster über die Steuerung empfangen würde. Entwickeln wir ein neues EA im MetaEditor namens GuiMtController mit den folgenden Inhalten:

//+------------------------------------------------------------------+
//|                                              GuiMtController.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Timer erstellen
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Timer löschen
   GuiController::HideForm(assembly, "Form1");
   EventKillTimer();   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }
  }
//+------------------------------------------------------------------+

Wie ich bereits erwähnt habe, sollte die Bibliothek MtGuiController.dll im Verzeichnis MQL5\Libraries abgelegt werden, um den Code zu kompilieren. Außerdem sollte der in der Zeile angegebene absolute Pfad:

string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

durch den aktuellen Speicherort Ihres Programms mit einem Fenster ersetzt werden.
Wenn alles richtig gemacht wird, wird das EA kompiliert. Nach dem Start erscheint unser Fenster im Hintergrund des MetaTrader-Hauptfensters:

Abb. 14. Der EA mit der integrierten grafischen Anwendung in C#

Nach einem Klick auf die Schaltfläche 1 zeigt der EA die Meldung "Click on element button1" auf der Registerkarte Experten an, die angibt, dass es das Ereignis eines Tastendrucks empfangen hat. 


MQL-Programm-Interaktionen mit GuiController. Ereignismodell

Lassen Sie uns die oben gezeigte MQL-Codeliste gründlich analysieren, um zu verstehen, wie das von uns entwickelte Programm funktioniert.

Die ersten Dinge, die wir sehen können, sind die import Direktive und assembly string:

#import  "MtGuiController.dll"
string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

Die erste Zeichenkette informiert den Compiler, dass Aufrufe der Methoden der offenen statischen Klasse in MtGuiController.dll verwendet werden sollen. In dieser Baugruppe ist es nicht erforderlich, die genauen Methoden anzugeben, auf die wir uns beziehen sollen. Der Compiler macht das automatisch.

Die zweite Zeichenkette enthält den Pfad zu dem Formular, das wir verwalten sollen. Diese Adresse sollte dem tatsächlichen Standort Ihres Formulars entsprechen.

Als Nächstes folgt der Standard OnInit-Code des EA-Initialisierungsverfahrens:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Timer erstellen
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }

Hier wird der Hochfrequenztimer gesetzt und eine der benutzerdefinierten Klassenmethoden zum ersten Mal aufgerufen. Die Timerfunktion wird etwas später beschrieben. Werfen wir nun einen Blick auf den Aufruf von ShowForm:

GuiController::ShowForm(assembly, "Form1");

In C# können Funktionen nicht getrennt von Klassen existieren. Somit hat jede Funktion (Methode) ihre eigene Klasse, in der sie definiert ist. Die einzelne Klasse GuiController ist in der MtGuiController.dll definiert. Sie enthält statische Methoden, mit denen Sie die Fenster verwalten können. Es gibt keine weiteren Klassen in MtGuiController.dll, d.h. die gesamte Verwaltung erfolgt über die Klasse, was sehr praktisch ist, da der Benutzer mit einer einzigen Interaktionsschnittstelle arbeitet und nicht innerhalb einer Reihe von unterschiedlichen Definitionen nach der notwendigen Funktion sucht.

Das erste, was im Initialisierungsblock ausgeführt wird, ist der Aufruf der Methode ShowForm. Wie der Name schon sagt, startet er den Prozess der Anzeige des Formulars. Der erste Parameter der Methode setzt den absoluten Pfad zu der Datei, in der das Formular definiert ist, während der zweite den Namen des Formulars selbst setzt. Mehrere Formulare können in einer einzigen Datei definiert werden. Daher ist es notwendig, die genaue Form, die wir in der Datei starten möchten, anzugeben. In diesem Fall wird das Formular nach der Formularklasse benannt, die von Visual Studio dem benutzerdefinierten Formular standardmäßig zugewiesen wurde. Wenn wir das zuvor erstellte Projekt in Visual Studio und die Datei Form1.Designer.cs im Code-Ansichtsmodus öffnen, sehen wir den notwendigen Namen der Klasse:

partial class Form1
{
        /// <summary>
        /// Benötigte Designer-Variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        ...
}

Es ist notwendig, weiter unten sinnvollere Klassennamen zu vergeben. In Visual Studio kann dies einfach durch Umbenennen der Klasse und aller Referenzen auf sie erreicht werden. In diesem Fall sollte auch der Wert des zweiten Parameters der Methode ShowForm geändert werden.

Die nächste Funktion ist OnTimer. Je nach der Einstellung des Timers wird er fünfmal pro Sekunde aufgerufen. Er enthält den interessantesten Code unseres gesamten Projekts. Der Funktionskörper enthält die for-Schleife, die die Seriennummern von Ereignissen wiederholt:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }

Aus Sicht der Steuerung ist ein Ereignis jede Benutzeraktion, die auf das Formular gerichtet ist. Wenn ein Benutzer beispielsweise auf eine Schaltfläche klickt oder einen Text in ein Textfeld eingibt, empfängt die Steuerung das entsprechende Ereignis und platziert es in der Ereignisliste. Die Anzahl der Ereignisse in der Liste wird von der statischen Methode GuiController::EventsTotal() gesendet, die von unserem MQL-Programm aufgerufen werden kann.

Windows Forms bietet eine Vielzahl von Ereignissen. Jedes Element, wie beispielsweise ein Formular, eine Schaltfläche oder ein Textfeld, enthält Dutzende von Ereignissen. Nicht alle Ereignisse können behandelt werden, aber das ist auch nicht notwendig. Der GuiController verarbeitet nur die wichtigsten. In der aktuellen Version gibt es nur drei behandelte Ereignisse. Das sind wie folgt:

  • das Ereignis eines Klicks auf eine Schaltfläche;
  • das Ereignis der Beendigung einer Texteingabe;
  • das Ereignis für horizontales Blättern.

Die Liste soll in Zukunft erweitert werden, obwohl ihr aktueller Zustand für die Zwecke des Artikels bereits ausreichend ist.

Nachdem das von unserem GuiController unterstützte Ereignis eingetreten ist, wird es behandelt und in die Ereignisliste aufgenommen. Die Behandlung eines Ereignisses besteht darin, Daten zu erzeugen, nach deren Erhalt ein MQL-Programm den Ereignistyp und seine Parameter relativ einfach definieren kann. Deshalb hat das Datenformat jedes Ereignisses eine sehr ähnliche Struktur wie das Ereignismodell der Funktion OnChartEvent. Aufgrund dieser Ähnlichkeit muss ein Benutzer, der mit GuiController arbeitet, nicht das Format eines neuen Ereignismodells erlernen. Natürlich hat der vorgestellte Ansatz seine eigenen Schwierigkeiten, z.B. sind komplexe Ereignisse (wie Scrollen) extrem schwierig in das vorgeschlagene Format zu integrieren, aber diese Probleme lassen sich leicht mit C#-Sprachwerkzeugen und seinem fortschrittlichen objektorientierten Programmiermodell lösen. In der Zwischenzeit ist das vorgeschlagene Modell ausreichend, um unsere Aufgaben zu lösen.

Jedes Mal, wenn ein neues Ereignis eintrifft, werden seine Daten für den Empfang über Referenztypen mit der statischen Methode GuiController::GetEvent verfügbar. Dieses Verfahren hat den folgenden Prototyp:

public static void GetEvent(int event_n, ref string el_name, ref int id, ref long lparam, ref double dparam, ref string sparam)

Lassen Sie uns seine Parameter beschreiben: 

  • event-n — Seriennummer eines zu empfangenden Ereignisses. Dank der Möglichkeit, die Seriennummer eines Ereignisses anzugeben, ist es einfacher, neue Ereignisse zu steuern, unabhängig von ihrer Anzahl;
  • el_name — Name des Elements, das dieses Ereignis erzeugt hat;
  • id — Ereignistyp;
  • lparam — Ganzzahl des Ereignisses;
  • dparam — der tatsächliche Wert des Events;
  • sparam — Zeichenkettenwert des Events.

Wie Sie sehen können, ähnelt das GuiController-Ereignismodell stark dem OnChartEvent. Jedes Ereignis in GuiController hat immer eine Seriennummer und eine Quelle (Elementname), die es erzeugt hat. Die übrigen Parameter sind optional. Einige Ereignisse, wie z.B. das Anklicken einer Schaltfläche, haben überhaupt keine zusätzlichen Parameter (lparam, dparam, sparam), während ein das Ereignis der Beendigung einer Texteingabe im Sparam-Parameter den von einem Benutzer in das Feld eingegebenen Text enthält.

Nachfolgend finden Sie eine Tabelle mit Ereignissen und deren Parametern, die derzeit unterstützt werden:

Ereignisname ID  Parameter
Exception  0 sparam - Nachricht, die die Ausnahme verursacht hat
ClickOnElement  1 -
TextChange  2 sparam - neuer, vom Nutzer eingegebener Text 
ScrollChange  3

lparam - vorherige Bildlaufebene

dparam - aktuelle Bildlaufebene

Nachdem wir uns nun mit dem Ereignismodell in GuiController beschäftigt haben, können wir endlich den Code verstehen, der in der for-Schleife präsentiert wird. Die Zeile:

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);

ruft das Ereignis mit dem Index i ab. Wenn ein Ereignistyp einem Tastendruck entspricht, wird der Name der Schaltfläche und eine Meldung über seine Betätigung in der Terminalkonsole angezeigt:

if(id == ClickOnElement)
   printf("Click on element " + el_name);

Beachten Sie, dass die ID mit der Konstante ClickOnElement verglichen wird, die nirgendwo im MQL-Programmcode definiert ist. Diese Konstante ist Teil der Aufzählung, die im GuiController selbst in C# definiert ist.

/// <summary>
/// Ereignis-Typ der GUI
/// </summary>
public enum GuiEventType
{
    Exception,
    ClickOnElement,
    TextChange,
    ScrollChange
}

Wie Sie sehen können, versteht und arbeitet der Compiler mit externen Aufzählungen, die in Bibliotheken von Net definiert sind. 

Lassen Sie uns noch einmal darauf eingehen, wie Nachrichten empfangen werden. Der Prozess beinhaltet den Timer, obwohl jede andere periodisch aufgerufene Funktion (wie OnTick) verwendet werden könnte. Allerdings wäre die Periodizität sehr schwer zu kontrollieren. Es ist nicht sicher, wie viel Zeit zwischen zwei aufeinanderfolgenden OnTick-Aufrufen vergeht.

Außerdem ist es unmöglich, die Periodizität eines Aufrufs (auch OnTimer) zu garantieren. So unterscheidet sich beispielsweise im Strategie-Tester die Frequenz in OnTimer stark von derjenigen, die für diese Funktion in der realen Arbeit eingestellt werden kann. Diese Effekte ermöglichen es dem Benutzer, zwischen zwei Funktionsaufrufen mehrere Ereignisse in Folge zu erzeugen. So können Benutzer beispielsweise zwei- bis dreimal auf die Schaltfläche klicken, bevor ein MQL-Programm Zeit hat, auf den ersten Klick zu reagieren.

Die Ereigniswarteschlange behebt dieses Problem. Jedes Ereignis tritt in die Liste ein und wartet dann darauf, dass seine Parameter vom MQL-Programm abgerufen werden. Das Programm merkt sich die letzte Nummer des Ereignisses, indem es eine statische Variable in der Funktion definiert. Beim nächsten Start erhält es neu eingetroffene Events! Aus diesem Grund hat die for-Schleife eine nicht standardisierte Signatur:

//-- Die Schleife merkt sich den letzten Index des Ereignisses und beginnt beim nächsten Start der Funktion mit der Arbeit ab da.
for(static int i = 0; i < GuiController::EventsTotal(); i++)

Ereignisse können mit der Methode GuiController::GetEvent empfangen werden. Sie können sie auch über GuiController::SendEvent senden. Die zweite Methode wird verwendet, wenn einige Daten an ein Fenster gesendet werden sollen, um dessen Inhalt zu ändern. Sie hat den gleichen Prototyp wie GetEvent. Der einzige Unterschied besteht darin, dass es keine Seriennummer eines Ereignisses gibt, da es hier bedeutungslos ist. Wir werden nicht im Detail darauf eingehen, aber wir werden die Arbeit mit ihm an einem Beispiel im letzten Teil des Artikels zeigen.

Die letzte Methode, die wir noch nicht untersucht haben, ist GuiController::HideForm. Seine Signatur ist ähnlich wie bei ShowForm, während die Aktion spiegelbildlich ist: Diese Methode blendet das Fenster aus. Dazu müssen Ort und Name angegeben werden.

Wie Sie sehen können, ist der MQL-Code zur Anzeige des Formulars und zur Analyse eingehender Ereignisse recht kompakt und einfach. Tatsächlich beschreibt der Code drei einfache Schritte:

  1. Anzeige eines Fensters, wenn das Programm gestartet wird;
  2. Empfang neuer Daten vom Fenster;
  3. Ausblenden des Fensters beim Verlassen des Programms.

Wie Sie sehen können, ist die Struktur so einfach wie möglich. Beachten Sie auch den Code des von uns entwickelten Formulars. Obwohl das Fensterformular den gleichen Code enthält, haben wir keine einzige Codezeile in C# geschrieben. Die erweiterten Möglichkeiten der automatischen Codegenerierung in Visual Studio sowie der GuiController haben uns die ganze Arbeit gekostet. Auf diese Weise manifestiert sich die Stärke der Net-Technologie, denn das ultimative Ziel leistungsstarker Umgebungen ist die Einfachheit.


Unter dem Dach des GuiControllers

Wenn Sie C# nicht wirklich kennen, können Sie diesen Abschnitt überspringen. Es ist von Interesse für diejenigen, die verstehen wollen, wie GuiController funktioniert und wie der Zugriff auf einzelne isolierte Net-Anwendungen erfolgt.

GuiController ist eine gemeinsame Klasse, die aus zwei Teilen besteht: dem statischen und dem Instanzteil. Der statische Teil der Klasse enthält offene statische Methoden zur Interaktion mit dem MetaTrader. Dieser Teil der Klasse implementiert die Schnittstelle zwischen MetaTrader 5 und dem Controller selbst. Der zweite Teil ist der Instanzteil, was bedeutet, dass die Daten und Methoden dieses Teils nur auf Instanzebene existieren. Ihre Aufgabe ist es, mit den unabhängigen Net-Gruppen zu interagieren, in denen sich grafische Fenster befinden. Das Grafikfenster in Windows Forms ist eine Klasse, die von der Basisklasse Form abgeleitet wird. So können Sie mit jedem Benutzerfenster auf einer höheren und abstrakten Ebene der Klasse Form arbeiten.

Net-Gruppen (z.B. DLL oder EXE) enthalten Net-Typen, die von Natur aus offen sind. Der Zugang zu ihnen, ihren Eigenschaften und sogar Methoden ist ganz einfach. Dies kann mit einem Mechanismus namens reflection in Net erfolgen. Dank dieses Mechanismus kann jede in Net erstellte Datei, wie DLL oder EXE, auf das Vorhandensein des notwendigen Elements untersucht werden. Das ist es, was die Klasse GuiController tut. Wenn der absolute Pfad zu einer Net-Gruppe an sie übergeben wird, lädt die Steuerung diese Baugruppe mit einem speziellen Mechanismus. Danach findet es das Grafikfenster, das angezeigt werden soll. Lassen Sie uns den Code der Methode GetGuiController zur Verfügung stellen, der die Arbeit ausführt:

/// <summary>
/// Erstellen GuiController für das Windows Formular
/// </summary>
/// <param name="assembly_path">Pfad zum Assembly</param>
/// <param name="form_name">Windows Formularname</param>
/// <returns></returns>
private static GuiController GetGuiController(string assembly_path, string form_name)
{
    //-- Laden der angegebenen Gruppe (assembly)
    Assembly assembly = Assembly.LoadFile(assembly_path);
    //-- Auffinden des angegebenen Formulars darin
    Form form = FindForm(assembly, form_name);
    //-- Zuweisen des Controllers zum gefundenen Formulars
    GuiController controller = new GuiController(assembly, form, m_global_events);
    //-- Rückgabe des Controllers an die aufrufende Methode
    return controller;
}

Dieses Verfahren ähnelt den Aktionen eines sogenannten Ressourcen-Grabbers: ein spezielles Programm, das die Extraktion von Medieninhalten wie Icons und Bildern aus dem Binärcode eines Programms ermöglicht.

Die Suche nach einer Form erfolgt über "Reflection". Die Methode FindForm empfängt alle Typen, die in der an sie übergebenen Baugruppe definiert sind. Unter diesen Typen wird nach Personen gesucht, deren Basistyp mit dem Formulartyp übereinstimmt. Wenn auch der Name eines erkannten Typs mit dem erforderlichen übereinstimmt, wird eine Instanz dieses Typs erzeugt, die als Formular zurückgegeben wird:

/// <summary>
/// Finden des benötigten Formulars
/// </summary>
/// <param name="assembly">Assembly</param>
/// <returns></returns>
private static Form FindForm(Assembly assembly, string form_name)
{
    Type[] types = assembly.GetTypes();
    foreach (Type type in types)
    {
        //assembly.CreateInstance()
        if (type.BaseType == typeof(Form) && type.Name == form_name)
        {
            object obj_form = type.Assembly.CreateInstance(type.FullName);
            return (Form)obj_form;
        }
    }
    throw new Exception("Form with name " + form_name + " in assembly " + assembly.FullName + "  not find");
}

Der spannendste Moment ist die Entwicklung der Anwendung selbst und deren Einführung. Schließlich erwacht das eigentliche Programm aus dem externen Binärdatensatz zum Leben und beginnt als eigenständige Anwendung zu arbeiten.

Nach dem Erzeugen der Instanz wird ihr ein Controller zugewiesen. Der Controller ist eine Instanz der Klasse GuiController, die das an ihn übergebene Formular überwacht. Zu den Zielen des Controllers gehören die Verfolgung von Ereignissen und deren Übergabe an das Formular.

Das Formular wird in einem parallelen Thread gestartet und gelöscht. Dadurch wird verhindert, dass der aktuelle Thread blockiert wird, während er auf den Abschluss des aktuellen Vorgangs wartet. Angenommen, wir haben das Fenster im aktuellen Thread gestartet. Während das Fenster funktioniert, hängt der externe Prozess, der es aufgerufen hat, und wartet darauf, dass sich das Fenster schließt. Das Starten des Fensters in einem separaten Thread löst dieses Problem.

Das Starten und Löschen eines Fensters erfolgt durch die entsprechenden Controller-Methoden:

/// <summary>
/// Das von MetaTrader aufgerufene benutzerdefinierte Formular sollte asynchron ausgeführt werden,
/// um sicherzustellen, dass die Schnittstelle weiterhin reagiert.
/// </summary>
public static void ShowForm(string assembly_path, string form_name)
{
    try
    {
        GuiController controller = GetGuiController(assembly_path, form_name);
        string full_path = assembly_path + "/" + form_name;
        m_controllers.Add(full_path, controller);
        controller.RunForm();
    }
    catch(Exception e)
    {
        SendExceptionEvent(e);
    }
}
        
/// <summary>
/// Nach dem der EA die Bearbeitung des Formulars beendet hat, sollte dessen Ausführung beendet werden.
/// </summary>
public static void HideForm(string assembly_path, string form_name)
{
    try
    {
        string full_path = assembly_path + "/" + form_name;
        if (!m_controllers.ContainsKey(full_path))
            return;
        GuiController controller = m_controllers[full_path];
        controller.DisposeForm();
    }
    catch(Exception ex)
    {
        SendExceptionEvent(ex);
    }
}

Die letzte Sache, die wir bei der Steuerung berücksichtigen sollten, ist die Arbeit mit Ereignissen. Wenn ein neues Formular mit Reflection erstellt wird, wird es an die Methode übergeben, die seine Ereignisse abonniert, oder besser gesagt, nur an die, die der Controller verarbeiten kann. Dazu wird das Mapping <element - Event-Handler-Liste> erstellt. In diesem Mapping wird der Event-Handler dem notwendigen Event zugeordnet: 

/// <summary>
/// Abonnieren der unterstützten Veranstaltungen
/// </summary>
/// <param name="form">Windows Formular</param>
private void SubscribeOnElements(Form form)
{
    Dictionary<Type, List<HandlerControl>> types_and_events = new Dictionary<Type, List<HandlerControl>>();
    types_and_events.Add(typeof(VScrollBar), new List<HandlerControl>() { vscrol => ((VScrollBar)vscrol).Scroll += OnScroll });
    types_and_events.Add(typeof(Button), new List<HandlerControl>()  { button => ((Button)button).Click += OnClick });
    types_and_events.Add(typeof(Label), new List<HandlerControl>());
    types_and_events.Add(typeof(TextBox), new List<HandlerControl>() { text_box => text_box.LostFocus += OnLostFocus, text_box => text_box.KeyDown += OnKeyDown });
    foreach (Control control in form.Controls)
    {
        if (types_and_events.ContainsKey(control.GetType()))
        {
            types_and_events[control.GetType()].ForEach(el => el.Invoke(control));
            m_controls.Add(control.Name, control);
        }
    }
}

Jedes Formular hat eine offene Liste der darin enthaltenen Elemente. Während der Suche in der Liste der Elemente findet die Methode diejenigen, die der Controller unterstützen kann, und abonniert die von ihm benötigten Ereignisse. Wenn das Element auf dem Formular nicht von der Steuerung unterstützt wird, wird es einfach ignoriert. Die damit verbundenen Ereignisse werden nicht an das MQL-Programm übergeben, und das MQL-Programm selbst ist nicht in der Lage, mit diesem Element zu interagieren.


Handelspanel auf GUI-Basis

Nachdem wir nun alle Teile unseres Systems abgedeckt haben, ist es an der Zeit, etwas wirklich Nützliches zu schaffen. Wir werden ein Analogon des Standard-Handelspanels aus der linken oberen Ecke des Charts erstellen:

Abb. 15. MetaTrader 5 integriertes Handelspanel

Natürlich wird unser Panel aus grafischen Standardelementen des Windows-Betriebssystem-Fensters bestehen, so dass es ein einfacheres Design hat, während die Funktionalität identisch bleibt.

Wir können ein solches Panel von Grund auf neu entwickeln. Die Beschreibung des Visual Designers überschreitet jedoch die Grenzen des Themas des Artikels. Laden wir daher einfach das Projekt mit dem Panel in Visual Studio hoch. Dies kann auf zwei Arten geschehen: Kopieren Sie das Projekt aus dem Archiv und öffnen Sie es in Visual Studio oder laden Sie es aus dem Remote-Git-Repository unter der folgenden Adresse herunter: 

https://github.com/PublicMqlProjects/TradePanelForm

In diesem Fall ist die Arbeit mit github die gleiche wie im entsprechenden Abschnitt beschrieben, also lassen Sie uns nicht noch einmal darauf eingehen.

Nach dem Herunterladen und Öffnen des Projekts sehen Sie das folgende Formular:

Abb. 16. Das Fenster des TradePanels im Editor des Visual Studio

Das Projekt beinhaltet das Layout des Handelspanels. In realen Projekten wie diesem müssen wir ständig Zugriff auf die auf diesem Formular platzierten Elemente erhalten und Ereignisse an sie senden. Für diese Zwecke ist es notwendig, auf jedes Element mit seinem Namen zu verweisen. Daher sollten die Namen der Elemente sinnvoll und einprägsam sein. Mal sehen, wie die Elemente, die wir verwenden sollen, genannt werden. Um den Namen jedes Elements anzuzeigen, suchen Sie die Eigenschaft Name im Fenster Properties, während Sie zuerst das gewünschte Element auswählen. Beispielsweise hat die Schaltfläche mit der Bezeichnung Kaufen den Namen Knopfkauf:

Abb. 17. Der Name des Elements im Eigenschaftsfenster

Es ist notwendig, den auf dem Element dargestellten Text und den Namen des Elements selbst zu unterscheiden. Dies sind unterschiedliche Werte, obwohl sie oft eine ähnliche Bedeutung haben.

Hier ist eine Liste der Elemente, die unser Handelspanel enthält:

  • Das grafische Hauptfenster (Formular) mit dem Namen TradePanelForm, in dem sich alle anderen Bedienelemente befinden.
  • Rotes Textfeld (Label) mit dem Namen AskLabel. Das Textfeld soll den Ask-Kurs des aktuellen Symbols anzeigen;
  • Blaues Textfeld (Label) mit dem Namen BidLabel. Das Textfeld soll den Bid-Preis des aktuellen Symbols anzeigen;
  • Texteingabefeld (TextBox) namens CurrentVolume. Hier ist das erforderliche Positionsvolumen einzugeben;
  • Vertikales Blättern oder Scrollen (VScrollBar) mit dem Namen IncrementVol. Die Scroll-Funktion erhöht oder verringert das Volumen um jeweils einen Schritt. Die Schrittweite ist durch ein MQL-Programm auf Basis der aktuellen Handelsumgebung zu definiert.
  • Die Schaltfläche Kaufen (Buy) mit Namen ButtonBuy. Durch Anklicken können Benutzer ein bestimmtes Volumen zum Ask-Preis kaufen — dasjenige, das im roten Textfeld angezeigt wird.
  • Die Schaltfläche Verkaufen (Sell) mit Namen ButtonSell. Durch Anklicken können Benutzer ein bestimmtes Volumen zum Angebotspreis verkaufen, der auf dem blauen Textetikett angezeigt wird.

Obwohl es nur wenige Elemente gibt, bietet ihre Kombination eine ziemlich fortschrittliche Oberfläche. Wie im vorherigen Beispiel enthält unsere Lösung keine einzige Zeichenkette von C#-Code. Im Eigenschaftenfenster werden alle notwendigen Element-Eigenschaften angezeigt und die Position und Größe der Elemente per "Drag-and-Drop", d.h. mit der Maus, eingestellt!


Integration des Grafikfensters mit EA-Code

Nun, da unser Fenster fertig ist, muss es in einen Handels-EA integriert werden. Wir werden MQL verwenden, um die Handelslogik zu schreiben, die mit den Interface-Elementen interagieren soll. Der vollständige EA-Code ist unten aufgeführt:

//+------------------------------------------------------------------+
//|                                                   TradePanel.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
#include <Trade\Trade.mqh>
string assembly = "c:\\Users\\Bazil\\source\\repos\\TradePanel\\TradePanel\\bin\\Debug\\TradePanel.dll";
string FormName = "TradePanelForm";
double current_volume = 0.0;

//-- Handelsmodul für die Auftragserteilung
CTrade Trade;  
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Erstellen des Timers, anzeigen des Fensters und festlegen des Volumens
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, FormName);
   current_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Formular löschen
   EventKillTimer();
   GuiController::HideForm(assembly, FormName);
//---
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Ask-/Bid-Preise aktualisieren   
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
   GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));
//---
}

//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- Erhalt des neuen Ereignisses vom Timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == TextChange && el_name == "CurrentVolume")
         TrySetNewVolume(sparam);
      else if(id == ScrollChange && el_name == "IncrementVol")
         OnIncrementVolume(lparam, dparam, sparam);
      else if(id == ClickOnElement)
         TryTradeOnClick(el_name);
   }
//---
}
//+------------------------------------------------------------------+
//| Volumen bestätigen                                               |
//+------------------------------------------------------------------+
double ValidateVolume(double n_vol)
{
   double min_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   double max_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
   //-- Prüfen des Minimums 
   if(n_vol < min_vol)
      return min_vol;
   //-- Prüfen des Maximums
   if(n_vol > max_vol)
      return max_vol;
   //-- Normalisieren des Volumens
   double vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double steps = MathRound(n_vol / vol_step);
   double corr_vol = NormalizeDouble(vol_step * steps, 2);
   return corr_vol;
}
//+------------------------------------------------------------------+
//| Ersetzen des neuen Volumens mit dem angegebenen Text             |
//+------------------------------------------------------------------+
bool TrySetNewVolume(string nstr_vol)
{
   double n_vol = StringToDouble(nstr_vol);
   current_volume = ValidateVolume(n_vol);
   string corr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, corr_vol);
   return true;
}
//+------------------------------------------------------------------+
//| Durchführen des Handelsauftrags                                  |
//+------------------------------------------------------------------+
bool TryTradeOnClick(string el_name)
{
   if(el_name == "ButtonBuy")
      return Trade.Buy(current_volume);
   if(el_name == "ButtonSell")
      return Trade.Sell(current_volume);
   return false;
}
//+------------------------------------------------------------------+
//| Aktuelles Volumen erhöhen oder vermindern                        |
//+------------------------------------------------------------------+
void OnIncrementVolume(long lparam, double dparam, string sparam)
{
   double vol_step = 0.0;
   //-- Wahrnehmen des Drucks auf eine Erhöhung
   if(dparam > lparam)
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- Wahrnehmen des Drucks auf eine Verminderung
   else if(dparam < lparam)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- Wahrnehmen des Drucks auf eine erneute Erhöhung
   else if(lparam == 0)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- Wahrnehmen des Drucks auf eine erneute Verminderung
   else
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double n_vol = current_volume + vol_step;
   current_volume = ValidateVolume(n_vol);
   string nstr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, nstr_vol);
}
//+------------------------------------------------------------------+

Der vorliegende Code ist der funktionierende Kern unseres Formulars. Es ist anzumerken, dass die gesamte Funktionalität in MQL5 innerhalb der Standardfunktionen zur Ereignisbehandlung geschrieben ist. Lassen Sie uns den bereitgestellten Code im Detail besprechen.

Das erste, was die OnInit-Funktion tut, ist, den Timer mit der Auflösung von 200 Millisekunden festzulegen. Das Fenster wird anschließend mit der Methode ShowForm angezeigt:

GuiController::ShowForm(assembly, FormName);

wobei "assembly" ein Pfad zu der Gruppe ist, in der sich das Fenster befindet, während FormName ein Name unserer Formularklasse ist.

Unmittelbar nach dem Start des Fensters stellen wir die Mindestvolumen im Textfeld CurrentVolume ein:

GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));

Das Mindestvolumen selbst wird auf Basis des aktuellen Handelsumfelds mit der Funktion SymbolInfoDouble berechnet.

Beim Schließen des EA wird auch das Formularfenster geschlossen. Dies geschieht in der Funktion OnDeinit mit der Methode GuiController::HideForm. 

Die OnTick-Funktion reagiert auf die Änderung des aktuellen Ask/Bid-Kurses. Wenn wir also die aktuellen Preise in der Funktion erhalten und an die entsprechenden Textetiketten des Formulars übergeben, zeigt das Panel zeitnah alle Änderungen des aktuellen Preises an.

//-- Abrufen des Ask-Preises
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//-- Abrufen des Bid-Preises
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
//-- Ersetzen des Textes in Textfeld AskLabel mit dem aktuellen in eine Zeichenkette umgewandelten Ask-Preis:
GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
//-- Ersetzen des Textes in Textfeld BidLabel mit dem aktuellen in eine Zeichenkette umgewandelten Bid-Preis:
GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));

Drei Aktionen, die Benutzer mit dem Formular machen können, werden in der Funktion OnTimer verfolgt. Zu diesen Maßnahmen gehören:

  • Die Eingabe eines neuen Datenträgers für das Textfeld CurrentVolume;
  • Der Klick auf die Schaltfläche Volumen erhöhen oder verringern, was in Form einer Bildlaufleiste ausgeführt wird;
  • Der Klick auf die Schaltfläche Buy oder Sell, um eine Handelsanfrage zu senden.

Abhängig von der vom Nutzer ausgeführten Aktion wird ein bestimmter Satz von Anweisungen ausgeführt. Wir haben noch kein Ereignis analysiert, bei dem Sie auf die Schaltfläche der Bildlaufleiste klicken (scrollen), um das aktuelle Volumen um den minimal zulässigen Schritt zu erhöhen/verringern, also wenden wir uns dem jetzt zu.

Das Scroll-Ereignis im aktuellen Ereignismodell besteht aus den beiden Parametern lparam und dparam. Der erste Parameter enthält einen konventionellen Wert, der die Schlittenverschiebung in Bezug auf die Nullstufe charakterisiert, bevor ein Benutzer auf die Bildlauftasten klickt. Der zweite Parameter enthält den gleichen Wert, nachdem er angeklickt wurde. Das Scrollen selbst hat einen bestimmten Betriebsbereich, z.B. von 0 bis 100. Wenn lparam also 30 ist, während dparam 50 ist, bedeutet dies, dass die vertikale Schriftrolle von 30 bis 50% nach unten bewegt wurde (die vertikale Schriftrolle bewegt sich um den gleichen Betrag nach rechts). Es ist nicht erforderlich, die Scrollposition im Panel zu definieren. Wir müssen nur wissen, auf welche Schaltfläche ein Benutzer geklickt hat. Dazu sollten wir die bisherigen und aktuellen Werte analysieren. Dazu steht die Funktion OnIncrementVolume zur Verfügung. Nach der Definition des Typs eines Scroll-Klicks wird das aktuelle Volumen um den minimalen Volumenschritt erhöht oder verringert, der über die Systemfunktion SystemInfoDouble definiert ist.

Scrollpfeile sind nicht die einzige Möglichkeit, ein neues Handelsvolumen festzulegen. Sie können es auch direkt in das Textfeld eingeben. Wenn ein Nutzer ein neues Zeichen eingibt, generiert Windows Forms ein entsprechendes Ereignis. Es ist jedoch wichtig für uns, die endgültige Zeichenkette zu analysieren, und nicht jedes einzelne Zeichen. Daher reagiert der GuiController auf das Drücken der Taste "Enter" oder das Ändern des Fokus der Textbeschriftung. Diese Ereignisse werden als das Ende der Texteingabe betrachtet. Wenn einer von ihnen auftritt, wird der generierte Text sequentiell an die vom EA gelesene Ereigniswarteschlange übergeben. Nachdem die Textänderung im Label-Ereignis erreicht ist, analysiert das MQL-Programm seinen neuen Wert und setzt ein neues Volumen entsprechend dem angegebenen. Die Analyse wird mit der Funktion ValidateVolume durchgeführt. Sie steuert die folgenden Parameter des eingegebenen Volumens:

  • Das Volumen sollte zwischen dem minimal und maximal zulässigen Wert liegen;
  • Der Volumenwert sollte ein Vielfaches seiner Schrittweite betragen. Wenn die Schrittweite beispielsweise 0,01 Lose beträgt und ein Benutzer den Wert 1,0234 eingibt, wird er an 1,02 angepasst.

Beachten Sie, dass es möglich ist, diese Parameter nur mit Hilfe des aktuellen Handelsumfelds zu steuern. Somit wird die gesamte Kontrolle über die vom Benutzer eingegebenen Werte vom MQL-Programm selbst übernommen und nicht von der vom Benutzer erstellten Form. 

Lassen Sie uns das Handelspanel auf dem Chart starten und versuchen, mehrere Positionen damit zu eröffnen:


Abb. 18. Die Panelbedienung in Echtzeit 

Wie Sie sehen können, erfüllt das Handelspanel erfolgreich alle ihm zugewiesenen Funktionen.


GUI-Verhalten im Strategietester

MetaTrader 5 Strategie-Tester hat eine Reihe von Funktionen, die von MQL-GUI-Entwicklern berücksichtigt werden sollten. Die wichtigste ist die Tatsache, dass die grafische Ereignisverarbeitungsfunktion OnChartEvent überhaupt nicht aufgerufen wird. Diese Funktion ist logisch, da die grafische Form die Arbeit mit einem Benutzer in Echtzeit beinhaltet. Es wäre jedoch äußerst interessant, eine bestimmte Art von Panels im Tester zu implementieren. Dies sind die sogenannten Trading Player, die es den Nutzern ermöglichen, ihre Handelsstrategien manuell zu testen. Beispielsweise generiert der Strategietester im Schnelldurchlauf die aktuellen Marktpreise, während ein Benutzer auf die Schaltflächen Kaufen und Verkaufen klickt, um die Handelsoperationen mit der Historie zu simulieren. Das TradePanel, das wir entwickelt haben, bietet genau diese Art von Panels. Trotz seiner Einfachheit kann es sich durchaus um einen reinen Handelspartner mit den notwendigsten Funktionen handeln. 

Aber lassen Sie uns darüber nachdenken, wie unser Panel im Strategietester MetaTrader 5 funktionieren wird. Das Grafikfenster von TradePanel existiert als eigenständige Net-Assembly. Daher ist es nicht von der aktuellen Umgebung des MetaTrader 5 oder gar dem Terminal selbst abhängig. Genau genommen kann es von jedem anderen Programm aus ausgeführt werden, während sogar Benutzer selbst Assemblies starten können, die sich im Exe-Container befinden.

Somit muss unser Programm OnChartEvent nicht aufrufen. Darüber hinaus ist es möglich, Daten im Fenster zu aktualisieren und neue Aufträge von Benutzern zu erhalten, in jedem Fall aber mit einer Funktion, die regelmäßig im Strategie-Tester gestartet wird. OnTick und OnTimer sind solche Funktionen. Das Panel arbeitet durch sie hindurch. Daher ist unser Panel zwar für den Echtzeitbetrieb ausgelegt, funktioniert aber auch gut im Strategietester. Hierfür sind keine Änderungen erforderlich. Lassen Sie uns diese Aussage überprüfen, indem wir unser Panel im Tester starten und mehrere Deals tätigen:


Abb. 19. Die Verwendung des Panels im Simulationsmodus des Strategie-Testers

Es stellt sich heraus, dass die Entwicklung von grafischen Oberflächen mit C# uns einen unerwarteten Bonus bei der Arbeit im Strategietester bietet. Für eine Anwendung von Windows Forms legt der Strategietester keine Einschränkungen fest. Die Funktionen zur Bedienung des Ereignismodells haben keinen Einfluss auf das Bedienfeld und die Art und Weise, wie damit gearbeitet wird. Es besteht auch keine Notwendigkeit, das Programm zu ändern, um im Strategie-Tester zu arbeiten. 


Schlussfolgerung

Der Artikel schlägt einen Ansatz vor, mit dem Nutzer schnell und einfach eine benutzerdefinierte visuelle Form entwickeln können. Dieser Ansatz unterteilt die grafische Anwendung in drei unabhängige Teile: das MQL-Programm, den GuiController-Adapter und das Visual Panel selbst. Alle Teile der Anwendung sind unabhängig voneinander. Das MQL-Programm arbeitet im MetaTrader-Handelsumfeld und führt Handels- oder Analysefunktionen basierend auf den Parametern aus, die es vom Panel über GuiController erhält. GuiController selbst ist ein unabhängiges Programm, das Sie beim Ändern eines Formulars oder seiner Elemente nicht ändern müssen. Schließlich wird das grafische Panel von den Benutzern selbst mit den erweiterten visuellen Werkzeugen von Visual Studio erstellt. Dadurch sind die Kenntnisse der Programmiersprache C# auch bei der Entwicklung einer recht komplexen Form möglicherweise nicht erforderlich.

Die benutzerdefinierten Formulare selbst sind nicht von dem Programm abhängig, das sie startet. Es kann der MetaTrader 5 selbst sein oder der Strategie-Tester. In beiden Fällen arbeitet das Fenster in Übereinstimmung mit der eingebetteten Logik. Außerdem ist das Fenster nicht von der Funktion abhängig, in der es aufgerufen wird. Dadurch funktionieren die grafischen Oberflächen sowohl im MetaTrader 5 selbst als auch in seinem Strategietester gleichermaßen gut, und es spielt keine Rolle, ob ein EA oder ein Indikator mit dem Fenster funktioniert. In allen Fällen ist das Verhalten des Fensters gleich.

In Anbetracht der oben genannten Merkmale wird der vorgeschlagene Ansatz sicherlich seine Anhänger finden. Es könnte bei denen, die eine halbautomatische Form entwickeln wollen, am beliebtesten sein: eine Handelsmaschine oder ein Player, ein Datenpanel oder eine andere visuelle Form in Form einer Standard-GUI. Der Ansatz wird auch diejenigen ansprechen, die sich nicht gut mit der Programmierung auskennen. Sie müssen nur über allgemeine Kenntnisse in MQL5 verfügen, um ein benutzerdefiniertes Formular zu entwickeln. 

Wie jede Technologie hat auch der vorgeschlagene Ansatz seine Nachteile. Die wichtigste ist die Unmöglichkeit, auf dem Markt zu arbeiten, da der Aufruf von DLLs von Drittanbietern verboten ist. Außerdem kann der Start einer unbekannten DLL oder EXE unsicher sein, da diese Module bösartige Funktionen enthalten können. Der offene Charakter des Projekts löst dieses Problem jedoch. Anwender wissen, dass die von ihnen entwickelten Programme keine anderen Elemente als die von ihnen angegebenen enthalten, und GuiController ist ein öffentliches Open-Source-Projekt. Ein weiterer Nachteil ist, dass die Interaktion zwischen den Anwendungen ein ziemlich komplizierter Prozess ist. Dies kann zu einem Einfrieren oder einem unerwarteten Programmabbruch führen. Hier hängt viel vom Schnittstellenentwickler ab. Es ist einfacher, ein solches System zu demontieren als das monolithische System, das in reinem MQL5 entwickelt wurde.

Das Projekt steckt derzeit noch in den Kinderschuhen. Vielleicht haben Sie hier nicht die notwendigen Bedienelemente gefunden, während die aktuellen Möglichkeiten der Interaktion mit Grafikfenstern noch sehr eingeschränkt sind. All das ist wahr. Der Artikel hat jedoch seine Hauptaufgabe erfüllt. Wir haben gezeigt, dass die Entwicklung von Windows Forms und die Interaktion einfacher ist, als man gemeinhin annimmt. Wenn sich dieser Artikel als nützlich für die MQL-Community erweist, dann werden wir auf jeden Fall weiter auf diesem Arbeitsbereich aufbauen.

 

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/5563

Beigefügte Dateien |
Source.zip (30.67 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (3)
Otto Pauser
Otto Pauser | 22 Apr 2019 in 21:20

Der Ansatz ist durchwegs interessant.

Doch warum, um Himmels Willen, sind Buy- und Sell-Seiten vertauscht ?

Turmbau zu Babel.

Carl Schreiber
Carl Schreiber | 22 Apr 2019 in 22:29
Er hat nur Rot und Schwarz vertauscht, wenn Du schnell genug schaust ;) sihe4st Du, dass unter Buy der höhere der beiden Preise steht - alles in Ordnung, technisch!
Otto Pauser
Otto Pauser | 23 Apr 2019 in 00:52
Carl Schreiber:
Er hat nur Rot und Schwarz vertauscht, wenn Du schnell genug schaust ;) sihe4st Du, dass unter Buy der höhere der beiden Preise steht - alles in Ordnung, technisch!

Buy und Sell sind rechts/links vertauscht. Warum? Damit man sich vertut?

Im Original ist SELL links.

Ubrigens das Original ist das beste Pnel überhaupt, es akzeptiert sowohl . als auch ,

Wäre toll wenn das überall so wäre. Aber solche Wünsche habe ich abgeschrieben.

Untersuchung von Techniken zur Analyse der Kerzen (Teil I): Überprüfen vorhandener Muster Untersuchung von Techniken zur Analyse der Kerzen (Teil I): Überprüfen vorhandener Muster

In diesem Artikel werden wir uns mit den beliebten Kerzenmustern beschäftigen und versuchen herauszufinden, ob sie in den heutigen Märkten noch relevant und effektiv sind. Die Analyse von Kerzen ist vor mehr als 20 Jahren erschienen und erfreut sich inzwischen großer Beliebtheit. Viele Händler halten die japanischen Kerzen für die bequemste und leicht verständlichste Form der Visualisierung von Wertpapierpreisen.

Die Stärke von ZigZag (Teil I). Entwicklung der Basisklasse des Indikators Die Stärke von ZigZag (Teil I). Entwicklung der Basisklasse des Indikators

Viele Forscher schenken dem Erkennen des Preisverhaltens nicht genügend Aufmerksamkeit. Gleichzeitig werden komplexe Methoden eingesetzt, die sehr oft nur "Black Boxes" sind, wie z.B. maschinelles Lernen oder neuronale Netze. Die wichtigste Frage, die sich in diesem Fall stellt, ist, welche Daten für das Training eines bestimmten Modells vorgelegt werden müssen.

Die Stärke von ZigZag (Teil II). Beispiele für das Empfangen, Verarbeiten und Anzeigen von Daten Die Stärke von ZigZag (Teil II). Beispiele für das Empfangen, Verarbeiten und Anzeigen von Daten

Im ersten Teil der Artikelserie habe ich einen modifizierten ZigZag-Indikator und eine Klasse zum Empfangen von Daten dieser Art von Indikatoren beschrieben. Hier werde ich zeigen, wie man Indikatoren entwickelt, die auf diesen Tools basieren, und ein EA für Tests schreiben, der gemäß den Signalen des ZigZag-Indikators handelt. Als Ergänzung wird der Artikel eine neue Version der Bibliothek EasyAndFast zur Entwicklung grafischer Benutzeroberflächen vorstellen.

MQL-Parsing mit Hilfe von MQL MQL-Parsing mit Hilfe von MQL

Der Artikel beschreibt einen Präprozessor, einen Scanner und einen Parser, die beim Parsen der MQL-basierten Quellcodes verwendet werden sollten. Die MQL-Implementierung ist beigefügt.