Die Erstellung eines Helfers im manuellen Handeln

Dmitriy Gizlyk | 11 Juli, 2016

Einführung

In diesem Artikel werde ich ein weiteres Beispiel der Erstellung eines vollwertigen Handelspanels darstellen, welches den Anhänger des manuellen Tradings während der Arbeit in Forex hilft.

1. Bestimmen wir die gewünschte Funktionalität für das Handelspanel

Zunächst müssen wir für uns selbst definieren, welches endgültiges Endergebnis wir erhalten möchten. Wir müssen entscheiden, welche Funktionalität wir von unserem Panel erwarten und welches Design für uns bequem sein wird. In diesem Artikel schlage ich meine Sicht auf das Handelspanels vor, aber würde auch gerne Ihre Vorschläge betrachten. Hoffentlich werden sie im Detail in meinen neuen Artikeln diskutiert. 

So sollte unser Panel sicherlich die folgenden Elemente haben.

  1. Die Tasten Kauf und Verkauf.
  2. Die Tasten der Schließung aller Positionen nach Symbolen und Konto oder einer separaten Richtung (die Order für einen Kauf oder Verkauf).
  3. Die Möglichkeit, die Ebene vom Stop-Loss und Take-Profit zu zeigen, sowie in Punkten auch in der Währung des Deposits(bei der Eingabe eines Parameters muss der andere Parameter automatisch korrigiert werden).
  4. Das Panel sollte automatisch die Ebene von Stop-Loss und Take-Profit bei manuell eingestellten Parametern (P. 2) berechnen und sie im Chart anzeigen.
  5. Ein Händler sollte in der Lage sein, die Ebene von Stop-Loss und / oder Take-Profit auf dem Chart zu bewegen. Dabei müssen alle Änderungen im Panel mit den Änderungen der entsprechenden Werte angezeigt werden.
  6. Das Panel sollte den kommenden Volumen der Transaktionen nach den eingegebenen Risikoparametern (in der Anlagewährung oder im Prozentsatz des aktuellen Saldo) berechnen.
  7. Der Händler muss in der Lage sein, selbständig die Höhe der Transaktion festzulegen. Zu gleicher Zeit müssen die entsprechenden Parameter, die von ihm abhängig sind, automatisch neuberechnet werden.
  8. Das Panel muss speichern, welche Parameter vom Händler eingegeben wurden, und welche - automatisch berechnet wurden. Es ist notwendig, um die vom Händler eingegebene Parameter sich bei den nächsten Neuberechnungen möglichst nicht ändern.
  9. Das Panel muss alle eingegebene Parameter speichern, um sie bei einem Neustart nicht wieder einzugeben.

2. Erstellen wir ein grafisches Muster des Panels

Nehmen wir ein leeres Blatt und zeichnen unser künftiges Handelspanel, platzieren alle notwendigen Elemente.

Beim Entwurf des künftigen Designs des Handelspanels denken Sie auch über die Zweckmäßigkeit seiner Anwendung. Zunächst einmal sollte es recht informativ sein, aber dennoch einfach zu lesen, und nicht mit unnötigen Elementen überlastet sein. Wir müssen immer daran denken, dass es ein Werkzeug für die eigentliche Arbeit des Händlers ist, und nicht nur ein schönes Bild auf dem Bildschirm.

Hier ist meine Variante.

Design

3. Der Aufbau des Panelsmusters in der MQL5 Sprache

3.1.  Vorwurf

Nun, da wir eine Vorstellung vor dem endgültigen Ziel haben, realisieren wir es im MQL5-Code. Dafür werden wir möglichst die Standard-Bibliotheken verwenden, die unsere Arbeit erleichtern können. In MQL5 gibt die Klasse CAppDialog, die die Basis für den Aufbau der Dialogfelder ist. Auf der Grundlage dieser Klasse bauen wir unser Panel. 
Dazu erstellen wir ein Duplikat der Klasse und initialisieren es in der Funktion OnInit().

#include <Controls\Dialog.mqh>

class CTradePanel : public CAppDialog
  {
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  };

CTradePanel TradePanel;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   // Create Trade Panel
   if(!TradePanel.Create(ChartID(),"Trade Panel",0,20,20,320,420))
     {
      return (INIT_FAILED);
     }
   // Run Trade Panel
   TradePanel.Run();
//---
   return(INIT_SUCCEEDED);
  }

Das Ergebnis dieser einfachen Manipulation - ist der Vorwurf unseres nächsten Panels.

Vorwurf

3.2. Deklarieren alle notwendigen Objekte

Nun zeichnen wir die notwendigen Steuerelemente auf unseren Vorwurf. Dazu erstellen wir die Objekte der Klassen für jedes Steuerelement. Die Erstellung der Objekte werden wir durch Standardklassen CLabel, CEdit, CButton und CBmpButton durchführen.

Fügen wir die notwendigen Include-Dateien hinzu und erstellen die Funktion Create() für die Klasse CTradePanel:

#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

Die Dateien "Edit.mqh" und "BmpButton.mqh" habe ich bewusst nicht hinzufügt, denn sie werden bereits aus "Dialog.mqh" aufgerufen.

Der nächste Schritt - für jedes Objekt auf dem Panel in der Klassen CTradePanel deklarieren die Variablen des entsprechenden Typs, und deklarieren da die gleiche Prozedur Create(..), in der alle Elemente auf ihren Plätzen platzieren. Bitte beachten Sie: Die Deklaration der Variablen und andere Maßnahmen im Rahmen der Klasse CTradePanel wird im Block "private" deklariert. Allerdings werden die verfügbaren Funktionen für den Aufruf aus jenseits der Klasse, wie Create(...), im Block "public" deklariert.

class CTradePanel : public CAppDialog
  {
private:

   CLabel            ASK, BID;                        // Display Ask and Bid prices
   CLabel            Balance_label;                   // Display label "Account Balance"
   CLabel            Balance_value;                   // Display Account balance
   CLabel            Equity_label;                    // Display label "Account Equity"
   CLabel            Equity_value;                    // Display Account Equity
   CLabel            PIPs;                            // Display label "Pips"
   CLabel            Currency;                        // Display Account currency
   CLabel            ShowLevels;                      // Display label "Show"
   CLabel            StopLoss;                        // Display label "Stop Loss"
   CLabel            TakeProfit;                      // Display label "TakeProfit"
   CLabel            Risk;                            // Display label "Risk"
   CLabel            Equity;                          // Display label "% to Equity"
   CLabel            Currency2;                       // Display Account currency
   CLabel            Orders;                          // Display label "Opened Orders"
   CLabel            Buy_Lots_label;                  // Display label "Buy Lots"
   CLabel            Buy_Lots_value;                  // Display Buy Lots value 
   CLabel            Sell_Lots_label;                 // Display label "Sell Lots"
   CLabel            Sell_Lots_value;                 // Display Sell Lots value 
   CLabel            Buy_profit_label;                // Display label "Buy Profit"
   CLabel            Buy_profit_value;                // Display Buy Profit value 
   CLabel            Sell_profit_label;               // Display label "Sell Profit"
   CLabel            Sell_profit_value;               // Display Sell profit value 
   CEdit             Lots;                            // Display volume of next order
   CEdit             StopLoss_pips;                   // Display Stop loss in pips
   CEdit             StopLoss_money;                  // Display Stop loss in account currency
   CEdit             TakeProfit_pips;                 // Display Take profit in pips
   CEdit             TakeProfit_money;                // Display Take profit in account currency
   CEdit             Risk_percent;                    // Display Risk percent to equity
   CEdit             Risk_money;                      // Display Risk in account currency
   CBmpButton        StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton        TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton        StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton        StopLoss_money_b;                // Select Stop loss in account currency
   CBmpButton        TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton        TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton        Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton        Risk_money_b;                    // Select Risk in account currency
   CBmpButton        Increase,Decrease;               // Increase and Decrease buttons
   CButton           SELL,BUY;                        // Sell and Buy Buttons
   CButton           CloseSell,CloseBuy,CloseAll;     // Close buttons
   
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  //--- Create function
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   
  };

