English Русский 中文 Español 日本語 Português
preview
Einen Expert Advisor von Grund auf entwickeln (Teil 30): CHART TRADE als Indikator?

Einen Expert Advisor von Grund auf entwickeln (Teil 30): CHART TRADE als Indikator?

MetaTrader 5Beispiele | 9 Dezember 2022, 10:13
334 0
Daniel Jose
Daniel Jose

Einführung

In einem früheren Artikel „Entwicklung eines Handels EA von Grund auf (Teil 29)“, haben wir Chart Trade aus dem EA entfernt. Zuvor haben wir das Gleiche mit einigen anderen Funktionen gemacht, wie Volume At Price und Times & Trade, um die Leistung und Zuverlässigkeit des EAs zu verbessern. Wenn wir den Chart Trade aus dem EA entfernen, bleibt uns nur noch das grundlegende Auftragssystem. Obwohl dies einigen Nutzern unzureichend erscheinen mag, kann der EA tatsächlich die gesamte Arbeit übernehmen. Aber es gibt Leute, die gerne in den Markt ein- und aussteigen, aber sie möchten sie nicht „schwebend“ platzieren und warten, bis der Preis ein bestimmtes Niveau erreicht hat, um in den Handel einzusteigen oder auszusteigen.

Wenn wir die MetaTrader 5-Plattform mit dem von uns gehandelten Vermögenswert verwenden (ich erwähne dies, weil wir das Cross-Order-System nutzen können, das wir in Teil 11 dieser Serie besprochen haben), haben wir Zugang zu QUICK TRADING mit Schaltflächen, die Marktaufträge platzieren. Sie sind in der oberen linken Ecke verfügbar. Sie sehen in etwa so aus:

Diese Schaltflächen funktionieren wie das einfache Chart Trading, können aber nicht für das Cross-Order-System verwendet werden, da sie dann nicht sichtbar sind. In diesem Fall müssen wir also zu unserem Chart Trade zurückkehren. Sie wird jedoch nicht mehr innerhalb des EAs verwendet und ist auch nicht mehr Teil des EA-Codes. Von nun an wird Chart Trade nur noch ein Indikator sein.

Warum müssen wir das mit dem Chart Trading tun? Der Grund dafür ist, dass der EA nur für das Handelssystem verantwortlich sein sollte, und alles, was nicht Teil dieses Systems ist, sollte irgendwie aus dem EA-Code entfernt werden. Dies mag jetzt noch bedeutungslos erscheinen, aber bald wird es klarer werden, da ich bereits eine Fortsetzung dieses Artikels vorbereite, in der ich den genauen Grund für dieses Phänomen erklären werde.

Es könnte sogar das Chart Trading als Skript verwendet werden, aber das hätte einen Nachteil: Jedes Mal, wenn wir den Zeitrahmen des Charts ändern, würde das Skript geschlossen, sodass wir es manuell erneut ausführen müssten.

Dies ist nicht der Fall, wenn wir ihn als Indikator verwenden. Einer der Gründe dafür ist, dass der Chart Trade den Ausführungs-Thread der Indikatoren nicht beeinflusst, sodass der EA frei ist und sich auf seinen Code und nur auf die Verwaltung von Aufträgen und Positionen konzentriert.

Obwohl wir nicht alle Steuerelemente und alle Informationen der ursprünglichen Version haben, ist dieser Chart Trade als Indikator viel einfacher und funktioniert trotzdem. Das MetaTrader 5-System ist hochfunktional und dennoch einfach. Es bietet jedoch keinen Zugang zu bestimmten Informationen, die wir in unserem Chart Trade haben werden.

Also, fangen wir an — das Thema wird sehr interessant sein.


2.0. Entwicklung eines Chart Trade Indikators

Um diesen Indikator zu erstellen, müssen wir eine ganze Reihe von Änderungen vornehmen. Wir werden diese Änderungen implementieren, um den Chart-Trade an den Chart zurückzugeben, ohne ihn zum Teil des EA-Codes zu machen.

