
Die Erstellung eines Helfers im manuellen Handeln
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.
- Die Tasten Kauf und Verkauf.
- Die Tasten der Schließung aller Positionen nach Symbolen und Konto oder einer separaten Richtung (die Order für einen Kauf oder Verkauf).
- 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).
- Das Panel sollte automatisch die Ebene von Stop-Loss und Take-Profit bei manuell eingestellten Parametern (P. 2) berechnen und sie im Chart anzeigen.
- 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.
- Das Panel sollte den kommenden Volumen der Transaktionen nach den eingegebenen Risikoparametern (in der Anlagewährung oder im Prozentsatz des aktuellen Saldo) berechnen.
- 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.
- 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.
- 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.
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.
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:
- Der Objekt-Name;
- der angezeigte Text;
- Koordinaten des Elements;
- Die Objektausrichtung nach dem Ankerpunkt.
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.
- Wir erstellen das Objekt durch die Funktion Create(...) der Elternklasse.
- Der gewünschte Text wird dann im Objekt platziert.
- Das Objekt wird nach dem Ankerpunkt ausgerichtet.
- Wir fügen das Objekt zum "Container" des Dialogfensters hinzu.
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:
- Radio-Auswahltasten, was in einem Stop-Loss, Take-Profit und das Risiko ausgedrückt wird: in Währungsformat oder in Punkten (oder im Prozentsatz für das Risiko);
- Checkbox für die Fixierung, ob die Ebene der Stop-Loss und Take-Profit auf dem Chart gezeigt werden oder nicht;
- Die Tasten der Erhöhung und Verringerung des Transaktionsvolumens.
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:
- Der Abstand von der Grenze des Fensters bis zum ersten Steuerelement;
- Der Abstand in Höhe zwischen den Steuerelementen;
- Die Höhe des Steuerelementes.
#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.
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"
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(); }
//+------------------------------------------------------------------+ //| 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:
- Jedes Mal, wenn Sie eine Linie löschen oder erstellen;
- 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:
- definieren, welche Linie (Stop-Loss oder Take-Profit) bewegt wurde;
- den Wert des Index in Punkten berechnen;
- den resultierenden Wert in der entsprechenden Zelle des Panels anzeigen;
- den Wert aller entsprechenden Indikatoren auf dem Panel neu zu berechnen;
- wenn es nötig ist, den Wert der Radio-Tasten zu ändern.
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.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2281





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.