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

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 23): Neues Auftragssystems (VI)

MetaTrader 5Handel | 10 Oktober 2022, 16:13
182 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel Entwicklung eines Trading Expert Advisors von Grund auf (Teil 22) haben wir ein System zum Verschieben von schwebenden (Pending) Orders und Positionsstopps entwickelt. Obwohl diese Methode relativ sicher ist (da sie den Stand auf dem Handelsserver widerspiegelt), ist sie nicht die beste Methode, um die Ebenen schnell zu verschieben.

Das Problem ist, dass jedes Mal, wenn wir etwas mit der Maus ändern, dieses Ereignis an den Server gesendet wird, und wir müssen dann auf eine Antwort warten. Das Problem hängt mit der Tatsache zusammen, dass das Ereignis bei jedem Tick gesendet wird, d. h., wenn wir ein Niveau um mehrere Ticks auf einmal verschieben müssten, müssten wir alle Zwischenwerte durchlaufen, was den gesamten Prozess sehr langsam macht. Auf diese Weise führen wir Änderungen am Code durch, um ihn flexibler zu machen und die Ebenen schneller zu ändern.


1.0. Planung

Um die Änderungen umzusetzen, müssen wir eine ganz einfache Sache tun: Wir informieren den Server nicht über alle Änderungen, sondern nur über die gewünschte Änderung. Schon allein dadurch wird alles gut funktionieren, auch wenn wir nicht absolut sicher sein können, dass alles genau so ist, wie wir es tun.

Wir wollen nun sehen, wo wir den Code ändern müssen. Wir verwenden eine eigene Funktion (siehe unten):

#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP))
                void MoveSelection(double price, uint keys)
                        {
                                static string memStr = NULL;
                                static ulong ticket = 0;
                                static eIndicatorTrade it;
                                eEventType ev;
                                double tp, sl, pr;
                                bool isPending;
                                
                                string sz0 = m_TradeLine.GetObjectSelected();
                                
                                if (sz0 != NULL)
                                {
                                        if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev);
                                        isPending = OrderSelect(ticket);
                                        switch (it)
                                        {
                                                case IT_TAKE:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP));
                                                        else ModifyPosition(ticket, price, macroGetPrice(IT_STOP));
                                                        break;
                                                case IT_STOP:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price);
                                                        else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price);
                                                        break;
                                                case IT_PENDING:
                                                        pr = macroGetPrice(IT_PENDING);
                                                        tp = macroGetPrice(IT_TAKE);
                                                        sl = macroGetPrice(IT_STOP);
                                                        ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr));
                                                        break;
                                        }
                                };
                        }
#undef macroGetPrice

Aber zusammen mit den Änderungen innerhalb der Funktion, müssen wir auch einige Änderungen im Zusammenhang mit Maus-Ereignissen zu implementieren. Darauf werden wir uns zunächst konzentrieren.

Die hervorgehobenen Segmente müssen durch etwas anderes ersetzt werden, um die neuen Positionen zu repräsentieren, die verwendet werden sollen. Aber alle Änderungen sollten für den Nutzer verständlich sein.

Ich habe einen effizienten Weg gefunden, alles leicht verständlich und gleichzeitig funktional zu gestalten, ohne große Änderungen an der gesamten Codestruktur vorzunehmen. Damit wird ein „Geisteretikett“ (ghost indication label) erstellt, das erst bei Bedarf sichtbar wird. Glücklicherweise bietet der MetaTrader 5 eine sehr einfache Möglichkeit, dies zu tun. Daher wird dieser Artikel für diejenigen, die bereits mit dem Material aus den früheren Artikeln dieser Reihe vertraut sind, sehr leicht zu verstehen sein.


2.0. Umsetzung

Um ein Geisteretikett zu implementieren, erstellen wir es einfach mit einem echten Etikett. Dies ist der genaue Schatten eines echten Etiketts, bis wir mit den Preisen arbeiten, wie wir es im vorherigen Artikel gezeigt haben. Zu diesem Zeitpunkt erscheint das Geisteretikett im Chart, sobald sich das echte Etikett bewegt. Auf diese Weise können Sie leicht vergleichen, was geschieht, und erkennen, ob Sie Änderungen vornehmen sollten oder nicht.