Auf den ersten Blick könnte man meinen, dass der unten gezeigte Code ausreichen würde, um den Chart Trade zumindest zu kompilieren. Aber nein, denn sie ist untrennbar mit mehreren Dingen verbunden, die wir abschalten müssen. Wir löschen sie nicht vollständig, da wir sie vielleicht eines Tages wieder in den EA zurückholen wollen.

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh>
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_Terminal                      Terminal;
//+------------------------------------------------------------------+
int OnInit()
{
        Chart.AddThese("IDE(,,170, 240)");

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Wenn wir versuchen, diesen Code zu kompilieren, werden wir eine Menge Fehler erhalten, aber wir werden sie nach und nach beheben, um den Code mit dem EA kompatibel zu halten, während wir ihn für die Verwendung als Indikator anpassen.

Als erstes müssen wir das System isolieren, das Unterfenster erstellt oder verwaltet. Da Chart Trade diese Unterfenster nicht verwendet, ist es nicht notwendig, diesen Code in den Indikator einzubetten. Das ist ziemlich einfach zu bewerkstelligen. Wir müssen die Datei C_Chart_IDE.mqh so bearbeiten, dass sie wie folgt aussieht:

#ifdef def_INTEGRATION_CHART_TRADER
        #include <NanoEA-SIMD\SubWindow\C_SubWindow.mqh>
        #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#else
        #include <NanoEA-SIMD\SubWindow\C_ChartFloating.mqh>
        #include <NanoEA-SIMD\Auxiliar\C_Terminal.mqh>
#endif  
//+------------------------------------------------------------------+
#ifdef def_INTEGRATION_CHART_TRADER
        class C_Chart_IDE : public C_SubWindow
#else 
        class C_Chart_IDE : public C_ChartFloating
#endif 

Auf diese Weise isolieren wir das System vollständig von den Unterfenstern und vermeiden gleichzeitig jede Verbindung zwischen Chart Trade und dem Order-Anzeigesystem des EA. Beachten Sie, dass dies durch die def_INTEGRATION_CHART_TRADER Definition gesteuert wird, aber da diese Definition nur im EA verwendet wird, wird alles, was darin steht, nicht in den Indikator kompiliert.

Da der Indikator keine Unterfenster verwenden wird, müssen wir auch ein paar Dinge anpassen. Eine davon ist unten abgebildet:

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
                                m_szLine = "";
                                while (m_szLine != "</chart>")

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

Die Funktion GetIdSubWinEA gibt die Nummer des Fensters zurück, in dem sich der Indikator befindet. Dieser Aufruf wird an mehreren Stellen durchgeführt. Hierfür gibt es zwei Lösungen. Die erste Lösung besteht darin, die obige Funktion an jedem Punkt, an dem der Aufruf erfolgt, wie folgt zu ändern.

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
#ifdef def_INTEGRATION_CHART_TRADER
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
#else 
                                m_SubWindow = 0;
#endif 

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

Das würde das Problem zwar lösen, aber dann müssten wir so viele Änderungen vornehmen, dass der Code nach einer Weile zu schwer verständlich werden würde, weil es zu viele dieser bedingten Kompilierungsanweisungen gäbe. Ich habe eine viel einfachere, aber ebenso wirksame Lösung: Um diesen und jeden anderen Aufruf durch die Definition zu „emulieren“, können wir einfach den folgenden Schnipsel in den Systemcode einfügen.

#ifndef def_INTEGRATION_CHART_TRADER
        #define GetIdSubWinEA() 0
        #define ExistSubWin() false
#endif 

Mit diesem Code wird das Aufrufproblem gelöst. Dies reicht (mit einigen weiteren Details) aus, um den Indikator kompilierbar zu machen.

Das zweitgrößte Problem betrifft die Funktionen, die an Mausereignissen beteiligt sind, nicht die Maus selbst, sondern die Klasse C_Mouse.

Wenn Chart Trade Teil des EAs war, war es möglich, auf Mauspositionen zuzugreifen (unter anderem), aber wenn wir nur die C_Mouse-Klasse zum Indikator hinzufügen, dann haben wir einen Interessenkonflikt zwischen dem Indikator und dem EA. Ich werde jetzt nicht zeigen, wie der Konflikt gelöst wird. Wir werden eine andere Richtung einschlagen, aber nur vorübergehend, bis alles geklärt ist und bis wir zumindest einen Teil der Funktionalität des ursprünglichen Chart Trade erhalten.

Zu diesem Zweck müssen wir einige Dinge aus der Klasse C_Mouse in unseren neuen Indikator übertragen. Aber keine Sorge, das ist nur eine Kleinigkeit. Die erste Änderung findet in der Klasse C_TemplateChart statt — wir werden die folgende Änderung in ihrer DispatchMessage-Funktion vornehmen:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionDP(dt, p);
#else
                        {
                                int w;
                                ChartXYToTimePrice(Terminal.Get_ID(), (int)lparam, (int)dparam, w, dt, p);
                        }
#endif 
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")

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

Durch Hinzufügen des hervorgehobenen Abschnitts erhalten wir bereits die Funktionalität, als würde die Klasse C_Mouse noch existieren. Die nächste Änderung wird in der Klasse C_ChartFloating vorgenommen, wo wir etwas Ähnliches wie zuvor machen. Aber der Code ist ein bisschen anders:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionXY(mx, my);
                        if ((Mouse.GetButtonStatus() & 0x01) == 1)
#else 
                        mx = (int)lparam;
                        my = (int)dparam;
                        if (((uint)sparam & 0x01) == 1)
#endif 
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)

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

Nun können wir den Indikator kompilieren, woraufhin wir das im folgenden Video gezeigte Ergebnis erhalten:



Obwohl der EA noch nicht voll funktionsfähig ist, müssen wir die folgende Entscheidung treffen: Entweder Chart Trade wird die gleichen Fähigkeiten wie der EA haben oder sie können reduziert werden, um etwas zwischen dem zu haben, was es vorher war und dem, was derzeit in MetaTrader 5 angeboten wird. Da meine Position ziemlich radikal ist, werde ich Chart Trade mit fast den gleichen Funktionen belassen, die es im EA hatte. Sie können sie reduzieren, wenn Sie dies wünschen.

Nachdem wir diese Entscheidung getroffen haben, können wir zum nächsten Thema übergehen, da der Chart Trade gerade zu einem Indikator geworden ist.


2.1. Wie man den Chart Trade Indikator funktionsfähig macht

Jetzt wird es noch schwieriger werden. Schauen wir uns also an, wie wir diesen Indikator funktionsfähig machen, sodass wir Aufträge senden, Positionen schließen und sogar die Ergebnisse von Transaktionen melden können. In der Tat ist dies nicht so schwierig, wie es auf den ersten Blick scheint, denn die MetaTrader 5-Plattform bietet einen Pfad, dem man folgen kann, sodass dies mit minimalem Aufwand möglich ist.

Hier werden wir dasselbe tun wie in Teil 16, in dem wir einige der Funktionen von MetaTrader 5 verwendet haben, um ein internes Client-Server-System (innerhalb der Plattform) zu erstellen, um Daten zwischen verschiedenen Prozessen zu übertragen. Wir werden hier etwas Ähnliches machen, nur die Simulation wird etwas anders sein, da wir eine Zwei-Wege-Kommunikation brauchen, die für den Nutzer unsichtbar bleiben muss.

Die Methode, die wir anwenden werden, ist nicht die einzig mögliche. Es gibt auch andere Möglichkeiten, z. B. die Verwendung einer DLL, um diese Übertragung zu ermöglichen. Wir werden jedoch MetaTrader 5-Variablen verwenden, da sie bei weitem am einfachsten zu navigieren, zu pflegen und bei Bedarf zu ändern sind.

Nachdem wir uns nun für diesen Weg entschieden haben, steht die zweite wichtige Entscheidung an: Wer wird der Server und wer der Client sein? Diese Entscheidung wird sich darauf auswirken, wie das System tatsächlich umgesetzt werden wird. Wie auch immer, wir haben bereits unser Nachrichtenprotokoll, dessen Implementierung unten gezeigt wird:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableLeverage      (_Symbol + "_Leverage")
#define def_GlobalVariableTake          (_Symbol + "_Take")
#define def_GlobalVariableStop          (_Symbol + "_Stop")
#define def_GlobalVariableResult        (_Symbol + "_Result")
#define def_GlobalVariableButton        (_Symbol + "_ButtonState")
//+------------------------------------------------------------------+
#define def_ButtonDTSelect              0x01
#define def_ButtonSWSelect              0x02
#define def_ButtonBuyMarket             0x04
#define def_ButtonSellMarket            0x08
#define def_ButtonClosePosition         0x10
//+------------------------------------------------------------------+

Das ist alles. Wir brauchen eigentlich nicht festzulegen, wer der Client und wer der Server sein wird, da wir das Nachrichtenprotokoll bereits definiert haben. Dieses Protokoll muss von Anfang an festgelegt werden, da dies die Entwicklung aller anderen Elemente erleichtert.

Bitte beachten Sie, dass wir für jeden Vermögenswert, für den das Chart Trade EA-Set vorhanden ist, 5 Variablen verwenden werden. Die Variablennamen hängen von dem Asset ab, mit dem das Set verknüpft ist, sodass das Set für mehrere Assets gleichzeitig verwendet werden kann.

Hier stellt sich eine wichtige Frage: Wer wird die Arbeit des Servers übernehmen? Er ist für die Erstellung solcher Variablen verantwortlich. Ich persönlich finde es praktischer, den EA als Server zu verwenden und Chart Trade als Client zu belassen. Die Idee ist, den EA immer auf dem Chart zu haben, während Chart Trade bei Bedarf zu bestimmten Zeiten zur Verfügung steht. Somit wird der EA für die Erstellung von 4 der 5 Variablen verantwortlich sein, da eine von ihnen für die Information, welche Taste gedrückt wurde, zuständig sein wird. Dies ist also die Aufgabe von Chart Trade.

Auf dieser Grundlage sieht der Datenfluss wie folgt aus:

  1. Der EA erstellt globale Variablen, die Anfangswerte für den Take-Profit, Stopp-Loss und den Hebel enthalten. Schließlich wird es auch die Variable erstellen, die über das Ergebnis des Tages informiert, sodass Chart Trade es dem Nutzer anzeigen kann und es nicht notwendig ist, diese Informationen anderswo zu suchen.
  2. Chart Trade erstellt eine Variable, die den Wert des Tastendrucks darstellt. Diese Variable teilt dem EA mit, was zu tun ist, z. B. eine offene Position zu schließen oder einen Marktkauf oder -verkauf durchzuführen. Diese Variable existiert nur während dieses Zeitraums und erlischt, sobald der Auftrag vom EA abgeschlossen ist.

