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

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

MetaTrader 5Beispiele | 27 Oktober 2022, 10:59
279 0
Daniel Jose
Daniel Jose

Einführung

Trotz der Codekorrekturen und -verbesserungen, die in den Artikeln Teil 24 und Teil 25 der Serie „Entwicklung eines Expert Advisors für den Handel von Grund auf“ gezeigt wurden, in denen wir gesehen haben, wie die Robustheit des Systems erhöht werden kann, gab es immer noch ein paar Details zu beachten. Aber nicht, weil sie weniger relevant wären, sondern weil sie wirklich wichtig sind.

Jetzt haben wir einige Fragen, die sich darauf beziehen, wie wir arbeiten wollen und welche Dinge wir während des Handelstages tun. Viele Händler geben einfach einen Auftrag zu einem bestimmten Preis auf und verschieben ihn nicht mehr von diesem Punkt aus. Was auch immer geschieht, sie werden davon ausgehen, dass dies der perfekte Einstiegszeitpunkt ist, und werden den Auftrag nicht verschieben. Sie können die Stopp-Levels verschieben oder sogar löschen, aber sie ändern nicht den Einstiegspunkt.

Daher haben die verbleibenden Mängel im Code keinen Einfluss darauf, wie die Händler tatsächlich arbeiten. Vielleicht erkennen sie sogar, dass das Auftragssystem Mängel aufweist (zum Beispiel die, die wir in diesem Artikel beheben werden). Wer aber gerne dem Preis hinterherläuft und versucht, trotzdem einen Handel einzugehen, aber nicht in den Markt einsteigen will, wird viele Fehler im System erleben. Einige von ihnen können sich einmischen und Transaktionen unsicher machen (um es milde auszudrücken), während andere verdienen und die Händler dem Markt hilflos ausgeliefert sind.


2.0. Umsetzung

Um unsere Reise in diesem Artikel zu beginnen, lassen Sie uns damit beginnen, einen Fehler zu beheben, der den EA zu einer echten Geldvernichtungsmaschine macht. Auch hier gilt: Wenn wir den Einstiegspunkt nicht ständig ändern, werden wir von diesem Problem nicht betroffen sein. Ich empfehle Ihnen jedoch, Ihren Code vorsichtshalber zu aktualisieren. Auch wenn der Fix bereits im angehängten Code implementiert ist, könnte man meinen, dass dies dem EA schadet, weil er an Leistung verliert, was auch stimmt. Aber was ist besser: etwas Leistung zu verlieren oder das Risiko einzugehen, durch einen schlechten Einstieg Geld zu verlieren?


2.0.1. Fehler beim Einstiegspunkt

Dieser Fehler ist der erste, den wir beheben werden, obwohl sie alle auf die eine oder andere Weise behoben werden müssen. Diese ist jedoch bei weitem die katastrophalste von allen. Dies geschieht, wenn wir einen schwebenden Auftrag, z. B. BUY STOP, platzieren und den Einstiegspunkt so verschieben, dass der Auftrag nun vom Typ BUY LIMIT ist. Es scheint hier kein Problem zu geben, aber dieser Fehler ist ziemlich katastrophal, da der EA im derzeitigen Entwicklungsstadium nicht in der Lage sein wird, die Änderung korrekt durchzuführen. In der Tat wollen viele EAs diese Änderung vornehmen, und wenn dies geschieht, sieht man Informationen auf dem Diagramm, aber der Server wird andere Informationen haben. Das System wird nur dann korrekt aktualisiert, wenn die Position geöffnet wird. Bis dahin sind die Daten zwischen dem, was der EA auf dem Chart anzeigt, und dem, was auf dem Server ist, inkohärent.

In einigen Fällen handelt es sich nur um eine Unstimmigkeit, in anderen Fällen ist das Problem eine komplette Katastrophe. Um dies zu verstehen, sollten Sie den Artikel aufmerksam lesen.

Um diesen Fehler zu beseitigen, haben wir eine Lösung, die verschiedene Wege durchlaufen kann, bevor sie angewendet wird. Das Funktionsprinzip ist jedoch immer dasselbe: Der Auftrag wird aus dem Orderbuch entfernt, an eine neue Position verschoben, die Auftragsart wird geändert und der Auftrag wird wieder in das Orderbuch aufgenommen. So sollte es auch gemacht werden, aber wie es gemacht wird, hängt von der jeweiligen Umsetzung ab.

Daher werden wir die einfachste Lösung umsetzen, aber da sie nicht ideal ist, müssen wir uns mit einigen Problemen auseinandersetzen.

