English 日本語
preview
Risikobasierter Trade Placement EA mit On-Chart UI (Teil 2): Hinzufügen von Interaktivität und Logik

Risikobasierter Trade Placement EA mit On-Chart UI (Teil 2): Hinzufügen von Interaktivität und Logik

MetaTrader 5Experten |
19 0
Chacha Ian Maroa
Chacha Ian Maroa

Einführung

Im ersten Teil dieser Serie haben wir uns auf die Gestaltung eines statischen On-Chart-Kontrollfeldes für unser Risk-Based Trade Placement Tool konzentriert. Ziel war es, eine saubere und übersichtliche Nutzeroberfläche zu schaffen, die es Händlern ermöglicht, Handelsparameter wie Auftragsart und Eröffnungspreis direkt im Chart einzugeben. In diesem Stadium war die Schnittstelle nur visuell und konnte nicht auf Nutzeraktionen reagieren.

In diesem zweiten Teil werden wir die Schnittstelle interaktiv und funktional gestalten. Unser Hauptziel ist es, die grafischen Komponenten mit echter Logik zu verbinden, sodass das Programm, wenn ein Nutzer auf die Schaltfläche „Calculate Lot" klickt, die Eingabefelder liest, die korrekte Losgröße auf der Grundlage des definierten Risikos berechnet und das Ergebnis sofort im Chart anzeigt. Wenn Sie auf die Schaltfläche Send Order klicken, führt das System einen Handel unter Verwendung der berechneten Losgröße und der nutzerdefinierten Parameter aus.

Am Ende dieses Artikels werden Sie einen voll funktionsfähigen Losgrößenrechner auf dem Chart haben, der auf Nutzeraktionen reagiert und Berechnungen in Echtzeit durchführt. Dieser praktische Schritt verwandelt unser statisches Panel in einen intelligenten Handelsassistenten, der die Präzision und Kontrolle bei der Planung von Handelsgeschäften verbessert.



Vorbereitungen für Interaktivität

Im ersten Teil dieser Serie war unser Bedienfeld statisch. Es konnte zwar Schaltflächen und Eingabefelder anzeigen, aber nicht reagieren, wenn ein Nutzer darauf klickte. In diesem Teil werden wir das Panel interaktiv gestalten, indem wir Nutzeraktionen wie das Anklicken von Schaltflächen verarbeiten.

In MQL5 wird die Interaktivität durch eine spezielle Ereignisbehandlungsfunktion namens OnChartEvent ermöglicht. Diese Funktion wird automatisch vom Terminal aufgerufen, wenn ein bestimmtes Chartsereignis eintritt, z. B. ein Mausklick auf ein Chartsobjekt, ein Tastendruck oder ein vom Programm generiertes nutzerdefiniertes Ereignis.

Hier ist die Struktur dieser Funktion, wie sie bereits in unserem Quellcode definiert ist:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

}

Diese Funktion hat vier Parameter:

  • id – identifiziert die Art des aufgetretenen Ereignisses. CHARTEVENT_OBJECT_CLICK steht zum Beispiel für einen Klick auf ein Chartsobjekt.
  • lparam – speichert zusätzliche Daten je nach Ereignistyp, wie die X-Koordinate der Maus oder den Tastencode.
  • dparam – enthält in der Regel numerische Daten im Zusammenhang mit dem Ereignis, z. B. die Y-Koordinate der Maus.
  • sparam – ist ein String-Parameter, der häufig den Namen des Objekts enthält, das an dem Ereignis beteiligt ist. Dies ist derjenige, auf den wir uns verlassen werden, um zu wissen, welche Schaltfläche oder welches Etikett angeklickt wurde.

Nun wollen wir innerhalb dieser Funktion Klick-Ereignisse erkennen. Da wir uns nur für Nutzerklicks auf unsere grafischen Komponenten interessieren, werden wir uns auf das Ereignis CHARTEVENT_OBJECT_CLICK konzentrieren. Wir können dies tun, indem wir die folgende bedingte Anweisung in den Körper unserer Funktion einfügen:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
   }

}

Dadurch wird sichergestellt, dass unser Programm nur reagiert, wenn ein Nutzer auf ein beliebiges Chartsobjekt klickt. Von hier aus können wir den Parameter sparam verwenden, um genau zu bestimmen, welches Objekt angeklickt wurde, und dann entsprechend reagieren.



Erfassen und Verarbeiten von Nutzeraktionen

Die erste Aktion, die wir behandeln wollen, ist das Schließen der grafischen Nutzeroberfläche, wenn der Nutzer auf das X-Symbol in der oberen rechten Ecke des Bedienfelds klickt.

Schaltfläche zum Schließen

Um dies zu ermöglichen, werden wir eine neue Funktion namens DESTROY_GUI definieren. Diese Funktion entfernt alle Objekte, die unsere Nutzeroberfläche bilden, und aktualisiert das Charts.

Hier ist die Definition der Funktion:


//+------------------------------------------------------------------+
//| Function to destroy the main GUI                                 |
//+------------------------------------------------------------------+
void DESTROY_GUI(){
   ObjectsDeleteAll(0, SmartRiskTrader);
   ObjectDelete(0, BTN_CLOSE_GUI);
   ObjectDelete(0, BTN_ORDER_TYPES);
   ObjectDelete(0, FIELD_ENTRY_PRICE);
   ObjectDelete(0, FIELD_STOP_LOSS);
   ObjectDelete(0, FIELD_TAKE_PROFIT);
   ObjectDelete(0, RISK);
   ObjectDelete(0, BTN_CALC_LOT);
   ObjectDelete(0, BTN_SEND_ORDER);
   ObjectDelete(0, RESULTS_TEXT);
   ObjectsDeleteAll(0, "ORDER_TYPE_GROUP");
   ChartRedraw();
}

