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

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 11): System von Kreuzaufträgen

MetaTrader 5Handel | 6 Juli 2022, 10:15
212 0
Daniel Jose
Daniel Jose

Einführung

Es gibt eine Art von Vermögenswerten, die den Händlern das Leben sehr schwer macht - es sind die Futures-Kontrakte. Aber warum machen sie einem das Leben schwer? Wenn ein Kontrakt eines Finanzinstruments ausläuft, wird ein neuer Kontrakt erstellt, mit dem wir dann weiter handeln. Wenn der Kontrakt ausläuft, müssen wir seine Analyse beenden, alles als Vorlage speichern und diese Vorlage in einen neuen Kontrakt importieren, um die Analyse fortzusetzen. Das ist für jeden, der mit dieser Art von Vermögenswerten handelt, normal, aber auch Terminkontrakte haben eine gewisse Historie, und anhand dieser Historie können wir sie laufend analysieren.

Professionelle Händler analysieren gerne bestimmte Informationen aus der Vergangenheit, in diesem Fall wird ein zweites Chart benötigt. Die zweite Tabelle ist nun nicht mehr nötig, wenn wir die entsprechenden Instrumente verwenden. Eines dieser Instrumente ist die Verwendung des Systems von Kreuzaufträgen.


Planung

Im ersten Artikel dieser Serie haben wir diese Art von Aufträgen bereits erwähnt, sind aber noch nicht auf die Umsetzung eingegangen. In diesem Artikel haben wir uns auf einige andere Dinge konzentriert, da wir ein komplettes System auf den Markt gebracht haben, das auf der MetaTrader 5 Plattform funktionieren kann. In diesem Artikel wird gezeigt, wie diese Funktionsweisen implementiert werden kann.

Um die Gründe für die Schaffung dieser Funktionsweise zu verstehen, sehen wir uns die beiden folgenden Bilder an:

           

Das Bild links zeigt einen typischen Futures-Kontrakt, in diesem Fall MINI DOLLAR FUTURE, der vor einigen Tagen begonnen hat, wie aus dem Chart ersichtlich ist. Das Chart auf der rechten Seite zeigt denselben Kontrakt und enthält zusätzliche Daten, die tatsächlich die Werte abgelaufener Kontrakte darstellen, so dass das Chart auf der rechten Seite ein historisches Chart ist. Das Chart auf der rechten Seite eignet sich besser für die Analyse alter Unterstützungs- und Widerstandsniveaus. Ein Problem ergibt sich jedoch, wenn wir handeln müssen. Sie ist unten dargestellt:

          

Wie man sehen kann, ist das gehandelte Symbol in HANDELSCHART angegeben, und selbst wenn wir die Historie verwenden, sagt HANDELSCHART, dass wir eine Order senden können - dies ist in der Symbolleiste zu sehen. Im linken Bild wurde für den aktuellen Kontrakt eine Order erstellt, im rechten Bild ist die Order nur im Nachrichtenfeld zu sehen, während im Chart nichts zu sehen ist.

Man könnte meinen, dass es sich nur um ein Anzeigeproblem handelt, aber nein, alles ist viel komplizierter. Damit wollen wir uns in diesem Artikel befassen.

Wichtig! Hier sehen wir, wie man Regeln erstellt, um historische Daten für die Arbeit nutzen zu können. In unserem Fall konzentrieren sich diese Regeln auf die Arbeit mit dem Mini-Dollar (WDO) und dem Mini-Index (WIN), die an der brasilianischen Börse (B3) gehandelt werden. Wenn Sie die Regeln richtig verstehen, können Sie sie für jede Art von Terminkontrakt an jeder Börse der Welt anwenden.

Das System ist nicht auf das eine oder andere Asset beschränkt, sondern es geht darum, die richtigen Teile des Codes anzupassen. Wenn dies richtig gemacht wird, dann haben wir einen Expert Advisor, mit dem wir uns keine Gedanken darüber machen müssen, ob ein Anlagekontrakt bald ausläuft und was der nächste Kontrakt sein wird - der EA wird dies für uns tun, indem er den Kontrakt bei Bedarf durch den richtigen ersetzt.


Wie man die Regeln des Spiels versteht