Die Lösung besteht darin, die nachstehende Funktion durch Hinzufügen der hervorgehobenen Zeilen zu ändern.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}


Mit dieser Lösung wird das Problem zwar teilweise, aber nicht vollständig gelöst. Bei BUY STOP- und SELL STOP-Aufträgen zum Beispiel wird das Problem durch Hinzufügen dieser einfachen Linien gelöst. Aber für BUY LIMIT und STOP LIMIT wird der Server den Auftrag sofort ausführen, sobald wir den Einstiegspunkt ändern. Noch schlimmer ist, dass wir in eine Verlustposition eröffnen. Wenn der Auftrag als leerer Auftrag (mit Profit-Target oder Stop-Loss-Level) konfiguriert ist und der Stop-Loss-Punkt außerhalb der Preisgrenzen liegt, wird der Server den Auftrag nicht nur sofort ausführen, sondern ihn auch sofort danach schließen, was für unser Handelskonto ein komplettes Desaster bedeuten würde. Deshalb ist es so schwierig, Handelssysteme zu entwickeln. Wir führen mehrere Tests auf einem Demokonto durch, und wenn alles zu funktionieren scheint, gehen wir zu einem echten Konto über, bei dem wir anfangen, Geld zu verlieren, ohne zu wissen, was wirklich vor sich geht.

Ich wiederhole noch einmal: Der Fehler betrifft NICHT den Fall, dass ein Einstiegspunkt einmal gesetzt und nie geändert wird. Das Problem tritt auf, wenn der Händler den Punkt verschiebt.

Eigentlich funktionieren STOP-Order gut. Nun müssen wir das Problem mit den schwebenden Aufträgen LIMIT-Order lösen. Auch wenn dieses Problem leicht zu lösen scheint, muss man eines verstehen: Es gibt KEINE perfekte Lösung, und die Lösung, die für den Systementwickler am besten funktioniert, ist möglicherweise nicht die, die für Sie funktioniert..

Ich werde hier eine der möglichen Lösungen für dieses Problem aufzeigen. Die Lösung wird in der gleichen Funktion wie oben gezeigt implementiert. Hier ist der neue Code:

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Dies geschieht folgendermaßen: Wenn wir den Einstiegspunkt eines schwebenden Auftrags ändern wollen, prüfen wir, ob die Order im Orderbuch (Depth of Market) vom Typ STOP LIMIT oder BUY LIMIT ist. Ist dies nicht der Fall, wird die Ausführung an einer anderen Stelle des Codes fortgesetzt. Wenn dies der Fall ist, erfassen wir sofort den aktuellen Preis des Vermögenswerts und verwenden die folgenden Kriterien: Bei einem KAUF-Auftrag verwenden wir den aktuellen ASK-Wert, bei Verkaufsaufträgen den BID-Preis. Dies ersetzt die alte Methode, bei der der LAST-Wert verwendet wurde, aber da er in einigen Märkten nicht verwendet wird, werden wir ihn nicht als Referenz verwenden. Prüfen wir dann, ob der Auftrag im Orderbuch ungültig wird oder ob er nur geändert wird.

Wenn der Auftrag noch gültig ist, ignoriert das System den Validierungscode und geht zu dem Teil über, in dem der Auftrag geändert wird. Wenn der Auftrag in der Markttiefe jedoch ungültig ist, führt das System den folgenden Code aus:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
break;


Der obige Code ändert jedoch nur die Aufträge SELL LIMIT und BUY LIMIT in SELL STOP bzw. BUY STOP. Was ist, wenn wir diese Typen wieder auf die ursprünglichen zurücksetzen oder eine solche Änderung einfach verhindern wollen?

