English Русский 中文 Español 日本語 Português
preview
Marktsimulation (Teil 01): Kreuzaufträge (I)

Marktsimulation (Teil 01): Kreuzaufträge (I)

MetaTrader 5Tester |
95 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 78): Neuer Chart Trade (V)“, habe ich gezeigt, wie der Expert Advisor die von Chart Trade gesendeten Anweisungen interpretieren kann. Die Informationen, die Chart Trade tatsächlich an den Expert Advisor übermittelt, hängen von der Interaktion des Nutzers mit dem Expert Advisor ab. Mit anderen Worten: Wenn der Nutzer auf die Schaltfläche „Buy“ (Kaufen), „Sell“ (Verkaufen) oder „Close all Positions“ (alle Positionen schließen) klickt, wird eine Nachricht an das Chart gesendet. Eine der Aufgaben des Expert Advisors besteht darin, die in dieser Nachricht enthaltenen Anweisungen abzufangen, zu dekodieren und auszuführen, wenn er mit diesem Chart verbunden ist.

Obwohl dieser Mechanismus einfach und recht zuverlässig ist, haben wir ein kleines Problem. Nun, es ist nicht wirklich ein Problem, eher eine Unannehmlichkeit. Und diese Unannehmlichkeit muss behoben werden, bevor wir tatsächlich damit beginnen können, Aufträge an den Handelsserver zu senden.

Wenn Sie nicht wissen, wovon ich spreche, liegt das vielleicht daran, dass Sie nicht mit bestimmten Vermögenswerten handeln, genauer gesagt, mit Terminkontrakten. Diese Arten von Vermögenswerten haben ein Verfallsdatum. Häufig werden zwei Arten gleichzeitig gehandelt: der volle Kontrakt, der ein höheres Volumen aufweist, und der Minikontrakt, der als ein Bruchteil des vollen Kontrakts angesehen werden kann. Die Mini-Version eignet sich für Strategien, die ein geringeres Volumen oder weniger Kontrakte erfordern.

Ich werde hier nicht im Detail auf diese Strategien eingehen. Der Punkt ist, dass man manchmal weniger Kontrakte braucht, um eine Strategie zu entwickeln. Wer sich dafür interessiert, sollte sich mit HEDGE-Strategien befassen. Für uns als Programmierer stellt sich die Frage, wie wir einen Handel mit einem Minikontrakt ausführen können, wenn im Chart der gesamte Kontrakt angezeigt wird.

Aber es gibt noch eine weitere Herausforderung: langfristige Strategien. Jedes Mal, wenn ein Kontrakt ausläuft, was zu einem festen, bekannten Datum geschieht, beginnt eine neue Serie. Für Händler, die mit längeren Zeithorizonten arbeiten, die sich über mehrere Serien erstrecken, stellt dies ein großes Problem dar. Das liegt daran, dass Indikatoren und gleitende Durchschnitte ihre Berechnungen mit jeder neuen Reihe von vorne beginnen müssen.

Um dies besser zu verstehen, betrachten wir den Dollar-Future-Kontrakt B3 (Brazilian Stock Exchange). Dieser Kontrakt läuft monatlich aus. Das bedeutet, dass jeden Monat eine Serie endet und eine neue beginnt. Wenn man bedenkt, dass jeder Monat etwa 20 Handelstage hat (fünf Handelstage pro Woche in vier Wochen), ergeben sich Probleme, wenn man versucht, z. B. einen gleitenden Durchschnitt über 20 Perioden zu verwenden. Zu dem Zeitpunkt, an dem der gleitende Durchschnitt vollständig berechnet und gezeichnet ist, läuft der Kontrakt aus und wird mit einer neuen Serie fortgesetzt. Und das nur bei einem Durchschnitt von 20 Perioden. Andere Indikatoren, die noch längere Zeiträume benötigen, leiden noch stärker. Kurz gesagt, dies ist ein großes Problem.

