English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 28): Der Zukunft entgegen (III)

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 28): Der Zukunft entgegen (III)

MetaTrader 5Beispiele | 4 November 2022, 11:25
267 0
Daniel Jose
Daniel Jose

Einführung

Als ich nach dem Artikel Entwicklung eines Trading Expert Advisors von Grund auf (Teil 18) mit der Entwicklung des Auftragssystems begann, hatte ich keine Ahnung, wie lange es dauern würde, bis ich an diesem Punkt angelangt war. Wir haben verschiedene Momente, Änderungen, Ergänzungen usw. durchlebt. Ich habe Ihnen gezeigt, wie Sie bestimmte Dinge tun können, z. B. Dinge markieren oder das System intuitiver gestalten. Aber es gab auch Dinge, die ich in diesem Stadium nicht zeigen konnte, weil der Weg noch nicht vollständig vorbereitet war. Auf dieser Reise konnten wir das Konzept so aufbauen, dass jeder die Idee verstehen kann und weiß, wie das System funktioniert.

In allen vorangegangenen Artikeln habe ich die Voraussetzungen dafür geschaffen, dass wir mit dem gleichen Verständnis für die Funktionsweise des Systems zu diesem Artikel kommen. Ich hoffe also, dass dieses Material nicht extrem verwirrend oder komplex sein wird. Eine Frage stand von Anfang an im Raum, und ich habe es vermieden, sie im Detail zu analysieren. Sie ist jedoch für erfahrenere Händler sehr wichtig. Auf den ersten Blick mag dies albern erscheinen, aber wenn es an der Zeit ist, zu handeln, werden wir verstehen, dass etwas in dem EA fehlt. Dann werden wir uns fragen: „Was fehlt hier?“ Ich spreche von einer Möglichkeit, die Werte von Take-Profit und Stop-Loss wiederherzustellen, die aus irgendeinem Grund gelöscht wurden und die wir im Chart wiederherstellen wollen.

Wenn Sie dies schon einmal versucht haben, können Sie verstehen, dass dies eine ziemlich schwierige und langwierige Aufgabe ist, da man ein bestimmtes Szenario einhalten muss, damit alles gut funktioniert, sonst macht man ständig Fehler.

Der MetaTrader 5 bietet ein Ticketsystem, das die Erstellung und Korrektur von Auftragswerten ermöglicht. Die Idee ist, einen Expert Advisor zu haben, der das gleiche Ticketsystem schneller und effizienter machen würde. Das System des MetaTrader 5 ist nicht perfekt; manchmal kann es langsamer und fehleranfälliger sein als der EA, den wir entwickeln.

Aber bis jetzt habe ich nie erklärt, wie man Werte für die TP- und SL-Levels (Take-Profit und Stop-Loss) generiert. Ich denke, dass das Löschen von Stopp-Preisen klar und intuitiv genug ist. Aber wie soll man das umsetzen, d.h. wie soll man vorgehen, um den Stopp zu setzen oder ihn direkt auf dem Chart wiederherzustellen? Das ist etwas sehr Faszinierendes, das einige Fragen aufwirft, und natürlich ist dies der Grund für die Erstellung dieses Artikels: eine der vielen Möglichkeiten zu zeigen, die Stopp-Preise direkt auf dem Chart zu erstellen, ohne Rückgriff auf eine externe Ressource, nur mit dem EA-Order-System.


2.0. Erste Schritte Umsetzung

Erstens müssen wir den EA zwingen, die Überprüfung einzustellen, was er seit den ersten Tagen seiner Entwicklung getan hat. Um diese Prüfung zu entfernen, müssen wir den gesamten durchgestrichenen Code entfernen und den hervorgehobenen Code hinzufügen:

inline double SecureChannelPosition(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())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                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;
};

Der durchgestrichene Code wird nicht verschwinden, aber er wird zu einem anderen Zeitpunkt wieder erscheinen. Zurzeit ist dies jedoch eher ein Hindernis als ein Vorteil. Sobald das erledigt ist, können wir darüber nachdenken, wie wir das TP- und SL-System direkt auf dem Chart implementieren, ohne die Hilfe anderer Ressourcen als des EA.

