English Русский 中文 Español 日本語 Português
preview
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 21): Neues Auftragssystem (IV)

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 21): Neues Auftragssystem (IV)

MetaTrader 5Handelssysteme | 12 September 2022, 15:02
142 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel Entwicklung eines Expert Advisors für den Handel von Grund auf (Teil 20) haben wir uns mit den wichtigsten Änderungen befasst, die vorgenommen werden müssen, um das visuelle System der Aufträge zu erhalten. Die weiteren Schritte bedürfen jedoch einer genaueren Erläuterung, sodass ich beschlossen habe, den Artikel in mehrere Teile aufzuteilen. Hier finden die wichtigsten, gemachten Änderungen ein Ende. Es wird eine ganze Reihe weiterer geben, aber sie sind alle notwendig. Nun, die ganze Arbeit wird recht interessant sein. Ich werde die Arbeit hier jedoch nicht abschließen, denn es gibt noch einiges zu tun, um das System wirklich fertigzustellen. Wie auch immer, am Ende dieses Artikels wird das System fast alle notwendigen Funktionen haben.

Lassen Sie uns direkt zur Umsetzung übergehen.


1.0. Umsetzung

Fügen wir zunächst eine Schaltfläche Close (Schließen) oder Cancel (Abbrechen) für den Auftrag hinzu. Die Klasse, die für die Schaltflächen zuständig ist, wird unten gezeigt.

1.0.1. Die Klasse C_Object_BtnBitMap

Diese Klasse ist für die Unterstützung von Bitmap-Schaltflächen auf dem Chart verantwortlich, wie Sie unten sehen können.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_BtnClose    "Images\\NanoEA-SIMD\\Btn_Close.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
		void Create(string szObjectName, string szResource1, string szResource2 = NULL)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
                bool GetStateButton(string szObjectName) const
                        {
                                return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
                        }
//+------------------------------------------------------------------+
};


Beim Schreiben dieser Codeklasse wurde mir klar, dass die Positionierungsklasse in die Klasse C_Object_Base verschoben werden könnte. Die gesamte Klasse C_Object_BackGround würde diesen Code eliminieren, da er zu einer niedrigeren Klasse gehören würde. Dies wird als Wiederverwendung von Code bezeichnet. Dieser Ansatz erfordert weniger Programmieraufwand, erhöht die Leistung, aber vor allem wird der Code stabiler, da die Änderungen häufiger überprüft werden.

Um eine Schaltfläche SCHLIESSEN hinzuzufügen, gehen wir wie folgt vor:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_TradeLine.mqh"
#include "C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
class C_ObjectsTrade
{

// ... Class code ...

}

Der nächste Schritt

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};

Und der nächste Schritt

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;


Und der nächste Schritt

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2;
                                string sz0;
                                

// ... Internal function code ...

                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                                m_BackGround.Size(sz0, 92, 22);
                                                break;
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 110, 22);
                                                break;
                                }
                                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }


Und der nächste Schritt

#define macroDelete(A)  {                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));         \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));        \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


Und schließlich, der letzte Schritt...

#define macroSetAxleY(A)        {                                               \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);    \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\
                                }
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {

// ... Internal code...
                                
                        }
#undef macroSetAxleX
#undef macroSetAxleY


Wenn Sie dieses System ausführen, erhalten Sie das folgende Ergebnis:


Aber diese Schaltfläche funktioniert immer noch nicht, obwohl MetaTrader 5 ein Ereignis für die Schaltfläche erzeugt, das vom Expert Advisor verarbeitet wird. Wir haben diese Funktion noch nicht implementiert. Wir werden in diesem Artikel etwas später darauf zurückkommen.


1.0.2. Die Klasse C_Object_Edit

Das System wäre nutzlos, wenn es den Händler nicht über die gehandelten Werte informieren könnte. Zu diesem Zweck gibt es die Klasse C_Object_Edit. Die Klasse wird später noch einige Änderungen erfahren, um die Funktionsweise zu erhöhen, aber für den Moment lassen wir sie so, wie sie ist: Sie wird den Händler darüber informieren, was vor sich geht. Um dies zu implementieren, müssen wir ein paar Codezeilen in die Klasse einfügen. Das erste Snippet, das den neuen Code enthält:

void Create(string szObjectName, color cor, int InfoValue)
{
        C_Object_Base::Create(szObjectName, OBJ_EDIT);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
        SetTextValue(szObjectName, InfoValue, cor);
}

