English Русский 中文 Español 日本語 Português
preview
Überwachung des Handels mit Push-Benachrichtigungen — Beispiel für einen MetaTrader 5 Dienst

Überwachung des Handels mit Push-Benachrichtigungen — Beispiel für einen MetaTrader 5 Dienst

MetaTrader 5Beispiele | 17 Februar 2025, 09:02
94 0
Artyom Trishkin
Artyom Trishkin

Inhalt


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:

  1. Struktur der Objekteigenschaften „(Teil I): Konzept, Datenmanagement und erste Ergebnisse“, 
  2. Struktur von Objektlisten „(Teil II): Erhebung (Collection) historischer Aufträge und Deals“ und
  3. 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:

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

    Beigefügte Dateien |
    Deal.mqh (49.73 KB)
    Position.mqh (67.53 KB)
    Select.mqh (43.22 KB)
    Account.mqh (32.94 KB)
    Accounts.mqh (15.89 KB)
    Reporter.mq5 (101.21 KB)
    MQL5.zip (36.5 KB)
    Entwicklung eines Expertenberaters für mehrere Währungen (Teil 16): Auswirkungen unterschiedlicher Kursverläufe auf die Testergebnisse Entwicklung eines Expertenberaters für mehrere Währungen (Teil 16): Auswirkungen unterschiedlicher Kursverläufe auf die Testergebnisse
    Es wird erwartet, dass der in der Entwicklung befindliche EA gute Ergebnisse beim Handel mit verschiedenen Brokern zeigt. Aber im Moment haben wir die Kurse eines MetaQuotes-Demokontos verwendet, um Tests durchzuführen. Lassen Sie uns sehen, ob unser EA bereit ist, auf einem Handelskonto mit anderen Kursen zu arbeiten, als die, die wir während der Tests und der Optimierung verwendet haben.
    Chaostheorie im Handel (Teil 2): Tiefer tauchen Chaostheorie im Handel (Teil 2): Tiefer tauchen
    Wir setzen unsere Untersuchung der Chaostheorie auf den Finanzmärkten fort. Dieses Mal werde ich seine Anwendbarkeit auf die Analyse von Währungen und anderen Vermögenswerten untersuchen.
    Von der Grundstufe bis zur Mittelstufe: Variablen (II) Von der Grundstufe bis zur Mittelstufe: Variablen (II)
    Heute werden wir uns ansehen, wie man mit statischen Variablen arbeitet. Diese Frage verwirrt oft viele Programmierer, sowohl Anfänger als auch solche mit einiger Erfahrung, denn es gibt mehrere Empfehlungen, die bei der Verwendung dieses Mechanismus beachtet werden müssen. Die hier vorgestellten Materialien sind ausschließlich für didaktische Zwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
    Von der Grundstufe bis zur Mittelstufe: Variablen (I) Von der Grundstufe bis zur Mittelstufe: Variablen (I)
    Vielen Programmieranfängern fällt es schwer zu verstehen, warum ihr Code nicht so funktioniert, wie sie es erwarten. Es gibt viele Dinge, die einen Code wirklich funktional machen. Es ist nicht nur ein Haufen verschiedener Funktionen und Operationen, die den Code zum Laufen bringen. Heute lade ich Sie dazu ein, zu lernen, wie man richtigen Code erstellt, anstatt Fragmente zu kopieren und einzufügen. Die hier vorgestellten Materialien sind ausschließlich für didaktische Zwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.