Um dies zu vermeiden, können wir die historischen Daten des Terminkontrakts verwenden. Die Verwendung historischer Daten ist jedoch nicht die Lösung für alle Probleme. Für uns Programmierer ergeben sich daraus neue Herausforderungen. Denken Sie daran, dass es dem Händler egal ist, wie der Server die Daten empfängt oder aufbereitet. Der Händler möchte nur genaue Informationen und eine zuverlässige Ausführung seiner Handelsgeschäfte. Es ist unsere Aufgabe als Programmierer, das Problem der Darstellung zu lösen und sicherzustellen, dass die Handelsanfragen aus dem Chart ordnungsgemäß an den Handelsserver weitergeleitet werden.

Im Artikel „Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 11): System von Kreuzaufträgen“, habe ich einige Details dazu erklärt. Da wir es hier aber auch mit einem Replay/Simulator zu tun haben, wird das Problem noch größer. Dennoch können wir die Strategie von Napoleon anwenden: teile und herrsche. Indem wir das Problem in kleinere Teile zerlegen, können wir schrittweise ein simuliertes Ordnungssystem entwickeln. Beginnen wir mit einem wichtigen Aspekt: der Handhabung von Indikatoren in Terminkontrakten. Und weil der Dollar-Terminkontrakt der extremste Fall ist, den ich kenne, werden wir uns auf ihn konzentrieren. Beachten Sie jedoch, dass die gleichen Grundsätze auch für andere Kontrakte mit ähnlicher Komplexität gelten.


Beginn der Implementierung

In dem bereits erwähnten Artikel, in dem wir das System von System von Kreuzaufträgen entwickelt haben, war die Anpassung an andere Arten von Kontrakten relativ komplex. Aus praktischen Gründen werden wir hier einen anderen Ansatz wählen, um solche Anpassungen zu vereinfachen - nicht für den Händler, sondern für uns als Programmierer. Der Händler wird sich an unsere Umsetzung anpassen müssen, aber im Gegenzug erhält er eine einfache Option: Er kann wählen, ob er den vollen Kontrakt oder den Minikontrakt handeln will.

Damit dies funktioniert (zumindest anfangs, wenn wir noch mit dem Live-Handelsserver kommunizieren), müssen wir ein paar gezielte Änderungen am bestehenden Code vornehmen. Beginnen wir mit einer wichtigen Tatsache: Wie bereits in der Einleitung erwähnt, ist das beste Chart dasjenige, das auf historischen Daten basiert. Dieser historische Chart kann jedoch nicht direkt gehandelt werden.

Um dieses Problem zu lösen, benötigen wir ein System, das die im historischen Chart platzierten Aufträge an den Kontrakt weiterleitet, den der Händler tatsächlich nutzen möchte. Denken Sie daran, dass der Händler entweder den gesamten Kontrakt oder den Minikontrakt handeln möchte. Aber darüber machen wir uns jetzt noch keine Gedanken. Zunächst müssen wir eine einfache Tatsache verstehen: Der im Chart angezeigte Inhalt stammt aus den historischen Daten des Kontrakts. Punkt.

Auf B3 gibt es für Terminkontrakte sechs verschiedene Namenskonventionen. Dies gilt für jeden einzelnen Kontrakt. Das bedeutet sechs Typen für Vollkontrakte und sechs Typen für die Minis. Dies ist eine scheinbar große Komplikation. Aber bei näherer Betrachtung ist es gar nicht so schlimm, wie es scheint. Obwohl es sechs Varianten gibt, lassen sie sich auf drei Haupttypen mit jeweils zwei Untervarianten reduzieren.

Diese Vereinfachung hilft uns sehr. Ich empfehle jedoch, die Unterschiede zwischen diesen drei Typen zu studieren. Die Chart-Daten unterscheiden sich erheblich voneinander, und viele Händler sind sich dessen gar nicht bewusst. Wenn Sie nur die Lösung programmieren, sollten Sie den Händler darüber informieren. Wenn Sie sowohl programmieren als auch Handel treiben, sollten Sie diesen Rat noch ernster nehmen – Sie könnten ernsthafte Probleme bekommen, wenn Sie diese Unterscheidungen nicht verstehen.

Es gibt also drei Arten der Benennung mit jeweils zwei Varianten. Gut. Aber für uns Programmierer sind nicht die Namen an sich wichtig, denn die sind eher für Händler relevant. Für uns ist die Schlüsselfrage: Gibt es eine Regel in dieser Namenskonvention? Und wenn ja, wie können wir sie nutzen, um ein System von Kreuzaufträgen aufzubauen?