Der Ablauf ist einfach, aber er garantiert, dass Chart Trade mit dem EA interagieren kann, einschließlich der Fähigkeit des Systems, Orders zu senden und Positionen zu schließen, so wie es vorher der Fall war.

Um sicherzustellen, dass der Chart Trade nur auf dem Chart existiert, wenn der EA vorhanden ist, müssen wir einige Prüfungen hinzufügen. Es hat keinen Sinn, den Chart Trade auf dem Chart zu haben, wenn der EA nicht verfügbar ist, um Aufträge zu senden. Zu diesen Kontrollen gehören die beiden folgenden Momente:

  • Die erste wird bei der Initialisierung des Indikators durchgeführt.
  •  Die zweite wird ausgeführt, wenn ein Ereignis auf dem Chart auftritt.

Schauen wir uns das im Code an, damit wir den Vorgang besser verstehen können. Während der Initialisierung wird der folgende Code ausgeführt:

#define def_SHORTNAME "CHART TRADE"
//+------------------------------------------------------------------+
int OnInit()
{
        long lparam = 0;
        double dparam = 0.0;
        string sparam = "";
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_SHORTNAME);
        if(!GlobalVariableGet(def_GlobalVariableLeverage, dparam)) return INIT_FAILED;
        Terminal.Init();
        Chart.AddThese("IDE(,,170, 215)");
        Chart.InitilizeChartTrade(dparam * Terminal.GetVolumeMinimal(), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), true);
        OnChartEvent(CHARTEVENT_OBJECT_ENDEDIT, lparam, dparam, sparam);

        return INIT_SUCCEEDED;
}

In der hervorgehobenen Zeile wird eine der globalen Variablen ausgewertet, in diesem Fall diejenige, die die Höhe der Hebelwirkung angibt. Wenn diese Variable fehlt, schlägt die Initialisierung fehl. Denken wir daran, dass der EA für die Erstellung dieser Variable verantwortlich ist, nicht Chart Trade. Auf diese Weise weiß der Indikator, ob der EA auf dem Chart vorhanden ist oder nicht, und wenn der EA seine Arbeit beendet hat, wird er diese Variable aus dem MetaTrader 5 entfernen und Chart Trade dazu zwingen, ebenfalls gelöscht zu werden. Dies geschieht am zweiten Punkt, an dem wir die gleiche Bedingung prüfen — ob die globale Variable des Hebels verfügbar ist oder nicht.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

Der oben genannte Punkt wird mit einer relativ hohen Frequenz umgesetzt. Auch wenn es verlockend ist, ein OnTime-Ereignissystem in einem Indikator zu platzieren, ist dies nicht ratsam: Alle Indikatoren verwenden denselben Thread, und die Platzierung eines OnTime-Ereignisses in einem Indikator wirkt sich auf alle anderen aus, wenn dies nicht mit äußerster Vorsicht geschieht.

Wenn Sie der Meinung sind, dass die obige Prüfung die allgemeine Leistung der Plattform stört, können Sie sie in einem OnTime-Ereignis unterbringen, aber seien Sie sich der Konsequenzen bewusst.

Unabhängig davon, wer die Entfernung des Chart Trade-Indikators aus dem Diagramm auslöst, geschieht dies an demselben Punkt, der unten zu sehen ist:

void OnDeinit(const int reason) 
{ 
        if (reason == REASON_INITFAILED)
        {
                Print("Unable to use Chart Trade. The EA is not on the chart of this asset...");
                if (!ChartIndicatorDelete(0, 0, def_SHORTNAME))
                        Print("Unable to delete Chart Trade from the chart.");
        }
}

Mit der ausgewählten Zeile wird der Indikator aus dem Chart entfernt. Im Falle eines Fehlers wird eine entsprechende Meldung angezeigt. Diese Meldungen sind in der Toolbox wie unten dargestellt zu sehen:

Auf diese Weise müssen Sie immer über alle Informationen in diesem Fenster informiert sein.

Es fehlt noch eine letzte Funktion, bevor wir zu einer tieferen Analyse der Änderungen in der Klasse C_Chart_IDE übergehen. Diese Funktion ist wie folgt:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        double value;
        
        if (GlobalVariableGet(def_GlobalVariableResult, value))
        {
                GlobalVariableDel(def_GlobalVariableResult);
                Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, value, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        }
   
        return rates_total;
}

Mit dieser Funktion werden globale Variablen überwacht. So wird der EA von Zeit zu Zeit, wenn er eine Position schließt, eine globale Variable erstellen, deren Name in def_GlobalVariableResult definiert ist. Sobald diese Variable erstellt ist und ihr Name mit dem von Chart Trade beobachteten übereinstimmt, wird der Wert dieser Variable erfasst und die Variable sofort gelöscht. Damit soll vermieden werden, dass der EA die Aktualisierung sendet, bevor die Verarbeitung abgeschlossen ist, wodurch diese Aktualisierung verloren geht. Indem wir jedoch den Wert vor dem Löschen erfassen, können wir ihn an die Klasse Chart Trade senden, die für die Bearbeitung von Nachrichten zuständig ist, sodass der vom EA übergebene Wert so schnell wie möglich in Chart Trade angezeigt wird.