2.0.1. Erstellen eines Etiketts für das Geisteretikett

Alle Änderungen werden in der Klasse C_IndicatorTradeView implementiert. Beginnen wir mit der Definition von drei neuen Richtlinien:

#define def_IndicatorGhost      "G"
#define def_IndicatorReal       "R"
#define def_IndicatorGhostColor clrDimGray

Damit wird der MetaTrader 5 für uns arbeiten. Die Regel lautet: Zuerst erstellen wir ein Geisteretikett und dann ein echtes. MetaTrader 5 achtet also darauf, das Geisteretikett erst dann anzuzeigen, wenn wir es wirklich brauchen. Da dies vom MetaTrader 5 selbst erledigt wird, sparen wir eine Menge Programmierarbeit.

Ein wichtiges Detail: Wenn Sie die Farbe des Geisterbildes ändern möchten, ändern Sie einfach die Farbe, die im ausgewählten Teil angegeben ist.

Der nächste Schritt besteht daher darin, die Funktion zu ändern, mit der eindeutige Namen erzeugt werden können.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}

Die hervorgehobenen Teile wurden gegenüber der vorherigen Version hinzugefügt oder geändert. Wir können MetaTrader 5 eindeutige Namen generieren lassen. Wir brauchen uns also keine Sorgen zu machen. Beachten Sie, dass ich den Ereignissen Werte hinzugefügt habe. Ich habe dies getan, um zu verhindern, dass das Geisteretikett Ereignisse empfängt und versucht, die Kontrolle zu übernehmen.

Der nächste Schritt ist offensichtlich - wir müssen das Etikett selbst erstellen:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2, cor3;
                                string sz0, sz1;
                                
                                switch (it)
                                {
                                        case IT_TAKE    :
                                                cor1 = clrForestGreen;
                                                cor2 = clrDarkGreen;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_STOP    :
                                                cor1 = clrFireBrick;
                                                cor2 = clrMaroon;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_PENDING:
                                                cor1 = clrCornflowerBlue;
                                                cor2 = clrDarkGoldenrod;
                                                cor3 = def_ColorVolumeEdit;
                                                break;
                                        case IT_RESULT  :
                                        default:
                                                cor1 = clrDarkBlue;
                                                cor2 = clrDarkBlue;
                                                cor3 = def_ColorVolumeResult;
                                                break;
                                }
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor);
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2);
                                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                                if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor);
                                m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1);
                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 92, 22);
                                                m_BackGround.Size(sz1, 92, 22);
                                                break;
                                        case IT_RESULT:
                                                m_BackGround.Size(sz1, 84, 34);
                                                break;
                                }
                                m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose);
                                m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0);
                                m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0);
                                m_EditInfo1.Size(sz0, 60, 14);
                                m_EditInfo1.Size(sz1, 60, 14);
                                if (it != IT_RESULT)
                                {
                                        m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor);
                                        m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                                        m_BtnMove.Size(sz1, 21, 21);
                                }else
                                {
                                        m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0);
                                        m_EditInfo2.Size(sz1, 60, 14);
                                }
                        }

Alle hervorgehobenen Zeilen erzeugen Geisteretikette. Eine Sache mag seltsam erscheinen: warum wir nicht alle Elemente reproduzieren. Tatsächlich ist das Geisteretikett keine exakte Kopie des echten Etiketts, sondern nur sein Schatten. Daher brauchen wir nicht alle Elemente zu erstellen. Das echte Etikett ist dasjenige, das definiert, was passieren soll, während das Geisteretikett nur als Referenz dafür dient, was der Handelsserver sieht.

Jetzt kommt der Teil, der eine genauere Untersuchung erfordert und bei dem MetaTrader 5 wirklich hart arbeiten wird. Man könnte meinen, dass das Anordnen von Objekten an den richtigen Stellen eine Menge Arbeit bedeutet, aber sehen Sie sich an, was sich im Quellcode tatsächlich geändert hat.