3.3. Die Erstellung der Initialisierungsverfahren für Gruppen der Objekten

Es ist die Zeit, den Körper der Funktion Create(...) zu registrieren. Bitte beachten Sie, dass wir alle oben deklarierten Objekte in dieser Funktion initialisieren müssen. Es ist leicht zu berechnen, dass wir oben 45 Objekte von 4 Arten deklariert haben. So ist es ratsam, 4 Verfahrene der Objekte-Initialisierung zu registrieren, eine für jeden Typ.  Die Initialisierungsfunktion der Klassen deklarieren wir im Block "private".

Natürlich war es möglich, Objekte in einem Array zu deklarieren, aber in diesem Fall hätten wir die Verbindung zwischen dem Variablennamen des Objekts  und seiner Funktionalität verloren, was in Folge die Arbeit mit Objekten erschwert. Von daher wurde die Wahl in Richtung der Transparenz des Codes gemacht und der Leichtigkeit der Arbeit mit ihm (Lass uns keine Probleme schaffen, um sie danach heroisch zu überwinden).

Die Klasse CLabel

Die Klasse CLabel wird für die Anzeige des informativen Textes auf unserem Panel verwendet. Bei der Erstellung einer Funktion muss es bestimmt werden, welche Funktionen für alle Elementen dieser Klasse einheitlich sein werden, und welche, und wo anders sein werden. In diesem Fall sehen die Unterschiede so aus:

Wenn wir die Unterschiede herausgefunden haben, bestimmen wir, welche von ihnen durch Funktionsparameter übergeben werden müssen, um sie universal zu machen, und welche Funktionen im eigentlichen Prozess erzeugt werden können.

Bei der Arbeit mit Objekten soll man daran denken, dass alle Chartsobjekte die separaten Namen haben müssen. Wie immer trifft die Entscheidung der Programmierer: ob die einzelnen Objektnamen selbst gegeben werden, oder durch das Programm erzeugt. Bei der Erstellung der universellen Funktion habe ich gewählt, die Namen von Objekten innerhalb des Programms zu erzeugen. Deshalb habe ich den Objektnamen für die Klasse mir Hinzufügen der Sequenznummer identifiziert.

string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);

Der angezeigte Text, die Objektkoordinaten und die Objektausrichtung nach dem Ankerpunkt werden in die Funktion mit Hilfe von Parametern übergeben. Wir werden Aufzählungen für die Objektausrichtung erstellen, die das Lesens des Codes und die Arbeit des Programmierers erleichtert:

  enum label_align
     {
      left=-1,
      right=1,
      center=0
     };

Außerdem müssen wir den Code des Charts angeben, die Nummer des Unterfensters und einen Link auf das erstellte Objekt.

Innerhalb der Funktion schreiben wir die Verfahren, die mit jedem Objekt dieser Klasse durchgeführt werden müssen.

bool CTradePanel::CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align)
  {
   // All objects must have separate name
   string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,0,0))
     {
      return false;
     }
   //--- Adjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Align text to Dialog box's grid
   ObjectSetInteger(chart,object.Name(),OBJPROP_ANCHOR,(align==left ? ANCHOR_LEFT_UPPER : (align==right ? ANCHOR_RIGHT_UPPER : ANCHOR_UPPER)));
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Die Klasse CButton

Die Klasse CButton richtet sich für die Erstellung der rechteckigen Tasten mit einem Schreiben. Dies sind unsere Standard-Tasten zur Öffnung und Schließung der Orders.

Am Anfang der Arbeit mit dieser Klasse von Objekten verwenden wir den gleichen Ansatz, wie im vorherigen Fall. Dabei sollten Besonderheiten seiner Arbeit in Betracht gezogen werden. Zunächst einmal gibt es keine Notwendigkeit, um den Text in der Taste auszurichten, da es in der Elterklasse die zentrierte Ausrichtung hat. Aber da erscheint schon die Tastengröße, die wir bereits in den Parameter übertragen werden.

Außerdem erscheint bei der Taste ihr aktuelle Zustand: gedrückt oder nicht gedrückt. Außerdem kann eine gedrückte Taste verriegelt werden oder nicht. Von daher müssen wir diesen zusätzlichen Optionen im Verfahren der Objektsinitialisierung beschreiben. Wir werden für unsere Tasten die Verriegelung (LockDown) deaktivieren und setzen es im Zustand "Depressive" fest.