Damit ist der erste Teil abgeschlossen, in dem wir den Chart Trade Indikator funktionsfähig machen. Der zweite Teil betrifft die Tasten. Wir müssen sie auch funktional gestalten. Dies kann einfach in der Funktion zur Verarbeitung von Meldungen in der Funktion C_Chart_IDE erfolgen:

// ... Previous code ...

case CHARTEVENT_OBJECT_CLICK:
        if (StringSubstr(sparam, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG)
	{
		Resize(-1);
		return;
	}
	sparam = StringSubstr(sparam, 9, StringLen(sparam));
	StringToUpper(sparam);
#ifdef def_INTEGRATION_CHART_TRADER
	if ((sparam == szMsgIDE[eBTN_SELL]) || (sparam == szMsgIDE[eBTN_BUY]))
		TradeView.ExecuteOrderInMarket(m_BaseFinance.Leverange, m_BaseFinance.FinanceTake, m_BaseFinance.FinanceStop, sparam == szMsgIDE[eBTN_BUY], m_BaseFinance.IsDayTrade);
	if (sparam == szMsgIDE[eBTN_CANCEL])
	{
		TradeView.CloseAllsPosition();
		ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
	}
#else 
	{
		union u00
		{
			double Value;
			ulong c;
		}u_local;
                                                        
		u_local.c = 0;
		if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; else
		if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonSellMarket; else
		if (sparam == szMsgIDE[eBTN_CANCEL])
		{
			u_local.c = def_ButtonClosePosition;
			ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
		}
                if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value);
	}
#endif 
	if (sparam == szMsgIDE[eCHECK_DAYTRADE]) InitilizeChartTrade(0, 0, 0, m_BaseFinance.IsDayTrade ? false : true);
	break;

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

Beachten Sie, dass das Fragment zwei Codes enthält. Der blau markierte Code wird verwendet, wenn Chart Trade in den EA eingebettet ist. Der grün Markierte wird verwendet, wenn Chart Trade als Indikator läuft.

Wir sind an dem grünen Code interessiert. Es wird eine globale Variable erstellt und der Zustand der Schaltflächen festgelegt. Wenn also ein Händler eine Position schließt, wird der Wert, der der Schaltfläche „Schließen“ entspricht, in die Variable übernommen. Wenn Sie jedoch einen Marktauftrag erteilen, sieht dieser Wert anders aus und ist nun eine Kombination aus zwei anderen Werten: Der eine gibt an, ob es sich um einen Kauf- oder Verkaufsauftrag handelt, während der andere angibt, ob Sie einen Tageshandel oder einen längeren Handel tätigen möchten. Dies ist alles, was Chart Trade dem EA mitteilen wird.

WICHTIG! Dieses System schließt sich selbst aus, d.h. wenn Sie auf die Schaltfläche „Verkaufen“ klicken und dann auf die Schaltfläche „Kaufen“ klicken, bevor der EA irgendetwas tut, wird der EA tatsächlich kaufen, da der Wert, der auf „Verkaufen“ hinweist, durch den nachfolgenden Kauf verloren geht. Wenn Sie eine Verkaufs- oder Kaufanfrage stellen, während bereits eine Position offen ist, und wenn Sie auf Abbrechen drücken, bevor der EA den entsprechenden Handel durchführt, wird die Position geschlossen.

Nun können wir uns dem EA-Code zuwenden, um zu sehen, wie er im neuen Modell funktioniert.


2.2. Ändern des EA-Codes zum Empfang von Nachrichten von Chart Trade

Dieser Teil ist ziemlich einfach, da wir nur ein paar kleine Details anpassen müssen. Beachten Sie jedoch Folgendes: Ändern Sie nach dem Laden des EAs und von Chart Trade keine globalen Variablen oder Daten, die im EA enthalten sind. Wir verwenden dazu das Auftragssystem oder Chart Trade selbst, sonst kann es zu Problemen kommen. Für manche Probleme gibt es Lösungen, für andere nicht. Nutzen wir also vorsichtshalber die zur Verfügung stehenden Mittel und versuchen nicht, uns das Leben zu verkomplizieren. 

Im vorigen Artikel (Teil 29) haben wir bei der Förderung der Abschaffung von Chart Trade einige Änderungen vorgenommen, die zum Teil wieder rückgängig gemacht werden. Wir werden nichts anderes in Bezug auf diese Frage ändern müssen. Aber wie bereits erwähnt, lassen sich einige Dinge beheben und andere nicht. Daher werden wir im nächsten Teil dieses Artikels einige kleine Probleme in der Beziehung zwischen Chart Trade und dem Expert Advisor beseitigen.