Wenn wir nicht wollen, dass das System den Typ des ausgeführten Auftrags ändert, müssen wir nur das hervorgehobene Fragment durch den folgenden Code ersetzen:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (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.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Mit diesem Code wird verhindert, dass die Auftragsart geändert wird. Man kann den Zeitpunkt ändern, zu dem ein schwebender Auftrag ausgeführt wird, aber ein LIMIT-Auftrag kann nicht in einen STOP-Auftrag umwandeln werden oder umgekehrt. Wenn Sie den Kurs weiter verfolgen und den Einstieg zu einem bestimmten Zeitpunkt erzwingen wollen, verwenden Sie den unten stehenden Code. Dies ist der Code, der im EA verwendet werden soll.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType


Wichtiger Hinweis: Seien Sie bei der Arbeit mit diesem Code wegen des Makros ForceNewType vorsichtig. Dieses Makro enthält ein Break, das, wenn es ausgeführt wird, dazu führt, dass der Code den 'case'-Block verlässt. Daher sollten Sie bei der Änderung dieses Blocks äußerst vorsichtig sein.

Das System wird nicht mehr den Fehler haben, den Einstiegspunkt zu verschieben, aber wir haben andere Probleme zu lösen. Ich habe Ihnen gezeigt, wie Sie das Problem durch eine Änderung oder Beibehaltung der gleichen Auftragsart beheben können — wählen Sie die für Sie am besten geeignete Variante. Bedenken Sie, dass jede dieser Lösungen ihre Vor- und Nachteile hat. Aber ich werde nicht ins Detail gehen. Ich zeige nur, wie man das System korrigieren und umsetzen kann.

Das Ergebnis dieser Änderungen ist in dem folgenden Video zu sehen:



2.0.2. Vorbereitung auf die Zukunft

Die obige Änderung löst das Problem, aber es gibt noch mehr, was getan werden kann. Hier werde ich den Beginn dieser Veränderung zeigen. Wenn man sich das Auftragssystem des EAs anschaut, gibt es noch viel Raum für Verbesserungen. Es sind nur wenige Änderungen erforderlich, die ich Ihnen erläutern möchte, damit Sie selbst entscheiden können, welcher Weg für Sie am besten geeignet ist, denn jeder Händler hat seine eigene Art, auf dem Markt zu handeln. Ich möchte nicht, dass Sie sich verpflichtet fühlen, das System zu nutzen, das ich Ihnen zeigen werde. Stattdessen möchte ich eine Grundlage schaffen, auf der jeder einen eigenen EA entwickeln kann.

Kommen wir also zur nächsten Tatsache: Ab Teil 18 habe ich gezeigt, wie man ein Auftragssystem entwickelt, das für diejenigen, die mit einem bestimmten Vermögenswert handeln, einfach zu handhaben ist. Aber in Teil 20 hat das Auftragssystem visuelle Elemente erhalten, denn irgendwann wird Chart Trade für den Handel überflüssig, da alles vom Auftragssystem selbst angezeigt wird, sodass Sie alles direkt auf dem Chart ändern und konfigurieren können. Um an diesen Punkt zu gelangen, müssen wir irgendwo anfangen, und wir werden es jetzt tun.

Wie wäre es, das gehandelte Volumen direkt im Auftrag zu ändern, ohne die Aufträge aus dem Chart entfernen zu müssen, um das Volumen im Chart Trade zu ändern und dann die Order wieder in den Chart zu setzen? Interessant, nicht wahr? Wir werden diese Funktion jetzt implementieren. Es ist in verschiedenen Szenarien sehr hilfreich, aber Sie sollten lernen und verstehen, wie man das System benutzt, denn Sie werden es auf keiner anderen Plattform finden. Um ehrlich zu sein, habe ich noch nie einen EA gesehen, der eine solche Funktionalität hat. Sehen wir uns an, was Sie tun können, um diese Funktionsweise in jedem EA zu haben.

Definieren wir zunächst einen neuen Indikatorindex.

#define def_IndicatorFloat      3


Wenn ein schwebender Auftrag diesen Wert als Ticket erhält, kann er auf eine völlig andere Weise bearbeitet werden. Alles, was vorher existierte, bleibt im Auftragssystem, während wir nur einen neuen Index 1 hinzufügen.

Danach werden wir dem System ein neues Objekt hinzufügen:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;


Dieses Objekt ermöglicht immer ein paar Dinge, solange der Auftrag noch nicht abgeschlossen ist.

Nun gehen wir zur Klasse C_Object_BitMap über, um sie zu bearbeiten. Fügen wir einige Definitionen hinzu:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled


Wir müssen wissen, was in dieser Klasse vor sich geht. Fügen wir also die folgenden Funktionen hinzu:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}


GetStateButton gibt den Zustand der Schaltfläche zurück. MetaTrader 5 ändert den Status, sodass wir keine zusätzlichen Schritte implementieren müssen, sondern nur herausfinden, ob der Wert der Schaltfläche True oder False ist. Aber es kann passieren, dass der Staat nicht das widerspiegelt, was wir wollen. Verwenden wir dann SetStateButton, um den Status so zu setzen, dass er den tatsächlichen Status widerspiegelt, den der Handelsserver und der EA sehen.

Eine weitere einfache Änderung findet sich in der Klasse C_Object_Edit:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