Glücklicherweise gibt es eine solche Regel. Wir verwenden sie bereits seit einiger Zeit. Wie das funktioniert, sehen Sie im folgenden Codefragment.

38. //+------------------------------------------------------------------+
39.       void CurrentSymbol(void)
40.          {
41.             MqlDateTime mdt1;
42.             string sz0, sz1;
43.             datetime dt = macroGetDate(TimeCurrent(mdt1));
44.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
45.       
46.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
47.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
48.             switch (eTS)
49.             {
50.                case DOL   :
51.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
52.                case IND   :
53.                case WIN   : sz1 = "GJMQVZ";       break;
54.                default    : return;
55.             }
56.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
57.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
58.          }
59. //+------------------------------------------------------------------+

Der Code aus der Datei C_Terminal.mqh

Dieses Codefragment stammt aus der Header-Datei C_Terminal.mqh. Beachten Sie, dass wir in Zeile 44 die Namen der unterstützten Futures-Kontrakte definieren. Sie können dieser Liste weitere Kontrakte hinzufügen, wenn Sie mit Werten wie Mais, Rindern, S&P, Euro usw. arbeiten möchten. Denken Sie daran, die Benennungsregeln für jeden Kontrakt zu befolgen, um den aktiven Kontrakt korrekt zu identifizieren. Das hier beschriebene Verfahren liefert keine Kontrakte aus der Vergangenheit und auch keine Kontrakte, die zwei oder mehr Jahre vor dem Ablaufdatum liegen. Es wird immer der gerade aktive Kontrakt aufgelöst.

Zu diesem Zweck werden in Zeile 46 die ersten drei Zeichen des Namens vom Vermögenswerts extrahiert. Unabhängig vom Vermögenswerts werden immer diese ersten drei Zeichen erfasst. Dies liegt daran, dass B3 (die brasilianische Börse) eine Namenskonvention verwendet, bei der die ersten drei Zeichen den Vermögenswert identifizieren. Nach der Extraktion wird der Name des Vermögenswerts in einer Variablen gespeichert, die im restlichen Code verwendet werden kann. Bitte beachten Sie diese Tatsache.

Als Nächstes wird in Zeile 47 die Enumeration der von uns definierten Kontraktsnamen durchlaufen. Hier geht es darum, die richtige Übereinstimmung zu finden. Aus diesem Grund müssen die Namen in der Enumeration in Zeile 44 den eigentlichen Kontraktsnamen ähneln. Da B3 Großbuchstaben verwendet, muss auch die Enumeration in Großbuchstaben erfolgen. Sobald die Übereinstimmung gefunden ist oder die Liste erschöpft ist, endet die Schleife in Zeile 47.

In Zeile 48 können wir den abgerufenen Wert testen. Wenn keine Übereinstimmung gefunden wird, springt die Ausführung zum Code in Zeile 54. Andernfalls wird der vollständige Kontraktsname erstellt. Die endgültige Namensgebung erfolgt in Zeile 57, wo das Verfahren bestätigt, dass der erzeugte Kontraktsname mit dem derzeit aktiven Kontrakt übereinstimmt. Kurz gesagt, das Verfahren durchsucht die möglichen Terminkontrakte, bis es den aktiven Kontrakt gefunden hat.

Allerdings gibt es hier eine wichtige Einschränkung. Das Verfahren verwendet den vom Anlagennamen abgeleiteten Namen des Basiskontrakts. Das bedeutet, dass Sie historische Daten eines bestimmten Kontrakts nur der aktiven Version desselben Kontrakts zuordnen können. Mit dem aktuellen Code ist es nicht möglich, historische Daten aus dem Gesamtkontrakt dem aktiven Minikontrakt zuzuordnen. Diese Unannehmlichkeiten werden wir in diesem Artikel beheben.

Auf diese Weise kann der Händler wählen, ob er den Vollkontrakt oder den Minikontrakt handeln möchte, wobei er sogar die historischen Daten beider Kontrakte nutzen kann. Unser Ziel ist es, dies mit minimalen Code-Änderungen zu ermöglichen, denn je mehr Code wir ändern, desto größer ist die Wahrscheinlichkeit, dass wir Fehler einführen.