Diese Funktion stellt sicher, dass beim Schließen der grafischen Nutzeroberfläche alle grafischen Komponenten aus dem Chart entfernt werden, sodass es sauber bleibt. Der Aufruf von ChartRedraw aktualisiert das Chart sofort, um diese Änderungen widerzuspiegeln.

Als Nächstes müssen wir erkennen, wann der Nutzer tatsächlich auf das Symbol X klickt. Wir tun dies innerhalb unserer Ereignisbehandlungsfunktion OnChartEvent. Wir prüfen einfach, ob das Ereignis ein Objektklick war und ob der Name des angeklickten Objekts mit BTN_CLOSE_GUI übereinstimmt. Wenn dies der Fall ist, rufen wir unsere Funktion DESTROY_GUI auf, um die Schnittstelle zu entfernen.

Wir gehen folgendermaßen vor:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      //--- To close GUI
      if(sparam == BTN_CLOSE_GUI){
         DESTROY_GUI();
      }      
   }
}

Wenn Sie den Code kompilieren und ausführen, wird durch Klicken auf das X-Symbol die gesamte grafische Nutzeroberfläche sofort aus dem Chart entfernt. Dies gibt dem Nutzer eine saubere und reaktionsschnelle Möglichkeit, das Panel zu schließen, genau wie jede Standard-Anwendungsschnittstelle.

Es wäre unpraktisch, wenn der Nutzer nach dem Schließen des Bedienfelds keine Möglichkeit hätte, es wieder zu öffnen. Um dieses Problem zu lösen, werden wir eine kleine Schaltfläche „GUI öffnen" erstellen, die in der linken unteren Ecke des Charts erscheint. Wenn der Nutzer auf diese Schaltfläche klickt, wird das Hauptfenster wieder angezeigt.

Schaltfläche GUI Open

Wir beginnen mit der Definition eines Makros, um unserer Schaltfläche einen eindeutigen Namen zu geben, auf den wir später leicht zurückgreifen können:

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+

...

#define BTN_GUI_OPEN      "BTN_GUI_OPEN"

Als Nächstes definieren wir eine Funktion namens CREATE_GUI_OPEN_BUTTON. Diese Funktion zeichnet die GUI-Schaltfläche „Öffnen" auf das Chart und positioniert sie ordentlich in der linken unteren Ecke:

//+------------------------------------------------------------------+
//| Function to create the GUI open button                           |
//+------------------------------------------------------------------+
void CREATE_GUI_OPEN_BUTTON(){
   CREATE_OBJ_BUTTON(BTN_GUI_OPEN, 20, 60, 120, 40, "Open GUI", clrWhite, 12, 2, clrDarkGreen, clrBlack);
   ObjectSetInteger(0, BTN_GUI_OPEN, OBJPROP_CORNER, CORNER_LEFT_LOWER);
   ChartRedraw();
}

Der Aufruf von ObjectSetInteger stellt sicher, dass die Position der Schaltfläche in der linken unteren Ecke des Charts verankert ist. Mit dem Befehl ChartRedraw wird das Chart sofort aktualisiert, sodass die Schaltfläche ohne Verzögerung erscheint.

Schließlich sollten wir diese Schaltfläche direkt nach dem Schließen der GUI anzeigen. Dazu rufen wir einfach die Funktion CREATE_GUI_OPEN_BUTTON innerhalb unserer Ereignisbehandlungslogik auf, wenn das X-Symbol angeklickt wird:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      //--- To close GUI
      if(sparam == BTN_CLOSE_GUI){
         
         ...
         
         CREATE_GUI_OPEN_BUTTON();
      }      
   }
}

Nachdem wir nun die Schaltfläche GUI öffnen erstellt haben, müssen wir sie funktionsfähig machen. Wenn der Nutzer auf diese Schaltfläche klickt, sollte das Hauptbedienfeld wieder auf der Karte angezeigt werden. Um dies zu erreichen, werden wir ein kleines Stück Logik in unsere Ereignisbehandlungsfunktion einbauen.

Die Idee ist einfach. Wenn die Schaltfläche „GUI öffnen" angeklickt wird, entfernen wir zunächst die Schaltfläche aus dem Charts, um Duplikate zu vermeiden, und erstellen dann die Haupt-GUI mit unserer bestehenden Funktion CREATE_GUI neu. So sieht die aktualisierte Ereignisbehandlungsfunktion aus:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To open GUI
      if(sparam == BTN_GUI_OPEN){
         DESTROY_GUI_OPEN_BUTTON();
         CREATE_GUI();
      }      
   }
}

In diesem Code ist die Funktion DESTROY_GUI_OPEN_BUTTON dafür verantwortlich, die Schaltfläche „Open GUI" zu entfernen, bevor das Hauptfenster neu erstellt wird.

//+------------------------------------------------------------------+
//| Function to destroy the GUI open button                          |
//+------------------------------------------------------------------+
void DESTROY_GUI_OPEN_BUTTON    (){
   ObjectDelete(0, BTN_GUI_OPEN);
   ChartRedraw();
}

Dadurch wird sichergestellt, dass das Chart übersichtlich bleibt und sich die grafischen Elemente nicht überschneiden.

Sobald Sie diesen Code kompiliert und getestet haben, werden Sie feststellen, dass Sie mit einem Klick auf die Schaltfläche GUI öffnen Ihr Hauptsteuerungsfeld wiederherstellen können, so wie es vorher war. Durch diese einfache Funktionalität wirkt die Schnittstelle viel interaktiver und vollständiger.

Als Nächstes werden wir das Dropdown-Menü für die Auftragsarten automatisieren. Wir werden die Funktion so gestalten, dass das Dropdown-Menü erscheint, wenn ein Händler auf die Schaltfläche klickt, und dass das Dropdown-Menü verschwindet, wenn er erneut darauf klickt. Um dies zu erreichen, definieren wir zunächst eine Funktion, die die Dropdown-Liste zerstört oder abbaut, wenn sie nicht benötigt wird. 