Der hervorgehobene Code verhindert, dass die Werte vom Händler geändert werden können, aber wie ich bereits sagte, wird sich dies in Zukunft ändern. Dies wird einige andere Änderungen erfordern, die aber im Moment nicht relevant sind.

Die folgende Funktion bewirkt, dass der Text angezeigt wird. Achten Sie bei dieser Funktion auf ein Detail:

void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE)
{
        color clr;
        clr = (cor != clrNONE ? cor  : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive));
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue));
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr);
}

Der ausgewählte Code zeigt die Farbe des Texthintergrunds auf der Grundlage des Eingabewerts an. Es ist wichtig, dass wir dies hier tun, da wir nicht ständig versuchen wollen, zu erraten, ob ein Wert negativ oder positiv ist, oder Zeit damit verbringen wollen, herauszufinden, ob es einen negativen Wert im Text gibt. Es ist sehr praktisch zu sehen und sofort zu erkennen, ob der Wert positiv oder negativ ist. Das macht der Code: Jetzt können Sie anhand der Farbe sofort feststellen, ob der Wert negativ oder positiv ist. Es gibt jedoch eine Bedingung, die besagt, dass die Farbe nicht vorher definiert sein darf. Dies wird auch später nützlich sein.

Als Nächstes haben wir die letzte Funktion dieser Klasse, die unten gezeigt wird.

long GetTextValue(string szObjectName) const
{
        return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * 
                                (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1));
};


Sie werden feststellen, dass Werte aufgrund ihrer Formatierung immer positiv sind, wenn wir sie darstellen. Aber wenn wir den Inhalt eines Objekts überprüfen, müssen wir die richtigen Informationen haben. Hier wird der hervorgehobene Code verwendet. Hier wird die Farbinformation verwendet: Wenn die Farbe anzeigt, dass der Wert negativ ist, wird sie korrigiert, um dem EA die richtigen Informationen zu liefern. Wenn die Farbe einen positiven Wert anzeigt, wird der Wert einfach gespeichert.

Die Farbdefinitionen befinden sich in der Klasse selbst. Diese können geändert werden, wenn Sie später andere Farben verwenden wollen, aber stellen Sie sicher, dass Sie verschiedene Farben verwenden, damit die vorherige Funktion korrekt funktioniert, sonst erhält der EA mehrdeutige Werte. So kann es passieren, dass der EA negative Werte als positiv ansieht, was zu Problemen bei der gesamten Analyse des EA führt.


1.0.3. Die Klasse C_Object_Label

Dies ist die letzte Klasse, die wir in diesem Stadium brauchen. Eigentlich hatte ich überlegt, diese Klasse nicht zu erstellen, da ihre Aktionen der Klasse C_Object_BtnBitMap ähnlich sind. Da ich aber unabhängig von der Klasse C_Object_Edit in der Lage sein wollte, Textinformationen hinzuzufügen, habe ich beschlossen, hier eine neue Klasse zu erstellen.

Der Code ist sehr einfach, siehe unten.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Edit.mqh"
//+------------------------------------------------------------------+
class C_Object_Label : public C_Object_Edit
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        };
//+------------------------------------------------------------------+
};


Wir brauchen nichts weiter in dieser Klasse, da der ganze Rest der Arbeit bereits von Objekten niedrigerer Klassen erledigt wird.

Wie Sie sehen, ist OOP ein sehr mächtiges Werkzeug. Je mehr wir den Code in Klassen organisieren, desto weniger müssen wir Klassen programmieren, die einander ähnlich sind.

Es gibt jedoch eine kleine Änderung, die wir vornehmen müssen. Beim Experimentieren ist mir aufgefallen, dass es sehr schwierig ist, die Paneldaten zu interpretieren, also habe ich sie wie folgt geändert.

    

Auf diese Weise ist es einfacher, große Werte anzuzeigen. So sieht das resultierende Objekt aus: Der obere Teil zeigt die Anzahl der Kontrakte oder den Hebelfaktor der offenen Position, während der untere Teil das Positionsergebnis anzeigt.

Um dies umzusetzen, müssen wir die Klasse C_Object_Base ändern, die hauptsächlich für die Positionierung von Objekten verantwortlich ist. Die Änderungen sind im nachstehenden Code hervorgehoben.

virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };


Danach können wir zum nächsten Schritt übergehen: die Änderung der Klasse C_ObjectsTrade.