Um dies zu erreichen, wurde der obige Codeschnipsel wie folgt geändert:

38. //+------------------------------------------------------------------+
39.       void CurrentSymbol(bool bUsingFull)
40.          {
41.             MqlDateTime mdt1;
42.             string sz0, sz1;
43.             datetime dt = macroGetDate(TimeCurrent(mdt1));
44.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
45.       
46.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
47.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
48.             switch (eTS)
49.             {
50.                case DOL   :
51.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
52.                case IND   :
53.                case WIN   : sz1 = "GJMQVZ";       break;
54.                default   : return;
55.             }
56.             sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS)));
57.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
58.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
59.          }
60. //+------------------------------------------------------------------+

Der Code aus der Datei C_Terminal.mqh

Wie Sie sehen können, waren die Änderungen minimal. Die erste bestand darin, der Funktion in Zeile 39 einen Parameter hinzuzufügen. Dieses Argument teilt der Prozedur mit, ob der zu erzeugende Kontraktname für den vollständigen Kontrakt oder den Minikontrakt sein soll. Die Wahl bleibt dem Händler überlassen. Unsere Aufgabe als Programmierer ist es, ihnen die Flexibilität zu geben, das von ihnen bevorzugte Chart zu verwenden, solange die im Chart dargestellten Daten in irgendeiner Weise dem gehandelten Kontrakt entsprechen. Natürlich könnten wir auch komplexere Verhaltensweisen implementieren, aber wir wollen das, was getan werden muss, nicht zu sehr verkomplizieren.

Neben dieser Änderung in Zeile 39 haben wir eine neue Zeile hinzugefügt. Technisch gesehen war dieser Zusatz nicht unbedingt notwendig, da wir den bestehenden Code auch hätten ändern können. Aber es vereinfacht sowohl die Erklärung als auch Ihr Verständnis der Vorgänge. Diese neue Zeile, 56, hätte direkt an der Stelle platziert werden können, an der die Variable sz0 in Zeile 58 verwendet wird, und das Ergebnis wäre dasselbe gewesen. Das würde die Erklärung jedoch verwirren und schwerer nachvollziehbar machen.

Was macht die Zeile 56 mit der Variablen sz0? Schauen wir uns das mal an. Im Wesentlichen ignorieren wir den Namen des Vermögenswerts selbst. Stattdessen wandeln wir die in Zeile 44 definierte Enumeration in eine Zeichenkette um. MQL5 ermöglicht dies durch die Funktion EnumToString.

Jetzt kommt ein Detail, das die Sache verkomplizieren kann, wenn Sie mit einem Terminkontrakt arbeiten, für den es keine Voll- und keine Miniversion gibt. Dies ist bei WAREN recht häufig der Fall. Aber in den Fällen, die ich aufzeigen möchte – Indizes und Währungen, insbesondere Dollar-Futures – gibt es beide Kontraktarten.

Enumerationen beginnen immer mit dem Wert Null, sofern Sie nichts anderes festlegen. In unserem Fall werden Minikontrakte mit geraden Werten und Vollkontrakte mit ungeraden Werten versehen. Dies zu verstehen ist wichtig. Die Werte sind binär. Sie sollten wissen, wie Sie dieses oder jenes Bit auswählen können. Im Binärsystem bestimmt das niedrigstwertige Bit (das ganz rechts), ob eine Zahl gerade oder ungerade ist. Durch die Anwendung einer bitweisen UND-Verknüpfung zur Isolierung dieses Bits können wir prüfen, ob der Enumerationswert gerade oder ungerade ist. Noch einmal: Minikontrakte sind gerade, Vollkontrakte sind ungerade.

Wenn der Wert also ungerade ist, wird der erste Teil des ternären Operators ausgeführt. Ist er grade, wird der zweite Teil ausgeführt. Im Moment denke ich, dass alles klar ist. Dann wird in jedem Zweig dieses ersten ternären Operators ein zweiter ternärer Operator verwendet. Mit diesem zweiten ternären Operator können wir die Variable eTS anpassen, um sicherzustellen, dass sie den richtigen Kontraktsnamen wiedergibt.