#define macroSetAxleY(A, B)     {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y);                              \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y);                                 \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y);                                 \
                m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0));      \
                m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y));         \
                m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1);  \
                                }
                                                                        
#define macroSetAxleX(A, B, C)  {                                                       \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B);      \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B);         \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3);     \
                m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21);    \
                m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80);      \
                m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21);  \
                                }                                                                               
//+------------------------------------------------------------------+
inline void ReDrawAllsIndicator(void)
                        {
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, -1);
                                ulong           ticket;
                                double          price;
                                eIndicatorTrade it;
                                eEventType      ev;
                                
                                for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev))
                                        PositionAxlePrice(ticket, it, price);
                        }
//+------------------------------------------------------------------+
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price));
                                macroSetAxleY(it, true);
                                macroSetAxleY(it, false);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 110; break;
                                        case IT_STOP: desl = 220; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true);
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false);
                        }
#undef macroSetAxleX
#undef macroSetAxleY

Ist das alles? Ändern wir einfach die Makros? Es ist zwar nicht nötig, den gesamten Code neu zu erstellen, wir passen ihn lediglich an. Alles, was wir tun müssen, ist MetaTrader 5 den Namen des Objekts mitzuteilen, das wir manipulieren wollen, und MetaTrader 5 erledigt den Rest für uns. Es ist nicht notwendig, eine Reihe von Funktionen dafür zu erstellen. Wir fügen einfach die hervorgehobenen Teile hinzu.

Eine weitere Funktion, die hier zu ändern ist, wird unten gezeigt:

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;
                                
        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Hier haben wir die ausgewählten Segmente hinzugefügt. Auf diese Weise wird unser Geisteretikett ausgewählt. Es spiegelt genau wider, was mit dem echten Etikett geschieht. Es spiegelt es sogar zu gut wider, so gut, dass wir noch ein paar Dinge implementieren müssen, damit es richtig funktioniert. Der Code ist nicht falsch, aber das Geisteretikett ist zu eng mit dem echten Etikett verbunden, was wir eigentlich vermeiden sollten.

Die letzte Funktion, die hier zu ändern ist, wird unten gezeigt:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                                {
                                        macroDestroy(IT_RESULT, true);
                                        macroDestroy(IT_RESULT, false);
                                        macroDestroy(IT_PENDING, true);
                                        macroDestroy(IT_PENDING, false);
                                        macroDestroy(IT_TAKE, true);
                                        macroDestroy(IT_TAKE, false);
                                        macroDestroy(IT_STOP, true);
                                        macroDestroy(IT_STOP, false);
                                } else
                                {
                                        macroDestroy(it, true);
                                        macroDestroy(it, false);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
                        }

Die hervorgehobenen Fragmente haben sich geändert, hier gibt es nichts Besonderes.


2.0.2. Trennen wir das Geisteretikett von der Wirklichkeit

Die im vorherigen Abschnitt vorgenommenen Änderungen erzeugen ein Geisteretikett. Aber wir haben ein Problem — es ist zu nah am echten Objekt. Dies ist etwas, das auf den ersten Blick sehr schwierig umzusetzen ist. Aber wenn wir uns den Code ansehen, sehen wir, dass wir bereits eine Lösung haben. Diese Lösung ist jedoch an der falschen Stelle angesiedelt. Wir müssen den Ort, an dem sich die Lösung befindet, ändern und sie in der ganzen Klasse besser sichtbar machen.

Die Lösung ist im folgenden Code dargestellt:

                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price, tp, sl;
                                bool            isBuy,
                                                        bKeyBuy,
                                                        bKeySell,
                                                        bEClick;
                                long            info;
                                datetime        dt;
                                uint            mKeys;
                                eIndicatorTrade         it;
                                eEventType                      ev;
                                
                                static bool bMounting = false, bIsDT = false, bIsMove = false;
                                static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_MOUSE_MOVE:

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