1.0.4 Die Klasse C_ObjectsTrade

Zeichnen wir nun die erforderlichen Objekte, um das gewünschte Ergebnis auf dem Diagramm zu erhalten: Wir werden alle dargestellten Informationen und alle damit verbundenen Objekte haben. Das ist nicht schwer, wir werden die Maßnahmen Schritt für Schritt analysieren. Wenn Sie verstehen, wie das geht, können Sie alle anderen Informationen hinzufügen, indem Sie einfach die Anweisungen befolgen, und alles wird gut. Als Erstes müssen wir neue Ereignisse definieren, auf die die Objekte reagieren sollen. Sie sind im nachstehenden Code hervorgehoben.

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};

Fügen wir nun die erforderlichen Objekte hinzu. Die neuen Objekte sind auch im nachstehenden Code hervorgehoben:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;
C_Object_Edit           m_EditInfo,
                        m_InfoVol;
C_Object_Label          m_BtnMove;


Danach erstellen wir Objekte und definieren, wie sie auf dem Bildschirm aussehen sollen. Bitte beachten Sie, dass die Objekte in der gleichen Reihenfolge erstellt werden müssen, in der sie erscheinen sollen: zuerst das Hintergrundobjekt, dann das nächste Objekt, das über den Hintergrund gelegt wird, und so weiter, bis alle Objekte erstellt sind. Wenn Sie dies in einer falschen Reihenfolge tun und eines der Objekte ausgeblendet wird, ändern Sie einfach seine Position in diesem Code. Hier ist nun der Code:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        int infoValue;
                                
        switch (it)
        {
                case IT_TAKE    :
                        infoValue = m_BaseFinance.FinanceTake;
                        cor1 = clrForestGreen;
                        cor2 = clrDarkGreen;
                        cor3 = clrNONE;
                        break;
                case IT_STOP    :
                        infoValue = - m_BaseFinance.FinanceStop;
                        cor1 = clrFireBrick;
                        cor2 = clrMaroon;
                        cor3 = clrNONE;
                        break;
                case IT_PENDING:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrCornflowerBlue;
                        cor2 = clrDarkGoldenrod;
                        cor3 = clrLightBlue;
                        break;
                case IT_RESULT  :
                default:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrDarkBlue;
                        cor2 = clrDarkBlue;
                        cor3 = clrSilver;
                        break;
                }                               
                m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2);
                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1);
                switch (it)
                {
                        case IT_TAKE:
                        case IT_STOP:
                        case IT_PENDING:
                                m_BackGround.Size(sz0, 92, 22);
                                break;
                        case IT_RESULT:
                                m_BackGround.Size(sz0, 84, 34);
                                break;
                }
                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue);
                m_EditInfo.Size(sz0, 60, 14);
                if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                else
                {
                        m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue);
                        m_InfoVol.Size(sz0, 60, 14);
                }
}

Alle hervorgehobenen Zeilen sind die Ergänzungen des Codes, die seit der letzten Version im vorherigen Artikel vorgenommen wurden. Jetzt können wir den Code für die folgende Funktion schreiben.

#define macroDelete(A)  {                                                                       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));                 \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));                \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT));                 \
                if (A != IT_RESULT)                                                             \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE));         \
                else                                                                            \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME));       \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


Beachten Sie den Vorteil der Verwendung des Makros: Ich musste nur die hervorgehobenen Teile hinzufügen, damit wir alle Objekte des Panels löschen können. Jetzt verwenden wir 6 Objekte in 4 Feldern. Würde man dies anders umsetzen, wäre der Aufwand zu groß und die Fehlerwahrscheinlichkeit hoch. Lassen Sie uns die Positionierungsfunktion beenden.

#define macroSetAxleY(A)        {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y);                         \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);                            \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);                            \
                m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));  \
                if (A != IT_RESULT)                                                                     \
                        m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y);                      \
                else                                                                                    \
                        m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1);                 \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B);                 \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);                    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);                \
                m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21);                \
                if (A != IT_RESULT)                                                             \
                        m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80);         \
                else                                                                            \
                        m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21);       \
                                }
                                                                                
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {
                                double ad;
                                int x, y;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetAxleY(it);
                                macroSetAxleX(it, m_PositionMinimalAlxeX);
                                if (Leverange == 0) return;
                                if (it == IT_PENDING)
                                {
                                        ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
                                        macroSetAxleY(IT_TAKE);
                                        macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110);
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
                                        macroSetAxleY(IT_STOP);
                                        macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220);
                                }
                        }