Beispiel: Wenn der Kontrakt WDO lautet, dann ist eTS gleich 2, eine gerade Zahl. Dadurch wird der zweite Teil des ersten ternären Operators ausgelöst. In ihm führt der zweite ternäre Operator die zweite Prüfung durch. Er prüft, ob der Prozeduraufruf den Voll- oder den Minikontrakt angefordert hat.

Wenn der Händler den gesamten Kontrakt angefordert hat, wird eTS um eins erhöht. Sein Wert ändert sich also von 2 auf 3. In der Enumeration entspricht die Position 3 der DOL. Wenn MQL5 EnumToString ausführt, wird der Wert 3 in die Zeichenkette DOL umgewandelt, wodurch der vollständige Kontraktname erzeugt wird, obwohl das Chart auf historischen Daten des Mini-Dollars basiert.

Der umgekehrte Weg funktioniert ebenfalls. Wenn das Chart die historischen Daten des vollen Dollarkontrakts anzeigt, der Händler aber den Mini anfordert, führt der erste ternäre Operator seinen ersten Zweig aus. Darin dekrementiert der zweite ternäre Operator eTS um eins und ändert seinen Wert von 3 auf 2. Dadurch wird sie auf WDO zurückgeführt.

Kurz gesagt: Der in Zeile 47 ermittelte Wert wird in Zeile 56 angepasst, sodass der Name des Kontrakts mit der Wahl des Händlers (Mini oder Voll) übereinstimmt, während er sich immer noch auf den historischen Chart eines der beiden Kontrakte stützt.

So weit, so gut. Was aber, wenn es einen Kontrakt gibt, der keine Miniversion, sondern nur den vollständigen Kontrakt enthält? Wie können wir damit umgehen? Man könnte meinen, dass es zwei mögliche Lösungen gibt. In Wirklichkeit gibt es nur einen. Wenn Sie versuchen, Werte in der Enumeration zu duplizieren, um künstlich gerade und ungerade Einträge zu erzeugen, wird der Compiler dies ablehnen. Stattdessen besteht die Lösung darin, die Enumeration in einer logischen Reihenfolge zu strukturieren. Wenn dann bei der Prüfung eines bestimmten Wertes kein Minikontrakt existiert, bleibt die Variable sz0 unverändert. In der Praxis erfordert dies einen zusätzlichen Test in Ihrem Code. Aber es ist nichts Komplexes dabei.

Damit haben wir den ersten Teil des Problems gelöst. Wir sind jedoch noch nicht fertig. Um fortzufahren, müssen wir einen anderen Teil der Header-Datei C_Terminal.mqh anpassen: den Klassenkonstruktor. Der Konstruktor ist für den Aufruf der Prozedur zuständig, die wir gerade geändert haben. Das Original muss daher durch die unten stehende neue Version ersetzt werden.

72. //+------------------------------------------------------------------+      
73.       C_Terminal(const long id = 0, const uchar sub = 0, const bool bUsingFull = false)
74.          {
75.             m_Infos.ID = (id == 0 ? ChartID() : id);
76.             m_Mem.AccountLock = false;
77.             m_Infos.SubWin = (int) sub;
78.             CurrentSymbol(bUsingFull);
79.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
80.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
81.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
82.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
83.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
84.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
85.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
86.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
87.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
88.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
89.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
90.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
91.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
92.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
93.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
94.             ChartChange();
95.          }
96. //+------------------------------------------------------------------+

Der Code aus der Datei C_Terminal.mqh

Beachten Sie, dass nur zwei sehr einfache Änderungen vorgenommen wurden. Der erste ist in Zeile 73, wo wir einen neuen Parameter hinzufügen. Dieser Parameter wird dann in Zeile 78 verwendet, wo wir das zuvor erläuterte Verfahren aufrufen. Standardmäßig habe ich Mini-Kontrakte bevorzugt, aber es steht dem Händler frei, die Option zu wählen, die am besten zu seiner Strategie passt. Um diese Flexibilität zu unterstützen, sind einige kleine Anpassungen in bestimmten Teilen des Codes erforderlich.

