
Die Handelsgeschäfte direkt auf dem Chart beurteilen, statt in der Handelshistorie unterzugehen
Inhalt
- Einführung
- Die Klasse Deal
- Die Klasse Position
- Die Klasse für die Verwaltung historischer Positionen
- Test
- Schlussfolgerung
Einführung
Moderne Händler stehen oft vor dem Problem, große Datenmengen im Zusammenhang mit Handelsgeschäften zu analysieren. Im MetaTrader 5-Client-Terminal kann die Anzeige der Handelshistorie ineffektiv und verwirrend werden, da das Chart mit Angaben von offenen und geschlossenen Positionen überladen ist. Dies gilt insbesondere für Händler, die mit einer großen Anzahl von Handelsaktivitäten (deals) arbeiten, bei denen sich das Chart schnell füllt, was es fast unmöglich macht, die Handelsaktivitäten zu analysieren und fundierte Entscheidungen zu treffen.
Ziel dieses Artikels ist es, eine Lösung anzubieten, die die Wahrnehmung und Analyse der Handelsgeschichte erleichtert. Wir werden einen Mechanismus für die schrittweise Anzeige geschlossener Positionen und verbesserte Handelsgeschäftsinformationen entwickeln. Dies ermöglicht es den Händlern, sich auf jedes einzelne Handelsgeschäft (deal) zu konzentrieren und ein tieferes Verständnis ihrer Handelsoperationen zu gewinnen.
Wir werden die Funktionen implementieren, die uns dies ermöglichen:
- die geschlossenen Positionen nacheinander mit den Navigationstasten anzuzeigen,
- die Tooltips zu verbessern, indem detailliertere Informationen über jedes Handelsgeschäft bereitgestellt werden,
- das Chart wird so zentriert, dass die wichtigsten Elemente immer sichtbar sind.
Darüber hinaus wird die Datenstruktur, die für die Implementierung dieser Funktionalität erforderlich ist, im Detail untersucht, und es werden die Grundprinzipien der Verbuchung von Handelsgeschäften und Positionen im MetaTrader 5 vorgestellt. Infolgedessen werden die Händler in der Lage sein, ihren Handelsverlauf effizienter zu verwalten und auf der Grundlage der erhaltenen Informationen fundierte Entscheidungen zu treffen.
Im Client-Terminal von MetaTrader 5 können wir die Handelshistorie anzeigen, indem wir die Option „Handelshistorie anzeigen“ auf der Registerkarte „Zeigen“ der Chart-Einstellungen (Taste F8) aktivieren:
Wenn Sie diese Option ankreuzen, kann das Terminal die gesamte Handelsgeschichte auf dem Chart in Form von Angaben zu geöffneten und geschlossenen Positionen anzeigen, die durch Linien verbunden sind. In diesem Fall werden alle Handelsgeschäfte, die mit dem Symbol getätigt wurden, vollständig angezeigt:
Wenn es viele Angebote gibt, wird das Chart mit Angaben überfrachtet, sodass es schwierig ist, etwas zu erkennen. Die Tooltips, die erscheinen, wenn man den Mauszeiger über eine Handelsgeschäftsbezeichnung oder eine Verbindungslinie zwischen Handelsgeschäften bewegt, sind nicht besonders informativ:
Die hier vorgestellte Lösung zeigt beim Start nur die zuletzt geschlossene Position an. Die restlichen Positionen werden durch Drücken der Cursortasten angesteuert:
- die Taste nach oben wird die erste geschlossene Position angezeigt;
- die Abwärtstaste zeigt die letzte geschlossene Position an;
- die linke Taste zeigt die vorherige geschlossene Position an;
- Mit der rechten Pfeiltaste wird die nächste geschlossene Position angezeigt.
Die Tooltips von Handelsgeschäften und Verbindungslinien sollen aussagekräftigere Informationen anzeigen. Wenn Sie die Umschalttaste gedrückt halten, werden außerdem Informationen über die aktuell gewählte geschlossene Position auf dem Chart angezeigt:
Bei der Anzeige jeder weiteren geschlossenen Position wird das Chart so zentriert, dass sich die Angaben der geöffneten/geschlossenen Positionen und die Verbindungslinie zwischen ihnen in der Mitte befinden.
Falls die Angaben zu Eröffnen und Schließen nicht in ein Chartfenster passen, wird das Chart so zentriert, dass sich das Eröffnungsgeschäft in der zweiten linken sichtbaren Leiste des Charts befindet.
Schauen wir uns die Grundprinzipien der Verbuchung der Handelsgeschäfte und Positionen an, sowie die Struktur der Klassen, die wir hier erstellen werden.
Wie wird eine Position gebildet? Zunächst wird eine Handelsanfrage an den Server gesendet - ein Auftrag (Handelsauftrag). Der Auftrag kann entweder storniert oder ausgeführt werden. Wenn ein Auftrag ausgeführt wird, erhalten wir ein Handelsgeschäft, d. h. die Tatsache, dass der Handelsauftrag ausgeführt wurde. Wenn zum Zeitpunkt der Ausführung des Auftrags noch keine Positionen vorhanden waren, wird durch das Handelsgeschäft eine Position in einer bestimmten Richtung geschaffen. Wenn eine Position vorhanden war, gibt es je nach Art der Positionsverbuchung mehrere Möglichkeiten. Im Falle der Netting-Abrechnung ist nur eine Position auf einem Symbol möglich. Dementsprechend ändert das Handelsgeschäft, das durch den Handelsauftrag zustande kommt, eine bestehende Position. Das kann sein:
- geschlossen — wenn ein Verkauf für eine Kaufposition mit einem Volumen, das dem Volumen der Position entspricht, durchgeführt wurde:
Position 1.0 Kauf - Handelsgeschäft 1.0 Verkauf = Volumen 0 (Schließen einer Position); - teilweise geschlossen — wenn ein Verkauf für eine Kaufposition mit einem Volumen durchgeführt wurde, das unter dem Positionsvolumen liegt:
Position 1,0 Kauf - Handelsgeschäft 0,5 (Verkauf) = Volumen 0,5 (teilweises Schließen der Position); - zusätzliches Volumen — wenn ein Kauf für eine Kaufposition getätigt wurde:
Position 1.0 Buy + Handelsgeschäft 0.5 (Kauf) = Volumen 1.5 (Erhöhung des Positionsvolumens); - Reversed — wenn ein Verkauf für eine Kaufposition mit einem Volumen größer als das Positionsvolumen durchgeführt wurde:
Position 1.0 Kaufen - Handelsgeschäft 1.5 (Verkauf) = Verkaufsposition 0.5 (Positionsumkehr);
Im Falle eines Hedging-Kontos kann jedes Handelsgeschäft entweder eine bestehende Position ändern oder eine neue Position erzeugen. Um eine bestehende Position zu ändern (Schließung oder teilweise Schließung), müssen Sie die ID der Position, mit der Sie handeln möchten, im Handelsauftrag angeben. Die Positions-ID ist eine eindeutige Nummer, die jeder neuen Position zugewiesen wird und während der gesamten Lebensdauer der Position unverändert bleibt:
- Eine neue Position eröffnen — einen Kauf oder Verkauf durchführen, während eine andere Position noch besteht:
Position 1.0 Buy + Handelsgeschäft 1.0 Verkauf = 2 unabhängige Positionen 1.0 Kauf und 1.0 Verkauf (Eröffnung einer Position), oder
Position 1.0 Buy + Handelsgeschäft 1.5 Kauf = 2 unabhängige Positionen 1.0 Kauf und 1.5 Kauf (Eröffnung einer Position), usw.; - Schließen einer bestehenden Position — wenn ein Verkauf mit der ID einer bereits bestehenden Kaufposition durchgeführt wird:
Position 1.0 Kauf mit ID 123 - Handelsgeschäft 1.0 Verkauf mit ID 123 = Schließen von 1.0 Kauf mit ID 123 (Schließen einer Position mit der ID 123); - Teilweise Schließung einer bestehenden Position — wenn ein Verkauf mit der ID einer bereits bestehenden Kaufposition mit einem geringeren Volumen als dem der bestehenden Position mit der angegebenen ID durchgeführt wird:
Position 1.0 Kauf mit ID 123 - Handelsgeschäft 0.5 Verkauf mit ID 123 = Volumen 0.5 Kauf mit ID 123 (Teilschließung einer Position mit ID 123);
Erfahren Sie mehr über Aufträge, Handelsgeschäfte und Positionen im Artikel „Orders, Positions und Abschlüsse in MetaTrader 5“.
Jeder an den Handelsserver gesendete Auftrag bleibt bis zur Ausführung in der Liste der aktiven Aufträge des Terminals. Wenn er ausgelöst wird, erscheint ein Handelsgeschäft, das eine neue Position erzeugt oder eine bestehende ändert. In dem Moment, in dem der Auftrag ausgeführt wird, wird er in die Liste der historischen Aufträge aufgenommen, und es wird ein Handelsgeschäft angelegt, das ebenfalls in die Liste der historischen Handelsgeschäfte aufgenommen wird. Wenn ein Handelsgeschäft eine Position anlegt, wird eine neue Position erstellt und in die Liste der aktiven Positionen aufgenommen.
Es gibt keine Liste der historischen (geschlossenen) Positionen im Terminal. Es gibt auch keine Liste aktiver Handelsgeschäfte im Terminal - sie werden sofort als historisch betrachtet und befinden sich dann in der entsprechenden Liste.
Die Erklärung ist einfach: Ein Auftrag ist eine Anweisung zur Durchführung von Handelsgeschäften auf einem Konto. Er existiert, bis er erfüllt ist. Er befindet sich in der Liste der bestehenden Aufträge. Sobald ein Auftrag ausgeführt ist, hört er (im Idealfall) auf zu existieren und wird in die Liste der ausgeführten Handelsaufträge aufgenommen. Auf diese Weise wird ein Handelsgeschäft erstellt. Ein Handelsgeschäft ist die Realisierung der Ausführung eines Handelsauftrags. Dies ist ein einmaliges Ereignis: Ein Auftrag wird ausgeführt - wir haben ein Handelsgeschäft (deal). Das ist alles. Ein Handelsgeschäft wird in die Liste der abgeschlossenen Kontoereignisse aufgenommen.
Ein Handelsgeschäft schafft eine Position, die so lange besteht, bis es abgeschlossen ist. Die Position befindet sich immer in der Liste der aktiven Positionen. Sobald sie geschlossen wird, wird sie aus der Liste entfernt.
Im Terminal gibt es keine Liste der geschlossenen Positionen. Es gibt jedoch eine Liste der abgeschlossenen Handelsgeschäfte (deals). Um eine Liste der geschlossenen Positionen zu erstellen, muss man sie daher aus Handelsgeschäften zusammenstellen, die zu einer zuvor geschlossenen Position gehören. Die Liste der „Deals“ steht uns jederzeit zur Verfügung. Die Eigenschaften der einzelnen Handelsgeschäfte enthalten eine ID der entsprechenden Position. So haben wir Zugriff auf alles, was notwendig ist, um den Lebenslauf jeder einzelnen Position, die zuvor auf dem Konto existierte, wiederherzustellen - die Positions-ID und die Liste ihrer Handelsgeschäfte.
Auf dieser Grundlage ergibt sich der folgende Algorithmus für die Wiederherstellung von zuvor geschlossenen Positionen aus der bestehenden historischen Liste von Handelsgeschäften:
- Erhalt einer Liste aller Angebote für das gewünschte Symbol.
- Durchgehen der Liste und Abrufen des jeweils nächsten Handelsgeschäfts aus der Liste.
- Überprüfen der Positions-ID in den Eigenschaften des Handelsgeschäfts.
- Wenn es keine Position mit einer solchen ID in unserer eigenen Liste historischer Positionen gibt, wird ein neues Objekt für historische Positionen erstellt.
- Abruf des Objekts für die historische Positionen unter Verwendung der zuvor erhaltenen ID.
- Hinzufügen des aktuellen Handelsgeschäfts zur Liste der historischen Positionsgeschäfte.
Nach Abschluss der Schleife durch die Liste der Endgeschäfte haben wir eine vollständige Liste aller geschlossenen Positionen des Kontos nach Symbolen, die Listen der zu jeder Position gehörenden Handelsgeschäfte enthält.
Nun wird jede historische Position eine Liste der Handelsgeschäfte (deals) enthalten, die an der Änderung dieser Position beteiligt waren, und wir werden in der Lage sein, vollständige Informationen über alle Änderungen einer geschlossenen Position während ihres Bestehens zu erhalten.
Um die oben beschriebene Logik zu implementieren, müssen wir drei Klassen erstellen:
- Die Klasse Deal. Die Klasse enthält die Liste aller Eigenschaften eines historischen Handelsgeschäfts, Methoden für den Zugriff auf die Eigenschaften - Setzen und Abrufen - sowie zusätzliche Methoden für die Anzeige von Handelsgeschäftsdaten.
- Die Klasse Position. Die Klasse enthält die Liste aller Handelsgeschäfte, Methoden für den Zugriff auf diese und zusätzliche Methoden für die Anzeige von Informationen über die Position und ihre Handelsgeschäfte.
- Die Klasse zur Verwaltung historischer Positionen. Die Klasse wird die Liste aller historischen Positionen enthalten, sie erstellen und aktualisieren sowie Methoden für den Zugriff auf die Eigenschaften von Positionen und ihren Handelsgeschäften und zusätzliche Methoden für die Anzeige von Informationen über Positionen und ihre Handelsgeschäfte.
Fangen wir an.
Die Klasse Deal
Um die Eigenschaften eines Handelsgeschäfts abzurufen und zu speichern, müssen wir die Historie der Aufträge und Handelsgeschäfte auswählen (HistorySelect()), die Historie der Handelsgeschäfte in einer Schleife durchgehen und ein Handelsgeschäftsticket über den Schleifenindex (HistoryDealGetTicket()) aus der Liste der historischen Handelsgeschäfte abrufen. In diesem Fall wird das Handelsgeschäft ausgewählt, um seine Eigenschaften mit den Funktionen HistoryDealGetInteger(), HistoryDealGetDouble() und HistoryDealGetString() abzurufen.
In der Klasse der Handelsgeschäfte wird davon ausgegangen, dass das Handelsgeschäft bereits ausgewählt wurde und seine Eigenschaften sofort abgerufen werden können. Zusätzlich zur Aufzeichnung und Abfrage der Handelsgeschäftseigenschaften ermöglicht die Klasse die Erstellung einer grafischen Beschriftung in einem Chart, das das Handelsgeschäft anzeigt. Die Angabe wird auf dem Hintergrund gezeichnet, sodass wir die gewünschten Anbgaben für jede Art von Handelsgeschäft zeichnen können, anstatt die Angaben aus einem vordefinierten Satz von Schriftsymbolen zu verwenden. Bei der Erstellung eines Objekts für jedes Handelsgeschäft erhalten wir eine Tick-Historie der Preise zu dem Zeitpunkt, an dem das Handelsgeschäft zustande kam, sodass wir den Spread zu dem Zeitpunkt, an dem das Handelsgeschäft durchgeführt wurde, berechnen und in der Beschreibung der Handelsgeschäftsparameter anzeigen können.
Da jede Position viele Abschlüsse haben kann, die an ihrer Änderung beteiligt waren, befinden sich alle Abschlüsse letztendlich in der Klasse des dynamischen Arrays von Zeigern auf die Instanzen der Klasse CObject und ihrer Nachkommen von CArrayObj.
Dementsprechend wird die Klasse „Deal“ von der Basisobjektklasse Standard Library CObject abgeleitet.
Ein Handelsgeschäft hat eine Reihe von „Integer“-, „Real“- und „String“-Eigenschaften. Jedes Handelsgeschäftsobjekt wird in der Liste der Objekte in der historischen Positionsklasse aufgeführt. Um die Suche nach dem gewünschten Handelsgeschäft zu implementieren, ist es notwendig, eine Enumeration der Parameter zu schreiben. Anhand der Werte dieser Enumeration ist es möglich, in der Liste nach Handelsgeschäften zu suchen und die Liste nach diesen Werten zu sortieren. Wir brauchen die Suche nur nach den Eigenschaften - Ticket und Zeit in Millisekunden. Mit dem Ticket können wir feststellen, ob ein solches Handelsgeschäft bereits in der Liste vorhanden ist oder nicht. Die Zeit in Millisekunden ermöglicht es uns, die Handelsgeschäfte in streng chronologischer Reihenfolge zu ordnen. Da aber Klassen eine Erweiterung implizieren, enthält die Liste der Deal-Eigenschaften alle Eigenschaften des Deals.
Um die Suche und Sortierung in CArrayObj-Listen der Standardbibliothek zu organisieren, wird die virtuelle Methode Compare(), die den Wert 0 zurückgibt, in der Klasse CObject zurückgegeben:
//--- method of comparing the objects virtual int Compare(const CObject *node,const int mode=0) const { return(0); }
Mit anderen Worten, diese Methode liefert immer Gleichheit. In allen Objekten der von CObject abgeleiteten Klassen sollte diese Methode umdefiniert werden, sodass beim Vergleich einer Eigenschaft (die im Parameter mode angegeben ist) von zwei verglichenen Objekten die Methode zurückkehrt:
- -1 — wenn der Wert der aktuellen Objekteigenschaft kleiner ist als der Wert des verglichenen Objekts;
- 1 — wenn der Wert der aktuellen Objekteigenschaft größer ist als der des Vergleichsobjekts;
- 0 — wenn die Werte der angegebenen Eigenschaft für beide verglichenen Objekte gleich sind.
Ausgehend von den obigen Ausführungen müssen wir also für jede der heute erstellten Klassen zusätzlich Enumerationen der Eigenschaften von Klassenobjekten und Methoden zu deren Vergleich erstellen.
Im Ordner \MQL5\Include\ erstellen wir den Ordner PositionsViewer\, der die neue Datei Deal.mqh der Klasse CDeal enthält:
Die Klasse CObject der Standardbibliothek sollte die Basisklasse sein:
Als Ergebnis erhalten wir die folgende Vorlage für die Klassendatei des Handelsgeschäftsobjekts:
//+------------------------------------------------------------------+ //| Deal.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" class CDeal : public CObject { private: public: CDeal(); ~CDeal(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CDeal::CDeal() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CDeal::~CDeal() { } //+------------------------------------------------------------------+
Wir binden die Dateien der Standardobjektklassen der CObject-Bibliothek und der CCanvas-Klasse in die erstellte Datei der Deal-Objektklasse ein und schreiben die Enumeration für die Sortierung nach Deal-Objekteigenschaften:
//+------------------------------------------------------------------+ //| Deal.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Object.mqh> #include <Canvas\Canvas.mqh> enum ENUM_DEAL_SORT_MODE { SORT_MODE_DEAL_TICKET = 0, // Mode of comparing/sorting by a deal ticket SORT_MODE_DEAL_ORDER, // Mode of comparing/sorting by the order a deal is based on SORT_MODE_DEAL_TIME, // Mode of comparing/sorting by a deal time SORT_MODE_DEAL_TIME_MSC, // Mode of comparing/sorting by a deal time in milliseconds SORT_MODE_DEAL_TYPE, // Mode of comparing/sorting by a deal type SORT_MODE_DEAL_ENTRY, // Mode of comparing/sorting by a deal direction SORT_MODE_DEAL_MAGIC, // Mode of comparing/sorting by a deal magic number SORT_MODE_DEAL_REASON, // Mode of comparing/sorting by a deal reason or source SORT_MODE_DEAL_POSITION_ID, // Mode of comparing/sorting by a position ID SORT_MODE_DEAL_VOLUME, // Mode of comparing/sorting by a deal volume SORT_MODE_DEAL_PRICE, // Mode of comparing/sorting by a deal price SORT_MODE_DEAL_COMMISSION, // Mode of comparing/sorting by commission SORT_MODE_DEAL_SWAP, // Mode of comparing/sorting by accumulated swap on close SORT_MODE_DEAL_PROFIT, // Mode of comparing/sorting by a deal financial result SORT_MODE_DEAL_FEE, // Mode of comparing/sorting by a deal fee SORT_MODE_DEAL_SL, // Mode of comparing/sorting by Stop Loss level SORT_MODE_DEAL_TP, // Mode of comparing/sorting by Take Profit level SORT_MODE_DEAL_SYMBOL, // Mode of comparing/sorting by a name of a traded symbol SORT_MODE_DEAL_COMMENT, // Mode of comparing/sorting by a deal comment SORT_MODE_DEAL_EXTERNAL_ID, // Mode of comparing/sorting by a deal ID in an external trading system }; //+------------------------------------------------------------------+ //| Deal class | //+------------------------------------------------------------------+ class CDeal : public CObject { }
Deklaration aller Variablen und Methoden, die für das Funktionieren der Klasse erforderlich sind, in den Abschnitten „private“, „protected“ und „public“:
//+------------------------------------------------------------------+ //| Deal class | //+------------------------------------------------------------------+ class CDeal : public CObject { private: MqlTick m_tick; // Deal tick structure //--- CCanvas object CCanvas m_canvas; // Canvas long m_chart_id; // Chart ID int m_width; // Canvas width int m_height; // Canvas height string m_name; // Graphical object name //--- Create a label object on the chart bool CreateLabelObj(void); //--- Draw (1) a mask, (2) Buy arrow on the canvas void DrawArrowMaskBuy(const int shift_x, const int shift_y); void DrawArrowBuy(const int shift_x, const int shift_y); //--- Draw (1) a mask, (2) Sell arrow on the canvas void DrawArrowMaskSell(const int shift_x, const int shift_y); void DrawArrowSell(const int shift_x, const int shift_y); //--- Draw the label appearance void DrawLabelView(void); //--- Get a (1) deal tick and (2) a spread of the deal minute bar bool GetDealTick(const int amount=20); int GetSpreadM1(void); //--- Return time with milliseconds string TimeMscToString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const; protected: //--- Integer properties long m_ticket; // Deal ticket. Unique number assigned to each deal long m_order; // Deal order number datetime m_time; // Deal execution time long m_time_msc; // Deal execution time in milliseconds since 01.01.1970 ENUM_DEAL_TYPE m_type; // Deal type ENUM_DEAL_ENTRY m_entry; // Deal entry - entry in, entry out, reverse long m_magic; // Magic number for a deal (see ORDER_MAGIC) ENUM_DEAL_REASON m_reason; // Deal execution reason or source long m_position_id; // The ID of the position opened, modified or closed by the deal //--- Real properties double m_volume; // Deal volume double m_price; // Deal price double m_commission; // Deal commission double m_swap; // Accumulated swap when closing double m_profit; // Deal financial result double m_fee; // Fee for making a deal charged immediately after performing a deal double m_sl; // Stop Loss level double m_tp; // Take Profit level //--- String properties string m_symbol; // Name of the symbol for which the deal is executed string m_comment; // Deal comment string m_external_id; // Deal ID in an external trading system (on the exchange) //--- Additional properties int m_digits; // Symbol digits double m_point; // Symbol point double m_bid; // Bid when performing a deal double m_ask; // Ask when performing a deal int m_spread; // Spread when performing a deal color m_color_arrow; // Deal label color //--- Draws an arrow corresponding to the deal type. It can be redefined in the inherited classes virtual void DrawArrow(void); public: //--- Set the properties //--- Integer properties void SetTicket(const long ticket) { this.m_ticket=ticket; } // Ticket void SetOrder(const long order) { this.m_order=order; } // Order void SetTime(const datetime time) { this.m_time=time; } // Time void SetTimeMsc(const long value) { this.m_time_msc=value; } // Time in milliseconds void SetTypeDeal(const ENUM_DEAL_TYPE type) { this.m_type=type; } // Type void SetEntry(const ENUM_DEAL_ENTRY entry) { this.m_entry=entry; } // Direction void SetMagic(const long magic) { this.m_magic=magic; } // Magic number void SetReason(const ENUM_DEAL_REASON reason) { this.m_reason=reason; } // Deal execution reason or source void SetPositionID(const long id) { this.m_position_id=id; } // Position ID //--- Real properties void SetVolume(const double volume) { this.m_volume=volume; } // Volume void SetPrice(const double price) { this.m_price=price; } // Price void SetCommission(const double value) { this.m_commission=value; } // Commission void SetSwap(const double value) { this.m_swap=value; } // Accumulated swap when closing void SetProfit(const double value) { this.m_profit=value; } // Financial result void SetFee(const double value) { this.m_fee=value; } // Deal fee void SetSL(const double value) { this.m_sl=value; } // Stop Loss level void SetTP(const double value) { this.m_tp=value; } // Take Profit level //--- String properties void SetSymbol(const string symbol) { this.m_symbol=symbol; } // Symbol name void SetComment(const string comment) { this.m_comment=comment; } // Comment void SetExternalID(const string ext_id) { this.m_external_id=ext_id; } // Deal ID in an external trading system //--- Get the properties //--- Integer properties long Ticket(void) const { return(this.m_ticket); } // Ticket long Order(void) const { return(this.m_order); } // Order datetime Time(void) const { return(this.m_time); } // Time long TimeMsc(void) const { return(this.m_time_msc); } // Time in milliseconds ENUM_DEAL_TYPE TypeDeal(void) const { return(this.m_type); } // Type ENUM_DEAL_ENTRY Entry(void) const { return(this.m_entry); } // Direction long Magic(void) const { return(this.m_magic); } // Magic number ENUM_DEAL_REASON Reason(void) const { return(this.m_reason); } // Deal execution reason or source long PositionID(void) const { return(this.m_position_id); } // Position ID //--- Real properties double Volume(void) const { return(this.m_volume); } // Volume double Price(void) const { return(this.m_price); } // Price double Commission(void) const { return(this.m_commission); } // Commission double Swap(void) const { return(this.m_swap); } // Accumulated swap when closing double Profit(void) const { return(this.m_profit); } // Financial result double Fee(void) const { return(this.m_fee); } // Deal fee double SL(void) const { return(this.m_sl); } // Stop Loss level double TP(void) const { return(this.m_tp); } // Take Profit level double Bid(void) const { return(this.m_bid); } // Bid when performing a deal double Ask(void) const { return(this.m_ask); } // Ask when performing a deal int Spread(void) const { return(this.m_spread); } // Spread when performing a deal //--- String properties string Symbol(void) const { return(this.m_symbol); } // Symbol name string Comment(void) const { return(this.m_comment); } // Comment string ExternalID(void) const { return(this.m_external_id); } // Deal ID in an external trading system //--- Set the color of the deal label void SetColorArrow(const color clr); //--- (1) Hide, (2) display the deal label on a chart void HideArrow(const bool chart_redraw=false); void ShowArrow(const bool chart_redraw=false); //--- Return the description of a (1) deal type, (2) position change method and (3) deal reason string TypeDescription(void) const; string EntryDescription(void) const; string ReasonDescription(void) const; //--- Return (1) a short description and (2) a tooltip text of a deal string Description(void); virtual string Tooltip(void); //--- Print deal properties in the journal void Print(void); //--- Compare two objects by the property specified in 'mode' virtual int Compare(const CObject *node, const int mode=0) const; //--- Constructors/destructor CDeal(void) { this.m_ticket=0; } CDeal(const ulong ticket); ~CDeal(); };
Betrachten wir die deklarierten Methoden im Detail.
Der parametrische Konstruktor:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CDeal::CDeal(const ulong ticket) { //--- Store the properties //--- Integer properties this.m_ticket = (long)ticket; // Deal ticket this.m_order = ::HistoryDealGetInteger(ticket, DEAL_ORDER); // Order this.m_time = (datetime)::HistoryDealGetInteger(ticket, DEAL_TIME); // Deal execution time this.m_time_msc = ::HistoryDealGetInteger(ticket, DEAL_TIME_MSC); // Deal execution time in milliseconds this.m_type = (ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE); // Type this.m_entry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(ticket, DEAL_ENTRY); // Direction this.m_magic = ::HistoryDealGetInteger(ticket, DEAL_MAGIC); // Magic number this.m_reason = (ENUM_DEAL_REASON)::HistoryDealGetInteger(ticket, DEAL_REASON); // Deal execution reason or source this.m_position_id= ::HistoryDealGetInteger(ticket, DEAL_POSITION_ID); // Position ID //--- Real properties this.m_volume = ::HistoryDealGetDouble(ticket, DEAL_VOLUME); // Volume this.m_price = ::HistoryDealGetDouble(ticket, DEAL_PRICE); // Price this.m_commission = ::HistoryDealGetDouble(ticket, DEAL_COMMISSION); // Commission this.m_swap = ::HistoryDealGetDouble(ticket, DEAL_SWAP); // Accumulated swap when closing this.m_profit = ::HistoryDealGetDouble(ticket, DEAL_PROFIT); // Financial result this.m_fee = ::HistoryDealGetDouble(ticket, DEAL_FEE); // Deal fee this.m_sl = ::HistoryDealGetDouble(ticket, DEAL_SL); // Stop Loss level this.m_tp = ::HistoryDealGetDouble(ticket, DEAL_TP); // Take Profit level //--- String properties this.m_symbol = ::HistoryDealGetString(ticket, DEAL_SYMBOL); // Symbol name this.m_comment = ::HistoryDealGetString(ticket, DEAL_COMMENT); // Comment this.m_external_id= ::HistoryDealGetString(ticket, DEAL_EXTERNAL_ID); // Deal ID in an external trading system //--- Graphics display parameters this.m_chart_id = ::ChartID(); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); this.m_point = ::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_width = 19; this.m_height = 19; this.m_name = "Deal#"+(string)this.m_ticket; this.m_color_arrow= (this.TypeDeal()==DEAL_TYPE_BUY ? C'3,95,172' : this.TypeDeal()==DEAL_TYPE_SELL ? C'225,68,29' : C'180,180,180'); //--- Parameters for calculating spread this.m_spread = 0; this.m_bid = 0; this.m_ask = 0; //--- Create a graphic label this.CreateLabelObj(); //--- If the historical tick and the Point value of the symbol were obtained if(this.GetDealTick() && this.m_point!=0) { //--- set the Bid and Ask price values, calculate and save the spread value this.m_bid=this.m_tick.bid; this.m_ask=this.m_tick.ask; this.m_spread=int((this.m_ask-this.m_bid)/this.m_point); } //--- If failed to obtain a historical tick, take the spread value of the minute bar the deal took place on else this.m_spread=this.GetSpreadM1(); }
Beachten Sie, dass das aktuelle Angebot bereits ausgewählt wurde. Deshalb füllen wir hier sofort alle Objekteigenschaften aus den Handelsgeschäftseigenschaften aus. Als Nächstes legen wir die Canvas-Parameter für die Anzeige der Deal-Beschriftung im Chart fest, erstellen ein grafisches Objekt dieser Beschriftung und ermitteln den Tick zum Zeitpunkt des Deals, um den zum Zeitpunkt des Deals vorhandenen Spread zu berechnen. Wenn wir keinen Tick erhalten haben, nehmen wir die durchschnittliche Spanne des Minutenbalkens, währenddessen das Handelsgeschäft stattgefunden hat.
Als Ergebnis erhalten wir bei der Erstellung eines Handelsgeschäftsobjekts sofort ein Objekt, in dem die Eigenschaften eines historischen Handelsgeschäfts entsprechend seinem Ticket eingestellt sind, sowie bereits erstellte, aber noch verborgene Angaben für die Visualisierung des Handelsgeschäfts auf einem Chart und den Spread-Wert zum Zeitpunkt des Handelsgeschäfts.
Im Destruktor der Klasse wird der Grund für die Deinitialisierung geprüft. Wenn der Zeitrahmen nicht geändert wurde, wird die grafische Ressource des grafischen Objekts der Angaben zerstört:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CDeal::~CDeal() { if(::UninitializeReason()!=REASON_CHARTCHANGE) this.m_canvas.Destroy(); }
Die virtuelle Methode, um zwei Objekte anhand einer bestimmten Eigenschaft miteinander zu vergleichen:
//+------------------------------------------------------------------+ //| Compare two objects by the specified property | //+------------------------------------------------------------------+ int CDeal::Compare(const CObject *node,const int mode=0) const { const CDeal * obj = node; switch(mode) { case SORT_MODE_DEAL_TICKET : return(this.Ticket() > obj.Ticket() ? 1 : this.Ticket() < obj.Ticket() ? -1 : 0); case SORT_MODE_DEAL_ORDER : return(this.Order() > obj.Order() ? 1 : this.Order() < obj.Order() ? -1 : 0); case SORT_MODE_DEAL_TIME : return(this.Time() > obj.Time() ? 1 : this.Time() < obj.Time() ? -1 : 0); case SORT_MODE_DEAL_TIME_MSC : return(this.TimeMsc() > obj.TimeMsc() ? 1 : this.TimeMsc() < obj.TimeMsc() ? -1 : 0); case SORT_MODE_DEAL_TYPE : return(this.TypeDeal() > obj.TypeDeal() ? 1 : this.TypeDeal() < obj.TypeDeal() ? -1 : 0); case SORT_MODE_DEAL_ENTRY : return(this.Entry() > obj.Entry() ? 1 : this.Entry() < obj.Entry() ? -1 : 0); case SORT_MODE_DEAL_MAGIC : return(this.Magic() > obj.Magic() ? 1 : this.Magic() < obj.Magic() ? -1 : 0); case SORT_MODE_DEAL_REASON : return(this.Reason() > obj.Reason() ? 1 : this.Reason() < obj.Reason() ? -1 : 0); case SORT_MODE_DEAL_POSITION_ID : return(this.PositionID() > obj.PositionID() ? 1 : this.PositionID() < obj.PositionID() ? -1 : 0); case SORT_MODE_DEAL_VOLUME : return(this.Volume() > obj.Volume() ? 1 : this.Volume() < obj.Volume() ? -1 : 0); case SORT_MODE_DEAL_PRICE : return(this.Price() > obj.Price() ? 1 : this.Price() < obj.Price() ? -1 : 0); case SORT_MODE_DEAL_COMMISSION : return(this.Commission() > obj.Commission() ? 1 : this.Commission() < obj.Commission() ? -1 : 0); case SORT_MODE_DEAL_SWAP : return(this.Swap() > obj.Swap() ? 1 : this.Swap() < obj.Swap() ? -1 : 0); case SORT_MODE_DEAL_PROFIT : return(this.Profit() > obj.Profit() ? 1 : this.Profit() < obj.Profit() ? -1 : 0); case SORT_MODE_DEAL_FEE : return(this.Fee() > obj.Fee() ? 1 : this.Fee() < obj.Fee() ? -1 : 0); case SORT_MODE_DEAL_SL : return(this.SL() > obj.SL() ? 1 : this.SL() < obj.SL() ? -1 : 0); case SORT_MODE_DEAL_TP : return(this.TP() > obj.TP() ? 1 : this.TP() < obj.TP() ? -1 : 0); case SORT_MODE_DEAL_SYMBOL : return(this.Symbol() > obj.Symbol() ? 1 : this.Symbol() < obj.Symbol() ? -1 : 0); case SORT_MODE_DEAL_COMMENT : return(this.Comment() > obj.Comment() ? 1 : this.Comment() < obj.Comment() ? -1 : 0); case SORT_MODE_DEAL_EXTERNAL_ID : return(this.ExternalID() > obj.ExternalID() ? 1 : this.ExternalID() < obj.ExternalID() ? -1 : 0); default : return(-1); } }
Die Methode erhält den Zeiger auf das verglichene Objekt und den Wert der verglichenen Eigenschaft aus der Enumeration ENUM_DEAL_SORT_MODE. Wenn der Wert der angegebenen Eigenschaft des aktuellen Objekts größer ist als der Wert der Eigenschaft des verglichenen Objekts, wird 1 zurückgegeben. Wenn der Wert der aktuellen Objekteigenschaft kleiner ist als der Wert der verglichenen Objekte, wird -1 zurückgegeben. In jedem anderen Fall haben wir Null.
Die Methode, die eine Beschreibung der Handelsgeschäftsart zurückgibt:
//+------------------------------------------------------------------+ //| Return the deal type description | //+------------------------------------------------------------------+ string CDeal::TypeDescription(void) const { switch(this.m_type) { case DEAL_TYPE_BUY : return "Buy"; case DEAL_TYPE_SELL : return "Sell"; case DEAL_TYPE_BALANCE : return "Balance"; case DEAL_TYPE_CREDIT : return "Credit"; case DEAL_TYPE_CHARGE : return "Additional charge"; case DEAL_TYPE_CORRECTION : return "Correction"; case DEAL_TYPE_BONUS : return "Bonus"; case DEAL_TYPE_COMMISSION : return "Additional commission"; case DEAL_TYPE_COMMISSION_DAILY : return "Daily commission"; case DEAL_TYPE_COMMISSION_MONTHLY : return "Monthly commission"; case DEAL_TYPE_COMMISSION_AGENT_DAILY : return "Daily agent commission"; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: return "Monthly agent commission"; case DEAL_TYPE_INTEREST : return "Interest rate"; case DEAL_TYPE_BUY_CANCELED : return "Canceled buy deal"; case DEAL_TYPE_SELL_CANCELED : return "Canceled sell deal"; case DEAL_DIVIDEND : return "Dividend operations"; case DEAL_DIVIDEND_FRANKED : return "Franked (non-taxable) dividend operations"; case DEAL_TAX : return "Tax charges"; default : return "Unknown: "+(string)this.m_type; } }
Hier werden die Beschreibungen aller Handelsgeschäftsarten zurückgegeben. Die meisten von ihnen werden im Programm nicht verwendet. Die Methode gibt jedoch alle Typen zurück, wobei die Möglichkeiten der Erweiterung und Vererbung von Klassen berücksichtigt werden.
Die Methode liefert eine Beschreibung der Positionsänderungsmethode:
//+------------------------------------------------------------------+ //| Return position change method | //+------------------------------------------------------------------+ string CDeal::EntryDescription(void) const { switch(this.m_entry) { case DEAL_ENTRY_IN : return "Entry In"; case DEAL_ENTRY_OUT : return "Entry Out"; case DEAL_ENTRY_INOUT : return "Reverse"; case DEAL_ENTRY_OUT_BY : return "Close a position by an opposite one"; default : return "Unknown: "+(string)this.m_entry; } }
Die Methode liefert eine Beschreibung des Handelsgeschäftsgrundes:
//+------------------------------------------------------------------+ //| Return a deal reason description | //+------------------------------------------------------------------+ string CDeal::ReasonDescription(void) const { switch(this.m_reason) { case DEAL_REASON_CLIENT : return "Terminal"; case DEAL_REASON_MOBILE : return "Mobile"; case DEAL_REASON_WEB : return "Web"; case DEAL_REASON_EXPERT : return "EA"; case DEAL_REASON_SL : return "SL"; case DEAL_REASON_TP : return "TP"; case DEAL_REASON_SO : return "SO"; case DEAL_REASON_ROLLOVER : return "Rollover"; case DEAL_REASON_VMARGIN : return "Var. Margin"; case DEAL_REASON_SPLIT : return "Split"; case DEAL_REASON_CORPORATE_ACTION: return "Corp. Action"; default : return "Unknown reason "+(string)this.m_reason; } }
Wir werden nur drei Werte aus der Methode verwenden: Schließen durch StopLoss, TakeProfit und StopOut.
Die Methode liefert eine Handelsgeschäftsbeschreibung:
//+------------------------------------------------------------------+ //| Return deal description | //+------------------------------------------------------------------+ string CDeal::Description(void) { return(::StringFormat("Deal: %-9s %.2f %-4s #%I64d at %s", this.EntryDescription(), this.Volume(), this.TypeDescription(), this.Ticket(), this.TimeMscToString(this.TimeMsc()))); }
Wir verwenden die Funktion StringFormat(), um die folgende Zeichenkette zu erstellen und zurückzugeben:
Deal: Entry In 0.10 Buy #1728374638 at 2023.06.12 16:51:36.838
Die virtuelle Methode liefert den Text einer Pop-up-Nachricht zum Abschluss:
//+------------------------------------------------------------------+ //| Returns a text of a deal pop-up message | //+------------------------------------------------------------------+ string CDeal::Tooltip(void) { return(::StringFormat("Position ID #%I64d %s:\nDeal #%I64d %.2f %s %s\n%s [%.*f]\nProfit: %.2f, SL: %.*f, TP: %.*f", this.PositionID(), this.Symbol(), this.Ticket(), this.Volume(), this.TypeDescription(), this.EntryDescription(), this.TimeMscToString(this.TimeMsc()), this.m_digits, this.Price(), this.Profit(), this.m_digits, this.SL(), this.m_digits, this.TP())); }
Ähnlich wie bei der oben beschriebenen Methode wird hier eine Zeichenkette des folgenden Typs gebildet und zurückgegeben:
Position ID #1752955040 EURUSD: Deal #1728430603 0.10 Sell Entry Out 2023.06.12 17:04:20.362 [1.07590] Profit: 15.00, SL: 1.07290, TP: 1.07590
Diese Zeichenkette wird später verwendet, um eine Handelsgeschäftsbeschreibung in einem Tooltip anzuzeigen, wenn der Mauszeiger über die Handelsgeschäftsbezeichnung im Chart bewegt wird. Die Methode kann in geerbten Klassen umdefiniert werden, um andere Handelsgeschäftsinformationen auszugeben.
Die Methode, die die Handelsgeschäftseigenschaften im Journal ausgibt:
//+------------------------------------------------------------------+ //| Print deal properties in the journal | //+------------------------------------------------------------------+ void CDeal::Print(void) { ::PrintFormat(" %s", this.Description()); }
Die Methode gibt nicht alle Eigenschaften des Handelsgeschäfts aus, sondern zeigt nur die Mindestinformationen über das Handelsgeschäft an, die von der oben beschriebenen Methode Description() zurückgegeben werden. Vor der Angabe zu einem Handelsgeschäft werden zwei Leerzeichen eingefügt, um die Informationen bei der Anzeige einer Positionsangaben zu ordnen, in der auf eine Angabenüberschrift die Daten zur Position folgen:
Position EURUSD 0.10 Buy #1752955040, Magic 0 -Opened 2023.06.12 16:51:36.838 [1.07440] -Closed 2023.06.12 17:04:20.362 [1.07590] Deal: Entry In 0.10 Buy #1728374638 at 2023.06.12 16:51:36.838 Deal: Entry Out 0.10 Sell #1728430603 at 2023.06.12 17:04:20.362
Mehr über die Funktionen PrintFormat() und StringFormat() erfahren Sie in den Artikeln „PrintFormat() studieren und vorgefertigte Beispiele anwenden“ und „StringFormat(). Inspektion und vorgefertigte Beispiele“.
Die Methode liefert Zeitwert mit Millisekunden:
//+------------------------------------------------------------------+ //| Return time with milliseconds | //+------------------------------------------------------------------+ string CDeal::TimeMscToString(const long time_msc, int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const { return(::TimeToString(time_msc/1000, flags) + "." + ::IntegerToString(time_msc %1000, 3, '0')); }
Hier wird eine Zeichenkette gebildet, die den Zeitwert in Millisekunden enthält, der durch Teilung durch 1000 in normale Zeit umgerechnet wird. Die Anzahl der Millisekunden, die sich aus der Division der Zeit in Millisekunden durch 1000 ergibt, wird der resultierenden Zeichenkette nach einem Punkt hinzugefügt. Die Zeichenkette der Millisekunden wird als dreistellige Zahl formatiert, wobei führende Nullen hinzugefügt werden, wenn sie kürzer als drei Ziffern ist. Daraus ergibt sich die folgende Darstellung der Zeit:
2023.06.12 17:04:20.362
Um zu verstehen, wie hoch der Spread zum Zeitpunkt des Abschlusses war, muss man den erforderlichen Tick für die Abschlusszeit in Millisekunden erhalten. Eine scheinbar triviale Aufgabe, nämlich das Kopieren eines Ticks zu einem bekannten Zeitpunkt mit Hilfe der Standardfunktion CopyTicks(), führte zu einer kleinen Suche nach einer Lösung, da es nicht möglich war, den Tick genau zum angegebenen Zeitpunkt zu kopieren. Nach einiger Suche nach einer Lösung habe ich den notwendigen Algorithmus gefunden: Wir müssen eine bestimmte Anzahl von Tick-Anfragen in einem immer größer werdenden Zeitbereich von „From“ bis zum Abschlusszeitpunkt stellen. Mehr dazu hier (auf Russisch).
Die Methode, um den Deal zu bekommen ticken:
//+------------------------------------------------------------------+ //| Get the deal tick | //+------------------------------------------------------------------+ bool CDeal::GetDealTick(const int amount=20) { MqlTick ticks[]; // We will receive ticks here int attempts = amount; // Number of attempts to get ticks int offset = 500; // Initial time offset for an attempt int copied = 0; // Number of ticks copied //--- Until the tick is copied and the number of copy attempts is over //--- we try to get a tick, doubling the initial time offset at each iteration (expand the "from_msc" time range) while(!::IsStopped() && (copied<=0) && (attempts--)!=0) copied = ::CopyTicksRange(this.m_symbol, ticks, COPY_TICKS_INFO, this.m_time_msc-(offset <<=1), this.m_time_msc); //--- If the tick was successfully copied (it is the last one in the tick array), set it to the m_tick variable if(copied>0) this.m_tick=ticks[copied-1]; //--- Return the flag that the tick was copied return(copied>0); }
Nach dem Erhalt eines Ticks werden der Brief- und der Geldkurs (Ask & Bid) daraus entnommen und die Spread-Größe wird als (Ask - Bid) / Point berechnet.
Wenn es nicht gelingt, mit dieser Methode einen Tick zu erhalten, ermitteln wir den Durchschnittswert dieser Spanne mit der Methode zur Ermittlung der Spanne des Minutenbarrens des Handelsgeschäfts:
//+------------------------------------------------------------------+ //| Gets the spread of the deal minute bar | //+------------------------------------------------------------------+ int CDeal::GetSpreadM1(void) { int array[1]={}; int bar=::iBarShift(this.m_symbol, PERIOD_M1, this.Time()); if(bar==WRONG_VALUE) return 0; return(::CopySpread(this.m_symbol, PERIOD_M1, bar, 1, array)==1 ? array[0] : 0); }
Hier erhalten wir die Eröffnungszeit des Minutenbalkens durch die Handelszeit, die verwendet wird, um den durchschnittlichen Spread des Balkens mit Hilfe der Funktion CopySpread() zu ermitteln. Wenn ein Fehler beim Abrufen eines Balkens oder einer Spanne auftritt, gibt die Methode Null zurück.
Die Methode, die ein Objekt für die Angaben auf einem Chart erstellt:
//+------------------------------------------------------------------+ //| Create a label object on the chart | //+------------------------------------------------------------------+ bool CDeal::CreateLabelObj(void) { //--- Create a graphical resource with a Bitmap object attached to it ::ResetLastError(); if(!this.m_canvas.CreateBitmap(this.m_name, this.m_time, this.m_price, this.m_width, this.m_height, COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: When creating a graphic object, error %d occurred in the CreateBitmap method of the CCanvas class",__FUNCTION__, ::GetLastError()); return false; } //--- If the graphical resource is successfully created, set the Bitmap object, anchor point, price, time and tooltip text ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_ANCHOR, ANCHOR_CENTER); ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIME, this.Time()); ::ObjectSetDouble(this.m_chart_id, this.m_name, OBJPROP_PRICE, this.Price()); ::ObjectSetString(this.m_chart_id, this.m_name, OBJPROP_TOOLTIP, this.Tooltip()); //--- Hide the created object from the chart and draw its appearance on it this.HideArrow(); this.DrawLabelView(); return true; }
Wir erstellen eine grafische Ressource, setzen ihren Ankerpunkt in die Mitte, geben den Preis und die Uhrzeit des Handelsgeschäfts sowie den Tooltip-Text an. Anschließend wird für das erstellte Objekt ein Flag gesetzt, das anzeigt, dass es in allen Chart-Zeitrahmen ausgeblendet ist, und sein Aussehen wird darauf gezeichnet. Um es anzuzeigen, müssen wir nur das Sichtbarkeitsflag für das Objekt in allen Zeitrahmen setzen.
Die Methode, die das Aussehen des Objekts der Angabe zeichnet:
//+------------------------------------------------------------------+ //| Draw the appearance of the label object | //+------------------------------------------------------------------+ void CDeal::DrawLabelView(void) { this.m_canvas.Erase(0x00FFFFFF); this.DrawArrow(); this.m_canvas.Update(true); }
Zunächst wird das Bitmap-Grafikobjekt mit einer vollständig transparenten Farbe gefüllt, dann wird ein dem Handelsgeschäftstyp entsprechender Pfeil darauf gezeichnet, und anschließend wird der Hintergrund aktualisiert, während das Chart neu gezeichnet wird.
Die virtuelle Methode, die den Pfeil zeichnet, der dem Handelsgeschäftstyp entspricht:
//+------------------------------------------------------------------+ //| Draw an arrow corresponding to the deal type | //+------------------------------------------------------------------+ void CDeal::DrawArrow(void) { switch(this.TypeDeal()) { case DEAL_TYPE_BUY : this.DrawArrowBuy(5, 10); break; case DEAL_TYPE_SELL : this.DrawArrowSell(5, 0); break; default : break; } }
Je nach Handelsgeschäftsart (Kauf oder Verkauf) wird die Methode zum Zeichnen des entsprechenden Pfeils aufgerufen. Die Methode ist virtuell, sodass sie in den abgeleiteten Klassen neu definiert werden kann. In einer untergeordneten Klasse können wir zum Beispiel ein anderes Etikett für eine andere Art von Handelsgeschäft zeichnen, oder Sie können zusätzlich die Methode zur Änderung der Position berücksichtigen, usw.
Die Methode für das Zeichnen der Kaufpfeil-Maske auf dem Hintergrund:
//+------------------------------------------------------------------+ //| Draw Buy arrow mask on the canvas | //+------------------------------------------------------------------+ void CDeal::DrawArrowMaskBuy(const int shift_x, const int shift_y) { int x[]={4+shift_x, 8+shift_x, 8+shift_x, 6+shift_x, 6+shift_x, 2+shift_x, 2+shift_x, 0+shift_x, 0+shift_x, 4+shift_x}; int y[]={0+shift_y, 4+shift_y, 5+shift_y, 5+shift_y, 7+shift_y, 7+shift_y, 5+shift_y, 5+shift_y, 4+shift_y, 0+shift_y}; this.m_canvas.Polygon(x, y, ::ColorToARGB(clrWhite, 220)); }
Die Pfeile, die als Deal-Angaben gezeichnet sind, haben einen weißen Umriss (Maske), um sich vom dunklen Hintergrund der Kerze abzuheben:
Da die Koordinaten der auf dem Hintergrund gezeichneten Figuren immer relativ zu den lokalen Koordinaten des Hintergrunds angegeben werden, ist es notwendig, Versätze einzugeben, die zu den Koordinaten der gestrichelten Linienpunkte entlang der X- und Y-Achse addiert werden, damit wir die gezeichnete Figur vor dem Hintergrund genau zentrieren können. Anschließend werden unter Berücksichtigung der an die Methode übergebenen Offsets die Werte der X- und Y-Koordinaten in den Arrays festgelegt und die Methode Polygon() aufgerufen, um einen weißen Umriss unter Verwendung der Koordinatenpunkte zu zeichnen. Als Nächstes wird der Pfeil innerhalb der Kontur mit Hilfe der Pfeilzeichnungsmethoden gezeichnet.
Die Methode für das Zeichnen des Kaufpfeils auf dem Hintergrund:
//+------------------------------------------------------------------+ //| Draw Buy arrow on the canvas | //+------------------------------------------------------------------+ void CDeal::DrawArrowBuy(const int shift_x, const int shift_y) { this.DrawArrowMaskBuy(shift_x, shift_y); int x[]={4+shift_x, 7+shift_x, 5+shift_x, 5+shift_x, 3+shift_x, 3+shift_x, 1+shift_x, 4+shift_x}; int y[]={1+shift_y, 4+shift_y, 4+shift_y, 6+shift_y, 6+shift_y, 4+shift_y, 4+shift_y, 1+shift_y}; this.m_canvas.Polygon(x, y, ::ColorToARGB(this.m_color_arrow)); this.m_canvas.Fill(4+shift_x, 4+shift_y, ::ColorToARGB(this.m_color_arrow)); }
Hier zeichnen wir zuerst die Maske des Kaufpfeils, dann zeichnen wir den Kaufpfeil unter Verwendung der angegebenen Koordinaten und füllen seinen inneren Raum mit Farbe.
Ähnliche Methoden werden für das Zeichnen des Pfeils für einen Verkauf angewandt:
//+------------------------------------------------------------------+ //| Draw Sell arrow mask on the canvas | //+------------------------------------------------------------------+ void CDeal::DrawArrowMaskSell(const int shift_x, const int shift_y) { int x[]={4+shift_x, 0+shift_x, 0+shift_x, 2+shift_x, 2+shift_x, 6+shift_x, 6+shift_x, 8+shift_x, 8+shift_x, 4+shift_x}; int y[]={8+shift_y, 4+shift_y, 3+shift_y, 3+shift_y, 1+shift_y, 1+shift_y, 3+shift_y, 3+shift_y, 4+shift_y, 8+shift_y}; this.m_canvas.Polygon(x, y, ::ColorToARGB(clrWhite, 220)); } //+------------------------------------------------------------------+ //| Draw Sell arrow on the canvas | //+------------------------------------------------------------------+ void CDeal::DrawArrowSell(const int shift_x, const int shift_y) { this.DrawArrowMaskSell(shift_x, shift_y); int x[]={4+shift_x, 1+shift_x, 3+shift_x, 3+shift_x, 5+shift_x, 5+shift_x, 7+shift_x, 4+shift_x}; int y[]={7+shift_y, 4+shift_y, 4+shift_y, 2+shift_y, 2+shift_y, 4+shift_y, 4+shift_y, 7+shift_y}; this.m_canvas.Polygon(x, y, ::ColorToARGB(this.m_color_arrow)); this.m_canvas.Fill(4+shift_x, 4+shift_y, ::ColorToARGB(this.m_color_arrow)); }
Methoden zum Ausblenden und Anzeigen des Deal-Angaben auf dem Chart:
//+------------------------------------------------------------------+ //| Hide the deal label on the chart | //+------------------------------------------------------------------+ void CDeal::HideArrow(const bool chart_redraw=false) { ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); if(chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Display the deal label on the chart | //+------------------------------------------------------------------+ void CDeal::ShowArrow(const bool chart_redraw=false) { ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Um ein Objekt im Chart auszublenden, sollten die Sichtbarkeitseigenschaften OBJPROP_TIMEFRAMES des grafischen Objekts auf OBJ_NO_PERIODS gesetzt werden.
Dementsprechend müssen wir, um das Objekt anzuzeigen, die Eigenschaft OBJPROP_TIMEFRAMES auf OBJ_ALL_PERIODS setzen.
Die Methode, die die Farbe der Deal-Angaben festlegt:
//+------------------------------------------------------------------+ //| Set the deal label color | //+------------------------------------------------------------------+ void CDeal::SetColorArrow(const color clr) { this.m_color_arrow=clr; this.DrawLabelView(); }
Die Variable m_color_arrow, die die Farbe der gezeichneten Angaben speichert, wird auf den Wert gesetzt, der der Methode übergeben wurde, und das gesamte grafische Objekt wird komplett neu gezeichnet. So ist es möglich, die Farbe des Deal-Labels auf dem Chart vom Kontrollprogramm aus „on the fly“ zu ändern.
Wir haben eine Deal-Klasse erstellt, die den Zugriff auf die Eigenschaften eines historischen Deals über sein Ticket ermöglicht und es uns erlaubt, die notwendigen Daten zu diesem Deal zu erhalten und sein Label im Chart anzuzeigen oder auszublenden.
Die Klassenobjekte werden in der Liste der Positionsobjektgeschäfte gespeichert, die wiederum die Eigenschaften der historischen Position anzeigt, Zugang zu ihren Handelsgeschäften und Parametern bietet und durch eine Linie verbundene Eröffnung und Schließen des Handelsgeschäfts anzeigt.
Die Klasse Position
Wir erstellen die neue Datei Position.mqh der Klasse CPosition in demselben Ordner, in dem die Klasse der Deals erstellt wurde.
Ähnlich wie bei der Deal-Klasse implementieren wir die Enumeration von Objekteigenschaften, um nach Positionseigenschaften zu suchen und zu sortieren, und binden die Dateien der Klassen Deal und CArrayObj für die Positionsklasse ein:
//+------------------------------------------------------------------+ //| Position.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Deal.mqh" #include <Arrays\ArrayObj.mqh> enum ENUM_POSITION_SORT_MODE { SORT_MODE_POSITION_TICKET = 0, // Mode of comparing/sorting by a position ticket SORT_MODE_POSITION_TIME, // Mode of comparing/sorting by position open time SORT_MODE_POSITION_TIME_MSC, // Mode of comparing/sorting by position open time im milliseconds SORT_MODE_POSITION_TIME_UPDATE, // Mode of comparing/sorting by position update time SORT_MODE_POSITION_TIME_UPDATE_MSC, // Mode of comparing/sorting by position update time im milliseconds SORT_MODE_POSITION_TYPE, // Mode of comparing/sorting by position type SORT_MODE_POSITION_MAGIC, // Mode of comparing/sorting by a position magic number SORT_MODE_POSITION_IDENTIFIER, // Mode of comparing/sorting by a position ID SORT_MODE_POSITION_REASON, // Mode of comparing/sorting by position open reason SORT_MODE_POSITION_VOLUME, // Mode of comparing/sorting by a position volume SORT_MODE_POSITION_PRICE_OPEN, // Mode of comparing/sorting by a position price SORT_MODE_POSITION_SL, // Mode of comparing/sorting by Stop Loss level for an open position SORT_MODE_POSITION_TP, // Mode of comparing/sorting by Take Profit level for an open position SORT_MODE_POSITION_PRICE_CURRENT, // Mode of comparing/sorting by the current symbol price SORT_MODE_POSITION_SWAP, // Mode of comparing/sorting by accumulated swap SORT_MODE_POSITION_PROFIT, // Mode of comparing/sorting by the current profit SORT_MODE_POSITION_SYMBOL, // Mode of comparing/sorting by a symbol a position is opened on SORT_MODE_POSITION_COMMENT, // Mode of comparing/sorting by a position comment SORT_MODE_POSITION_EXTERNAL_ID, // Mode of comparing/sorting by a position ID in an external system SORT_MODE_POSITION_TIME_CLOSE, // Mode of comparing/sorting by a position open time SORT_MODE_POSITION_TIME_CLOSE_MSC, // Mode of comparing/sorting by a position open time in milliseconds SORT_MODE_POSITION_PRICE_CLOSE, // Mode of comparing/sorting by a position price }; //+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { }
In den geschützten und öffentlichen Abschnitten der Klasse deklarieren wir die Variablen und Methoden für die Handhabung der Klasse:
//+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { private: protected: CArrayObj m_list_deals; // List of position deals CDeal m_temp_deal; // Temporary deal object for searching by property in the list //--- Return time with milliseconds string TimeMscToString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const; //--- Integer properties long m_ticket; // Position ticket datetime m_time; // Position opening time long m_time_msc; // Position opening time in milliseconds since 01.01.1970 datetime m_time_update; // Position update time long m_time_update_msc; // Position update time in milliseconds since 01.01.1970 ENUM_POSITION_TYPE m_type; // Position type long m_magic; // Magic number for a position (see ORDER_MAGIC) long m_identifier; // Position ID ENUM_POSITION_REASON m_reason; // Position opening reason //--- Real properties double m_volume; // Position volume double m_price_open; // Position price double m_sl; // Stop Loss level for an open position double m_tp; // Take Profit level for an open position double m_price_current; // Current price by symbol double m_swap; // Accumulated swap double m_profit; // Current profit //--- String properties string m_symbol; // A symbol the position is open for string m_comment; // Position comment string m_external_id; // Position ID in an external system (on the exchange) //--- Additional properties long m_chart_id; // Chart ID int m_profit_pt; // Profit in points int m_digits; // Symbol digits double m_point; // One symbol point value double m_contract_size; // Symbol trade contract size string m_currency_profit; // Symbol profit currency string m_account_currency; // Deposit currency string m_line_name; // Line graphical object name color m_line_color; // Connecting line color //--- Create a line connecting open-close deals virtual bool CreateLine(void); //--- Return the pointer to (1) open and (2) close deal CDeal *GetDealIn(void) const; CDeal *GetDealOut(void) const; //--- (1) Hide and (2) display deal labels on the chart void HideDeals(const bool chart_redraw=false); void ShowDeals(const bool chart_redraw=false); //--- (1) Hide and (2) display the connecting line between the deal labels void HideLine(const bool chart_redraw=false); void ShowLine(const bool chart_redraw=false); public: //--- Set the properties //--- Integer properties void SetTicket(const long ticket) { this.m_ticket=ticket; } // Position ticket void SetTime(const datetime time) { this.m_time=time; } // Position open time void SetTimeMsc(const long value) { this.m_time_msc=value; } // Position open time in milliseconds 01.01.1970 void SetTimeUpdate(const datetime time) { this.m_time_update=time; } // Position update time void SetTimeUpdateMsc(const long value) { this.m_time_update_msc=value; } // Position update time in milliseconds 01.01.1970 void SetTypePosition(const ENUM_POSITION_TYPE type) { this.m_type=type; } // Position type void SetMagic(const long magic) { this.m_magic=magic; } // Magic number for a position (see ORDER_MAGIC) void SetID(const long id) { this.m_identifier=id; } // Position ID void SetReason(const ENUM_POSITION_REASON reason) { this.m_reason=reason; } // Position opening reason //--- Real properties void SetVolume(const double volume) { this.m_volume=volume; } // Position volume void SetPriceOpen(const double price) { this.m_price_open=price; } // Position price void SetSL(const double value) { this.m_sl=value; } // Stop Loss level for an open position void SetTP(const double value) { this.m_tp=value; } // Take Profit level for an open position void SetPriceCurrent(const double price) { this.m_price_current=price; } // Current price by symbol void SetSwap(const double value) { this.m_swap=value; } // Accumulated swap void SetProfit(const double value) { this.m_profit=value; } // Current profit //--- String properties void SetSymbol(const string symbol) { this.m_symbol=symbol; } // Symbol a position is opened for void SetComment(const string comment) { this.m_comment=comment; } // Position comment void SetExternalID(const string ext_id) { this.m_external_id=ext_id; } // Position ID in an external system (on the exchange) //--- Get the properties //--- Integer properties long Ticket(void) const { return(this.m_ticket); } // Position ticket datetime Time(void) const { return(this.m_time); } // Position open time long TimeMsc(void) const { return(this.m_time_msc); } // Position open time in milliseconds since 01.01.1970 datetime TimeUpdate(void) const { return(this.m_time_update); } // Position update time long TimeUpdateMsc(void) const { return(this.m_time_update_msc);} // Position update time in milliseconds since 01.01.1970 ENUM_POSITION_TYPE TypePosition(void) const { return(this.m_type); } // Position type long Magic(void) const { return(this.m_magic); } // Magic number for a position (see ORDER_MAGIC) long ID(void) const { return(this.m_identifier); } // Position ID ENUM_POSITION_REASON Reason(void) const { return(this.m_reason); } // Position opening reason //--- Real properties double Volume(void) const { return(this.m_volume); } // Position volume double PriceOpen(void) const { return(this.m_price_open); } // Position price double SL(void) const { return(this.m_sl); } // Stop Loss level for an open position double TP(void) const { return(this.m_tp); } // Take Profit for an open position double PriceCurrent(void) const { return(this.m_price_current); } // Current price by symbol double Swap(void) const { return(this.m_swap); } // Accumulated swap double Profit(void) const { return(this.m_profit); } // Current profit //--- String properties string Symbol(void) const { return(this.m_symbol); } // A symbol position is opened on string Comment(void) const { return(this.m_comment); } // Position comment string ExternalID(void) const { return(this.m_external_id); } // Position ID in an external system (on the exchange) //--- Additional properties ulong DealIn(void) const; // Open deal ticket ulong DealOut(void) const; // Close deal ticket datetime TimeClose(void) const; // Close time long TimeCloseMsc(void) const; // Close time in milliseconds int ProfitInPoints(void) const; // Profit in points double PriceClose(void) const; // Close price //--- Add a deal to the list of deals, return the pointer CDeal *DealAdd(const long ticket); //--- Set the color of the (1) connecting line, (2) Buy and Sell deals void SetLineColor(const color clr=C'225,68,29'); void SetDealsColor(const color clr_deal_buy=C'3,95,172', const color clr_deal_sell=C'225,68,29'); //--- Return a position type description string TypeDescription(void) const; //--- Return position open time and price description string TimePriceCloseDescription(void); //--- Return position close time and price description string TimePriceOpenDescription(void); //--- Return a brief position description string Description(void); //--- Returns a text of a position popup message virtual string Tooltip(void); //--- Print the properties of the position and its deals in the journal void Print(void); //--- (1) Hide and (2) display a graphical representation of a position on a chart void Hide(const bool chart_redraw=false); void Show(const bool chart_redraw=false); //--- Compare two objects by the property specified in 'mode' virtual int Compare(const CObject *node, const int mode=0) const; //--- Constructor/destructor CPosition(const long position_id, const string symbol); CPosition(void) { this.m_symbol=::Symbol(); } ~CPosition(); };
Um in der sortierten Liste in der Klasse des dynamischen Arrays von Zeigern auf die Instanzen der Klasse CObject und ihrer Nachkommen CArrayObj zu suchen, erhält die Methode Search() den Zeiger auf die Instanz des Objekts, in dem wir einen Vergleich nach der Eigenschaft durchführen sollen, die zum Sortieren der Liste verwendet wird. Um zu vermeiden, dass ständig ein neues Objekt mit einer bestimmten Eigenschaft erstellt und zerstört wird, was ziemlich leistungsintensiv ist, wird eine Instanz eines deal-Objekts im geschützten Bereich der Klasse deklariert. Um eine Suche durchzuführen, setzen wir einfach den erforderlichen Wert der gewünschten Eigenschaft in diesem Objekt und übergeben ihn als gewünschte Instanz an die Suchmethode.
Schauen wir uns die angegebenen Methoden genauer an.
Der parametrische Konstruktor:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPosition::CPosition(const long position_id, const string symbol) { this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC); this.m_identifier = position_id; this.m_account_currency = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_symbol = (symbol==NULL ? ::Symbol() : symbol); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol,SYMBOL_DIGITS); this.m_point = ::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT); this.m_contract_size = ::SymbolInfoDouble(this.m_symbol,SYMBOL_TRADE_CONTRACT_SIZE); this.m_currency_profit = ::SymbolInfoString(this.m_symbol,SYMBOL_CURRENCY_PROFIT); this.m_chart_id = ::ChartID(); this.m_line_name = "line#"+(string)this.m_identifier; this.m_line_color = C'225,68,29'; }
Der Konstruktor erhält die Positions-ID und das Symbol, für das die Position geöffnet wurde. Die Liste der Positionsgeschäfte erhält die Flag, dass sie nach einer Handelsgeschäftszeit in Millisekunden sortiert ist. Die ID und das Symbol werden in den Klassenvariablen gespeichert, während einige Parameter des Kontos und des Symbols sowie die Chart-ID und die Eigenschaften der Linie, die die Angaben zum Eröffnen und Schließen verbindet, festgelegt werden.
Im Destruktor der Klasse entfernen wir alle grafischen Objekte, deren Präfix der Zeilenname ist, und löschen die Liste der Deals:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPosition::~CPosition() { ::ObjectDelete(this.m_chart_id, this.m_line_name); this.m_list_deals.Clear(); }
Das Löschen des grafischen Linienobjekts erfolgt aus folgenden Gründen durch ein Präfix: Wenn die geerbte Klasse mehrere Linien anzeigen soll, die alle Positionsgeschäfte miteinander verbinden, und nicht nur die öffnenden und schließenden, dann kann der Name jeder Linie als „Linienname“ + „Zeilennummer“ festgelegt werden. In diesem Fall werden alle diese Zeilen trotzdem im Destruktor gelöscht, da sie alle ein gemeinsames Präfix - „m_line_name“ - haben.
Die Methode, die zwei Objekte anhand einer bestimmten Eigenschaft vergleicht:
//+------------------------------------------------------------------+ //| Compare two objects by the specified property | //+------------------------------------------------------------------+ int CPosition::Compare(const CObject *node,const int mode=0) const { const CPosition *obj=node; switch(mode) { case SORT_MODE_POSITION_TICKET : return(this.Ticket() > obj.Ticket() ? 1 : this.Ticket() < obj.Ticket() ? -1 : 0); case SORT_MODE_POSITION_TIME : return(this.Time() > obj.Time() ? 1 : this.Time() < obj.Time() ? -1 : 0); case SORT_MODE_POSITION_TIME_MSC : return(this.TimeMsc() > obj.TimeMsc() ? 1 : this.TimeMsc() < obj.TimeMsc() ? -1 : 0); case SORT_MODE_POSITION_TIME_UPDATE : return(this.TimeUpdate() > obj.TimeUpdate() ? 1 : this.TimeUpdate() < obj.TimeUpdate() ? -1 : 0); case SORT_MODE_POSITION_TIME_UPDATE_MSC: return(this.TimeUpdateMsc() > obj.TimeUpdateMsc() ? 1 : this.TimeUpdateMsc() < obj.TimeUpdateMsc() ? -1 : 0); case SORT_MODE_POSITION_TYPE : return(this.TypePosition() > obj.TypePosition() ? 1 : this.TypePosition() < obj.TypePosition() ? -1 : 0); case SORT_MODE_POSITION_MAGIC : return(this.Magic() > obj.Magic() ? 1 : this.Magic() < obj.Magic() ? -1 : 0); case SORT_MODE_POSITION_IDENTIFIER : return(this.ID() > obj.ID() ? 1 : this.ID() < obj.ID() ? -1 : 0); case SORT_MODE_POSITION_REASON : return(this.Reason() > obj.Reason() ? 1 : this.Reason() < obj.Reason() ? -1 : 0); case SORT_MODE_POSITION_VOLUME : return(this.Volume() > obj.Volume() ? 1 : this.Volume() < obj.Volume() ? -1 : 0); case SORT_MODE_POSITION_PRICE_OPEN : return(this.PriceOpen() > obj.PriceOpen() ? 1 : this.PriceOpen() < obj.PriceOpen() ? -1 : 0); case SORT_MODE_POSITION_SL : return(this.SL() > obj.SL() ? 1 : this.SL() < obj.SL() ? -1 : 0); case SORT_MODE_POSITION_TP : return(this.TP() > obj.TP() ? 1 : this.TP() < obj.TP() ? -1 : 0); case SORT_MODE_POSITION_PRICE_CURRENT : return(this.PriceCurrent() > obj.PriceCurrent() ? 1 : this.PriceCurrent() < obj.PriceCurrent() ? -1 : 0); case SORT_MODE_POSITION_SWAP : return(this.Swap() > obj.Swap() ? 1 : this.Swap() < obj.Swap() ? -1 : 0); case SORT_MODE_POSITION_PROFIT : return(this.Profit() > obj.Profit() ? 1 : this.Profit() < obj.Profit() ? -1 : 0); case SORT_MODE_POSITION_SYMBOL : return(this.Symbol() > obj.Symbol() ? 1 : this.Symbol() < obj.Symbol() ? -1 : 0); case SORT_MODE_POSITION_COMMENT : return(this.Comment() > obj.Comment() ? 1 : this.Comment() < obj.Comment() ? -1 : 0); case SORT_MODE_POSITION_EXTERNAL_ID : return(this.ExternalID() > obj.ExternalID() ? 1 : this.ExternalID() < obj.ExternalID() ? -1 : 0); case SORT_MODE_POSITION_TIME_CLOSE : return(this.TimeClose() > obj.TimeClose() ? 1 : this.TimeClose() < obj.TimeClose() ? -1 : 0); case SORT_MODE_POSITION_TIME_CLOSE_MSC : return(this.TimeCloseMsc() > obj.TimeCloseMsc() ? 1 : this.TimeCloseMsc() < obj.TimeCloseMsc() ? -1 : 0); case SORT_MODE_POSITION_PRICE_CLOSE : return(this.PriceClose() > obj.PriceClose() ? 1 : this.PriceClose() < obj.PriceClose() ? -1 : 0); default : return -1; } }
Ein ähnliches Verfahren wurde bei der Entwicklung der Klasse der Handelsgeschäftsobjekte angewandt. Hier ist alles ähnlich: Wenn der Wert der aktuellen Objekteigenschaft größer ist als der zu vergleichende Wert, wird 1 zurückgegeben; wenn er kleiner ist, erhalten wir -1; wenn die Eigenschaften gleich sind, haben wir Null.
Die Methode, die die Zeit in Millisekunden zurückgibt:
//+------------------------------------------------------------------+ //| Return time with milliseconds | //+------------------------------------------------------------------+ string CPosition::TimeMscToString(const long time_msc, int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const { return(::TimeToString(time_msc/1000, flags) + "." + ::IntegerToString(time_msc %1000, 3, '0')); }
Hierbei handelt es sich um eine Kopie der gleichnamigen Methode der Klasse des Handelsgeschäftsobjekts, die in der Klasse des Handelsgeschäfts betrachtet wird.
Die Methode, die den Zeiger auf das offene Handelsgeschäft zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the opening deal | //+------------------------------------------------------------------+ CDeal *CPosition::GetDealIn(void) const { int total=this.m_list_deals.Total(); for(int i=0; i<total; i++) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; if(deal.Entry()==DEAL_ENTRY_IN) return deal; } return NULL; }
Wir müssen in der Liste der Positionen einen Deal mit der Eigenschaft DEAL_ENTRY_IN finden. Dieses Handelsgeschäft sollte an erster Stelle in einer nach Zeit sortierten Liste stehen. Daher beginnen wir den Zyklus am Anfang der Liste - bei Index 0.
Wir holen ein Handelsgeschäft aus der Liste nach Index und geben, wenn es sich um ein Markteintrittsgeschäft handelt, den Zeiger auf das in der Liste gefundene Handelsgeschäft zurück. Am Ende des Zyklus wird NULL zurückgegeben — Deal nicht gefunden.
Die Methode, die den Zeiger auf das Schließen des Handelsgeschäfts zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the close deal | //+------------------------------------------------------------------+ CDeal *CPosition::GetDealOut(void) const { for(int i=this.m_list_deals.Total()-1; i>=0; i--) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) return deal; } return NULL; }
Im Vergleich zur vorherigen Methode ist hier alles umgekehrt - das Handelsgeschäft zum Schließen der Position befindet sich ganz am Ende der Liste, sodass wir den Zyklus vom Ende her beginnen.
Sobald ein Handelsgeschäft zum Schließen einer Position gefunden wird, geben wir den Zeiger zurück. Andernfalls wird NULL zurückgegeben.
Die Methoden zur Erlangung zusätzlicher Eigenschaften einer historischen Position:
//+------------------------------------------------------------------+ //| Return the open deal ticket | //+------------------------------------------------------------------+ ulong CPosition::DealIn(void) const { CDeal *deal=this.GetDealIn(); return(deal!=NULL ? deal.Ticket() : 0); } //+------------------------------------------------------------------+ //| Return the close deal ticket | //+------------------------------------------------------------------+ ulong CPosition::DealOut(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Ticket() : 0); } //+------------------------------------------------------------------+ //| Return the close time | //+------------------------------------------------------------------+ datetime CPosition::TimeClose(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Time() : 0); } //+------------------------------------------------------------------+ //| Return the close time in milliseconds | //+------------------------------------------------------------------+ long CPosition::TimeCloseMsc(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.TimeMsc() : 0); } //+------------------------------------------------------------------+ //| Return the close price | //+------------------------------------------------------------------+ double CPosition::PriceClose(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Price() : 0); }
In allen Methoden, erhalten wir zunächst den Zeiger auf ein Handelsgeschäft, über den wir die entsprechende Eigenschaft abrufen, und, wenn der Zeiger gültig ist, geben wir den Wert der Eigenschaft zurück, andernfalls Null.
Die Methode liefert den Gewinn in Punkten:
//+------------------------------------------------------------------+ //| Return a profit in points | //+------------------------------------------------------------------+ int CPosition::ProfitInPoints(void) const { //--- If symbol Point has not been received previously, inform of that and return 0 if(this.m_point==0) { ::Print("The Point() value could not be retrieved."); return 0; } //--- Get position open and close prices double open =this.PriceOpen(); double close=this.PriceClose(); //--- If failed to get the prices, return 0 if(open==0 || close==0) return 0; //--- Depending on the position type, return the calculated value of the position profit in points return int(this.TypePosition()==POSITION_TYPE_BUY ? (close-open)/this.m_point : (open-close)/this.m_point); }
Hier erhalten wir zunächst die Deals von Eröffnung und Schließen der entsprechenden Handelsgeschäfte und geben dann das Ergebnis der Gewinnberechnung in Punkten zurück, je nach Richtung der Position.
Die Methode, die ein Handelsgeschäft zur Liste der Handelsgeschäfte hinzufügt:
//+------------------------------------------------------------------+ //| Add a deal to the list of deals | //+------------------------------------------------------------------+ CDeal *CPosition::DealAdd(const long ticket) { //--- A temporary object gets a ticket of the desired deal and the flag of sorting the list of deals by ticket this.m_temp_deal.SetTicket(ticket); this.m_list_deals.Sort(SORT_MODE_DEAL_TICKET); //--- Set the result of checking if a deal with such a ticket is present in the list bool added=(this.m_list_deals.Search(&this.m_temp_deal)!=WRONG_VALUE); //--- Set the flag of sorting by time in milliseconds for the list this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC); //--- If a deal with such a ticket is already in the list, return NULL if(added) return NULL; //--- Create a new deal object CDeal *deal=new CDeal(ticket); if(deal==NULL) return NULL; //--- Add the created object to the list in sorting order by time in milliseconds //--- If failed to add the deal to the list, remove the the deal object and return NULL if(!this.m_list_deals.InsertSort(deal)) { delete deal; return NULL; } //--- If this is a position closing deal, set the profit from the deal properties to the position profit value and //--- create a connecting line between the position open-close deal labels if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { this.SetProfit(deal.Profit()); this.CreateLine(); } //--- Return the pointer to the created deal object return deal; }
Die Methode empfängt das Ticket eines ausgewählten Handelsgeschäfts. Vergewissern wir uns zunächst, dass sich kein Handelsgeschäft mit einem solchen Ticket in der Liste befindet. Dazu wird dem temporären Handelsgeschäftsobjekt das an die Methode übergebene Ticket zugewiesen. Die Liste der Handelsgeschäfte wird nach dem Wert der Handelsgeschäftstickets sortiert, und es wird nach einem Handelsgeschäft in der Liste gesucht, das ein solches Ticket hat. Unmittelbar nach der Suche nach einem Handelsgeschäft wird das Flag für die Sortierung nach Zeit in Millisekunden an die Liste zurückgegeben. Wenn ein solches Handelsgeschäft bereits in der Liste enthalten ist, muss es nicht hinzugefügt werden - die Methode gibt NULL zurück. Gleichzeitig ist die Sortierung der Handelsgeschäftsliste bereits standardmäßig eingestellt - nach Zeit in Millisekunden. Befindet sich ein solches Handelsgeschäft nicht in der Liste, wird ein neues Handelsgeschäftsobjekt erstellt und der Liste in der Reihenfolge der Sortierung nach Zeit in Millisekunden hinzugefügt. Wenn es sich um das Schließen einer Position handelt, wird der Gewinn in den Positionseigenschaften festgelegt und eine Verbindungslinie zwischen Eröffnen und Schließen der Position erstellt. Als Ergebnis wird der Zeiger auf das neu erstellte Handelsgeschäftsobjekt zurückgegeben.
Die Methode, die eine Beschreibung der Positionsart zurückgibt:
//+------------------------------------------------------------------+ //| Return a position type description | //+------------------------------------------------------------------+ string CPosition::TypeDescription(void) const { return(this.m_type==POSITION_TYPE_BUY ? "Buy" : this.m_type==POSITION_TYPE_SELL ? "Sell" : "Unknown::"+(string)this.m_type); }
Rückgabe der Beschreibung in Abhängigkeit vom Typ einer in der Variablen m_type gespeicherten Position. Wenn der Variablenwert nicht mit den Enumerationskonstanten des Positionstyps übereinstimmt, wird "Unknown::"+variable_value zurückgegeben.
Die Methode liefert den Zeitpunkt, an dem die Position geschlossen wurde, und Preisbeschreibung:
//+------------------------------------------------------------------+ //| Return position close time and price description | //+------------------------------------------------------------------+ string CPosition::TimePriceCloseDescription(void) { if(this.TimeCloseMsc()==0) return "Not closed yet"; return(::StringFormat("Closed %s [%.*f]", this.TimeMscToString(this.TimeCloseMsc()),this.m_digits, this.PriceClose())); }
Wenn im Positionsobjekt kein Hinweis auf das Schließen vorhanden ist, gibt die Methode eine Meldung zurück, dass die Position noch nicht geschlossen worden ist. Andernfalls wird eine Zeichenfolge des folgenden Typs erstellt und zurückgegeben:
Closed 2023.06.12 17:04:20.362 [1.07590]
Die Methode liefert die Beschreibung der Eröffnungszeit und des Preises der Position:
//+------------------------------------------------------------------+ //| Return position open time and price description | //+------------------------------------------------------------------+ string CPosition::TimePriceOpenDescription(void) { return(::StringFormat("Opened %s [%.*f]", this.TimeMscToString(this.TimeMsc()),this.m_digits, this.PriceOpen())); }
Hier wird eine Zeichenkette des folgenden Typs erstellt und zurückgegeben:
Opened 2023.06.12 16:51:36.838 [1.07440]
Die Methode liefert eine kurze Positionsbeschreibung:
//+------------------------------------------------------------------+ //| Return a brief position description | //+------------------------------------------------------------------+ string CPosition::Description(void) { return(::StringFormat("Position %s %.2f %s #%I64d, Magic %I64d", this.Symbol(), this.Volume(), this.TypeDescription(), this.ID(), this.Magic())); }
In der Methode wird eine Zeichenkette wie folgt erstellt und zurückgegeben:
Position EURUSD 0.10 Buy #1752955040, Magic 123
Die virtuelle Methode liefert den Text einer Popup-Meldung zur Position:
//+------------------------------------------------------------------+ //| Return a text of a position pop-up message | //+------------------------------------------------------------------+ string CPosition::Tooltip(void) { //--- Get the pointers to the open and close deals CDeal *deal_in =this.GetDealIn(); CDeal *deal_out=this.GetDealOut(); //--- If no deals are received, return an empty string if(deal_in==NULL || deal_out==NULL) return NULL; //--- Get commission, swap and deal fee that are common for two deals double commission=deal_in.Commission()+deal_out.Commission(); double swap=deal_in.Swap()+deal_out.Swap(); double fee=deal_in.Fee()+deal_out.Fee(); //--- Get the final profit of the position and the spread values when opening and closing double profit=deal_out.Profit(); int spread_in=deal_in.Spread(); int spread_out=deal_out.Spread(); //--- If the reason for closing the position is StopLoss, TakeProfit or StopOut, set the reason description in the variable string reason=(deal_out.Reason()==DEAL_REASON_SL || deal_out.Reason()==DEAL_REASON_TP || deal_out.Reason()==DEAL_REASON_SO ? deal_out.ReasonDescription() : ""); //--- Create and return the tooltip string return(::StringFormat("%s\nCommission %.2f, Swap %.2f, Fee %.2f\nSpread In/Out %d/%d, Profit %+.2f %s (%d points)\nResult: %s %+.2f %s", this.Description(), commission, swap, fee, spread_in, spread_out, profit,this.m_currency_profit, this.ProfitInPoints(), reason, profit+commission+fee+swap, this.m_currency_profit)); }
In der Methode wird eine Zeichenkette wie folgt erstellt und zurückgegeben:
Position EURUSD 0.10 Buy #1752955040, Magic 0 Commission 0.00, Swap 0.00, Fee 0.00 Spread In/Out 0/0, Profit +15.00 USD (150 points) Result: TP +15.00 USD
Der Text wird als Tooltip-Text für die Zeichenkette festgelegt, die den Open- und Close-Trade einer Position verbindet. Die Methode ist virtuell und kann in abgeleiteten Klassen überschrieben werden, wenn es notwendig ist, etwas andere Daten auszugeben. Bei der Verwendung der von der Methode zurückgegebenen Zeichenkette als QuickInfo-Text ist die Länge der Zeichenkette zu berücksichtigen, da sie für eine QuickInfo auf etwa 160 Zeichen begrenzt ist, einschließlich Steuercodes. Leider kann ich den genauen Wert nicht nennen, da er empirisch ermittelt wurde.
Die Methode zur Anzeige von Deal-Angaben auf dem Chart:
//+------------------------------------------------------------------+ //| Display deal labels on the chart | //+------------------------------------------------------------------+ void CPosition::ShowDeals(const bool chart_redraw=false) { for(int i=this.m_list_deals.Total()-1; i>=0; i--) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; deal.ShowArrow(); } if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
In der Schleife durch die Liste der Handelsgeschäfte werden nacheinander die Deals abgerufen und die Methode ShowArrow() des empfangenen Objekts aufgerufen, die die Angaben des Handelsgeschäfts auf dem Chart anzeigt. Am Ende der Schleife wird das Chart neu gezeichnet, wenn das entsprechende Flag gesetzt ist.
Die Methode zum Ausblenden von Angaben im Chart:
//+------------------------------------------------------------------+ //| Hide deal labels on the chart | //+------------------------------------------------------------------+ void CPosition::HideDeals(const bool chart_redraw=false) { for(int i=this.m_list_deals.Total()-1; i>=0; i--) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; deal.HideArrow(); } if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
In einer Schleife durch die Liste der Handelsgeschäfte wird jedes aufeinanderfolgende Handelsgeschäft abgerufen und die Methode HideArrow() des empfangenen Objekts aufgerufen, wodurch die Beschriftung des Handelsgeschäfts aus dem Chart ausgeblendet wird. Am Ende der Schleife wird das Chart neu gezeichnet, wenn das entsprechende Flag gesetzt ist.
Die Methode, die die Verbindungslinie zwischen offenen und abgeschlossenen Handelsgeschäften herstellt:
//+------------------------------------------------------------------+ //| Create a line connecting open-close deals | //+------------------------------------------------------------------+ bool CPosition::CreateLine(void) { //--- If the graphical line object could not be created, report this in the journal and return 'false' ::ResetLastError(); if(!::ObjectCreate(this.m_chart_id, this.m_line_name, OBJ_TREND, 0, 0, 0, 0, 0)) { ::Print("ObjectCreate() failed. Error ", ::GetLastError()); return false; } //--- Hide the line this.HideLine(); //--- Set the line to be drawn with dots, define the color and return 'true' ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_STYLE, STYLE_DOT); ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_COLOR, this.m_line_color); return true; }
Es wird eine Linie mit Preis- und Zeitkoordinaten an der Nullposition erstellt (Preis ist 0, Zeit ist 01.01.1970 00:00:00). Wir blenden die Linie aus dem Chart aus und setzen den Zeichenstil auf Punkte und die Standardfarbe.
Zunächst werden die Linien eines jeden Objekts verdeckt erstellt. In dem Moment, in dem sie angezeigt werden müssen, werden die erforderlichen Koordinaten und die Anzeige für sie mit
Die Methode, die die Verbindungslinie zwischen den Handelsgeschäftsbezeichnungen anzeigt:
//+------------------------------------------------------------------+ //| Display the connecting line between deal labels | //+------------------------------------------------------------------+ void CPosition::ShowLine(const bool chart_redraw=false) { //--- Get the pointers to the open and close deals CDeal *deal_in= this.GetDealIn(); CDeal *deal_out=this.GetDealOut(); //--- If no deals are received, leave if(deal_in==NULL || deal_out==NULL) return; //--- Set a start and end time, a price from the deal properties and a tooltip text for the line ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIME, 0, deal_in.Time()); ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIME, 1, deal_out.Time()); ::ObjectSetDouble(this.m_chart_id, this.m_line_name, OBJPROP_PRICE, 0, deal_in.Price()); ::ObjectSetDouble(this.m_chart_id, this.m_line_name, OBJPROP_PRICE, 1, deal_out.Price()); ::ObjectSetString(this.m_chart_id, this.m_line_name, OBJPROP_TOOLTIP, this.Tooltip()); //--- Show the line on the chart and update it if the flag is set ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Die Methode verbirgt die Verbindungslinie zwischen den Handelsgeschäftsangaben:
//+------------------------------------------------------------------+ //| Hide the connecting line between the deal labels | //+------------------------------------------------------------------+ void CPosition::HideLine(const bool chart_redraw=false) { ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Das Sichtbarkeits-Flag für das Objekt wird in allen Chart-Zeitrahmen zurückgesetzt, und wenn das Flag gesetzt ist, wird das Chart aktualisiert.
Die Methode zur Einstellung der Farbe der Verbindungslinie:
//+------------------------------------------------------------------+ //| Set the color of the connecting line | //+------------------------------------------------------------------+ void CPosition::SetLineColor(const color clr=C'225,68,29') { if(::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_COLOR, clr)) this.m_line_color=clr; }
Der an die Methode übergebene Wert wird für das Objekt als Farbeigenschaft festgelegt. Wenn alles erfolgreich ist, wird die Farbe auf die Variable m_line_color gesetzt.
Die Methodeneinstellung Farbe des Kauf- und Verkaufsgeschäfts:
//+------------------------------------------------------------------+ //| Set Buy and Sell deal color | //+------------------------------------------------------------------+ void CPosition::SetDealsColor(const color clr_deal_buy=C'3,95,172', const color clr_deal_sell=C'225,68,29') { //--- In the loop by the list of deals int total=this.m_list_deals.Total(); for(int i=0; i<total; i++) { //--- get the next deal object CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; //--- In case of Buy deal type, set a color for a Buy deal for the object if(deal.TypeDeal()==DEAL_TYPE_BUY) deal.SetColorArrow(clr_deal_buy); //--- In case of Sell deal type, set a color for a Sell deal for the object if(deal.TypeDeal()==DEAL_TYPE_SELL) deal.SetColorArrow(clr_deal_sell); } }
Die Methode empfängt die Farben der Kauf- und Verkaufsgeschäfte. In der Schleife durch die Liste der Positionsgeschäfte holen Sie sich den Zeiger auf jedes nachfolgende Handelsgeschäft und setzen die Farbe für das Handelsgeschäftsetikett entsprechend seinem Typ.
Die Methode zeigt eine grafische Darstellung einer Position auf einem Chart an:
//+------------------------------------------------------------------+ //| Display a graphical representation of a position on a chart | //+------------------------------------------------------------------+ void CPosition::Show(const bool chart_redraw=false) { this.ShowDeals(false); this.ShowLine(chart_redraw); }
Die Methode ruft nacheinander geschützte Methoden auf, die Einstiegs- und Ausstiegsgeschäfte sowie eine Verbindungslinie auf dem Chart anzeigen.
Die Methode verbirgt eine grafische Darstellung einer Position auf einem Chart:
//+------------------------------------------------------------------+ //| Hide a graphical representation of a position on a chart | //+------------------------------------------------------------------+ void CPosition::Hide(const bool chart_redraw=false) { this.HideLine(false); this.HideDeals(chart_redraw); }
Die Methode ruft nacheinander geschützte Methoden auf, die Einstiegs- und Ausstiegsgeschäfte und eine Verbindungslinie zwischen ihnen auf dem Chart ausblenden.
Die beiden oben genannten Methoden sind öffentlich und werden verwendet, um die Anzeige einer Position auf dem Chart vom Kontrollprogramm aus zu steuern.
Die Methode, die die Positionseigenschaften und Handelsgeschäfte im Journal ausdruckt:
//+------------------------------------------------------------------+ //| Print the position properties and deals in the journal | //+------------------------------------------------------------------+ void CPosition::Print(void) { ::PrintFormat("%s\n-%s\n-%s", this.Description(), this.TimePriceOpenDescription(), this.TimePriceCloseDescription()); for(int i=0; i<this.m_list_deals.Total(); i++) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; deal.Print(); } }
Zunächst wird eine Kopfzeile mit einer kurzen Beschreibung der Position und zwei Zeilen mit dem Zeitpunkt und dem Preis der Eröffnung und Schließung der Position angezeigt. Anschließend werden die Beschreibungen aller Positionsgeschäfte aus der Liste in einer Schleife gedruckt.
Als Ergebnis erhalten wir die folgenden Daten in der Zeitschrift:
Position EURUSD 0.10 Sell #2523224572, Magic 0 -Opened 2024.05.31 17:06:15.134 [1.08734] -Closed 2024.05.31 17:33:17.772 [1.08639] Deal: Entry In 0.10 Sell #2497852906 at 2024.05.31 17:06:15.134 Deal: Entry Out 0.10 Buy #2497993663 at 2024.05.31 17:33:17.772
Dies ist nicht sehr informativ, aber die Print()-Methoden in diesen Klassen wurden nur zur Fehlersuche entwickelt.
Jetzt haben wir bereits zwei Klassen: die der Deals und die der Positionen, die eine Liste der Handelsgeschäfte enthält, die an der Laufzeit der Position beteiligt waren. Die Klassen sind einfach und enthalten grundlegende Informationen zu den Handelsgeschäften und einige zusätzliche Daten zu den Preisen und dem Zeitpunkt der Eröffnung/Schließung einer Position sowie deren Gewinn in Punkten. Die übrigen Methoden werden verwendet, um diese Informationen zu erhalten und in einem Chart oder einer Textzeile anzuzeigen.
Lassen Sie uns nun eine allgemeine Klasse erstellen, in der alle Positionen aus Handelsgeschäften gesammelt und in eine Liste historischer Positionen gestellt werden. Die Klasse ermöglicht den Zugriff auf die Eigenschaften der Positionen und ihrer Handelsgeschäfte, aktualisiert und ergänzt die Liste der historischen Positionen und zeigt die angegebene Position im Chart an.
Die Klasse für die Verwaltung historischer Positionen
Wir erstellen in demselben Ordner, in dem wir die beiden vorherigen Klassen erstellt haben, eine neue Datei PositionsControl.mqh der Klasse CPositionsControl.
Die Klasse sollte vom Basisobjekt der CObject Standard Library abgeleitet werden, während die Positionsklassendatei in die neue Klassendatei CPositionsControl aufgenommen wird:
//+------------------------------------------------------------------+ //| PositionsControl.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Position.mqh" //+------------------------------------------------------------------+ //| Class of historical positions | //+------------------------------------------------------------------+ class CPositionsControl : public CObject { }
Wir deklarieren die Variablen und Methoden zur Handhabung der Klasse in den Abschnitten „private“, „protected“ und „public“:
//+------------------------------------------------------------------+ //| Class of historical positions | //+------------------------------------------------------------------+ class CPositionsControl : public CObject { private: string m_symbol; // The symbol the position is open for long m_current_id; // ID of the current position displayed on the chart bool m_key_ctrl; // Flag for allowing to control the chart using the keyboard //--- Return the position type by deal type ENUM_POSITION_TYPE PositionTypeByDeal(const CDeal *deal); protected: CPosition m_temp_pos; // Temporary position object for searching CArrayObj m_list_pos; // List of positions long m_chart_id; // Chart ID //--- Return the position object from the list by ID CPosition *GetPositionObjByID(const long id); //--- Return the flag of the market position bool IsMarketPosition(const long id); //--- Return the pointer to the (1) first and the (2) last open position in the list CPosition *GetFirstClosedPosition(void); CPosition *GetLastClosedPosition(void); //--- Return the pointer to the (1) previous and (2) next closed position in the list CPosition *GetPrevClosedPosition(CPosition *current); CPosition *GetNextClosedPosition(CPosition *current); //--- Displays a graphical representation of the specified position on a chart void Show(CPosition *pos, const bool chart_redraw=false); //--- Center the chart on the currently selected position void CentersChartByCurrentSelected(void); //--- Return the ID of the current selected position long CurrentSelectedID(void) const { return this.m_current_id; } //--- Return the selected position (1) open and (2) close time datetime TimeOpenCurrentSelected(void); datetime TimeCloseCurrentSelected(void); //--- Hide the graphical representation of all positions on the chart except the specified one void HideAllExceptOne(const long pos_id, const bool chart_redraw=false); public: //--- Return the chart (1) symbol and (2) ID string Symbol(void) const { return this.m_symbol; } long ChartID(void) const { return this.m_chart_id; } //--- Create and update the list of positions. It can be redefined in the inherited classes virtual bool Refresh(void); //--- Return the number of positions in the list int Total(void) const { return this.m_list_pos.Total(); } //--- (1) Hide and (2) display the graphical representation of the first position on the chart void HideFirst(const bool chart_redraw=false); void ShowFirst(const bool chart_redraw=false); //--- (1) Hide and (2) display the graphical representation of the last position on the chart void HideLast(const bool chart_redraw=false); void ShowLast(const bool chart_redraw=false); //--- Display a graphical representation of the (1) current, (2) previous and (3) next position void ShowCurrent(const bool chart_redraw=false); void ShowPrev(const bool chart_redraw=false); void ShowNext(const bool chart_redraw=false); //--- Return the description of the currently selected position string CurrentSelectedDescription(void); //--- Print the properties of all positions and their deals in the journal void Print(void); //--- Constructor/destructor CPositionsControl(const string symbol=NULL); ~CPositionsControl(); };
Betrachten wir die deklarierten Methoden im Detail.
Der Konstruktor der Klasse:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPositionsControl::CPositionsControl(const string symbol=NULL) { this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); this.m_symbol = (symbol==NULL ? ::Symbol() : symbol); this.m_chart_id = ::ChartID(); this.m_current_id = 0; this.m_key_ctrl = ::ChartGetInteger(this.m_chart_id, CHART_KEYBOARD_CONTROL); }
Wir setzen das Flag für die Sortierung nach Schließzeit in Millisekunden für die Liste der historischen Positionen. Wir setzen das an den Konstruktor übergebene Symbol in die Variable. Wenn dies eine leere Zeichenkette ist, handelt es sich um das aktuelle Symbol dem Chart, auf dem das Programm läuft. Die Chart-ID wird als die aktuelle Chart-ID festgelegt. Es wird möglich sein, eine andere ID in den abgeleiteten Klassen festzulegen. In der Variablen m_key_ctrl setzen wir das Flag, das die Steuerung des Charts über Tasten erlaubt. Nach der Ausführung des Programms wird dieser Wert auf die Chart-Eigenschaft zurückgesetzt, um den Zustand vor dem Start des Programms wiederherzustellen.
Im Destruktor der Klasse wird die Liste der Positionen zerstört, während die Chart-Eigenschaft CHART_KEYBOARD_CONTROL den Wert zurückerhält, der vor dem Programmstart vorhanden war:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPositionsControl::~CPositionsControl() { this.m_list_pos.Shutdown(); ::ChartSetInteger(this.m_chart_id, CHART_KEYBOARD_CONTROL, this.m_key_ctrl); }
Die Methode, die ein Positionsobjekt aus der Liste nach ID zurückgibt:
//+------------------------------------------------------------------+ //| Return the position object from the list by ID | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetPositionObjByID(const long id) { //--- Set the position ID for the temporary object and set the flag of sorting by position ID for the list this.m_temp_pos.SetID(id); this.m_list_pos.Sort(SORT_MODE_POSITION_IDENTIFIER); //--- Get the index of the position object with such an ID (or -1 if it is absent) from the list //--- Use the obtained index to get the pointer to the positino object from the list (or NULL if the index value is -1) int index=this.m_list_pos.Search(&this.m_temp_pos); CPosition *pos=this.m_list_pos.At(index); //--- Set the flag of sorting by position close time in milliseconds for the list and //--- return the pointer to the position object (or NULL if it is absent) this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); return pos; }
Die Methodenlogik wird in den Codekommentaren beschrieben.
Die Methode, die das Flag der Marktposition:
//+------------------------------------------------------------------+ //| Return the market position flag | //+------------------------------------------------------------------+ bool CPositionsControl::IsMarketPosition(const long id) { //--- In a loop by the list of current positions in the terminal for(int i=::PositionsTotal()-1; i>=0; i--) { //--- get the position ticket by the loop index ulong ticket=::PositionGetTicket(i); //--- If the ticket is received, the position can be selected and its ID is equal to the one passed to the method, //--- this is the desired market position, return 'true' if(ticket!=0 && ::PositionSelectByTicket(ticket) && ::PositionGetInteger(POSITION_IDENTIFIER)==id) return true; } //--- No such market position, return 'false' return false; }
Bei der Erstellung einer Positionsliste aus einer Liste von Handelsgeschäften ist es wichtig, die aktuellen Marktpositionen nicht zu berücksichtigen und sie nicht der Liste der historischen Positionen hinzuzufügen. Um zu verstehen, dass eine Position noch nicht geschlossen ist, müssen wir sie in der Liste der aktiven Positionen anhand ihrer ID finden. Wenn eine solche Stelle existiert, soll sie nicht in die Liste aufgenommen werden. Die Methode sucht nach Positionen in der Liste der Marktpositionen anhand ihres Tickets, prüft, ob die Positions-ID mit dem an die Methode übergebenen Wert übereinstimmt, und gibt, wenn eine solche Position existiert (sie kann ausgewählt werden), den Wert true zurück. Andernfalls wird false zurückgegeben.
Die Methode liefert den Zeiger auf die erste geschlossene Position in der Liste:
//+------------------------------------------------------------------+ //| Return the pointer to the first closed position in the list | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetFirstClosedPosition(void) { this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); return this.m_list_pos.At(0); }
Die Liste der Positionen wird nach der Positionsschließzeit in Millisekunden sortiert, und es wird ein Zeiger auf die erste Position (die älteste) in der Liste zurückgegeben.
Die Methode, die den Zeiger auf die letzte geschlossene Position in der Liste zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the last closed position in the list | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetLastClosedPosition(void) { this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); return this.m_list_pos.At(this.m_list_pos.Total()-1); }
Die Liste der Positionen wird nach der Positionsschließzeit in Millisekunden sortiert, und es wird ein Zeiger auf die letzte Position in der Liste zurückgegeben.
Die Methode, die den Zeiger auf die vorherige geschlossene Position in der Liste:
//+------------------------------------------------------------------+ //| Return the pointer to the previous closed position in the list | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetPrevClosedPosition(CPosition *current) { this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); int prev=this.m_list_pos.SearchLess(current); return this.m_list_pos.At(prev); }
Ausgehend von der aktuell gewählten Position, deren Zeiger an die Methode übergeben wird, wird die vorherige Position in der Liste gesucht, sortiert nach der Schließzeit in Millisekunden. Die Methode SearchLess() der Klasse CArrayObj gibt den Zeiger auf das erste in der Liste gefundene Objekt zurück, das den niedrigeren Wert hat, nach dem die Liste sortiert ist. In diesem Fall wird die Liste nach der Schließzeit in Millisekunden sortiert. Dementsprechend ist das erste gefundene Objekt, dessen Schließzeit kleiner ist als die an die Methode übergebene, die vorherige Position. Wenn das Positionsobjekt nicht gefunden wird oder das allererste Element der Liste an die Methode übergeben wird (es gibt keine vorherigen Elemente), gibt die Methode NULL zurück.
Die Methode liefert den Zeiger auf die nächste geschlossene Position in der Liste:
//+------------------------------------------------------------------+ //| Return the pointer to the next closed position in the list | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetNextClosedPosition(CPosition *current) { this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); int next=this.m_list_pos.SearchGreat(current); return this.m_list_pos.At(next); }
Ausgehend von der aktuell gewählten Position, deren Zeiger an die Methode übergeben wird, wird die nächste Position in der Liste gesucht, sortiert nach der Schließzeit in Millisekunden. Die Methode SearchGreat() der Klasse CArrayObj liefert den Zeiger auf das erste in der Liste gefundene Objekt mit dem höheren Wert, nach dem die Liste sortiert ist. In diesem Fall wird die Liste nach der Schließzeit in Millisekunden sortiert. Dementsprechend ist das erste gefundene Objekt mit einer Schließzeit, die größer ist als die an die Methode übergebene, die nächste Position. Wenn das Positionsobjekt nicht gefunden wird oder das letzte Element der Liste an die Methode übergeben wird (es gibt keine weiteren Elemente), gibt die Methode NULL zurück.
Die Methode, die eine grafische Darstellung einer bestimmten Position im Chart anzeigt:
//+----------------------------------------------------------------------------+ //| Display a graphical representation of the specified position on the chart | //+----------------------------------------------------------------------------+ void CPositionsControl::Show(CPosition *pos,const bool chart_redraw=false) { if(pos!=NULL) { pos.Show(chart_redraw); this.m_current_id=pos.ID(); } }
Die Methode erhält den Zeiger auf die Position, deren grafische Darstellung auf dem Chart angezeigt werden soll. Wenn ein gültiges Objekt empfangen wird, rufen wir dessen Methode Show() auf und fügen die Positions-ID der Variablen m_current_id hinzu. Wir können die aktuell ausgewählte Position durch die Positions-ID in der Variablen m_current_id definieren. Eine Position gilt als ausgewählt, wenn ihre grafische Darstellung im Chart angezeigt wird.
Die Methode verbirgt die grafische Darstellung der ersten Position in der Tabelle:
//+------------------------------------------------------------------------+ //| Hide the graphical representation of the first position from the chart | //+------------------------------------------------------------------------+ void CPositionsControl::HideFirst(const bool chart_redraw=false) { CPosition *pos=this.GetFirstClosedPosition(); if(pos!=NULL) pos.Hide(chart_redraw); }
Es wird der Zeiger auf die erste Position in der Liste ermittelt und die Methode Hide() aufgerufen.
Die Methode, die eine grafische Darstellung der ersten Position auf dem Chart anzeigt:
//+---------------------------------------------------------------------------+ //| Display the graphical representation of the first position on the chart | //+---------------------------------------------------------------------------+ void CPositionsControl::ShowFirst(const bool chart_redraw=false) { //--- Get the pointer to the first closed position CPosition *pos=this.GetFirstClosedPosition(); if(pos==NULL) return; //--- Hide labels of all positions except the first one by its ID this.HideAllExceptOne(pos.ID()); //--- Display the labels of the first position on the chart and //--- center the chart by the labels of the currently selected position this.Show(pos,chart_redraw); this.CentersChartByCurrentSelected(); }
Wir verwenden die oben genannte Methode GetFirstClosedPosition(), um den Zeiger auf die erste Position in der Liste zu erhalten, sortiert nach der Zeit in Millisekunden. Es werden alle Angaben aller Positionen außer der ersten ausgeblendet, die erste Position angezeigt und das Chart auf die Angaben seiner Abschlüsse im Chart zentriert.
Die Methode verbirgt die grafische Darstellung der letzten Position auf dem Chart:
//+------------------------------------------------------------------+ //| Hide graphical representation of the last position from the chart| //+------------------------------------------------------------------+ void CPositionsControl::HideLast(const bool chart_redraw=false) { CPosition *pos=this.GetLastClosedPosition(); if(pos!=NULL) pos.Hide(chart_redraw); }
Ermitteln des Zeigers auf die letzte Position in der Liste und Aufruf der Methode Hide().
Die Methode, die eine grafische Darstellung der letzten Position auf dem Chart anzeigt:
//+-----------------------------------------------------------------------+ //| Display the graphical representation of the last position on the chart| //+-----------------------------------------------------------------------+ void CPositionsControl::ShowLast(const bool chart_redraw=false) { //--- Get the pointer to the last closed position CPosition *pos=this.GetLastClosedPosition(); if(pos==NULL) return; //--- Hide labels of all positions except the last one by its ID this.HideAllExceptOne(pos.ID(), false); //--- Display the labels of the last position on the chart and //--- center the chart by the labels of the currently selected position this.Show(pos,chart_redraw); this.CentersChartByCurrentSelected(); }
Wir verwenden die oben genannte Methode GetLastClosedPosition(), um den Zeiger auf die letzte Position in der Liste zu erhalten, sortiert nach der Zeit in Millisekunden. Es werden alle Angaben aller Positionen außer der letzten ausgeblendet, die letzte Position angezeigt und das Chart auf die Angaben seiner Abschlüsse im Chart zentriert.
Die Methode, die eine grafische Darstellung der aktuellen Position im Chart anzeigt:
//+--------------------------------------------------------------------------+ //| Display a graphical representation of the current position on the chart | //+--------------------------------------------------------------------------+ void CPositionsControl::ShowCurrent(const bool chart_redraw=false) { //--- Get a pointer to the currently selected closed position CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID()); if(curr==NULL) return; //--- Display the labels of the current position on the chart and //--- center the chart by the labels of the currently selected position this.Show(curr,chart_redraw); this.CentersChartByCurrentSelected(); }
Ermittelt den Zeiger auf eine Position, deren ID in der Variablen m_current_id festgelegt ist, zeigt deren grafische Darstellung im Chart an und zentriert das Chart anhand der Angaben, die im Chart enthalten sind.
Die Methode, die eine grafische Darstellung der Position vorher im Chart anzeigt:
//+------------------------------------------------------------------------+ //|Display a graphical representation of the previous position on the chart| //+------------------------------------------------------------------------+ void CPositionsControl::ShowPrev(const bool chart_redraw=false) { //--- Get the pointer to the current and previous positions CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID()); CPosition *prev=this.GetPrevClosedPosition(curr); if(curr==NULL || prev==NULL) return; //--- Hide the current position, display the previous one and //--- center the chart by the labels of the currently selected position curr.Hide(); this.Show(prev,chart_redraw); this.CentersChartByCurrentSelected(); }
Hier erhalten wir die Zeiger auf die aktuell ausgewählte und die vorherige Position in der Liste. Ausgeblendet wird die Angaben der aktuellen Position und die Angaben der Position vorher angezeigt. Wenn sie angezeigt werden, wird die Positions-ID auf die Variable m_current_id gesetzt, was bedeutet, dass diese Position nun die aktuelle ist. Wir verwenden die Angaben, um das Chart zu zentrieren.
Die Methode zeigt eine grafische Darstellung der nächsten Position auf dem Chart an:
//+---------------------------------------------------------------------+ //| Display a graphical representation of the next position on the chart| //+---------------------------------------------------------------------+ void CPositionsControl::ShowNext(const bool chart_redraw=false) { //--- Get the pointer to the current and next positions CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID()); CPosition *next=this.GetNextClosedPosition(curr); if(curr==NULL || next==NULL) return; //--- Hide the current position, display the next one and //--- center the chart by the labels of the currently selected position curr.Hide(); this.Show(next,chart_redraw); this.CentersChartByCurrentSelected(); }
Die Methode ist identisch mit der vorherigen, nur dass wir hier die Zeiger auf die aktuell ausgewählte und die nächste Position in der Liste erhalten. Blendet die Angaben der aktuellen Position aus und zeigt die Symbole der nächsten Position an. Wenn sie angezeigt werden, wird die Positions-ID auf die Variable m_current_id gesetzt, was bedeutet, dass diese Position nun die aktuelle ist. Wir verwenden die Angaben, um das Chart zu zentrieren.
Die Methode verbirgt die grafische Darstellung aller Positionen im Chart mit Ausnahme der angegebenen Position:
//+------------------------------------------------------------------+ //| Hide the graphical representation | //| of all positions except the specified one | //+------------------------------------------------------------------+ void CPositionsControl::HideAllExceptOne(const long pos_id,const bool chart_redraw=false) { //--- In a loop by the list of positions int total=this.m_list_pos.Total(); for(int i=0; i<total; i++) { //--- get the pointer to the next position and CPosition *pos=this.m_list_pos.At(i); if(pos==NULL || pos.ID()==pos_id) continue; //--- hide the graphical representation of the position pos.Hide(); } //--- After the loop, update the chart if the flag is set if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Die Methode erhält die ID der Position, deren Angaben auf dem Chart verbleiben sollen. Die Angaben für alle anderen Positionen sind ausgeblendet.
Die Methode, die das Chart an der aktuell ausgewählten Position zentriert:
//+------------------------------------------------------------------+ //| Center the chart at the currently selected position | //+------------------------------------------------------------------+ void CPositionsControl::CentersChartByCurrentSelected(void) { //--- Get the index of the first visible bar on the chart and the number of visible bars int bar_open=0, bar_close=0; int first_visible=(int)::ChartGetInteger(this.m_chart_id, CHART_FIRST_VISIBLE_BAR); int visible_bars =(int)::ChartGetInteger(this.m_chart_id, CHART_VISIBLE_BARS); //--- Get the position opening time and use it to get the opening bar datetime time_open=this.TimeOpenCurrentSelected(); if(time_open!=0) bar_open=::iBarShift(this.m_symbol, PERIOD_CURRENT, time_open); //--- Get the position opening time and use it to get the closing bar datetime time_close=this.TimeCloseCurrentSelected(); if(time_close!=0) bar_close=::iBarShift(this.m_symbol, PERIOD_CURRENT, time_close); //--- Calculate the width of the window the deal labels are located in int width=bar_open-bar_close; //--- Calculate the chart offset so that the window with deals is in the center of the chart int shift=(bar_open + visible_bars/2 - width/2); //--- If the window width is greater than the chart width, the opening deal is located on the second visible bar if(shift-bar_open<0) shift=bar_open+1; //--- If the deal opening bar is to the left of the first visible bar of the chart //--- or the deal opening bar is to the right of the chart last visible bar, //--- scroll the chart by the calculated offset if(bar_open>first_visible || bar_open<first_visible+visible_bars) ::ChartNavigate(this.m_chart_id, CHART_CURRENT_POS, first_visible-shift); }
Die gesamte Logik der Methode ist in den Kommentaren zum Code ausführlich beschrieben. Die Methode verschiebt das Symbol-Chart so, dass sich visuell alle Positionsgeschäfte mit der Verbindungslinie in der Mitte des Charts befinden. Wenn die Angaben aller Handelsgeschäfte nicht in die Chart-Breite passen, wird das Chart so verschoben, dass die Eröffnung der Position auf dem zweiten sichtbaren Balken des Charts von links liegt.
Die Methode, die die Öffnungszeit der aktuell ausgewählten Position zurückgibt:
//+------------------------------------------------------------------+ //| Return the opening time of the currently selected position | //+------------------------------------------------------------------+ datetime CPositionsControl::TimeOpenCurrentSelected(void) { CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID()); return(pos!=NULL ? pos.Time() : 0); }
Ermittelt den Zeiger auf die aktuell ausgewählte Position anhand der in der Variablen m_current_id festgelegten ID. Wenn der Zeiger erfolgreich ermittelt wurde, wird die Eröffnungszeit der Position zurückgegeben. Andernfalls wird Null zurückgegeben.
Die Methode liefert die Schließzeit der aktuell ausgewählten Position:
//+------------------------------------------------------------------+ //| Return the current selected position closing time | //+------------------------------------------------------------------+ datetime CPositionsControl::TimeCloseCurrentSelected(void) { CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID()); return(pos!=NULL ? pos.TimeClose() : 0); }
Ermittelt den Zeiger auf die aktuell ausgewählte Position anhand der in der Variablen m_current_id festgelegten ID. Wenn der Zeiger erfolgreich empfangen wurde, wird die Positionsschlusszeit zurückgegeben. Andernfalls wird Null zurückgegeben.
Die Methode liefert den Typ einer Position aus der Art des Deals:
//+------------------------------------------------------------------+ //| Return position type by deal type | //+------------------------------------------------------------------+ ENUM_POSITION_TYPE CPositionsControl::PositionTypeByDeal(const CDeal *deal) { if(deal==NULL) return WRONG_VALUE; switch(deal.TypeDeal()) { case DEAL_TYPE_BUY : return POSITION_TYPE_BUY; case DEAL_TYPE_SELL : return POSITION_TYPE_SELL; default : return WRONG_VALUE; } }
Die Methode erhält den Zeiger auf das Handelsgeschäft. Je nach Handelsgeschäftsart wird der entsprechende Positionstyp zurückgegeben.
Die Methode zur Erstellung der Liste der historischen Positionen:
//+------------------------------------------------------------------+ //| Create historical position list | //+------------------------------------------------------------------+ bool CPositionsControl::Refresh(void) { //--- If failed to request the history of deals and orders, return 'false' if(!::HistorySelect(0,::TimeCurrent())) return false; //--- Set the flag of sorting by time in milliseconds for the position list this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_MSC); //--- Declare a result variable and a pointer to the position object bool res=true; CPosition *pos=NULL; //--- In a loop based on the number of history deals int total=::HistoryDealsTotal(); for(int i=total-1; i>=0; i--) { //--- get the ticket of the next deal in the list ulong ticket=::HistoryDealGetTicket(i); //--- If the deal ticket is not received, or it is not a buy/sell deal, or if the deal is not for the symbol set for the class, move on ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE); if(ticket==0 || (deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL) || ::HistoryDealGetString(ticket, DEAL_SYMBOL)!=this.m_symbol) continue; //--- Get the value of the position ID from the deal long pos_id=::HistoryDealGetInteger(ticket, DEAL_POSITION_ID); //--- If this is a market position, move on if(this.IsMarketPosition(pos_id)) continue; //--- Get the pointer to a position object from the list pos=this.GetPositionObjByID(pos_id); //--- If there is no position with this ID in the list yet if(pos==NULL) { //--- Create a new position object and, if the object could not be created, add 'false' to the 'res' variable and move on pos=new CPosition(pos_id, this.m_symbol); if(pos==NULL) { res &=false; continue; } //--- If failed to add the position object to the list, add 'false' to the 'res' variable, remove the position object and move on if(!this.m_list_pos.InsertSort(pos)) { res &=false; delete pos; continue; } } //--- If the deal object could not be added to the list of deals of the position object, add 'false' to the 'res' variable and move on CDeal *deal=pos.DealAdd(ticket); if(deal==NULL) { res &=false; continue; } //--- All is successful. //--- Set position properties depending on the deal type if(deal.Entry()==DEAL_ENTRY_IN) { pos.SetTime(deal.Time()); pos.SetTimeMsc(deal.TimeMsc()); ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); pos.SetPriceOpen(deal.Price()); pos.SetVolume(deal.Volume()); } if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { pos.SetPriceCurrent(deal.Price()); } if(deal.Entry()==DEAL_ENTRY_INOUT) { ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); pos.SetVolume(deal.Volume()-pos.Volume()); } } //--- Set the flag of sorting by close time in milliseconds for the position list this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC); //--- Return the result of creating and adding a position to the list return res; }
Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben.
Werfen wir einen kurzen Blick auf die Logik der Suche nach historischen Positionen: Das Client-Terminal enthält nur eine Liste der aktuellen Positionen. Jede Position hat ihre eigenen Handelsgeschäfte, die in der Liste der historischen Handelsgeschäfte aufgeführt sind. Jedes Handelsgeschäft enthält eine ID der Position, an der es beteiligt war. Anhand dieser ID kann eine Position bestimmt werden, zu der ein Handelsgeschäft gehört. Um eine Liste historischer Positionen zu erstellen, müssen wir also eine Schleife durch die Liste historischer Handelsgeschäfte ziehen und die ID verwenden, um die Position zu bestimmen, zu der das Handelsgeschäft gehört. Vergewissern Sie sich, dass eine Stelle mit einer solchen ID nicht in der Liste der aktiven Stellen enthalten ist. Ist dies der Fall, handelt es sich noch nicht um eine geschlossene Position, und das Handelsgeschäft sollte übersprungen werden. Wenn eine solche Position nicht mehr auf dem Markt ist, müssen wir ein neues historisches Positionsobjekt mit einer solchen ID erstellen und dieses Handelsgeschäft zur Liste der Handelsgeschäfte der erstellten Position hinzufügen. Bevor wir ein Positionsobjekt erstellen, müssen wir prüfen, ob eine solche Position bereits zuvor erstellt wurde. Wenn ja, ist es nicht notwendig, ein Positionsobjekt zu erstellen. Stattdessen fügen wir dieses Handelsgeschäft einfach der Liste der bereits erstellten Positionen hinzu. Wenn ein solches Handelsgeschäft bereits in der Positionsliste enthalten ist, muss es natürlich nicht hinzugefügt werden. Nach Abschluss des Zyklus der historischen Transaktionen wird als Ergebnis eine Liste der bereits geschlossenen Positionen erstellt, und jede Position in dieser Liste enthält eine Liste der zugehörigen Handelsgeschäfte.
Die Methode, die die Eigenschaften der Positionen und ihre Handelsgeschäfte im Journal ausgibt:
//+------------------------------------------------------------------+ //| Print the properties of positions and their deals in the journal | //+------------------------------------------------------------------+ void CPositionsControl::Print(void) { int total=this.m_list_pos.Total(); for(int i=0; i<total; i++) { CPosition *pos=this.m_list_pos.At(i); if(pos==NULL) continue; pos.Print(); } }
Beginnend mit der frühesten Position (ganz am Anfang der Liste) wird jede nachfolgende Position ermittelt und ihre Beschreibung in einer Schleife an das Journal ausgegeben.
Die Methode liefert die Beschreibung jeder ausgewählten Position:
//+------------------------------------------------------------------+ //| Return the description of the currently selected position | //+------------------------------------------------------------------+ string CPositionsControl::CurrentSelectedDescription(void) { CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID()); return(pos!=NULL ? pos.Tooltip() : NULL); }
Ermittelt den Zeiger auf die aktuell ausgewählte Position, deren ID in der Variablen m_current_id festgelegt ist, und gibt die Zeichenkette zurück, die zur Anzeige des Tooltips erstellt wurde. Diese Zeichenfolge kann verwendet werden, um sie in einem Chart-Kommentar anzuzeigen. Zeigen wir es im Test-EA für eine visuelle Anzeige einiger Eigenschaften der Position, deren Angaben im Chart angezeigt werden. Der Test-EA wird die folgenden Funktionen enthalten:
- Beim Start wird ein Handelsverlauf in Form einer Liste historischer Positionen erstellt. Jede Position enthält die Liste der zugehörigen Handelsgeschäfte;
- Sobald die Liste der historischen Positionen vollständig ist, wird die letzte geschlossene Position im Chart als offene und geschlossene Markierungen angezeigt, die durch eine Linie verbunden sind;
- Sie können mit den Cursortasten durch die Liste der historischen Positionen navigieren, während Sie die Strg-Taste gedrückt halten:
- Linke Taste - Anzeige der vorherigen Position auf dem Chart;
- Rechte Taste - Anzeige der nächsten Position auf dem Chart;
- Nach-oben-Taste - Anzeige der ersten Position auf dem Chart;
- Abwärts-Taste - Anzeige der letzten Position auf dem Chart;
- Halten Sie die Umschalttaste gedrückt, um einen Kommentar auf dem Chart anzuzeigen, der die aktuell ausgewählte Position in der Liste beschreibt.
Test
Erstellen Sie in \MQL5\Experts\PositionsViewer\ eine neue EA-Datei PositionViewer.mq5.
Binden Sie die Datei der historischen Positionsführungsklasse ein, deklarieren Sie Makrosubstitutionen für die Schlüsselcodes und EA-Globalvariablen:
//+------------------------------------------------------------------+ //| PositionViewer.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <PositionsViewer\PositionsControl.mqh> #define KEY_LEFT 37 #define KEY_RIGHT 39 #define KEY_UP 38 #define KEY_DOWN 40 //--- global variables CPositionsControl ExtPositions; // Historical position class instance bool ExtChartScroll; // Chart scrolling flag bool ExtChartHistory; // Trading history display flag //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+
Setzen Sie in OnInit() von EA den folgenden Code:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Save the chart auto scroll flag and disable auto scroll ExtChartScroll=ChartGetInteger(ChartID(), CHART_AUTOSCROLL); ChartSetInteger(ChartID(), CHART_AUTOSCROLL, false); //--- Save the trading history display flag and disable history display ExtChartHistory=ChartGetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY); ChartSetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY, false); //--- Create a list of closed positions and display the list creation time in the journal ulong start=GetTickCount64(); Print("Reading trade history and creating a list of historical positions"); ExtPositions.Refresh(); ulong msec=GetTickCount64()-start; PrintFormat("List of historical positions created in %I64u msec", msec); //ExtPositions.Print(); //--- If this is a launch after changing the chart period, display the currently selected position if(UninitializeReason()==REASON_CHARTCHANGE) ExtPositions.ShowCurrent(true); //--- otherwise, display the last closed position else ExtPositions.ShowLast(true); //--- Successful return(INIT_SUCCEEDED); }
Die Flags für das automatische Scrollen des Charts und die Anzeige der Handelshistorie werden hier gespeichert, um sie beim Beenden des Programms wiederherzustellen. Das automatische Scrollen des Charts und die Anzeige der Historie sind deaktiviert. Es wird eine Liste aller historischen Positionen erstellt, die jemals für das aktuelle Symbol existierten, und der Zeitpunkt der Listenerstellung wird im Journal angezeigt. Wenn wir die Zeichenkette //ExtPositions.Print(); auskommentieren, nachdem wir die Liste der geschlossenen Positionen erstellt haben, werden alle historischen Positionen aus der erstellten Liste im Journal angezeigt. Wenn es sich nicht um eine Änderung des Chart-Zeitrahmens handelt, werden die Angaben der letzten geschlossenen Position auf dem Chart gezeichnet, und der Chart wird so gescrollt, dass sich die Angaben in seiner Mitte befinden. Handelt es sich um eine Änderung des Chart-Zeitrahmens, so wird die aktuell ausgewählte Position in der Liste angezeigt.
Stellen Sie in OnDeinit() die gespeicherten Werte für den automatischen Chart-Scroll und die Anzeige der Handelshistorie wieder her und entfernen Sie Kommentare aus dem Chart:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Restore the auto scroll and trading history property initial value and remove chart comments ChartSetInteger(ChartID(), CHART_AUTOSCROLL, ExtChartScroll); ChartSetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY, ExtChartHistory); Comment(""); }
In OnTradeTransaction() rufen Sie die Methode zur Aktualisierung der Liste der Kontrollklasse für historische Positionen jedes Mal auf, wenn ein neues Handelsgeschäft stattfindet. Wenn die Position geschlossen ist, werden ihre Angaben im Chart angezeigt:
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { //--- In case of a transaction type, add a new deal if(trans.type==TRADE_TRANSACTION_DEAL_ADD) { //--- update the list of positions and their deals ExtPositions.Refresh(); //--- Get the new deal ticket ulong deal_ticket=trans.deal; //--- If the ticket is not received or failed to get the method for updating the position from the deal properties, leave long entry; if(deal_ticket==0 || !HistoryDealGetInteger(deal_ticket, DEAL_ENTRY, entry)) return; //--- If this is an exit deal, display the last closed position on the chart if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY) ExtPositions.ShowLast(true); } }
In der Ereignishandhabung wird auf das Drücken von Tasten auf Cursortasten reagiert und bei gedrückter Strg-Taste kann durch die Liste der geschlossenen Positionen navigiert werden. Es wird auch auf die Umschalttaste reagiert, um eine Positionsangaben im Chart-Kommentar anzuzeigen, und es wird der Chart-Maßstabs geändert, um das Chart anhand der aktuellen Positionsangaben zu zentrieren:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If the event ID is pressing a key if(id==CHARTEVENT_KEYDOWN) { //--- If the Ctrl key is held down if(TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0) { //--- If the chart scrolling with keys is active, disable it if((bool)ChartGetInteger(0, CHART_KEYBOARD_CONTROL)) ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false); //--- If the left key is pressed, display the previous closed position if(lparam==KEY_LEFT) ExtPositions.ShowPrev(true); //--- If the right key is pressed, display the next closed position if(lparam==KEY_RIGHT) ExtPositions.ShowNext(true); //--- If the up key is pressed, display the first closed position if(lparam==KEY_UP) ExtPositions.ShowFirst(true); //--- If the down key is pressed, display the last closed position if(lparam==KEY_DOWN) ExtPositions.ShowLast(true); } //--- If Ctrl is not pressed, else { //--- If the chart scrolling with keys is inactive, enable it if(!(bool)ChartGetInteger(0, CHART_KEYBOARD_CONTROL)) ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true); } } //--- If the Shift key is held down, display a description of the current position in the chart comment if(TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)<0) Comment(ExtPositions.CurrentSelectedDescription()); //--- If the Shift key is not pressed, check the comment on the chart and delete it if it is not empty else { if(ChartGetString(ChartID(),CHART_COMMENT)!=NULL) Comment(""); } //--- If the horizontal scale of the chart has changed, display the currently selected position static int scale=-1; if(id==CHARTEVENT_CHART_CHANGE) { int scale_curr=(int)ChartGetInteger(ChartID(), CHART_SCALE); if(scale!=scale_curr) { ExtPositions.ShowCurrent(true); scale=scale_curr; } } }
So sehen die geschlossenen Positionen mit einer Standardanzeige der Handelshistorie aus:
Dies ist ein gutes Beispiel für das, was ganz am Anfang des Artikels geschrieben wurde:
Jetzt kompilieren wir den EA und starten ihn auf dem Chart:
Beim Start wurde die standardmäßig angezeigte Historie ausgeblendet und nur die letzte geschlossene Position blieb sichtbar. Wir können durch die Historie der Positionen navigieren, indem wir die Strg-Taste gedrückt halten und die Cursortasten drücken. Wenn man sich durch die Liste der geschlossenen Positionen bewegt, kann man leicht eine grafische Darstellung der Handelsgeschichte an der Stelle sehen, an der das Chart mit den Bezeichnungen aller Handelsgeschäfte der hier geschlossenen Positionen gefüllt wurde.
Wenn wir außerdem die Umschalttaste gedrückt halten, wird im Chart-Kommentar eine Beschreibung der aktuell ausgewählten geschlossenen Position angezeigt.
Suchen wir nun eine Position, deren Transaktionen in ausreichendem Abstand zueinander liegen, und sehen wir uns an, wie sie im Chart zentriert werden, wenn sie nicht in einen sichtbaren Bereich des Chartfensters passen, wenn dessen horizontale Skala vergrößert wird:
Wenn beide Handelsgeschäfte nicht in das Chart-Fenster passen, wird es so zentriert, dass das Handelsgeschäft, das an der Position des zweiten Balkens von links eintritt, sichtbar ist.
Wenn wir die Umschalttaste drücken, sehen wir eine Beschreibung dieser geschlossenen Position im Chart-Kommentar:
Das ist praktisch, weil wir so eine Beschreibung der Position sehen können, ohne mit dem Mauszeiger über die Verbindungslinie zwischen den Handelsgeschäften fahren zu müssen. Wenn Sie sich mit den Cursortasten durch die Liste der geschlossenen Positionen bewegen und dabei die Tasten Strg + Umschalt gedrückt halten, können Sie die Beschreibung der aktuell im Chart angezeigten geschlossenen Position deutlich erkennen.
Wenn Sie den EA starten, wird die Liste der historischen Positionen erstellt. In meinem Fall mit einer kurzen Historie ab 2023, dem Zeitpunkt der Erstellung der Liste:
PositionViewer (EURUSD,M15) Reading trade history and creating a list of historical positions
PositionViewer (EURUSD,M15) List of historical positions created in 6422 msec
Ein späterer Wechsel der Chart-Zeitrahmen erfordert keine Zeit mehr, um die Liste neu zu erstellen:
PositionViewer (EURUSD,M1) Reading trade history and creating a list of historical positions PositionViewer (EURUSD,M1) List of historical positions created in 31 msec PositionViewer (EURUSD,M5) Reading trade history and creating a list of historical positions PositionViewer (EURUSD,M5) List of historical positions created in 47 msec PositionViewer (EURUSD,M1) Reading trade history and creating a list of historical positions PositionViewer (EURUSD,M1) List of historical positions created in 31 msec
Schlussfolgerung
Die Handelsgeschichte, die durch das erstellte Programm zur Verfügung gestellt wird, ist im Falle eines aktiven und häufigen Handels bequemer - das Chart ist nie mit Angaben zu den geschlossenen Positionen überladen - jede Position wird unabhängig angezeigt, und das Umschalten zwischen der Anzeige der aktuellen und der nächsten oder vorherigen Position erfolgt mit den Cursortasten. Jedes Handelsgeschäftsetikett enthält in seinem Tooltip mehr Informationen als der Standard-Handelsverlauf. Wenn Sie den Mauszeiger über die Verbindungslinie zwischen den Handelsgeschäften bewegen, wird ein Tooltip mit Informationen über die geschlossene Position angezeigt.
Die in diesem Artikel erstellten Klassen können in unseren eigenen Entwicklungen verwendet werden, um die Funktionalität zu erweitern und komplexere und funktionellere Programme mit Informationen über die Handelshistorie und die Historie der geschlossenen Positionen zu erstellen.
Alle Unterrichtsdateien und der Test EA sind dem Artikel beigefügt und stehen für das Selbststudium zur Verfügung. Das Archiv MQL5.zip enthält die Dateien so, dass sie sofort in das MQL5-Terminalverzeichnis entpackt werden können, und der neue Ordner PositionsViewer\ wird im Ordner Experts zusammen mit allen Projektdateien erstellt: die EA-Datei PositionViewer.mq5 und drei enthaltene Klassendateien. Es steht uns frei, die EA-Datei einfach zu kompilieren und zu verwenden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15026





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.