bool CTradePanel::CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must have separate name
   string name=m_name+"Button"+(string)ObjectsTotal(chart,-1,OBJ_BUTTON);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Adjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- set button flag to unlock
   object.Locking(false);
   //--- set button flag to unpressed
   if(!object.Pressed(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Die Klasse CEdit

Die Klasse CEdit ist für die Erstellung der Objekte vorgesehen, die Daten eingeben. Als solche Objekte gelten bei uns die Eingabe-Zellen für Volumen der Transaktionen, die Ebene Stop Loss und Take Profit (in Punkten und in der Währung des Deposits) und die Ebene des Risikos.

Verwenden wir den gleichen Ansatz, wie bei zwei vorher beschriebenen Klassen. Aber im Gegensatz mit Tasten, im Prozess der Initialisierung dieser Klasse muss es angegeben werden, wie der Text in einer Zelle ausgerichtet werden muss. Man muss sich noch daran erinnern, dass alle Informationen, die in eine Zelle übertragen werden oder weitergegeben, werden immer als Text angenommen. Deswegen, wenn Zahlen für die Anzeige einem Objekt gegeben werden, sollten sie zunächst um Text gewandelt werden.

Die Objekte von der Klasse CEdit, im Gegensatz zu Tasten, haben keine "Pressed" / "Depressive", aber zugleich ist von dieser Klasse die Erstellung der Objekte vorgesehen, die einem Benutzer für die Bearbeitung während der Arbeit des Programms nicht verfügbar ist. In unserem Fall sollten alle Objekte einem Benutzer verfügbar sein, um sie zu bearbeiten. Wir zeigen dies in unserer Funktion der Initialisierung. 

bool CTradePanel::CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must have separate name
   string name=m_name+"Edit"+(string)ObjectsTotal(chart,-1,OBJ_EDIT);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Adjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Align text in Edit box
   if(!object.TextAlign(ALIGN_CENTER))
     {
      return false;
     }
   //--- set Read only flag to false
   if(!object.ReadOnly(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Die Klasse CBmpButton

Die Klasse CBmpButton ist für die Erstellung der nicht standarten Tasten mit der Verwendung der grafischen Objekte statt Schreibens vorgesehen. Diese Tasten, die intuitive jedem Benutzer klar sind, verwendet man bei der Erstellung der standardisierten Elemente für eine Vielzahl von Anwendungen. In unserem Fall erstellen wir durch diese Klasse:

Die Arbeit mit diesen Klasse-Objekten ist ähnlich mit der CButton-Klasse. Der Unterschied liegt an der Übertragung der grafischen Objekte für den Zustand der gedrückten und nicht gedrückten Taste statt eines Textes. Für unser Panel verwenden wir die Tasten, die mit Bildern zusammen mit MQL5 geliefert wurden. Dabei, um das fertige Softwareprodukt mit einer Datei zu verteilen, verschreiben diese Bilder als Ressourcen.

#resource "\\Include\\Controls\\res\\RadioButtonOn.bmp"
#resource "\\Include\\Controls\\res\\RadioButtonOff.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOn.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOff.bmp"
#resource "\\Include\\Controls\\res\\SpinInc.bmp"
#resource "\\Include\\Controls\\res\\SpinDec.bmp"

Beachten Sie auch, dass alle Elemente dieser Klasse lackiert werden (dh sie behalten ihren Zustand "gedrückt" oder "nicht gedrückt"), außer der Tasten, der Erhöhung und der Verringerung des Lots. Deshalb fügen wir zu der Initialisierungsfunktion die zusätzlichen Parameter hinzu.

//+------------------------------------------------------------------+
//| Create BMP Button                                                |
//+------------------------------------------------------------------+
bool CTradePanel::CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock)
  {
   // All objects must have separate name
   string name=m_name+"BmpButton"+(string)ObjectsTotal(chart,-1,OBJ_BITMAP_LABEL);
   //--- Calculate coordinates
   uint y1=(uint)(y-(Y_STEP-CONTROLS_BUTTON_SIZE)/2);
   uint y2=y1+CONTROLS_BUTTON_SIZE;
   //--- Call Create function
   if(!object.Create(m_chart_id,name,m_subwin,x-CONTROLS_BUTTON_SIZE,y1,x,y2))
      return(false);
   //--- Assign BMP pictures to button status
   if(!object.BmpNames(BmpOFF,BmpON))
      return(false);
   //--- Add object to controls
   if(!Add(object))
      return(false);
   //--- set Lock flag to true
   object.Locking(lock);
//--- succeeded
   return(true);
  }

Nach dem Verschreiben der Funktionen der Objekte-Erstellung, deklarieren wir zwangsweise diese Funktionen im Block "privat" in unserer Klasse.

private:

   //--- Create Label object
   bool              CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align);
   //--- Create Button
   bool              CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Create Edit object
   bool              CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Create BMP Button
   bool              CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock);

3.4. Platzieren wir alle Elemente zu ihren Plätzen

Wenn wir nun die Initialisierungsfunktion für jede Objektklasse geschrieben haben, dann ist es die Zeit, eine auch für unser Handelspanel zu schreiben. Die Hauptziele dieser Funktion - die Berechnung der Koordinaten von jedem der Objekte des Panels und eine schrittweise Erstellung der Objekte, indem die entsprechende Initialisierung-Funktion aufgerufen wird.

Erinnern wir uns noch mal daran, dass die Elemente am Panel für den Benutzer bequem positioniert werden muss, und dabei soll es ästhetisch aussehen. Wir haben schon Beachtung dieser Frage bei der Erstellung unseres Layouts-Panels geschenkt, und jetzt werden wir an diesem Konzept bleiben. Zugleich sollte es verstanden werden, dass die Größe unseres Panels bei der Verwendung unserer Klasse im endgültigen Programm unterschiedlich sein kann. Und damit das Konzept unserer Design bei der Änderung des Handelspanels gleich bleibt, müssen wir die Koordinaten jedes Objekts berechnen, und sind nicht explizit angeben. Zu diesem Zweck erstellen wir solche Leuchtfeuer:

   #define  Y_STEP   (int)(ClientAreaHeight()/18/4)      // height step between elements
   #define  Y_WIDTH  (int)(ClientAreaHeight()/18)        // height of element
   #define  BORDER   (int)(ClientAreaHeight()/24)        // distance between border and elements

So können wir die Koordinaten des ersten Steuerelementes und jede nachfolgende relativ zu dem vorherigen berechnen.
Außerdem bestimmen wir die optimale Größe unseres Panels, wir können sie als die Standardwerte für die Parameter angeben, die an einer Funktion übergeben werden.

bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
      // At first call create function of parents class
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
     {
      return false;
     }
   // Calculate coordinates and size of BID object
   // Coordinates are calculated in dialog box, not in chart
   int l_x_left=BORDER;
   int l_y=BORDER;
   int y_width=Y_WIDTH;
   int y_sptep=Y_STEP;
   // Create object
   if(!CreateLabel(chart,subwin,BID,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits),l_x_left,l_y,left))
     {
      return false;
     }
   // Adjust font size for object
   if(!BID.FontSize(Y_WIDTH))
     {
      return false;
     }
   // Repeat same functions for other objects
   int l_x_right=ClientAreaWidth()-20;
   if(!CreateLabel(chart,subwin,ASK,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits),l_x_right,l_y,right))
     {
      return false;
     }
   if(!ASK.FontSize(Y_WIDTH))
     {
      return false;
     }
   l_y+=2*Y_WIDTH;
...................
  }

Den vollständigen Funktionscode kann man im beigefügten Beispiel finden.

Das Ergebnis unserer Arbeit ist das folgende Panel.

Bedienfeld

Aber erstmal ist es nur ein Muster - ein schönes Bild am Chart. Im nächsten Schritt verwirklichen wir ihn ins Leben.

4. "Die Verwirklichung des Bildes"

Nun, da wir ein grafisches Muster unseres Handelspanels erstellt haben, ist es die Zeit, bei ihm Reaktionen auf aktuelle Ereignisse zu entwickeln. Von daher, um einen Ereignis-Verarbeiter zu erstellen und einzustellen, muss man entscheiden, Auf welche Ereignisse und wie er reagieren soll.

4.1. Die Änderung des Werkzeugpreises

Bei Änderung des Werkzeugpreises im Terminal MT5 wird das Ereignis NewTick generiert, das die Funktion OnTick() des Expertes aufruft. Von Daher, müssen wir aus dieser Funktion die entsprechende Funktion unserer Klasse aufrufen, die das Ereignis verarbeiten wird. Geben wir ihr einen ähnlichen Namen onTick() und deklarieren sie im Block "public", denn sie wird von einem externen Programm aufgerufen.

public:

   virtual void      OnTick(void);
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   TradePanel.OnTick();
  }
Welche Änderungen im Panel bei der Abwechslung des Werkzeugpreises auftreten? Das erste, was wir tun müssen - ist es, die Werte von Ask und Bid auf unserem Panel zu ändern.
//+------------------------------------------------------------------+
//| Event "New Tick"                                                  |
//+------------------------------------------------------------------+
void CTradePanel::OnTick(void)
  {
   //--- Change Ask and Bid prices on panel
   ASK.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));
   BID.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));

Und dann, falls es die offenen Positionen auf dem Konto gibt, ändern Sie den Wert der Mittel (Equity) auf dem Panel. Als Bestätigung, fügte ich eine Prüfung der Übereinstimmung der Mittel hinzu, welche die tatsächliche Höhe der Mittel auf dem Konto darstellt, auch wenn es keine offenen Positionen gibt. Dies ermöglicht die tatsächliche Menge der Mittel nach "Notfallsituationen" anzuzeigen. So brauchen wir nicht das Vorhandensein der offenen Positionen zu überprüfen: wir überprüfen direkt die Übereinstimmung der aktuellen Höhe der Mittel auf dem Konto und der angezeigten Höhe auf dem Panel. Bei Bedarf liefern den wahren Wert auf das Panel.