Jeder Entwickler wird seine eigene Idee zur Lösung dieses Problems haben: Einige davon werden für einen Händler leichter zu verstehen sein, während andere schwieriger sein werden; einige werden schwieriger in der Praxis umzusetzen sein, während andere einfacher sein werden. Ich behaupte nicht, dass der Weg, den ich hier zeige, der geeignetste oder einfachste ist, aber er ist bei weitem der beste für meine Arbeitsweise und die Nutzung der Plattform. Außerdem muss ich keine neuen Elemente erstellen. Wir werden nur einige Dinge im Code korrigieren.


2.0.1. Modellierung des Widerstandssystems

Der EA-Code selbst gibt im derzeitigen Entwicklungsstadium einige Hinweise darauf, wie wir das System, das wir erstellen werden, modellieren sollten. Sehen wir uns den folgenden Code an:

#define macroUpdate(A, B) if (B > 0) {                                                                  \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                     } else RemoveIndicator(ticket, A);
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Die hervorgehobenen Zeilen enthalten ein Makro, das die Aufgabe ausführt. Wir müssen sie so ändern, dass sie die nötige Hilfe bietet, um das zu implementieren, was wir brauchen, nämlich die Anzeige des Stopp-Preises. Schauen wir uns den Makrocode genauer an. Er ist unten dargestellt:

#define macroUpdate(A, B){ if (B > 0) {                                                                 \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }

Wir tun Folgendes: Wenn der B-Wert, der Take-Profit oder Stop-Loss sein kann, größer als 0 ist, prüfen wir, ob der Indikator auf dem Chart ist. Wenn nicht, erstellen wir ihn, platzieren ihn und legen den Wert fest, den er anzeigen soll. Wenn der B-Wert gleich 0 ist, entfernen wir den Indikator vollständig aus dem Chart, und zwar an der markierten Stelle im Makrocode. Aber würde es ausreichen, wenn wir den Indikator nicht vollständig aus dem Chart entfernen, sondern sein Element beibehalten, und wenn dieses Element so konfiguriert werden kann, dass es das anzeigt, was wir tun wollen (d.h. den/die fehlenden Stop(s) für einen Auftrag oder eine Position erstellen), wird das Element wieder zu einem Auftrag oder zu einer OCO-Position? JA, das würde ausreichen, und das ist die Idee: ein Element zu lassen, in diesem Fall ein Objekt, das verwendet wird, um die Stopp-Preise zu verschieben und den Stopp-Preis zu erstellen, der uns fehlt. Wir brauchen dieses Element nur zu ziehen, und schon wird die das Limit erstellt. Dies ist der theoretische Rahmen, den wir für den Aufbau des Systems verwenden werden.

Doch damit allein können wir nicht alles erreichen, was wir brauchen. Es ist noch eine weitere Änderung erforderlich. Wir werden dies tun, bevor wir fortfahren. Diese Änderung ist im nachstehenden Code hervorgehoben:

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

//... Internal code...

        m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
        m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));

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

}

Wir prüfen, ob die von Chart Trade eingegebenen Anfangswerte Null sind oder nicht. Wenn dies der Fall ist, wird kein Indikator erstellt und im Chart wird nur der Einstiegspunkt angezeigt.

Dies sorgt für eine linearere Arbeit im übrigen System. Wenn kein Stopp-Preis angegeben wird, verhält sich das gesamte Auftragsmodell genauso, wie wenn der Wert von Anfang an angegeben wird, wenn wir eine schwebenden Auftrag auf dem Chart platzieren wollen.

Kein einziger Händler wird sich also wundern: Was sind das für Zahlen, die an dem Auftrag oder der Position hängen? Denn der Händler weiß, dass sie die Elemente darstellen, die auf dem Chart bewegt werden können.

Aber trotz alledem haben wir noch ein kleines Problem, für das wir zwei Makros ändern werden. Siehe unten:

#define macroSetLinePrice(ticket, it, price)    ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)
#define macroGetPrice(ticket, it, ev)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE)

Diese Änderung ist für den Rest des Systems, das wir aufbauen werden, sehr wichtig. Aber warum ziehe ich die Schnittlinien heraus? Der Grund dafür ist, dass wir das System noch flexibler gestalten müssen, und um dies zu erreichen, wurde der durchgestrichene Code entfernt und eine hervorgehobene Zeile an seiner Stelle eingefügt. Wozu brauchen wir macroGetPrice, wenn wir kein Makro haben, das dem Preis einen Wert zuweist? Tatsächlich gibt es nur einen Punkt, der diese Preisanpassung vornimmt und sie in das Chartobjekt schreibt. Dies ist im folgenden Code zu sehen:

#define macroSetPrice(ticket, it, ev, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE, price)
//---

// ... Additional code inside the class....

//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                if (it != IT_RESULT) macroSetPrice(ticket, it, EV_MOVE, price);
                                macroSetPrice(ticket, it, EV_LINE, price);
                                macroSetAxleY(it);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 160; break;
                                        case IT_STOP: desl = 270; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2));
                        }

Daher ist es nicht erforderlich, dass das Makro den Preis von Objekten so anpasst, dass er an allen anderen Stellen des Codes sichtbar ist. Eigentlich muss es im Moment noch nicht einmal ein Makro sein, aber ich werde es so belassen, um die Gefahr eines Fehlers zu verringern, wenn sich der Code später ändert.

Nun, da unser System auf dem neuesten Stand ist, können wir zum nächsten Thema übergehen und alles wie geplant zum Laufen bringen.


2.0.2. Neue Aktualisierungsfunktion

Ich habe dies bereits im vorherigen Thema erwähnt: Alles, was wir tun müssen, ist, die Aktualisierungsfunktion zu konfigurieren, während der EA alle damit verbundenen Probleme lösen wird. Da das Hauptaugenmerk nur auf den Take-Profit- und Stop-Loss-Indikatoren liegt und diese innerhalb des Makros ausgeführt werden, müssen wir nur das Makro korrekt einrichten.

Es gibt jedoch ein Problem, das noch gelöst werden muss. Wir müssen die Schaltfläche für das Verschieben unabhängig vom Rest des Indikators erstellen. Dazu isolieren wir den Code zur Erstellung der Schaltfläche:

// ... class code ...

#define def_ColorLineTake       clrDarkGreen
#define def_ColorLineStop       clrMaroon

// ... class code ....

inline void CreateBtnMoveIndicator(ulong ticket, eIndicatorTrade it, color C = clrNONE)
                        {
                                string sz0 = macroMountName(ticket, it, EV_MOVE);

                                ObjectDelete(Terminal.Get_ID(), macroMountName(ticket, it, EV_MOVE));
                                m_BtnMove.Create(ticket, sz0, "Wingdings", "u", 17, (C == clrNONE ? (it == IT_TAKE ? def_ColorLineTake : def_ColorLineStop) : C));
                                m_BtnMove.Size(sz0, 21, 23);
                        }

// ... the rest of the class code ...

Mit diesem Code wird nur die Schaltfläche für das Verschieben erstellt und nichts anderes. Es ist sehr einfach und überschaubar. Ich habe sogar darüber nachgedacht, diesen Code als Makro zu belassen, habe mich dann aber entschieden, ihn zu einer Funktion zu machen. Sie ist als integrierte Funktion deklariert, sodass der Compiler sie genauso behandelt wie ein Makro. Um das Leben einfacher zu machen, hat derselbe Codeteil zwei neue Definitionen, da wir irgendwann nur noch eine Schaltfläche „Bewegen“ erstellen werden und diese die gleichen Farben haben soll, die im gesamten System verwendet werden. Es ist nicht erwünscht, dass sich das System in ähnlichen Fällen anders verhält oder anders aussieht. Um Probleme zu vermeiden, belassen wir die Farben wie oben gezeigt.

Nun können wir die Funktion Update aufrufen; der vollständige Code ist oben dargestellt. Beachten Sie, dass der einzige Unterschied zwischen der untenstehenden Version und der früher im Artikel vorgestellten Version der hervorgehobene Code ist. Dieser Code ist ein Makro, das von der Funktion Update selbst verwendet wird.