//+------------------------------------------------------------------+
//| Function to destroy the order type dropdown                      |
//+------------------------------------------------------------------+
void DESTROY_ORDER_TYPE_DROPDOWN(){
   ObjectsDeleteAll(0, "ORDER_TYPE_GROUP");
   ChartRedraw();
}

Die obige Funktion entfernt alle Chartsobjekte, die zur Gruppe „ORDER_TYPE_GROUP" gehören. Jedes Objekt, das bei der Erstellung des Dropdowns erstellt wird, wird dieser Gruppe zugewiesen. Nach dem Entfernen aktualisiert die Funktion ChartRedraw das Charts, damit die Änderungen sofort sichtbar werden.

Nun müssen wir steuern, wann das Dropdown-Menü angezeigt und wann es ausgeblendet werden soll.

Auswahlliste

Dies geschieht über die Funktion OnChartEvent, die alle Nutzerinteraktionen auf dem Chart verarbeitet. Wir ändern es so, dass es erkennt, wenn die Schaltfläche Select Order Types (Auftragstypen) angeklickt wird, und blenden dann die Dropdown-Liste entsprechend ein oder aus.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To handle order types dropdown
      if(sparam == BTN_ORDER_TYPES){
         
         bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         
         if(state == true) {
            CREATE_ORDER_TYPE_DROPDOWN();
         }
         
         if(state == false){
            DESTROY_ORDER_TYPE_DROPDOWN();
         }         
      }      
   }
}

Die Eigenschaft OBJPROP_STATE sagt uns, ob die Schaltfläche gedrückt (true) oder losgelassen (false) ist. Wenn die Schaltfläche gedrückt wird, wird die Funktion CREATE_ORDER_TYPE_DROPDOWN aufgerufen, um die Auswahlliste anzuzeigen. Wenn er freigegeben wird, wird die Funktion DESTROY_ORDER_TYPE_DROPDOWN aufgerufen, um ihn auszublenden. Dieser einfache Mechanismus macht die Schaltfläche reaktionsschnell. Der Händler kann nun darauf klicken, um die Liste der Auftragsarten anzuzeigen, und erneut klicken, um sie auszublenden.

Im ersten Teil dieser Serie haben wir eine statische GUI erstellt, die fest kodierte Werte für die Felder Eröffnungspreis, Stop-Loss und Take-Profit anzeigt. Dies war zwar in der Entwurfsphase nützlich, ist aber für den praktischen Einsatz nicht ideal. Im Live-Handel ändern sich die Preise ständig, und diese Felder sollten Werte anzeigen, die das aktuelle Marktumfeld widerspiegeln. 

Um dies zu erreichen, erstellen wir zunächst zwei Hilfsfunktionen – eine für die Abfrage des aktuellen Ask-Preises und eine weitere für die Abfrage des Bid-Preises des Symbols, auf das unsere Schnittstelle geladen ist. Mit diesen Funktionen können wir überall in unserem Code auf Live-Preise verweisen, ohne dieselbe Logik zu wiederholen.

//+------------------------------------------------------------------+
//| Function to get Ask Price                                        |
//+------------------------------------------------------------------+
double Ask(){
   double askPrc = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   return NormalizeDouble(askPrc, _Digits);
}

//+------------------------------------------------------------------+
//| Function to get Bid Price                                        |
//+------------------------------------------------------------------+
double Bid(){
   double bidPrc = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   return NormalizeDouble(bidPrc, _Digits);
}

Beide Funktionen verwenden die eingebaute Funktion SymbolInfoDouble, um die Preisdaten für das aktuelle Chartsymbol abzurufen. Die Konstanten SYMBOL_ASK und SYMBOL_BID geben den Typ des abzurufenden Preises an. Anschließend verwenden wir die Funktion NormalizeDouble, um das Ergebnis so zu runden, dass es der Anzahl der vom Symbol verwendeten Dezimalstellen entspricht, die durch die vordefinierte Variable _Digits dargestellt wird. Mit diesen beiden Funktionen können wir nun von jeder Stelle unseres Codes aus auf die Funktionen für Ask oder Bid verweisen, die immer genaue und aktuelle Preise liefern.

Als Nächstes wollen wir die Funktion CREATE_GUI so ändern, dass die Felder für die Werte von Eröffnungskurs, Stop-Loss und Take-Profit anzeigen, die auf dem aktuellen Ask-Preis basieren, anstatt feste Textwerte. Ursprünglich wurden diese Felder mit fest kodierten Zahlen wie z. B.:

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
     
   //--- Entry Price
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, "1.14030", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Stop Loss
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, "1.13302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Take Profit
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, "1.16302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
     
   ...
    
}