Die Futures-Kontrakte WDO (Mini-Dollar), WIN (Mini-Index), DOL (Dollar-Future) und IND (Index-Future) unterliegen sehr spezifischen Regeln in Bezug auf Laufzeit und Kontraktspezifikation. Lassen Sie uns zunächst sehen, wie Sie das Ablaufdatum des Kontrakts herausfinden können:


Achten Sie auf die hervorgehobenen Informationen: Die blaue Farbe zeigt das Ablaufdatum des Kontrakts an, und die rote Farbe gibt das Datum an, an dem der Kontrakt nicht mehr existiert und nicht mehr gehandelt werden kann. Dies zu wissen, ist sehr wichtig.

Die Kontraktlaufzeit ist im Kontrakt selbst angegeben, aber es wird dort kein Name genannt. Glücklicherweise können wir den Namen anhand der strengen Regeln, die auf dem gesamten Markt gelten, leicht finden. Im Falle von Dollar- und Index-Futures-Kontrakten ergibt sich Folgendes:

Die ersten drei Buchstaben bezeichnen die Kontraktart:

Code Kontrakt
WIN Mini Ibovespa-Index-Terminkontrakt 
IND Ibovespa-Index-Terminkontrakt
WDO Mini-Dollar-Terminkontrakt
DOL Dollar-Terminkontrakt

Auf diesen Code folgt ein Buchstabe, der den Ablaufmonat des Kontrakts angibt:

Verfallsmonat Buchstaben für WDO und DOL Buchstaben für WIN und IND 
Januar F  
Februar  G  G
März  H  
April  J  J
Mai  K  
Juni  M  M
Juli  N  
August  Q  Q
September  U  
Oktober  V  V
November  X  
Dezember  Z  Z

Es folgen zwei Ziffern, die das Jahr des Kontraktendes angeben. Ein Dollar-Future-Kontrakt, der im April 2022 ausläuft, wird zum Beispiel als DOLJ22 bezeichnet. Dies ist der Kontrakt, der bis Anfang Mai gehandelt werden kann. Wenn der Mai beginnt, läuft der Kontrakt aus. Da die Regel für WIN und IND leicht abweicht, läuft der Kontrakt tatsächlich an dem Mittwoch aus, der dem 15. des angegebenen Monats am nächsten liegt. Die Regel ist also komplizierter, aber der EA kann dies verwalten, und er wird immer den richtigen Kontrakt liefern.


Umsetzung

Unser EA hat bereits die notwendigen Punkte, um die Regeln zu erhalten. Hier müssen wir einige Einstellungen vornehmen, die das System zum Versenden von Aufträgen betreffen. Machen wir uns also an die Arbeit. Fügen wir zunächst den folgenden Code in das Objekt der Klasse C_Terminal ein:

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

Dieser Code verwendet die Regeln, die wir oben gesehen haben, um den Namen des Assets zu generieren. Um sicherzustellen, dass wir immer den aktuellen Kontrakt verwenden, implementieren wir eine Prüfung, die in der hervorgehobenen Zeile gezeigt wird, d.h. das Asset sollte für die Plattform gültig sein, und der EA wird den generierten Namen verwenden. Wenn Sie mit anderen Terminkontrakten arbeiten möchten, sollten Sie den vorherigen Code anpassen, damit der Name korrekt generiert wird, da der Name von Fall zu Fall variieren kann. Der Code ist jedoch nicht nur auf Vermögenswerte beschränkt, die mit ihm verknüpft sind - er kann zur Abbildung jeder Art von Terminkontrakten verwendet werden, sofern Sie eine korrekte Regel verwenden.

Nun folgt der Teil mit den Auftragsdetails. Wenn Sie das System in diesem Entwicklungsstadium verwenden, werden Sie das folgende Verhalten feststellen:


Mit anderen Worten: wir können den Modus einer Kreuzauftrages bereits nutzen, aber er ist noch nicht vollständig implementiert - es gibt noch keine Angabe für die Reihenfolge auf dem Chart. Die Umsetzung ist nicht so schwierig, wie sich viele von Ihnen vielleicht vorgestellt haben, denn wir müssen die Aufträge mit horizontalen Linien kennzeichnen. Aber das ist noch nicht alles. Wenn wir Kreuzaufträge verwenden, vermissen wir einige Dinge, die MetaTrader 5 zur Verfügung stellt. Daher müssen wir die fehlende Logik implementieren, damit das Auftragssystem sicher, stabil und zuverlässig arbeiten kann. Andernfalls kann die Verwendung von Kreuzaufträgen zu Problemen führen.