#define macroUpdate(A, B){                                                                                                      \
                if (B == 0) {   if (macroGetPrice(ticket, A, EV_LINE) > 0) RemoveIndicator(ticket, A);                          \
                                if (macroGetPrice(ticket, A, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, A);                  \
                            } else if (b0 = (macroGetPrice(ticket, A, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, (B == 0 ? pr : B));                                                                \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                                        \
                        }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetPrice(ticket, IT_RESULT, EV_LINE);
                                        if ((pr == 0) && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                macroUpdate(IT_TAKE, tp);
                                macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Schauen wir uns dieses Makro genauer an, um zu verstehen, was hier eigentlich passiert. Dieses Verständnis ist notwendig, um einige der Probleme lösen zu können, die bei der Nutzung des Systems immer noch auftreten. Es ist noch nicht fertig und wir müssen noch einige Änderungen vornehmen.

Im ersten Schritt haben wir das folgende Verhalten: Wenn wir eine der Stop-Orders (Take-Profit oder Stop-Loss) entfernen, entfernen wir sofort den entsprechenden Indikator aus dem Symbolchart. Zu diesem Zweck wird geprüft, ob es eine Linie gibt, die einen der Punkte darstellt, die das Vorhandensein des Indikators anzeigen. Dann prüfen wir, ob es ein Bewegungsobjekt dieses Indikators gibt. Wenn es nicht vorhanden ist, erstellen wir es. Der Indikator wird also nicht auf dem Chart angezeigt, aber ein Rest davon ist immer noch auf dem Chart zu sehen, nämlich als Bewegungsobjekt

Der zweite Schritt erfolgt bei der Erstellung eines Stop-Levels: Die Order auf dem Server wird mit einem Stop-Level versehen, das auf dem Chart erscheinen soll. In diesem Fall wird das Objekt, das die Bewegung darstellte, entfernt und ein vollständiger Indikator erstellt, der an der entsprechenden Stelle platziert wird und angibt, wo das aktuelle Stopp-Level (Take-Profit oder Stop-Loss) liegt.

Im letzten Schritt positionieren wir den Indikator an der richtigen Stelle. Ein interessantes Detail: Wenn der Stop-Level-Indikator nur ein Objekt ist, das die Möglichkeit einer Bewegung darstellt, dann ist der Punkt, an dem er platziert wird, genau der Preis des Auftrags oder der Position. Mit anderen Worten, das Bewegungsobjekt, das die Erstellung von Take-Profit und Stop-Loss ermöglicht, ist mit der Preislinie des Auftrags oder der Position verbunden, zu der es gehört. Auf diese Weise lässt sich leicht feststellen, ob bei einem Auftrag oder einer Position eines der Stopp-Preise fehlt.

Das ist im Grunde das, was wir tun müssen: Wenn wir auf das Objekt klicken, das die Bewegung anzeigt, wird wie üblich ein Geist (ghost) erstellt, und gleichzeitig wird auch eine Darstellung des kompletten Indikators erstellt. Dies geschieht ohne Hinzufügen oder Ändern von Code. Von nun an können wir die Stopp-Preise wie gewohnt verschieben und anpassen. Aber solange wir nicht auf einen bestimmten Punkt klicken, gibt es die Stopp-Order nicht. Dies wird im Demo-Video am Ende des Artikels deutlich gezeigt, wo ich zeige, wie das System auf einem echten Konto funktioniert.

Es scheint zwar alles in Ordnung zu sein, aber hier gibt es einige Ungereimtheiten, die uns zwingen, den Code an einigen Stellen zu erstellen bzw. zu ändern. Wir werden es im nächsten Thema sehen, da dieser Schritt nun abgeschlossen ist.


2.0.3. Lösung für die Unannehmlichkeiten von schwebenden Indikatoren

Die erste Unannehmlichkeit tritt auf, wenn wir uns im Modus des schwebenden Indikators befinden: Er befindet sich auf dem Chart, aber nicht auf dem Server. Weitere Informationen finden Sie in den Artikeln Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 26) und (Teil 27), in denen ich zeige, wie der schwebende Indicator funktioniert und wie er implementiert wurde. Diese Indikatoren sind nützlich und werden daher nicht aus dem EA entfernt. Sie passen jedoch nicht in das oben beschriebene System, da sie anders funktionieren als die Indikatoren, die die auf dem Handelsserver vorhandenen Aufträge und Positionen tatsächlich darstellen. Um die Probleme zu lösen, die bei der Verwendung eines schwebenden Indikators auftreten, müssen wir die Funktion DispatchMessage aufrufen und die Dinge dort anpassen, wie unten gezeigt.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;

// ... Internal code...
                        
        switch (id)
        {

// ... Internal code...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_TYPE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1)));
                                                m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1)));
                                                m_Selection.ticket = 0;
                                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                        } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam));
                                        break;
                                case EV_DS:
                                        if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam));
                                        break;
                                case EV_CLOSE:
                                        if (ticket == def_IndicatorFloat)
                                        {
                                                macroGetDataIndicatorFloat;
                                                RemoveIndicator(def_IndicatorFloat, it);
                                                if (it != IT_PENDING) UpdateIndicators(def_IndicatorFloat, (it == IT_TAKE ? 0 : m_Selection.tp), (it == IT_STOP ? 0 : m_Selection.sl), m_Selection.vol, m_Selection.bIsBuy);
                                        }else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)

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