Dies funktionierte zwar visuell, entsprach aber nicht den tatsächlichen Marktpreisen des betrachteten Finanzinstruments. Um unsere Schnittstelle nützlicher zu gestalten, ersetzen wir diese Werte durch Ausdrücke, die aktuelle Preisdaten verwenden. Hier ist die aktualisierte Fassung dieses Abschnitts:

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
     
   //--- Entry Price
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, DoubleToString(Ask(), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Stop Loss
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25,   DoubleToString(Ask() - (200 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Take Profit
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, DoubleToString(Ask() + (400 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   ...
   
}

Lassen Sie uns dies aufschlüsseln:

  • Das Feld für den Eröffnungspreis zeigt nun direkt den aktuellen Ask-Preis an, indem DoubleToString(Ask(), Digits()) verwendet wird. So erhalten wir eine saubere Preiskette, die der Preisskala des Instruments entspricht.
  • Das Feld für Stop-Loss wird mit Ask-Preis minus 200 Punkte berechnet, während das Feld für Take-Profit mit den Ask-Preis plus 400 Punkte gesetzt wird. Dies gibt dem Händler eine logische Referenz für die Festlegung seiner Handelsstufen, ohne dass er die Werte manuell eingeben muss.
  • Die Funktion Point sorgt dafür, dass sich die Offsets an das Preisformat des Symbols anpassen, egal ob es sich um ein Devisenpaar, einen Aktien-CFD oder einen Rohstoff handelt.

Auf diese Weise passt sich unsere GUI automatisch an das gehandelte Instrument an. Er bietet Händlern realistische Einstiegs- und Ausstiegsniveaus, sobald die Nutzeroberfläche geladen ist, und macht den Rechner praktischer und intuitiver in der Anwendung.

Bisher haben wir ein Dropdown-Menü erstellt, das erscheint, wenn der Nutzer auf die Schaltfläche Order Type klickt, und wieder verschwindet, wenn er erneut darauf klickt. Die Auswahlliste selbst tut jedoch noch nichts – die Auswahl einer Auftragsart aktualisiert die Schnittstelle nicht. In diesem Abschnitt wird diese Interaktion so gehandhabt, dass, wenn ein Händler auf einen bestimmten Auftragstyp klickt (z. B. Marktkauf), die Hauptschaltfläche aktualisiert wird, um diese Auswahl anzuzeigen, das Dropdown-Menü automatisch geschlossen wird und die Felder für Eröffnungskurs, Stop-Loss und Take-Profit mit sinnvollen Werten gefüllt werden.

Hier ist der Code, der dies tut, wenn der Nutzer die Option „Market Buy" wählt.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the market buy order
      if(sparam == "ORDER_TYPE_GROUP_MARKET_BUY"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits()));
         
         ChartRedraw();        
      }           
   }
}

Lassen Sie uns Schritt für Schritt vorgehen.

bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT);

Hier tun wir zwei Dinge:

  1.  Überprüfung des aktuellen Zustands der Schaltfläche für die Auftragsart (ob sie gedrückt ist oder nicht).
  2.  Lesen des Textes aus dem angeklickten Dropdown-Element, das in diesem Fall „Market Buy" sein sollte.

ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);

In dieser Zeile wird die Beschriftung der Hauptschaltfläche für die Auftragsart aktualisiert, sodass sie jetzt „Market Buy" lautet. Dies gibt dem Nutzer eine unmittelbare Rückmeldung – er kann die ausgewählte Auftragsart auf der Hauptschaltfläche deutlich erkennen.

DESTROY_ORDER_TYPE_DROPDOWN();

Sobald die Auswahl getroffen ist, rufen wir unsere frühere Funktion DESTROY_ORDER_TYPE_DROPDOWN auf, die alle Dropdown-Objekte aus dem Chart löscht.

Um sicherzustellen, dass die Haupttaste nicht mehr gedrückt wird, setzen wir ihren Zustand zurück:

if(state == true){
   ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
}

Als Nächstes füllen wir automatisch die wichtigsten Eingabefelder mit realistischen Standardwerten:

ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits()));
ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits()));
ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits()));

Die einzelnen Zeilen haben folgende Funktion:

  • Entry Price: Wird auf den aktuellen Ask-Preis gesetzt, da dies der Kurs ist, zu dem ein Marktkauf ausgeführt wird.
  • Stop Loss: Wird 200 Punkte unter dem Ask-Preis positioniert, um eine logische Sicherheitsspanne zu gewährleisten.
  • Take Profit: Wird 400 Punkte über dem Ask-Preis positioniert, was ein günstiges Risiko-Ertrags-Verhältnis ergibt.

Schließlich folgt der Aufruf:

ChartRedraw();

Dieser Befehl erzwingt die sofortige Aktualisierung des Charts und stellt sicher, dass die letzten Änderungen an Objekten und deren Text sofort auf dem Bildschirm erscheinen.

Jetzt wollen wir, dass jede Auftragsart in der Auswahlliste funktioniert. Wann immer ein Händler eine der Auftragsarten auswählt, sollten drei Dinge automatisch geschehen:

  1. Der Text der gewählten Auftragsart erscheint auf der Schaltfläche Order Type.
  2. Die Dropdown-Liste wird geschlossen und die Schaltfläche kehrt in ihren normalen Zustand zurück.
  3. Die Felder für Eröffnungspreis, Stop-Loss und Take-Profit werden mit angemessenen Standardwerten für diesen spezifischen Auftragstyp aktualisiert.

Wenn der Händler „Market Sell" auswählt, führt der EA folgende Schritte aus:

  • Aktualisiert die Hauptschaltfläche für den Auftragstyp, sodass „Market Sell" angezeigt wird.
  • Schließt die Auswahlliste und stellt den Zustand der Schaltfläche für die Auftragsarten wieder her.
  • Weist automatisch den Eröffnungspreis, den Stop-Loss und den Take-Profit den Werten zu, die für einen Verkaufsauftrag geeignet sind.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the market sell order
      if(sparam == "ORDER_TYPE_GROUP_MARKET_SELL"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_SELL", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (400 * Point()), Digits()));
                  
         ChartRedraw();        
      }          
   }
}

Da es sich um einen Market Sell handelt, wird der Auftrag zum Bid-Preis ausgeführt. Der Stop-Loss wird oberhalb der Eröffnung gesetzt (200 Punkte höher), während der Take-Profit unterhalb des Einstiegs liegt (400 Punkte niedriger). Dies ergibt ein realistisches Setup für einen typischen Verkaufshandel.

Bei Buy Limit ist die Logik ähnlich, aber die Preise werden angepasst, um einen schwebenden Auftrag unter dem aktuellen Marktpreis zu berücksichtigen.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the buy limit order
      if(sparam == "ORDER_TYPE_GROUP_BUY_LIMIT"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_LIMIT", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Ask() - (400 * Point()), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits()));
         
         ChartRedraw();        
      }          
   }
}