Da wir den Code des Expert Advisors noch nicht ändern, muss die erforderliche Änderung im Code des Chart Trade vorgenommen werden. Der Übersichtlichkeit halber werden wir dies in einem eigenen Abschnitt behandeln.


Chart Trade in ein System von Kreuzaufträgen umwandeln

Die Änderungen, die erforderlich sind, damit Chart Trade als System mit Kreuzaufträgen funktioniert, sind recht einfach. Sie könnten in Erwägung ziehen, der Schnittstelle ein Objekt hinzuzufügen, mit dem der Händler die Art der Kreuzaufträge direkt wechseln kann. Diesen Ansatz werde ich hier jedoch nicht verfolgen. Mein Ziel ist es, die Änderungen so gering wie möglich zu halten. Das Hinzufügen eines solchen Objekts würde viel mehr Code erfordern, nur um diese Funktion zu unterstützen. Stattdessen können wir dem Händler die Möglichkeit geben, die Art der Kreuzaufträge über die Indikatoreinstellungen zu ändern. Dieser Ansatz ist einfach und erfordert nur sehr wenige Änderungen am bestehenden Code. Der erste Schritt ist im nachstehenden Code zu sehen.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Chart Trade Base Indicator."
04. #property description "See the articles for more details."
05. #property version   "1.80"
06. #property icon "/Images/Market Replay/Icons/Indicators.ico"
07. #property link "https://www.mql5.com/pt/articles/12536"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh>
12. //+------------------------------------------------------------------+
13. #define def_ShortName "Indicator Chart Trade"
14. //+------------------------------------------------------------------+
15. C_ChartFloatingRAD *chart = NULL;
16. //+------------------------------------------------------------------+
17. enum eTypeContract {MINI, FULL};
18. //+------------------------------------------------------------------+
19. input ushort         user01 = 1;         //Leverage
20. input double         user02 = 100.1;     //Finance Take
21. input double         user03 = 75.4;      //Finance Stop
22. input eTypeContract  user04 = MINI;      //Cross order in contract
23. //+------------------------------------------------------------------+
24. int OnInit()
25. {
26.    chart = new C_ChartFloatingRAD(def_ShortName, new C_Mouse(0, "Indicator Mouse Study"), user01, user02, user03, (user04 == FULL));
27.    
28.    if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED;
29. 
30.    return INIT_SUCCEEDED;
31. }
32. //+------------------------------------------------------------------+
33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
34. {
35.    return rates_total;
36. }
37. //+------------------------------------------------------------------+
38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
39. {
40.    if (_LastError < ERR_USER_ERROR_FIRST) 
41.       (*chart).DispatchMessage(id, lparam, dparam, sparam);
42. }
43. //+------------------------------------------------------------------+
44. void OnDeinit(const int reason)
45. {
46.    switch (reason)
47.    {
48.       case REASON_INITFAILED:
49.          ChartIndicatorDelete(ChartID(), 0, def_ShortName);
50.          break;
51.       case REASON_CHARTCHANGE:
52.          (*chart).SaveState();
53.          break;
54.    }
55. 
56.    delete chart;
57. }
58. //+------------------------------------------------------------------+

Quellcode des Indikators Chart Trade

In Zeile 17 fügen wir eine Enumeration hinzu. Es hilft dem Händler (oder Nutzer), die Kontraktsart zu definieren. Beachten Sie, dass diese Enumeration in Zeile 22 verwendet wird. Dies ist der Punkt, an dem der Händler entscheidet, ob der Expert Advisor mit dem vollen Kontrakt oder dem Minikontrakt arbeiten soll. Hier gibt es einen Nachteil: Die Auswahl sollte idealerweise im Expert Advisor und nicht im Chart Trade getroffen werden. Aber da Chart Trade und der Expert Advisor vorerst noch getrennte Einheiten sind, belassen wir es dabei.

Die eigentliche Herausforderung liegt nicht im Chart Trade oder gar im Expert Advisor. Wie ich im vorherigen Artikel erklärt habe, kann Chart Trade bereits den Expert Advisor steuern. Das Problem liegt in einem anderen Teil des Systems, der später entwickelt werden soll. Hier liegt die eigentliche Schwierigkeit, denn schließlich muss alles über den Expert Advisor laufen. Im Idealfall müssten wir alles Notwendige darin unterbringen. Für den Moment und zu Demonstrationszwecken werden wir die Auswahl jedoch in Chart Trade vornehmen.