Die Lösung ist der hervorgehobene Code. Aber wie ist das möglich? Denken Sie daran, dass das System bei der Platzierung eines schwebenden Auftrags die Daten erstellt und manipuliert, sodass Sie am Ende eine Darstellung der Etiketten auf dem Chart haben, die anzeigt, wo der Auftrag platziert wird. Dies geschieht im folgenden Code:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
        if (bKeyBuy != bKeySell)
        {
                if (!bMounting)
                {
                        Mouse.Hide();
                        bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl);
                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange);
                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange);
                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                        bMounting = true;
                }
                tp = price + (bKeyBuy ? valueTp : (-valueTp));
                sl = price + (bKeyBuy ? (-valueSl) : valueSl);
                UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy);
                if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT);
                }else if (bMounting)
                {
                        UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false);
                        Mouse.Show();
                        memLocal = 0;
                        bMounting = false;

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

Je nachdem, ob Sie die SHIFT-Taste zum Kaufen oder die STRG-Taste zum Verkaufen drücken, erstellt das System eine Darstellung des zu erstellenden schwebenden Auftrags. Sie wird direkt auf dem Chart angezeigt. Die Werte, die als Take Profit oder Stop Loss verwendet werden sollen, werden im Chart Trade erfasst, dann bewegen Sie die Darstellung zu dem Punkt, an dem Sie die Order platzieren wollen, und danach teilt ein Klick mit der linken Maustaste dem System mit, dass dort eine Order platziert werden muss. Sobald sich die Maus wieder bewegt, wird die Beschriftung, die den Auftrag darstellt, entfernt, und die Auftragsanzeige bleibt bestehen.

Bislang gab es nichts Besonderes. Aber wenn Sie sich den Code ansehen, werden Sie Folgendes feststellen:

// ... CHARTEVENT_MOUSE_MOVE code....

        }else if ((!bMounting) && (bKeyBuy == bKeySell))
        {
                if (bEClick)
                {
                        bIsMove = false;
                        m_TradeLine.SpotLight();
                }
                MoveSelection(price, mKeys);
        }
break;

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

Wenn wir also keinen schwebenden Auftrag erstellen und die Tasten loslassen, senden wir die Mauspreisposition an ein Etikett, das ausgewählt werden kann. Dies geschieht in der hervorgehobenen Zeile. Sobald wir mit der linken Maustaste klicken, beenden wir diese Übertragung, der Indikator wird abgewählt, und dies ändert den Status einer Variable, die derzeit lokal ist, bIsMove. Aber das sollten wir ändern. Der Zustand dieser Variablen wird nur durch ein anderes Ereignis innerhalb der Funktion DispatchMessage geändert. Dieses Ereignis sieht folgendermaßen aus:

// ... Code ....

        case EV_MOVE:
                if (bIsMove)
                {
                        m_TradeLine.SpotLight();
                        bIsMove = false;
                }else
                {
                        m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                        bIsMove = true;
                }
        break;

Dieser Code ändert den Zustand der Variable bIsMove und ändert gleichzeitig das Etikett, um anzuzeigen, ob es ausgewählt ist oder nicht.

Wenn wir also diese Variable in der gesamten Klasse sichtbar machen, können wir das Geisteretikett von dem echten trennen, und es wird möglich sein, entweder nur das echte oder nur das Geisteretikett zu manipulieren — je nachdem, was Sie am interessantesten finden. Aber hier werden wir das echte Etikett ändern, während das Geisteretikett zeigt, was der Handelsserver sieht.

Auf diese Weise müssen wir nicht viel am Code herumbasteln, sondern nur ein paar Details anpassen. Sobald die linke Maustaste gedrückt und ein Objekt verschoben wurde, wird ein Auftrag zur Änderung eines schwebenden Auftrags oder eines Stop Levels gesendet.

Schauen wir uns an, wie das in der Praxis abläuft. Erstellen wir zunächst eine private Variable.

bool    m_bIsMovingSelect;

Dies entspricht dem, was ich oben erklärt habe. Sie muss jedoch initialisiert werden.

C_IndicatorTradeView() : m_bIsMovingSelect(false) {}

Jetzt gehen wir zu DispatchMessage und verwenden es anstelle von bIsMove.

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