Sie zeigt an, ob der Wert bearbeitet werden kann oder nicht. Wir möchten die Auftragsvolumina direkt im Chart ändern können, ohne Chart Trade zu verwenden. Jeder schwebender Auftrag, der erstellt wird, ist immer schreibgeschützt, aber wir werden ein System erstellen, das dies ändert.

Kehren wir also zu C_IndicatorTradeView zurück und implementieren einige weitere Änderungen. Wir werden eine neue Funktion für das System erstellen. Sie lautet wie folgt:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat


Wenn diese Funktion aufgerufen wird, werden alle Indikatorobjekte umbenannt, d.h. der Wert, der auf den Ticket des Auftrags zeigt, wird durch einen anderen Wert ersetzt. In diesem Fall handelt es sich um den Indikator, den wir zu Beginn dieses Themas besprochen haben. Wir haben noch eine weitere Frage. Ich verwende keine Struktur, um die Liste der Indikatorobjekte zu pflegen, sondern mache es auf eine andere Weise. Auf diese Weise überlassen wir MetaTrader 5 die Betreuung dieser Liste für uns. Aus diesem Grund können wir nicht unbegrenzt schwebenden Aufträge erstellen, da wir auf einen einzigen schwebenden Auftrag beschränkt sind. Dies kann mit der folgenden Zeile überprüft werden:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;


Die Überprüfung ist einfach: Wenn sich die Indikatorzeile irgendwo befindet, gibt das Makro einen Wert ungleich 0 zurück, sodass wir wissen, dass es bereits einen Indikator gibt, der das reservierte Ticket verwendet. Dies wird später wichtig sein, damit der EA die Daten des Indikators wiederherstellen kann, für den der Antrag abgelehnt wurde. MetaTrader 5 ändert den Status des Bitmap-Objekts automatisch, daher müssen wir den Aufrufer über den Fehler informieren.

Die nächste erforderliche Änderung betrifft die Funktion, die die Indikatoren erstellt:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator


Alle hervorgehobenen Teile sind hinzugefügt worden, um unser neues System zu unterstützen. Im Grunde genommen erstellen wir hier ein Kontrollkästchen, das immer auf true gesetzt wird, was bedeutet, dass der Auftrag sofort in das Orderbuch gestellt wird. Ich wollte diese Art des Handels nicht ändern, aber es reicht nicht aus, den Wert des Kontrollkästchens von „true“ auf „false“ zu ändern, um zu verhindern, dass die Aufträge direkt erteilt werden. Diese Änderung würde weitere, noch tiefgreifendere Änderungen erfordern, und das Problem ist, dass Sie irgendwann einen Auftrag platzieren und vergessen könnten, das Kontrollkästchen zu aktivieren. Der Einstiegspunkt würde verpasst werden, und Sie würden denken, dass der EA defekt ist, während es in Wirklichkeit nur an der Vergesslichkeit lag. Um dies zu vermeiden, werden die schwebenden Aufträge standardmäßig direkt in das Orderbuch übernommen, sodass Sie ihren Status ausdrücklich ändern müssen.

Die nächste wirklich wichtige Funktion ist unten dargestellt:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType


Die hervorgehobenen Codeteile bewirken etwas Interessantes: Sie aktualisieren nur die Werte, die im Selektor verwendet werden, aber diese Werte werden tatsächlich im Indikator selbst gespeichert. Es kann auch vorkommen, dass wir das System auf eine allgemeinere Art und Weise bewegen, sodass wir diese Werte im Selektor angeben müssen, damit die Funktion, die die Positionsberechnungen durchführt, die richtigen Werte angibt.

Es gibt etwas in dieser Funktion, das vielleicht keinen Sinn ergibt. Es ist für die Erstellung und Änderung der Daten eines schwebenden Auftrags zuständig, aber wenn Sie sich den Ablauf ansehen, werden Sie keinen Punkt finden, an dem der schwebende Auftrag an das Orderbuch zurückgegeben wird. Sie können das Wertvolumen des Auftrags direkt im Chart verschieben, ändern und anpassen, aber Sie können nicht sehen, wie der Auftrag in den Chart zurückkehrt.

Das ist eine Tatsache. Das gesamte System zum Ändern und Anlegen von schwebenden Aufträgen ist in der obigen Funktion implementiert. Seltsamerweise stellt diese Funktion den Auftrag nicht ins Orderbuch zurück, nur weil wir es wollen, sondern weil sie tatsächlich eine Anfrage stellt, wie unten gezeigt. Um es nicht zu kompliziert zu machen, zeige ich nur den Teil, der für einen Antrag auf Platzierung der Order in der Markttiefe verantwortlich ist.

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

// ... Internal code...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

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