//--- Check and change (if necessary) equity
   if(Equity_value.Text()!=DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
     {
      Equity_value.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
     }
  

Eine ähnliche Iteration tun wir für die Anzeige der Balance.
Ich versuche die nächste Frage zu erraten: "Warum soll man die Balance auf jedem Tick überprüfen, denn es ändert sich nur bei Handelsoperatonen?" Ja, es ist so, und ein wenig später werden wir über die Reaktionen auf die Handelsereignisse sprechen. Aber es gibt eine geringe Wahrscheinlichkeit, dass die Handelsoperationen in den Momenten durchgeführt werden, wenn unser Panel nicht läuft oder es gibt keine Terminal-Server-Verbindung. Eben deswegen, damit auf dem Panel immer die aktuelle Balance angezeigt würde, auch nach Notfallsituationen, habe ich diese Operation hinzugefügt.

Der nächste Schritt bei der Änderung des Preises, wir überprüfen die Verfügbarkeit der offenen Positionen auf dem aktuellen Werkzeug, und falls es zur Verfügung steht, wir überprüfen und korrigieren den Wert des offenen Volumens und des aktuellen Profits auf Positionen in Buy oder Sell.

//--- Check and change (if necessary) Buy and Sell lots and profit value.
   if(PositionSelect(_Symbol))
     {
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           Buy_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Buy_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Buy_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Sell_Lots_value.Text()!=DoubleToString(0,2))
              {
               Sell_Lots_value.Text(DoubleToString(0,2));
              }
           break;
         case POSITION_TYPE_SELL:
           Sell_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Sell_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Sell_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Buy_Lots_value.Text()!=DoubleToString(0,2))
              {
               Buy_Lots_value.Text(DoubleToString(0,2));
              }
           break;
        }
     }
   else
     {
      if(Buy_Lots_value.Text()!=DoubleToString(0,2))
        {
         Buy_Lots_value.Text(DoubleToString(0,2));
        }
      if(Sell_Lots_value.Text()!=DoubleToString(0,2))
        {
         Sell_Lots_value.Text(DoubleToString(0,2));
        }
      if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
      if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
     }

Auch vergessen wir nicht den Zustand des Checkbox zu überprüfen, der die Ebene von Stop-Loss und Take-Profit auf dem Chart zeigt. Falls es erforderlich ist, korrigieren wir die Positionen der Linien. Fügen wir zum Code dieser Funktionen einen Aufruf. Genauere wird es unten betrachtet.

   //--- Move SL and TP lines if necessary
   if(StopLoss_line.Pressed())
     {
      UpdateSLLines();
     }
   if(TakeProfit_line.Pressed())
     {
      UpdateTPLines();
     }
   return;
  }

4.2. Fügen wir die Werte in die editierbaren Felder ein.

Auf unserem Panel gibt es eine Reihe von editierbaren Feldern, und natürlich müssen wir den Empfang und die Verarbeitung der eingegebenen Informationen einstellen.

Fügen wir die Informationen in die editierbaren Felder ein - das Änderungsereignis des grafischen Objekts, das zu einer Gruppe der Ereignisse ChartEvent gehört. Die Ereignisse dieser Gruppe werden von der Funktion OnChartEvent verarbeitet. Sie hat vier Eingangsparameter: den Ereignis-Identifikator und 3 Parameter, die das Ereignis charakterisieren, die zu Arten long, double und string gehören. Wie in vorherigen Situation werden wir einen Ereignis-Verarbeiter in unserer Klasse erstellen und wir rufen ihn aus der Funktion OnChartEvent mit der Übertragung aller Eingabeparameter aufrufen, die das Ereignis charakterisieren. Voraussichtlich gesagt, möchte ich sagen, dass von dieser Funktion auch das Ereignis des Klickens auf die Tasten des Handels-Panels verarbeitet wird. Von daher wird diese Funktion ein Operator, der nach der Analyse eines Ereignisses die Funktion der Verarbeitung eines bestimmten Ereignisses aufrufen wird. Dann wird die Informationen über das Ereignis in die Funktion der Elternklasse übertragen, um die Operationen durchzuführen, die in der Elternklasse vorgeschrieben sind.

public:

   virtual bool      OnEvent(const int id,const long &lparam, const double &dparam, const string &sparam);

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   TradePanel.OnEvent(id, lparam, dparam, sparam);
  }

Für den Aufbau eines solchen Operators verwenden wir ein Makro.

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
EVENT_MAP_END(CAppDialog)

Dementsprechend müssen wir alle Ereignisverarbeiter-Funktionen im Block "privat" unserer Klasse deklarieren

private:

   //--- On Event functions
   void              LotsEndEdit(void);                              // Edit Lot size
   void              SLPipsEndEdit(void);                            // Edit Stop Loss in pips
   void              TPPipsEndEdit(void);                            // Edit Take Profit in pips
   void              SLMoneyEndEdit(void);                           // Edit Stop Loss in money
   void              TPMoneyEndEdit(void);                           // Edit Take Profit in money
   void              RiskPercentEndEdit(void);                       // Edit Risk in percent
   void              RiskMoneyEndEdit(void);                         // Edit Risk in money

 Um die Daten zu speichern, die aus editierbaren Feldern erhalten wurden, fügen wir zusätzliche Variable im Block "private" ein

private:

   //--- variables of current values
   double            cur_lot;                         // Lot of next order
   int               cur_sl_pips;                     // Stop Loss in pips
   double            cur_sl_money;                    // Stop Loss in money
   int               cur_tp_pips;                     // Take Profit in pips
   double            cur_tp_money;                    // Take Profit in money
   double            cur_risk_percent;                // Risk in percent
   double            cur_risk_money;                  // Risk in money

Betrachten wir das Beispiel eines bestimmten Ereignisses - die Eingabe des Volumens von der geplanten Transaktion. Erinnern Sie sich, dass die Eingabe aller Informationen in solchen Feldern als Texteingabe wahrgenommen wird, unabhängig von dessen Inhalt. Im Wesentlichen, bei der Eingabe eines Textes ins Feld wird eine Reihe von Ereignissen erzeugt:  Einrichten mit der Maus auf das Objekt, Drücken auf die Maustasten, der Anfang der Korrektur Feldes, Drücken die Tasten auf der Tastatur, das Ende der Korrektur des Feldes usw Wir interessieren uns nur für das letzte Ereignis, wenn die Eingabe der Informationen schon vorbei ist. Von daher wird der Funktionsaufruf für das Ereignis "ON_END_EDIT" durchgeführt.

Das erste, was wir in den Ereignisverarbeitungsfunktionen machen müssen - den eingegebenen Text zu lesen und zu versuchen, den in den Wert double zu konvertieren.

Dann brauchen wir eine "Normalisierung" des erhaltenen Wert zu machen, das heißt, den Wert unter den Bedingungen des Handelswerkzeuges zu führen (den minimalen und maximalen Volumen einer Order, sowie den Schritt der Volumenänderung). Um diese Operation durchzuführen, schreiben wir eine separate Funktion, weil sie uns beim Drücken der Tasten Volumen-Erhöhung und Volumen-Verringerung der Transaktion gebraucht wird. Den resultierenden Wert müssen wir ins Panel zurücksetzen, um den Händler über den tatsächlichen Volumen der zukünftigen Transaktion zu informieren.