Von diesem Standpunkt aus betrachtet, scheint es nicht so einfach zu sein. Das ist in der Tat nicht einfach, denn wir müssen die gesamte Logik erstellen, die die MetaTrader-Plattform ursprünglich bietet. Als Erstes müssen wir also das interne MetaTrader-System vergessen - es steht uns nicht mehr zur Verfügung, sobald wir mit dem Cross-Order-System arbeiten.

Von nun an wird der das Ticket des Auftrags die Regeln diktieren. Dies hat jedoch einige negative Folgen. Einer der negativsten Punkte ist, dass wir nicht wissen, wie viele Aufträge in einem Chart platziert werden. Eine Begrenzung ihrer Zahl wäre für den Händler sicherlich unangenehm. Daher müssen wir etwas tun, damit der Händler das System auf dieselbe Weise nutzen kann, wie es normalerweise mit der vollständigen MetaTrader-Logik geschieht. Dies ist das erste zu lösende Problem.


Klasse C_HLineTrade

Um dieses Problem zu lösen, werden wir eine neue Klasse C_HLineTrade erstellen, die das von MetaTrader 5 bereitgestellte System zur Anzeige von Aufträgen im Chart ersetzen wird. Beginnen wir also mit der Klassendeklaration:

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

Beachten wir, dass hier einige Dinge definiert sind - sie werden im Code häufig verwendet. Seien Sie also bitte sehr aufmerksam bei weiteren Änderungen - es wird in der Tat viele Änderungen geben. Als Nächstes deklarieren wir den Konstruktor und den Destruktor der Klasse:
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

Der Konstruktor verhindert, dass die ursprünglichen Linien sichtbar sind, während der Destruktor sie wieder in das Chart einfügt. Beide Funktionen haben eine gemeinsame Funktion, die wie folgt lautet:

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

Die hervorgehobene Zeile prüft, ob das Objekt (in diesem Fall eine horizontale Linie) zu den von der Klasse verwendeten Objekten gehört. Ist dies der Fall, wird das Objekt gelöscht. Man beachte, dass wir nicht wissen, wie viele Objekte wir haben, aber das System wird Objekt für Objekt prüfen und versuchen, alles zu bereinigen, was von der Klasse erstellt wurde. Die nächste empfohlene Funktion aus dieser Klasse ist unten dargestellt:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
Für diese Funktion ist es unerheblich, wie viele Objekte erstellt werden und ob das Objekt zum Zeitpunkt des Aufrufs bereits existiert. So wird sichergestellt, dass die Linie an der richtigen Stelle erstellt und platziert wird. Diese erstellte Zeile ersetzt die ursprünglich im MetaTrader verwendete Zeile.

Unser Ziel ist es, sie funktional und nicht schön zu gestalten. Aus diesem Grund werden die Linien beim Erstellen nicht ausgewählt - Sie können dieses Verhalten bei Bedarf ändern. Ich verwende jedoch das Nachrichtensystem von MetaTrader 5, um Linien zu positionieren. Um sie verschieben zu können, müssen wir dies also ausdrücklich angeben. Um anzuzeigen, welche Zeile gerade angepasst wird, gibt es eine weitere Funktion:

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
Mit dieser Funktion wird die Auswahl einer Zeile realisiert. Wenn eine andere Zeile ausgewählt ist, wird die vorherige Auswahl aufgehoben. Das ist ganz einfach. Die Funktion manipuliert nur Zeilen, die tatsächlich von der Klasse bearbeitet werden. Eine weitere erwähnenswerte Funktion dieser Klasse ist die folgende:
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
Diese Funktion ist vielleicht die wichtigste in dieser Klasse: Da wir nicht wissen, wie viele Linien sich im Chart befinden, müssen wir wissen, welche Linie der Benutzer manipuliert. Mit dieser Funktion wird dem System mitgeteilt, welche Zeile manipuliert wird.