#undef macroSetAxleX
#undef macroSetAxleY


Auch hier wurde nur sehr wenig Code hinzugefügt. Dennoch kann die Funktion mit allen Elementen arbeiten, da sie diese dank der Verwendung von Makros korrekt positioniert. Nach der Kompilierung des EA in diesem Stadium erhalten wir folgendes Ergebnis:


Obwohl alles schön aussieht, funktionieren diese Steuerelemente noch nicht: Wir müssen Ereignisse für jedes der Objekte implementieren. Ohne die Ereignisse ist diese Schnittstelle fast nutzlos, da sie nur die ursprünglich verwendeten Zeilen ersetzt.


2.0. Wie man das Problem behebt

Wenn es einfach wäre, könnte es jeder machen. Aber wir haben immer Probleme zu lösen, und das ist Teil des Entwicklungsprozesses. Ich könnte einfach die Lösung anbieten, anstatt zu zeigen, wie man die Aufgabe löst. Aber ich möchte Sie mit diesen Artikeln motivieren, sich mit Problemen auseinanderzusetzen und zu lernen, wie man tatsächlich programmiert. Es wird also etwas Interessantes in diesem Abschnitt zu finden sein.


2.0.1. Anpassung der Dinge bei der Aktualisierung der Tabelle

Dies ist das erste zu lösende Problem. Die Ursache liegt darin, dass die Positionen der Objekte nicht zusammen mit der Aktualisierung der Karte aktualisiert werden. Um dies zu verstehen, sehen Sie sich das folgende Bild an:

Solche Dinge können sehr ärgerlich sein, aber die Lösung ist sehr einfach: MetaTrader 5 selbst erzeugt ein Ereignis, das anzeigt, dass der Chart aktualisiert werden muss. Wir müssen also das Ereignis erfassen und unser Auftragssystem aktualisieren.

Die Änderung sollte durch den Ereignisaufruf CHARTEVENT_CHART_CHANGE erfasst werden. Das Aktualisieren ist einfacher, wenn Sie die Funktion UpdatePosition verwenden, die im EA-Code enthalten ist. Wir müssen nur eine einzige Zeile in unseren Code einfügen. Dies geschieht in C_OrderView wie unten gezeigt:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....
                                
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

// ... The rest of the code...

Diese einfache Lösung hat ein Problem: Wenn Sie viele Aufträge für einen Vermögenswert haben, kann es einige Zeit dauern, wodurch der EA mit der Aktualisierung stecken bleiben kann, bevor er mit der weiteren Verarbeitung fortfahren kann. Es gibt andere, kompliziertere Lösungen, die den Prozess beschleunigen. Aber diese Lösung ist für dieses System völlig ausreichend. Das Ergebnis ist wie folgt.


Alles scheint richtig zu sein, nicht wahr? Und doch gibt es hier einen Fehler. Es ist schwer zu erkennen, bis wir das System getestet haben. Aber es gibt tatsächlich einen Fehler in diesem System, der auch nach der Behebung nicht verschwindet.


2.0.2. EA, bitte hör auf, Elemente automatisch auszuwählen

Wenn Sie sich das obige Gif ansehen, werden Sie feststellen, dass die Haltelinie ausgewählt ist, obwohl wir sie nicht ausgewählt haben. Der EA tut dies jedes Mal. Je nach den Einstellungen bei der Erstellung des Panels kann es vorkommen, dass der EA den Take-Profit oder die Positionslinie usw. bei jeder Bewegung des Charts auswählt.

Man kann verrückt werden, wenn man versucht zu verstehen, was hier vor sich geht, aber die Lösung ist noch einfacher als die vorherige: Wir fügen einfach eine Zeile in denselben Code ein, und der EA wird automatisch aufhören, eine Zeile auszuwählen. Die Lösung ist im unten stehenden Code hervorgehoben.

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        double infoValue;

// ... Internal code...

        Select(NULL);
}