// ... Internal code...

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Internal code...

                                }else if ((!bMounting) && (bKeyBuy == bKeySell))
                                {
                                        if (bEClick)
                                        {
                                                m_bIsMovingSelect = false;
                                                m_TradeLine.SpotLight();
                                        }
                                        MoveSelection(price, mKeys);
                                }
                                break;

// ... Internal code...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {

// ... Internal code....

                                case EV_MOVE:
                                        if (m_bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_bIsMovingSelect = false;
                                        }else
                                        {
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                                m_bIsMovingSelect = true;
                                        }
                                        break;
                        }
                        break;
                }
}

Die Änderungen sind hervorgehoben. So weiß nun die ganze Klasse, wann wir mit einem vom Nutzer gewählten Label arbeiten oder nicht. Auf diese Weise können wir das Geisteretikett vom echten Etikett trennen und erhalten eine genauere Darstellung.


2.0.3. Nur bewegen, was wichtig ist

In den beiden vorangegangenen Themen haben wir einige Korrekturen am System vorgenommen, um ein Geisteretikett zu erhalten. Jetzt müssen wir die Komponenten verschieben, und dafür müssen wir einige Arbeit leisten. Der erste Schritt besteht darin, eine Struktur zu erstellen, in der verschiedene Informationen gespeichert werden, die wir benötigen, um übermäßige Aufrufe zwischen Funktionen zu vermeiden. Die neue Struktur ist unten dargestellt:

struct st00
{
        eIndicatorTrade it;
        bool            bIsMovingSelect,
                        bIsBuy;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_InfoSelection;

Das hervorgehobene Segment gab es schon vorher im Code, aber jetzt ist es Teil der Struktur. Machen Sie sich keine Sorgen. Im weiteren Verlauf wird klarer werden, warum die Struktur diese Elemente aufweist.

Hier geht es um die Funktionen, die geändert wurden und einer Erklärung bedürfen. Die erste davon ist SetTextValue.

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;

        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Wenn wir etwas verschieben, wollen wir nicht, dass das Geisteretikett den neuen Daten folgt, sondern dass es stehen bleibt und anzeigt, wo das Etikett war. Dies kann einfach durch Hinzufügen der hervorgehobenen Punkte geschehen. Das Geisteretikett steht also still, während sich das Etikett frei bewegt. Hier geht es nicht um die Bewegung selbst, sondern um die Angabe der Werte, ​die sich auf dem Server befinden.

Danach folgt der unten abgebildete Bewegungscode:

void MoveSelection(double price)
{
        double tp, sl;
                                
        if (!m_InfoSelection.bIsMovingSelect) return;
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_PENDING:
                        tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr);
                        sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr);
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
        }
}

Achten Sie bitte auf den hervorgehobenen Teil. Die Bewegung passt sich so an, dass das System die Daten korrekt anzeigt. Dies mag seltsam erscheinen, aber die Funktion UpdateINfosIndicators hat eine andere Lösung. Wenn wir es jetzt nicht tun, werden wir später Probleme bekommen. Andere Funktionen sind recht einfach und bedürfen keiner Erklärung.

void SetPriceSelection(double price)
{
        bool isPending;
        if (!m_InfoSelection.bIsMovingSelect) return;
        isPending = OrderSelect(m_InfoSelection.ticket);
        m_InfoSelection.bIsMovingSelect = false;
        m_TradeLine.SpotLight();
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl);
                        else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl);
                        break;
                case IT_STOP:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price);
                        else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price);
                        break;
                case IT_PENDING:
                        ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr));
                        break;
        }
}

Die obige Funktion informiert den Server darüber, was passiert und wie die neuen Daten lauten. Bitte beachten Sie, dass in den hervorgehobenen Zeilen ein Indikator ausgewählt werden muss, da sonst die Anfrage an den Server nicht gestellt wird.