Schauen wir uns zunächst an, was wir im EA rückgängig machen und aktivieren müssen, damit eine gewisse Kommunikation zwischen dem EA und Chart Trade möglich ist.

Ändern wir zunächst Folgendes:

input int       user20      = 1;        //Leverage
input double    user21      = 100;      //Take Profit
input double    user22      = 81.74;    //Stop Loss 
input bool      EA_user23   = true;     //Day Trade ?

Der Wert, der angibt, ob der EA lieber Kauf- oder Verkaufsgeschäfte eröffnet, bleibt unverändert. Dies muss im Chart Trade oder in einem schwebenden Auftrag geschehen, die Sie im Chart platzieren. Ich habe Ihnen bereits in früheren Artikeln gezeigt, wie das geht, also beginnen wir mit der Codierung. Fügen wir die folgenden Änderungen im OnInit-Ereignis hinzu:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound.PlayAlert(C_Sounds::TRADE_ALLOWED);
                return INIT_FAILED;
        }
        
        Terminal.Init();

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Init(user32, user33, user30, user31);
        TimesAndTrade.Init(user41);
        EventSetTimer(1);
#endif 

        Mouse.Init(user50, user51, user52);
        
#ifdef def_INTEGRATION_CHART_TRADER
        static string   memSzUser01 = "";
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23);
        TradeView.Initilize();
   OnTrade();
#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
#endif 
   
        return INIT_SUCCEEDED;
}

Wie Sie sehen können, fügen wir hier globale Variablen hinzu, die für die Kommunikation verwendet werden sollen. Wie bereits erwähnt, muss der EA immer vor dem Chart Trade gestartet werden, da wir sonst den Indikator nicht initialisieren können. Bitte beachten Sie, dass die Werte, die zunächst von Chart Trade verwendet werden, im EA festgelegt werden. Die Initialisierung wird abgeschlossen, auch wenn es vorhergehende Trades gab: der kumulierte Wert wird auch wieder in Chart Trade übertragen.

Achten Sie auf ein wichtiges Detail: Die erstellten Variablen sind vom temporären Typ, da wir nicht wollen, dass diese Variablen im Falle des EA-Dump gespeichert werden, da sie nach einer gewissen Zeit nicht mehr von Nutzen sind. Selbst wenn etwas passiert und die Plattform abgeschaltet wird, sind diese Variablen nicht mehr vorhanden.

Ein weiterer zu implementierender Zusatz ist unten dargestellt:

void OnDeinit(const int reason)
{
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Auch wenn die Variablen temporär sind, bitten wir den EA, sie zwangsweise zu entfernen. Dies hat zur Folge, dass der Chart Trade nicht mehr auf dem Chart verbleibt. Es gibt ein kleines Problem, wenn OnDeinit aufgerufen wird, aber wir werden es im nächsten Thema behandeln. Lassen Sie uns nun einen weiteren interessanten Punkt betrachten. Dies kann auf zwei verschiedene Arten geschehen, wobei die Ergebnisse beinahe identisch sein werden. Ich sage BEINAHE identisch, da es kleine Unterschiede gibt, die einen Unterschied machen könnten. (Entschuldigung für das Wortspiel)

Der Chart Trade hat einige Schaltflächen, die über eine globale Variable Nachrichten an den EA senden. Hier ist die Funktion innerhalb des EA, die angemessen auf diese Klicks reagiert:

inline void ChartTrade_ClickButton(void)
{
        union u00
        {
                double Value;
                ulong c;
        }u_local;
        
        if (GlobalVariableGet(def_GlobalVariableButton, u_local.Value))
        {
                GlobalVariableDel(def_GlobalVariableButton);
                                
                if (u_local.c == def_ButtonClosePosition) TradeView.CloseAllsPosition(); else
                        TradeView.ExecuteOrderInMarket(GlobalVariableGet(def_GlobalVariableLeverage), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), ((u_local.c & def_ButtonBuyMarket) == def_ButtonBuyMarket), ((u_local.c & def_ButtonDTSelect) == def_ButtonDTSelect));
                TradeView.Initilize();
        }
}

Die Funktion wird als inline deklariert, d.h. sie wird vom Compiler an die Stelle gesetzt werden, an der sie deklariert ist. Dadurch wird es so schnell wie möglich laufen.

Ein sehr wichtiges Detail: Wo platzieren wir diese Funktion? Überlegen wir mal. Wir haben hier eine Prüfung, die nicht ständig läuft. Wir haben also zwei Möglichkeiten. Die erste ist, die Funktion innerhalb von OnTick zu platzieren, die zweite - innerhalb von OnTime. Die Auswahl muss nach einer bestimmten Logik erfolgen, sonst kann es zu Problemen kommen.