Aber das ist nur ein kleiner Teil dessen, was wir tun müssen. Das System ist noch weit davon entfernt, funktionsfähig zu sein. Gehen wir also zum nächsten Schritt über - wir fügen die Funktionen der Klasse C_Router hinzu, die für das Auftragsrouting zuständig ist, und ändern sie. Diese Klasse erbt die Funktionen, die wir gerade in der Klasse C_HLineTrade erstellt haben. Betrachten wir den folgenden Code:

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade



Die neue Klasse C_Router

Die Ausgangsklasse C_Router hatte eine Einschränkung, die nur einen offenen Auftrag zuließ. Diese Einschränkung soll aufgehoben werden, wofür wir wichtige Änderungen an der Klasse C_Router vornehmen müssen.

Die erste Änderung betrifft die Funktion zur Aktualisierung der Klasse, die nun wie folgt aussieht:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

Zuvor erfasste diese Funktion nur Daten zu einer offenen Position und speicherte sie in ihrer Beobachtungsstelle. Jetzt zeigt die Funktion absolut alle offenen Positionen und schwebenden Aufträge auf dem Chart an. Es ist definitiv ein Ersatz für das vom MetaTrader bereitgestellte System. Da es sich hierbei um gravierende Dinge handelt, ist es wichtig, die Arbeitsweise zu verstehen, weil im Fehlerfall das ganze System der Kreuzaufträge beeinträchtigt werden würde. Bevor wir also mit einem echten Konto handeln, sollten wir dieses System auf einem Demokonto testen. Solche Systeme müssen ordnungsgemäß getestet werden, bis wir absolut sicher sind, dass alles so funktioniert, wie es soll. Zunächst müssen wir das System konfigurieren, da es ein wenig anders funktioniert als der MetaTrader 5.

Sehen Sie sich die hervorgehobenen Zeilen an und antworten Sie wahrheitsgemäß: Haben sie verstanden, was hier passiert? Der Grund, warum diese beiden Codezeilen hier stehen, wird später klar, wenn wir in diesem Artikel über die Klasse C_OrderView sprechen. Ohne diese beiden Zeilen ist der Code sehr instabil und funktioniert seltsam. Der restliche Code ist recht einfach - er erstellt jede der Linien über das Objekt der Klasse C_HLineTrade. In diesem Fall haben wir nur eine Zeile, die nicht ausgewählt werden kann. Dies lässt sich leicht erkennen, wie der folgende Code zeigt:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

Mit anderen Worten: Das System ist sehr einfach und überschaubar geworden. Die Funktion wird vom EA während eines Ereignisses in OnTrade aufgerufen:

C_TemplateChart Chart;

// ... Expert Advisor code ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

// ... The rest of the Expert Advisor code ...

Der hervorgehobene Code ermöglicht die Aktualisierung von Befehlen auf dem Bildschirm. Beachten Sie, dass wir hierfür das Chart C_TemplateChart verwenden, da sich die Struktur der Klassen im System geändert hat. Die neue Struktur ist unten dargestellt:

Diese Struktur ermöglicht einen gerichteten Nachrichtenfluss innerhalb der EA-Richtung. Wenn Sie sich nicht sicher sind, wie der Nachrichtenfluss in eine bestimmte Klasse gelangt, werfen Sie einen Blick auf dieses Chart zu den abgeleiteten Klassen. Die einzige Klasse, die als öffentlich gilt, ist die Objektklasse C_Terminal, während alle anderen durch Vererbung zwischen den Klassen gehandhabt werden, und absolut keine Variablen sind in diesem System öffentlich.

Da das System aber nicht nur einen einzigen Auftrag analysiert, muss man noch etwas anderes verstehen: Wie kann man das Ergebnis von Operationen verstehen? Warum ist das wichtig? Wenn wir nur eine offene Position haben, kann das System alles leicht verstehen, aber wenn die Zahl der offenen Positionen steigt, müssen wir herausfinden, was vor sich geht. Hier ist die Funktion, die diese Informationen liefert:

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

Es gibt nicht viele Änderungen. Schauen Sie sich den hervorgehobenen Funktionscode an:

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