Diese Dinge werden während der Entwicklung immer passieren. Ich meine die Fehler, die leicht zu erkennen sind und die weniger offensichtlichen, die wir manchmal gar nicht bemerken. Wie auch immer, sie können passieren. Daher ist das Erlernen des Programmierens ein kontinuierlicher Prozess. Manchmal kann man diese Probleme selbst lösen und sie mit anderen teilen, um ihnen zu helfen, das gleiche Problem zu lösen. Versuchen wir es. Ich persönlich praktiziere das, denn so habe ich das Programmieren gelernt. Auf die gleiche Weise können Sie auch lernen, wie man Programme erstellt. Im Allgemeinen gehört es zum Lernen dazu, sich einen Quellcode zu besorgen und ihn zu ändern. Sie sollten dies mit einem funktionierenden Code tun, damit es einfacher ist, zu verstehen, wie er erstellt wurde, und dies trägt oft gute Früchte, da wir viel lernen, wenn wir verstehen, wie jeder Programmierer es geschafft hat, ein bestimmtes Problem zu lösen.

Nun ja, aber das war nur etwas, um Sie zum Lernen zu motivieren. Lassen Sie uns zu etwas wirklich Wichtigem übergehen.


3.0. Ereignisbehandlung

Wir werden ein System aufbauen, das das Ergebnis einer Position meldet. Dies wird den entsprechenden Bereich in Chart Trade ersetzen, aber ich werde Chart Trade so lassen, wie es ist, weil es das Gesamtergebnis der Positionen anzeigt. Bei einem Hedging-Konto unterscheidet sich der Wert von dem im Auftragssystem angegebenen Wert, da es sich bei dem einen um einen lokalen Wert handelt, während der andere den Gesamtwert darstellt. Bei anderen Kontenarten gibt es keinen derartigen Unterschied. Wenn Sie möchten, können Sie das Ergebnissystem aus Chart Trade entfernen.

3.0.1. Positionsergebnis anzeigen

Diejenigen, die den Code zum ersten Mal sehen, sind oft ratlos, wissen nicht, wo sie nach Informationen suchen sollen, und erstellen möglicherweise andere Operationen, um etwas zu tun, was der ursprüngliche Code bereits tut. Das ist es, was normalerweise zu vielen Problemen führt - wir können zusätzlichen Code erzeugen, der zusätzliche Fehler erzeugt, die der ursprüngliche Code nicht hatte. Dies entspricht nicht der REUSE-Regel, nach der man nur programmieren sollte, wenn es wirklich notwendig ist. Wenn Sie also wissen, wie MetaTrader 5 funktioniert, und wenn Sie wissen, wie der EA bereits funktioniert, dann sollten Sie die Stelle finden können, an der das in Chart Trade angezeigte Ergebnis erzeugt wird. Denn wenn das Ergebnis der Positionen präsentiert wird, sollten wir es nutzen. Achten Sie auf den unten stehenden Code.

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
        TimesAndTrade.Update();
}


Als Nächstes gehen wir zu dem hervorgehobenen Punkt über. Der Quellcode ist unten abgebildet.

inline double CheckPosition(void)
                        {
                                double Res = 0, sl, profit, bid, ask;
                                ulong ticket;
                                
                                bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
                                ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
                                for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        sl = PositionGetDouble(POSITION_SL);
                                        if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                                        {
                                                if (ask < sl) ClosePosition(ticket);
                                        }else
                                        {
                                                if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                                        }
                                        Res += profit;
                                }
                                return Res;
                        };


Nun könnte man denken: Aber wie soll ich die Daten von dort übernehmen und auf das Panel anwenden, wenn es keine Möglichkeit gibt, zu wissen, auf welche Objekte ich mich beziehen soll? Es mag den Anschein erwecken, als seien die Objekte ohne jedes Kriterium und ohne Sorgfalt geschaffen worden... Das ist nicht wahr! Wenn Sie so denken oder sich die Dinge so vorstellen, wäre es besser, ein wenig mehr darüber zu erfahren, wie die Plattform MetaTrader 5 tatsächlich funktioniert. Es stimmt zwar, dass ich keine Liste, kein Array und keine Struktur erstellt habe, die auf die zu erstellenden Objekte verweist, aber das war beabsichtigt. Weil ich weiß, dass es funktioniert. Ich werde Ihnen zeigen, dass es wirklich funktioniert: Sie können auf ein bestimmtes Objekt verweisen, ohne eine Struktur zu verwenden, um die Objekte zu speichern, die auf dem Chart sein werden. Was wirklich benötigt wird, ist den Objektnamen korrekt zu modellieren. Das war's.

Q Wie wurden die Objektnamen modelliert?

Die Antwort lautet wie folgt:

1 - Reihenfolge der Kopfzeile Durch diese Reihenfolge wird das im Auftragssystem verwendete Objekt von allen anderen Objekten unterschieden.
2 - Begrenzendes Zeichen Zeigt an, dass weitere Informationen folgen werden
3 - Typenbezeichnung Unterscheidet zwischen Take und Stop.
4 - Begrenzendes Zeichen Das Gleiche wie 2.
5 - Ticket für Order oder Positionen Merkt sich das Auftragsticket - verknüpft OCO-Auftragsdaten und unterscheidet zwischen Aufträgen
6 - Begrenzendes Zeichen Das Gleiche wie 2.
7 - Ereignisanzeige Unterscheidet zwischen Objekten innerhalb desselben Bereichs

Das heißt, Modellierung ist alles, auch wenn es denjenigen, die nicht programmieren, so vorkommt, als würden wir etwas wiederholen, so schaffen wir in Wirklichkeit etwas Einzigartiges - jedes der Objekte ist einzigartig und kann durch eine einfache Regel referenziert werden. Und diese Regel wird durch den folgenden Code erstellt:

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev)
                        {
                                return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev);
                        }


Wenn Sie also dem vorherigen Code mitteilen, auf welches Auftragsticket, welchen Indikator und welches Ereignis Sie zugreifen möchten, erhalten Sie den Namen eines bestimmten Objekts. Dadurch können seine Attribute manipuliert werden. Da wir wissen, dass dies der erste Schritt ist, müssen wir eine weitere Entscheidung treffen: Wie können wir diese Manipulation sicher machen, ohne den Code zu beschädigen und ohne ihn in einen Frankenstein zu verwandeln?

Gut, machen wir es. Wir gehen zur Klasse C_ObjectsTrade und fügen den folgenden Code hinzu.

inline void SetResult(ulong ticket, double dVolume, double dResult)
                        {
                                m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult);
                                m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult);
                        }


Wir gehen nun zur Klasse C_Router über und fügen den hervorgehobenen Code hinzu.

inline double CheckPosition(void)
                        {
                                double Res = 0, sl, profit, bid, ask;
                                ulong ticket;
                                
                                bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
                                ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
                                for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT));
                                        sl = PositionGetDouble(POSITION_SL);
                                        if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                                        {
                                                if (ask < sl) ClosePosition(ticket);
                                        }else
                                        {
                                                if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                                        }
                                        Res += profit;
                                }
                                return Res;
                        };

Damit ist eines der Probleme gelöst. Aber wir haben noch andere ungelöste Probleme.


3.0.2. Angabe des Volumens eines schwebenden Auftrags

Lassen Sie uns nun das Problem des in einem schwebenden Auftrag (pending order) angegebenen Volumens lösen. Zu diesem Zweck müssen wir eine neue Funktion in der Klasse C_ObjectsTrade erstellen.

inline void SetVolumePendent(ulong ticket, double dVolume)
                        {
                                m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                        }


Sobald dies geschehen ist, verwenden wir die Funktion UpdatePosition aus der Klasse C_Router, und die Aktualisierung wird reibungslos erfolgen.

void UpdatePosition(int iAdjust = -1)
{

// ... Internal code ....

        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
                bTest = CheckLimits(price);
                vol = OrderGetDouble(ORDER_VOLUME_CURRENT);

// ... Internal code...

                CreateIndicatorTrade(ul, price, IT_PENDING);
                SetVolumePendent(ul, vol);
                CreateIndicatorTrade(ul, take, IT_TAKE);
                CreateIndicatorTrade(ul, stop, IT_STOP);
        }
};


Damit ist das Problem gelöst. Nun müssen wir das Problem der Werte lösen, die als Take und Stop angegeben sind, da diese Werte nicht richtig sind, nachdem wir den Auftrag auf dem Chart platziert haben.


3.0.3. Ereignis beim Klicken auf die Schaltfläche „Panel Close“

Die einzige sichere Möglichkeit, einen Auftrag oder eines seiner Stopplevel zu entfernen, ist die Schaltfläche „Close“ (Schließen), die sich in der Ecke der einzelnen Werte befindet. Aber hier haben wir ein falsch implementiertes Ereignis. Beheben wir das:

Das Click-Ereignis sollte eigentlich in der Klasse C_OrderView implementiert werden. Wir ersetzen das alte System durch den hervorgehobenen Code.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   	ticket;
        double  	price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType 	ev;
                                
        switch (id)
        {

// ... Internal code...
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetInfosOrder(sparam, ticket, price, it, ev))
                        {
                                switch (ev)
                                {
                                        case EV_CLOSE:
                                                if (OrderSelect(ticket)) switch (it)
                                                {
                                                        case IT_PENDING:
                                                                RemoveOrderPendent(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                                break;
                                                }
                                                if (PositionSelectByTicket(ticket)) switch (it)
                                                {
                                                        case IT_RESULT:
                                                                ClosePosition(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0);
                                                                break;
                                                }
                                                break;

// ... Rest of the code...


Es gibt noch eine weitere Sache, die in dieser Klasse hinzukommt. Was würde passieren, wenn der Händler versehentlich ein Objekt löscht, das Positionsdaten anzeigt? Um dies zu vermeiden, wurde der folgende Code in das System eingefügt.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType      ev;
                                
        switch (id)
        {
                case CHART_EVENT_OBJECT_DELETE:
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

// ... Rest of the code...

Wenn der Operator etwas löscht, das er nicht löschen sollte, wird der EA das gelöschte Objekt schnell ersetzen.

Das folgende Video zeigt, wie das System derzeit funktioniert. Es gab auch einige Änderungen, auf die ich in dem Artikel nicht eingegangen bin, da es sich um geringfügige Änderungen handelte, die die Erklärung nicht beeinträchtigten.




Schlussfolgerung

Obwohl das System fertig zu sein scheint und Sie vielleicht damit handeln möchten, muss ich Sie warnen, dass es noch nicht vollständig ist. Dieser Artikel sollte Ihnen zeigen, wie Sie Dinge hinzufügen und ändern können, um ein viel praktischeres und einfacher zu bedienendes Bestellsystem zu haben. Aber es fehlt noch das System, das für die Bewegung von Positionen verantwortlich ist, und das ist, was den EA sehr lehrreich, praktisch und intuitiv zu bedienen machen wird. Aber das werden wir für den nächsten Artikel aufheben.

Die Anlage enthält das System in seinem derzeitigen Entwicklungsstadium.


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10499

Beigefügte Dateien |
Lernen Sie, wie man ein Handelssystem mit dem DeMarker entwickelt Lernen Sie, wie man ein Handelssystem mit dem DeMarker entwickelt
Hier ist ein neuer Artikel in unserer Serie darüber, wie man ein Handelssystem anhand der beliebtesten technischen Indikatoren entwickelt. In diesem Artikel stellen wir Ihnen vor, wie Sie ein Handelssystem mit dem Indikator DeMarker erstellen können.
Lernen Sie, wie man ein Handelssystem mit dem VIDYA entwickelt Lernen Sie, wie man ein Handelssystem mit dem VIDYA entwickelt
Willkommen zu einem neuen Artikel aus unserer Serie über das Lernen, wie man ein Handelssystem durch die beliebtesten technischen Indikatoren zu entwerfen, in diesem Artikel werden wir über ein neues technisches Werkzeug lernen und lernen, wie man ein Handelssystem durch Variable Index Dynamic Average (VIDYA) zu entwerfen.
Neuronale Netze leicht gemacht (Teil 19): Assoziationsregeln mit MQL5 Neuronale Netze leicht gemacht (Teil 19): Assoziationsregeln mit MQL5
Wir fahren mit der Besprechung von Assoziationsregeln fort. Im vorigen Artikel haben wir den theoretischen Aspekt dieser Art von Problemen erörtert. In diesem Artikel werde ich die Implementierung der FP Growth-Methode mit MQL5 zeigen. Außerdem werden wir die implementierte Lösung anhand realer Daten testen.
Datenwissenschaft und maschinelles Lernen — Neuronales Netzwerk (Teil 02): Entwurf von Feed Forward NN-Architekturen Datenwissenschaft und maschinelles Lernen — Neuronales Netzwerk (Teil 02): Entwurf von Feed Forward NN-Architekturen
Bevor wir fertig sind, müssen wir noch einige kleinere Dinge im Zusammenhang mit dem neuronalen Feed-Forward-Netz behandeln, unter anderem den Entwurf. Sehen wir uns an, wie wir ein flexibles neuronales Netz für unsere Eingaben, die Anzahl der verborgenen Schichten und die Knoten für jedes Netz aufbauen und gestalten können.