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

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 14): Hinzufügen des Volumens zum Preis (II)

MetaTrader 5Handelssysteme | 18 Juli 2022, 11:11
223 0
Daniel Jose
Daniel Jose

Einführung

Unser EA verfügt bereits über einige Ressourcen, die beim Trading helfen — wir haben sie in unseren vorherigen Artikeln hinzugefügt. Dieser EA hat jedoch einige Probleme mit der Visualisierung und Größenänderung. Sie stören den Handel nicht, aber an einigen Stellen führt dies zu einem Durcheinander auf dem Bildschirm, bis Sie ihn zur Aktualisierung zwingen. Außerdem fehlen noch einige Dinge, die uns wertvolle Informationen liefern würden. Dies sind spezifische Dinge, aber die Informationen können notwendig sein.

Beginnen wir also mit der Implementierung dieser neuen Verbesserungen. Dieser interessante Artikel kann einige neue Ideen und Methoden zur Präsentation von Informationen liefern. Gleichzeitig kann es helfen, kleinere Fehler in Projekten zu beheben.


Planung und Implementierung einer neuen Funktion in Volumen zum Preis

1. Planung

Es gibt eine merkwürdige Sache beim Handel. Wir sehen oft, dass der Markt in bestimmten Preisregionen akkumuliert, und wenn Stopps entweder auf der Kauf- oder auf der Verkaufsseite ausgelöst werden, gibt es eine schnelle Preisbewegung. Diese Bewegung kann über Times & Trade beobachtet werden. Wir haben dies in den vorherigen Artikeln Times & Trade (I) und Times & Trade (II) berücksichtigt. In diesen Artikeln haben wir uns angesehen, wie man ein alternatives grafisches System zum Lesen und Analysieren eines Auftragsflusses ausgeführter Aufträge erstellt. Wenn man genauer hinsehen, kann man feststellen, dass der Preis in manchen Momenten dazu neigt, in die Akkumulationsregion zurückzukehren, wo er in diesem Moment nicht gehen möchte. Aber wenn wir den Indikator „Volume at Price“ (Volumen zum Preis) beobachten, ist es schwierig festzustellen, ob sich vor kurzem Preis in dieser bestimmten Region nicht verändert hat. Dieser Indikator wurde im Artikel „Hinzufügen des Volumens zum Preis (I)“ implementiert. Damit können wir relativ neue Bewegungen analysieren, indem wir einfach den Startpunkt der Analyse ändern, was wiederum durch Anpassen des Werts des in der folgenden Abbildung angegebenen Objekts geschieht:

Dies ist jedoch eigentlich unpraktisch, da wir an den Hauptzeitrahmen gebunden sind, d.h. wenn wir einen 60-Minuten-Zeitrahmen-Chart haben, können Sie Bewegungen unterhalb dieses Zeitrahmens nicht bewerten. Sie müssen zu einem niedrigeren Zeitrahmen wechseln, um den Analysezeitpunkt anpassen zu können. Aber beim Handel mit Futures-Kontrakten verwenden die meisten Händler tatsächlich niedrigere Zeitrahmen, wie z. B. 5, 10 oder 30 Minuten, sodass es kein Problem mit der Anpassung des Analyseanfangspunkts gibt. Aber wie ich bereits erklärt habe, verlässt der Preis manchmal die Akkumulation, weil Stopps ausgelöst wurden, und eine solche Rückkehr erfolgt normalerweise in weniger als 5 Minuten. In solchen Fällen erscheint auf dem Chart eine Kerze mit einem langen oberen oder unteren Schatten. In solchen Fällen sagt uns Price Action (Preisbewegung), dass das, was passiert ist, eine Marktsondierung war, diese Art von Bewegung ist unten auf den durch Pfeile angezeigten Kerzen zu sehen:

   

Typische Testbewegung des Käufers oder Auslösen eines engen Stopps

 

Typische Testbewegung des Verkäufers oder Stoppauslösung auf Käuferseite

Diese Art von Bewegung tritt häufig auf, und die Analyse des in jeder Preisspanne generierten Volumens ist sehr wichtig, da es ermöglicht zu verstehen, ob der Markt testet oder ob sich der Trend wirklich umkehrt. Aber es ist unmöglich, dies richtig oder ziemlich schnell mit dem zuvor vorgeschlagenen Volumenindikator zu tun.