Dieser konfigurierte Wert wird dann in Zeile 26 verwendet. Beachten Sie, dass wir dem Konstruktor einen booleschen und keinen numerischen Wert übergeben. Warum? Denn obwohl ein boolescher Wert für den Endnutzer vielleicht nicht so aussagekräftig erscheint, für uns Programmierer ist das ganz klar. Schließlich gibt es nur zwei mögliche Bedingungen: Der Händler wird entweder den vollen Kontrakt oder den Minikontrakt verwenden. Aus kodierungstechnischer Sicht ist daher ein boolescher Wert die beste Wahl. Der boolesche Wert wird dann an den Konstruktor der Klasse übergeben. Der folgende Codeausschnitt zeigt, wie dies aussieht.

213. //+------------------------------------------------------------------+
214.       C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop, const bool bUsingFull)
215.          :C_Terminal(0, 0, bUsingFull)
216.          {
217.             m_Mouse = MousePtr;
218.             m_Info.IsSaveState = false;
219.             if (!IndicatorCheckPass(szShortName)) return;
220.             if (!RestoreState())
221.             {
222.                m_Info.Leverage = Leverage;
223.                m_Info.IsDayTrade = true;
224.                m_Info.FinanceTake = FinanceTake;
225.                m_Info.FinanceStop = FinanceStop;
226.                m_Info.IsMaximized = true;
227.                m_Info.minx = m_Info.x = 115;
228.                m_Info.miny = m_Info.y = 64;
229.             }
230.             CreateWindowRAD(170, 210);
231.             AdjustTemplate(true);
232.          }
233. //+------------------------------------------------------------------+

Fragment der Datei C_ChartFloatingRAD.mqh

Hier ist die Änderung genau so einfach wie im Konstruktor der Klasse C_Terminal. Wir fügen lediglich einen neuen Parameter hinzu, der vom Konstruktor entgegengenommen wird (Zeile 214), und übergeben ihn dann an den Konstruktor von C_Terminal (Zeile 215). So einfach ist das. Alles ist übersichtlich und selbsterklärend.

Wir brauchen noch eine kleine Änderung. Dieses Mal handelt es sich um einen Zusatz zur Klasse C_ChartFloatingRAD. Diese Änderung ermöglicht es Chart Trade, dem Expert Advisor mitzuteilen, was der Händler tatsächlich zu tun beabsichtigt. Die Änderung ist in dem nachstehenden Ausschnitt dargestellt.

330.       case MSG_BUY_MARKET:
331.          ev = evChartTradeBuy;
332.       case MSG_SELL_MARKET:
333.          ev = (ev != evChartTradeBuy ? evChartTradeSell : ev);
334.       case MSG_CLOSE_POSITION:
335.          if ((m_Info.IsMaximized) && (sz < 0))
336.          {
337.             string szTmp = StringFormat("%d?%s?%s?%c?%d?%.2f?%.2f", ev, _Symbol, GetInfoTerminal().szSymbol, (m_Info.IsDayTrade ? 'D' : 'S'),
338.                                         m_Info.Leverage, FinanceToPoints(m_Info.FinanceTake, m_Info.Leverage), FinanceToPoints(m_Info.FinanceStop, m_Info.Leverage));                           
339.             PrintFormat("Send %s - Args ( %s )", EnumToString((EnumEvents) ev), szTmp);
340.             EventChartCustom(GetInfoTerminal().ID, ev, 0, 0, szTmp);
341.          }
342.       break;

Fragment der Datei C_ChartFloatingRAD.mqh

Diese Anpassung ist so subtil, dass sie möglicherweise unbemerkt bleibt. Sie passiert in Zeile 337 auf, wo wir einen neuen Wert hinzufügen, der an den Expert Advisor gesendet wird. Dieser Wert teilt dem Expert Advisor mit, welcher Vermögenswert – oder genauer gesagt, welcher Kontrakt – im Chart Trade angezeigt wird. Beachten Sie, dass diese Änderung eine weitere Aktualisierung des Expert Advisors erzwingt. Aber damit befassen wir uns später.