Ein Limit-Kaufauftrag wird unterhalb des aktuellen Ask-Preises platziert. Der Stop-Loss wird weiter unten gesetzt, während der Take-Profit oberhalb des Ask platziert wird.

Ein Verkaufslimit ist das Gegenteil eines Kauflimits – es wird oberhalb des aktuellen Bid-Preises gesetzt.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the sell limit order
      if(sparam == "ORDER_TYPE_GROUP_SELL_LIMIT"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_LIMIT", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Bid() + (400 * Point()), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits()));
         
         
         ChartRedraw();        
      }          
   }
}

Der Eröffnungspreis liegt über dem aktuellen Marktniveau. Der Stop-Loss ist sogar noch höher, während der Take-Profit niedriger ist, was der typischen Struktur von Sell-Limit-Orders entspricht.

Ein Kauf-Stopp-Auftrag wird über dem Markt platziert, in der Erwartung, dass der Kurs weiter steigt.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the buy stop order
      if(sparam == "ORDER_TYPE_GROUP_BUY_STOP"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_STOP", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Ask(), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (600 * Point()), Digits()));
      
         
         ChartRedraw();        
      }         
   }
}

Hier wird die Eröffnung leicht über dem Ask-Preis platziert. Der Stop-Loss wird in der Nähe des aktuellen Ask gesetzt, während der Take-Profit höher platziert wird, um eine mögliche Aufwärtsbewegung zu erfassen.

Der Verkaufsstopp wird unterhalb des aktuellen Kurses gesetzt, in der Erwartung, dass der Kurs weiter fällt.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- To select the sell stop order
      if(sparam == "ORDER_TYPE_GROUP_SELL_STOP"){     
         bool   state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE);
         string text  = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_STOP", OBJPROP_TEXT);
         
         ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
         
         DESTROY_ORDER_TYPE_DROPDOWN();
         
         if(state == true){
            ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false);
         }
         
         //--- Set reasonable values to start with
         ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits()));
         ObjectSetString(0, FIELD_STOP_LOSS,   OBJPROP_TEXT, DoubleToString(Bid(), Digits()));
         ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (600 * Point()), Digits()));
         
         ChartRedraw();        
      }          
   }
}

Dieser Auftrag setzt einen Ausbruch nach unten voraus. Der Einstieg liegt unter dem Bid-Preis, der Stop-Loss wird höher gesetzt und der Take-Profit viel niedriger, um eine mögliche Fortsetzung des Abwärtstrends aufzufangen.

Zu diesem Zeitpunkt ist jeder Auftragstyp in der Dropdown-Liste interaktiv und verhält sich wie erwartet. Wenn ein Händler auf eines dieser Symbole klickt, reagiert die Schnittstelle sofort:

  • Der gewählte Typ erscheint auf der Hauptschaltfläche der Auftragsarten.
  • Die Auswahlliste verschwindet.
  • Standardpreise für Eröffnung, Stop-Loss und Take-Profit werden automatisch ausgefüllt.

So entsteht eine dynamische, nutzerfreundliche Handelsoberfläche, auf der der Händler intuitiv verschiedene Ordertypen erkunden kann – ohne jedes Mal manuell Werte eingeben zu müssen.

Fügen wir nun Interaktivität zu unseren Schaltflächen hinzu, sodass der EA, wenn ein Nutzer auf die Ausführungssteuerung klickt, die Losgröße berechnet und anzeigt oder den Auftrag tatsächlich an den Server sendet. Aber bevor wir die Knöpfe verbinden, brauchen wir ein paar unterstützende Teile. Diese geben unserem Code die Fähigkeit zu handeln, eine eindeutige Handels-ID und kleine Hilfsroutinen, die bestimmte Auftragsarten öffnen.

Binden wir zunächst die Standard-Handelsbibliothek ein. Wir platzieren diese Zeile unter den Makros am Anfang der Datei.

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

Diese Bibliothek enthält die Klasse CTrade und fertige Handelsmethoden. Durch die Verwendung der Bibliothek bleibt der Handelscode einfach und zuverlässig.

Als Nächstes geben wir eine Nutzereingabe für die magische Zahl des Experten an. Wir platzieren diese Eingabe nach dem Include-Abschnitt.

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber = 254700680002;

Die magische Zahl identifiziert die von diesem EA erstellten Trades eindeutig. Wenn man sie zu einer Eingabe macht, kann der Nutzer sie ändern, ohne den Quellcode zu bearbeiten.

Definieren wir nun ein paar globale Variablen. Diese gehören direkt unter den Bereich der Nutzereingabe.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
double accountBalance;
double contractSize;

Das Handels-Objekt bietet uns einfache Methoden zur Platzierung von Marktorders und schwebenden Aufträge. Die beiden anderen Variablen sind Platzhalter für Konto- und Vertragsinformationen, die wir später verwenden können.

Wir initialisieren das Handelsobjekt in OnInit, indem Sie die magische Zahl des Experten setzen. Fügen wir diese Zeile zu OnInit hinzu.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   ...
   
   //--- Set Expert Magic Number
   Trade.SetExpertMagicNumber(magicNumber);
      
   return(INIT_SUCCEEDED);
}

Wir werden nun kleine Hilfsfunktionen hinzufügen, um jede Auftragsart aufzugeben. Jeder Helfer umhüllt einen einzelnen Handelsaufruf und gibt bei Erfolg true oder bei Misserfolg false zurück. Wenn wir die Aufrufe auf diese Weise verpacken, bleibt der Hauptarbeitsablauf einfach zu lesen und zu pflegen.