Wir können jedoch eine kleine Änderung an der Indikatorobjektklasse vornehmen, um eine klarere Vorstellung davon zu bekommen, was vor sich geht. Dies erscheint als Spur eines Handels, der im angegebenen Zeitraum stattgefunden hat.


2. Implementierung

Als erstes müssen wir analysieren, wie viel Tracking-Zeit wir einstellen möchten, ob es 60, 45, 30, 19, 7 oder 1 Minute sein wird. Unabhängig davon empfehlen wir, Werte zu verwenden, die ein Vielfaches sind, damit das Tracking-System wirklich nützlich sein kann. Aus praktischen Gründen werden wir es mit 30 Minuten Tracking implementieren, also werden wir es in der folgenden Codezeile definieren:

#define def_MaxTrailMinutes     30

Aber warum genau 30 Minuten? Tatsächlich wird das Tracking-System jede Minute durchgeführt, aber die maximale Tracking-Zeit beträgt 30 Minuten. Das heißt, wir haben immer ein 30-Minuten-Tracking. Wenn das Tracking beispielsweise auf die 31. Minute umschaltet, wird die erste Handelsminute nicht mehr angezeigt. Wie wird es umgesetzt? Dies verwendet das unten gezeigte Erfassungssystem:

inline void SetMatrix(MqlTick &tick)
{
        int pos;
                                
        if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return;
        pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2;
        pos = (pos >= 0 ? pos : (pos * -1) - 1);
        if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else
        if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume;
        m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell);
        m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell;
        m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal);
        m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos));
        m_Infos.Momentum = macroGetMin(tick.time);
        m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
        if (m_Infos.memMomentum != m_Infos.Momentum)
        {
                for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0;
                m_Infos.memMomentum = m_Infos.Momentum;
        }
        m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume;
}

Die hervorgehobenen Zeilen sind diejenigen, die dem Quellcode der Objektklasse hinzugefügt wurden – sie implementieren das Erfassen des Volumens. Die folgenden Zeilen garantieren, dass das Tracking wie erwartet durchgeführt wird.

m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);

Das Spurenerfassungssystem ist bereit. Jetzt müssen wir eine neue Entscheidung treffen. Denken wir daran, dass die Ablaufverfolgung jede 1 Minute erfasst. Dies kann so dargestellt werden, dass wir das Volumen in jeder Preisklasse innerhalb von 1 Minute sehen können. Solange wir auf diese das Chart erstellen, könnten wir etwas wie unten gezeigt in Betracht ziehen:

 

Hellere Farbtöne stehen für neuere Volumen, was eine gute Idee sein könnte ...

Obwohl es eine gute Idee zu sein scheint, wenn das Volumen niedrig ist oder die Bewegung sehr schnell ist, selbst bei einem beeindruckenden Volumen für den Moment, kann dies tatsächlich nicht sichtbar sein, da die Anpassung an das maximale Volumen, das bisher gefunden wurde, dargestellt wird. Daher möchten wir vielleicht etwas anders zeichnen, um dies zu lösen, also würde es so aussehen:

Jede Farbe repräsentiert einen bestimmten Zeitraum in der Volumenkurve.

Dies kann bei der Analyse sehr schmaler Bänder des Volumens hilfreich sein und gelegentliche Probleme beheben, die im ersten Fall aufgetreten sind. Aber wir hätten immer noch das Anpassungsproblem, das auftritt, wenn das Volumen im Vergleich zur Gesamtvolumen an einer anderen Stelle möglicherweise nicht so stark ist. Außerdem müssen die Farben für jeden Zeitraum sorgfältig ausgewählt werden, damit die Analyse bei sehr aktiven Trades nicht durcheinander gerät.

Daher verwenden wir hier ein einfacheres Modell, das wiederum angepasst werden kann, um die Bewegungen verschiedener Zeiträume zu analysieren. Vergessen wir jedoch nicht die oben genannten Probleme. Dies liegt an Ihnen. Dann wird das Volumen wie folgt dargestellt:

Wir sehen hier reine Streifen. Wenn es passiert, sollten wir sowohl Times & Trade als auch Price Action analysieren, um zu verstehen, was passiert.