Abschließende Überlegungen

Was wir in diesem Artikel getan haben, demonstriert die Flexibilität von MQL5. Aber es schafft auch neue Herausforderungen, die wir später lösen müssen. Solche Dinge sind alles andere als einfach und lassen sich nicht immer vollständig und leicht lösen. Die Implementierung eines Systems mit Kreuzaufträgen, das es dem Nutzer von Chart Trade ermöglicht, dem Expert Advisor mitzuteilen, dass der im Chart dargestellte Vermögenswert nicht notwendigerweise derjenige ist, mit dem gehandelt wird, bringt erhebliche Komplexität mit sich. Und ich möchte klarstellen, dass die meisten dieser Probleme nicht durch Chart Trade oder den Expert Advisor selbst verursacht werden.

Das eigentliche Problem entsteht, wenn wir Elemente einbringen, die ich noch nicht eingeführt habe, oder Teile des Codes, die noch entwickelt werden müssen. Dem Nutzer die Auswahl der Kontraktsart innerhalb von Chart Trade zu ermöglichen, ist langfristig nicht die beste Lösung. Zumindest aus meiner Sicht im Moment nicht.

Es ist möglich, dass zukünftige Änderungen es praktisch und nachhaltig machen werden, dies direkt in Chart Trade zu konfigurieren. Im Moment ziehe ich es jedoch vor, die Dinge so einfach zu halten, dass sie klar erklärt werden können. Es ist einfach, schnell eine persönliche Lösung zu finden; eine Lösung zu finden, die andere verstehen und anwenden können, ist anspruchsvoller. Deshalb können Sie auch in Zukunft Updates für dieses Chart Trade-Expert Advisor System erwarten. Sie werden sicherlich kommen.

Im folgenden Video können Sie sehen, wie der Prozess direkt auf dem Chart aussieht.


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

Beigefügte Dateien |
Anexo.zip (490.53 KB)
Neuronale Netze im Handel: Ein selbstanpassendes Multi-Agenten-Modell (MASA) Neuronale Netze im Handel: Ein selbstanpassendes Multi-Agenten-Modell (MASA)
Ich lade Sie ein, sich mit dem Multi-Agent Self-Adaptive (MASA) Framework vertraut zu machen, das Reinforcement Learning und adaptive Strategien kombiniert und ein harmonisches Gleichgewicht zwischen Rentabilität und Risikomanagement unter turbulenten Marktbedingungen bietet.
Von der Grundstufe bis zur Mittelstufe: Template und Typenname (III) Von der Grundstufe bis zur Mittelstufe: Template und Typenname (III)
In diesem Artikel werden wir den ersten Teil des Themas behandeln, der für Anfänger nicht so leicht zu verstehen ist. Um nicht noch mehr Verwirrung zu stiften und dieses Thema richtig zu erklären, werden wir die Erklärung in Etappen unterteilen. Dieser Artikel ist der ersten Phase gewidmet. Auch wenn es am Ende des Artikels so aussehen mag, als hätten wir eine Sackgasse erreicht, werden wir in Wirklichkeit einen Schritt in Richtung einer anderen Situation machen, die im nächsten Artikel besser verstanden wird.
Von der Grundstufe bis zur Mittelstufe: Template und Typename (IV) Von der Grundstufe bis zur Mittelstufe: Template und Typename (IV)
In diesem Artikel werden wir uns genau ansehen, wie wir das Problem lösen können, das am Ende des vorherigen Artikels angesprochen wurde. Es wurde versucht, ein Template eines solchen Typs zu erstellen, um ein Template für die „union“ von Daten erstellen zu können.
Entwicklung eines Replay-Systems (Teil 78): Neuer Chart Trade (V) Entwicklung eines Replay-Systems (Teil 78): Neuer Chart Trade (V)
In diesem Artikel werden wir uns ansehen, wie ein Teil des Empfängercodes implementiert wird. Hier werden wir einen Expert Advisor implementieren, um zu testen und zu lernen, wie die Interaktion mit dem Protokoll funktioniert. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.