
Überwachung des Handels mit Push-Benachrichtigungen — Beispiel für einen MetaTrader 5 Dienst
Inhalt
- Einführung
- Projektstruktur
- Die Klasse Deal
- Historische Positionsklasse
- Klasse zum Suchen und Sortieren nach Eigenschaften von Deals und Positionen
- Klassensammlung von historischen Positionen
- Konto-Klasse
- Klassensammlung von Konten
- Service-App zur Erstellung von Handelsberichten und zum Versand von Benachrichtigungen
- Schlussfolgerung
Einführung
Eine wichtige Komponente beim Handel auf den Finanzmärkten ist die Verfügbarkeit von Informationen über die Ergebnisse von Geschäften, die in einem bestimmten Zeitraum in der Vergangenheit getätigt wurden.
Wahrscheinlich war jeder Händler schon einmal mit der Notwendigkeit konfrontiert, die Handelsergebnisse des vergangenen Tages, der vergangenen Woche, des vergangenen Monats usw. zu überwachen, um seine Strategie auf der Grundlage der Handelsergebnisse anzupassen. Das MetaTrader 5 Kundenterminal bietet Statistiken in Form von Berichten an, die es uns ermöglichen, die Handelsergebnisse in einer bequemen visuellen Form zu bewerten. Der Bericht kann uns helfen, unser Portfolio zu optimieren und zu verstehen, wie wir Risiken reduzieren und die Stabilität des Handels erhöhen können.
Um eine Strategie zu analysieren, klicken Sie auf Bericht im Kontextmenü des Handelsverlaufs oder auf Berichte im Menü Ansicht (oder drücken Sie einfach Alt+E):
![]() | ![]() |
Weitere Details finden Sie in dem Artikel „Neuer MetaTrader-Bericht: Die 5 wichtigsten Handelsmetriken“.
Wenn die vom Client-Terminal bereitgestellten Standardberichte aus irgendeinem Grund nicht ausreichen, bietet die MQL5-Sprache zahlreiche Möglichkeiten, eigene Programme zu erstellen, einschließlich solcher, die Berichte erzeugen und an das Smartphone des Händlers senden. Diese Möglichkeit wollen wir heute diskutieren.
Unser Programm sollte mit dem Start des Terminals beginnen, den Wechsel eines Handelskontos, den Beginn des Tages und die Zeit für die Erstellung und Versendung von Berichten verfolgen. Der Programmtyp Service ist für diese Zwecke geeignet.
Laut der MQL5-Referenz ist ein Service ein Programm, das im Gegensatz zu Indikatoren, EAs und Skripten keine Verbindung zu einem Chart benötigt, um zu funktionieren. Wie Skripte reagieren Dienste nicht auf Ereignisse außer auf Auslöser (trigger). Um einen Dienst zu starten, sollte sein Code die OnStart-Handler-Funktion enthalten. Dienste akzeptieren keine anderen Ereignisse außer Start, aber sie können mit EventChartCustom nutzerdefinierte Ereignisse an Charts senden. Dienste werden in <terminal_directory>\MQL5\Services gespeichert.
Jeder Dienst, der im Terminal läuft, arbeitet in seinem eigenen Fluss. Dies bedeutet, dass ein in einer Schleife laufender Dienst den Betrieb anderer Programme nicht beeinträchtigen kann. Unser Dienst soll in einer Endlosschleife arbeiten, soll in der angegebenen Zeit überwachen, soll die gesamte Handelshistorie lesen, die Listen von geschlossenen Positionen erstellen, diese Listen nach verschiedenen Kriterien sortieren und soll Berichte darüber im Journal und mittels Push-Benachrichtigungen an das Smartphone des Nutzers senden. Wenn der Dienst zum ersten Mal gestartet wird oder seine Einstellungen geändert werden, sollte er außerdem prüfen, ob Push-Benachrichtigungen vom Terminal aus gesendet werden können. Um dies zu erreichen, sollten wir eine Interaktion mit dem Nutzer über Nachrichtenfenster arrangieren und auf die Antwort und Reaktion des Nutzers warten. Außerdem gibt es beim Senden von Push-Benachrichtigungen Beschränkungen hinsichtlich der Häufigkeit der Benachrichtigungen pro Zeiteinheit. Daher ist es notwendig, Verzögerungen bei der Übermittlung von Benachrichtigungen festzulegen. All dies sollte den Betrieb anderer Anwendungen, die auf dem Client-Terminal laufen, in keiner Weise beeinträchtigen. Ausgehend von all dem oben Gesagten sind Dienste das geeignetste Instrument für die Erstellung eines solchen Projekts.
Nun muss man sich ein Bild von den Komponenten machen, die notwendig sind, um alles zusammenzusetzen.
Projektstruktur
Schauen wir uns das Programm und seine Bestandteile von Anfang bis Ende an:
- Service-App. Die App hat Zugriff auf die Daten aller Konten, die während der gesamten Dauer des Betriebs des Dienstes aktiv waren. Aus den Kontendaten erhält die App Listen der geschlossenen Positionen und fasst sie zu einer Gesamtliste zusammen. Je nach den Einstellungen kann der Dienst Daten über geschlossene Positionen nur aus dem aktuellen aktiven Konto oder aus dem aktuellen und allen zuvor verwendeten Konten im Kundenterminal verwenden.
Die Handelsstatistik wird für die gewünschten Handelsperioden auf der Grundlage der Daten über geschlossene Positionen aus der Kontenliste erstellt. Anschließend wird sie als Push-Benachrichtigung an das Smartphone des Nutzers gesendet. Darüber hinaus werden die Handelsstatistiken in tabellarischer Form in den Terminalprotokollen der Experten angezeigt. - Einbeziehen der Konten. Die Sammlung enthält eine Liste der Konten, mit denen das Terminal während des laufenden Betriebs des Dienstes verbunden war. Die Kontenliste ermöglicht den Zugriff auf jedes Konto in der Liste und auf alle geschlossenen Positionen aller Konten. Die Listen stehen in der Service-App zur Verfügung, und der Dienst nimmt eine Auswahl vor und erstellt auf dieser Grundlage Statistiken.
- Objektklasse des Kontos. Speichert Daten für ein Konto mit einer Liste (Sammlung) aller geschlossenen Positionen, deren Deale auf diesem Konto während des laufenden Betriebs des Dienstes getätigt wurden. Ermöglicht den Zugriff auf die Kontoeigenschaften, das Erstellen und Aktualisieren der Liste der geschlossenen Positionen dieses Kontos und liefert Listen der geschlossenen Positionen nach verschiedenen Auswahlkriterien.
- Die Klassen für die Liste der historischen Positionen. Enthält eine Liste von Positionsobjekten und ermöglicht den Zugriff auf die Eigenschaften geschlossener Positionen sowie das Erstellen und Aktualisieren der Liste von Positionen. Gibt die Liste der geschlossenen Positionen zurück.
- Objektklasse Position. Speichert und ermöglicht den Zugriff auf die Eigenschaften einer geschlossenen Position. Enthält Funktionen zum Vergleich zweier Objekte anhand verschiedener Eigenschaften, die es ermöglichen, Positionslisten anhand verschiedener Auswahlkriterien zu erstellen. Enthält eine Liste der Angebote für diese Position und ermöglicht den Zugang zu ihnen.
- Objektklasse handeln. Speichert die Eigenschaften eines einzelnen Deals und ermöglicht den Zugriff darauf. Das Objekt enthält Funktionen für den Vergleich zweier Objekte anhand verschiedener Eigenschaften, wodurch es möglich ist, Listen von Deals anhand verschiedener Auswahlkriterien zu erstellen.
Das Konzept, eine geschlossene Position aus der Liste der historischen Deals wiederzufinden, haben wir im Artikel „Die Handelsgeschäfte direkt auf dem Chart beurteilen, statt in der Handelshistorie unterzugehen“ besprochen. Die Liste der Deals ermöglicht es, die Zugehörigkeit jedes Deals zu einer bestimmten Position anhand der in den Dealseigenschaften festgelegten Positions-ID (PositionID) zu bestimmen. Es wird ein Positionsobjekt erstellt, in dem die gefundenen Deals in die Liste aufgenommen werden. Hier werden wir genauso verfahren. Aber um die Konstruktion von Deal- und Positionsobjekten zu organisieren, werden wir ein völlig anderes, seit langem bewährtes Konzept verwenden, bei dem jedes Objekt identische Methoden des Zugriffs auf Eigenschaften hat, um sie zu setzen und zu erhalten. Dieses Konzept ermöglicht es uns, Objekte in einem einzigen gemeinsamen Schlüssel zu erstellen, sie in Listen zu speichern, nach einer beliebigen Objekteigenschaft zu filtern und zu sortieren und neue Listen im Zusammenhang mit der angegebenen Eigenschaft zu erhalten.
Lesen Sie die folgenden Artikel, um das Konzept des Erstellens von Klassen in diesem Projekt richtig zu verstehen:
- Struktur der Objekteigenschaften „(Teil I): Konzept, Datenmanagement und erste Ergebnisse“,
- Struktur von Objektlisten „(Teil II): Erhebung (Collection) historischer Aufträge und Deals“ und
- Methoden zum Filtern von Objekten in Listen nach Eigenschaften „(Teil III): Erhebung (Collection) von Marktorders und Positionen“
Im Wesentlichen beschreiben die drei Artikel die Möglichkeit, eine Datenbank für beliebige Objekte in MQL5 zu erstellen, sie in der Datenbank zu speichern und die erforderlichen Eigenschaften und Werte zu erhalten. Dies ist genau die Funktionalität, die in diesem Projekt benötigt wird, und aus diesem Grund wurde beschlossen, Objekte und ihre Sammlungen nach dem in den Artikeln beschriebenen Konzept aufzubauen. Nur wird es hier ein wenig einfacher gemacht - ohne abstrakte Objektklassen mit geschützten Konstruktoren zu erstellen und ohne nicht unterstützte Objekteigenschaften in den Klassen zu definieren. Alles wird einfacher - jedes Objekt hat seine eigene Liste von Eigenschaften, die in drei Arrays gespeichert sind und in die man schreiben und abrufen kann. Alle diese Objekte werden in den Listen gespeichert, aus denen dann neue Listen mit nur den gewünschten Objekten entsprechend den angegebenen Eigenschaften abgerufen werden können.
Kurz gesagt, jedes im Projekt erstellte Objekt hat einen Satz eigener Eigenschaften, so wie jedes Objekt oder jede Entität in MQL5. Nur in MQL5 gibt es Standardfunktionen für den Erhalt von Eigenschaften, und für Projektobjekte werden dies Methoden für den Erhalt von Integer-, Real- und String-Eigenschaften sein, die direkt in der Klasse jedes Objekts eingestellt werden. Im weiteren Verlauf werden alle diese Objekte in Listen gespeichert — dynamische Arrays von Zeigern auf die CObject-Objekte der Standardbibliothek. Die Klassen der Standardbibliothek ermöglichen es uns, komplexe Projekte mit minimalen Aufwand zu erstellen. In diesem Fall handelt es sich um eine Datenbank mit den geschlossenen Positionen aller Konten, auf denen Handel betrieben wurde, mit der Möglichkeit, Listen von Objekten zu erhalten, die nach jeder gewünschten Eigenschaft sortiert und ausgewählt werden können.
Jede Position besteht nur von dem Moment an, in dem sie eröffnet wird — einem „In-Deal“ bis zu dem Moment, in dem sie geschlossen wird — einem „Out/OutBuy-Deal“. Mit anderen Worten, es ist ein Objekt, das nur als Marktobjekt existiert. Jedes Handelsgeschäft bzw. „Deal“ ist im Gegenteil nur ein historischer Gegenstand, da ein Deal lediglich die Tatsache der Ausführung eines Auftrags (Handelsauftrags) ist. Daher gibt es im Kundenterminal keine Positionen in der historischen Liste - sie existieren nur in der Liste der aktuellen Marktpositionen.
Um eine bereits geschlossene Marktposition wiederherzustellen, ist es daher notwendig, eine bereits bestehende Position aus historischen Deals „zusammenzusetzen“. Glücklicherweise enthält jeder Deal die ID einer Position, an der der Deal beteiligt war. Wir müssen die Liste der historischen Deals durchgehen, den nächsten Deal aus der Liste holen, die Positions-ID überprüfen und das Positionsobjekt erstellen. Wir fügen das erstellte Deal-Objekt der neuen, historischen Position hinzu. Wir werden dies weiter umsetzen. In der Zwischenzeit erstellen wir die Klassen für die Objeke der DEals und Positionen, mit denen wir weiterarbeiten werden.
Die Klasse Deal
Im Terminalverzeichnis \MQL5\Services\ erstellen wir den neuen Ordner AccountReporter\ mit der neuen Datei Deal.mqh der Klasse CDeal.
Die Klasse sollte von der Basisklasse der Standardbibliothek CObject abgeleitet werden, während ihre Datei in die neu erstellte Klasse eingebunden werden sollte:
//+------------------------------------------------------------------+ //| 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> //+------------------------------------------------------------------+ //| Deal class | //+------------------------------------------------------------------+ class CDeal : public CObject { }
Fügen wir nun die Enumeration von Integer-, Real- und String-Eigenschaften der Deals hinzu, während wir in den Abschnitten private, protected und public die Mitgliedsvariablen der Klasse und die Methoden zur Behandlung von Deal-Eigenschaften deklarieren:
//+------------------------------------------------------------------+ //| 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> //--- Enumeration of integer deal properties enum ENUM_DEAL_PROPERTY_INT { DEAL_PROP_TICKET = 0, // Deal ticket DEAL_PROP_ORDER, // Deal order number DEAL_PROP_TIME, // Deal execution time DEAL_PROP_TIME_MSC, // Deal execution time in milliseconds DEAL_PROP_TYPE, // Deal type DEAL_PROP_ENTRY, // Deal direction DEAL_PROP_MAGIC, // Deal magic number DEAL_PROP_REASON, // Deal execution reason or source DEAL_PROP_POSITION_ID, // Position ID DEAL_PROP_SPREAD, // Spread when performing a deal }; //--- Enumeration of real deal properties enum ENUM_DEAL_PROPERTY_DBL { DEAL_PROP_VOLUME = DEAL_PROP_SPREAD+1,// Deal volume DEAL_PROP_PRICE, // Deal price DEAL_PROP_COMMISSION, // Commission DEAL_PROP_SWAP, // Accumulated swap when closing DEAL_PROP_PROFIT, // Deal financial result DEAL_PROP_FEE, // Deal fee DEAL_PROP_SL, // Stop Loss level DEAL_PROP_TP, // Take Profit level }; //--- Enumeration of string deal properties enum ENUM_DEAL_PROPERTY_STR { DEAL_PROP_SYMBOL = DEAL_PROP_TP+1, // Symbol the deal is executed for DEAL_PROP_COMMENT, // Deal comment DEAL_PROP_EXTERNAL_ID, // Deal ID in an external trading system }; //+------------------------------------------------------------------+ //| Deal class | //+------------------------------------------------------------------+ class CDeal : public CObject { private: MqlTick m_tick; // Deal tick structure long m_lprop[DEAL_PROP_SPREAD+1]; // Array for storing integer properties double m_dprop[DEAL_PROP_TP-DEAL_PROP_SPREAD]; // Array for storing real properties string m_sprop[DEAL_PROP_EXTERNAL_ID-DEAL_PROP_TP]; // Array for storing string properties //--- Return the index of the array the deal's (1) double and (2) string properties are located at int IndexProp(ENUM_DEAL_PROPERTY_DBL property) const { return(int)property-DEAL_PROP_SPREAD-1; } int IndexProp(ENUM_DEAL_PROPERTY_STR property) const { return(int)property-DEAL_PROP_TP-1; } //--- 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: //--- 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 public: //--- Set the properties //--- Set deal's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_DEAL_PROPERTY_INT property,long value){ this.m_lprop[property]=value; } void SetProperty(ENUM_DEAL_PROPERTY_DBL property,double value){ this.m_dprop[this.IndexProp(property)]=value; } void SetProperty(ENUM_DEAL_PROPERTY_STR property,string value){ this.m_sprop[this.IndexProp(property)]=value; } //--- Integer properties void SetTicket(const long ticket) { this.SetProperty(DEAL_PROP_TICKET, ticket); } // Ticket void SetOrder(const long order) { this.SetProperty(DEAL_PROP_ORDER, order); } // Order void SetTime(const datetime time) { this.SetProperty(DEAL_PROP_TIME, time); } // Time void SetTimeMsc(const long value) { this.SetProperty(DEAL_PROP_TIME_MSC, value); } // Time in milliseconds void SetTypeDeal(const ENUM_DEAL_TYPE type) { this.SetProperty(DEAL_PROP_TYPE, type); } // Type void SetEntry(const ENUM_DEAL_ENTRY entry) { this.SetProperty(DEAL_PROP_ENTRY, entry); } // Direction void SetMagic(const long magic) { this.SetProperty(DEAL_PROP_MAGIC, magic); } // Magic number void SetReason(const ENUM_DEAL_REASON reason) { this.SetProperty(DEAL_PROP_REASON, reason); } // Deal execution reason or source void SetPositionID(const long id) { this.SetProperty(DEAL_PROP_POSITION_ID, id); } // Position ID //--- Real properties void SetVolume(const double volume) { this.SetProperty(DEAL_PROP_VOLUME, volume); } // Volume void SetPrice(const double price) { this.SetProperty(DEAL_PROP_PRICE, price); } // Price void SetCommission(const double value) { this.SetProperty(DEAL_PROP_COMMISSION, value); } // Commission void SetSwap(const double value) { this.SetProperty(DEAL_PROP_SWAP, value); } // Accumulated swap when closing void SetProfit(const double value) { this.SetProperty(DEAL_PROP_PROFIT, value); } // Financial result void SetFee(const double value) { this.SetProperty(DEAL_PROP_FEE, value); } // Deal fee void SetSL(const double value) { this.SetProperty(DEAL_PROP_SL, value); } // Stop Loss level void SetTP(const double value) { this.SetProperty(DEAL_PROP_TP, value); } // Take Profit level //--- String properties void SetSymbol(const string symbol) { this.SetProperty(DEAL_PROP_SYMBOL,symbol); } // Symbol name void SetComment(const string comment) { this.SetProperty(DEAL_PROP_COMMENT,comment); } // Comment void SetExternalID(const string ext_id) { this.SetProperty(DEAL_PROP_EXTERNAL_ID,ext_id); } // Deal ID in an external trading system //--- Get the properties //--- Return deal’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_DEAL_PROPERTY_INT property) const { return this.m_lprop[property]; } double GetProperty(ENUM_DEAL_PROPERTY_DBL property) const { return this.m_dprop[this.IndexProp(property)]; } string GetProperty(ENUM_DEAL_PROPERTY_STR property) const { return this.m_sprop[this.IndexProp(property)]; } //--- Integer properties long Ticket(void) const { return this.GetProperty(DEAL_PROP_TICKET); } // Ticket long Order(void) const { return this.GetProperty(DEAL_PROP_ORDER); } // Order datetime Time(void) const { return (datetime)this.GetProperty(DEAL_PROP_TIME); } // Time long TimeMsc(void) const { return this.GetProperty(DEAL_PROP_TIME_MSC); } // Time in milliseconds ENUM_DEAL_TYPE TypeDeal(void) const { return (ENUM_DEAL_TYPE)this.GetProperty(DEAL_PROP_TYPE); } // Type ENUM_DEAL_ENTRY Entry(void) const { return (ENUM_DEAL_ENTRY)this.GetProperty(DEAL_PROP_ENTRY); } // Direction long Magic(void) const { return this.GetProperty(DEAL_PROP_MAGIC); } // Magic number ENUM_DEAL_REASON Reason(void) const { return (ENUM_DEAL_REASON)this.GetProperty(DEAL_PROP_REASON); } // Deal execution reason or source long PositionID(void) const { return this.GetProperty(DEAL_PROP_POSITION_ID); } // Position ID //--- Real properties double Volume(void) const { return this.GetProperty(DEAL_PROP_VOLUME); } // Volume double Price(void) const { return this.GetProperty(DEAL_PROP_PRICE); } // Price double Commission(void) const { return this.GetProperty(DEAL_PROP_COMMISSION); } // Commission double Swap(void) const { return this.GetProperty(DEAL_PROP_SWAP); } // Accumulated swap when closing double Profit(void) const { return this.GetProperty(DEAL_PROP_PROFIT); } // Financial result double Fee(void) const { return this.GetProperty(DEAL_PROP_FEE); } // Deal fee double SL(void) const { return this.GetProperty(DEAL_PROP_SL); } // Stop Loss level double TP(void) const { return this.GetProperty(DEAL_PROP_TP); } // Take Profit level //--- String properties string Symbol(void) const { return this.GetProperty(DEAL_PROP_SYMBOL); } // Symbol name string Comment(void) const { return this.GetProperty(DEAL_PROP_COMMENT); } // Comment string ExternalID(void) const { return this.GetProperty(DEAL_PROP_EXTERNAL_ID); } // Deal ID in an external trading system //--- Additional properties 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 (int)this.GetProperty(DEAL_PROP_SPREAD); } // Spread when performing a deal //--- 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 deal description string Description(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){} CDeal(const ulong ticket); ~CDeal(); };
Werfen wir einen Blick auf die Implementierung der Methoden der Klasse.
Im Klassenkonstruktor kann man davon ausgehen, dass das Deal bereits ausgewählt wurde und wir seine Eigenschaften abrufen können:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CDeal::CDeal(const ulong ticket) { //--- Store the properties //--- Integer properties this.SetTicket((long)ticket); // Deal ticket this.SetOrder(::HistoryDealGetInteger(ticket, DEAL_ORDER)); // Order this.SetTime((datetime)::HistoryDealGetInteger(ticket, DEAL_TIME)); // Deal execution time this.SetTimeMsc(::HistoryDealGetInteger(ticket, DEAL_TIME_MSC)); // Deal execution time in milliseconds this.SetTypeDeal((ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE)); // Type this.SetEntry((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(ticket, DEAL_ENTRY)); // Direction this.SetMagic(::HistoryDealGetInteger(ticket, DEAL_MAGIC)); // Magic number this.SetReason((ENUM_DEAL_REASON)::HistoryDealGetInteger(ticket, DEAL_REASON)); // Deal execution reason or source this.SetPositionID(::HistoryDealGetInteger(ticket, DEAL_POSITION_ID)); // Position ID //--- Real properties this.SetVolume(::HistoryDealGetDouble(ticket, DEAL_VOLUME)); // Volume this.SetPrice(::HistoryDealGetDouble(ticket, DEAL_PRICE)); // Price this.SetCommission(::HistoryDealGetDouble(ticket, DEAL_COMMISSION)); // Commission this.SetSwap(::HistoryDealGetDouble(ticket, DEAL_SWAP)); // Accumulated swap when closing this.SetProfit(::HistoryDealGetDouble(ticket, DEAL_PROFIT)); // Financial result this.SetFee(::HistoryDealGetDouble(ticket, DEAL_FEE)); // Deal fee this.SetSL(::HistoryDealGetDouble(ticket, DEAL_SL)); // Stop Loss level this.SetTP(::HistoryDealGetDouble(ticket, DEAL_TP)); // Take Profit level //--- String properties this.SetSymbol(::HistoryDealGetString(ticket, DEAL_SYMBOL)); // Symbol name this.SetComment(::HistoryDealGetString(ticket, DEAL_COMMENT)); // Comment this.SetExternalID(::HistoryDealGetString(ticket, DEAL_EXTERNAL_ID)); // Deal ID in an external trading system //--- Additional parameters this.m_digits = (int)::SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS); this.m_point = ::SymbolInfoDouble(this.Symbol(), SYMBOL_POINT); //--- Parameters for calculating spread this.m_bid = 0; this.m_ask = 0; this.SetProperty(DEAL_PROP_SPREAD, 0); //--- 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; int spread=(int)::fabs((this.m_ask-this.m_bid)/this.m_point); this.SetProperty(DEAL_PROP_SPREAD, spread); } //--- If failed to obtain a historical tick, take the spread value of the minute bar the deal took place on else this.SetProperty(DEAL_PROP_SPREAD, this.GetSpreadM1()); }
Wir speichern die Eigenschaften des Deals sowie die Ziffern und den Punkt des Symbols, für das das Deal getätigt wurde, in den Arrays der Klasseneigenschaften, um Berechnungen durchzuführen und die Dealsinformationen anzuzeigen. Als Nächstes rufen wir den historischen Tick zum Zeitpunkt des Dealsabschlusses ab. Auf diese Weise erhalten wir Zugang zu den Geld- und Briefkursen (Ask & Bid) zum Zeitpunkt des Deals und damit die Möglichkeit, den Spread zu berechnen.
Die Methode, die zwei Objekte anhand einer bestimmten Eigenschaft vergleicht:
//+------------------------------------------------------------------+ //| 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 DEAL_PROP_TICKET : return(this.Ticket() > obj.Ticket() ? 1 : this.Ticket() < obj.Ticket() ? -1 : 0); case DEAL_PROP_ORDER : return(this.Order() > obj.Order() ? 1 : this.Order() < obj.Order() ? -1 : 0); case DEAL_PROP_TIME : return(this.Time() > obj.Time() ? 1 : this.Time() < obj.Time() ? -1 : 0); case DEAL_PROP_TIME_MSC : return(this.TimeMsc() > obj.TimeMsc() ? 1 : this.TimeMsc() < obj.TimeMsc() ? -1 : 0); case DEAL_PROP_TYPE : return(this.TypeDeal() > obj.TypeDeal() ? 1 : this.TypeDeal() < obj.TypeDeal() ? -1 : 0); case DEAL_PROP_ENTRY : return(this.Entry() > obj.Entry() ? 1 : this.Entry() < obj.Entry() ? -1 : 0); case DEAL_PROP_MAGIC : return(this.Magic() > obj.Magic() ? 1 : this.Magic() < obj.Magic() ? -1 : 0); case DEAL_PROP_REASON : return(this.Reason() > obj.Reason() ? 1 : this.Reason() < obj.Reason() ? -1 : 0); case DEAL_PROP_POSITION_ID : return(this.PositionID() > obj.PositionID() ? 1 : this.PositionID() < obj.PositionID() ? -1 : 0); case DEAL_PROP_SPREAD : return(this.Spread() > obj.Spread() ? 1 : this.Spread() < obj.Spread() ? -1 : 0); case DEAL_PROP_VOLUME : return(this.Volume() > obj.Volume() ? 1 : this.Volume() < obj.Volume() ? -1 : 0); case DEAL_PROP_PRICE : return(this.Price() > obj.Price() ? 1 : this.Price() < obj.Price() ? -1 : 0); case DEAL_PROP_COMMISSION : return(this.Commission() > obj.Commission() ? 1 : this.Commission() < obj.Commission() ? -1 : 0); case DEAL_PROP_SWAP : return(this.Swap() > obj.Swap() ? 1 : this.Swap() < obj.Swap() ? -1 : 0); case DEAL_PROP_PROFIT : return(this.Profit() > obj.Profit() ? 1 : this.Profit() < obj.Profit() ? -1 : 0); case DEAL_PROP_FEE : return(this.Fee() > obj.Fee() ? 1 : this.Fee() < obj.Fee() ? -1 : 0); case DEAL_PROP_SL : return(this.SL() > obj.SL() ? 1 : this.SL() < obj.SL() ? -1 : 0); case DEAL_PROP_TP : return(this.TP() > obj.TP() ? 1 : this.TP() < obj.TP() ? -1 : 0); case DEAL_PROP_SYMBOL : return(this.Symbol() > obj.Symbol() ? 1 : this.Symbol() < obj.Symbol() ? -1 : 0); case DEAL_PROP_COMMENT : return(this.Comment() > obj.Comment() ? 1 : this.Comment() < obj.Comment() ? -1 : 0); case DEAL_PROP_EXTERNAL_ID : return(this.ExternalID() > obj.ExternalID() ? 1 : this.ExternalID() < obj.ExternalID() ? -1 : 0); default : return(-1); } }
Dies ist eine virtuelle Methode, die die gleichnamige Methode in der übergeordneten Klasse CObject außer Kraft setzt. Je nach Vergleichsmodus (eine der Eigenschaften des Deal-Objekts) werden diese Eigenschaften für das aktuelle Objekt und für das durch den Zeiger an die Methode übergebene Objekt verglichen. Die Methode gibt 1 zurück, wenn der Wert der aktuellen Objekteigenschaft größer ist als der des Vergleichsobjekts. Ist er kleiner, erhalten wir -1. Wenn die Werte gleich sind, erhalten wir 0.
Die Methode, die eine Beschreibung des Deal-Typs zurückgibt:
//+------------------------------------------------------------------+ //| Return the deal type description | //+------------------------------------------------------------------+ string CDeal::TypeDescription(void) const { switch(this.TypeDeal()) { 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.TypeDeal(); } }
Je nach Art des Deals wird dessen Textbeschreibung zurückgegeben. Für dieses Projekt ist diese Methode überflüssig, da wir nicht alle Arten von Deals verwenden werden, sondern nur diejenigen, die sich auf die Position - Kauf oder Verkauf - beziehen.
Die Methode liefert eine Beschreibung der Positionsänderungsmethode:
//+------------------------------------------------------------------+ //| Return position change method | //+------------------------------------------------------------------+ string CDeal::EntryDescription(void) const { switch(this.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.Entry(); } }
Die Methode liefert eine Beschreibung des Handelsgeschäftsgrundes:
//+------------------------------------------------------------------+ //| Return a deal reason description | //+------------------------------------------------------------------+ string CDeal::ReasonDescription(void) const { switch(this.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.Reason(); } }
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()))); }
Die Methode, die die Handelsgeschäftseigenschaften im Journal ausgibt:
//+------------------------------------------------------------------+ //| Print deal properties in the journal | //+------------------------------------------------------------------+ void CDeal::Print(void) { ::Print(this.Description()); }
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')); }
Alle Methoden, die Textbeschreibungen zurückgeben und protokollieren, dienen der Beschreibung des Deals. Bei diesem Projekt werden sie eigentlich nicht benötigt, aber man sollte immer an Erweiterungen und Verbesserungen denken. Aus diesem Grund werden solche Methoden hier vorgestellt.
Methode, die den Deal-Tick empfängt:
//+------------------------------------------------------------------+ //| Get the deal tick | //| https://www.mql5.com/ru/forum/42122/page47#comment_37205238 | //+------------------------------------------------------------------+ 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.Symbol(), ticks, COPY_TICKS_INFO, this.TimeMsc()-(offset <<=1), this.TimeMsc()); //--- 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); }
Die Logik der Methode wird in den Codekommentaren beschrieben. 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 der Spanne mit der Methode zur Ermittlung der Spanne des Minutenbarrens des Deals:
//+------------------------------------------------------------------+ //| Gets the spread of the deal minute bar | //+------------------------------------------------------------------+ int CDeal::GetSpreadM1(void) { int array[1]={}; int bar=::iBarShift(this.Symbol(), PERIOD_M1, this.Time()); if(bar==WRONG_VALUE) return 0; return(::CopySpread(this.Symbol(), PERIOD_M1, bar, 1, array)==1 ? array[0] : 0); }
Die Klasse der Deals ist fertig. Die Objekte der Klasse werden in der Liste der Deals in der historischen Positionsklasse gespeichert, aus der es möglich sein wird, Zeiger auf die gewünschten Deals zu erhalten und deren Daten zu bearbeiten.
Historische Positionsklasse
Wir erstellen in \MQL5\Services\AccountReporter\ die neue Datei Position.mqh mit der Klasse CPosition.
Die Klasse sollte von der Basisobjektklasse CObject Standard Library abgeleitet werden:
//+------------------------------------------------------------------+ //| 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" //+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { }
Da die Positionsklasse eine Liste für diese Position enthalten wird, ist es notwendig, in die erstellte Datei die Deal-Klassendatei und die Klassendatei des dynamischen Arrays von Zeigern auf CObject-Objekte aufzunehmen:
//+------------------------------------------------------------------+ //| 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> //+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { }
Fügen wir nun die Enumeration der Integer-, Real- und String-Deal-Eigenschaften hinzu, während wir in den Abschnitten private, protected und public die Mitgliedsvariablen der Klasse und die Methoden zur Behandlung der Positionseigenschaften deklarieren:
//+------------------------------------------------------------------+ //| 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> //--- Enumeration of integer position properties enum ENUM_POSITION_PROPERTY_INT { POSITION_PROP_TICKET = 0, // Position ticket POSITION_PROP_TIME, // Position open time POSITION_PROP_TIME_MSC, // Position open time in milliseconds POSITION_PROP_TIME_UPDATE, // Position change time POSITION_PROP_TIME_UPDATE_MSC, // Position change time in milliseconds POSITION_PROP_TYPE, // Position type POSITION_PROP_MAGIC, // Position magic number POSITION_PROP_IDENTIFIER, // Position ID POSITION_PROP_REASON, // Position open reason POSITION_PROP_ACCOUNT_LOGIN, // Account number POSITION_PROP_TIME_CLOSE, // Position close time POSITION_PROP_TIME_CLOSE_MSC, // Position close time in milliseconds }; //--- Enumeration of real position properties enum ENUM_POSITION_PROPERTY_DBL { POSITION_PROP_VOLUME = POSITION_PROP_TIME_CLOSE_MSC+1,// Position volume POSITION_PROP_PRICE_OPEN, // Position price POSITION_PROP_SL, // Stop Loss for open position POSITION_PROP_TP, // Take Profit for open position POSITION_PROP_PRICE_CURRENT, // Symbol current price POSITION_PROP_SWAP, // Accumulated swap POSITION_PROP_PROFIT, // Current profit POSITION_PROP_CONTRACT_SIZE, // Symbol trade contract size POSITION_PROP_PRICE_CLOSE, // Position close price POSITION_PROP_COMMISSIONS, // Accumulated commission POSITION_PROP_FEE, // Accumulated payment for deals }; //--- Enumeration of string position properties enum ENUM_POSITION_PROPERTY_STR { POSITION_PROP_SYMBOL = POSITION_PROP_FEE+1,// A symbol the position is open for POSITION_PROP_COMMENT, // Comment to a position POSITION_PROP_EXTERNAL_ID, // Position ID in the external system POSITION_PROP_CURRENCY_PROFIT, // Position symbol profit currency POSITION_PROP_ACCOUNT_CURRENCY, // Account deposit currency POSITION_PROP_ACCOUNT_SERVER, // Server name }; //+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { private: long m_lprop[POSITION_PROP_TIME_CLOSE_MSC+1]; // Array for storing integer properties double m_dprop[POSITION_PROP_FEE-POSITION_PROP_TIME_CLOSE_MSC]; // Array for storing real properties string m_sprop[POSITION_PROP_ACCOUNT_SERVER-POSITION_PROP_FEE]; // Array for storing string properties //--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_POSITION_PROPERTY_DBL property) const { return(int)property-POSITION_PROP_TIME_CLOSE_MSC-1;} int IndexProp(ENUM_POSITION_PROPERTY_STR property) const { return(int)property-POSITION_PROP_FEE-1; } 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; //--- Additional properties int m_profit_pt; // Profit in points int m_digits; // Symbol digits double m_point; // One symbol point value double m_tick_value; // Calculated tick value //--- Return the pointer to (1) open and (2) close deal CDeal *GetDealIn(void) const; CDeal *GetDealOut(void) const; public: //--- Return the list of deals CArrayObj *GetListDeals(void) { return(&this.m_list_deals); } //--- Set the properties //--- Set (1) integer, (2) real and (3) string properties void SetProperty(ENUM_POSITION_PROPERTY_INT property,long value) { this.m_lprop[property]=value; } void SetProperty(ENUM_POSITION_PROPERTY_DBL property,double value) { this.m_dprop[this.IndexProp(property)]=value; } void SetProperty(ENUM_POSITION_PROPERTY_STR property,string value) { this.m_sprop[this.IndexProp(property)]=value; } //--- Integer properties void SetTicket(const long ticket) { this.SetProperty(POSITION_PROP_TICKET, ticket); } // Position ticket void SetTime(const datetime time) { this.SetProperty(POSITION_PROP_TIME, time); } // Position open time void SetTimeMsc(const long value) { this.SetProperty(POSITION_PROP_TIME_MSC, value); } // Position open time in milliseconds since 01.01.1970 void SetTimeUpdate(const datetime time) { this.SetProperty(POSITION_PROP_TIME_UPDATE, time); } // Position update time void SetTimeUpdateMsc(const long value) { this.SetProperty(POSITION_PROP_TIME_UPDATE_MSC, value); } // Position update time in milliseconds since 01.01.1970 void SetTypePosition(const ENUM_POSITION_TYPE type) { this.SetProperty(POSITION_PROP_TYPE, type); } // Position type void SetMagic(const long magic) { this.SetProperty(POSITION_PROP_MAGIC, magic); } // Magic number for a position (see ORDER_MAGIC) void SetID(const long id) { this.SetProperty(POSITION_PROP_IDENTIFIER, id); } // Position ID void SetReason(const ENUM_POSITION_REASON reason) { this.SetProperty(POSITION_PROP_REASON, reason); } // Position open reason void SetTimeClose(const datetime time) { this.SetProperty(POSITION_PROP_TIME_CLOSE, time); } // Close time void SetTimeCloseMsc(const long value) { this.SetProperty(POSITION_PROP_TIME_CLOSE_MSC, value); } // Close time in milliseconds void SetAccountLogin(const long login) { this.SetProperty(POSITION_PROP_ACCOUNT_LOGIN, login); } // Acount number //--- Real properties void SetVolume(const double volume) { this.SetProperty(POSITION_PROP_VOLUME, volume); } // Position volume void SetPriceOpen(const double price) { this.SetProperty(POSITION_PROP_PRICE_OPEN, price); } // Position price void SetSL(const double value) { this.SetProperty(POSITION_PROP_SL, value); } // Stop Loss level for an open position void SetTP(const double value) { this.SetProperty(POSITION_PROP_TP, value); } // Take Profit level for an open position void SetPriceCurrent(const double price) { this.SetProperty(POSITION_PROP_PRICE_CURRENT, price); } // Current price by symbol void SetSwap(const double value) { this.SetProperty(POSITION_PROP_SWAP, value); } // Accumulated swap void SetProfit(const double value) { this.SetProperty(POSITION_PROP_PROFIT, value); } // Current profit void SetPriceClose(const double price) { this.SetProperty(POSITION_PROP_PRICE_CLOSE, price); } // Close price void SetContractSize(const double value) { this.SetProperty(POSITION_PROP_CONTRACT_SIZE, value); } // Symbol trading contract size void SetCommissions(void); // Total commission of all deals void SetFee(void); // Total deal fee //--- String properties void SetSymbol(const string symbol) { this.SetProperty(POSITION_PROP_SYMBOL, symbol); } // Symbol a position is opened for void SetComment(const string comment) { this.SetProperty(POSITION_PROP_COMMENT, comment); } // Position comment void SetExternalID(const string ext_id) { this.SetProperty(POSITION_PROP_EXTERNAL_ID, ext_id); } // Position ID in an external system (on the exchange) void SetAccountServer(const string server) { this.SetProperty(POSITION_PROP_ACCOUNT_SERVER, server); } // Server name void SetAccountCurrency(const string currency) { this.SetProperty(POSITION_PROP_ACCOUNT_CURRENCY, currency); } // Account deposit currency void SetCurrencyProfit(const string currency) { this.SetProperty(POSITION_PROP_CURRENCY_PROFIT, currency); } // Profit currency of the position symbol //--- Get the properties //--- Return (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_POSITION_PROPERTY_INT property) const { return this.m_lprop[property]; } double GetProperty(ENUM_POSITION_PROPERTY_DBL property) const { return this.m_dprop[this.IndexProp(property)]; } string GetProperty(ENUM_POSITION_PROPERTY_STR property) const { return this.m_sprop[this.IndexProp(property)]; } //--- Integer properties long Ticket(void) const { return this.GetProperty(POSITION_PROP_TICKET); } // Position ticket datetime Time(void) const { return (datetime)this.GetProperty(POSITION_PROP_TIME); } // Position open time long TimeMsc(void) const { return this.GetProperty(POSITION_PROP_TIME_MSC); } // Position open time in milliseconds since 01.01.1970 datetime TimeUpdate(void) const { return (datetime)this.GetProperty(POSITION_PROP_TIME_UPDATE);} // Position change time long TimeUpdateMsc(void) const { return this.GetProperty(POSITION_PROP_TIME_UPDATE_MSC); } // Position update time in milliseconds since 01.01.1970 ENUM_POSITION_TYPE TypePosition(void) const { return (ENUM_POSITION_TYPE)this.GetProperty(POSITION_PROP_TYPE);}// Position type long Magic(void) const { return this.GetProperty(POSITION_PROP_MAGIC); } // Magic number for a position (see ORDER_MAGIC) long ID(void) const { return this.GetProperty(POSITION_PROP_IDENTIFIER); } // Position ID ENUM_POSITION_REASON Reason(void) const { return (ENUM_POSITION_REASON)this.GetProperty(POSITION_PROP_REASON);}// Position opening reason datetime TimeClose(void) const { return (datetime)this.GetProperty(POSITION_PROP_TIME_CLOSE); } // Close time long TimeCloseMsc(void) const { return this.GetProperty(POSITION_PROP_TIME_CLOSE_MSC); } // Close time in milliseconds long AccountLogin(void) const { return this.GetProperty(POSITION_PROP_ACCOUNT_LOGIN); } // Login //--- Real properties double Volume(void) const { return this.GetProperty(POSITION_PROP_VOLUME); } // Position volume double PriceOpen(void) const { return this.GetProperty(POSITION_PROP_PRICE_OPEN); } // Position price double SL(void) const { return this.GetProperty(POSITION_PROP_SL); } // Stop Loss level for an open position double TP(void) const { return this.GetProperty(POSITION_PROP_TP); } // Take Profit level for an open position double PriceCurrent(void) const { return this.GetProperty(POSITION_PROP_PRICE_CURRENT); } // Current price by symbol double Swap(void) const { return this.GetProperty(POSITION_PROP_SWAP); } // Accumulated swap double Profit(void) const { return this.GetProperty(POSITION_PROP_PROFIT); } // Current profit double ContractSize(void) const { return this.GetProperty(POSITION_PROP_CONTRACT_SIZE); } // Symbol trading contract size double PriceClose(void) const { return this.GetProperty(POSITION_PROP_PRICE_CLOSE); } // Close price double Commissions(void) const { return this.GetProperty(POSITION_PROP_COMMISSIONS); } // Total commission of all deals double Fee(void) const { return this.GetProperty(POSITION_PROP_FEE); } // Total deal fee //--- String properties string Symbol(void) const { return this.GetProperty(POSITION_PROP_SYMBOL); } // A symbol position is opened on string Comment(void) const { return this.GetProperty(POSITION_PROP_COMMENT); } // Position comment string ExternalID(void) const { return this.GetProperty(POSITION_PROP_EXTERNAL_ID); } // Position ID in an external system (on the exchange) string AccountServer(void) const { return this.GetProperty(POSITION_PROP_ACCOUNT_SERVER); } // Server name string AccountCurrency(void) const { return this.GetProperty(POSITION_PROP_ACCOUNT_CURRENCY); } // Account deposit currency string CurrencyProfit(void) const { return this.GetProperty(POSITION_PROP_CURRENCY_PROFIT); } // Profit currency of the position symbol //--- Additional properties ulong DealIn(void) const; // Open deal ticket ulong DealOut(void) const; // Close deal ticket int ProfitInPoints(void) const; // Profit in points int SpreadIn(void) const; // Spread when opening int SpreadOut(void) const; // Spread when closing double SpreadOutCost(void) const; // Spread cost when closing double PriceOutAsk(void) const; // Ask price when closing double PriceOutBid(void) const; // Bid price when closing //--- Add a deal to the list of deals, return the pointer CDeal *DealAdd(const long ticket); //--- 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 position description string Description(void); //--- Print the properties of the position and its deals 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; //--- Constructor/destructor CPosition(const long position_id, const string symbol); CPosition(void){} ~CPosition(); };
Werfen wir einen Blick auf die Implementierung der Methoden der Klasse.
Wir legen die Positions-ID und das Symbol aus den Parametern fest, die der Methode im Klassenkonstruktor übergeben wurden, und schreiben die Konto- und Symboldaten:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPosition::CPosition(const long position_id, const string symbol) { this.m_list_deals.Sort(DEAL_PROP_TIME_MSC); this.SetID(position_id); this.SetSymbol(symbol); this.SetAccountLogin(::AccountInfoInteger(ACCOUNT_LOGIN)); this.SetAccountServer(::AccountInfoString(ACCOUNT_SERVER)); this.SetAccountCurrency(::AccountInfoString(ACCOUNT_CURRENCY)); this.SetCurrencyProfit(::SymbolInfoString(this.Symbol(),SYMBOL_CURRENCY_PROFIT)); this.SetContractSize(::SymbolInfoDouble(this.Symbol(),SYMBOL_TRADE_CONTRACT_SIZE)); this.m_digits = (int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); this.m_point = ::SymbolInfoDouble(this.Symbol(),SYMBOL_POINT); this.m_tick_value = ::SymbolInfoDouble(this.Symbol(), SYMBOL_TRADE_TICK_VALUE); }
Wir löschen im Destruktor der Klasse die Liste der Deals der Positionen:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPosition::~CPosition() { this.m_list_deals.Clear(); }
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 POSITION_PROP_TICKET : return(this.Ticket() > obj.Ticket() ? 1 : this.Ticket() < obj.Ticket() ? -1 : 0); case POSITION_PROP_TIME : return(this.Time() > obj.Time() ? 1 : this.Time() < obj.Time() ? -1 : 0); case POSITION_PROP_TIME_MSC : return(this.TimeMsc() > obj.TimeMsc() ? 1 : this.TimeMsc() < obj.TimeMsc() ? -1 : 0); case POSITION_PROP_TIME_UPDATE : return(this.TimeUpdate() > obj.TimeUpdate() ? 1 : this.TimeUpdate() < obj.TimeUpdate() ? -1 : 0); case POSITION_PROP_TIME_UPDATE_MSC : return(this.TimeUpdateMsc() > obj.TimeUpdateMsc() ? 1 : this.TimeUpdateMsc() < obj.TimeUpdateMsc() ? -1 : 0); case POSITION_PROP_TYPE : return(this.TypePosition() > obj.TypePosition() ? 1 : this.TypePosition() < obj.TypePosition() ? -1 : 0); case POSITION_PROP_MAGIC : return(this.Magic() > obj.Magic() ? 1 : this.Magic() < obj.Magic() ? -1 : 0); case POSITION_PROP_IDENTIFIER : return(this.ID() > obj.ID() ? 1 : this.ID() < obj.ID() ? -1 : 0); case POSITION_PROP_REASON : return(this.Reason() > obj.Reason() ? 1 : this.Reason() < obj.Reason() ? -1 : 0); case POSITION_PROP_ACCOUNT_LOGIN : return(this.AccountLogin() > obj.AccountLogin() ? 1 : this.AccountLogin() < obj.AccountLogin() ? -1 : 0); case POSITION_PROP_TIME_CLOSE : return(this.TimeClose() > obj.TimeClose() ? 1 : this.TimeClose() < obj.TimeClose() ? -1 : 0); case POSITION_PROP_TIME_CLOSE_MSC : return(this.TimeCloseMsc() > obj.TimeCloseMsc() ? 1 : this.TimeCloseMsc() < obj.TimeCloseMsc() ? -1 : 0); case POSITION_PROP_VOLUME : return(this.Volume() > obj.Volume() ? 1 : this.Volume() < obj.Volume() ? -1 : 0); case POSITION_PROP_PRICE_OPEN : return(this.PriceOpen() > obj.PriceOpen() ? 1 : this.PriceOpen() < obj.PriceOpen() ? -1 : 0); case POSITION_PROP_SL : return(this.SL() > obj.SL() ? 1 : this.SL() < obj.SL() ? -1 : 0); case POSITION_PROP_TP : return(this.TP() > obj.TP() ? 1 : this.TP() < obj.TP() ? -1 : 0); case POSITION_PROP_PRICE_CURRENT : return(this.PriceCurrent() > obj.PriceCurrent() ? 1 : this.PriceCurrent() < obj.PriceCurrent() ? -1 : 0); case POSITION_PROP_SWAP : return(this.Swap() > obj.Swap() ? 1 : this.Swap() < obj.Swap() ? -1 : 0); case POSITION_PROP_PROFIT : return(this.Profit() > obj.Profit() ? 1 : this.Profit() < obj.Profit() ? -1 : 0); case POSITION_PROP_CONTRACT_SIZE : return(this.ContractSize() > obj.ContractSize() ? 1 : this.ContractSize() < obj.ContractSize() ? -1 : 0); case POSITION_PROP_PRICE_CLOSE : return(this.PriceClose() > obj.PriceClose() ? 1 : this.PriceClose() < obj.PriceClose() ? -1 : 0); case POSITION_PROP_COMMISSIONS : return(this.Commissions() > obj.Commissions() ? 1 : this.Commissions() < obj.Commissions() ? -1 : 0); case POSITION_PROP_FEE : return(this.Fee() > obj.Fee() ? 1 : this.Fee() < obj.Fee() ? -1 : 0); case POSITION_PROP_SYMBOL : return(this.Symbol() > obj.Symbol() ? 1 : this.Symbol() < obj.Symbol() ? -1 : 0); case POSITION_PROP_COMMENT : return(this.Comment() > obj.Comment() ? 1 : this.Comment() < obj.Comment() ? -1 : 0); case POSITION_PROP_EXTERNAL_ID : return(this.ExternalID() > obj.ExternalID() ? 1 : this.ExternalID() < obj.ExternalID() ? -1 : 0); case POSITION_PROP_CURRENCY_PROFIT : return(this.CurrencyProfit() > obj.CurrencyProfit() ? 1 : this.CurrencyProfit() < obj.CurrencyProfit() ? -1 : 0); case POSITION_PROP_ACCOUNT_CURRENCY : return(this.AccountCurrency() > obj.AccountCurrency() ? 1 : this.AccountCurrency() < obj.AccountCurrency() ? -1 : 0); case POSITION_PROP_ACCOUNT_SERVER : return(this.AccountServer() > obj.AccountServer() ? 1 : this.AccountServer() < obj.AccountServer() ? -1 : 0); default : return -1; } }
Dies ist eine virtuelle Methode, die die gleichnamige Methode in der übergeordneten Klasse CObject außer Kraft setzt. Je nach Vergleichsmodus (eine der Eigenschaften des Positionsobjekts) werden diese Eigenschaften für das aktuelle Objekt und für das durch den Zeiger an die Methode übergebene Objekt verglichen. Die Methode gibt 1 zurück, wenn der Wert der aktuellen Objekteigenschaft größer ist als der des Vergleichsobjekts. Ist er kleiner, erhalten wir -1. Wenn die Werte gleich sind, erhalten wir 0.
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')); }
Die Methode, die den Zeiger auf das offene Deal 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; }
In der Schleife über die Liste der Positions-Deals suchen wir nach einem Deal mit der Positionsänderung DEAL_ENTRY_IN (Markteintritt) und geben den Zeiger auf das gefundene Deal zurück
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; }
Wir suchen in der Schleife durch die Liste der Positions-Deals nach einem Deal mit der Positionsänderung DEAL_ENTRY_OUT (Marktaustritt) oder DEAL_ENTRY_OUT_BY (Close by) und geben den Zeiger auf den gefundenen Deal zurück.
Die Methode, die die Ticketnummer des Deals der Eröffnung zurückgibt:
//+------------------------------------------------------------------+ //| Return the open deal ticket | //+------------------------------------------------------------------+ ulong CPosition::DealIn(void) const { CDeal *deal=this.GetDealIn(); return(deal!=NULL ? deal.Ticket() : 0); }
Sie holt den Zeiger auf den Markteintritts-Deal und gibt dessen Ticket zurück.
Die Methode, die die Ticketnummer des Deals des Schließens zurückgibt:
//+------------------------------------------------------------------+ //| Return the close deal ticket | //+------------------------------------------------------------------+ ulong CPosition::DealOut(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Ticket() : 0); }
Sie holt sich den Zeiger auf den Marktaustritts-Deal und gibt dessen Ticket zurück.
Die Methode, die den Spread beim Öffnen zurückgibt:
//+------------------------------------------------------------------+ //| Return spread when opening | //+------------------------------------------------------------------+ int CPosition::SpreadIn(void) const { CDeal *deal=this.GetDealIn(); return(deal!=NULL ? deal.Spread() : 0); }
Sie ermittelt den Zeiger auf den Markteintritts-Deal und gibt den im Deal eingetragenen Spread zurück.
Die Methode, die den Spread beim Schließen zurückgibt:
//+------------------------------------------------------------------+ //| Return spread when closing | //+------------------------------------------------------------------+ int CPosition::SpreadOut(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Spread() : 0); }
Sie ermittelt den Zeiger auf den Marktaustritts-Deal und gibt die in diesem Deal festgelegten Spread zurück.
Die Methode liefert den Briefkurs (Ask) beim Schließen zurück:
//+------------------------------------------------------------------+ //| Return Ask price when closing | //+------------------------------------------------------------------+ double CPosition::PriceOutAsk(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Ask() : 0); }
Sie ermittelt den Zeiger auf den Marktaustritts-Deal und gibt den im Deal festgelegten Wert für den Briefkurs zurück.
Die Methode liefert den Geldkurs (Bid) beim Schließen:
//+------------------------------------------------------------------+ //| Return the Bid price when closing | //+------------------------------------------------------------------+ double CPosition::PriceOutBid(void) const { CDeal *deal=this.GetDealOut(); return(deal!=NULL ? deal.Bid() : 0); }
Sie ermittelt den Zeiger auf den Marktaustritts-Deal und gibt den im Deal festgelegten Geldkurs zurück.
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)::round(this.TypePosition()==POSITION_TYPE_BUY ? (close-open)/this.m_point : (open-close)/this.m_point); }
Die Methode, die die Spanne beim Schließen zurückgibt:
//+------------------------------------------------------------------+ //| Return the spread value when closing | //+------------------------------------------------------------------+ double CPosition::SpreadOutCost(void) const { //--- Get close deal CDeal *deal=this.GetDealOut(); if(deal==NULL) return 0; //--- Get position profit and position profit in points double profit=this.Profit(); int profit_pt=this.ProfitInPoints(); //--- If the profit is zero, return the spread value using the TickValue * Spread * Lots equation if(profit==0) return(this.m_tick_value * deal.Spread() * deal.Volume()); //--- Calculate and return the spread value (proportion) return(profit_pt>0 ? deal.Spread() * ::fabs(profit / profit_pt) : 0); }
Die Methode verwendet zwei Methoden zur Werteberechnung des Spreads:
- Wenn der Positionsgewinn ungleich Null ist, werden die Kosten des Spreads im Verhältnis berechnet: Spreadgröße in Punkten * Positionsgewinn in Geld / Positionsgewinn in Punkten.
- Wenn der Positionsgewinn gleich Null ist, wird der Spread-Wert nach folgender Gleichung berechnet: Berechneter Tick-Wert * Spread-Größe in Punkten * Dealsvolumen.
Die Methode, die die Gesamtprovision für alle Deals festlegt:
//+------------------------------------------------------------------+ //| Set the total commission for all deals | //+------------------------------------------------------------------+ void CPosition::SetCommissions(void) { double res=0; int total=this.m_list_deals.Total(); for(int i=0; i<total; i++) { CDeal *deal=this.m_list_deals.At(i); res+=(deal!=NULL ? deal.Commission() : 0); } this.SetProperty(POSITION_PROP_COMMISSIONS, res); }
Um die Provision für die gesamte Laufzeit der Position zu ermitteln, müssen wir die Provisionen aller Deals in der Position addieren. In der Schleife durch die Liste der Positionsgeschäfte addieren wir die Provision jedes Deals zu dem resultierenden Wert, der schließlich von der Methode zurückgegeben wird.
Die Methode zur Festlegung der gesamten Dealsgebühr:
//+------------------------------------------------------------------+ //| Sets the total deal fee | //+------------------------------------------------------------------+ void CPosition::SetFee(void) { double res=0; int total=this.m_list_deals.Total(); for(int i=0; i<total; i++) { CDeal *deal=this.m_list_deals.At(i); res+=(deal!=NULL ? deal.Fee() : 0); } this.SetProperty(POSITION_PROP_FEE, res); }
Hier ist alles genau gleich wie in der vorherigen Methode - wir geben die Gesamtsumme der Fee-Werte jedes Positionsgeschäfts zurück.
Diese beiden Methoden müssen aufgerufen werden, wenn alle Handelsgeschäfte der Position bereits aufgelistet sind, da das Ergebnis sonst unvollständig ist.
Die Methode, die ein Deal zur Liste der Positionsgeschä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(DEAL_PROP_TICKET); //--- Set the result of checking if a deal with such a ticket is present in the list bool exist=(this.m_list_deals.Search(&this.m_temp_deal)!=WRONG_VALUE); //--- Return sorting by time in milliseconds for the list this.m_list_deals.Sort(DEAL_PROP_TIME_MSC); //--- If a deal with such a ticket is already in the list, return NULL if(exist) 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 if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { this.SetProfit(deal.Profit()); this.SetSwap(deal.Swap()); } //--- Return the pointer to the created deal object return deal; }
Die Methodenlogik wird in den Codekommentaren beschrieben. Die Methode erhält das Ticket des aktuell ausgewählten Deals. Wenn es noch keine Deals mit einem solchen Ticket in der Liste gibt, wird ein neues Dealsobjekt erstellt und zur Liste der Positionsgeschäfte hinzugefügt.
Die Methoden, die Beschreibungen einiger Positionseigenschaften zurückgeben:
//+------------------------------------------------------------------+ //| Return a position type description | //+------------------------------------------------------------------+ string CPosition::TypeDescription(void) const { return(this.TypePosition()==POSITION_TYPE_BUY ? "Buy" : this.TypePosition()==POSITION_TYPE_SELL ? "Sell" : "Unknown::"+(string)this.TypePosition()); } //+------------------------------------------------------------------+ //| 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())); } //+------------------------------------------------------------------+ //| 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())); } //+------------------------------------------------------------------+ //| Return a brief position description | //+------------------------------------------------------------------+ string CPosition::Description(void) { return(::StringFormat("%I64d (%s): %s %.2f %s #%I64d, Magic %I64d", this.AccountLogin(), this.AccountServer(), this.Symbol(), this.Volume(), this.TypeDescription(), this.ID(), this.Magic())); }
Diese Methoden werden z.B. verwendet, um eine Stellenbeschreibung im Journal anzuzeigen.
Mit der Methode „Print“ kann die Positionsbeschreibung im Journal angezeigt werden:
//+------------------------------------------------------------------+ //| 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 Positionsbeschreibung gedruckt. Dann wird eine Beschreibung jedes Deals mit Hilfe der Print()-Methode in einer Schleife über alle Positionsgeschäfte gedruckt.
Die historische Positionsklasse ist fertig. Lassen Sie uns nun eine statische Klasse für die Auswahl, Suche und Sortierung von Deals und Positionen nach ihren Eigenschaften erstellen.
Klasse zum Suchen und Sortieren nach Eigenschaften von Deals und Positionen
Diese Klasse wurde besprochen in dem Artikel „Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil III). Erhebung (Collection) von Marktorders und Positionen“ (Anordnen des Suchbereichs).
In \MQL5\Services\AccountReporter\ erstellen wir die neue Datei Select.mqh der Klasse CSelect:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2024, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { }
Wir definieren die die Enumeration der Vergleichsmodi, binden die Dateien der Deal- und Positionsklassen ein und deklarieren die Speicherliste:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2024, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" enum ENUM_COMPARER_TYPE { EQUAL, // Equal MORE, // More LESS, // Less NO_EQUAL, // Not equal EQUAL_OR_MORE, // Equal or more EQUAL_OR_LESS // Equal or less }; //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Deal.mqh" #include "Position.mqh" //+------------------------------------------------------------------+ //| Storage list | //+------------------------------------------------------------------+ CArrayObj ListStorage; // Storage object for storing sorted collection lists //+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { }
Wir schreiben alle Methoden zur Auswahl von Objekten und zur Erstellung von Listen, die den Suchkriterien entsprechen:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2024, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" enum ENUM_COMPARER_TYPE // Comparison modes { EQUAL, // Equal MORE, // More LESS, // Less NO_EQUAL, // Not equal EQUAL_OR_MORE, // Equal or more EQUAL_OR_LESS // Equal or less }; //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Deal.mqh" #include "Position.mqh" //+------------------------------------------------------------------+ //| Storage list | //+------------------------------------------------------------------+ CArrayObj ListStorage; // Storage object for storing sorted collection lists //+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { private: //--- Method for comparing two values template<typename T> static bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); public: //+------------------------------------------------------------------+ //| Deal handling methods | //+------------------------------------------------------------------+ //--- Return the list of deals with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode); //--- Return the deal index with the maximum value of the (1) integer, (2) real and (3) string properties static int FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property); static int FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property); static int FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property); //--- Return the deal index with the minimum value of the (1) integer, (2) real and (3) string properties static int FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property); static int FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property); static int FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property); //+------------------------------------------------------------------+ //| Position handling methods | //+------------------------------------------------------------------+ //--- Return the list of positions with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode); //--- Return the position index with the maximum value of the (1) integer, (2) real and (3) string properties static int FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property); static int FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property); static int FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property); //--- Return the position index with the minimum value of the (1) integer, (2) real and (3) string properties static int FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property); static int FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property); static int FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property); }; //+------------------------------------------------------------------+ //| Method for comparing two values | //+------------------------------------------------------------------+ template<typename T> bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode) { switch(mode) { case EQUAL : return(value1==value2 ? true : false); case NO_EQUAL : return(value1!=value2 ? true : false); case MORE : return(value1>value2 ? true : false); case LESS : return(value1<value2 ? true : false); case EQUAL_OR_MORE : return(value1>=value2 ? true : false); case EQUAL_OR_LESS : return(value1<=value2 ? true : false); default : return false; } } //+------------------------------------------------------------------+ //| Deal list handling methods | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of deals with one integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } int total=list_source.Total(); for(int i=0; i<total; i++) { CDeal *obj=list_source.At(i); long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop, value, mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of deals with one real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } for(int i=0; i<list_source.Total(); i++) { CDeal *obj=list_source.At(i); double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of deals with one string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } for(int i=0; i<list_source.Total(); i++) { CDeal *obj=list_source.At(i); string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDeal *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDeal *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDeal *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_INT property) { int index=0; CDeal *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_DBL property) { int index=0; CDeal *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the deal index in the list | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_STR property) { int index=0; CDeal *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDeal *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Position list handling method | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of positions with one integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } int total=list_source.Total(); for(int i=0; i<total; i++) { CPosition *obj=list_source.At(i); long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop, value, mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of positions with one real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } for(int i=0; i<list_source.Total(); i++) { CPosition *obj=list_source.At(i); double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of positions with one string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); if(!ListStorage.Add(list)) { delete list; return NULL; } for(int i=0; i<list_source.Total(); i++) { CPosition *obj=list_source.At(i); string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CPosition *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CPosition *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CPosition *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_INT property) { int index=0; CPosition *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_DBL property) { int index=0; CPosition *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the position index in the list | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_STR property) { int index=0; CPosition *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CPosition *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; }
Siehe im Abschnitt „Einrichten der Suche“ im oben genannten Artikel.
Jetzt können wir eine Klasse erstellen, die die Liste der historischen Positionen verarbeitet.
Die Klasse für die Kollektion der historischen Positionen
Im Terminalordner \MQL5\Services\AccountReporter\ erstellen wir eine die Datei PositionsControl.mqh für die Klasse CPositionsControl.
Die Klasse sollte vom Basisobjekt CObject der Standardbibliothek abgeleitet werden, während die Klasse für historische Positionen sowie die Such- und Filterklassen in die zu erstellende Datei aufgenommen werden sollten:
//+------------------------------------------------------------------+ //| 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" #include "Select.mqh" //+------------------------------------------------------------------+ //| Collection class of historical positions | //+------------------------------------------------------------------+ class CPositionsControl : public CObject { }
Wir deklarieren die privaten, geschützten und öffentlichen Methoden der Klasse:
//+------------------------------------------------------------------+ //| Collection class of historical positions | //+------------------------------------------------------------------+ class CPositionsControl : public CObject { private: //--- Return (1) position type and (2) reason for opening by deal type ENUM_POSITION_TYPE PositionTypeByDeal(const CDeal *deal); ENUM_POSITION_REASON PositionReasonByDeal(const CDeal *deal); protected: CPosition m_temp_pos; // Temporary position object for searching CArrayObj m_list_pos; // List of positions //--- 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); public: //--- Create and update the list of positions. It can be redefined in the inherited classes virtual bool Refresh(void); //--- Return (1) the list, (2) number of positions in the list CArrayObj *GetPositionsList(void) { return &this.m_list_pos; } int PositionsTotal(void) const { return this.m_list_pos.Total(); } //--- Print the properties of all positions and their deals in the journal void Print(void); //--- Constructor/destructor CPositionsControl(void); ~CPositionsControl(); };
Betrachten wir nun die Implementierungen der angegebenen Methoden.
Wir setzen im Klassenkonstruktor das Sortier-Flag für die Liste der historischen Positionen nach Schließzeit in Millisekunden:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPositionsControl::CPositionsControl(void) { this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC); }
Im Destruktor der Klasse löschen wir die Liste der historischen Positionen:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPositionsControl::~CPositionsControl() { this.m_list_pos.Shutdown(); }
Die Methode, die den Zeiger auf das 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(POSITION_PROP_IDENTIFIER); //--- Get the index of the position object with the specified 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); //--- Return 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(POSITION_PROP_TIME_CLOSE_MSC); return pos; }
Die Methode, die das Flag der Marktposition zurückgibt:
//+------------------------------------------------------------------+ //| 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; }
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; } }
Je nach Typ des Deals wird die entsprechende Positionsart zurückgegeben.
Die Methode liefert den Grund für die Eröffnung einer Position nach dem Typ des Deals:
//+------------------------------------------------------------------+ //| Returns the reason for opening a position by deal type | //+------------------------------------------------------------------+ ENUM_POSITION_REASON CPositionsControl::PositionReasonByDeal(const CDeal *deal) { if(deal==NULL) return WRONG_VALUE; switch(deal.Reason()) { case DEAL_REASON_CLIENT : return POSITION_REASON_CLIENT; case DEAL_REASON_MOBILE : return POSITION_REASON_MOBILE; case DEAL_REASON_WEB : return POSITION_REASON_WEB; case DEAL_REASON_EXPERT : return POSITION_REASON_EXPERT; default : return WRONG_VALUE; } }
Je nach dem Grund für den Deal wird der entsprechende Grund für die Eröffnung der Position zurückgegeben.
Die Methode zur Erstellung oder Aktualisierung 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(POSITION_PROP_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, 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)) 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 string pos_symbol=HistoryDealGetString(ticket, DEAL_SYMBOL); pos=new CPosition(pos_id, pos_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.SetTicket(deal.Order()); pos.SetMagic(deal.Magic()); pos.SetTime(deal.Time()); pos.SetTimeMsc(deal.TimeMsc()); ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); ENUM_POSITION_REASON reason=this.PositionReasonByDeal(deal); pos.SetReason(reason); pos.SetPriceOpen(deal.Price()); pos.SetVolume(deal.Volume()); } if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { pos.SetPriceCurrent(deal.Price()); pos.SetPriceClose(deal.Price()); pos.SetTimeClose(deal.Time()); pos.SetTimeCloseMsc(deal.TimeMsc()); } if(deal.Entry()==DEAL_ENTRY_INOUT) { ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); pos.SetVolume(deal.Volume()-pos.Volume()); } } //--- All historical positions are created and the corresponding deals are added to the deal lists of the position objects //--- Set the flag of sorting by close time in milliseconds for the position list this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC); //--- In the loop through the created list of closed positions, we set the Commissions and Fee values for each position for(int i=0; i<this.m_list_pos.Total(); i++) { CPosition *pos=this.m_list_pos.At(i); if(pos==NULL) continue; pos.SetCommissions(); pos.SetFee(); } //--- Return the result of creating and adding a position to the list return res; }
In einer Schleife durch die Liste der Deals im Terminal erhalten wir den nächsten Deal und prüfen dessen Positions-ID. Wenn es sich um eine Marktposition handelt, sollten wir den Deal nicht auslassen. Wenn eine solche Position noch nicht in der Liste der historischen Positionen enthalten ist, legen wir ein neues Positionsobjekt an und platzieren es in der Liste der historischen Positionen. Wenn im historischen Positions-Objekt noch keine Deals mit dem Ticket des ausgewählten Deals vorhanden sind, wird das Deal in die Liste der Bestandsobjektgeschäfte aufgenommen. Am Ende der Schleife zur Erstellung historischer Positions-Objekte setzen wir für jede Position eine gemeinsame Provision und eine Deals-Gebühr für alle Positions-Deals fest. Die Methode ist virtuell, was es uns ermöglicht, eine optimalere Logik in der abgteleiteten Klasse zu erstellen, wenn die Aktualisierung der Liste der Positionen viel häufiger als mindestens einmal pro Tag erforderlich ist.
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(); } }
Für den Fall, dass wir die Liste der erstellten historischen Positionen kontrollieren müssen, ermöglicht uns diese Methode, jede Position mit ihren Deals im Journal anzuzeigen.
Die Service-App „merkt“ sich alle Konten, mit denen sie während des laufenden Servicebetriebs verbunden war. Mit anderen Worten: Wenn es keine Neustarts des Terminals gab und eine Verbindung zu verschiedenen Konten und Handelsservern bestand, dann merkt sich das Programm diese Konten, die wiederum die Listen aller geschlossenen Positionen speichern. Handelsberichte werden für geschlossene Positionen angezeigt, die auf jedem der verbundenen Konten vorhanden waren. Oder wenn die Einstellungen vorsehen, dass nur Berichte über das aktuelle Konto angezeigt werden, werden die Listen der geschlossenen Positionen nach dem aktuellen Konto-Login und dem Server sortiert.
Aus den obigen Ausführungen ergibt sich, dass wir eine Kontoklasse benötigen, in der eine Verwaltungsklasse für die Liste der auf diesem Konto gehandelten geschlossenen Positionen gespeichert wird. In der Service-App erhalten wir das gewünschte Konto, über das wir wiederum die Liste der geschlossenen Positionen abrufen können.
Die Konto-Klasse
In \MQL5\Services\AccountReporter\ erstellen wir die neue Datei Account.mqh der Klasse CAccount.
Die Klasse sollte vom Basisobjekt CObject der Standardbibliothek abgeleitet werden, während die Klassendatei der Kollektion der historischen Positionen in die erstellte Datei aufgenommen werden sollte:
//+------------------------------------------------------------------+ //| Account.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 "PositionsControl.mqh" //+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CObject { }
Im geschützten Teil der Klasse deklarieren wir das Objekt der Kontrolle der historischen Positionen (Klasse der Liste der geschlossenen Kontopositionen) und die Liste der Integer-, Real- und String-Eigenschaften:
//+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CObject { private: protected: CPositionsControl m_positions; // Historical positions control object //--- account integer properties long m_login; // Account number ENUM_ACCOUNT_TRADE_MODE m_trade_mode; // Trading account type long m_leverage; // Leverage int m_limit_orders; // Maximum allowed number of active pending orders ENUM_ACCOUNT_STOPOUT_MODE m_margin_so_mode; // Mode of setting the minimum available margin level bool m_trade_allowed; // Trading permission of the current account bool m_trade_expert; // Trading permission of an EA ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; // Margin calculation mode int m_currency_digits; // Number of digits for an account currency necessary for accurate display of trading results bool m_fifo_close; // The flag indicating that positions can be closed only by the FIFO rule bool m_hedge_allowed; // Allowed opposite positions on a single symbol //--- account real properties double m_balance; // Account balance in a deposit currency double m_credit; // Credit in a deposit currency double m_profit; // Current profit on an account in the account currency double m_equity; // Equity on an account in the deposit currency double m_margin; // Reserved margin on an account in a deposit currency double m_margin_free; // Free funds available for opening a position in a deposit currency double m_margin_level; // Margin level on an account in % double m_margin_so_call; // Margin Call level double m_margin_so_so; // Stop Out level double m_margin_initial; // Funds reserved on an account to ensure a guarantee amount for all pending orders double m_margin_maintenance; // Funds reserved on an account to ensure a minimum amount for all open positions double m_assets; // Current assets on an account double m_liabilities; // Current liabilities on an account double m_commission_blocked; // Current sum of blocked commissions on an account //--- account string properties string m_name; // Client name string m_server; // Trade server name string m_currency; // Deposit currency string m_company; // Name of a company serving account public:
Im Abschnitt „public“ erstellen wir die Methoden für die Behandlung von Listen, die Methoden zum Setzen und Zurückgeben von Eigenschaften des Kontoobjekts und andere fest:
public: //--- Return the (1) control object, (2) the list of historical positions, (3) number of positions CPositionsControl*GetPositionsCtrlObj(void) { return &this.m_positions; } CArrayObj *GetPositionsList(void) { return this.m_positions.GetPositionsList();} int PositionsTotal(void) { return this.m_positions.PositionsTotal(); } //--- Return the list of positions by (1) integer, (2) real and (3) string property CArrayObj *GetPositionsList(ENUM_POSITION_PROPERTY_INT property, long value, ENUM_COMPARER_TYPE mode) { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); } CArrayObj *GetPositionsList(ENUM_POSITION_PROPERTY_DBL property, double value, ENUM_COMPARER_TYPE mode) { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); } CArrayObj *GetPositionsList(ENUM_POSITION_PROPERTY_STR property, string value, ENUM_COMPARER_TYPE mode) { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); } //--- (1) Update and (2) print the list of closed positions in the journal bool PositionsRefresh(void) { return this.m_positions.Refresh();} void PositionsPrint(void) { this.m_positions.Print(); } //--- set (1) login and (2) server void SetLogin(const long login) { this.m_login=login; } void SetServer(const string server) { this.m_server=server; } //--- return integer account properties long Login(void) const { return this.m_login; } // Account number ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const { return this.m_trade_mode; } // Trading account type long Leverage(void) const { return this.m_leverage; } // Provided leverage int LimitOrders(void) const { return this.m_limit_orders; } // Maximum allowed number of active pending orders ENUM_ACCOUNT_STOPOUT_MODE MarginSoMode(void) const { return this.m_margin_so_mode; } // Mode of setting the minimum available margin level bool TradeAllowed(void) const { return this.m_trade_allowed; } // Trading permission of the current account bool TradeExpert(void) const { return this.m_trade_expert; } // Trading permission for EA ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const { return this.m_margin_mode; } // Margin calculation mode int CurrencyDigits(void) const { return this.m_currency_digits; } // Number of digits for an account currency necessary for accurate display of trading results bool FIFOClose(void) const { return this.m_fifo_close; } // The flag indicating that positions can be closed only by the FIFO rule bool HedgeAllowed(void) const { return this.m_hedge_allowed; } // Allowed opposite positions on a single symbol //--- return real account properties double Balance(void) const { return this.m_balance; } // Account balance in a deposit currency double Credit(void) const { return this.m_credit; } // Credit in deposit currency double Profit(void) const { return this.m_profit; } // Current profit on an account in the account currency double Equity(void) const { return this.m_equity; } // Available equity in the deposit currency double Margin(void) const { return this.m_margin; } // The amount of reserved collateral funds on the account in the deposit currency double MarginFree(void) const { return this.m_margin_free; } // Free funds available for opening a position in a deposit currency double MarginLevel(void) const { return this.m_margin_level; } // Margin level on an account in % double MarginSoCall(void) const { return this.m_margin_so_call; } // Margin Call level double MarginSoSo(void) const { return this.m_margin_so_so; } // Stop Out level double MarginInitial(void) const { return this.m_margin_initial; } // Funds reserved on an account to ensure a guarantee amount for all pending orders double MarginMaintenance(void) const { return this.m_margin_maintenance; } // Funds reserved on an account to ensure the minimum amount for all open positions double Assets(void) const { return this.m_assets; } // Current assets on an account double Liabilities(void) const { return this.m_liabilities; } // Current amount of liabilities on the account double CommissionBlocked(void) const { return this.m_commission_blocked; } // Current sum of blocked commissions on an account //--- return account string properties string Name(void) const { return this.m_name; } // Client name string Server(void) const { return this.m_server; } // Trade server name string Currency(void) const { return this.m_currency; } // Deposit currency string Company(void) const { return this.m_company; } // Name of the company servicing the account //--- return (1) account description, (2) trading account type and (3) margin calculation mode string Description(void) const; string TradeModeDescription(void) const; string MarginModeDescription(void)const; //--- virtual method for comparing two objects virtual int Compare(const CObject *node,const int mode=0) const; //--- Display the account description in the journal void Print(void) { ::Print(this.Description()); } //--- constructors/destructor CAccount(void){} CAccount(const long login, const string server_name); ~CAccount() {} };
Betrachten wir nun die Implementierung der angegebenen Methoden.
Im Konstruktor der Klasse legen wir alle Eigenschaften des aktuellen Kontos auf das Objekt fest:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(const long login, const string server_name) { this.m_login=login; this.m_server=server_name; //--- set account integer properties this.m_trade_mode = (ENUM_ACCOUNT_TRADE_MODE)::AccountInfoInteger(ACCOUNT_TRADE_MODE); // Trading account type this.m_leverage = ::AccountInfoInteger(ACCOUNT_LEVERAGE); // Leverage this.m_limit_orders = (int)::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); // Maximum allowed number of active pending orders this.m_margin_so_mode = (ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);// Mode of setting the minimum available margin level this.m_trade_allowed = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); // Trading permission of the current account this.m_trade_expert = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); // Trading permission of an EA this.m_margin_mode = (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE); // Margin calculation mode this.m_currency_digits = (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS); // Number of digits for an account currency necessary for accurate display of trading results this.m_fifo_close = ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE); // The flag indicating that positions can be closed only by the FIFO rule this.m_hedge_allowed = ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED); // Allowed opposite positions on a single symbol //--- set account real properties this.m_balance = ::AccountInfoDouble(ACCOUNT_BALANCE); // Account balance in a deposit currency this.m_credit = ::AccountInfoDouble(ACCOUNT_CREDIT); // Credit in a deposit currency this.m_profit = ::AccountInfoDouble(ACCOUNT_PROFIT); // Current profit on an account in the account currency this.m_equity = ::AccountInfoDouble(ACCOUNT_EQUITY); // Equity on an account in the deposit currency this.m_margin = ::AccountInfoDouble(ACCOUNT_MARGIN); // Reserved margin on an account in a deposit currency this.m_margin_free = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); // Free funds available for opening a position in a deposit currency this.m_margin_level = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); // Margin level on an account in % this.m_margin_so_call = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); // Margin Call level this.m_margin_so_so = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); // Stop Out level this.m_margin_initial = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); // Funds reserved on an account to ensure a guarantee amount for all pending orders this.m_margin_maintenance = ::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); // Funds reserved on an account to ensure a minimum amount for all open positions this.m_assets = ::AccountInfoDouble(ACCOUNT_ASSETS); // Current assets on an account this.m_liabilities = ::AccountInfoDouble(ACCOUNT_LIABILITIES); // Current liabilities on an account this.m_commission_blocked = ::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); // Current sum of blocked commissions on an account //--- set account string properties this.m_name = ::AccountInfoString(ACCOUNT_NAME); // Client name this.m_currency = ::AccountInfoString(ACCOUNT_CURRENCY); // Deposit currency this.m_company = ::AccountInfoString(ACCOUNT_COMPANY); // Name of a company serving account }
Die Methode zum Vergleich zweier Objekte:
//+------------------------------------------------------------------+ //| Method for comparing two objects | //+------------------------------------------------------------------+ int CAccount::Compare(const CObject *node,const int mode=0) const { const CAccount *obj=node; return(this.Login()>obj.Login() ? 1 : this.Login()<obj.Login() ? -1 : this.Server()>obj.Server() ? 1 : this.Server()<obj.Server() ? -1 : 0); }
Die Methode vergleicht zwei Kontoobjekte anhand von nur zwei Eigenschaften - Login und Servername. Wenn die Logins der beiden verglichenen Objekte gleich sind, wird die Gleichheit des Servernamens geprüft. Wenn auch die Server gleich sind, dann sind die beiden Objekte gleich. Andernfalls wird entweder 1 oder -1 zurückgegeben, je nachdem, ob der Wert der zu vergleichenden Eigenschaft zwischen den beiden Objekten größer oder kleiner ist.
Die Methoden, die Beschreibungen einiger Eigenschaften von Kontoobjekten zurückgeben:
//+------------------------------------------------------------------+ //| Return the description of the trading account type | //+------------------------------------------------------------------+ string CAccount::TradeModeDescription(void) const { string mode=::StringSubstr(::EnumToString(this.TradeMode()), 19); if(mode.Lower()) mode.SetChar(0, ushort(mode.GetChar(0)-32)); return mode; } //+------------------------------------------------------------------+ //| Return the description of the margin calculation mode | //+------------------------------------------------------------------+ string CAccount::MarginModeDescription(void) const { string mode=::StringSubstr(::EnumToString(this.MarginMode()), 20); ::StringReplace(mode, "RETAIL_", ""); if(mode.Lower()) mode.SetChar(0, ushort(mode.GetChar(0)-32)); return mode; }
Diese Methoden werden verwendet, um die Kontobeschreibung in der Methode Description zu erstellen:
//+------------------------------------------------------------------+ //| Return the account description | //+------------------------------------------------------------------+ string CAccount::Description(void) const { return(::StringFormat("%I64d: %s (%s, %s, %.2f %s, %s)", this.Login(), this.Name(), this.Company(), this.TradeModeDescription(), this.Balance(), this.Currency(), this.MarginModeDescription())); }
Die Methode gibt eine solche Zeichenkette aus
68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging)
Diese Zeichenfolge kann mit der Methode Print() der Klasse in das Protokoll gedruckt werden.
Nun müssen wir eine Klasse erstellen, die eine Liste aller Konten speichert, mit denen während des Betriebs der Service-App eine Verbindung hergestellt wurde.
Klassensammlung von Konten
Im Terminalordner \MT5\MQL5\Services\AccountReporter\ erstellen wir eine neue Datei Accounts.mqh der Klasse CAccounts.
Die Klasse sollte von dem Basisobjekt CObject der Standardbibliothek abgeleitet werden, während die Kontoklassendatei in die erstellte Datei aufgenommen werden sollte:
//+------------------------------------------------------------------+ //| Accounts.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 "Account.mqh" //+------------------------------------------------------------------+ //| Account collection class | //+------------------------------------------------------------------+ class CAccounts : public CObject { }
Wir deklarieren die Methoden für den Klassenbetrieb in den Abschnitten privat, geschützt und öffentlich (private, protected , public):
//+------------------------------------------------------------------+ //| Account collection class | //+------------------------------------------------------------------+ class CAccounts : public CObject { private: CArrayObj m_list; // List of account objects CAccount m_tmp; // Temporary account object for searching protected: //--- Create a new account object and add it to the list CAccount *Add(const long login, const string server); public: //--- Create a new account object bool Create(const long login, const string server); //--- Return the pointer to the specified account object by (1) login and server, (2) index in the list CAccount *Get(const long login, const string server); CAccount *Get(const int index) const { return this.m_list.At(index); } //--- Combine the lists of account positions and return the combined one CArrayObj *GetCommonPositionsList(void); //--- Return the list of positions for the specified account CArrayObj *GetAccountPositionsList(const long login, const string server); //--- Return the number of stored accounts int Total(void) const { return this.m_list.Total(); } //--- Update the lists of positions of the specified account bool PositionsRefresh(const long login, const string server); //--- Constructor/destructor CAccounts(); ~CAccounts(); };
Betrachten wir nun die Implementierung der angegebenen Methoden.
Setzen Sie im Klassenkonstruktor das Flag für sortierte Liste auf die Liste der Konten:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccounts::CAccounts() { this.m_list.Sort(); }
Im Destruktor der Klasse löschen wir die Liste der Konten :
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAccounts::~CAccounts() { this.m_list.Clear(); }
Geschützte Methode, die ein neues Kontoobjekt erstellt und der Liste hinzufügt:
//+------------------------------------------------------------------+ //| Create a new account object and add it to the list | //+------------------------------------------------------------------+ CAccount *CAccounts::Add(const long login,const string server) { //--- Create a new account object CAccount *account=new CAccount(login, server); if(account==NULL) return NULL; //--- If the created object is not added to the list, remove it and return NULL if(!this.m_list.Add(account)) { delete account; return NULL; } //--- Return the pointer to a created object return account; }
Dies ist eine geschützte Methode, die als Teil der öffentlichen Methode funktioniert, die ein neues Kontoobjekt erstellt:
//+------------------------------------------------------------------+ //| Create a new account object | //+------------------------------------------------------------------+ bool CAccounts::Create(const long login,const string server) { //--- Set login and server to the temporary account object this.m_tmp.SetLogin(login); this.m_tmp.SetServer(server); //--- Set the sorted list flag for the account object list //--- and get the object index having the same login and server as the ones the temporary object has this.m_list.Sort(); int index=this.m_list.Search(&this.m_tmp); //--- Return the flag of an object being successfully added to the list (Add method operation result) or 'false' if the object is already in the list return(index==WRONG_VALUE ? this.Add(login, server)!=NULL : false); }
Die Methode, die den Zeiger auf das angegebene Kontoobjekt zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the specified account object | //+------------------------------------------------------------------+ CAccount *CAccounts::Get(const long login,const string server) { //--- Set login and server to the temporary account object this.m_tmp.SetLogin(login); this.m_tmp.SetServer(server); //--- Set the sorted list flag for the account object list //--- and get the object index having the same login and server as the ones the temporary object has this.m_list.Sort(); int index=this.m_list.Search(&this.m_tmp); //--- Return the pointer to the object in the list by index or NULL if the index is -1 return this.m_list.At(index); }
Die Methode, die die Listen der Positionen des angegebenen Kontos aktualisiert:
//+------------------------------------------------------------------+ //| Update the lists of positions of the specified account | //+------------------------------------------------------------------+ bool CAccounts::PositionsRefresh(const long login, const string server) { //--- Get the pointer to the account object with the specified login and server CAccount *account=this.Get(login, server); if(account==NULL) return false; //--- If the received object is not the current account, if(account.Login()!=::AccountInfoInteger(ACCOUNT_LOGIN) || account.Server()!=::AccountInfoString(ACCOUNT_SERVER)) { //--- inform that updating data of the non-current account will result in incorrect data and return 'false' ::Print("Error. Updating the list of positions for a non-current account will result in incorrect data."); return false; } //--- Return the result of updating the current account data return account.PositionsRefresh(); }
Die Methode, die die Listen der Kontenpositionen kombiniert und eine kombinierte Liste zurückgibt:
//+--------------------------------------------------------------------+ //| Combine the lists of account positions and return the combined one | //+--------------------------------------------------------------------+ CArrayObj *CAccounts::GetCommonPositionsList(void) { //--- Create a new list and reset the flag of managing memory CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); //--- In the loop through the list of accounts, int total=this.m_list.Total(); for(int i=0; i<total; i++) { //--- get another account object CAccount *account=this.m_list.At(i); if(account==NULL) continue; //--- Get the list of closed account positions CArrayObj *src=account.GetPositionsList(); if(src==NULL) continue; //--- If this is the first account in the list, if(i==0) { //--- copy the elements from the account positions list to the new list if(!list.AssignArray(src)) { delete list; return NULL; } } //--- If this is not the first account in the list, else { //--- add elements from the account position list to the end of the new list if(!list.AddArray(src)) continue; } } //--- Send a new list to the storage if(!ListStorage.Add(list)) { delete list; return NULL; } //--- Return the pointer to the created and filled list return list; }
Die Methode, die die Liste der Positionen für das angegebene Konto zurückgibt:
//+------------------------------------------------------------------+ //| Return the list of positions for the specified account | //+------------------------------------------------------------------+ CArrayObj *CAccounts::GetAccountPositionsList(const long login,const string server) { CAccount *account=this.Get(login, server); return(account!=NULL ? account.GetPositionsList() : NULL); }
Ermittelt den Zeiger auf das Kontoobjekt nach Login und Server und gibt den Zeiger auf die Liste der historischen Positionen zurück oder NULL, wenn das Kontoobjekt nicht ermittelt werden konnte.
Alle Methoden dieser Klasse werden in den Kommentaren ausführlich beschrieben. Wenn etwas noch unklar ist, können Sie in der Artikeldiskussion Fragen stellen.
Alle Klassen, die die Grundlage der Service-App bilden, sind fertig. Beginnen wir mit der Umsetzung des Programms selbst.
Service-App zur Erstellung von Handelsberichten und zum Versand von Benachrichtigungen
Lassen Sie uns entscheiden, wie das Programm funktionieren soll.
Beim Start des Dienstes werden das Vorhandensein einer MetaQuotes-ID auf dem Client-Terminal und die Erlaubnis, Push-Benachrichtigungen an das Smartphone zu senden, überprüft.
Diese Einstellungen finden Sie im Menü Extras - Optionen auf der Registerkarte Benachrichtigungen:
Wenn das Feld MetaQuotes ID keinen Wert enthält oder das Kontrollkästchen Push-Benachrichtigungen aktivieren nicht aktiviert ist, zeigt der Dienst ein Fenster an, in dem Sie aufgefordert werden, diese Parameter einzustellen. Wenn Sie diese Parameter nicht einstellen, sollten Sie eine Warnung erhalten, dass es keine MQID gibt oder dass das Senden von Benachrichtigungen an das Smartphone nicht erlaubt ist und dass alle Nachrichten nur im Journal erscheinen. Wenn wir alle Parameter einstellen, werden die Berichte sowohl an das Smartphone als auch an das Experts Terminal Journal gesendet. In der Hauptschleife überprüft das Programm ständig den Status der Einstellungen für das Senden von Benachrichtigungen im Terminal. Wenn also die Berechtigung zum Senden von Benachrichtigungen beim Start des Dienstes nicht gesetzt wurde, können wir sie jederzeit nach dem Start des Dienstprogramms aktivieren - es wird die Änderungen sehen und das entsprechende Flag aktivieren.
In den Diensteinstellungen können wir die Parameter für den Nachrichtenversand und die Zeiträume auswählen, für die wir Berichte erstellen möchten:
- Allgemeine Berichtsparameter
- welche Konten für Berichte verwendet werden sollen: (alle oder das aktuelle Konto),
- ob Berichte nach Symbolen erstellt werden sollen: (ja/nein) — zunächst wird ein Bericht erstellt, aus dem dann separate Berichte für jedes am Handel beteiligte Symbol erstellt werden,
- ob Berichte nach magischen Zahlen erstellt werden sollen: (ja/nein) — es wird ein Bericht erstellt, aus dem dann für jede der beim Handel verwendeten magischen Zahlen ein eigener Bericht erstellt wird,
- ob Provisionen in den Bericht aufgenommen werden sollen: (ja/nein) — wenn aktiviert, werden die Kosten für Provisionen, Swaps und Gebühren für die Durchführung von Deals zusätzlich zum Gesamtbetrag aller Kosten separat angezeigt,
- ob mögliche Verluste auf Spreads beim Schließen von Positionen in den Bericht aufgenommen werden sollen: (ja/nein) — wenn aktiviert, wird die Summe aller möglichen Spreadkosten beim Schließen separat angezeigt;
- Einstellungen für den Tagesbericht
- ob Berichte über die letzten 24 Stunden gesendet werden sollen; dies gilt auch für Berichte für bestimmte Zeiträume: (ja/nein) — wenn aktiviert, werden Berichte für die letzten 24 Stunden und für konfigurierbare Handelszeitintervalle (für die Anzahl von Tagen, Monaten und Jahren) täglich zur angegebenen Zeit gesendet,
- Sendezeit des Berichts: (Standardwert 8),
- Sendeminuten des Berichts: (Standardwert 0);
- Tägliche Berichtseinstellungen für nutzerdefinierte Zeiträume
- ob Berichte für die angegebene Anzahl von Tagen gesendet werden sollen: (ja/nein) — wenn aktiviert, werden Berichte für die angegebene Anzahl von Tagen täglich zur oben angegebenen Zeit erstellt; die Anzahl der Tage des Berichts wird berechnet, indem die angegebene Anzahl von Tagen vom aktuellen Datum abgezogen wird,
- Anzahl der Tage für Berichte über die angegebene Anzahl von Tagen: (Standardwert 7),
- ob Berichte für die angegebene Anzahl von Monaten gesendet werden sollen: (ja/nein) — wenn aktiviert, werden Berichte für die angegebene Anzahl von Monaten täglich zu der oben angegebenen Zeit erstellt; die Anzahl der Monate des Berichts wird berechnet, indem die angegebene Anzahl von Monaten vom aktuellen Datum abgezogen wird,
- Anzahl der Monate für Berichte über die angegebene Anzahl von Monaten: (Standardwert 3),
- ob Berichte für die angegebene Anzahl von Jahren gesendet werden sollen: (ja/nein) — wenn aktiviert, werden Berichte für die angegebene Anzahl von Jahren täglich zu der oben angegebenen Zeit erstellt; die Anzahl der Jahre des Berichts wird berechnet, indem die angegebene Anzahl von Jahren vom aktuellen Datum abgezogen wird,
- Anzahl der Jahre für Berichte über die angegebene Anzahl von Jahren: (Standardwert 2);
- Wöchentliche Berichtseinstellungen für alle anderen Zeiträume
- Wochentag für den Versand von Wochenberichten: (Standard ist Samstag) — wenn der angegebene Tag kommt, werden die in den Einstellungen unten angegebenen Berichte erstellt und gesendet,
- Sende-Stunde des Berichts: (Standardwert 8),
- Sende-Minute des Berichts: (Standardwert 0),
- ob Berichte ab dem Beginn der aktuellen Woche gesendet werden sollen: (ja/nein) — wenn aktiviert, wird wöchentlich am angegebenen Tag ein Bericht vom Beginn der aktuellen Woche erstellt,
- ob Berichte ab dem Beginn des aktuellen Monats gesendet werden sollen: (ja/nein) — wenn aktiviert, wird wöchentlich am angegebenen Tag ein Bericht vom Anfang des aktuellen Monats erstellt,
- ob Berichte vom Beginn des laufenden Jahres gesendet werden sollen: (ja/nein) — wenn aktiviert, wird wöchentlich am angegebenen Tag ein Bericht vom Beginn des laufenden Jahres erstellt,
- ob Berichte für den gesamten Handelszeitraum gesendet werden sollen: (ja/nein) — wenn aktiviert, wird wöchentlich am angegebenen Tag ein Bericht für den gesamten Handelszeitraum erstellt.
Diese Einstellungen reichen aus, um die meisten Handelsperioden abzudecken, die für die Erstellung von Berichten über sie von Interesse sind.
Im Terminalordner \MQL5\Services\AccountReporter\ erstellen wir die neue Datei Reporter.mq5 der Service-App:
Geben wir die notwendigen Makro-Ersetzungen ein, verbinden wir externe Dateien, schreiben wir Aufzählungen, Eingaben und globale Variablen, damit das Programm funktioniert:
//+------------------------------------------------------------------+ //| Reporter.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property service #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #define COUNTER_DELAY 1000 // Counter delay in milliseconds during the working loop #define REFRESH_ATTEMPTS 5 // Number of attempts to obtain correct account data #define REFRESH_DELAY 500 // Delay in milliseconds before next attempt to get data #define TABLE_COLUMN_W 10 // Width of the statistics table column for displaying in the journal #include <Arrays\ArrayString.mqh> // Dynamic array of string variables for a symbol list object #include <Arrays\ArrayLong.mqh> // Dynamic array of long type variables for the magic number list object #include <Tools\DateTime.mqh> // Expand the MqlDateTime structure #include "Accounts.mqh" // Collection class of account objects //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_USED_ACCOUNTS // Enumerate used accounts in statistics { USED_ACCOUNT_CURRENT, // Current Account only USED_ACCOUNTS_ALL, // All used accounts }; enum ENUM_REPORT_RANGE // Enumerate statistics ranges { REPORT_RANGE_DAILY, // Day REPORT_RANGE_WEEK_BEGIN, // Since the beginning of the week REPORT_RANGE_MONTH_BEGIN, // Since the beginning of the month REPORT_RANGE_YEAR_BEGIN, // Since the beginning of the year REPORT_RANGE_NUM_DAYS, // Number of days REPORT_RANGE_NUM_MONTHS, // Number of months REPORT_RANGE_NUM_YEARS, // Number of years REPORT_RANGE_ALL, // Entire period }; enum ENUM_REPORT_BY // Enumerate statistics filters { REPORT_BY_RANGE, // Date range REPORT_BY_SYMBOLS, // By symbols REPORT_BY_MAGICS, // By magic numbers }; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "============== Report options ==============" input ENUM_USED_ACCOUNTS InpUsedAccounts = USED_ACCOUNT_CURRENT;// Accounts included in statistics input bool InpReportBySymbols = true; // Reports by Symbol input bool InpReportByMagics = true; // Reports by Magics input bool InpCommissionsInclude= true; // Including Comissions input bool InpSpreadInclude = true; // Including Spread input group "========== Daily reports for daily periods ==========" input bool InpSendDReport = true; // Send daily report (per day and specified periods) input uint InpSendDReportHour = 8; // Hour of sending the report (Local time) input uint InpSendDReportMin = 0; // Minutes of sending the report (Local time) input group "========= Daily reports for specified periods =========" input bool InpSendSReportDays = true; // Send a report for the specified num days input uint InpSendSReportDaysN = 7; // Number of days to report for the specified number of days input bool InpSendSReportMonths = true; // Send a report for the specified num months input uint InpSendSReportMonthsN= 3; // Number of months to report for the specified number of months input bool InpSendSReportYears = true; // Send a report for the specified num years input uint InpSendSReportYearN = 2; // Number of years to report for the specified number of years input group "======== Weekly reports for all other periods ========" input ENUM_DAY_OF_WEEK InpSendWReportDayWeek= SATURDAY; // Day of sending the reports (Local time) input uint InpSendWReportHour = 8; // Hour of sending the reports (Local time) input uint InpSendWReportMin = 0; // Minutes of sending the reports (Local time) input bool InpSendWReport = true; // Send a report for the current week input bool InpSendMReport = false; // Send a report for the current month input bool InpSendYReport = false; // Send a report for the current year input bool InpSendAReport = false; // Send a report for the entire trading period //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CAccounts ExtAccounts; // Account management object long ExtLogin; // Current account login string ExtServer; // Current account server bool ExtNotify; // Push notifications enabling flag //+------------------------------------------------------------------+ //| Service program start function | //+------------------------------------------------------------------+ void OnStart() { }
Wir sehen, dass die Datei \MQL5\Include\Tools\DateTime.mqh enthalten ist. Sie ist von der Standardstruktur MqlDateTime abgeleitet:
//+------------------------------------------------------------------+ //| DateTime.mqh | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Structure CDateTime. | //| Purpose: Working with dates and time. | //| Extends the MqlDateTime structure. | //+------------------------------------------------------------------+ struct CDateTime : public MqlDateTime { //--- additional information string MonthName(const int num) const; string ShortMonthName(const int num) const; string DayName(const int num) const; string ShortDayName(const int num) const; string MonthName(void) const { return(MonthName(mon)); } string ShortMonthName(void) const { return(ShortMonthName(mon)); } string DayName(void) const { return(DayName(day_of_week)); } string ShortDayName(void) const { return(ShortDayName(day_of_week)); } int DaysInMonth(void) const; //--- data access datetime DateTime(void) { return(StructToTime(this)); } void DateTime(const datetime value) { TimeToStruct(value,this); } void DateTime(const MqlDateTime& value) { this=value; } void Date(const datetime value); void Date(const MqlDateTime &value); void Time(const datetime value); void Time(const MqlDateTime &value); //--- settings void Sec(const int value); void Min(const int value); void Hour(const int value); void Day(const int value); void Mon(const int value); void Year(const int value); //--- increments void SecDec(int delta=1); void SecInc(int delta=1); void MinDec(int delta=1); void MinInc(int delta=1); void HourDec(int delta=1); void HourInc(int delta=1); void DayDec(int delta=1); void DayInc(int delta=1); void MonDec(int delta=1); void MonInc(int delta=1); void YearDec(int delta=1); void YearInc(int delta=1); //--- check void DayCheck(void); };
Die Struktur enthält fertige Methoden für die Arbeit mit Daten und Zeit. Wir müssen die Anfangszeit des Statistikzeitraums berechnen. Genau hier kommen die Strukturmethoden ins Spiel, um die Gültigkeit der Daten, die man durch Subtraktion der Anzahl der Tage, Wochen, Monate und Jahre vom aktuellen Datum erhält, nicht unabhängig zu berechnen. Hier werden alle Berechnungen mit Korrektur der falschen Werte durchgeführt. Wenn zum Beispiel mehr Tage vom aktuellen Datum abgezogen werden, als der Monat hat, dann sollte das resultierende Datum durch Berechnung des Monats und des Tages angepasst werden, wobei Schaltjahre zu berücksichtigen sind. Es ist jedoch einfacher, die Methoden zur Reduzierung der Tage, Monate und Jahre einer bestimmten Struktur zu verwenden, um sofort das richtige Enddatum zu erhalten.
Die Service-App selbst sollte in einer Endlosschleife arbeiten. In der Schleife sorgen wir für eine Verzögerung von etwa einer Sekunde. Nach Beendigung der Wartezeit sind alle Kontrollen und Berechnungen zu veranlassen. Der gesamte Text der Schleife ist zur besseren Übersichtlichkeit und zum besseren Verständnis in betitelte Blöcke unterteilt. Schauen wir uns den Hauptteil des Programms selbst an:
//+------------------------------------------------------------------+ //| Service program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CArrayObj *PositionsList = NULL; // List of closed account positions long account_prev = 0; // Previous login double balance_prev = EMPTY_VALUE; // Previous balance bool Sent = false; // Flag of sent report for non-daily periods int day_of_year_prev= WRONG_VALUE; // The previous day number of the year //--- Create lists of symbols and magic numbers traded in history and a list of messages for Push notifications CArrayString *SymbolsList = new CArrayString(); CArrayLong *MagicsList = new CArrayLong(); CArrayString *MessageList = new CArrayString(); if(SymbolsList==NULL || MagicsList==NULL || MessageList==NULL) { Print("Failed to create list CArrayObj"); return; } //--- Check for the presence of MetaQuotes ID and permission to send notifications to it ExtNotify=CheckMQID(); if(ExtNotify) Print(MQLInfoString(MQL_PROGRAM_NAME)+"-Service notifications OK"); //--- The main loop int count=0; while(!IsStopped()) { //+------------------------------------------------------------------+ //| Delay in the loop | //+------------------------------------------------------------------+ //--- Increase the loop counter. If the counter has not exceeded the specified value, repeat Sleep(16); count+=10; if(count<COUNTER_DELAY) continue; //--- Waiting completed. Reset the loop counter count=0; //+------------------------------------------------------------------+ //| Check notification settings | //+------------------------------------------------------------------+ //--- If the notification flag is not set, we check the notification settings in the terminal and, if activated, we report this if(!ExtNotify && TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)) { Print("Now MetaQuotes ID is specified and sending notifications is allowed"); SendNotification("Now MetaQuotes ID is specified and sending notifications is allowed"); ExtNotify=true; } //--- If the notification flag is set, but the terminal does not have permission for them, we report this if(ExtNotify && (!TerminalInfoInteger(TERMINAL_MQID) || !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))) { string caption=MQLInfoString(MQL_PROGRAM_NAME); string message="The terminal has a limitation on sending notifications. Please check your notification settings"; MessageBox(message, caption, MB_OK|MB_ICONWARNING); ExtNotify=false; } //+------------------------------------------------------------------+ //| Change account | //+------------------------------------------------------------------+ //--- If the current login is not equal to the previous one if(AccountInfoInteger(ACCOUNT_LOGIN)!=account_prev) { //--- if we failed to wait for the account data to be updated, repeat on the next loop iteration if(!DataUpdateWait(balance_prev)) continue; //--- Received new account data //--- Save the current login and balance as previous ones for the next check account_prev=AccountInfoInteger(ACCOUNT_LOGIN); balance_prev=AccountInfoDouble(ACCOUNT_BALANCE); //--- Reset the sent message flag and call the account change handler Sent=false; AccountChangeHandler(); } //+------------------------------------------------------------------+ //| Daily reports | //+------------------------------------------------------------------+ //--- Fill the structure with data about local time and date MqlDateTime tm={}; TimeLocal(tm); //--- Clear the list of messages sent to MQID MessageList.Clear(); //--- If the current day number in the year is not equal to the previous one, it is the beginning of a new day if(tm.day_of_year!=day_of_year_prev) { //--- If hours/minutes have reached the specified values for sending statistics if(tm.hour>=(int)InpSendDReportHour && tm.min>=(int)InpSendDReportMin) { //--- If sending daily statistics is allowed if(InpSendDReport) { //--- update the lists of closed positions for the day on the current account ExtAccounts.PositionsRefresh(ExtLogin, ExtServer); //--- if the settings are set to receive statistics from all accounts - //--- get a list of closed positions of all accounts that were active when the service was running if(InpUsedAccounts==USED_ACCOUNTS_ALL) PositionsList=ExtAccounts.GetCommonPositionsList(); //--- otherwise, get the list of closed positions of the current account only else PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer); //--- Create messages about trading statistics for a daily time range, //--- print the generated messages to the log and send them to MQID SendReport(REPORT_RANGE_DAILY, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of days, //--- Create messages about trade statistics for the number of days in InpSendSReportDaysN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportDays) SendReport(REPORT_RANGE_NUM_DAYS, InpSendSReportDaysN, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of months, //--- Create messages about trade statistics for the number of months in InpSendSReportMonthsN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportMonths) SendReport(REPORT_RANGE_NUM_MONTHS, InpSendSReportMonthsN, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of years, //--- Create messages about trade statistics for the number of years in InpSendSReportYearN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportYears) SendReport(REPORT_RANGE_NUM_YEARS, InpSendSReportYearN, PositionsList, SymbolsList, MagicsList, MessageList); } //--- Set the current day as the previous one for subsequent verification day_of_year_prev=tm.day_of_year; } } //+------------------------------------------------------------------+ //| Weekly reports | //+------------------------------------------------------------------+ //--- If the day of the week is equal to the one set in the settings, if(tm.day_of_week==InpSendWReportDayWeek) { //--- if the message has not been sent yet and it is time to send messages if(!Sent && tm.hour>=(int)InpSendWReportHour && tm.min>=(int)InpSendWReportMin) { //--- update the lists of closed positions on the current account ExtAccounts.PositionsRefresh(ExtLogin, ExtServer); //--- if the settings are set to receive statistics from all accounts - //--- get a list of closed positions of all accounts that were active when the service was running if(InpUsedAccounts==USED_ACCOUNTS_ALL) PositionsList=ExtAccounts.GetCommonPositionsList(); //--- otherwise, get the list of closed positions of the current account only else PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer); //--- If the settings allow sending trading statistics for a week, //--- Create messages about trading statistics from the beginning of the current week, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendWReport) SendReport(REPORT_RANGE_WEEK_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for a month, //--- Create messages about trading statistics from the beginning of the current month, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendMReport) SendReport(REPORT_RANGE_MONTH_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for a year, //--- Create messages about trading statistics from the beginning of the current year, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendYReport) SendReport(REPORT_RANGE_YEAR_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the entire period, //--- Create messages about trading statistics from the start of the epoch (01.01.1970 00:00), //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendAReport) SendReport(REPORT_RANGE_ALL, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- Set the flag that all messages with statistics are sent to the journal Sent=true; } } //--- If the day of the week specified in the settings for sending statistics has not yet arrived, reset the flag of sent messages else Sent=false; //--- If the list of messages to send to MQID is not empty, call the function for sending notifications to a smartphone if(MessageList.Total()>0) SendMessage(MessageList); } //+------------------------------------------------------------------+ //| Service shutdown | //+------------------------------------------------------------------+ //--- Clear and delete lists of messages, symbols and magic numbers if(MessageList!=NULL) { MessageList.Clear(); delete MessageList; } if(SymbolsList!=NULL) { SymbolsList.Clear(); delete SymbolsList; } if(MagicsList!=NULL) { MagicsList.Clear(); delete MagicsList; } }
Wie wir sehen, wird beim Start des Dienstes geprüft, ob das Terminal berechtigt ist, Benachrichtigungen an das Smartphone zu senden. Die Funktion CheckMQID() wird aufgerufen, um die einzelnen Einstellungen zu überprüfen und die erforderlichen Parameter in den Einstellungen des Client-Terminals zu aktivieren:
//+------------------------------------------------------------------+ //| Check for the presence of MetaQuotes ID | //| and permission to send notifications to the mobile terminal | //+------------------------------------------------------------------+ bool CheckMQID(void) { string caption=MQLInfoString(MQL_PROGRAM_NAME); // Message box header string message=caption+"-Service OK"; // Message box text int mb_id=IDOK; // MessageBox() return code //--- If MQID is not installed in the terminal settings, we will make a request to install it with explanations on the procedure if(!TerminalInfoInteger(TERMINAL_MQID)) { message="The client terminal does not have a MetaQuotes ID for sending Push notifications.\n"+ "1. Install the mobile version of the MetaTrader 5 terminal from the App Store or Google Play.\n"+ "2. Go to the \"Messages\" section of your mobile terminal.\n"+ "3. Click \"MQID\".\n"+ "4. In the client terminal, in the \"Tools - Settings\" menu, in the \"Notifications\" tab, in the MetaQuotes ID field, enter the received code."; mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONWARNING); } //--- If the Cancel button is pressed, inform about the refusal to use Push notifications if(mb_id==IDCANCEL) { message="You refused to enter your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal"; MessageBox(message, caption, MB_OK|MB_ICONINFORMATION); } //--- If the Retry button is pressed, else { //--- If the terminal has MetaQuotes ID installed for sending Push notifications if(TerminalInfoInteger(TERMINAL_MQID)) { //--- if the terminal does not have permission to send notifications to a smartphone, if(!TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)) { //--- show the message asking for permission to send notifications in the settings message="Please enable sending Push notifications in the terminal settings in the \"Notifications\" tab in the \"Tools - Settings\" menu."; mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONEXCLAMATION); //--- If the Cancel button is pressed in response to the message, if(mb_id==IDCANCEL) { //--- inform about the refusal to send notifications to a smartphone string message="You have opted out of sending Push notifications. The service will send notifications to the “Experts” tab of the terminal."; MessageBox(message, caption, MB_OK|MB_ICONINFORMATION); } //--- If the Retry button is pressed in response to the message (this is expected to be done after enabling permission in the settings), //--- but there is still no permission to send notifications in the terminal, if(mb_id==IDRETRY && !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)) { //--- inform that the user has refused to send notifications to a smartphone, and messages will only be in the journal string message="You have not allowed push notifications. The service will send notifications to the “Experts” tab of the terminal."; MessageBox(message, caption, MB_OK|MB_ICONINFORMATION); } } } //--- If the terminal has MetaQuotes ID installed for sending Push notifications, else { //--- inform that the terminal does not have MetaQuotes ID installed to send notifications to a smartphone, and messages will only be sent to the journal string message="You have not set your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal"; MessageBox(message, caption, MB_OK|MB_ICONINFORMATION); } } //--- Return the flag that MetaQuotes ID is set in the terminal and sending notifications is allowed return(TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)); }
Nach Ausführung der oben vorgestellten Funktion wird eine Schleife gestartet, in der das Flag für die Erlaubnis zum Senden von Benachrichtigungen im Programm und die Einstellungen für diese Erlaubnis im Terminal kontrolliert werden:
//+------------------------------------------------------------------+ //| Check notification settings | //+------------------------------------------------------------------+ //--- If the notification flag is not set, we check the notification settings in the terminal and, if activated, we report this if(!ExtNotify && TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)) { Print("Now MetaQuotes ID is specified and sending notifications is allowed"); SendNotification("Now MetaQuotes ID is specified and sending notifications is allowed"); ExtNotify=true; } //--- If the notification flag is set, but the terminal does not have permission for them, we report this if(ExtNotify && (!TerminalInfoInteger(TERMINAL_MQID) || !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))) { string caption=MQLInfoString(MQL_PROGRAM_NAME); string message="The terminal has a limitation on sending notifications. Please check your notification settings"; MessageBox(message, caption, MB_OK|MB_ICONWARNING); ExtNotify=false; }
Wenn sich im Terminal etwas ändert, gibt der Dienst die notwendigen Warnungen aus: Wenn er aktiviert war und nun deaktiviert wurde, meldet der Dienst, dass es Einschränkungen beim Senden von Benachrichtigungen gibt. Wenn sie hingegen deaktiviert war, der Nutzer aber die Berechtigungen in den Einstellungen aktiviert hat, meldet der Dienst, dass nun alles normal ist, und sendet eine Benachrichtigung darüber an ein Smartphone.
Als Nächstes wird in der Schleife geprüft, ob das Konto geändert wurde:
//+------------------------------------------------------------------+ //| Change account | //+------------------------------------------------------------------+ //--- If the current login is not equal to the previous one if(AccountInfoInteger(ACCOUNT_LOGIN)!=account_prev) { //--- if we failed to wait for the account data to be updated, repeat on the next loop iteration if(!DataUpdateWait(balance_prev)) continue; //--- Received new account data //--- Save the current login and balance as previous ones for the next check account_prev=AccountInfoInteger(ACCOUNT_LOGIN); balance_prev=AccountInfoDouble(ACCOUNT_BALANCE); //--- Reset the sent message flag and call the account change handler Sent=false; AccountChangeHandler(); }
Sobald sich das Login ändert und sich von dem zuvor gespeicherten unterscheidet, rufen wir die Funktion zum Warten auf das Laden des aktuellen Kontos auf:
//+------------------------------------------------------------------+ //| Waiting for account data update | //+------------------------------------------------------------------+ bool DataUpdateWait(double &balance_prev) { int attempts=0; // Number of attempts //--- Until the program stop flag is disabled and until the number of attempts is less than the number set in REFRESH_ATTEMPTS while(!IsStopped() && attempts<REFRESH_ATTEMPTS) { //--- If the balance of the current account differs from the balance of the previously saved balance value, //--- we assume that we were able to obtain the account data, return 'true' if(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)-balance_prev, 8)!=0) return true; //--- Wait half a second for the next attempt, increase the number of attempts and //--- log a message about waiting for data to be received and the number of attempts Sleep(500); attempts++; PrintFormat("%s::%s: Waiting for account information to update. Attempt %d", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__, attempts); } //--- If failed to obtain the new account data after all attempts, //--- report this to the log, write an empty value to the "previous balance" and return 'false' PrintFormat("%s::%s: Could not wait for updated account data... Try again", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__); balance_prev=EMPTY_VALUE; return false; }
Die Funktion wartet, bis keine Kontostandsdaten mehr aus dem Terminal-Cache empfangen werden. Schließlich ist der Saldo des neuen Kontos wahrscheinlich ein anderer als der des alten Kontos. Die Funktion unternimmt eine bestimmte Anzahl von Versuchen, um die Differenz zwischen dem gespeicherten Saldo des vorherigen Kontos und dem des neuen Kontos zu ermitteln. Im Falle eines Fehlschlags (oder wenn die Salden immer noch gleich sind), setzt die Funktion schließlich EMPTY_VALUE auf den vorherigen Saldo, und bei der nächsten Iteration der Schleife wird geprüft, ob aktuelle Daten für das neue Konto eingegangen sind, indem sie mit diesem neuen Wert verglichen werden, der höchstwahrscheinlich nicht mehr auf dem Kontosaldo liegen kann.
In der nächsten Schleife werden Datums- und Zeitkontrollen durchgeführt, um tägliche und wöchentliche Berichte zu erstellen:
//+------------------------------------------------------------------+ //| Daily reports | //+------------------------------------------------------------------+ //--- Fill the structure with data about local time and date MqlDateTime tm={}; TimeLocal(tm); //--- Clear the list of messages sent to MQID MessageList.Clear(); //--- If the current day number in the year is not equal to the previous one, it is the beginning of a new day if(tm.day_of_year!=day_of_year_prev) { //--- If hours/minutes have reached the specified values for sending statistics if(tm.hour>=(int)InpSendDReportHour && tm.min>=(int)InpSendDReportMin) { //--- If sending daily statistics is allowed if(InpSendDReport) { //--- update the lists of closed positions for the day on the current account ExtAccounts.PositionsRefresh(ExtLogin, ExtServer); //--- if the settings are set to receive statistics from all accounts - //--- get a list of closed positions of all accounts that were active when the service was running if(InpUsedAccounts==USED_ACCOUNTS_ALL) PositionsList=ExtAccounts.GetCommonPositionsList(); //--- otherwise, get the list of closed positions of the current account only else PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer); //--- Create messages about trading statistics for a daily time range, //--- print the generated messages to the log and send them to MQID SendReport(REPORT_RANGE_DAILY, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of days, //--- Create messages about trade statistics for the number of days in InpSendSReportDaysN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportDays) SendReport(REPORT_RANGE_NUM_DAYS, InpSendSReportDaysN, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of months, //--- Create messages about trade statistics for the number of months in InpSendSReportMonthsN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportMonths) SendReport(REPORT_RANGE_NUM_MONTHS, InpSendSReportMonthsN, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the specified number of years, //--- Create messages about trade statistics for the number of years in InpSendSReportYearN, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendSReportYears) SendReport(REPORT_RANGE_NUM_YEARS, InpSendSReportYearN, PositionsList, SymbolsList, MagicsList, MessageList); } //--- Set the current day as the previous one for subsequent verification day_of_year_prev=tm.day_of_year; } } //+------------------------------------------------------------------+ //| Weekly reports | //+------------------------------------------------------------------+ //--- If the day of the week is equal to the one set in the settings, if(tm.day_of_week==InpSendWReportDayWeek) { //--- if the message has not been sent yet and it is time to send messages if(!Sent && tm.hour>=(int)InpSendWReportHour && tm.min>=(int)InpSendWReportMin) { //--- update the lists of closed positions on the current account ExtAccounts.PositionsRefresh(ExtLogin, ExtServer); //--- if the settings are set to receive statistics from all accounts - //--- get a list of closed positions of all accounts that were active when the service was running if(InpUsedAccounts==USED_ACCOUNTS_ALL) PositionsList=ExtAccounts.GetCommonPositionsList(); //--- otherwise, get the list of closed positions of the current account only else PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer); //--- If the settings allow sending trading statistics for a week, //--- Create messages about trading statistics from the beginning of the current week, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendWReport) SendReport(REPORT_RANGE_WEEK_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for a month, //--- Create messages about trading statistics from the beginning of the current month, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendMReport) SendReport(REPORT_RANGE_MONTH_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for a year, //--- Create messages about trading statistics from the beginning of the current year, //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendYReport) SendReport(REPORT_RANGE_YEAR_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- If the settings allow sending trading statistics for the entire period, //--- Create messages about trading statistics from the start of the epoch (01.01.1970 00:00), //--- print the created messages to the log and add them to the list for sending to MQID if(InpSendAReport) SendReport(REPORT_RANGE_ALL, 0, PositionsList, SymbolsList, MagicsList, MessageList); //--- Set the flag that all messages with statistics are sent to the journal Sent=true; } } //--- If the day of the week specified in the settings for sending statistics has not yet arrived, reset the flag of sent messages else Sent=false; //--- If the list of messages to send to MQID is not empty, call the function for sending notifications to a smartphone if(MessageList.Total()>0) SendMessage(MessageList);
Hier wird die gesamte Logik im Listing kommentiert. Beachten Sie, dass wir eine Nachricht nicht sofort nach ihrer Erstellung in der Schleife zum Senden von Nachrichten an ein Smartphone senden können. Da es viele solcher Nachrichten geben kann (je nachdem, welche Berichte in den Einstellungen ausgewählt wurden), sind strenge Beschränkungen für Push-Benachrichtigungen festgelegt: nicht mehr als zwei Nachrichten pro Sekunde und nicht mehr als zehn Nachrichten pro Minute. Daher werden alle erstellten Nachrichten auf die Liste CArrayString der Standardbibliothek gesetzt. Nachdem alle Berichte erstellt wurden und wenn dieses Array nicht leer ist, rufen wir die Funktion zum Senden von Benachrichtigungen an ein Smartphone auf. Bei dieser Funktion werden alle erforderlichen Sendeverzögerungen so eingerichtet, dass die festgelegten Beschränkungen nicht verletzt werden.
Schauen wir uns alle Funktionen an, die für den Betrieb der Service-App verwendet werden.
Die Funktion, die eine Liste mit einem bestimmten Bereich von Statistiken zurückgibt:
//+------------------------------------------------------------------+ //| Return a list with the specified statistics range | //+------------------------------------------------------------------+ CArrayObj *GetListDataRange(ENUM_REPORT_RANGE range, CArrayObj *list, datetime &time_start, const int num_periods) { //--- Current date CDateTime current={}; current.Date(TimeLocal()); //--- Period start date CDateTime begin_range=current; //--- Set the period start time to 00:00:00 begin_range.Hour(0); begin_range.Min(0); begin_range.Sec(0); //--- Adjust the start date of the period depending on the specified period of required statistics switch(range) { //--- Day case REPORT_RANGE_DAILY : // decrease Day by 1 begin_range.DayDec(1); break; //--- Since the beginning of the week case REPORT_RANGE_WEEK_BEGIN : // decrease Day by (number of days passed in the week)-1 begin_range.DayDec(begin_range.day_of_week==SUNDAY ? 6 : begin_range.day_of_week-1); break; //--- Since the beginning of the month case REPORT_RANGE_MONTH_BEGIN : // set the first day of the month as Day begin_range.Day(1); break; //--- Since the beginning of the year case REPORT_RANGE_YEAR_BEGIN : // set Month to the first month of the year, and Day to the first day of the month begin_range.Mon(1); begin_range.Day(1); break; //--- Number of days case REPORT_RANGE_NUM_DAYS : // Decrease Day by the specified number of days begin_range.DayDec(fabs(num_periods)); break; //--- Number of months case REPORT_RANGE_NUM_MONTHS : // Decrease Month by the specified number of months begin_range.MonDec(fabs(num_periods)); break; //--- Number of years case REPORT_RANGE_NUM_YEARS : // Decrease Year by the specified number of years begin_range.YearDec(fabs(num_periods)); break; //---REPORT_RANGE_ALL Entire period default : // Set the date to 1970.01.01 begin_range.Year(1970); begin_range.Mon(1); begin_range.Day(1); break; } //--- Write the start date of the period and return the pointer to the list of positions, //--- the opening time of which is greater than or equal to the start time of the requested period time_start=begin_range.DateTime(); return CSelect::ByPositionProperty(list,POSITION_PROP_TIME,time_start,EQUAL_OR_MORE); }
Die Funktion erhält die Angabe des Statistikbereichs, mit dem wir arbeiten (täglich, ab Wochenbeginn, Monat, Jahr, mit einer bestimmten Anzahl von Tagen, Monaten, Jahren oder der gesamten Handelsperiode), sowie die Liste der geschlossenen Positionen, die nach dem Anfangsdatum der Periode sortiert werden müssen. Als Nächstes passen wir je nach dem Bereich der empfangenen Statistiken das Anfangsdatum des gewünschten Bereichs an, erhalten eine Liste der geschlossenen Positionen ab dem Beginn des berechneten Datums und geben diese zurück.
Funktion zur Bearbeitung von Kontoänderungen:
//+------------------------------------------------------------------+ //| Account change handler | //+------------------------------------------------------------------+ void AccountChangeHandler(void) { //--- Set the current account login and server long login = AccountInfoInteger(ACCOUNT_LOGIN); string server = AccountInfoString(ACCOUNT_SERVER); //--- Get the pointer to the account object based on the current account data CAccount *account = ExtAccounts.Get(login, server); //--- If the object is empty, create a new account object and get a pointer to it if(account==NULL && ExtAccounts.Create(login, server)) account=ExtAccounts.Get(login, server); //--- If the account object is eventually not received, report this and leave if(account==NULL) { PrintFormat("Error getting access to account object: %I64d (%s)", login, server); return; } //--- Set the current login and server values from the account object data ExtLogin =account.Login(); ExtServer=account.Server(); //--- Display the account data in the journal and display a message about the start of creating the list of closed positions account.Print(); Print("Beginning to create a list of closed positions..."); //--- Create the list of closed positions and report the number of created positions and the time spent in the journal upon completion ulong start=GetTickCount(); ExtAccounts.PositionsRefresh(ExtLogin, ExtServer); PrintFormat("A list of %d positions was created in %I64u ms", account.PositionsTotal(), GetTickCount()-start); }
In ihr wird ein neues Kontoobjekt erstellt, wenn es noch nicht verwendet wurde, oder es wird ein Zeiger auf ein zuvor erstelltes Konto abgerufen, wenn zuvor eine Verbindung zu diesem bestanden hat. Dann wird die Liste der geschlossenen Positionen des Kontos erstellt. Das Journal zeigt Meldungen über den Beginn der Erstellung einer Liste historischer Positionen, ihre Fertigstellung und die Anzahl der dafür aufgewendeten Millisekunden an.
Die Funktion, die Statistiken für den angegebenen Zeitbereich erstellt:
//+------------------------------------------------------------------+ //| Create statistics for the specified time range | //+------------------------------------------------------------------+ void SendReport(ENUM_REPORT_RANGE range, int num_periods, CArrayObj *list_common, CArrayString *list_symbols, CArrayLong *list_magics, CArrayString *list_msg) { string array_msg[2] = {NULL, NULL}; // Array of messages (0) for displaying in the journal and (1) for sending to a smartphone datetime time_start = 0; // Here we will store the start time of the statistics period CArrayObj *list_tmp = NULL; // Temporary list for sorting by symbols and magic number //--- Get a list of positions for the 'range' period CArrayObj *list_range=GetListDataRange(range, list_common, time_start, num_periods); if(list_range==NULL) return; //--- If the list of positions is empty, report to the journal that there were no transactions for the given period of time if(list_range.Total()==0) { PrintFormat("\"%s\" no trades",ReportRangeDescription(range, num_periods)); return; } //--- Create the lists of symbols and magic numbers of positions in the received list of closed positions for a period of time, while resetting them beforehand list_symbols.Clear(); list_magics.Clear(); CreateSymbolMagicLists(list_range, list_symbols, list_magics); //--- Create statistics on closed positions for the specified period, //--- print the generated statistics from array_msg[0] in the journal and //--- set the string from array_msg[1] to the list of messages for push notifications if(CreateStatisticsMessage(range, num_periods, REPORT_BY_RANGE, MQLInfoString(MQL_PROGRAM_NAME),time_start, list_range, list_symbols, list_magics, 0, array_msg)) { Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_RANGE, time_start)); // Statistics title Print(StatisticsTableHeader("Symbols ", InpCommissionsInclude, InpSpreadInclude)); // Table header Print(array_msg[0]); // Statistics for a period of time Print(""); // String indentation list_msg.Add(array_msg[1]); // Save the message for Push notifications to the list for later sending } //--- If statistics are allowed separately by symbols if(InpReportBySymbols) { //--- Display the statistics and table headers to the journal Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_SYMBOLS, time_start)); Print(StatisticsTableHeader("Symbol ", InpCommissionsInclude, InpSpreadInclude)); //--- In the loop by the list of symbols, for(int i=0; i<list_symbols.Total(); i++) { //--- get the name of the next symbol string symbol=list_symbols.At(i); if(symbol=="") continue; //--- sort out the list of positions leaving only positions with the received symbol list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_SYMBOL, symbol, EQUAL); //--- Create statistics on closed positions for the specified period by the current list symbol, //--- print the generated statistics from array_msg[0] and //--- set the string from array_msg[1] to the list of messages for push notifications if(CreateStatisticsMessage(range, num_periods, REPORT_BY_SYMBOLS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg)) { Print(array_msg[0]); list_msg.Add(array_msg[1]); } } //--- After the loop has completed for all symbols, display the separator line to the journal Print(""); } //--- If statistics are allowed separately by magic numbers if(InpReportByMagics) { //--- Display the statistics and table headers to the journal Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_MAGICS, time_start)); Print(StatisticsTableHeader("Magic ", InpCommissionsInclude, InpSpreadInclude)); //--- In the loop by the list of magic numbers, for(int i=0; i<list_magics.Total(); i++) { //--- get the next magic number long magic=list_magics.At(i); if(magic==LONG_MAX) continue; //--- sort out the list of positions leaving only positions with the received magic number list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_MAGIC, magic, EQUAL); //--- Create statistics on closed positions for the specified period by the current list magic number, //--- print the generated statistics from array_msg[0] and //--- set the string from array_msg[1] to the list of messages for push notifications if(CreateStatisticsMessage(range, num_periods, REPORT_BY_MAGICS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg)) { Print(array_msg[0]); list_msg.Add(array_msg[1]); } } //--- After the loop has completed for all magic numbers, display the separator line to the journal Print(""); } }
Die Funktion ruft die Funktion zur Erstellung von Statistiken für den angegebenen Handelszeitraum auf und zeigt den Tabellenkopf und die Statistiken im Journal an. Nachrichten für Push-Benachrichtigungen werden im Zeiger auf die Liste der an die Methode übergebenen Nachrichten festgelegt. Wenn die Statistiken Berichte nach Symbolen und magischen Zahlen enthalten, werden nach dem Senden der Hauptstatistiken an das Journal der Titel und die Überschrift der Statistiktabelle nach Symbolen und magischen Zahlen angezeigt. Es folgt ein Bericht mit Symbolen und magischen Zahlen in tabellarischer Form.
Die Funktion, die die Tabellenkopfzeile erstellt und zurückgibt:
//+------------------------------------------------------------------+ //| Create and return the table header row | //+------------------------------------------------------------------+ string StatisticsTableHeader(const string first, const bool commissions, const bool spreads) { //--- Declare and initialize the table column headers string h_trades="Trades "; string h_long="Long "; string h_short="Short "; string h_profit="Profit "; string h_max="Max "; string h_min="Min "; string h_avg="Avg "; string h_costs="Costs "; //--- table columns disabled in the settings string h_commiss=(commissions ? "Commiss " : ""); string h_swap=(commissions ? "Swap " : ""); string h_fee=(commissions ? "Fee " : ""); string h_spread=(spreads ? "Spread " : ""); //--- width of table columns int w=TABLE_COLUMN_W; int c=(commissions ? TABLE_COLUMN_W : 0); //--- Table column separators that can be disabled in the settings string sep1=(commissions ? "|" : ""); string sep2=(spreads ? "|" : ""); //--- Create a table header row return StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s", w,first, w,h_trades, w,h_long, w,h_short, w,h_profit, w,h_max, w,h_min, w,h_avg, w,h_costs, c,h_commiss,sep1, c,h_swap,sep1, c,h_fee,sep1, w,h_spread,sep2); }
Die Funktion erstellt die folgende Zeile
| Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread |
Die letzten vier Spalten - ihre Anzeige hängt davon ab, ob die Verwendung von Provisionen, Swaps, Abschlussgebühren und Spread-Werten in der Statistik erlaubt ist.
Die erste Spalte der Überschrift enthält den Namen, der in den Parametern an die Funktion übergeben wurde, da verschiedene Tabellen verschiedene Überschriften haben sollten.
Mehr über die Formatierung von Textnachrichten erfahren Sie in den Artikeln „PrintFormat() studieren und vorgefertigte Beispiele anwenden“ und „StringFormat(), Übersicht und fertige Beispiele“.
Die Funktion, die den Kopf der Beschreibung des angeforderten Statistikzeitraums zurückgibt:
//+------------------------------------------------------------------+ //| Return the description header of the requested statistics period | //+------------------------------------------------------------------+ string StatisticsRangeTitle(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const datetime time_start, const string symbol=NULL, const long magic=LONG_MAX) { string report_by_str= ( report_by==REPORT_BY_SYMBOLS ? (symbol==NULL ? "by symbols " : "by "+symbol+" ") : report_by==REPORT_BY_MAGICS ? (magic==LONG_MAX ? "by magics " : "by magic #"+(string)magic+" ") : "" ); return StringFormat("Report %sfor the period \"%s\" from %s", report_by_str,ReportRangeDescription(range, num_periods), TimeToString(time_start, TIME_DATE)); }
Je nach Statistikbereich und Statistikfilter (nach Symbol, magischer Zahl oder Datum) wird eine Zeichenkette des folgenden Typs erstellt und zurückgegeben:
Report for the period "3 months" from 2024.04.23 00:00
oder
Report by symbols for the period "3 months" from 2024.04.23 00:00
oder
Report by magics for the period "3 months" from 2024.04.23 00:00
usw.
Die Funktion liefert einen Nachrichtentext mit Statistik:
//+------------------------------------------------------------------+ //| Return a message text with statistics | //+------------------------------------------------------------------+ bool CreateStatisticsMessage(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const string header, const datetime time_start, CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics, const int index, string &array_msg[]) { //--- Get a symbol and a magic number by index from the passed lists string symbol = list_symbols.At(index); long magic = list_magics.At(index); //--- If the passed lists are empty, or no data was received from them, return 'false' if(list==NULL || list.Total()==0 || (report_by==REPORT_BY_SYMBOLS && symbol=="") || (report_by==REPORT_BY_MAGICS && magic==LONG_MAX)) return false; CPosition *pos_min = NULL; // Pointer to the position with the minimum property value CPosition *pos_max = NULL; // Pointer to the position with the maximum property value CArrayObj *list_tmp = NULL; // Pointer to a temporary list for sorting by properties int index_min= WRONG_VALUE; // Index of the position in the list with the minimum property value int index_max= WRONG_VALUE; // Index of the position in the list with the maximum property value //--- Get the sum of the position properties from the list of positions double profit=PropertyValuesSum(list, POSITION_PROP_PROFIT); // Total profit of positions in the list double commissions=PropertyValuesSum(list,POSITION_PROP_COMMISSIONS); // Total commission of positions in the list double swap=PropertyValuesSum(list, POSITION_PROP_SWAP); // General swap of positions in the list double fee=PropertyValuesSum(list, POSITION_PROP_FEE); // Total deal fee in the list double costs=commissions+swap+fee; // All commissions double spreads=PositionsCloseSpreadCostSum(list); // Total spread costs for all items in the list //--- Define text descriptions of all received values string s_0=(report_by==REPORT_BY_SYMBOLS ? symbol : report_by==REPORT_BY_MAGICS ? (string)magic : (string)list_symbols.Total())+" "; string s_trades=StringFormat("%d ", list.Total()); string s_profit=StringFormat("%+.2f ", profit); string s_costs=StringFormat("%.2f ",costs); string s_commiss=(InpCommissionsInclude ? StringFormat("%.2f ",commissions) : ""); string s_swap=(InpCommissionsInclude ? StringFormat("%.2f ",swap) : ""); string s_fee=(InpCommissionsInclude ? StringFormat("%.2f ",fee) : ""); string s_spread=(InpSpreadInclude ? StringFormat("%.2f ",spreads) : ""); //--- Get the list of only long positions and create a description of their quantity list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_BUY, EQUAL); string s_long=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" "; //--- Get the list of only short positions and create a description of their quantity list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_SELL, EQUAL); string s_short=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" "; //--- Get the index of the position in the list with the maximum profit and create a description of the received value index_max=CSelect::FindPositionMax(list, POSITION_PROP_PROFIT); pos_max=list.At(index_max); double profit_max=(pos_max!=NULL ? pos_max.Profit() : EMPTY_VALUE); string s_max=(profit_max!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_max) : "No trades "); //--- Get the index of the position in the list with the minimum profit and create a description of the received value index_min=CSelect::FindPositionMin(list, POSITION_PROP_PROFIT); pos_min=list.At(index_min); double profit_min=(pos_min!=NULL ? pos_min.Profit() : EMPTY_VALUE); string s_min=(profit_min!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_min) : "No trades "); //--- Create a description of the average profit value of all positions in the list string s_avg=StringFormat("%.2f ", PropertyAverageValue(list, POSITION_PROP_PROFIT)); //--- Table column width int w=TABLE_COLUMN_W; int c=(InpCommissionsInclude ? TABLE_COLUMN_W : 0); //--- Separators for table columns that can be disabled in the settings string sep1=(InpCommissionsInclude ? "|" : ""); string sep2=(InpSpreadInclude ? "|" : ""); //--- For displaying in the journal, create a string with table columns featuring the values obtained above array_msg[0]=StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s", w,s_0, w,s_trades, w,s_long, w,s_short, w,s_profit, w,s_max, w,s_min, w,s_avg, w,s_costs, c,s_commiss,sep1, c,s_swap,sep1, c,s_fee,sep1, w,s_spread,sep2); //--- For sending MQID notifications, create a string with table columns featuring the values obtained above array_msg[1]=StringFormat("%s:\nTrades: %s Long: %s Short: %s\nProfit: %s Max: %s Min: %s Avg: %s\n%s%s%s%s%s", StatisticsRangeTitle(range, num_periods, report_by, time_start, (report_by==REPORT_BY_SYMBOLS ? symbol : NULL), (report_by==REPORT_BY_MAGICS ? magic : LONG_MAX)), s_trades, s_long, s_short, s_profit, s_max, s_min, s_avg, (costs!=0 ? "Costs: "+s_costs : ""), (InpCommissionsInclude && commissions!=0 ? " Commiss: "+s_commiss : ""), (InpCommissionsInclude && swap!=0 ? " Swap: "+s_swap : ""), (InpCommissionsInclude && fee!=0 ? " Fee: "+s_fee : ""), (InpSpreadInclude && spreads!=0 ? " Spreads: "+s_spread : "")); //--- All is successful return true; }
Die Funktion nutzt die Sortierung der Liste und die Suche nach Indizes geschlossener Positionen mit Hilfe der Klasse CSelect, die wir zuvor implementiert haben. Aus den empfangenen Listen werden Texte für die Anzeige der Daten im Bericht erstellt.
Berichtstexte werden ganz am Ende der Funktion in zweifacher Ausfertigung erstellt — für die tabellarische Darstellung im Journal und für eine reguläre Zeile für eine Push-Benachrichtigung.
Die Funktion, die die Listen der magischen Zahlen und Positionssymbole aus der übergebenen Liste füllt:
//+--------------------------------------------------------------------------+ //| Fill the lists of magic numbers and position symbols from the passed list| //+--------------------------------------------------------------------------+ void CreateSymbolMagicLists(CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics) { //--- If an invalid pointer to a list of positions is passed, or the list is empty, leave if(list==NULL || list.Total()==0) return; int index=WRONG_VALUE; // Index of the necessary symbol or magic number in the list //--- In a loop by the list of positions for(int i=0; i<list.Total(); i++) { //--- get the pointer to the next position CPosition *pos=list.At(i); if(pos==NULL) continue; //--- Get the position symbol string symbol=pos.Symbol(); //--- Set the sorted list flag for the symbol list and get the symbol index in the symbol list list_symbols.Sort(); index=list_symbols.Search(symbol); //--- If there is no such symbol in the list, add it if(index==WRONG_VALUE) list_symbols.Add(symbol); //--- Get the position magic number long magic=pos.Magic(); //--- Set the sorted list flag for the magic number list and get the magic number index in the list of magic numbers list_magics.Sort(); index=list_magics.Search(magic); //--- If there is no such magic number in the list, add it if(index==WRONG_VALUE) list_magics.Add(magic); } }
Wir wissen zunächst nicht, welche Symbole und magischen Zahlen für den Handel auf dem Konto verwendet wurden. Um Berichte nach Symbolen und magischen Zahlen zu erhalten, müssen Sie alle Symbole und alle magischen Zahlen der geschlossenen Positionen in der vollständigen Liste aller geschlossenen Positionen finden und sie in die entsprechenden Listen eintragen. Eine vollständige Liste aller geschlossenen Positionen und Zeiger auf Symbol- und magische Zahlenlisten werden an diese Funktion übergeben. Alle gefundenen Symbole und magischen Zahlen werden in die entsprechenden Listen eingetragen. Nach Beendigung der Funktionsoperation haben wir zwei gefüllte Listen mit Symbolen und magischen Zahlen, die dann verwendet werden können, um Berichte über Symbole und magische Zahlen getrennt zu erstellen.
Um die Summe der Werte einer beliebigen ganzzahligen oder reellen Eigenschaft aller Positionen in der Liste zu erhalten, müssen wir die Werte dieser Eigenschaft in einer Schleife addieren. Warum brauchen wir das? Zum Beispiel, um den Wert des gesamten Spreads oder den Gesamtgewinn oder -verlust zu erhalten. Schreiben wir die Funktionen, die es uns ermöglichen, die Werte der angegebenen Eigenschaften aller Positionen in der Liste zu addieren.
Die Funktion, die die Summe der Werte einer angegebenen ganzzahligen Eigenschaft aller Elemente in der Liste zurückgibt:
//+------------------------------------------------------------------+ //| Return the sum of the values of the specified | //| integer property of all positions in the list | //+------------------------------------------------------------------+ long PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property) { long res=0; int total=list.Total(); for(int i=0; i<total; i++) { CPosition *pos=list.At(i); res+=(pos!=NULL ? pos.GetProperty(property) : 0); } return res; }
Holen wir in einer Schleife durch der Liste, deren Zeiger an die Funktion übergeben wird, den Wert der angegebenen Eigenschaft aus dem Objekt mit dem Schleifenindex und fügen ihn zu dem resultierenden Wert hinzu. Am Ende der Schleife erhalten wir die Summe der Werte der angegebenen Eigenschaft aller Positionen in der an die Funktion übergebenen Liste.
Die Funktion, die die Summe der Werte einer bestimmten realen Eigenschaft aller Elemente in der Liste zurückgibt:
//+------------------------------------------------------------------+ //| Return the sum of the values of the specified | //| real property of all positions in the list | //+------------------------------------------------------------------+ double PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property) { double res=0; int total=list.Total(); for(int i=0; i<total; i++) { CPosition *pos=list.At(i); res+=(pos!=NULL ? pos.GetProperty(property) : 0); } return res; }
Nach dem gleichen Prinzip werden wir Funktionen erstellen, die den Durchschnittswert der angegebenen Eigenschaft zurückgeben.
Die Funktion liefert den Durchschnittswert einer bestimmten ganzzahligen Eigenschaft aller Positionen in der Liste:
//+------------------------------------------------------------------+ //| Return the average value of the specified | //| integer property of all positions in the list | //+------------------------------------------------------------------+ double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property) { long res=0; int total=list.Total(); for(int i=0; i<total; i++) { CPosition *pos=list.At(i); res+=(pos!=NULL ? pos.GetProperty(property) : 0); } return(total>0 ? (double)res/(double)total : 0); }
Die Funktion liefert den Durchschnittswert der angegebenen Materialeigenschaft für alle Positionen in der Liste:
//+------------------------------------------------------------------+ //| Return the average value of the specified | //| real property of all positions in the list | //+------------------------------------------------------------------+ double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property) { double res=0; int total=list.Total(); for(int i=0; i<total; i++) { CPosition *pos=list.At(i); res+=(pos!=NULL ? pos.GetProperty(property) : 0); } return(total>0 ? res/(double)total : 0); }
Die Funktion liefert die Summe der Kosten durch den Spread für Deals, die alle Positionen in der Liste schließen:
//+------------------------------------------------------------------+ //| Returns the sum of the spread costs | //| of deals closing all positions in the list | //+------------------------------------------------------------------+ double PositionsCloseSpreadCostSum(CArrayObj *list) { double res=0; if(list==NULL) return 0; int total=list.Total(); for(int i=0; i<total; i++) { CPosition *pos=list.At(i); res+=(pos!=NULL ? pos.SpreadOutCost() : 0); } return res; }
Da die Position nicht die Eigenschaft „Spread Cost“ hat, können wir die oben genannten Funktionen hier nicht verwenden. Daher verwenden wir hier direkt die Methode des Positionsobjekts, die die Kosten des Spreads beim Schließen einer Position berechnet und zurückgibt. Addiert alle erhaltenen Werte aller Positionen in der Liste zum Endergebnis und gibt den erhaltenen Wert zurück.
Die Funktion liefert die Beschreibung des Berichtszeitraums:
//+------------------------------------------------------------------+ //| Return the report period description | //+------------------------------------------------------------------+ string ReportRangeDescription(ENUM_REPORT_RANGE range, const int num_period) { switch(range) { //--- Day case REPORT_RANGE_DAILY : return("Daily"); //--- Since the beginning of the week case REPORT_RANGE_WEEK_BEGIN : return("Weekly"); //--- Since the beginning of the month case REPORT_RANGE_MONTH_BEGIN : return("Month-to-date"); //--- Since the beginning of the year case REPORT_RANGE_YEAR_BEGIN : return("Year-to-date"); //--- Number of days case REPORT_RANGE_NUM_DAYS : return StringFormat("%d days", num_period); //--- Number of months case REPORT_RANGE_NUM_MONTHS : return StringFormat("%d months", num_period); //--- Number of years case REPORT_RANGE_NUM_YEARS : return StringFormat("%d years", num_period); //--- Entire period case REPORT_RANGE_ALL : return("Entire period"); //--- any other default : return("Unknown period: "+(string)range); } }
Je nach dem übergebenen Wert des Berichtszeitraums und der Anzahl der Tage/Monate/Jahre wird ein Beschreibungsstring erstellt und zurückgegeben.
Wir haben uns alle Funktionen des Serviceprogramms und das Programm selbst — seine Hauptschleife — angesehen. Kompilieren wir es und starten wir den Dienst. Nach der Kompilierung befindet sich das Programm im Abschnitt Dienste des Navigator-Terminalfensters.
Klicken Sie mit der rechten Maustaste auf unseren Dienst und wählen Sie „Dienst hinzufügen“:
Das Fenster mit den Programmeinstellungen wird geöffnet:
Sobald der Dienst gestartet ist, wird ein täglicher Bericht erstellt, der Folgendes enthält
- einen allgemeinen Bericht für drei Monate und Bericht für drei Monate in Form von Symbolen und magischen Zahlen,
- einen allgemeinen Bericht für zwei Jahre und Bericht für zwei Jahre in Form von Symbolen und magischen Zahlen:
Reporter -Service notifications OK Reporter 68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging) Reporter Beginning to create a list of closed positions... Reporter A list of 155 positions was created in 8828 ms Reporter "Daily" no trades Reporter "7 days" no trades Reporter Report for the period "3 months" from 2024.04.23 00:00 Reporter | Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | 2 | 77 | 17 | 60 | +247.00 | +36.70 | -0.40 | 3.20 | 0.00 | 0.00 | 0.00 | 0.00 | 5.10 | Reporter Reporter Report by symbols for the period "3 months" from 2024.04.23 00:00 Reporter | Symbol | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | EURUSD | 73 | 17 | 56 | +241.40 | +36.70 | -0.40 | 3.30 | 0.00 | 0.00 | 0.00 | 0.00 | 4.30 | Reporter | GBPUSD | 4 | 0 | 4 | +5.60 | +2.20 | +0.10 | 1.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.80 | Reporter Reporter Report by magics for the period "3 months" from 2024.04.23 00:00 Reporter | Magic | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | 0 | 75 | 15 | 60 | +246.60 | +36.70 | -0.40 | 3.28 | 0.00 | 0.00 | 0.00 | 0.00 | 4.90 | Reporter | 10879099 | 1 | 1 | 0 | +0.40 | +0.40 | +0.40 | 0.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 27394171 | 1 | 1 | 0 | +0.00 | +0.00 | +0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter Reporter Report for the period "2 years" from 2022.07.23 00:00 Reporter | Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | 2 | 155 | 35 | 120 | +779.50 | +145.00 | -22.80 | 5.03 | 0.00 | 0.00 | 0.00 | 0.00 | 15.38 | Reporter Reporter Report by symbols for the period "2 years" from 2022.07.23 00:00 Reporter | Symbol | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | EURUSD | 138 | 30 | 108 | +612.40 | +36.70 | -22.80 | 4.43 | 0.00 | 0.00 | 0.00 | 0.00 | 6.90 | Reporter | GBPUSD | 17 | 5 | 12 | +167.10 | +145.00 | -7.20 | 9.83 | 0.00 | 0.00 | 0.00 | 0.00 | 8.48 | Reporter Reporter Report by magics for the period "2 years" from 2022.07.23 00:00 Reporter | Magic | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Commiss | Swap | Fee | Spread | Reporter | 0 | 131 | 31 | 100 | +569.10 | +36.70 | -8.50 | 4.34 | 0.00 | 0.00 | 0.00 | 0.00 | 8.18 | Reporter | 1 | 2 | 0 | 2 | +2.80 | +1.80 | +1.00 | 1.40 | 0.00 | 0.00 | 0.00 | 0.00 | 1.80 | Reporter | 123 | 2 | 0 | 2 | +0.80 | +0.40 | +0.40 | 0.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 1024 | 2 | 1 | 1 | +0.10 | +0.10 | +0.00 | 0.05 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | Reporter | 140578 | 1 | 0 | 1 | +145.00 | +145.00 | +145.00 | 145.00 | 0.00 | 0.00 | 0.00 | 0.00 | 4.00 | Reporter | 1114235 | 1 | 0 | 1 | +2.30 | +2.30 | +2.30 | 2.30 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 1769595 | 1 | 0 | 1 | +15.00 | +15.00 | +15.00 | 15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 1835131 | 1 | 0 | 1 | +3.60 | +3.60 | +3.60 | 3.60 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 2031739 | 1 | 0 | 1 | +15.00 | +15.00 | +15.00 | 15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 2293883 | 1 | 0 | 1 | +1.40 | +1.40 | +1.40 | 1.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 2949243 | 1 | 0 | 1 | -15.00 | -15.00 | -15.00 | -15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | Reporter | 10879099 | 1 | 1 | 0 | +0.40 | +0.40 | +0.40 | 0.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 12517499 | 1 | 1 | 0 | +15.00 | +15.00 | +15.00 | 15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | Reporter | 12976251 | 1 | 0 | 1 | +2.90 | +2.90 | +2.90 | 2.90 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 13566075 | 1 | 0 | 1 | +15.00 | +15.00 | +15.00 | 15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 13959291 | 1 | 0 | 1 | +15.10 | +15.10 | +15.10 | 15.10 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 15728763 | 1 | 0 | 1 | +11.70 | +11.70 | +11.70 | 11.70 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 16121979 | 1 | 0 | 1 | +15.00 | +15.00 | +15.00 | 15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 16318587 | 1 | 0 | 1 | -15.00 | -15.00 | -15.00 | -15.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | Reporter | 16580731 | 1 | 0 | 1 | +2.10 | +2.10 | +2.10 | 2.10 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter | 21299323 | 1 | 0 | 1 | -22.80 | -22.80 | -22.80 | -22.80 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | Reporter | 27394171 | 1 | 1 | 0 | +0.00 | +0.00 | +0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | Reporter Reporter Beginning of sending 31 notifications to MQID Reporter 10 out of 31 messages sent. Reporter No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up. Reporter 20 out of 31 messages sent. Reporter No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up. Reporter 30 out of 31 messages sent. Reporter No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up. Reporter Sending 31 notifications completed
Nach dem Senden von Berichten an das Journal beginnt der Dienst mit dem Senden von Berichten an das Smartphone. 31 Nachrichten wurden in 4 Losen gesendet - 10 Nachrichten pro Minute.
Da am Vortag oder in den sieben Tagen vor dem Datum des Berichtsempfangs kein Handel stattfand, liefert der Dienst die entsprechende Meldung.
Wenn wir in den Einstellungen Berichte nach Symbolen und magischen Zahlen deaktivieren sowie Provisionen und Spreads ausschalten, Berichte für eine bestimmte Anzahl von Tagen verbieten, aber tägliche Berichte für die aktuelle Woche, den aktuellen Monat und das aktuelle Jahr zulassen,
Die Statistik sieht wie folgt aus:
Reporter -Service notifications OK Reporter 68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging) Reporter Beginning to create a list of closed positions... Reporter A list of 155 positions was created in 8515 ms Reporter "Daily" no trades Reporter "Weekly" no trades Reporter Report for the period "Month-to-date" from 2024.07.01 00:00 Reporter | Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Reporter | 2 | 22 | 3 | 19 | +46.00 | +5.80 | -0.30 | 2.09 | 0.00 | Reporter Reporter Report for the period "Year-to-date" from 2024.01.01 00:00 Reporter | Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Reporter | 2 | 107 | 31 | 76 | +264.00 | +36.70 | -7.20 | 2.47 | 0.00 | Reporter Reporter Report for the period "Entire period" from 1970.01.01 00:00 Reporter | Symbols | Trades | Long | Short | Profit | Max | Min | Avg | Costs | Reporter | 2 | 155 | 35 | 120 | +779.50 | +145.00 | -22.80 | 5.03 | 0.00 | Reporter Reporter Beginning of sending 3 notifications to MQID Reporter Sending 3 notifications completed
Alle oben genannten Berichtstabellen werden auf der Registerkarte Terminalexperten angezeigt.
Die Berichte werden in einer etwas anderen Form an das Smartphone gesendet:
Hier wird für die Kommission Null im Bericht nicht angezeigt (unabhängig davon, ob sie in den Einstellungen erlaubt sind), um Platz in der Berichtszeile zu sparen, die maximal 255 Zeichen lang sein darf.
Schlussfolgerung
Am Beispiel der Entwicklung einer Service-App haben wir die Möglichkeit betrachtet, verschiedene Daten zu speichern und Datenlisten nach allen möglichen Kriterien zu erhalten. Das betrachtete Konzept ermöglicht es, Sätze verschiedener Daten in Listen von Objekten zu erstellen, Zeiger auf die gewünschten Objekte nach den angegebenen Eigenschaften zu erhalten und auch Listen von Objekten zu erstellen, die nach der gewünschten Eigenschaft sortiert sind. All dies ermöglicht es, Daten in Form einer Datenbank zu speichern und die erforderlichen Informationen zu erhalten. Wir können die Informationen, die wir erhalten, z. B. in Form von Handelsberichten darstellen, sie an ein Journal weiterleiten und sie als Benachrichtigungen über MetaQuotes ID an das Smartphone des Nutzers senden.
Es ist auch möglich, das heute vorgestellte Programm weiter zu verfeinern, um Berichte zu erweitern und sie auf einem separaten Chart in Form von Tabellen, Grafiken und Diagrammen anzuzeigen, und zwar genau in der Form, die der Nutzer benötigt. All dies wird durch die Sprache MQL5 ermöglicht.
Sie können alle Projektdateien und das Archiv in den MQL5-Terminalordner entpacken und das Programm sofort verwenden, indem Sie vorher die Datei Reporter.mq5 kompilieren.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15346





- 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.