Wie auch immer, die einzige Funktion, die geändert werden muss, um die Volumendarstellung zu ändern, ist die folgende Funktion:

void Redraw(void)
{
        uint            x, y, y1, p;
        double  reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0);
        double  desl = Terminal.GetPointPerTick() / 2.0;
        ulong           uValue;
                                
        Erase();
        p = m_WidthMax - 8;
        for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++)
        {
                if (m_InfoAllVaP[c0].nVolTotal == 0) continue;
                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y);
                y1 = y + Terminal.GetHeightBar();
                FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency));
                FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency));
                uValue = 0;
                for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0];
                FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency));
        }
        C_Canvas::Update();
};

Genauer gesagt muss, nur der markierte Code geändert werden. Wir können damit spielen, bis wir das gewünschte Ergebnis erhalten. Außer dem hervorgehobenen Teil muss nichts anderes in der Klasse geändert werden. Nachdem Sie das Programm kompiliert und auf dem Chart ausgeführt haben, sehen Sie etwa Folgendes:



Lösung des Rendering-Problems

Obwohl der Code keine besonderen Probleme hatte, gibt es einen kleinen Fehler beim Ändern der Größe eines Chart: Wenn ein maximiertes Chart auf eine andere Größe und dann wieder auf maximiert wird, gehen einige Objekte verloren und verhalten sich nicht wie erwartet, und werden an falschen Stellen positioniert. Es gibt nicht viele Dinge zu reparieren. Das Problem liegt im folgenden Code – wir haben ihn in früheren Artikeln verwendet.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        ChartRedraw();
}

Es gibt eine sehr einfache Änderung, aber Sie denken vielleicht "Ich sehe nichts - der Code ist korrekt". Auf den ersten Blick sehe ich auch keinen Fehler, und der Code behielt bei einem Laufzeitfehler. Aber als ich einige zusätzliche Funktionen hinzufügte, bemerkte ich das Problem, das genau das war, was ich oben beschrieben habe. Um dieses Problem zu lösen, muss der Code wie folgt geändert werden:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

Es mag albern klingen, aber um zu verstehen, warum, sehen wir sich einfach den gesamten Funktionscode und den hervorgehobenen Teil an. Jetzt, da das System repariert ist, können wir mit dem nächsten Schritt fortfahren.


Zusätzliche Ressourcen hinzufügen

Die Funktion, die wir jetzt hinzufügen werden, ist sehr einfach und viele sehen vielleicht keinen großen Grund, sie zu implementieren, aber die Implementierung wird sehr hilfreich sein, wenn wir mit Aufträgen arbeiten, einschließlich Positionierung, Bewegung oder einfach nur den Indikator Volume at Price beobachten.

Als erstes müssen wir die Klasse ändern, zu der der angepasste Code für die Preislinien gehören wird. Dieser Code kommt aus der Klasse C_OrderView und wandert in die Klasse C_Terminal, erfährt dafür aber auch kleine Änderungen, da er anfängt, mit den Variablen der Klasse selbst zu arbeiten. Unten sehen Sie, wie der neue Code aussehen wird.

double AdjustPrice(const double arg)
{
        double v0, v1;
                                
        if(m_Infos.TypeSymbol == OTHER) return arg;
        v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg));
        v1 = fmod(round(v0), 5.0);
        v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0);
        return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0);
};

Auf diese Weise können wir eine neue EA-Klasse erstellen – es wird die Klasse C_Mouse sein. Dieses Klassenobjekt wird für Mausereignisse verantwortlich sein und die Basis dafür sein, also lassen Sie uns sehen, wie es in dieser Phase der Entwicklung läuft. Aber schauen wir uns zuerst die aktuelle Klassenstruktur unseres Expert Advisors an, die in der folgenden Abbildung dargestellt ist:

Um das nächste System zu implementieren, ist es notwendig, eine neue Struktur einzuführen ...

Lassen Sie uns also angesichts der obigen Struktur den Code der Objektklasse C_Mouse aufschlüsseln, beginnend mit der Deklaration von Variablen, die unten zu sehen ist:

class C_Mouse
{
        private  :
                struct st00
                {
                color   cor01,
                        cor02,
                        cor03;
                string  szNameObjH,
                        szNameObjV,
                        szNameObjT,
                        szNameObjI,
                        szNameObjB;
                }m_Infos;
                struct st01
                {
                        int      X,
                                 Y;
                        datetime dt;
                        double   price;
                        uint     ButtonsStatus;
                }Position;

Da in diesem Stadium der Entwicklung nur wenige benötigt werden, gehen wir zum nächsten Punkt über, der unsere Aufmerksamkeit verdient:

~C_Mouse()
{
// ... Internal code ...
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, true);
}

Dieser Code stellt das Fadenkreuz CHART_CROSSHAIR_TOOL wieder her und deaktiviert die Verwendung von Mausereignissen auf dem Chart, was bedeutet, dass sich MT5 nicht mehr darum kümmern muss, solche Ereignisse an das Chart zu senden, da sie von der Plattform selbst verarbeitet werden.

Wir haben auch zwei sehr häufige Funktionen, die verwendet werden, wenn wir die Maus steuern:

inline void Show(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, m_Infos.cor01);
}
//+------------------------------------------------------------------+
inline void Hide(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, clrNONE);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, 0, 0);
}

Das Interessante ist, dass die Maus nicht wirklich verschwindet, nur die Objekte, die wir erstellen, verschwinden vom Bildschirm, und wenn wir die Maus "einschalten", ist tatsächlich nur die Preislinie sichtbar. Das mag merkwürdig erscheinen, aber es hat seine Verwendung in einigen spezifischen Punkten des EA. Einer dieser Punkte ist das Klassenobjekt C_OrderView in dem im folgenden Code hervorgehobenen Teil:

inline void MoveTo(uint Key)
{
        static double local = 0;
        int w = 0;
        datetime dt;
        bool bEClick, bKeyBuy, bKeySell;
        double take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell) Mouse.Hide(); else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); else local = 0;
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Achten Sie auf die Zeile über dem hervorgehobenen Teil:

Mouse.GetPositionDP(dt, price);

Diese Zeile erfasst den Wert der Mausposition. Unten ist der Code, der diese Werte meldet:

inline void GetPositionDP(datetime &dt, double &price)
{
        dt = Position.dt;
        price = Position.price;
}

Aber das ist noch nicht alles. In einigen Fällen benötigen wir die kartesischen Koordinaten des Charts in Bezug auf die Bildschirmposition. Es gibt eine weitere Funktion, die es ermöglicht, die relevanten Werte zu erhalten. Sie ist unten dargestellt:

inline void GetPositionXY(int &X, int &Y)
{
        X = Position.X;
        Y = Position.Y;
}

Zurück zur Klasse C_OrderView, dort gibt es einen interessanten Punkt, der ebenfalls Aufmerksamkeit verdient:

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(Mouse.GetButtonStatus());
                        break;

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

}

Etwas früher in dieser Funktion ist die Funktion MoveTo zu sehen. Es ist auch Teil der Klasse C_OrderView. Aber noch wichtiger ist die Funktion Mouse.GetButtonsStatus . Diese Funktion gibt den Status von Schaltflächen und Tasten zurück, die Mausereignissen zugeordnet sind.

Diese Funktion Mouse.GetButtonStatus wird unten gezeigt:

inline uint GetButtonStatus(void) const
{
        return Position.ButtonsStatus;
}

Dies ist eine Zeile, die eine Variable zurückgibt, die die seit dem letzten Mausereignis aufgezeichneten Werte enthält. Wir kommen nun zu dem Code, der diesen Wert aufzeichnet. Aber schauen wir uns zuerst den Maus-Initialisierungscode an, denn er sollte dem EA mitteilen, dass wir die Maus initialisieren möchten und dass der EA von nun an verschiedene mausbezogene Dinge handhaben wird. Der dafür verantwortliche Code ist unten dargestellt:

// ... Other things ....

input group "Mouse"
input color     user50 = clrBlack;      //Price line
input color     user51 = clrDarkGreen;  //Positive move
input color     user52 = clrMaroon;     //Negative move
//+------------------------------------------------------------------+

// ... General information ...

//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);

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