Die Funktion besteht aus drei hervorgehobenen Teilen: der gelbe Teil informiert über das Ergebnis der offenen Positionen, und die grünen Teile überprüfen die Position für den Fall, dass der Stop-Loss aufgrund einer hohen Volatilität verfehlt wird, in welchem Fall es notwendig ist, die Position so schnell wie möglich zu schließen. Diese Funktion liefert also nicht das Ergebnis einer einzelnen Position, es sei denn, Sie haben eine offene Position für einen bestimmten Vermögenswert.

Es gibt auch andere Funktionen, die dem System helfen, weiter zu funktionieren, wenn wir das Modell der Kreuzaufträgen verwenden. Schauen Sie sich diese im untenstehenden Code an:

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
Der Erste ist für die Änderung des noch offenen Auftrags zuständig, der andere für die Änderung der offenen Position. Obwohl sie gleich zu sein scheinen, sind sie es nicht. Es gibt noch eine weitere wichtige Funktion für das System:
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

Mit dieser letzten Funktion schließen wir die Betrachtung der Klasse C_Router ab. Wir haben das Basissystem implementiert, das die normalerweise im MetaTrader unterstützten Funktionen abdeckt - nein, wegen des Systems der Kreuzaufträge können wir nicht mehr auf diese Unterstützung zählen. Das System ist jedoch noch nicht vollständig. Wir müssen noch etwas hinzufügen, damit das System wirklich funktioniert. Wenn ein Auftrag vorliegt, sieht es im Moment wie folgt aus. Dies ist erforderlich, um den nächsten Schritt durchführen zu können.



Schauen Sie sich das Bild oben genau an. Das Meldungsfeld zeigt den offenen Auftrag und die Anlage, für die er geöffnet wurde. Der gehandelte Vermögenswert wird in HANDELSCHART angezeigt. Vergessen wir nicht, dass es sich um dieselbe Anlage handelt, die auch im Meldungsfenster angegeben ist. Überprüfen wir nun den im Chart angezeigten Vermögenswert. Der Name kann in der Kopfzeile des Chartfensters überprüft werden. Aber es ist völlig anders - es ist kein Vermögenswert auf dem Chart, sondern es ist die Mini-Index-Historie, was bedeutet, dass wir jetzt nicht das MetaTrader 5 interne System verwenden, sondern das in diesem Artikel beschriebene Cross-Order-System. Jetzt haben wir nur noch die Funktion, die es ermöglicht, den Platz des Auftrags anzuzeigen. Aber das reicht nicht aus, denn wir wollen ein voll funktionsfähiges System haben, das die Abwicklung von Kreuzaufträgen über das System ermöglicht. Wir brauchen also etwas anderes. Das Ereignis, das sich auf das Verschieben der Auftrags bezieht, wird in einer anderen Klasse implementiert.


Neue Funktionalität in der Klasse C_OrderView

Die Objektklasse C_OrderView kann zwar einige Dinge tun, aber noch keine offenen oder schwebenden Auftragsdaten verarbeiten. Wenn wir jedoch ein Nachrichtensystem hinzufügen, haben wir mehr Möglichkeiten, es zu nutzen. Dies ist die einzige Ergänzung, die wir in der Klasse vornehmen werden. Der vollständige Funktionscode ist unten dargestellt:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

Dieser Code vervollständigt das System der Kreuzaufträge. Unsere Möglichkeiten haben sich erweitert, sodass wir fast das Gleiche tun können, was ohne das Kreuzauftrags-System möglich war. Im Allgemeinen sollte die Funktion recht klar sein. Es gibt jedoch einen Ereignistyp, der nicht sehr häufig vorkommt - CHARTEVENT_OBJECT_DELETE. Wenn der Nutzer eine Linie löscht, wird dies Auswirkungen im Chart und für den Auftrag haben, daher sollten Sie sehr vorsichtig sein, wenn Sie Linien vom Chart löschen. Wir brauchen uns keine Sorgen zu machen, wenn wir den EA aus dem Chart entfernen, da die Aufträge intakt bleiben, wie in der folgenden Animation zu sehen ist:


Wenn der EA jedoch auf dem Chart läuft, müssen wir sehr vorsichtig sein, wenn wir Linien aus dem Chart löschen, insbesondere solche, die in der Liste der Objekte verborgen sind. Andernfalls können wir unten sehen, was im Auftragssystem passiert, wenn wir die vom Cross-Order-System erzeugten Zeilen entfernen.