Durch die oben genannten Änderungen werden praktisch alle anderen Probleme im Zusammenhang mit der Einrichtung von Floating-Indikatoren beseitigt, da jetzt die Daten des Floating-Indikators auf dieselbe Weise angepasst werden können wie die Daten auf dem Handelsserver. Doch damit sind unsere Probleme nicht vollständig gelöst. Wir müssen eine weitere Unannehmlichkeit beseitigen, die schon seit langem besteht. Um dies zu erörtern, lassen Sie uns zum nächsten Thema übergehen, denn es verdient eine eigene Diskussion.


2.0.4. Der Nachteil eines negativen Take-Profit

Der letzte Nachteil, den wir in diesem Artikel erörtern, ist, dass der Take-Profit-Wert oft negativ konfiguriert werden kann, und dies geschieht schon seit geraumer Zeit. Für das Handelssystem macht dies jedoch keinen Sinn: Wenn Sie versuchen, den Wert an den Server zu senden, wird eine Fehlermeldung zurückgegeben. Daher müssen wir dies beheben und auch ein anderes Problem lösen, nämlich das, dass bei einem schwebenden Auftrag der Stopp-Wert ins Positive geändert werden kann.

Der EA erlaubt dies jetzt, und was noch schlimmer ist, das Auftragssystem zeigt an, dass dieser Wert auf dem Server liegt, während der Server in Wirklichkeit einen Fehler zurückgibt und der EA ihn einfach ignoriert. Bei schwebenden Aufträgen ist das Problem komplizierter, denn bei Positionen sollte das Verhalten anders sein, und dieser Fehler ist noch nicht behoben worden. Sobald wir die Möglichkeit haben, die Stopp-Preise direkt auf dem Chart zu definieren, sollte dieser Nachteil verschwinden.

An dieser Stelle sei erwähnt, dass wir im Falle einer offenen Position einen Stop-Loss mit einem positiven Wert haben können, was bedeutet, dass wir bei Auslösung des Stop-Loss den entsprechenden Wert auf unserem Konto buchen können. Bei einem schwebenden Auftrag wird dies jedoch ein Fehler sein, der den Server daran hindert, einen korrekten Auftrag zu erstellen. Um dieses Problem zu lösen, müssen wir den Wert der Gewinnmitnahme überprüfen: Wenn er gleich oder kleiner als 0 wird, müssen wir verhindern, dass er sich auf einen kleineren Wert ändert. Auch bei schwebenden Aufträgen sollte der Stop-Loss nicht größer als 0 sein dürfen. In der Tat zwingen wir den EA, einen Mindestwert zu verwenden, wenn die Bedingung 0 erfüllt ist. In diesem Fall macht der Auftrag oder die Position für das Handelssystem einen gewissen Sinn, aber es macht keinen Sinn, eine Position mit einem Stop-Loss oder Take-Profit in Höhe des Eröffnungskurses zu eröffnen.

Um dies so einfach wie möglich zu machen, müssen wir eine Variable im System anlegen, die unten zu sehen ist:

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
                        bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl,
                        MousePrice;
}m_Selection;

Warum ändern wir nicht einfach den Preispunkt der Mauslinie? Der Grund dafür ist, dass für die korrekte Manipulation der Maus ein Systemaufruf verwendet werden muss, d. h. die Mauspositionswerte müssten über die WINDOWS-API manipuliert werden, und das würde uns zwingen, die Verwendung externer DLLs zu aktivieren, und das möchte ich nicht tun. Auf diese Weise ist es einfacher, einen lokalen Wert innerhalb des EA zusammenzustellen, und die hervorgehobenen Daten werden diesen Wert für uns speichern.

Dieser Wert wird an drei verschiedenen Stellen verwendet. An erster Stelle steht die Bewegungsfunktion. Der nachstehende Code zeigt genau, wo dies der Fall ist:

void MoveSelection(double price)
{
        if (m_Selection.ticket == 0) return;
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        UpdateIndicators(m_Selection.ticket, price, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateIndicators(m_Selection.ticket, m_Selection.tp, price, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_PENDING:
                        PositionAxlePrice(m_Selection.ticket, IT_PENDING, price);
                        UpdateIndicators(m_Selection.ticket, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.vol, m_Selection.bIsBuy);
                        m_Selection.MousePrice = price;
                        break;
        }
        if (Mouse.IsVisible())
        {
                m_TradeLine.SpotLight(macroMountName(m_Selection.ticket, m_Selection.it, EV_LINE));
                Mouse.Hide();
        }
}

Warum packen wir nicht alles in die obige Funktion, da sie für das Verschieben von Stop-Order-Punkten verantwortlich ist? Der Grund dafür ist, dass wir einige Berechnungen durchführen müssen, um die Preise von Take-Profit oder Stop-Loss korrekt festzulegen, und es ist viel einfacher, dies an einem anderen Punkt zu tun. Wir müssen diesen Wert auch an einer anderen Stelle ändern, also hier ist die zweite Stelle, an der auf den Wert verwiesen wird. Siehe den nachstehenden Code:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,

// ... Internal code ....      
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse button click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                        m_Selection.it = IT_PENDING;
                                        m_Selection.pr = price;
                                }
                                m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
                                m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));
                                m_Selection.bIsBuy = bKeyBuy;
                                m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy);
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0);
                                        bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price);
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(m_Selection.MousePrice); else MoveSelection(price);
                        }
                        break;

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