Der Helfer für Market-Buy ruft die Methode Trade.Buy mit dem angegebenen Volumen, dem Eröffnungspreis, dem Stop-Loss und dem Take-Profit auf. Wenn der Aufruf fehlschlägt, werden der Fehlercode und die Informationen über das Handelsergebnis ausgedruckt, und es wird false zurückgegeben. Im Erfolgsfall wird true zurückgegeben.

//+------------------------------------------------------------------+
//| Function to open a market buy position                           |
//+------------------------------------------------------------------+
bool OPEN_MARKET_BUY(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){
      Print("Error while executing a market buy order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

Der Helfer für Market-Sell macht dasselbe mit Trade.Sell. Er behandelt Fehler auf die gleiche Weise und gibt ein boolesches Ergebnis an den Aufrufer zurück

//+------------------------------------------------------------------+
//| Function to open a market sell position                          |
//+------------------------------------------------------------------+
bool OPEN_MARKET_SELL(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){
      Print("Error while executing a market sell order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

Wir fügen auch Hilfsmittel für schwebende Aufträge hinzu Der Helfer für Kauf-Limit verwendet Trade.BuyLimit und der für Verkaufs-Limit verwendet Trade.SellLimit. Beide Hilfsprogramme akzeptieren Eröffnungspreise, Stop-Loss, Take-Profit und Losgröße. Sie melden Fehler und geben im Fehlerfall false zurück.

//+------------------------------------------------------------------+
//| Function to open a buy limit order                               |
//+------------------------------------------------------------------+
bool OPEN_BUY_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.BuyLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){
      Print("Error while executing a buy limit order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

//+------------------------------------------------------------------+
//| Function to open a sell limit order                              |
//+------------------------------------------------------------------+
bool OPEN_SELL_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.SellLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){
      Print("Error while executing a sell limit order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

Schließlich fügen wir Helfer für die Aufträge von Buy-Stop und Sell-Stop mit Trade.BuyStop und Trade.SellStop hinzu. Jeder Helfer folgt dem gleichen Muster, indem er den Handel versucht, bei Fehlschlägen Diagnoseinformationen ausgibt und ein boolesches Ergebnis zurückgibt.

//+------------------------------------------------------------------+
//| Function to open a buy stop order                                |
//+------------------------------------------------------------------+
bool OPEN_BUY_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.BuyStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){
      Print("Error while executing a buy stop order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

//+------------------------------------------------------------------+
//| Function to open a sell stop order                               |
//+------------------------------------------------------------------+
bool OPEN_SELL_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   if(!Trade.SellStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){
      Print("Error while executing a sell stop order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

Durch die Verwendung dieser kleinen Wrapper wird der Arbeitsablauf für das Senden der Aufträge vereinfacht. Später werden wir je nach ausgewählter Auftragsart die entsprechende Hilfskraft aufrufen. Alle Fehlermeldungen, die von diesen Helfern ausgegeben werden, helfen bei der Diagnose von Problemen wie unzureichendem Spielraum oder ungültigen Parametern.

Fügen wir nun Interaktivität zu unseren Schaltflächen Calculate Lot und Send Order hinzu, sodass der EA reagieren kann, wenn ein Nutzer auf sie klickt.

Ausführungstasten

Diese beiden Schaltflächen stellen die letzte Stufe unserer grafischen Schnittstelle dar. Die Erste berechnet die korrekte Losgröße auf der Grundlage eines festgelegten Risikoprozentsatzes, während die Zweite die ausgewählte Auftragsart an den Markt oder den Server sendet. Um dies zu ermöglichen, müssen wir erkennen, wann die einzelnen Schaltflächen angeklickt werden, und dann die entsprechende Aktion durchführen. 

Wir beginnen mit der Erweiterung unserer Funktion OnChartEvent-Funktion. Wie Sie wissen, wird diese Funktion automatisch aufgerufen, wenn ein Nutzer mit dem Chart interagiert. Hier behandeln wir speziell CHARTEVENT_OBJECT_CLICK, das ausgelöst wird, wenn der Nutzer auf eine Schaltfläche klickt. In diesem Ereignis prüfen wir den Objektnamen, um zu erfahren, welche Taste gedrückt wurde.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{
   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- When the lot calculation button is clicked
      if(sparam == BTN_CALC_LOT)  {

      }
      
      //--- When the send order button is clicked
      if(sparam == BTN_SEND_ORDER){

      }           
   }   
}

Wenn der Objektname mit BTN_CALC_LOT übereinstimmt, bedeutet dies, dass der Nutzer auf die Schaltfläche Calculate Lot geklickt hat. Entspricht sie BTN_SEND_ORDER, wurde die Schaltfläche Send Order angeklickt. In diesem Stadium erkennt das Ereignis nur Klicks, führt aber keine eigentliche Arbeit aus. Um jede Aktion richtig zu behandeln, definieren wir zwei Hilfsfunktionen – eine für die Losberechnung und eine für die Auftragsausführung.

Handhabung der Losgrößenberechnung

//+------------------------------------------------------------------+
//| Function to handles lot calculation process                      |
//+------------------------------------------------------------------+
void HandleLotCalculation(){

   string order_type  = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT);
   string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT);
   string stop_loss   = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT);
   string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT);
   string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT);
   accountBalance     = AccountInfoDouble(ACCOUNT_BALANCE);
   contractSize       = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   
   if(order_type == "Select Order Type"){
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!");
      ChartRedraw();
      return;
   }
   
   if(order_type == "Market Buy "){
      double stopDistance      = Ask() - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }
   
   if(order_type == "Market Sell"){
      double stopDistance      = StringToDouble(stop_loss) - Bid();
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }
   
   if(order_type == "Buy Limit  "){
      double stopDistance      = StringToDouble(entry_price) - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }
   
   if(order_type == "Sell Limit "){
      double stopDistance      = StringToDouble(stop_loss) - StringToDouble(entry_price);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }
   
   if(order_type == "Buy Stop   "){
      double stopDistance      = StringToDouble(entry_price) - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }
   
   if(order_type == "Sell Stop  "){
      double stopDistance      = StringToDouble(stop_loss) - StringToDouble(entry_price);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      ChartRedraw();
   }   
}

Die Funktion HandleLotCalculation verwaltet alles, was mit der Berechnung der richtigen Losgröße zusammenhängt. Wenn diese Funktion aufgerufen wird, ruft sie zunächst alle relevanten Daten aus den GUI-Feldern ab – Auftragsart, Eröffnungspreis, Stop-Loss, Take-Profit und Risikoprozentsatz. Es liest auch den aktuellen Kontostand und die Kontraktgröße aus dem Handelsumfeld ab.

Die erste Prüfung stellt sicher, dass der Nutzer eine Auftragsart ausgewählt hat. Ist dies nicht der Fall, erscheint eine freundliche Meldung im Charts, die den Nutzer auffordert, eine Auftragsart auszuwählen, bevor er fortfährt. Dadurch werden unnötige Berechnungen vermieden.

Sobald ein gültiger Auftragstyp bestätigt wurde, berechnet die Funktion die Losgröße unterschiedlich, je nachdem, ob es sich um einen Marktkauf, Marktverkauf, Kauflimit, Verkaufslimit, Kaufstopp oder Verkaufsstopp handelt. In jedem Fall wird der Abstand zwischen dem Eröffnungspreis und dem Stop-Loss berechnet, um das Risiko in Bezug auf den Kurs zu messen.

Anschließend wird der Risikobetrag berechnet, indem der Risikoprozentsatz mit dem Kontostand multipliziert wird. Dividiert man diesen Risikobetrag durch das Produkt aus Kontraktgröße und Stoppabstand, erhält man die richtige Losgröße für das festgelegte Risiko. Das Ergebnis wird auf zwei Dezimalstellen normalisiert und dann übersichtlich auf dem Ergebnisfeld in der GUI angezeigt.

Auf diese Weise können Händler sofort sehen, welche Losgröße erforderlich ist, um z. B. 2 % oder 3 % ihres Guthabens zu riskieren, ohne manuell rechnen zu müssen.

Abwicklung der Auftragsdurchführung

//+------------------------------------------------------------------+
//| Function to handles order execution process.                     |
//+------------------------------------------------------------------+
void HandleOrderExecution(){

   string order_type  = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT);
   string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT);
   string stop_loss   = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT);
   string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT);
   string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT);
   accountBalance     = AccountInfoDouble(ACCOUNT_BALANCE);
   contractSize       = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   
   if(order_type == "Select Order Type"){
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!");
      ChartRedraw();
      return;
   }
   
   if(order_type == "Market Buy "){
      double stopDistance      = Ask() - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_MARKET_BUY(Ask(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
   
   if(order_type == "Market Sell"){
      double stopDistance      = StringToDouble(stop_loss) - Bid();
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_MARKET_SELL(Bid(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
   
   if(order_type == "Buy Limit  "){
      double stopDistance      = StringToDouble(entry_price) - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_BUY_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
   
   if(order_type == "Sell Limit "){
      double stopDistance      = StringToDouble(stop_loss) - StringToDouble(entry_price);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_SELL_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
   
   if(order_type == "Buy Stop   "){
      double stopDistance      = StringToDouble(entry_price) - StringToDouble(stop_loss);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_BUY_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
   
   if(order_type == "Sell Stop  "){
      double stopDistance      = StringToDouble(stop_loss) - StringToDouble(entry_price);
      double riskPercentValue  = StringToDouble(riskPercent);
      double amountAtRisk      = (riskPercentValue / 100.0) * accountBalance;
      double lotSize           = amountAtRisk / (contractSize * stopDistance);
      lotSize                  = lotSize             = NormalizeDouble(lotSize, 2);
      ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2));
      OPEN_SELL_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize);
      ChartRedraw();
   }
}

Die zweite Hilfsfunktion, HandleOrderExecution, führt die gleichen Schritte wie die vorherige aus, geht aber einen Schritt weiter – sie platziert den eigentlichen Handel. Sobald die Funktion die Auftragsart bestätigt und die korrekte Losgröße berechnet hat, ruft sie die entsprechende, zuvor definierte Handelsausführungsfunktion auf (wie OPEN_MARKET_BUY(), OPEN_SELL_LIMIT() usw.).

Genau wie bei der Losberechnung wird auch hier geprüft, ob eine Auftragsart ausgewählt wurde. Ist dies nicht der Fall, wird der Nutzer durch eine Meldung aufgefordert, eine Entscheidung zu treffen. Danach werden der Stopp-Abstand, der Risikobetrag und die endgültige Losgröße auf die gleiche Weise berechnet. Der Hauptunterschied besteht darin, dass nach der Bestimmung der Losgröße die entsprechende Hilfsfunktion verwendet wird, um den Auftrag an den Handelsserver zu senden.

Jede Hilfsfunktion behandelt den jeweiligen Auftragstyp und gibt nützliche Meldungen in der Registerkarte „Experten" aus, falls ein Fehler auftritt – z. B. ungültige Preise oder eine unzureichende Marge. Nachdem der Handel platziert wurde, aktualisiert die Funktion den Chart, um das Ergebnis und die verwendete Losgröße anzuzeigen.

Schließlich rufen wir diese beiden Funktionen in unserem Event-Handler auf. In der Funktion OnChartEvent prüfen wir einfach, welche Schaltfläche angeklickt wurde, und rufen den entsprechenden Handler auf:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{
   //--- Handle click events
   if(id == CHARTEVENT_OBJECT_CLICK){
      
      ...
      
      //--- When the lot calculation button is clicked
      if(sparam == BTN_CALC_LOT)  {
         HandleLotCalculation();
      }
      
      //--- When the send order button is clicked
      if(sparam == BTN_SEND_ORDER){
         HandleOrderExecution();
      }           
   }   
}

Dadurch wird die Schnittstelle vollständig interaktiv. Wenn Sie auf die Schaltfläche Lot berechnen klicken, berechnet der EA sofort die richtige Losgröße und zeigt sie an. Wenn Sie auf die Schaltfläche Auftrag senden klicken, wird der entsprechende Handel mit den berechneten Parametern eröffnet.

Dieser Ansatz macht die grafische Nutzeroberfläche nicht nur visuell vollständig, sondern auch funktional und praktisch. Der Händler kann nun ein risikobasiertes Handelsmanagement direkt vom Chart aus durchführen, ohne Befehle eingeben oder zwischen verschiedenen Panels wechseln zu müssen.



Schlussfolgerung

Wir sind nun am Ende von Teil 2 angelangt, womit unser Projekt abgeschlossen ist. In diesem zweiten und letzten Teil haben wir unsere GUI von einer statischen zu einer vollständig interaktiven und funktionalen Oberfläche gemacht. Wir haben jede Schaltfläche, jedes Dropdown-Feld und jedes Eingabefeld mit echten Handelsaktionen verknüpft, sodass unser EA risikobasierte Losgrößen berechnen und Trades direkt aus dem Chart heraus ausführen kann.

Auf diesem Weg haben Sie nicht nur gelernt, wie man eine optisch ansprechende Oberfläche gestaltet, sondern auch, wie man sie mithilfe von OnChartEvent intelligent auf Nutzeraktionen reagieren lässt. Sie wissen nun, wie Sie Hilfsfunktionen für eine saubere und modulare Handelsausführungslogik strukturieren können – etwas, das das Rückgrat eines jeden professionellen Expert Advisors bildet.

Mit diesem neu erworbenen Wissen können Sie das, was wir hier aufgebaut haben, erweitern, um noch anspruchsvollere und funktionsreichere GUIs zu erstellen – von fortgeschrittenen Auftragspanels bis hin zu vollwertigen Dashboards für das Handelsmanagement. Das Fundament ist bereits gelegt, nur Ihre Kreativität setzt die Grenzen.

Und schließlich haben wir als Bonus den vollständigen Quellcode dieses Projekts beigefügt, damit Sie es erforschen, verändern und an Ihre eigenen Handelsideen anpassen können. Unabhängig davon, ob Sie Tools für den persönlichen Handel oder für Kunden entwickeln, bietet Ihnen dieses Projekt einen leistungsstarken Ausgangspunkt für die Entwicklung interaktiver, risikobewusster Handelsschnittstellen, die vollständig in MQL5 enthalten sind.

Dies ist der Abschluss unserer Serie über den Aufbau eines risikobasierten Expert Advisors für die Handelsplatzierung mit einem On-Chart-Kontrollfeld – ein praktisches Tool, das Sie sofort einsetzen können, und ein großer Schritt nach vorn bei der Beherrschung der MQL5-GUI-Entwicklung.

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

Beigefügte Dateien |
SmartRiskTrader.mq5 (37.92 KB)
Der MQL5 Standard Library Explorer (Teil 3): Experte für den Kanal der Standardabweichung Der MQL5 Standard Library Explorer (Teil 3): Experte für den Kanal der Standardabweichung
In dieser Diskussion werden wir einen Expert Advisor entwickeln, der die Klassen CTrade und CStdDevChannel verwendet und dabei mehrere Filter zur Verbesserung der Rentabilität anwendet. In dieser Phase wird unsere vorherige Diskussion in die Praxis umgesetzt. Außerdem werde ich einen weiteren einfachen Ansatz vorstellen, der Ihnen helfen soll, die MQL5-Standardbibliothek und die ihr zugrunde liegende Codebasis besser zu verstehen. Nehmen Sie an der Diskussion teil, um diese Konzepte in der Praxis zu erkunden.
Automatisieren von Handelsstrategien in MQL5 (Teil 39): Statistische Rückkehr zum Mittelwert mit Konfidenzintervallen und Dashboard Automatisieren von Handelsstrategien in MQL5 (Teil 39): Statistische Rückkehr zum Mittelwert mit Konfidenzintervallen und Dashboard
In diesem Artikel entwickeln wir einen MQL5 Expert Advisor für den Handel von einer Rückkehr zum Mittelwert, der statistischen Momente wie Mittelwert, Varianz, Schiefe, Kurtosis und dem Jarque-Bera-Test über einen bestimmten Zeitraum, um nicht-normale Verteilungen zu identifizieren und Kauf- bzw.Verkaufssignale auf der Grundlage von Konfidenzintervallen mit adaptiven Schwellenwerten zu erzeugen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Entwicklung des Price Action Analysis Toolkit (Teil 49): Integration von Trend-, Momentum- und Volatilitätsindikatoren in ein MQL5-System Entwicklung des Price Action Analysis Toolkit (Teil 49): Integration von Trend-, Momentum- und Volatilitätsindikatoren in ein MQL5-System
Vereinfachen Sie Ihre MetaTrader 5 Charts mit dem Multi Indicator Handler EA. Dieses interaktive Dashboard fasst Trend-, Momentum- und Volatilitätsindikatoren in einem Echtzeit-Panel zusammen. Wechseln Sie im Handumdrehen zwischen den Profilen und konzentrieren Sie sich auf die Analyse, die Sie am meisten benötigen. Mit den Ein-Klick-Steuerelementen zum Ausblenden/Einblenden können Sie sich auf die Kursentwicklung konzentrieren. Lesen Sie weiter, um Schritt für Schritt zu erfahren, wie Sie es in MQL5 selbst erstellen und anpassen können.