//+------------------------------------------------------------------+
//| Read lots value after edit                                       |
//+------------------------------------------------------------------+
void CTradePanel::LotsEndEdit(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

Darüber hinaus müssen wir, je nach den aktuellen Einstellungen der Radio-Taste, den Wert aller anderen editierbaren Felder auf dem Panel neu zu berechnen. Dies ist notwendig, denn bei der Änderung des Volumens der Transaktion wird auch die Höhe des Risikos bei Schließung von Stop-Loss (falls Stop-Loss in Punkten angegeben wird) oder Stop-Loss-Niveau in den Punkten geändert (Falls Stop-Loss in Form von Geld angegeben wird). Mit Stop-Loss wird auch die Höhe des Risikos mit wachsen. Ähnlich ist die Situation mit den Werten des Take-Profits. Natürlich werden diese Operationen durch die entsprechenden Funktionen organisiert.

   //--- Check and modify value of other labels 
   if(StopLoss_money_b.Pressed())
     {
      StopLossPipsByMoney();
     }
   if(TakeProfit_money_b.Pressed())
     {
      TakeProfitPipsByMoney();
     }
   if(StopLoss_pips_b.Pressed())
     {
      StopLossMoneyByPips();
     }
   if(TakeProfit_pips_b.Pressed())
     {
      TakeProfitMoneyByPips();
     }

Wenn wir ein Werkzeug für die tägliche Arbeit eines Benutzers erstellen, müssen wir immer den Begriff "Usability" (Einfachheit der Verwendung) im Auge halten. Und hier sollten wir an den Absatz 8 denken, der am Anfang der Funktionalität unseres Handelspanels beschrieben wurde: "Das Panel muss speichern, welche Parameter vom Händler eingefügt wurden, und welche - automatisch berechnet wurden. Es ist notwendig, um die vom Händler eingegebene Parameter sich bei den nächsten Neuberechnungen möglichst nicht ändern. Mit anderen Worten, in der Zukunft, bei Änderung des Stop-Loss in Punkten müssen wir uns daran erinnern, was der Händler zuletzt geändert hat - den Volumen der Transaktion oder die Höhe des Risikos. Falls es erforderlich ist, können die zuletzt eingegebenen Daten unverändert gelassen werden.
Zu diesem Zweck fügen wir die Variable RiskByValue in den Block "private" ein und in der Verarbeitungsfunktion dieses Ereignis weisen wir ihr den Wert true zu.

private:
   bool              RiskByValue;                     // Flag: Risk by Value or Value by Risk
   RiskByValue=true;
   return;
  }

Die Organisationsprinzipe der Korrektur-Funktionen im Zusammenhang mit editierbaren Feldern betrachten wir im Beispiel der Funktion StopLossMoneyByPips, weil sie die umfassendste Funktionalität hat.

1. Im Wesentlichen wird diese Funktion bei uns in drei Fällen aufgerufen: bei der Änderung der Lot, bei der Eingabe im Feld des Stop-Loss in Punkten und bei der Bewegung Linie Stop-Loss. Deshalb sollten wir als Erste den aktuellen Wert des Volumens der kommenden Transaktion überprüfen. Wenn der der Spezifikation des Werkzeugs und der Realität des Marktes nicht entspricht, dann korrigieren wir den angezeigten Wert auf dem Panel.

//+------------------------------------------------------------------+
//|  Modify SL money by Order lot and SL pips                        |
//+------------------------------------------------------------------+
void CTradePanel::StopLossMoneyByPips(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

2. Die zweite Komponente für die Berechnung des Geldwertes vom möglichen Risiko - ist die Menge aus Änderungen der Mittels, bei Preisänderungen des Werkzeugs für einen Tick bei der offenen Position von 1 Lot. Um dies zu tun, rechnen wir den Preis für einen Tick und die minimale Größe der Preiseänderungen des Werkzeugs:

   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
   double tick_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);

3. Nach erhaltenen Daten berechnen wir die potenzielle Verluste, und den erhaltenden Wert zeigen wir auf dem Panel im entsprechenden Feld.

   cur_sl_money=NormalizeDouble(tick_value*cur_lot*(tick_size/_Point)*cur_sl_pips,2);
   StopLoss_money.Text(DoubleToString(cur_sl_money,2));

4 Es sollte beachtet werden, dass die Höhe der möglichen Verluste bei einer Schließung der Order durch Stop-Loss in der Tat unser Risiko in Form von Geld ist. Deshalb müssen wir den berechneten Wert im Feld des Risikos in Form von Geld verdoppeln, und dann den relativen Risikowert (Das Risiko in Prozent) berechnen.

   cur_risk_money=cur_sl_money;    Risk_money.Text(DoubleToString(cur_risk_money,2));    cur_risk_percent=NormalizeDouble(cur_risk_money/AccountInfoDouble(ACCOUNT_BALANCE)*100,2);    Risk_percent.Text(DoubleToString(cur_risk_percent,2));
return;
}

Die Berechnungsfunktion des Stop-Loss in Punkten abgesehen vom Wert des Geldwertes ist die Umkehrung der oben genannten Funktionen, mit der Ausnahme, dass das Risiko sich nicht ändert, aber es ist notwendig, die Position der Anzeigelinien der Stop-Loss Ebene auf dem Chart zu korrigieren.

Ähnlich wird die Funktion für die Korrektur des Take-Profit-Wertes vorgeschrieben.

Ähnlich gestalten wir die Funktionen für die Verarbeitung der Korrektur-Ereignisse der andren Felder. Man muss sich dabei daran erinnern, dass wir bei der Korrektur der Felder auch den Zustand der Radio-Taste ändern müssen. Und um den Zustand der Tasten in jeder Funktion nicht zu duplizieren, rufen wir die Verarbeitungsfunktion des Druckens der entsprechenden Taste. 

4.3. Die Verarbeitung der Ereignisse des Druckens auf die Radio-Taste.

Die Radio-Taste - ist ein Interface-Element, das dem Benutzer eine Option (Punkt) aus einer vorgegebenen Menge (Gruppe) zur Auswahl gibt.
Deshalb müssen wir den Zustand der verbundenen Tasten beim Drucken einer Radio-Taste ändern. Zur gleichen Zeit wird die Umschaltung der Radio-Tasten nicht zu einer Neuberechnung der Parameter führen.
Nach dieser Weise wird die Verarbeitungsfunktion des Drucken-Ereignisses der Radio-Taste nur den Zustand der verbundenen Radio-Tasten ändern, dh die gedrückte Radio-Taste zu einem Zustand "gedrückt" führen, und andere verbundene Tasten, zum Zustand der nicht "gedrückt" führen.

Bezüglich der technischen Seite bezieht sich das Drücken der Taste zur Ereignisgruppe ChartEvent. Von daher wird die Verarbeitung genauso wie die Korrektur des editierbaren Feldes durchgeführt. Deklarieren wir die Ereignisverarbeiter-Funktionen im Block "private":

private:

   //--- On Event functions
   void              SLPipsClick();                                  // Click Stop Loss in pips
   void              TPPipsClick();                                  // Click Take Profit in pips
   void              SLMoneyClick();                                 // Click Stop Loss in money
   void              TPMoneyClick();                                 // Click Take Profit in money
   void              RiskPercentClick();                             // Click Risk in percent
   void              RiskMoneyClick();                               // Click Risk in money

Wir ergänzen das Makro des Ereignisverarbeiters:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
EVENT_MAP_END(CAppDialog)

Die Ereignisverarbeiter-Funktion wird so aussehen:

//+------------------------------------------------------------------+
//| Click Stop Loss in pips                                          |
//+------------------------------------------------------------------+
void CTradePanel::SLPipsClick(void)
  {
   StopLoss_pips_b.Pressed(cur_sl_pips>0);
   StopLoss_money_b.Pressed(false);
   Risk_money_b.Pressed(false);
   Risk_percent_b.Pressed(false);
   return;
  }

 Alle Ereignisverarbeiter-Funktionen können Sie im hinzugefügten Code lesen.

4.4. Das Drücken der Tasten der Änderung des Transaktionsvolumens.