Die letzte empfohlene Funktion ist unten dargestellt:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,
                bKeySell,
                bEClick;
        datetime dt;
        uint    mKeys;
        char    cRet;
        eIndicatorTrade         it;
        eEventType              ev;
                                
        static bool bMounting = false, bIsDT = false;
        static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //Pressed SHIFT
                        bKeySell = (mKeys & 0x08) == 0x08;    //Pressed CTRL
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.ticket = def_IndicatorTicket0;
                                        m_InfoSelection.bIsMovingSelect = true;
                                        m_InfoSelection.pr = price;
                                        bMounting = true;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        MoveSelection(0);
                                        m_InfoSelection.bIsMovingSelect = false;
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                MoveSelection(0);
                                m_InfoSelection.bIsMovingSelect = false;
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev))
                        {
                                CreateIndicatorTrade(ticket, it);
                                GetInfosTradeServer(ticket);
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateInfosIndicators(0, ticket, m_InfoSelection.pr, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ChartSetInteger(ChartID(), CHART_SHOW_OBJECT_DESCR, false);
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        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_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                        }
                                        break;
                        }
                        break;
        }
}

Bei dieser Funktion gibt es einige Dinge zu beachten, aber achten Sie auf den Unterschied zur vorherigen Version: die Wiederverwendung von viel mehr Code hier. Wenn wir also irgendwo anders einen Fehler im Code haben, können wir ihn schnell bemerken und beheben. Zuvor war dieser Funktionscode ein wenig instabil, mit einigen Korrekturen und Fehlern in anderen Teilen. Einer der wichtigsten Punkte war, dass es bei der Platzierung eines schwebenden Auftrags ein separates System für bewegliche Indikatoren gab, und jetzt haben wir ein einziges System, das gleiche, das für die Platzierung von Aufträgen auf dem Chart verwendet wird. Es wird auch zum Verschieben von Objekten verwendet, d. h., dasselbe Ereignis CHARTEVENT_MOUSE_MOVE wird jetzt sowohl für die Platzierung eines schwebenden Auftrags als auch für das Verschieben von Objekten verwendet. Es mag wie eine Kleinigkeit erscheinen, aber es macht alle Änderungen am Code sichtbar, und wenn wir ein Problem haben, wird es angezeigt, solange wir das Mausereignis verwenden.


Schlussfolgerung

Bitte sehen Sie sich das unten stehende Video an, um eine bessere Vorstellung von den vorgenommenen Änderungen zu bekommen. Sie werden sehen, dass wir jetzt nur noch ein paar Details brauchen, um den EA in Bezug auf das Auftragssystem vollständig zu machen.


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

Beigefügte Dateien |
Experimente mit neuronalen Netzen (Teil 2): Intelligente Optimierung neuronaler Netze Experimente mit neuronalen Netzen (Teil 2): Intelligente Optimierung neuronaler Netze
In diesem Artikel werde ich mit Hilfe von Experimenten und unkonventionellen Ansätzen ein profitables Handelssystem entwickeln und prüfen, ob neuronale Netze für Händler eine Hilfe sein können. Der MetaTrader 5 als ein autarkes Tool für den Einsatz neuronaler Netze im Handel.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 22): Neues Auftragssystems (V) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 22): Neues Auftragssystems (V)
Heute werden wir die Entwicklung des neuen Auftragssystems fortsetzen. Es ist nicht einfach, ein neues System einzuführen, da wir häufig auf Probleme stoßen, die den Prozess erheblich erschweren. Wenn diese Probleme auftreten, müssen wir innehalten und die Richtung, in die wir uns bewegen, neu analysieren.
DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox
Der Artikel behandelt die Gruppierung von WinForms-Objekten und die Erstellung des Listenobjekts CheckBox-Objekte.
Matrix- und Vektoroperationen in MQL5 Matrix- und Vektoroperationen in MQL5
Matrizen und Vektoren wurden in MQL5 für effiziente Operationen mit mathematischen Berechnungen eingeführt. Die neuen Typen bieten integrierte Methoden zur Erstellung von prägnantem und verständlichem Code, der der mathematischen Notation nahe kommt. Arrays bieten umfangreiche Möglichkeiten, aber es gibt viele Fälle, in denen Matrizen viel effizienter sind.