Sehen Sie, wie sich das System selbst aufbaut: Wir programmieren immer weniger, während das System immer größer wird.

Der hervorgehobene Code hat etwas mit dem Indikator zu tun, den wir zu Beginn des Themas erstellt haben. Obwohl alles gut zu funktionieren scheint, gibt es einige Dinge, die wir später ändern werden, denn wenn der schwebende Auftrag in das Orderbuch zurückkehrt, hat er den Nachteil, ein Tageshandelsauftrag zu sein, sodass er am Ende des Tages geschlossen wird. Sie wird später geändert werden, aber Sie sollten sich dessen bewusst sein. Jetzt sind Sie vielleicht verwirrt und verstehen immer noch nicht, wie der schwebende Auftrag tatsächlich in das Orderbuch gelangt und es wieder verlässt, wenn wir auf das Kontrollkästchen klicken. Siehe das nachstehende Diagramm:

Achten Sie darauf, dass alle Anrufe von der gleichen Stelle kommen. Wir haben einen Auftrag aus der Markttiefe entfernt, aber er wird weiterhin im Chart angezeigt. Alle Manipulationen werden wie in den vorherigen Artikeln beschrieben durchgeführt. Wenn Sie jedoch versuchen, einen bestimmten Zeitpunkt zu finden, zu dem der Auftrag in die Markttiefe zurückkehrt, können Sie sich im Code ein wenig verirren. Wenn Sie sich das Diagramm ansehen, können Sie erkennen, dass der Aufruf von der Funktion DispatchMessage kommt, da dies die einzige Stelle ist, die die Funktion SetPriceSelection aufruft. Aber wenn wir uns die Funktion SetPriceSelection ansehen, gibt es keinen Hinweis auf die Erstellung eines Auftrags mit dem im Floating-System verwendeten Index. Aber achten Sie auf eine Sache. Wir haben die Auftragserstellung nach Index 0, und das ist genau das, was wir verwenden. Wir ändern das Auftragsticket und teilen mit, dass es das Ticket mit Index 0 sein wird — auf diese Weise wird der Auftrag erstellt. Wie das funktioniert, zeigt der folgende Code.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

Der Code ist bis auf die hervorgehobene Zeile perfekt. Derzeit gibt es keine Möglichkeit, dies zu beheben. Dies wird im nächsten Artikel geschehen, da wir einige Änderungen an der Klasse selbst vornehmen müssen.

Das folgende Video zeigt das Ergebnis der Änderungen. Achten Sie darauf, wie das Volumen geändert wird und wie ein neuer Auftrag zu dem angegebenen Zeitpunkt gesendet wird. Der EA ist jetzt viel einfacher zu bedienen.



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

Beigefügte Dateien |
Marktmathematik: Gewinn, Verlust und Kosten Marktmathematik: Gewinn, Verlust und Kosten
In diesem Artikel zeige ich Ihnen, wie Sie den Gesamtgewinn oder -verlust eines Handels einschließlich Provision und Swap berechnen können. Ich werde das genaueste mathematische Modell zur Verfügung stellen und es verwenden, um den Code zu schreiben und ihn mit der Norm zu vergleichen. Außerdem werde ich versuchen, in die Hauptfunktion von MQL5 zur Berechnung des Gewinns einzudringen und alle erforderlichen Werte aus der Spezifikation zu ermitteln.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 25): Herstellen eines robusten Systems (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 25): Herstellen eines robusten Systems (II)
In diesem Artikel werden wir den letzten Schritt zu einem schnellen EA machen. Machen Sie sich also auf eine längere Lektüre gefasst. Um unseren Expert Advisor zuverlässig zu machen, werden wir zunächst alles aus dem Code entfernen, was nicht Teil des Handelssystems ist.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 27): Der Zukunft entgegen (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 27): Der Zukunft entgegen (II)
Gehen wir nun zu einem vollständigeren Auftragssystem direkt auf dem Chart über. In diesem Artikel zeige ich einen Weg, das Auftragssystem zu reparieren, oder besser gesagt, es intuitiver zu gestalten.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 24): Herstellen eines robusten Systems (I) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 24): Herstellen eines robusten Systems (I)
In diesem Artikel werden wir das System zuverlässiger machen, um eine robuste und sichere Nutzung zu gewährleisten. Eine der Möglichkeiten, die gewünschte Robustheit zu erreichen, besteht darin, den Code so oft wie möglich wiederzuverwenden, damit er ständig in verschiedenen Fällen getestet wird. Aber das ist nur eine der Möglichkeiten. Eine andere Möglichkeit ist die Verwendung von OOP.