Im Gegensatz zu Radio-Tasten, wenn Sie auf die Tasten der Änderung des Transaktionsvolumens drücken, muss das Programm eine Reihe von Operationen durchführen, die wir im Code registrieren müssen. Zunächst einmal ist es eine Zunahme oder Abnahme des Wertes der Variable cur_lot auf die Größe der Schrittänderung des Transaktionsvolumens. Dann müssen Sie den erhaltenden Wert mit dem maximalen und minimalen möglichen Wert für das Werkzeug vergleichen. Als Option, würde ich die Verfügbarkeit der Mittel für die Öffnung der Order mit einem solchen Volumen vorschlagen, denn es kann später bei der Eröffnung der Order nicht genug Geld auf dem Konto des Händlers sein. Dann müssen wir einen neuen Wert des Transaktionsvolumen im Panel anzeigen und den dazugehörigen Wert korrigieren, wie es falls der manuellen Eingabe der Werte des Transaktionsvolumens im editierbaren Feld wäre.

Genauso wie früher, werden wir die Funktion im Block privat deklarieren:

private:
................
   //--- On Event functions
................
   void              IncreaseLotClick();                             // Click Increase Lot
   void              DecreaseLotClick();                             // Click Decrease Lot

Wir ergänzen die Interrupt-Verarbeitung Funktion mit dem Makro:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
EVENT_MAP_END(CAppDialog)

Betrachten wir die Funktion der Verarbeitung der Ereignisse:

//+------------------------------------------------------------------+
//|  Increase Lot Click                                              |
//+------------------------------------------------------------------+
void CTradePanel::IncreaseLotClick(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text())+SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));
   //--- Call end edit lot function
   LotsEndEdit();
   return;
  }

Zuerst lesen wir den aktuellen Wert des Transaktionenvolumens und erhöhen den Wert aus der Spezifikation des Werkzeugs. Dann sofort führen den erhaltenden Wert in Übereinstimmung mit der Spezifikation des Werkzeugs durch die Funktion NormalizeLots, die wir bereits gesehen haben.

Als nächstes rufen wir die Funktion der Verarbeitung der Volumenänderungen des Lots im Eingabefenster auf, da in dieser Funktion wir früher alle notwendigen Prozeduren registriert haben.

Nach ähnlicher Weise bauen wir die Verringerung-Funktion des Lots

4.5. Die Zustandsänderung des Checkboxes.

Im nächsten Schritt bauen wir einen Verarbeiter der Reaktionen-Ereignisse beim Drucken auf Checkboxs. Auf unserem Panel gibt es zwei Checkboxs, um die Anzeige der Ebenen von Stop-Loss und Take-Profit auf dem Chart zu deaktivieren oder aktivieren.

Was soll geschehen, wenn der Zustand des Checkboxs sich ändert? Grundsätzlich muss die Hauptfunktion des Ereignisses auf dem Chart die Anzeige der Linien sein. Dieses Problem kann auf zwei Arten gelöst werden:

  1. Jedes Mal, wenn Sie eine Linie löschen oder erstellen;
  2. Die Erstellung der Linien am Chart mit allen Panel-Objekten, und sie bei der Änderung des Zustandes des Checkboxes anzeigen oder verbergen.

Ich würde die zweite Option nehmen. Zu diesem Zweck verbinden wir eine andere Bibliothek:

#include <ChartObjects\ChartObjectsLines.mqh>

Dann Objekte der horizontalen Linien deklarieren wir im Block private und deklarieren deren Initialisierung-Funktion :

private:
.................
   CChartObjectHLine BuySL, SellSL, BuyTP, SellTP;    // Stop Loss and Take Profit Lines
   
   //--- Create Horizontal line
   bool              CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment);

Verschreiben eine Initialisierung-Prozedur der horizontalen Linien. Zuerst erstellen wir eine Linie auf dem Chart.

//+------------------------------------------------------------------+
//| Create horizontal line                                           |
//+------------------------------------------------------------------+
bool CTradePanel::CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment)
  {
   // All objects must have separate name
   string name="HLine"+(string)ObjectsTotal(chart,-1,OBJ_HLINE);
   //--- Create horizontal line
   if(!object.Create(chart,name,subwindow,0))
      return false;

Dann definieren wir die Farbe, den Linientyp, und fügen Kommentare hinzu, die beim Zeigen aufs Objekt auftauchen.

   //--- Set color of line
   if(!object.Color(clr))
      return false;
   //--- Set dash style to line
   if(!object.Style(STYLE_DASH))
      return false;
   //--- Add comment to line
   if(!object.Tooltip(comment))
      return false;

Verbergen wir die Linien aus dem Chart und erstellen die Anzeige der Linien im Hintergrund.

   //--- Hide line 
   if(!object.Timeframes(OBJ_NO_PERIODS))
      return false;
   //--- Move line to background
   if(!object.Background(true))
      return false;

Von daher, dass es eine von unseren Panel-Optionen gibt - ermöglichen wir dem Händler, die Ebene-Linien von Stop-Loss und Take-Profit auf dem Chart zu bewegen, wir bieten dem Benutzer die Möglichkeit, die Linien auszuwählen:

   if(!object.Selectable(true))
      return false;
   return true;
  }

Nun fügen wir die Initialisierungslinien zur Erstellungsfunktion unser Handelspanels.

//+------------------------------------------------------------------+
//| Create Trade Panel function                                       |
//+------------------------------------------------------------------+
bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
...................
...................
   //--- Create horizontal lines of SL & TP
   if(!CreateHLine(chart,subwin,BuySL,SL_Line_color,"Buy Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellSL,SL_Line_color,"Sell Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,BuyTP,TP_Line_color,"Buy Take Profit"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellTP,TP_Line_color,"Sell Take Profit"))
     {
      return false;
     }
    return true;
  }

Sobald wir eine Linie direkt erstellt haben, schreiben wir die Funktion der Verarbeitung der Ereignisse. Die Funktion der er Verarbeitung der Ereignisse wird nach gleicher Weise wie bei der Funktionsverarbeitung der vorhergehenden Ereignisse aufgebaut. Deklarieren wir die Ereignisverarbeiter-Funktionen im Block private:

private:
...............
   void              StopLossLineClick();                            // Click StopLoss Line 
   void              TakeProfitLineClick();                          // Click TakeProfit Line

Fügen wir einen Funktionsaufruf zum Ereignisverarbeiter:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
   ON_EVENT(ON_CLICK,StopLoss_line,StopLossLineClick)
   ON_EVENT(ON_CLICK,TakeProfit_line,TakeProfitLineClick)
EVENT_MAP_END(CAppDialog)

Und schreiben wir schließlich selbst die Funktion der Verarbeitung der Ereignisse. Am Anfang der Funktion wird der Zustand des Checkboxes überprüft. Was als nächstes passieren soll, hängt vom Zustand des Checkboxes. Wenn er gedrückt ist, ist es notwendig, vor der Anzeige die Ebenen zu aktualisieren. Dann zeigen wir die Linie auf dem Chart an.

//+------------------------------------------------------------------+
//| Show and Hide Stop Loss Lines                                    |
//+------------------------------------------------------------------+
void CTradePanel::StopLossLineClick()
  {
   if(StopLoss_line.Pressed()) // Button pressed
     {
      if(BuySL.Price(0)<=0)
        {
         UpdateSLLines();
        }
      BuySL.Timeframes(OBJ_ALL_PERIODS);
      SellSL.Timeframes(OBJ_ALL_PERIODS);
     }

Wenn das Checkbox nicht gedrückt ist, werden die Linien verborgen.

   else                         // Button unpressed
     {
      BuySL.Timeframes(OBJ_NO_PERIODS);
      SellSL.Timeframes(OBJ_NO_PERIODS);
     }
   ChartRedraw();
   return;
  }