Und um die Demo des Systems zu beenden, sehen wir uns an, was mit dem Auftrag passiert, wenn wir die Preislinien ziehen. Beachten Sie Folgendes: Die gezogene Linie muss markiert sein; ist sie nicht markiert, kann sie nicht verschoben werden. Die Preisänderung tritt ein, wenn die Linie auf dem Chart freigegeben wird, vorher bleibt der Preis an der gleichen Stelle wie vorher.


Wenn es schwierig ist, zu erkennen, ob eine Zeile ausgewählt ist oder nicht, sollten wir Änderungen am Auswahlcode vornehmen. Die Änderungen sind unten hervorgehoben. Diese Änderung ist in der beigefügten Version bereits umgesetzt.

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

Das Ergebnis dieser Codeänderung ist in der nachstehenden Abbildung zu sehen.


Schlussfolgerung

Hier habe ich also gezeigt, wie man ein Kreuzauftrags-System in MetaTrader erstellt. Ich hoffe, dass dieses System für jeden nützlich sein wird, der dieses Wissen nutzen will. Aber bitte denken Sie daran: Bevor Sie mit diesem System auf einem Live-Konto handeln, sollten Sie es so gründlich wie möglich in vielen verschiedenen Marktszenarien testen, denn obwohl dieses System in der MetaTrader-Plattform implementiert ist, hat es fast keine Unterstützung von Seiten der Plattform in Bezug auf die Fehlerbehandlung, so dass, wenn sie zufällig auftreten, müssen Sie schnell handeln, um nicht große Verluste zu bekommen. Aber durch das Testen in verschiedenen Szenarien können Sie herausfinden, wo die Probleme auftreten: bei den Bewegungen, der maximalen Anzahl von Aufträgen, die Ihr Computer verarbeiten kann, dem maximal zulässigen Spread für das Analysesystem, dem zulässigen Volatilitätsniveau für offene Aufträge, denn je größer die Anzahl der offenen Aufträge und der zu analysierenden Informationen ist, desto wahrscheinlicher wird etwas Schlimmes passieren. Dies liegt daran, dass jeder einzelne Auftrag bei jedem Tick, den das System empfängt, analysiert wird, was ein Problem darstellen kann, wenn viele Aufträge gleichzeitig offen sind.

Ich empfehle, diesem System nicht zu vertrauen, bis Sie es auf einem Demo-Konto mit vielen Szenarien testen. Auch wenn der Code perfekt zu sein scheint, hat er keine Fehleranalyse.

Ich hänge den Code aller Expert Advisors an, wie er im Moment ist.


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

Beigefügte Dateien |
EA.zip (12013.17 KB)
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize
In diesem Artikel werde ich das Basisobjekt aller Bibliotheks-WinForms-Objekte erstellen und mit der Implementierung der AutoSize-Eigenschaft des Paneel-Objekts für WinForms beginnen – automatische Größenanpassung zum Anpassen des internen Inhalts des Objekts.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren
Wie kann man auf nutzerdefinierte Indikatoren direkt in einem Expert Advisor zugreifen? Ein Handels-EA kann nur dann wirklich nützlich sein, wenn er nutzerdefinierte Indikatoren verwenden kann; andernfalls ist er nur ein Satz von Codes und Anweisungen.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 12): Times and Trade (I) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 12): Times and Trade (I)
Heute werden wir „Times and Trade“ (Zeiten und Handel) mit einer schnellen Interpretation erstellen, um den Auftragsfluss zu lesen. Es ist der erste Teil, in dem wir das System aufbauen werden. Im nächsten Artikel vervollständigen wir das System mit den fehlenden Informationen. Um diese neue Funktionsweisen zu implementieren, müssen wir dem Code unseres Expert Advisors mehrere neue Dinge hinzufügen.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 9): Ein konzeptioneller Sprung (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 9): Ein konzeptioneller Sprung (II)
In diesem Artikel platzieren wir einen Handelschart in einem schwebenden Fenster. Im vorherigen Teil haben wir ein Basissystem erstellt, das die Verwendung von Vorlagen innerhalb eines schwebenden Fensters ermöglicht.