Wir müssen also drei Farben definieren, die das System verwenden wird. Diese Farben sollten so gewählt werden, dass die Daten klar und auf dem Chart sichtbar sind. Werfen wir einen Blick auf den Code von Mouse.Init, um es etwas besser zu verstehen. Er ist unten zu sehen.

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        if (m_Infos.szNameObjH != NULL) return;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        m_Infos.szNameObjH = "H" + (string)MathRand();
        m_Infos.szNameObjV = "V" + (string)MathRand();
        m_Infos.szNameObjT = "T" + (string)MathRand();
        m_Infos.szNameObjB = "B" + (string)MathRand();
        m_Infos.szNameObjI = "I" + (string)MathRand();
//---
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjH, OBJ_HLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjV, OBJ_VLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjT, OBJ_TREND, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjB, OBJ_BITMAP, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjI, OBJ_TEXT, 0, 0, 0);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TOOLTIP, "\n");
//---
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_WIDTH, 2);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_BMPFILE, "::" + def_Fillet);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_BACK, false);
        Hide();
        Show();
}

An diesem Code ist nichts Besonderes - wir erstellen nur einige Objekte, die von der Klasse verwendet werden sollen. Der hervorgehobene Teil kann jedoch etwas verwirrend sein, denn wenn wir in der Klasse danach suchen, werden wir keine Stelle finden, an der er deklariert ist. Dies liegt daran, dass es tatsächlich im Code der EA-Datei zusammen mit den Deklarationen anderer Ressourcen deklariert wird. Später werde ich das alles in einer Datei zusammenfassen, aber vorerst bleibt es dabei. Wenn wir uns also den EA-Code ansehen, finden wir die folgenden Zeilen:

#define def_Resource    "Resources\\SubSupport.ex5"
#define def_Fillet      "Resources\\Fillet.bmp"
//+------------------------------------------------------------------+
#resource def_Resource
#resource def_Fillet

Die Zeile zeigt die Ressource, die im Maus-Initialisierungscode hervorgehoben wurde.

Nun, wir haben unseren Höhepunkt innerhalb dieser Klasse erreicht, die durch das folgende Fragment aufgerufen wird:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

Dann ruft die hervorgehobene Zeile im EA-Code den folgenden Code auf:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Position.X = (int)lparam;
                        Position.Y = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);
                        key = (uint) sparam;
                        if ((key & 0x10) == 0x10)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
                                b1 = 1;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 1))
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
                                b1 = 2;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 2))
                        {
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                                ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                        }
                        if (((key & 0x01) != 0x01) && (b1 == 2))
                        {
                                b1 = 0;
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                Hide();
                                Show();
                        }
                        Position.ButtonsStatus = (b1 == 0 ? key : 0);
                        break;
        }
}

Bitte beachten Sie, dass der obige Code nicht der Fill-Implementierungscode ist. Es unterstützt und löst nur die Hauptaufgaben für den EA bis zu diesem Entwicklungsstadium. Um dies zu verstehen, achten Sie auf eine Sache im Maus-Initialisierungscode – er hat die folgende Zeile:

ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);

Diese Zeile verhindert, dass ein Fadenkreuz angezeigt wird, wenn wir die mittleren Maustaste betätigen. Aber warum verhindern wir, dass ein Fadenkreuz erstellt wird? Um dies zu verstehen, werfen wir einen Blick auf das folgende GIF:

Dies ist das WDO-Chart, es bewegt sich von 0,5 auf 0,5. Aber wenn wir versuchen, eine Analyse durchzuführen, sehen wir, dass wir nicht sehr genau sind, während es in einigen Fällen wichtig ist, eine gewisse Genauigkeit zu haben, um die Analyse durchzuführen. Aber das Fadenkreuz in MetaTrader 5 ist für bestimmte Fälle nicht genau genug. In diesem Fall sollten wir auf ein neues System zurückgreifen und damit MetaTrader 5 dazu zwingen, kein Fadenkreuz mehr zu erstellen, wenn der EA läuft. Stattdessen erstellen wir unser eigenes Fadenkreuz, um die Analyse durchzuführen. Dies ermöglicht es uns, Daten und Werte hinzuzufügen, die für uns relevanter sind, und sie so darzustellen, wie wir es für am geeignetsten halten. Dies ist in der folgenden Abbildung zu sehen, die das Ergebnis der Verwendung des Datenmodellierungssystems bei laufendem EA zeigt.