Am Ende des Funktionsaufrufs wird das Chart neu gezeichnet.

4.6. Handelsoperationen

Nun, wenn die Ereignisverarbeitungsfunktionen für die Hauptsteuerungselemente beschrieben wurden, gehen wir zur Ereignisverarbeitung des Druckens auf die Tasten der Handelsoperationen. Für die Durchführung der Handelsoperationen auf dem Konto verwenden wir auch die Standard-Bibliothek MQL5 "Trade.mqh", in der die Klasse CTrade der Handelsoperationen beschreiben wurde.

#include <Trade\Trade.mqh>

Deklarieren wir die Klasse der Handelsoperationen im Block private:

private:
................
   CTrade            Trade;                           // Class of trade operations

Und in unserer Funktion der Initialisierung führen wir die Initialisierung der Handelsklasse durch. Hier stellen wir die magische Zahl der Transaktionen geben, den Level des Schlupfs für die Erledigung und Politik der Ausführung von Handelsorders.

//+------------------------------------------------------------------+
//| Class initialization function                                    |
//+------------------------------------------------------------------+
CTradePanel::CTradePanel(void)
  {
   Trade.SetExpertMagicNumber(0);
   Trade.SetDeviationInPoints(5);
   Trade.SetTypeFilling((ENUM_ORDER_TYPE_FILLING)0);
   return;
  }

Beim Bedarf können Sie hier zusätzliche Funktionen hinzufügen, die magische Zahl und den Level des Schlupfs von einem externen Programm setzen. Vergessen Sie nicht, dass solche Funktionen im öffentlichen Block public deklariert werden müssen.

Nach den vorbereitenden Arbeiten schreiben wir die Ereignisverarbeitungfunktion des Druckens auf die Tasten. Genauso wie früher, werden wir die Funktion im Block private deklarieren:

private:
.....................
   void              BuyClick();                                     // Click BUY button
   void              SellClick();                                    // Click SELL button
   void              CloseBuyClick();                                // Click CLOSE BUY button
   void              CloseSellClick();                               // Click CLOSE SELL button
   void              CloseClick();                                   // Click CLOSE ALL button

Dann ergänzen wir den Operator der Ereignisverarbeitung mit neuen Funktionen:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
...................
   ON_EVENT(ON_CLICK,BUY,BuyClick)
   ON_EVENT(ON_CLICK,SELL,SellClick)
   ON_EVENT(ON_CLICK,CloseBuy,CloseBuyClick)
   ON_EVENT(ON_CLICK,CloseSell,CloseSellClick)
   ON_EVENT(ON_CLICK,CloseAll,CloseClick)
EVENT_MAP_END(CAppDialog)

Und schreiben wir natürlich selbst die Funktion der Verarbeitung der Ereignisse. Betrachten wir, zum Beispiel, die Funktion des Kaufs. Welche Maßnahmen soll unser Programm beim Klick auf die Taste "BUY" erfüllen?

Vielleicht müssen wir zuerst den Level der kommenden Transaktion aktualisieren. Lesen wir den Wert auf dem Feld des Lots, führen wir es in Übereinstimmung mit der Spezifikation des Werkzeugs und überprüfen, ob Mittel für die Eröffnung der Order ausreichend sind, und dann setzen wir wieder den aktualisierten Wert aufs Panel zurück.

void CTradePanel::BuyClick(void)
  {
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   Lots.Text(DoubleToString(cur_lot,2));

Im nächsten Schritt erhalten wir den Marktpreis des Werkzeugs, und berechnen die Preisebene von Stop-Loss und Take-Profit in Übereinstimmung mit den gegebenen Parametern auf dem Panel:

   double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double SL=(cur_sl_pips>0 ? NormalizeDouble(price-cur_sl_pips*_Point,_Digits) : 0);
   double TP=(cur_tp_pips>0 ? NormalizeDouble(price+cur_tp_pips*_Point,_Digits) : 0);

Und schließlich senden wir eine Anfrage für die Order im Server des Brokers. Auch, wenn ein Fehler auftritt, müssen wir hier eine Funktion mit der Meldung über den Fehler hinzufügen.

   if(!Trade.Buy(NormalizeLots(cur_lot),_Symbol,price,SL,TP,"Trade Panel"))
      MessageBox("Error of open BUY ORDER "+Trade.ResultComment(),"Trade Panel Error",MB_ICONERROR|MB_OK);;
   return;
  }

Ebenso bauen wir die Verarbeitungsfunktionen des Druckens auf andere Handel-Tasten. Mehr über den Code dieser Funktionen kann man in der beigefügten Datei finden.

5. Die "manuelle" Bewegung der Ebene von Stop-Loss und Take-Profit.

Wir erinnern uns daran, dass Händler sehr oft die Ebene von Stop-Loss und Take-Profit auf einige bedeutende Ebene am Chart bewegen. Und meiner Meinung nach wäre es falsch, den Benutzer zu zwingen, die Zahl der Punkte aus dem aktuellen Preis bis zu dieser Ebene zu berechnen. Von daher ermöglichen dem Benutzer, einfach die Linie an die gewünschte Stelle zu bewegen, und den Rest erledigt das Programm selbst.

Für mich selbst habe ich beschlossen, das Programm mit dem Code der Verarbeitung der Mausbewegungen auf dem Chart nicht zu belasten, und eine Standardfunktion des Terminals, Objekten bewegen zu verwenden. Zu diesem Zweck ließen wir dem Benutzer die Möglichkeit, die horizontale Linie zu bewegen und auszuwählen. Mit dem Programm werden wir die Veranstaltung "CHARTEVENT_OBJECT_DRAG" verarbeiten.

Wie immer, deklarieren wir zuerst die die Ereignisverarbeiter-Funktion im Block public, denn diese Funktion werden wir aus dem externen Programm aufrufen:

public:
................
   virtual bool      DragLine(string name);
Der Aufruf dieser Funktion wird aus der Funktion OnChartEvent des Hauptprogramms durchgeführt, bei dem Auftreten des Ereignisses mit der Namensübertragung des Objektes.
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      if(TradePanel.DragLine(sparam))
        {
         ChartRedraw();
        }
     }
...........

In der Funktion der Verarbeitung des Ereignisses müssen wir:

Die ersten drei Punkte in der Verarbeitungsfunktion des Aufrufsereignisses zu erledigen, und für die Durchführung der letzten Punkte rufen wir die Funktion der "manuellen" Korrektur des entsprechenden Feldes auf. Und natürlich, nach der Verarbeitung der Ereignisse nehmen wir die Auswahl der Linie ab.