Denken wir nach. Wenn wir diese Funktion in OnTick einfügen, wird sie bei jedem neuen Tick, der vom Handelsserver in die Plattform kommt, ausgeführt. Dies scheint eine gute Lösung zu sein, da die Funktion nicht oft ausgeführt wird, sondern nur zu bestimmten Zeiten. Dies schafft jedoch ein Problem: Wenn die Volatilität des gehandelten Vermögenswerts sehr gering ist, wird auch die Häufigkeit von OnTick-Aufrufen sehr gering sein, was bedeutet, dass es Probleme mit der Auslösung des Ereignisses durch einen Klick auf den Chart Trade geben kann.

Hier ist die zweite Möglichkeit. Die Platzierung der Funktion in OnTime stellt sicher, dass die Funktion ausgeführt wird, da das OnTime-Ereignis mit einer gewissen Regelmäßigkeit ausgelöst wird. Wenn wir dies tun, müssen wir jedoch bedenken, dass wir OnTime nicht mehr für etwas anderes als die Beobachtung der globalen Variablen verwenden können.

In diesem Stadium haben wir eine sehr gute Wahl getroffen, da der EA nur den Markt beobachten wird. In den nächsten Artikeln werden Sie sehen, dass dies immer deutlicher wird. Dann ist es eine gute Idee, eine Funktion in OnTime einzufügen. Aber jetzt ist die Frage: OnTime wird nur jede Sekunde ausgelöst, nicht wahr?

Meistens wird er einmal pro Sekunde ausgelöst, aber wir können mit der Funktion EventSetMillisecondTimer auch einen kürzeren Zeitraum einstellen. So können wir das Ereignis in weniger als 1 Sekunde auslösen. Ich glaube, dass 500 ms für die meisten Fälle ausreichen würden, also werden wir die folgende Zeile in OnInit des EAs einfügen:

#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
        EventSetMillisecondTimer(500);
#endif 

Vergessen wir nicht, dieses Ereignis in der Funktion OnDeinit zu beenden, wofür wir einfach die folgende Zeile in das Ereignis einfügen:

void OnDeinit(const int reason)
{
        EventKillTimer();

Schauen wir uns nun an, wie das Ereignis OnTime aussieht. Es handelt sich um einen sehr einfachen Code, bei dem nur die hervorgehobene Zeile tatsächlich kompiliert wird.

void OnTimer()
{
#ifndef def_INTEGRATION_CHART_TRADER
        ChartTrade_ClickButton();
#endif

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
#endif 
}

Ist das alles? Nein, es gibt noch ein kleines Problem. Erinnern wir uns daran, dass wir die Initialisierungsvariablen des EAs geändert haben und jedes Mal, wenn ein neuer schwebender Auftrag auf dem Chart platziert wird, Daten von Chart Trade abrufen möchten. Zu diesem Zweck müssen wir ein kleines Detail im Code der Klasse C_IndicatorTradeView hinzufügen. Er ist unten aufgeführt:

        case CHARTEVENT_MOUSE_MOVE:
                Mouse.GetPositionDP(dt, price);
                mKeys   = Mouse.GetButtonStatus();
                bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse button click
                bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                if (bKeyBuy != bKeySell)
                {
                        if (!bMounting)
                        {
#ifdef def_INTEGRATION_CHART_TRADER
                                m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
#else 
                                m_Selection.vol = GlobalVariableGet(def_GlobalVariableLeverage) * Terminal.GetVolumeMinimal();
                                valueTp = GlobalVariableGet(def_GlobalVariableTake);
                                valueSl = GlobalVariableGet(def_GlobalVariableStop);
                                m_Selection.bIsDayTrade = EA_user23;
#endif 

Die hervorgehobenen Zeilen erfassen nun die Werte in den globalen Variablen, sodass alle Werte im Chart Trade in das Auftragssystem übernommen werden. Das einzige Detail ist, dass alle schwebenden Aufträge der vom EA festgelegten Zeit folgen, aber das kann direkt im Chart geändert werden. Weitere Einzelheiten finden Sie unter Entwicklung eines Expert Advisors von Grund auf (Teil 27), in dem ich gezeigt habe, wie Sie schwebende Aufträge direkt im Chart ändern können, ohne Chart Trade zu nutzen.

Es gibt auch eine kleine Änderung im Soundsystem. Die Töne wurden in der Klasse C_Router hinzugefügt, um eine Benachrichtigung über das Öffnen und Schließen von Positionen sicherzustellen, wenn wir mit Chart Trade arbeiten. Wenn Sie etwas am Soundsystem entfernen oder ändern wollen, sollten Sie daran denken, dass es noch andere Punkte zu beachten gibt.

Bevor wir fertig sind, sollten wir uns daran erinnern, dass OnDeinit ein Problem hat, das wir beheben wollten. Lassen Sie uns also zum nächsten Thema übergehen und dieses Problem lösen.


2.3. Verhindern, dass Chart Trade das Chart vorzeitig verlässt

Wenn wir etwas tun, wie z.B. den Chart-Zeitrahmen oder die EA-Parameter ändern (das kann alles sein), ruft MetaTrader 5 OnDeinit auf. Das Auslösen dieses Ereignisses bedeutet, dass die Dinge neu analysiert werden müssen, um sicherzustellen, dass alles weiterhin so funktioniert, wie es sollte.

In den meisten Fällen verursacht das Auslösen dieses Ereignisses keine Probleme. Da wir jedoch ein Client-Server-System erstellen und globale Variablen verwenden, um festzustellen, ob der Server (der EA) nicht mehr läuft, müssen wir wissen, wie wir bestimmte Situationen umgehen können.

Die Funktion, die das ursprüngliche OnDeinit-Ereignis verarbeitet, sieht folgendermaßen aus:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Die hervorgehobene Zeile entfernt die globale Variable, genau die, die wir im Chart Trade Indikator verwenden, um zu prüfen, ob der EA im Chart läuft oder nicht. Diese Prüfung wird im folgenden Code durchgeführt:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

Das heißt, jedes Mal, wenn im EA etwas passiert, das OnDeinit auslöst, wird diese Variable gelöscht. Und bevor der EA die Variable neu erstellt, kann der Indikator bereits aus dem Chart entfernt sein, was wirklich seltsam sein kann. An manchen Stellen wird es passieren, an anderen nicht. Wir müssen also irgendwie sicherstellen, dass alles wie erwartet funktioniert.

Ich habe in der Dokumentation eine Lösung für dieses Problem gefunden. Dies ist im Abschnitt Codes der Deinitialisierungsgründe zu sehen. Anhand dieser Codes können wir die Ereignisbehandlungsfunktion OnDeinit so konfigurieren, dass die Variablen erst gelöscht werden, wenn der EA aus dem Chart entfernt wird.

Die Lösung und der neue Verarbeitungscode werden also folgendermaßen aussehen:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        break;
                default:                
                        GlobalVariableDel(def_GlobalVariableLeverage);
                        GlobalVariableDel(def_GlobalVariableTake);
                        GlobalVariableDel(def_GlobalVariableStop);
                        GlobalVariableDel(def_GlobalVariableResult);
                        GlobalVariableDel(def_GlobalVariableButton);
        };
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Wenn wir jetzt nur den Zeitrahmen oder die Darstellungsart des Charts ändern, haben wir nicht mehr die Unannehmlichkeiten, die mit dem Verschwinden des Chart Trade Indikators aus dem Chart verbunden sind. In jeder anderen Situation kann es aus dem Chart entfernt werden, da wir nicht wirklich die Gewissheit haben, dass alles klappen wird. Aus diesem Grund ist es nicht mehr sinnvoll, die EA-Parameter zu ändern, sobald der EA und der Chart Trade geladen wurden.


Schlussfolgerung

Sehen Sie, was ein wenig Kreativität bewirken kann? Manchmal müssen wir Probleme lösen, die unlösbar scheinen. Aber durch das Studium der Dokumentation können wir die Lösung finden. Deshalb ist es wichtig, die Dokumentation immer zu prüfen und zu verstehen, um Ideen in die Praxis umsetzen zu können.


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

Beigefügte Dateien |
EA_-_p_Parte_30_u.zip (14532.23 KB)
DoEasy. Steuerung (Teil 23): Verbesserung der WinForms-Objekte TabControl und SplitContainer DoEasy. Steuerung (Teil 23): Verbesserung der WinForms-Objekte TabControl und SplitContainer
In diesem Artikel werde ich neue Mausereignisse relativ zu den Grenzen der Arbeitsbereiche von WinForms-Objekten hinzufügen und einige Mängel in der Funktionsweise der TabControl- und SplitContainer-Steuerelemente beheben.
Neuronale Netze leicht gemacht (Teil 30): Genetische Algorithmen Neuronale Netze leicht gemacht (Teil 30): Genetische Algorithmen
Heute möchte ich Ihnen eine etwas andere Lernmethode vorstellen. Wir können sagen, dass sie von Darwins Evolutionstheorie entlehnt ist. Sie ist wahrscheinlich weniger kontrollierbar als die zuvor besprochenen Methoden, aber sie ermöglicht die Ausbildung nicht-differenzierbarer Modelle.
Verwenden von Linien in MQL5 Verwenden von Linien in MQL5
In diesem Artikel erfahren Sie, wie Sie mit den wichtigsten Linien wie Trendlinien, Unterstützung und Widerstand von MQL5 verwenden können.
Lernen Sie, wie man ein Handelssystem mit dem Accelerator Oscillator entwickelt Lernen Sie, wie man ein Handelssystem mit dem Accelerator Oscillator entwickelt
Ein neuer Artikel aus unserer Serie über die Erstellung einfacher Handelssysteme anhand der beliebtesten technischen Indikatoren. Wir werden einen neuen Indikator kennenlernen, den Accelerator Oscillator, und wir werden lernen, wie man ein Handelssystem mit ihm entwickelt.