Wie wir sehen, entsprechen die angegebenen Werte den exakten Bewegungswerten. Außerdem haben wir eine visuelle Anzeige: Wenn der Wert positiv ist, wird die Anzeige grün, und wenn er negativ ist, wird die Anzeige rot. Eine Spur wird erstellt und auch Start- und Endpunkte werden sichtbar. Aber wie ich bereits erwähnt habe, ist das System noch nicht vollständig. Sie können immer noch Verbesserungen daran vornehmen, wenn Sie möchten und Sie es brauchen. Bis eine neue Version der C_Mouse-Klasse herauskommt, können Sie diese Version verbessern und mehr Daten haben, die Sie benötigen. Aber dazu müssen Sie verstehen, wie alles funktioniert, also schauen wir uns den Nachrichtencode der C_Mouse-Klasse genauer an. 


Den Code von DispathMessage der Klasse C_Mouse verstehen

Der Code beginnt mit dem Erfassen und Anpassen der Werte der Mauspositionsvariablen. Dies geschieht im folgenden Code:

Position.X = (int)lparam;
Position.Y = (int)dparam;
ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);

Positionswerte werden von der MetaTrader 5-Plattform gemeldet, aber die Werte stammen tatsächlich vom Betriebssystem und stellen Bildschirmkoordinaten dar, d.h. X und Y. Aber wir müssen sie in Chartkoordinaten umwandeln und dafür verwenden wir die Funktion ChartXYToTimePrice, verfügbar in MQL5, das unser Leben stark vereinfacht.

Sobald dies erledigt ist, verschieben wir die Preis- und Zeitlinien.

ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);

Aber die Zeitlinie ist für uns zunächst unsichtbar, sodass wir sie zunächst nicht auf dem Chart sehen können. Danach erfassen wir den Zustand der Maus:

key = (uint) sparam;

So weit, so gut. Lassen Sie uns nun Folgendes tun: Wir prüfen, ob die mittlere Taste gedrückt ist. Ist dies der Fall, wird die Zeitlinie im Chart sichtbar. Es ist im folgenden Code implementiert:

if ((key & 0x10) == 0x10)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
        b1 = 1;
}

Zu diesem Zweck verwenden wir die statische Variable, um dieses Ereignis zu speichern, sodass von nun an kein anderes Ereignis vom EA akzeptiert und verarbeitet wird. Es wird sich mit der Studie befassen, die wir auf dem Chart durchführen möchten. Aber tatsächlich startet die Studie erst, wenn wir die linke Maustaste drücken, d.h. ich verwende den gleichen Arbeitsmodus, der allen Nutzern der MetaTrader 5-Plattform bereits für die Recherche bekannt ist. Dies ist der geeignetste Weg, da der Nutzer, wenn er eine neue Methode zum Ausführen von Recherchelinien lernen muss, das System aufgeben kann. Der EA wartet dann auf diesen Linksklick, was durch den folgenden Code erledigt wird.

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

Wenn ein Klick auftritt, wird das Chartbewegungssystem gesperrt. Dann präsentieren wir eine Trendlinie, die die Analysepunkte anzeigt. Dann wechselt das System zum nächsten Schritt, was an einem neuen Wert in b1 ersichtlich ist. Jetzt ist es eigentlich der Teil, in dem Sie weitere Informationen hinzufügen oder das eingeben können, was Sie für am relevantesten halten. Hier demonstriere ich nur das System, aber fühlen Sie sich frei, alles zu setzen, was Sie wollen. Dies sollte wie unten gezeigt durchgeführt werden:

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Achten Sie auf die hervorgehobene Zeile, denn dort wird der Wert, den Sie auf dem Chart sehen, angezeigt und berechnet. Dort können Sie weitere nützliche Informationen hinzufügen. Die Bedienung dieses Teils bewirkt, dass die Daten berechnet und präsentiert werden, während die linke Taste gedrückt wird. Dies ist also dasselbe Verhalten wie die Standardeinstellung in MetaTrader 5, aber die Werte werden gemäß Ihren Wünschen und Bedürfnissen angepasst und modelliert.

Jetzt müssen wir einen weiteren Test durchführen, der unten gezeigt wird.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}