//+------------------------------------------------------------------+
//| Function of moving horizontal lines                              |
//+------------------------------------------------------------------+
bool CTradePanel::DragLine(string name)
  {
   if(name==BuySL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(BuySL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      SLPipsEndEdit();
      BuySL.Selected(false);
      return true;
     }
   if(name==SellSL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(SellSL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      SLPipsEndEdit();
      SellSL.Selected(false);
      return true;
     }
   if(name==BuyTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(BuyTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      TPPipsEndEdit();
      BuyTP.Selected(false);
      return true;
     }
   if(name==SellTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(SellTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      TPPipsEndEdit();
      SellTP.Selected(false);
      return true;
     }
   return false;
  }

6. Die Speicherung der aktuellen Einstellungen beim Neustart

Ich möchte Sie daran erinnern, dass der Benutzer beim Neustart des Programms wahrscheinlich nicht wieder alle Werte aufs Panel eingeben würde. Und es wird wohl vielen ärgern, das Panel ständig in die benutzerfreundliche Grafiksstelle zu ziehen. Vielleicht wäre jemand damit zufrieden, wenn diese Aktion nur beim Neustart des Terminals notwendig wäre. Aber vergessen wir nicht, dass einen Neustart des Programms auch bei einer einfachen Änderung der Timeframe des Charts auftritt. Dies wird aber viel öfter vorkommen. Darüber hinaus fordern viele Handelssysteme, Charts auf mehrere Timeframes zu untersuchen. Also brauchen wir nur den Zustand der Radio-Taste der und Checkboxen zu speichern, genauso wie die Werte aller Felder, die manuell vom Benutzer eingegeben wurden. Und natürlich muss das Panel den Status und die Position des Fensters speichern.

Im Hinblick auf die letzte Operation wurde sie bereits in der Mutterklasse realisiert. Wir realisieren nur das Lesen der gespeicherten Informationen beim Start des Programms.

Aber bezüglich der editierbaren Felder und den Zustand der Tasten müssen wir einiges tun. Obwohl ich sagen muss, dass die meiste Arbeit bereits von den Entwicklern erledigt wurde, dafür vielen Dank.

Ich werde nicht weit in die Klassenvererbung gehen, aber ich sage, dass alle kommenden Klassen vom Vorläufer der CObject-Klasse die Funktionen Save und Load haben. Unsere Klasse CTradePanel hat von der Eltern-Klasse den Aufruf der Funktion geerbt, die alle hinzugefügten Objekte bei der Deinitializerung der Klasse speichert. Doch hier warten wir auf eine unangenehme Überraschung - Die Klassen Klassen CEdit und CBmpButton die "leeren" Funktionen geerbt:

   //--- methods for working with files
   virtual bool      Save(const int file_handle)                         { return(true);   }
   virtual bool      Load(const int file_handle)                         { return(true);   }
Deshalb müssen wir diese Funktionen für die Objekte neu schreiben, deren Daten wir speichern wollen. Zu diesem Zweck werden wir zwei neue Klassen erstellen - CEdit_new und CBmpButton_new, welche Erben von der Klassen CEdit und CBmpButton sein werden. In den Funktionen schreiben wir die Speicherung und das Lesen der Daten.
class CEdit_new : public CEdit
  {
public:
                     CEdit_new(void){};
                    ~CEdit_new(void){};
   virtual bool      Save(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      string text=Text();
      FileWriteInteger(file_handle,StringLen(text));
      return(FileWriteString(file_handle,text)>0); 
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      int size=FileReadInteger(file_handle);
      string text=FileReadString(file_handle,size);
      return(Text(text));
     }
   
  };

class CBmpButton_new : public CBmpButton
  {
public:
                     CBmpButton_new(void){};
                    ~CBmpButton_new(void){};
   virtual bool      Save(const int file_handle)
    {
     if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(FileWriteInteger(file_handle,Pressed()));
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(Pressed((bool)FileReadInteger(file_handle)));
     }
  };

Und natürlich ändern wir die Typen der gespeicherten Objekte auf die neuen.

   CEdit_new         Lots;                            // Display volume of next order
   CEdit_new         StopLoss_pips;                   // Display Stop loss in pips
   CEdit_new         StopLoss_money;                  // Display Stop loss in account currency
   CEdit_new         TakeProfit_pips;                 // Display Take profit in pips
   CEdit_new         TakeProfit_money;                // Display Take profit in account currency
   CEdit_new         Risk_percent;                    // Display Risk percent to equity
   CEdit_new         Risk_money;                      // Display Risk in account currency
   CBmpButton_new    StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton_new    TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton_new    StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton_new    StopLoss_money_b;                // Select Stop loss in account currency
   CBmpButton_new    TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton_new    TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton_new    Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton_new    Risk_money_b;                    // Select Risk in account currency

Aber es reicht nicht nur die Informationen zu speichern, wir müssen sie noch lesen. Um dies zu tun, schreiben wir die Start-Funktion unseres Handelspanels:

public:
.................
   virtual bool      Run(void);

Lesen wir zuerst die gespeicherten Daten:

//+------------------------------------------------------------------+
//| Run of Trade Panel                                               |
//+------------------------------------------------------------------+
bool CTradePanel::Run(void)
  {
   IniFileLoad();

Dann erneuern wir die Werte der Variablen:

   cur_lot=StringToDouble(Lots.Text());
   cur_sl_pips=(int)StringToInteger(StopLoss_pips.Text());     // Stop Loss in pips
   cur_sl_money=StringToDouble(StopLoss_money.Text());         // Stop Loss in money
   cur_tp_pips=(int)StringToInteger(TakeProfit_pips.Text());   // Take Profit in pips
   cur_tp_money=StringToDouble(TakeProfit_money.Text());       // Take Profit in money
   cur_risk_percent=StringToDouble(Risk_percent.Text());       // Risk in percent
   cur_risk_money=StringToDouble(Risk_money.Text());           // Risk in money
   RiskByValue=true;
Und schließlich rufen wir die Verarbeitungsfunktionen des Druckens auf Checkboxs auf, die den Zustand der Ebene von Stop-Loss und Take-Profit aktualisieren:
   StopLossLineClick();
   TakeProfitLineClick();
   return(CAppDialog::Run());
  }

7. "Allgemeine Reinigung"

Wir haben einen tollen Job gemacht und hoffen, dass der Benutzer damit zufrieden ist. Doch es kommt eine Zeit, wenn der Benutzer aus irgendeinem Grund unser Programm verlässt. Und nach dem Schluss müssen wir etwas aufräumen: alle von uns erstellt Grafikobjekte entfernen, aber die Objekte lassen, die vom Benutzer oder anderen Programme erstellt wurden.

Bei Deinitialisierung des Programms wird das Ereignis Deinit erzeugt, das eine Funktion OnDeinit mit der Gründe-Angabe der Deprovisionierung aufruft. Von daher müssen wir aus der gegebenen Funktion des Hauptprogramms die Funktion der Deinitialisierung unsere Klasse aufrufen:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   TradePanel.Destroy(reason);
   return;
  }

Diese Funktion müssen wir im Block public unserer Klasse deklarieren:

public:
.............
   virtual void      Destroy(const int reason);
Im Körper dieser Funktion werden wir horizontale Linien vom Chart entfernen und die Funktion der Deinitialisierung der Eltern-Klasse aufrufen, die alle notwendigen Informationen speichert, und die Objekte des Handelspanels aus dem Chart löscht.
//+------------------------------------------------------------------+
//| Application deinitialization function                            |
//+------------------------------------------------------------------+
void CTradePanel::Destroy(const int reason)
  {
   BuySL.Delete();
   SellSL.Delete();
   BuyTP.Delete();
   SellTP.Delete();
   CAppDialog::Destroy(reason);
   return;
  }

Fazit

Liebe Leser, Kollegen und Freunde!

Ich hoffe sehr, dass Sie meinen Artikel bis zu Ende gelesen haben, und ich hoffe, dass er für Sie nützlich war.

Hier habe ich versucht, in einfachen Worten über die Erstellungserfahrungen des Handelspanels zu erzählen, damit Sie mit einem fertigen Werkzeug auf dem Markt arbeiten könnten.

Bitte senden Sie mir Ihre Ideen und Vorschläge, was Sie noch auf unserem Handelspanel sehen möchten. Meinerseits verspreche ich, die interessantesten Ideen ins Leben zu verwirklichen, und über diese in den künftigen Artikeln zu erzählen.