Dadurch haben wir ein vorhersehbares Verhalten im System. Es gibt einen weiteren Punkt, an dem der Wert referenziert wird, aber aufgrund der Komplexität habe ich beschlossen, das Ganze so zu ändern, dass das Makro nicht mehr existiert. Jetzt wird es eine Funktion sein. Die neue Aktualisierungsfunktion ist also unten dargestellt:

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0, bPen = true;
                                
        if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
        {
                bPen = (pr = macroGetPrice(ticket, IT_RESULT, EV_LINE)) == 0;
                if (bPen && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                {
                        CreateIndicator(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                        ChartRedraw();
                }
                pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                SetTextValue(ticket, IT_PENDING, vol);
        }
        b0 = UpdateIndicatorsLimits(ticket, IT_TAKE, tp, vol, pr, isBuy, bPen);
        b0 = (UpdateIndicatorsLimits(ticket, IT_STOP, sl, vol, pr, isBuy, bPen) ? true : b0);
        if (b0) ChartRedraw();
}

Die hervorgehobenen Punkte ersetzen das vorherige Makro, aber da ich gesagt habe, dass der erforderliche Code viel komplizierter ist, wollen wir sehen, worauf sich der dritte und letzte Punkt tatsächlich bezieht. Im obigen Code gibt es keinen Unterschied zwischen den Indikatoren von Take-Profit und Stop-Loss: Sie werden beide gleich behandelt. Werfen Sie einen Blick auf den unten stehenden Code.

inline bool UpdateIndicatorsLimits(ulong ticket, eIndicatorTrade it, double price, double vol, double pr, bool isBuy, bool isPen)
{
        bool b0 = false;
        double d1 = Terminal.GetPointPerTick();
                
        if (
    price  == 0)
        {
                if (macroGetPrice(ticket, it, EV_LINE) > 0) RemoveIndicator(ticket, it);
                if (macroGetPrice(ticket, it, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, it);
        } else if (b0 = (macroGetPrice(ticket, it, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, it);
        switch (it)
        {
                case IT_TAKE:
                        price = (price == 0 ? 0 : (((isBuy ? price - pr : pr - price) > 0) ? price : (isBuy ? pr + d1 : pr - d1)));
                        break;
                case IT_STOP:
                        price = (price == 0 ? 0 : (isPen ? (((isBuy ? price - pr : pr - price) < 0) ? price : (isBuy ? pr - d1 : pr + d1)) : price));
                        break;
        }
        if (m_Selection.it == it) m_Selection.MousePrice = price;
        PositionAxlePrice(ticket, it, (price == 0 ? pr : price));
        SetTextValue(ticket, it, vol, (isBuy ? price - pr : pr - price));
                        
        return b0;
}

Von nun an kann der Take-Profit-Wert eines schwebenden Auftrags nicht mehr falsch sein, da wir eine Grenze für zulässige Werte haben. Es ist nicht mehr möglich, eine schwebende Kauforder zu platzieren und dann den Take-Profit auf einen negativen Wert (d.h. unter den Einstiegspunkt) zu verschieben, da die Berechnung des Indikators von Take-Profit dies verhindert. Der Vorteil, den Code auf diese Weise zu schreiben, ist, dass der Wert von Take-Profit niemals negativ sein kann, egal ob es sich um eine Order oder eine Position handelt, da der EA selbst dies nicht zulässt.

Was nun den Stop-Loss betrifft, so gibt es eine etwas andere Berechnung, bei der wir prüfen, was wir verwalten - einen Auftrag oder eine Position. Wenn es sich um einen Auftrag handelt, wird der Stop-Wert niemals positiv sein, und wenn es sich um eine Position handelt, wird der EA einfach alle anderen Bedingungen ignorieren. In diesem Fall muss der EA den vom Händler angegebenen Wert akzeptieren. So können wir jetzt einen positiven Stop-Loss haben, aber nur im Falle von Positionen, ohne dass der Rest des Order-System-Codes Schaden nimmt, und auf diese Weise wird der EA schließlich mit dem Handels-Server interagieren, ohne dass die übermittelten Daten zurückgewiesen werden.


Schlussfolgerung

Nach mehreren Artikeln haben wir schließlich einen Höhepunkt erreicht und verfügen nun über ein fast vollständiges Bestellsystem, das sich an verschiedene Situationen und Marktbedingungen anpassen lässt. Von nun an werden wir in der Lage sein, mit einem vollgrafischen System zu handeln, das neben der Marktanalyse auch die Maus und die Tastatur nutzt, um Geschäfte einzugehen oder zu beenden.

Für diejenigen, die gerade erst angekommen sind und sehen möchten, wie sich das System verhält oder wie es im derzeitigen Entwicklungsstadium aussieht, sehen Sie sich bitte das folgende Video an. Und vielen Dank an alle, die diese Artikelserie bisher verfolgt haben. Aber die Arbeit ist noch nicht zu Ende, und wir haben noch viel zu tun, bis dieser Expert Advisor zu etwas Unvergesslichem wird. Wir sehen uns im nächsten Artikel.



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

Neuronale Netze leicht gemacht (Teil 25): Praxis des Transfer-Learnings Neuronale Netze leicht gemacht (Teil 25): Praxis des Transfer-Learnings
In den letzten beiden Artikeln haben wir ein Tool zur Erstellung und Bearbeitung von Modellen neuronaler Netze entwickelt. Nun ist es an der Zeit, die Einsatzmöglichkeiten der Technologie des Transfer-Learnings anhand praktischer Beispiele zu bewerten.
Neuronale Netze leicht gemacht (Teil 24): Verbesserung des Instruments für Transfer Learning Neuronale Netze leicht gemacht (Teil 24): Verbesserung des Instruments für Transfer Learning
Im vorigen Artikel haben wir ein Tool zum Erstellen und Bearbeiten der Architektur neuronaler Netze entwickelt. Heute werden wir die Arbeit an diesem Instrument fortsetzen. Wir werden versuchen, sie nutzerfreundlicher zu gestalten. Dies mag ein Schritt weg von unserem Thema sein. Aber ist es nicht so, dass ein gut organisierter Arbeitsplatz eine wichtige Rolle bei der Erreichung dieses Ziels spielt?
Neuronale Netze leicht gemacht (Teil 26): Reinforcement-Learning Neuronale Netze leicht gemacht (Teil 26): Reinforcement-Learning
Wir untersuchen weiterhin Methoden des Reinforcement-Learnings. Mit diesem Artikel beginnen wir ein weiteres großes Thema, das Reinforcement-Learning. Dieser Ansatz ermöglicht es den Modellen, bestimmte Strategien zur Lösung der Probleme zu entwickeln. Es ist zu erwarten, dass diese Eigenschaft des Reinforcement-Learnings (Lernen durch Verstärkung) neue Horizonte für die Entwicklung von Handelsstrategien eröffnen wird.
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
In diesem Artikel werde ich die Entwicklung von TabControl fortsetzen und die Anordnung von Tabulatorüberschriften auf allen vier Seiten des Steuerelements für alle Modi der Einstellung der Größe der Überschriften implementieren: Normal, Fixed und Fill To Right (rechts auffüllend).