Nach dem Loslassen der linken Maustaste wird der Chart freigegeben und kann gezogen werden, und alle Elemente, die zum Erstellen der Analyse verwendet wurden, werden ausgeblendet, nur die Preislinie wird wieder sichtbar. Und schließlich haben wir den letzten Codeteil, der unten gezeigt wird:

Position.ButtonsStatus = (b1 == 0 ? key : 0);

Wenn keine Studie aufgerufen wird, wird der Status der Maustaste gespeichert und kann an anderer Stelle im EA verwendet werden. Wenn jedoch eine Studie aufgerufen wurde, wird NULL als Status verwendet, sodass keine Aufträge erstellt oder Positionen geändert werden können.

Im folgenden Video können Sie sehen, wie diese Spur tatsächlich funktioniert und wie er das Volumen auf dem Bildschirm anpasst. Der Indikator hilft sehr und es wird großartig sein, wenn Sie lernen, wie man ihn richtig verwendet. Zusammen mit Times & Trade bilden sie ein Double-Noise-Analysetool, das zu den fortschrittlichsten Handelsmethoden auf dem Markt gehört.




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

Beigefügte Dateien |
EA_-_Mouse.zip (5986.31 KB)
DoEasy. Steuerung (Teil 6): Paneel-Steuerung, automatische Größenanpassung des Containers an den inneren Inhalt DoEasy. Steuerung (Teil 6): Paneel-Steuerung, automatische Größenanpassung des Containers an den inneren Inhalt
In diesem Artikel werde ich meine Arbeit an dem WinForms-Objekt Panel fortsetzen und seine automatische Größenanpassung an die allgemeine Größe der Dock-Objekte, die sich innerhalb des Paneels befinden, implementieren. Außerdem werde ich die neuen Eigenschaften zum Objekt der Symbolbibliothek hinzufügen.
Datenwissenschaft und maschinelles Lernen (Teil 05): Entscheidungsbäume Datenwissenschaft und maschinelles Lernen (Teil 05): Entscheidungsbäume
Entscheidungsbäume imitieren die Art und Weise, wie Menschen denken, um Daten zu klassifizieren. Schauen wir mal, wie man so einen Baum erstellt und ihn zur Klassifizierung und Vorhersage einiger Daten verwenden kann. Das Hauptziel des Entscheidungsbaum-Algorithmus ist es, die Daten mit Fremdanteilen und die reinen oder knotennahen Daten abzutrennen.
Techniken des MQL5-Assistenten, die Sie kennen sollten (Teil 02): Kohonen-Karten Techniken des MQL5-Assistenten, die Sie kennen sollten (Teil 02): Kohonen-Karten
Der Händler von heute ist ein Philomath, der fast immer (entweder bewusst oder unbewusst...) nach neuen Ideen sucht, sie ausprobiert, sich entscheidet, sie zu modifizieren oder zu verwerfen; ein explorativer Prozess, der einiges an Sorgfalt kosten sollte. Dies legt eindeutig einen hohen Stellenwert auf die Zeit des Händlers und die Notwendigkeit, Fehler zu vermeiden. Diese Artikelserie wird vorschlagen, dass der MQL5-Assistent eine Hauptstütze für Händler sein sollte. Warum? Denn der Händler spart nicht nur Zeit, indem er seine neuen Ideen mit dem MQL5-Assistenten zusammenstellt, und reduziert Fehler durch doppelte Codierung erheblich. Er ist letztendlich so eingestellt, dass er seine Energie auf die wenigen kritischen Bereiche seiner Handelsphilosophie konzentriert.
Neuronale Netze leicht gemacht (Teil 14): Datenclustering Neuronale Netze leicht gemacht (Teil 14): Datenclustering
Es ist mehr als ein Jahr her, dass ich meinen letzten Artikel veröffentlicht habe. Das ist eine ganze Menge Zeit, um Ideen zu überarbeiten und neue Ansätze zu entwickeln. In dem neuen Artikel möchte ich von der bisher verwendeten Methode des überwachten Lernens abweichen. Diesmal werden wir uns mit Algorithmen des unüberwachten Lernens beschäftigen. Wir werden insbesondere einen der Clustering-Algorithmen - K-Means - betrachten.