Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XIII): Objektklasse "Account" und die Kollektion von Konto-Objekten

30 August 2019, 09:04
Artyom Trishkin
0
277

Inhalt

Bevor wir die Bibliothek der Handelsklassen entwickeln, müssen wir einige zusätzliche handelsbezogene Klassen schaffen. Genauer gesagt, benötigen wir Daten über ein Handelskonto und die gehandelten Symbole. Dieser Artikel widmet sich dem Konto-Objekt.

Da sich die Kontodaten während des Handels ändern können, werden wir ein Konto-Objekt vorbereiten, gefolgt von der Kollektion der Konto-Objekte. Als Nächstes werden wir Tracking-Ereignisse auf dem Konto implementieren. Dies wird es uns ermöglichen, Veränderungen bei Leverage, Saldo, Gewinn/Verlust, Eigenkapital und Kontobeschränkungen zu erkennen.


Das Konto-Objekt

Ein Volumen des Handelskontos ist identisch mit zuvor angelegten Objekten, die ich in den vergangenen Artikeln beschrieben habe. Der einzige Unterschied zwischen diesem Objekt und den zuvor betrachteten besteht darin, dass es sich nicht um eine Art abstraktes Objekt handelt, bei dem Nachfolger einen bestimmten Objektstatus angeben. Das Konto-Objekt ist ein unabhängiges Objekt, das alle Eigenschaften des Kontos enthält. Solche Objekte sollen in die Kollektion der Konto-Objekte aufgenommen werden, so dass Benutzer Daten verschiedener Konten anhand verschiedener Parameter vergleichen können.

Wie üblich beginnen wir mit der Entwicklung aller Enumerationen der Eigenschaften des Konto-Objekts, die für die Arbeit mit der Klasse notwendig sind.
Öffnen Sie die Konto-Eigenschaften in der Editorhilfe:

Für die Funktion AccountInfoInteger()

ENUM_ACCOUNT_INFO_INTEGER

ID

Beschreibung

Typ der Eigenschaften

ACCOUNT_LOGIN

Kontonummer

long

ACCOUNT_TRADE_MODE

Typ des Handelskontos

ENUM_ACCOUNT_TRADE_MODE

ACCOUNT_LEVERAGE

Leverage (Hebel)

long

ACCOUNT_LIMIT_ORDERS

Maximalzahl erlaubter aktiver Pending-Orders

int

ACCOUNT_MARGIN_SO_MODE

Modus der Bestimmung die minimal verfügbare Margenhöhe

ENUM_ACCOUNT_STOPOUT_MODE

ACCOUNT_TRADE_ALLOWED

Handelserlaubnis des aktuellen Kontos

bool

ACCOUNT_TRADE_EXPERT

Handelserlaubnis eines EAs

bool

ACCOUNT_MARGIN_MODE

Modus der Margenberechnung

ENUM_ACCOUNT_MARGIN_MODE

ACCOUNT_CURRENCY_DIGITS

Anzahl der Stellen für eine Kontowährung, die für die genaue Darstellung der Handelsergebnisse erforderlich sind

int


Für die Funktion AccountInfoDouble()

ENUM_ACCOUNT_INFO_DOUBLE

ID

Beschreibung

Typ der Eigenschaften

ACCOUNT_BALANCE

Kontosaldo in der Kontowährung

double

ACCOUNT_CREDIT

Gutschrift in der Kontowährung

double

ACCOUNT_PROFIT

Laufender Gewinn auf einem Konto in Kontowährung

double

ACCOUNT_EQUITY

Kapital des Kontos in der Kontowährung

double

ACCOUNT_MARGIN

Reservierte Marge auf einem Konto in der Kontowährung

double

ACCOUNT_MARGIN_FREE

Frei verfügbare Geldmittel in der Kontowährung, um eine Position auf einem Konto zu eröffnen.

double

ACCOUNT_MARGIN_LEVEL

Höhe der Marge auf einem Konto in %

double

ACCOUNT_MARGIN_SO_CALL

Margenhöhe, ab der eine Einzahlung auf ein Konto erforderlich ist (Margin Call). Je nach ACCOUNT_MARGIN_SO_MODE wird die Eigenschaft entweder in % oder in der Kontowährung festgelegt.

double

ACCOUNT_MARGIN_SO_SO

Margenhöhe, ab der die verlustreichste Position geschlossen wird (Stop Out). Je nach ACCOUNT_MARGIN_SO_MODE wird die Eigenschaft entweder in % oder in der Kontowährung festgelegt.

double

ACCOUNT_MARGIN_INITIAL

Auf einem Konto reservierte Mittel, um einen Garantiebetrag für alle Pending-Order zu gewährleisten.

double

ACCOUNT_MARGIN_MAINTENANCE

Auf einem Konto reservierte Gelder, um einen Mindestbetrag für alle offenen Positionen zu gewährleisten.

double

ACCOUNT_ASSETS

Aktuelles Vermögen auf einem Konto

double

ACCOUNT_LIABILITIES

Laufende Verbindlichkeiten auf einem Konto

double

ACCOUNT_COMMISSION_BLOCKED

Aktuelle Summe der blockierten Kommissionen auf einem Konto

double


Für die Funktion AccountInfoString()

ENUM_ACCOUNT_INFO_STRING

ID

Beschreibung

Typ der Eigenschaften

ACCOUNT_NAME

Name des Kontoinhabers

string

ACCOUNT_SERVER

Name des Handelsservers

string

ACCOUNT_CURRENCY

Kontowährung

string

ACCOUNT_COMPANY

Name des kontoführenden Instituts

string


Das Konto-Objekt wird alle diese Eigenschaften enthalten, die im Klassenkonstruktor eingetragen werden sollen.

Fügen Sie in der Bibliotheksdatei Defines.mqh die Eigenschaften integer, double und string des Konto-Objekts hinzu, die den oben angezeigten Tabellen der Kontoeigenschaften entsprechen.
Da wir die Enumeration für die Arbeit mit Kontoereignissen bereits erstellt haben, wäre es sinnvoll, Kontoeigentumsdaten vor die zuvor erstellten Daten für die Arbeit mit Kontoereignissen zu stellen:

//+------------------------------------------------------------------+
//| Data for working with accounts                                   |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Account integer properties                                       |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
   ACCOUNT_PROP_LOGIN,                                      // Account number
   ACCOUNT_PROP_TRADE_MODE,                                 // Trading account type
   ACCOUNT_PROP_LEVERAGE,                                   // Provided leverage
   ACCOUNT_PROP_LIMIT_ORDERS,                               // Maximum allowed number of active pending orders
   ACCOUNT_PROP_MARGIN_SO_MODE,                             // Mode of setting the minimum available margin level
   ACCOUNT_PROP_TRADE_ALLOWED,                              // Permission to trade for the current account from the server side
   ACCOUNT_PROP_TRADE_EXPERT,                               // Permission to trade for an EA from the server side
   ACCOUNT_PROP_MARGIN_MODE,                                // Margin calculation mode
   ACCOUNT_PROP_CURRENCY_DIGITS                             // Number of digits for an account currency necessary for accurate display of trading results
  };
#define ACCOUNT_PROP_INTEGER_TOTAL    (9)                   // Total number of account's integer properties
#define ACCOUNT_PROP_INTEGER_SKIP     (0)                   // Number of account's integer properties not used in sorting
//+------------------------------------------------------------------+
//| Account real properties                                          |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_DOUBLE
  {
   ACCOUNT_PROP_BALANCE = ACCOUNT_PROP_INTEGER_TOTAL,       // Account balance in a deposit currency
   ACCOUNT_PROP_CREDIT,                                     // Credit in a deposit currency
   ACCOUNT_PROP_PROFIT,                                     // Current profit on an account in the account currency
   ACCOUNT_PROP_EQUITY,                                     // Equity on an account in the deposit currency
   ACCOUNT_PROP_MARGIN,                                     // Reserved margin on an account in a deposit currency
   ACCOUNT_PROP_MARGIN_FREE,                                // Free funds available for opening a position in a deposit currency
   ACCOUNT_PROP_MARGIN_LEVEL,                               // Margin level on an account in %
   ACCOUNT_PROP_MARGIN_SO_CALL,                             // Margin level, at which a deposit to an account is required (Margin Call)
   ACCOUNT_PROP_MARGIN_SO_SO,                               // Margin level, at which the most loss-making position is closed (Stop Out)
   ACCOUNT_PROP_MARGIN_INITIAL,                             // Funds reserved on an account to ensure a guarantee amount for all pending orders 
   ACCOUNT_PROP_MARGIN_MAINTENANCE,                         // Funds reserved on an account to ensure a minimum amount for all open positions
   ACCOUNT_PROP_ASSETS,                                     // Current assets on an account
   ACCOUNT_PROP_LIABILITIES,                                // Current liabilities on an account
   ACCOUNT_PROP_COMMISSION_BLOCKED                          // Current sum of blocked commissions on an account
  };
#define ACCOUNT_PROP_DOUBLE_TOTAL     (14)                  // Total number of account's real properties
#define ACCOUNT_PROP_DOUBLE_SKIP      (0)                   // Number of account's real properties not used in sorting
//+------------------------------------------------------------------+
//| Account string properties                                        |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_STRING
  {
   ACCOUNT_PROP_NAME = (ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_DOUBLE_TOTAL), // Client name
   ACCOUNT_PROP_SERVER,                                     // Trade server name
   ACCOUNT_PROP_CURRENCY,                                   // Deposit currency
   ACCOUNT_PROP_COMPANY                                     // Name of a company serving an account
  };
#define ACCOUNT_PROP_STRING_TOTAL     (4)                   // Total number of account's string properties
#define ACCOUNT_PROP_STRING_SKIP      (0)                   // Number of account string properties not used in sorting
//+------------------------------------------------------------------+
//| Possible account sorting criteria                                |
//+------------------------------------------------------------------+
#define FIRST_ACC_DBL_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP)
#define FIRST_ACC_STR_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_ACCOUNT_MODE
  {
   SORT_BY_ACCOUNT_LOGIN               =  0,                      // Sort by account number
   SORT_BY_ACCOUNT_TRADE_MODE          =  1,                      // Sort by trading account type
   SORT_BY_ACCOUNT_LEVERAGE            =  2,                      // Sort by leverage
   SORT_BY_ACCOUNT_LIMIT_ORDERS        =  3,                      // Sort by maximum acceptable number of existing pending orders
   SORT_BY_ACCOUNT_MARGIN_SO_MODE      =  4,                      // Sort by mode for setting the minimum acceptable margin level
   SORT_BY_ACCOUNT_TRADE_ALLOWED       =  5,                      // Sort by permission to trade for the current account
   SORT_BY_ACCOUNT_TRADE_EXPERT        =  6,                      // Sort by permission to trade for an EA
   SORT_BY_ACCOUNT_MARGIN_MODE         =  7,                      // Sort by margin calculation mode
   SORT_BY_ACCOUNT_CURRENCY_DIGITS     =  8,                      // Sort by number of digits for an account currency
    
   SORT_BY_ACCOUNT_BALANCE             =  FIRST_ACC_DBL_PROP,     // Sort by an account balance in the deposit currency
   SORT_BY_ACCOUNT_CREDIT              =  FIRST_ACC_DBL_PROP+1,   // Sort by credit in a deposit currency
   SORT_BY_ACCOUNT_PROFIT              =  FIRST_ACC_DBL_PROP+2,   // Sort by the current profit on an account in the deposit currency
   SORT_BY_ACCOUNT_EQUITY              =  FIRST_ACC_DBL_PROP+3,   // Sort by an account equity in the deposit currency
   SORT_BY_ACCOUNT_MARGIN              =  FIRST_ACC_DBL_PROP+4,   // Served by an account reserved margin in the deposit currency
   SORT_BY_ACCOUNT_MARGIN_FREE         =  FIRST_ACC_DBL_PROP+5,   // Sort by account free funds available for opening a position in the deposit currency
   SORT_BY_ACCOUNT_MARGIN_LEVEL        =  FIRST_ACC_DBL_PROP+6,   // Sort by account margin level in %
   SORT_BY_ACCOUNT_MARGIN_SO_CALL      =  FIRST_ACC_DBL_PROP+7,   // Sort by margin level requiring depositing funds to an account (Margin Call)
   SORT_BY_ACCOUNT_MARGIN_SO_SO        =  FIRST_ACC_DBL_PROP+8,   // Sort by margin level, at which the most loss-making position is closed (Stop Out)
   SORT_BY_ACCOUNT_MARGIN_INITIAL      =  FIRST_ACC_DBL_PROP+9,   // Sort by funds reserved on an account to ensure a guarantee amount for all pending orders 
   SORT_BY_ACCOUNT_MARGIN_MAINTENANCE  =  FIRST_ACC_DBL_PROP+10,  // Sort by funds reserved on an account to ensure a minimum amount for all open positions
   SORT_BY_ACCOUNT_ASSETS              =  FIRST_ACC_DBL_PROP+11,  // Sort by the amount of the current assets on an account
   SORT_BY_ACCOUNT_LIABILITIES         =  FIRST_ACC_DBL_PROP+12,  // Sort by the current liabilities on an account
   SORT_BY_ACCOUNT_COMMISSION_BLOCKED  =  FIRST_ACC_DBL_PROP+13,  // Sort by the current amount of blocked commissions on an account

   SORT_BY_ACCOUNT_NAME                =  FIRST_ACC_STR_PROP,     // Sort by a client name
   SORT_BY_ACCOUNT_SERVER              =  FIRST_ACC_STR_PROP+1,   // Sort by a trade server name
   SORT_BY_ACCOUNT_CURRENCY            =  FIRST_ACC_STR_PROP+2,   // Sort by a deposit currency
   SORT_BY_ACCOUNT_COMPANY             =  FIRST_ACC_STR_PROP+3    // Sort by a name of a company serving an account
  };
//+------------------------------------------------------------------+
//| Data for working with account events                             |
//+------------------------------------------------------------------+

Hier ist uns alles aus den vorangegangenen Artikeln bekannt, so dass es keinen Sinn macht, über die Anordnung der Enumerationen und das Setzen unbenutzter Objekteigenschaften und Makro-Substitutionen zu verweilen, um die Anzahl der Eigenschaften anzugeben, die für eine genaue Berechnung einer Adresse der ersten Enumerationskonstante für den nächsten Typ von Objekteigenschaften übergeben wurden. Alles, was in den vorherigen Artikeln beschrieben wurde, nämlich im Abschnitt " Implementierung der Ereignisbehandlung auf einem Netting-Konto" des sechsten Teils der Bibliotheksbeschreibung.

Das Einzige, worauf wir hier näher eingehen können, ist "Margenberechnungsmodus":
Da es in MQL4 keine Enumeration von ENUM_ACCOUNT_MARGIN_MODE gibt, müssen wir sie für die Kompilierung in MQL4 angeben. Fügen wir es an das Ende der Datei ToMQL4.mqh hinzu:

//+------------------------------------------------------------------+
//| Margin calculation mode                                          |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_MARGIN_MODE
  {
   ACCOUNT_MARGIN_MODE_RETAIL_NETTING,
   ACCOUNT_MARGIN_MODE_EXCHANGE,
   ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
  };
//+------------------------------------------------------------------+

Da nun alle Daten vorbereitet sind, kann ein Konto-Objekt angelegt werden.

Erstellen Sie im Bibliotheksordner \MQL5\Include\DoEasy\Objects\ den Unterordner Accounts und stellen Sie die neue Klasse CAccount in der Datei Account.mqh vor.

Fügen Sie in der neu erstellten Klassendatei die Deklarationen aller notwendigen Methoden hinzu.
Die meisten dieser Methoden sind bereits "Standard" für die Bibliotheksobjekte. Es gibt jedoch einen kleinen Vorbehalt: Da diese Klasse nicht die Existenz von Abkömmlingen impliziert, gibt es keinen 'protected' Klassenkonstruktor, der den Status eines Objekts akzeptiert und setzt. Daher verfügt das Konto-Objekt über keine Eigenschaft "status", und sein Konstruktor akzeptiert keine Argumente. Gleichzeitig werden wir die virtuellen Methoden lassen, die die Flags des Objekts zurückgeben, das eine bestimmte Eigenschaft unterstützt für mögliche zukünftige Nachkommen der Klasse:
.

//+------------------------------------------------------------------+
//|                                                      Account.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Account class                                                    |
//+------------------------------------------------------------------+
class CAccount : public CObject
  {
private:
   long              m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL];           // Integer properties
   double            m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL];          // Real properties
   string            m_string_prop[ACCOUNT_PROP_STRING_TOTAL];          // String properties

//--- Return the index of the array the account's (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_ACCOUNT_PROP_DOUBLE property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL;                          }
   int               IndexProp(ENUM_ACCOUNT_PROP_STRING property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_DOUBLE_TOTAL;}
public:
//--- Konstructor                   
                     CAccount(void);
protected:
   
public:
//--- Set (1) integer, (2) real and (3) string properties of an account
   void              SetProperty(ENUM_ACCOUNT_PROP_INTEGER property,long value)        { this.m_long_prop[property]=value;                                  }
   void              SetProperty(ENUM_ACCOUNT_PROP_DOUBLE property,double value)       { this.m_double_prop[this.IndexProp(property)]=value;                }
   void              SetProperty(ENUM_ACCOUNT_PROP_STRING property,string value)       { this.m_string_prop[this.IndexProp(property)]=value;                }
//--- Return (1) integer, (2) real and (3) string properties of an account from the property array
   long              GetProperty(ENUM_ACCOUNT_PROP_INTEGER property)             const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_ACCOUNT_PROP_DOUBLE property)              const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_ACCOUNT_PROP_STRING property)              const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Return the flag of calculating MarginCall and StopOut levels in %
   bool              IsPercentsForSOLevels(void)                                 const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT;          }
//--- Return the flag of supporting the property by the account object
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_INTEGER property)               { return true; }
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE property)                { return true; }
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_STRING property)                { return true; }

//--- Compare CAccount objects by all possible properties (for sorting the lists by a specified account object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CAccount objects by account properties (to search for equal account objects)
   bool              IsEqual(CAccount* compared_account) const;
//+------------------------------------------------------------------+
//| Methods of a simplified access to the account object properties  |
//+------------------------------------------------------------------+
//--- Return the account's integer properties
   ENUM_ACCOUNT_TRADE_MODE    TradeMode(void)                     const { return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE);        }
   ENUM_ACCOUNT_STOPOUT_MODE  MarginSOMode(void)                  const { return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE);  }
   ENUM_ACCOUNT_MARGIN_MODE   MarginMode(void)                    const { return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE);      }
   long              Login(void)                                  const { return this.GetProperty(ACCOUNT_PROP_LOGIN);                                      }
   long              Leverage(void)                               const { return this.GetProperty(ACCOUNT_PROP_LEVERAGE);                                   }
   long              LimitOrders(void)                            const { return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS);                               }
   long              TradeAllowed(void)                           const { return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED);                              }
   long              TradeExpert(void)                            const { return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT);                               }
   long              CurrencyDigits(void)                         const { return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS);                            }
   
//--- Return the account's real properties
   double            Balance(void)                                const { return this.GetProperty(ACCOUNT_PROP_BALANCE);                                    }
   double            Credit(void)                                 const { return this.GetProperty(ACCOUNT_PROP_CREDIT);                                     }
   double            Profit(void)                                 const { return this.GetProperty(ACCOUNT_PROP_PROFIT);                                     }
   double            Equity(void)                                 const { return this.GetProperty(ACCOUNT_PROP_EQUITY);                                     }
   double            Margin(void)                                 const { return this.GetProperty(ACCOUNT_PROP_MARGIN);                                     }
   double            MarginFree(void)                             const { return this.GetProperty(ACCOUNT_PROP_MARGIN_FREE);                                }
   double            MarginLevel(void)                            const { return this.GetProperty(ACCOUNT_PROP_MARGIN_LEVEL);                               }
   double            MarginSOCall(void)                           const { return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_CALL);                             }
   double            MarginSOSO(void)                             const { return this.GetProperty(ACCOUNT_PROP_MARGIN_SO_SO);                               }
   double            MarginInitial(void)                          const { return this.GetProperty(ACCOUNT_PROP_MARGIN_INITIAL);                             }
   double            MarginMaintenance(void)                      const { return this.GetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE);                         }
   double            Assets(void)                                 const { return this.GetProperty(ACCOUNT_PROP_ASSETS);                                     }
   double            Liabilities(void)                            const { return this.GetProperty(ACCOUNT_PROP_LIABILITIES);                                }
   double            ComissionBlocked(void)                       const { return this.GetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED);                         }
   
//--- Return the account's string properties
   string            Name(void)                                   const { return this.GetProperty(ACCOUNT_PROP_NAME);                                       }
   string            Server(void)                                 const { return this.GetProperty(ACCOUNT_PROP_SERVER);                                     }
   string            Currency(void)                               const { return this.GetProperty(ACCOUNT_PROP_CURRENCY);                                   }
   string            Company(void)                                const { return this.GetProperty(ACCOUNT_PROP_COMPANY);                                    }

//+------------------------------------------------------------------+
//| Descriptions of the account object properties                    |
//+------------------------------------------------------------------+
//--- Return the description of the account's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING property);
//--- Return a name of a trading account type
   string            TradeModeDescription(void)    const;
//--- Return the description of the mode for setting the minimum available margin level
   string            MarginSOModeDescription(void) const;
//--- Return the description of the margin calculation mode
   string            MarginModeDescription(void)   const;
//--- Display the description of the account properties in the journal (full_prop=true - all properties, false - supported ones only)
   void              Print(const bool full_prop=false);
//--- Display a short account description in the journal
   void              PrintShort(void);
//---
  };
//+------------------------------------------------------------------+

Schreiben Sie den Klassenkonstruktor außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CAccount::CAccount(void)
  {
//--- Sichern der ganzzahligen Eigenschaften
   this.m_long_prop[ACCOUNT_PROP_LOGIN]                              = ::AccountInfoInteger(ACCOUNT_LOGIN);
   this.m_long_prop[ACCOUNT_PROP_TRADE_MODE]                         = ::AccountInfoInteger(ACCOUNT_TRADE_MODE);
   this.m_long_prop[ACCOUNT_PROP_LEVERAGE]                           = ::AccountInfoInteger(ACCOUNT_LEVERAGE);
   this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS]                       = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);
   this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE]                     = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED]                      = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
   this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT]                       = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT);
   this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE]                        = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ;
   this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS]                    = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ;
   
//--- Sichern der Double-Eigenschaften
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)]          = ::AccountInfoDouble(ACCOUNT_BALANCE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)]           = ::AccountInfoDouble(ACCOUNT_CREDIT);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)]           = ::AccountInfoDouble(ACCOUNT_PROFIT);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)]           = ::AccountInfoDouble(ACCOUNT_EQUITY);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)]           = ::AccountInfoDouble(ACCOUNT_MARGIN);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)]      = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)]     = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)]   = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)]     = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)]   = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)]           = ::AccountInfoDouble(ACCOUNT_ASSETS);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)]      = ::AccountInfoDouble(ACCOUNT_LIABILITIES);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED);
   
//--- Sichern der String-Eigenschaften
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)]             = ::AccountInfoString(ACCOUNT_NAME);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)]           = ::AccountInfoString(ACCOUNT_SERVER);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)]         = ::AccountInfoString(ACCOUNT_CURRENCY);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)]          = ::AccountInfoString(ACCOUNT_COMPANY);
   
  }
//+-------------------------------------------------------------------+

Hier ist alles klar: Jeder Objekteigenschaft wird über die Funktion AccountInfo die entsprechende Account-Eigenschaft zugeordnet.
Für die beiden Eigenschaften, die in MQL4 nicht vorhanden sind, erfolgt die Auswahl während der bedingten Kompilierungsanweisungen: wir erhalten die entsprechenden Eigenschaften für den "Margenberechnungsmodus" und die Eigenschaft "Anzahl der Dezimalstellen für die Kontowährung" für MQL5, während für MQL4 Sie einfach ACCOUNT_MARGIN_MODE_RETAIL_HEDGING (Hedge Account) aus der Enumeration ENUM_ACCOUNT_MARGIN_MODE für die erste Eigenschaft angeben und zwei Dezimalstellen für die zweite Eigenschaft zurück.

Lassen Sie uns die Methode zum Suchen und Sortieren von Konto-Objekten in ihrer Liste der Kollektionen implementieren.
Die Methode ist identisch mit den in den Bibliotheksobjekten zuvor beschriebenen. Deshalb werde ich hier nur die Auflistung anführen:

//+-------------------------------------------------------------------+
//|Compare CAccount objects by all possible properties                |
//+-------------------------------------------------------------------+
int CAccount::Compare(const CObject *node,const int mode=0) const
  {
   const CAccount *account_compared=node;
//--- compare integer properties of two accounts
   if(mode<ACCOUNT_PROP_INTEGER_TOTAL)
     {
      long value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_ACCOUNT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- comparing real properties of two accounts
   else if(mode<ACCOUNT_PROP_DOUBLE_TOTAL+ACCOUNT_PROP_INTEGER_TOTAL)
     {
      double value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_ACCOUNT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- comparing string properties of two accounts
   else if(mode<ACCOUNT_PROP_DOUBLE_TOTAL+ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_STRING_TOTAL)
     {
      string value_compared=account_compared.GetProperty((ENUM_ACCOUNT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_ACCOUNT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);

     }
   return 0;
  }
//+------------------------------------------------------------------+

Um zwei Konto-Objekte zu vergleichen, müssen wir deren unänderliche Eigenschaften vergleichen, um festzustellen, ob diese Objekte zu verschiedenen Konten gehören. Kontonummer (Login), Benutzername und Firmenname werden zur genauen Identifizierung angegeben. Dies sind die Eigenschaften der beiden verglichenen Konten, die wir in der Vergleichsmethode der Konto-Objekte überprüfen werden:

//+------------------------------------------------------------------+
//| Compare CAccount objects by account properties                   |
//+------------------------------------------------------------------+
bool CAccount::IsEqual(CAccount *compared_account) const
  {
   if(this.GetProperty(ACCOUNT_PROP_COMPANY)!=compared_account.GetProperty(ACCOUNT_PROP_COMPANY) ||
      this.GetProperty(ACCOUNT_PROP_LOGIN)!=compared_account.GetProperty(ACCOUNT_PROP_LOGIN)     ||
      this.GetProperty(ACCOUNT_PROP_NAME)!=compared_account.GetProperty(ACCOUNT_PROP_NAME)
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

Der Zeiger auf ein verglichenes Objekt wird an die Methode übergeben und die drei Eigenschaften der beiden Objekte ( Firmenname, Kontonummer und Kundenname) werden überprüft. Wenn eine der Objekteigenschaften nicht gleich ist, gehören sie zu verschiedenen Konten — Rückgabe von false. Nachdem alle drei Vergleiche erfolgreich bestanden wurden, wird true zurück gegeben — die Objekte sind gleich.

Andere Klassenmethoden sind "Service-Methoden" und müssen nicht kontrolliert werden, da ihre Listen alle notwendigen Informationen enthalten. Außerdem haben wir ähnliche Methoden in den vorherigen Teilen der Bibliotheksbeschreibung analysiert:

//+------------------------------------------------------------------+
//| Display account properties in the journal                        |
//+------------------------------------------------------------------+
void CAccount::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров аккаунта","Beginning of Account parameter list")," ==================");
   int beg=0, end=ACCOUNT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ACCOUNT_PROP_INTEGER prop=(ENUM_ACCOUNT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ACCOUNT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ACCOUNT_PROP_DOUBLE prop=(ENUM_ACCOUNT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ACCOUNT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ACCOUNT_PROP_STRING prop=(ENUM_ACCOUNT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров аккаунта","End of Account parameter list")," ==================\n");
  }
//+------------------------------------------------------------------+
//| Display the brief account description in the journal             |
//+------------------------------------------------------------------+
void CAccount::PrintShort(void)
  {
   string mode=(this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? ", Hedge" : this.MarginMode()==ACCOUNT_MARGIN_MODE_EXCHANGE ? ", Exhange" : "");
   string names=TextByLanguage("Счёт ","Account ")+(string)this.Login()+": "+this.Name()+" ("+this.Company()+" ";
   string values=DoubleToString(this.Balance(),(int)this.CurrencyDigits())+" "+this.Currency()+", 1:"+(string)+this.Leverage()+mode+", "+this.TradeModeDescription()+")";
   ::Print(names,values);
  }
//+------------------------------------------------------------------+
//| Display a description of an account integer property             |
//+------------------------------------------------------------------+
string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property)
  {
   return
     (
      property==ACCOUNT_PROP_LOGIN           ?  TextByLanguage("Номер счёта","Account number")+": "+(string)this.GetProperty(property)                                                    :
      property==ACCOUNT_PROP_TRADE_MODE      ?  TextByLanguage("Тип торгового счета","Account trade mode")+": "+this.TradeModeDescription()                                               :
      property==ACCOUNT_PROP_LEVERAGE        ?  TextByLanguage("Размер предоставленного плеча","Account leverage")+": "+(string)this.GetProperty(property) :
      property==ACCOUNT_PROP_LIMIT_ORDERS    ?  TextByLanguage("Максимально допустимое количество действующих отложенных ордеров","Maximum allowed number of active pending orders")+": "+
                                                   (string)this.GetProperty(property)                                                                                                     :
      property==ACCOUNT_PROP_MARGIN_SO_MODE  ?  TextByLanguage("Режим задания минимально допустимого уровня залоговых средств","Mode for setting the minimal allowed margin")+": "+
                                                   this.MarginSOModeDescription()                                                          :
      property==ACCOUNT_PROP_TRADE_ALLOWED   ?  TextByLanguage("Разрешенность торговли для текущего счета","Allowed trade for the current account")+": "+
                                                   (this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))                                                 :
      property==ACCOUNT_PROP_TRADE_EXPERT    ?  TextByLanguage("Разрешенность торговли для эксперта","Allowed trade for an Expert Advisor")+": "+
                                                   (this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))                                                 :
      property==ACCOUNT_PROP_MARGIN_MODE     ?  TextByLanguage("Режим расчета маржи","Margin calculation mode")+": "+
                                                   this.MarginModeDescription()                                                         :
      property==ACCOUNT_PROP_CURRENCY_DIGITS ?  TextByLanguage("Количество знаков после запятой для валюты счета","Number of decimal places in account currency")+": "+
                                                   (string)this.GetProperty(property)                                                                                                     :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return a description of an account real property                 |
//+------------------------------------------------------------------+
string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE property)
  {
   return
     (
      property==ACCOUNT_PROP_BALANCE         ?  TextByLanguage("Баланс счета","Account balance")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_CREDIT          ?  TextByLanguage("Предоставленный кредит","Account credit")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_PROFIT          ?  TextByLanguage("Текущая прибыль на счете","Current profit of an account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_EQUITY          ?  TextByLanguage("Собственные средства на счете","Account equity")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_MARGIN          ?  
         TextByLanguage("Зарезервированные залоговые средства на счете","Account margin used in deposit currency")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_MARGIN_FREE     ?  
         TextByLanguage("Свободные средства на счете, доступные для открытия позиции","Account free margin")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_MARGIN_LEVEL    ?  TextByLanguage("Уровень залоговых средств на счете в процентах","Account margin level in percentage")+": "+
                                                   ::DoubleToString(this.GetProperty(property),1)+"%"                                                                                     :
      property==ACCOUNT_PROP_MARGIN_SO_CALL  ?  TextByLanguage("Уровень залоговых средств для наступления Margin Call","Margin call level")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(this.IsPercentsForSOLevels() ? 1 : (int)this.CurrencyDigits()))+
                                                   (this.IsPercentsForSOLevels() ? "%" : this.Currency())                                                                                 :
      property==ACCOUNT_PROP_MARGIN_SO_SO    ?  TextByLanguage("Уровень залоговых средств для наступления Stop Out","Margin stop out level")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(this.IsPercentsForSOLevels() ? 1 : (int)this.CurrencyDigits()))+
                                                   (this.IsPercentsForSOLevels() ? "%" : this.Currency())                                                                                 :
      property==ACCOUNT_PROP_MARGIN_INITIAL  ? 
         TextByLanguage("Зарезервированные средства для обеспечения гарантийной суммы по всем отложенным ордерам","Amount reserved on account to cover margin of all pending orders ")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_MARGIN_MAINTENANCE  ? 
         TextByLanguage("Зарезервированные средства для обеспечения минимальной суммы по всем открытым позициям","Min equity reserved on account to cover min amount of all open positions")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_ASSETS          ?  TextByLanguage("Текущий размер активов на счёте","Current account assets")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_LIABILITIES     ?  TextByLanguage("Текущий размер обязательств на счёте","Current liabilities on account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_COMMISSION_BLOCKED ?  
         TextByLanguage("Сумма заблокированных комиссий по счёту","Currently blocked commission amount on account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return a description of an account string property               |
//+------------------------------------------------------------------+
string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING property)
  {
   return
     (
      property==ACCOUNT_PROP_NAME      ?  TextByLanguage("Имя клиента","Client name")+": \""+this.GetProperty(property)+"\""                                                     :
      property==ACCOUNT_PROP_SERVER    ?  TextByLanguage("Имя торгового сервера","Trade server name")+": \""+this.GetProperty(property)+"\""                                     :
      property==ACCOUNT_PROP_CURRENCY  ?  TextByLanguage("Валюта депозита","Account currency")+": \""+this.GetProperty(property)+"\""                                            :
      property==ACCOUNT_PROP_COMPANY   ?  TextByLanguage("Имя компании, обслуживающей счет","Name of company that serves account")+": \""+this.GetProperty(property)+"\""  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return a trading account type name                               |
//+------------------------------------------------------------------+
string CAccount::TradeModeDescription(void) const
  {
   return
     (
      this.TradeMode()==ACCOUNT_TRADE_MODE_DEMO    ? TextByLanguage("Демонстрационный счёт","Demo account") :
      this.TradeMode()==ACCOUNT_TRADE_MODE_CONTEST ? TextByLanguage("Конкурсный счёт","Contest account")    :
      this.TradeMode()==ACCOUNT_TRADE_MODE_REAL    ? TextByLanguage("Реальный счёт","Real account")         :
      TextByLanguage("Неизвестный тип счёта","Unknown account type")
     );
  }
//+------------------------------------------------------------------+
//| Return a description of a mode for setting                       |
//| minimum available margin level                                   |
//+------------------------------------------------------------------+
string CAccount::MarginSOModeDescription(void) const
  {
   return
     (
      this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT  ? TextByLanguage("Уровень задается в процентах","Account stop out mode in percentage") : 
      TextByLanguage("Уровень задается в деньгах","Account stop out mode in money")
     );
  }
//+------------------------------------------------------------------+
//| Return a description of a margin calculation mode                |
//+------------------------------------------------------------------+
string CAccount::MarginModeDescription(void) const
  {
   return
     (
      this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_NETTING ? TextByLanguage("Внебиржевой рынок в режиме \"Неттинг\"","Netting mode") :
      this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? TextByLanguage("Внебиржевой рынок в режиме \"Хеджинг\"","Hedging mode") :
      TextByLanguage("Биржевой рынок","Exchange markets mode")
     );
  }
//+------------------------------------------------------------------+

Die vollständige Auflistung der Kontoklasse finden Sie in den an den Artikel angehängten Dateien. Testen wir die Klasse.

Testen des Konto-Objekts

Um zu überprüfen, ob die Klasse die Kontodaten korrekt erhält, wird vorübergehend die Klassendatei zum Hauptobjekt der Bibliothek inkludiert — die Klasse CEngine:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
#include "Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {

Nach dem Einbinden der Objektklassendatei des Kontos ist das Programm in der Lage, das Objekt zu sehen.

Zum Testen der Klasse nehmen Sie den EA aus dem vorherigen Artikel — \MQL5\Experts\TestDoEasy\Part11\TestDoEasyPart11.mq5 und speichern Sie es unter dem Namen TestDoEasyPart12_1.mq5 im Ordner \MQL5\Experts\TestDoEasy\Part12.

Um das Konto-Objekt einzubinden und zu testen, fügen Sie folgende Zeilen der Funktion OnInit() des EA hinzu (die Prüfung ist während der Initialisierung durchzuführen):

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal, 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;

//--- Check and remove remaining EA graphical objects
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Set trailing activation button status
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Set CTrade trading class parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Fast check of the account object
   CAccount* acc=new CAccount();
   if(acc!=NULL)                
     {                          
      acc.PrintShort();         
      acc.Print();              
      delete acc;               
     }                          
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Hier erstellen Sie das Konto-Objekt und es werden Erfolg verkürzt die Kontodaten angezeigt und die vollständige Liste der Kontoparameter anschließend im Journal ausgedruckt. Entfernen Sie das Konto-Objekt nach Beendigung.

Starten Sie den EA auf dem Chart mit einem beliebigen Symbol und sehen Sie sich die Expertenprotokolle an:


Alle Kontodaten werden korrekt angezeigt.

Die Kollektion von Konto-Objekten

Da beim Ändern eines Kontos alle EAs neu initialisiert werden, werden zuerst die Destruktoren aufgerufen, gefolgt von Klassenkonstruktoren. Dadurch verliert der EA vor dem Ändern des Kontos das vorher vorhandene Konto-Objekt. Um die Kollektion des Kontos aufrechtzuerhalten, müssen wir uns die Daten der vorherigen Konten merken, mit dem das Terminal verbunden ist. Speichern Sie dazu die Daten des aktuellen Konto-Objekts in der Datei im Destruktor der Klasse der Kollektion der Konten und laden Sie Daten aus den Dateien in den Klassenkonstruktor. Somit wird die Kollektion der Konten mit Daten über alle Konten gefüllt, an die sich das Terminal während des Programmbetriebs auf Basis der Bibliothek angeschlossen hat. Da die Dateien im gemeinsamen Ordner aller Client-Terminals gespeichert sind, sieht jedes gestartete Terminal alle Konten, mit denen das Terminal auf dem angeschlossenen PC arbeitet, vorausgesetzt, dass das bibliotheksbasierte Programm in diesem Terminal arbeitet.
Die neue Klasse der Kollektion der Konten wird die Möglichkeit haben, Daten über alle bestehenden Konten anhand ihrer verschiedenen Parameter zu vergleichen.

Um das Konto-Objekt in der Datei zu speichern, müssen wir die Methode zum Speichern von Daten in einer Datei in der Klasse CAccount erstellen.
Alle erstellten Objekte werden von CObject übernommen — dem Basisobjekt der Standardbibliothek. Die Klasse verfügt auch über die virtuellen Methoden zum Speichern und Laden eines Objekts in eine Datei:

//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class for storing elements.                        |
//+------------------------------------------------------------------+
class CObject
  {
private:
   CObject          *m_prev;               // previous item of list
   CObject          *m_next;               // next item of list

public:
                     CObject(void): m_prev(NULL),m_next(NULL)            {                 }
                    ~CObject(void)                                       {                 }
   //--- methods to access protected data
   CObject          *Prev(void)                                    const { return(m_prev); }
   void              Prev(CObject *node)                                 { m_prev=node;    }
   CObject          *Next(void)                                    const { return(m_next); }
   void              Next(CObject *node)                                 { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(const int file_handle)                         { return(true);   }
   virtual bool      Load(const int file_handle)                         { return(true);   }
   //--- Methode zur Identifizierung des Objekts
   virtual int       Type(void)                                    const { return(0);      }
   //--- method of comparing the objects
   virtual int       Compare(const CObject *node,const int mode=0) const { return(0);      }
  };
//+------------------------------------------------------------------+

Die Methoden tun nichts, und wir müssen sie in unseren abgeleiteten Klassen neu definieren, wo sie gebraucht werden (Klasse CAccount).

Um alle Eigenschaften des Konto-Objekts in der Datei zu speichern, verwenden wir eine einfache Struktur und speichern sie in der Datei. Die Objektfelder enthalten jedoch Zeilen, d.h. es handelt sich nicht um eine POD-Struktur. Daher müssen wir alle Zeichenketteneigenschaften des Objekts konvertieren, wenn wir sie in den Arrays uchar der Strukturfelder mit der konstanten Größe speichern. In diesem Fall können wir alle Daten über die Eigenschaften des Account Objekts als Struktur mit der Funktion FileWriteArray() in der Datei speichern.

Um das Verzeichnis für die Speicherung der Bibliotheksdateien und die konstante Größe der Arrays uchar zu erstellen, erstellen Sie Makro-Substitutionen in der Datei Defines.mqh:

#define DIRECTORY                   ("DoEasy\\")               // Library directory for placing class object folders
#define UCHAR_ARRAY_SIZE            (64)                       // Size of uchar arrays for storing string properties

Da die Länge der Kommentarzeile auf 64 Zeichen begrenzt ist, wird die Array-Größe entsprechend diesem Wert erstellt. Weiterhin müssen wir möglicherweise Auftragsobjekte in Dateien speichern, und die Länge von weniger als 64 Symbolen kann sich als ungeeignet erweisen. Es kann sich durchaus herausstellen, dass eine größere Zeichenkettengröße für die Zeichenketteneigenschaften des Kontos zugewiesen wird. Wenn der Test zeigt, dass die Größe nicht ausreicht, um die Namen der Unternehmen zu speichern, die das Konto betreuen, kann sie jederzeit erhöht werden.

Erstellen Sie die notwendige Struktur zum Speichern der Eigenschaften des Konto-Objekts und Klassenmitgliedervariablen für die Arbeit mit Dateien im 'private' Bereich der Klasse CAccount:

//+------------------------------------------------------------------+
//| Account class                                                    |
//+------------------------------------------------------------------+
class CAccount : public CObject
  {
private:
   struct SData
     {
      //--- Account integer properties
      long           login;                        // ACCOUNT_LOGIN (Account number)
      int            trade_mode;                   // ACCOUNT_TRADE_MODE (Trading account type)
      long           leverage;                     // ACCOUNT_LEVERAGE (Leverage)
      int            limit_orders;                 // ACCOUNT_LIMIT_ORDERS (Maximum allowed number of active pending orders)
      int            margin_so_mode;               // ACCOUNT_MARGIN_SO_MODE (Mode of setting the minimum available margin level)
      bool           trade_allowed;                // ACCOUNT_TRADE_ALLOWED (Permission to trade for the current account from the server side)
      bool           trade_expert;                 // ACCOUNT_TRADE_EXPERT (Permission to trade for an EA from the server side)
      int            margin_mode;                  // ACCOUNT_MARGIN_MODE (Margin calculation mode)
      int            currency_digits;              // ACCOUNT_CURRENCY_DIGITS (Number of digits for an account currency)
      //--- Account real properties
      double         balance;                      // ACCOUNT_BALANCE (Account balance in the deposit currency)
      double         credit;                       // ACCOUNT_CREDIT (Credit in the deposit currency)
      double         profit;                       // ACCOUNT_PROFIT (Current profit on an account in the deposit currency)
      double         equity;                       // ACCOUNT_EQUITY (Equity on an account in the deposit currency)
      double         margin;                       // ACCOUNT_MARGIN (Reserved margin on an account in the deposit currency)
      double         margin_free;                  // ACCOUNT_MARGIN_FREE (Free funds available for opening a position in the deposit currency)
      double         margin_level;                 // ACCOUNT_MARGIN_LEVEL (Margin level on an account in %)
      double         margin_so_call;               // ACCOUNT_MARGIN_SO_CALL (Margin Call level)
      double         margin_so_so;                 // ACCOUNT_MARGIN_SO_SO (StopOut level)
      double         margin_initial;               // ACCOUNT_MARGIN_INITIAL (Funds reserved on an account to ensure a guarantee amount for all pending orders)
      double         margin_maintenance;           // ACCOUNT_MARGIN_MAINTENANCE (Funds reserved on an account to ensure a minimum amount for all open positions)
      double         assets;                       // ACCOUNT_ASSETS (Current assets on an account)
      double         liabilities;                  // ACCOUNT_LIABILITIES (Current liabilities on an account)
      double         comission_blocked;            // ACCOUNT_COMMISSION_BLOCKED (Current sum of blocked commissions on an account)
      //--- Account string properties
      uchar          name[UCHAR_ARRAY_SIZE];       // ACCOUNT_NAME (Client name)
      uchar          server[UCHAR_ARRAY_SIZE];     // ACCOUNT_SERVER (Trade server name)
      uchar          currency[UCHAR_ARRAY_SIZE];   // ACCOUNT_CURRENCY (Deposit currency)
      uchar          company[UCHAR_ARRAY_SIZE];    // ACCOUNT_COMPANY (Name of a company serving an account)
     };
   SData             m_struct_obj;                                      // Account object structure
   uchar             m_uchar_array[];                                   // uchar array of the account object structure

   
//--- Object properties
   long              m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL];           // Integer properties
   double            m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL];          // Real properties
   string            m_string_prop[ACCOUNT_PROP_STRING_TOTAL];          // String properties

//--- Return the array of the index the account (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_ACCOUNT_PROP_DOUBLE property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL;                                   }
   int               IndexProp(ENUM_ACCOUNT_PROP_STRING property) const { return(int)property-ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_DOUBLE_TOTAL;         }
protected:
//--- Create (1) the account object structure and (2) the account object from the structure
   bool              ObjectToStruct(void);
   void              StructToObject(void);
public:
Gemäß der Auflistung werden im geschützten Bereich der Klasse CAccount zusätzlich zwei Methoden deklariert — diejenige zum Erzeugen der Struktur aus den Objekt-Eigenschaftsfeldern und eine umgekehrte Methode zum Erzeugen des Konto-Objekts aus der Struktur.

Die erste Methode ist zum Schreiben des Konto-Objekts in die Datei zu verwenden, während die zweite Methode zum Lesen aus der Datei verwendet werden soll.

Lassen Sie uns die Methoden außerhalb des Klassenkörpers schreiben:

//+------------------------------------------------------------------+
//| Create the account object structure                              |
//+------------------------------------------------------------------+
bool CAccount::ObjectToStruct(void)
  {
//--- Save the integer properties
   this.m_struct_obj.login=this.Login();
   this.m_struct_obj.trade_mode=this.TradeMode();
   this.m_struct_obj.leverage=this.Leverage();
   this.m_struct_obj.limit_orders=(int)this.LimitOrders();
   this.m_struct_obj.margin_so_mode=this.MarginSOMode();
   this.m_struct_obj.trade_allowed=this.TradeAllowed();
   this.m_struct_obj.trade_expert=this.TradeExpert();
   this.m_struct_obj.margin_mode=this.MarginMode();
   this.m_struct_obj.currency_digits=(int)this.CurrencyDigits();
//--- Save the real properties
   this.m_struct_obj.balance=this.Balance();
   this.m_struct_obj.credit=this.Credit();
   this.m_struct_obj.profit=this.Profit();
   this.m_struct_obj.equity=this.Equity();
   this.m_struct_obj.margin=this.Margin();
   this.m_struct_obj.margin_free=this.MarginFree();
   this.m_struct_obj.margin_level=this.MarginLevel();
   this.m_struct_obj.margin_so_call=this.MarginSOCall();
   this.m_struct_obj.margin_so_so=this.MarginSOSO();
   this.m_struct_obj.margin_initial=this.MarginInitial();
   this.m_struct_obj.margin_maintenance=this.MarginMaintenance();
   this.m_struct_obj.assets=this.Assets();
   this.m_struct_obj.liabilities=this.Liabilities();
   this.m_struct_obj.comission_blocked=this.ComissionBlocked();
//--- Save the string properties
   ::StringToCharArray(this.Name(),this.m_struct_obj.name);
   ::StringToCharArray(this.Server(),this.m_struct_obj.server);
   ::StringToCharArray(this.Currency(),this.m_struct_obj.currency);
   ::StringToCharArray(this.Company(),this.m_struct_obj.company);
   //--- Saving the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,TextByLanguage("Не удалось сохранить структуру объекта в uchar-массив, ошибка ","Failed to save object structure to uchar array, error "),(string)::GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Wie wir in der Auflistung sehen können, werden alle Integer- und Realeigenschaften des Objektes in den gleichnamigen Strukturfeldern gespeichert. Um die Zeichenketteneigenschaften zu speichern, konvertieren Sie die Zeichenkette in das uchar-Array und speichern Sie sie in den entsprechenden Strukturfeldern.
Nach dem Speichern der Objekteigenschaften wird die gesamte Struktur in das uchar-Array gespeichert, das dann in der Datei.
gespeichert wird.

//+------------------------------------------------------------------+
//| Create the account object from the structure                     |
//+------------------------------------------------------------------+
void CAccount::StructToObject(void)
  {
//--- Sichern der ganzzahligen Eigenschaften
   this.m_long_prop[ACCOUNT_PROP_LOGIN]                              = this.m_struct_obj.login;
   this.m_long_prop[ACCOUNT_PROP_TRADE_MODE]                         = this.m_struct_obj.trade_mode;
   this.m_long_prop[ACCOUNT_PROP_LEVERAGE]                           = this.m_struct_obj.leverage;
   this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS]                       = this.m_struct_obj.limit_orders;
   this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE]                     = this.m_struct_obj.margin_so_mode;
   this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED]                      = this.m_struct_obj.trade_allowed;
   this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT]                       = this.m_struct_obj.trade_expert;
   this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE]                        = this.m_struct_obj.margin_mode;
   this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS]                    = this.m_struct_obj.currency_digits;
//--- Sichern der Double-Eigenschaften
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)]          = this.m_struct_obj.balance;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)]           = this.m_struct_obj.credit;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)]           = this.m_struct_obj.profit;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)]           = this.m_struct_obj.equity;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)]           = this.m_struct_obj.margin;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)]      = this.m_struct_obj.margin_free;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)]     = this.m_struct_obj.margin_level;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)]   = this.m_struct_obj.margin_so_call;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)]     = this.m_struct_obj.margin_so_so;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)]   = this.m_struct_obj.margin_initial;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=this.m_struct_obj.margin_maintenance;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)]           = this.m_struct_obj.assets;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)]      = this.m_struct_obj.liabilities;
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=this.m_struct_obj.comission_blocked;
//--- Sichern der String-Eigenschaften
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)]             = ::CharArrayToString(this.m_struct_obj.name);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)]           = ::CharArrayToString(this.m_struct_obj.server);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)]         = ::CharArrayToString(this.m_struct_obj.currency);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)]          = ::CharArrayToString(this.m_struct_obj.company);
  }
//+------------------------------------------------------------------+

Das Verfahren der Rücktransformation der Strukturfelder in die Eigenschaften der Konto-Objekte ist nahezu identisch mit dem oben beschriebenen ersten Verfahren.
Hier erhalten wir die Eigenschaften der Zeichenketten der Konto-Objekte durch Umwandlung der uchar-Arrays der Struktur in Zeichenketten.

Jetzt ist alles bereit, um die virtuellen Methoden zum Speichern und Laden/Speichern des Objekts in/aus der Datei zu erstellen.

Deklarieren Sie im 'public' Bereich der Klasse CAccount die virtuellen Methoden Save() und Load():

public:
//--- Konstructor
                     CAccount(void);
//--- Set (1) integer, (2) real and (3) string account properties
   void              SetProperty(ENUM_ACCOUNT_PROP_INTEGER property,long value)        { this.m_long_prop[property]=value;                                  }
   void              SetProperty(ENUM_ACCOUNT_PROP_DOUBLE property,double value)       { this.m_double_prop[this.IndexProp(property)]=value;                }
   void              SetProperty(ENUM_ACCOUNT_PROP_STRING property,string value)       { this.m_string_prop[this.IndexProp(property)]=value;                }
//--- Return (1) integer, (2) real and (3) string account properties from the properties array
   long              GetProperty(ENUM_ACCOUNT_PROP_INTEGER property)             const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_ACCOUNT_PROP_DOUBLE property)              const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_ACCOUNT_PROP_STRING property)              const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Return the flag of the MarginCall and StopOut levels calculation in %
   bool              IsPercentsForSOLevels(void)                                 const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT;          }
//--- Rückgabe des Flags der Order mit den Eigenschaften
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_INTEGER property)               { return true; }
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_DOUBLE property)                { return true; }
   virtual bool      SupportProperty(ENUM_ACCOUNT_PROP_STRING property)                { return true; }

//--- Compare CAccount objects with one another by all possible properties (for sorting the lists by a specified account object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CAccount objects by account properties (to search for equal account objects)
   bool              IsEqual(CAccount* compared_account) const;
//--- (1) Save the account object to the file, (2), download the account object from the file
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
//+------------------------------------------------------------------+
//| Methods of simplified access to the account object properties    |
//+------------------------------------------------------------------+

Schreiben wir die Methoden, um das Konto-Objekt in der Datei zu speichern und aus der Datei herunterzuladen:

//+------------------------------------------------------------------+
//| Save the account object to the file                              |
//+------------------------------------------------------------------+
bool CAccount::Save(const int file_handle)
  {
   if(!this.ObjectToStruct())
     {
      Print(DFUN,TextByLanguage("Не удалось создать структуру объекта.","Could not create object structure"));
      return false;
     }
   if(::FileWriteArray(file_handle,this.m_uchar_array)==0)
     {
      Print(DFUN,TextByLanguage("Не удалось записать uchar-массив в файл.","Could not write uchar array to file"));
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

wobei:

  • das Handle der bereits zum Schreiben geöffneten Datei wird an die Methode übergeben,
  • wir alle Objektfelder in der POD-Struktur speichern,
  • wir die POD-Struktur in die Datei schreiben, deren Handle der Methode übergeben wurde.

Die Methode zum Laden der Objektdaten aus der Datei:

//+------------------------------------------------------------------+
//| Download the account object from the file                        |
//+------------------------------------------------------------------+
bool CAccount::Load(const int file_handle)
  {
   if(::FileReadArray(file_handle,this.m_uchar_array)==0)
     {
      Print(DFUN,TextByLanguage("Не удалось загрузить uchar-массив из файла.","Could not load uchar array from file"));
      return false;
     }
   if(!::CharArrayToStruct(this.m_struct_obj,this.m_uchar_array))
     {
      Print(DFUN,TextByLanguage("Не удалось создать структуру объекта из uchar-массива.","Could not create object structure from uchar array"));
      return false;
     }
   this.StructToObject();
   return true;
  }
//+------------------------------------------------------------------+

wobei:

  • das Handle der zuvor zum Lesen geöffneten Datei der Methode übergeben wird,
  • die Daten aus der Datei in das uchar-Array geladen werden,
  • die Array-Daten in der POD-Struktur gespeichert werden,
  • die POD-Strukturdaten in die Objektfelder geschrieben werden.
Wir haben das Konto-Objekt für das Laden/Speichern von Daten in/aus der Datei verbessert.

Das Arbeiten mit der Kollektion der Konten geschieht wie folgt: Beim Start des Ausführungsprogramms wird das laufende Konto geprüft, das Konto-Objekt mit den aktuellen Kontodaten erstellt und in die Liste der Konto-Objekte gestellt. Als Nächstes werden wir uns den Ordner mit den Dateien der zuvor gespeicherten Konten ansehen. Wenn es Dateien enthält, werden wir sie nacheinander lesen, um zu prüfen, ob die Konsistenz mit dem aktuellen Konto aufrechterhalten wird, und sie in die Liste der Kollektion der Konten aufnehmen. Nachdem wir die Liste erstellt haben, überprüfen wir den Status des aktuellen Kontos im Timer und zeichnen die aufgetretenen Änderungen auf, falls vorhanden.
Für einige Änderungen werden wir Ereignisse erstellen und an das Programm senden, um Änderungen an den Kontoparametern zu steuern. So ist beispielsweise eine plötzliche Änderung des Hebels ein sehr einschneidendes und unangenehmes Ereignis, über das ein Benutzer und sein Programm rechtzeitig informiert werden sollten.

Da wir im Timer arbeiten müssen, sowie eine neue Kollektionsliste erstellen müssen, werden wir Makro-Substitutionen mit den Timer-Parametern und Listen-ID für sie in der Datei Defines.mqh erstellen. Achten Sie auch darauf, die Namen der zuvor erstellten Makro-Substitutionen für Kollektion der Timer für die Aufträge, Deals und Positionen zu ändern (fügen Sie " ORD" zu ihrem Namen hinzu, um zwischen Makro-Substitutionen zu unterscheiden, die zu verschiedenen Kollektionen der Timer gehören). Legen Sie die Pause für die Aktualisierung der Kontodaten auf eine Sekunde fest. Ich denke, das wird ausreichen, um Änderungen zu verfolgen und die Belastung des Systems zu verringern:

//+------------------------------------------------------------------+
//| Macro-Substitution                                               |
//+------------------------------------------------------------------+
//--- Beschreibung der Funktion mit der Zeilennummer des Fehlers
#define DFUN_ERR_LINE               (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                        (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                ("Russian")                // Country language
#define END_TIME                    (D'31.12.3000 23:59:59')   // End date for requesting account history data
#define TIMER_FREQUENCY             (16)                       // Minimal frequency of the library timer in milliseconds
//--- Parameters of orders and deals collection timer
#define COLLECTION_ORD_PAUSE        (250)                      // Orders and deals collection timer pause in milliseconds
#define COLLECTION_ORD_COUNTER_STEP (16)                       // Increment of the orders and deals collection timer counter
#define COLLECTION_ORD_COUNTER_ID   (1)                        // Orders and deals collection timer counter ID
//--- Parameters of the account collection timer
#define COLLECTION_ACC_PAUSE        (1000)                     // Account collection timer pause in milliseconds
#define COLLECTION_ACC_COUNTER_STEP (16)                       // Account timer counter increment
#define COLLECTION_ACC_COUNTER_ID   (2)                        // Account timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID       (0x7778+1)                 // Historical collection list ID
#define COLLECTION_MARKET_ID        (0x7778+2)                 // Market collection list ID
#define COLLECTION_EVENTS_ID        (0x7778+3)                 // Event collection list ID
#define COLLECTION_ACCOUNT_ID       (0x7778+4)                 // Account collection list ID
//--- Data parameters for file operations
#define DIRECTORY                   ("DoEasy\\")               // Library directory for storing object folders
#define UCHAR_ARRAY_SIZE            (64)                       // Size of uchar arrays for storing string properties

Ersetzen Sie im Text der Klasse CEngine COLLECTION_PAUSE, COLLECTION_COUNTER_STEP und COLLECTION_COUNTER_STEP durch die entsprechenden neuen Namen der Makro-Substitution: COLLECTION_ORD_PAUSE, COLLECTION_ORD_COUNTER_STEP und COLLECTION_ORD_COUNTER_ID.

Da wir die Kollektion der Konten erstellen, bedeutet dies die Möglichkeit, die Eigenschaften mehrerer Konto-Objekte zu vergleichen. Fügen Sie dazu der Kollektion der Konten in der Klasse CSelect Auswahl- und Sortiermethoden zur Auswahl der dem Kriterium entsprechenden Objekte hinzu. Die Klasse wurde im dritten Teil der Bibliotheksbeschreibung beschrieben.

Öffnen Sie die Datei Select.mqh, die im Dienstklassenordner der Bibliothek \MQL5\Include\DoEasy\Services ausgewählt wurde, verbinden Sie die Datei mit der Account-Klasse damit und fügen Sie die neuen Methoden zum Arbeiten mit Account-Objekten hinzu:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Storage list                                                     |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Storage object for storing sorted collection lists
//+------------------------------------------------------------------+
//| Klasse zum Sortieren der Objekte, die dem Kriterium entsprechen  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Vergleichsmethode zweier Werte
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
//+------------------------------------------------------------------+
//| Methods of working with orders                                   |
//+------------------------------------------------------------------+
   //--- Rückgabe der Auftragsliste mit der (1) Integer-, (2) Double- und (3) String-Eigenschaft, die dem angegebenen Kriterium entspricht
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the order index with the maximum value of the order's (1) integer, (2) real and (3) string properties
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
   //--- Return the order index with the minimum value of the order's (1) integer, (2) real and (3) string properties
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
//+------------------------------------------------------------------+
//| Methods of working with events                                   |
//+------------------------------------------------------------------+
   //--- Return the list of events with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
   //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
//+------------------------------------------------------------------+
//| Methods of working with accounts                                 |
//+------------------------------------------------------------------+
   //--- Return the list of accounts with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
   //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
  };
//+------------------------------------------------------------------+

Implementieren wir die deklarierte Methoden außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Methods of working with account lists                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Return the list of accounts with one integer                     |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER 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);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CAccount *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of accounts with one real                        |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE 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);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CAccount *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of accounts with one string                      |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING 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);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CAccount *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the account index in the list                             |
//| mit dem Maximum der Integer-Eigenschaft                          |
//+------------------------------------------------------------------+
int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CAccount *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CAccount *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 account index in the list                             |
//| mit dem Maximum der Double-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CAccount *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CAccount *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 account index in the list                             |
//| mit dem Maximum der String-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CAccount *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CAccount *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 account index in the list                             |
//| mit dem Minimum der Integer-Eigenschaft                          |
//+------------------------------------------------------------------+
int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_INTEGER property)
  {
   int index=0;
   CAccount* min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      CAccount* 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 account index in the list                             |
//| mit dem Minimum der Double-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_DOUBLE property)
  {
   int index=0;
   CAccount* min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      CAccount* 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 account index in the list                             |
//| mit dem Minimum der String-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindAccountMin(CArrayObj* list_source,ENUM_ACCOUNT_PROP_STRING property)
  {
   int index=0;
   CAccount* min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      CAccount* 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;
  }
//+------------------------------------------------------------------+

Die Arbeit der Methoden wurde im dritten Teil der Bibliotheksbeschreibung besprochen, so dass wir hier nicht auf ihre Beschreibung eingehen werden. Sie können jederzeit zurück zum benötigten Artikel gelangen.

Lassen Sie uns ein Beispiel einer Klasse der Kollektion der Konten erstellen.

Erstellen Sie in der Bibliotheksdatei MQL5\Include\DoEasy\Collections\ die neue AccountsCollection.mqh Klassendatei, beziehen Sie die notwendigen Klassendateien ein und tragen Sie sofort die Standardmethoden ein:

//+------------------------------------------------------------------+
//|                                           AccountsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "ListObj.mqh"                    
#include "..\Services\Select.mqh"         
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Account class                                                    |
//+------------------------------------------------------------------+
class CAccountsCollection : public CListObj
  {
private:
   CListObj          m_list_accounts;                 // List of account objects
public:
//--- Rückgabe des der gesamten Kollektionsliste des Ereignisses "wie besehen"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                            }
//--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);   }
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);   }
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);   }
//--- Konstructor
                     CAccountsCollection();

  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CAccountsCollection::CAccountsCollection(void)
  {
   this.m_list_accounts.Clear();
   this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN);
   this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID);
  }
//+------------------------------------------------------------------+

Dieser kurze Code im Klassenkonstruktor wird verwendet, um die Liste vorzubereiten, in der die Konto-Objekte gespeichert werden sollen:

  • die Liste wird geleert
  • die Liste ist so eingestellt, dass sie nach einer Kontonummer sortiert wird und
  • die ID der Kollektion der Konten ist der Liste zugeordnet.

Die Arbeit der Kontenerfassungsklasse ist wie folgt gegliedert: Wenn das Programm an eine Symboltabelle angehängt ist, haben wir Zugriff auf die aktuellen Daten eines einzelnen Kontos. Wir sind in der Lage, die Veränderungen seiner Eigenschaften zu verfolgen und auf deren Veränderungen zu reagieren. Die restlichen Konten können nur im Programm "verfolgt" werden — ihr letzter Zustand zum Zeitpunkt der Verbindung zu einem neuen Konto. Daher enthält die Liste der Kontoverbindungen die Objekte aller Konten, mit denen wir uns jemals verbunden haben, obwohl wir nur die Änderungen des aktuellen Kontos verfolgen können. Außerdem werden wir in der Lage sein, die Daten aller Konten zu vergleichen, die wir für jedes Objekt haben.

Um wichtige Kontoeigenschaften zu verfolgen, verwenden Sie das Hash-Control — das die Summe aller Kontoeigenschaften ab der aktuellen Zeit mit der Summe vergleicht, die während der vorherigen Überprüfung erhalten wurde. Sobald sich die Summe ändert, überprüfen wir, was sich genau geändert hat und setzen das entsprechende Änderungskennzeichen. Als nächstes, bei der Verfolgung von Kontoereignissen (Änderungen wichtiger Kontoeigenschaften), signalisiert das Flag, dass wir alle verfolgten Eigenschaften überprüfen und Ereignisse über die geänderten Eigenschaften an das Programm senden müssen.

Lassen Sie uns alle notwendigen Variablen und Klassenmethoden hinzufügen und anschließend analysieren:

//+------------------------------------------------------------------+
//|                                           AccountsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Account collection                                               |
//+------------------------------------------------------------------+
class CAccountsCollection : public CListObj
  {
private:
   struct MqlDataAccount
     {
      double         hash_sum;               // Account data hash sum
      //--- Account integer properties
      long           login;                  // ACCOUNT_LOGIN (Account number)
      long           leverage;               // ACCOUNT_LEVERAGE (Leverage)
      int            limit_orders;           // ACCOUNT_LIMIT_ORDERS (Maximum allowed number of active pending orders)
      bool           trade_allowed;          // ACCOUNT_TRADE_ALLOWED (Permission to trade for the current account from the server side)
      bool           trade_expert;           // ACCOUNT_TRADE_EXPERT (Permission to trade for an EA from the server side)
      //--- Account real properties
      double         balance;                // ACCOUNT_BALANCE (Account balance in a deposit currency)
      double         credit;                 // ACCOUNT_CREDIT (Credit in a deposit currency)
      double         profit;                 // ACCOUNT_PROFIT (Current profit on an account in the account currency)
      double         equity;                 // ACCOUNT_EQUITY (Equity on an account in the deposit currency)
      double         margin;                 // ACCOUNT_MARGIN (Reserved margin on an account in a deposit currency)
      double         margin_free;            // ACCOUNT_MARGIN_FREE (Free funds available for opening a position in a deposit currency)
      double         margin_level;           // ACCOUNT_MARGIN_LEVEL (Margin level on an account in %)
      double         margin_so_call;         // ACCOUNT_MARGIN_SO_CALL (Margin Call)
      double         margin_so_so;           // ACCOUNT_MARGIN_SO_SO (Stop Out)
      double         margin_initial;         // ACCOUNT_MARGIN_INITIAL (Funds reserved on an account to ensure a guarantee amount for all pending orders)
      double         margin_maintenance;     // ACCOUNT_MARGIN_MAINTENANCE (Funds reserved on an account to ensure a minimum amount for all open positions)
      double         assets;                 // ACCOUNT_ASSETS (Current assets on an account)
      double         liabilities;            // ACCOUNT_LIABILITIES (Current liabilities on an account)
      double         comission_blocked;      // ACCOUNT_COMMISSION_BLOCKED (Current sum of blocked commissions on an account)
     };
   MqlDataAccount    m_struct_curr_account;  // Account current data
   MqlDataAccount    m_struct_prev_account;  // Account previous data
   
   CListObj          m_list_accounts;                                   // Account object list
   string            m_folder_name;                                     // Name of a folder account objects are stored in
   int               m_index_current;                                   // Index of an account object featuring the current account data
   bool              m_is_account_event;                                // Event flag in the account data

//--- Write the current account data to the account object properties
  void               SetAccountsParams(CAccount* account);
//--- Save the current data status values of the current account as previous ones
   void              SavePrevValues(void)                                                                   { this.m_struct_prev_account=this.m_struct_curr_account;                }
//--- Check the account object presence in the collection list
   bool              IsPresent(CAccount* account);
//--- Find and return the account object index with the current account data
   int               Index(void);
public:
//--- Return the full account collection list "as is"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                         }
//--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
//--- Return the (1) current account object index, (2) occurred event flag in the account data
   int               IndexCurrentAccount(void)                                                        const { return this.m_index_current;                                          }
   bool              IsAccountEvent(void)                                                             const { return this.m_is_account_event;                                       }

//--- Constructor, destructor
                     CAccountsCollection();
                    ~CAccountsCollection();
//--- Add the account object to the list
   bool              AddToList(CAccount* account);
//--- (1) Save account objects from the list to the files
//--- (2) Save account objects from the files to the list
   bool              SaveObjects(void);
   bool              LoadObjects(void);
//--- Update the current account data
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Im 'private' Bereich der Klasse gibt es die Struktur MqlDataAccount zum Speichern wichtiger Kontoeigenschaften. Sie soll alle verfolgten Objekteigenschaften des Kontos speichern. Wir haben zwei Variablen mit dem Strukturtyp: die erste speichert die Daten des laufenden Kontos, während die andere die vorherigen Daten speichert. Die einzige Eigenschaft, die in der Struktur unverändert bleibt, ist ein login, der die Kontonummer speichert. Der Feldwert soll zur Definition des ersten Starts verwendet werden. Wenn die Struktur im Feld "Login" Null enthält, ist dies der erste Start, und der aktuelle Kontostand sollte für den späteren Vergleich als der vorherige gespeichert werden. Legen Sie im Hash-Feld der Struktur die Summe der Werte aller Strukturfelder fest und vergleichen Sie sie mit dem Wert, der in der Struktur des "vorherigen" Kontostandes eingestellt ist. Wenn eine Abweichung zwischen den Werten dieser beiden Felder festgestellt wird, gilt eine Änderung der Eigenschaften des Konto-Objekts als erkannt.

Da die Liste der Kollektion der Konten die Daten verschiedener Konten speichern soll (alle Konten, mit denen wir während des bibliothekarischen Programmbetriebs verbunden sind und das aktuelle), obwohl wir die in der Liste gespeicherten Kontodaten nicht durch Lesen aus der Datei verfolgen können, müssen wir den genauen Index des Konto-Objekts in der Liste kennen, das das aktuelle Konto-Objekt ist, das wir verfolgen müssen. Dieser Index soll verwendet werden, um das Konto-Objekt zu erhalten und den Status seiner Eigenschaften im Timer zu überprüfen. Wir haben auch die Klassenvariable, die als Flag zum Ändern der Eigenschaften der Konto-Objekte verwendet werden soll, und die Variable, die die Adresse des Ordners im Bibliotheksverzeichnis enthält, in dem wir die Klassenobjekte speichern werden.

Der gleiche 'private' Bereich enthält die vier Methoden. Lassen Sie uns einen Blick auf ihre Umsetzung werfen.

Die Methode zum Schreiben von Daten des aktuellen Kontos in die Eigenschaften des Konto-Objekts:

//+------------------------------------------------------------------+
//| Write the current account data to the account object properties  |
//+------------------------------------------------------------------+
void CAccountsCollection::SetAccountsParams(CAccount *account)
  {
   if(account==NULL)
      return;
//--- Account number
   this.m_struct_curr_account.login=account.Login();
//--- Leverage
   account.SetProperty(ACCOUNT_PROP_LEVERAGE,::AccountInfoInteger(ACCOUNT_LEVERAGE));
   this.m_struct_curr_account.leverage=account.Leverage();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.leverage;
//--- Maximum allowed number of active pending orders
   account.SetProperty(ACCOUNT_PROP_LIMIT_ORDERS,::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS));
   this.m_struct_curr_account.limit_orders=(int)account.LimitOrders();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.limit_orders;
//--- Permission to trade for the current account from the server side
   account.SetProperty(ACCOUNT_PROP_TRADE_ALLOWED,::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED));
   this.m_struct_curr_account.trade_allowed=account.TradeAllowed();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.trade_allowed;
//--- Permission to trade for an EA from the server side
   account.SetProperty(ACCOUNT_PROP_TRADE_EXPERT,::AccountInfoInteger(ACCOUNT_TRADE_EXPERT));
   this.m_struct_curr_account.trade_expert=account.TradeExpert();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.trade_expert;
//--- Account balance in a deposit currency
   account.SetProperty(ACCOUNT_PROP_BALANCE,::AccountInfoDouble(ACCOUNT_BALANCE));
   this.m_struct_curr_account.balance=account.Balance();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.balance;
//--- Credit in a deposit currency
   account.SetProperty(ACCOUNT_PROP_CREDIT,::AccountInfoDouble(ACCOUNT_CREDIT));
   this.m_struct_curr_account.credit=account.Credit();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.credit;
//--- Current profit on an account in the account currency
   account.SetProperty(ACCOUNT_PROP_PROFIT,::AccountInfoDouble(ACCOUNT_PROFIT));
   this.m_struct_curr_account.profit=account.Profit();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.profit;
//--- Equity on an account in the deposit currency
   account.SetProperty(ACCOUNT_PROP_EQUITY,::AccountInfoDouble(ACCOUNT_EQUITY));
   this.m_struct_curr_account.equity=account.Equity();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.equity;
//--- Reserved margin on an account in a deposit currency
   account.SetProperty(ACCOUNT_PROP_MARGIN,::AccountInfoDouble(ACCOUNT_MARGIN));
   this.m_struct_curr_account.margin=account.Margin();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin;
//--- Free funds available for opening a position in a deposit currency
   account.SetProperty(ACCOUNT_PROP_MARGIN_FREE,::AccountInfoDouble(ACCOUNT_MARGIN_FREE));
   this.m_struct_curr_account.margin_free=account.MarginFree();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_free;
//--- Margin level on an account in %
   account.SetProperty(ACCOUNT_PROP_MARGIN_LEVEL,::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
   this.m_struct_curr_account.margin_level=account.MarginLevel();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_level;
//--- Margin Call level
   account.SetProperty(ACCOUNT_PROP_MARGIN_SO_CALL,::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL));
   this.m_struct_curr_account.margin_so_call=account.MarginSOCall();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_so_call;
//--- StopOut level
   account.SetProperty(ACCOUNT_PROP_MARGIN_SO_SO,::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO));
   this.m_struct_curr_account.margin_so_so=account.MarginSOSO();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_so_so;
//--- Funds reserved on an account to ensure a guarantee amount for all pending orders
   account.SetProperty(ACCOUNT_PROP_MARGIN_INITIAL,::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL));
   this.m_struct_curr_account.margin_initial=account.MarginInitial();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_initial;
//--- Funds reserved on an account to ensure a minimum amount for all open positions
   account.SetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE,::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE));
   this.m_struct_curr_account.margin_maintenance=account.MarginMaintenance();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.margin_maintenance;
//--- Current assets on an account
   account.SetProperty(ACCOUNT_PROP_ASSETS,::AccountInfoDouble(ACCOUNT_ASSETS));
   this.m_struct_curr_account.assets=account.Assets();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.assets;
//--- Current liabilities on an account
   account.SetProperty(ACCOUNT_PROP_LIABILITIES,::AccountInfoDouble(ACCOUNT_LIABILITIES));
   this.m_struct_curr_account.liabilities=account.Liabilities();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.liabilities;
//--- Current sum of blocked commissions on an account
   account.SetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED,::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED));
   this.m_struct_curr_account.comission_blocked=account.ComissionBlocked();
   this.m_struct_curr_account.hash_sum+=(double)this.m_struct_curr_account.comission_blocked;
  }
//+------------------------------------------------------------------+

Lassen Sie uns einen Blick auf die Hebelwirkung am Beispiel des Updates werfen:
die Methode erhält den Zeiger auf das Konto-Objekt, die Daten des aktuellen Kontos werden zu den Feldern des Konto-Objekts und die Felder der Struktur mit dem aktuellen Kontostand hinzugefügt. Als Nächstes wird der Wert jeder erhaltenen Eigenschaft zum Hash hinzugefügt.

Die Methode SavePrevValues() zum Speichern der Struktur des aktuellen Kontostandes in der vorherigen Statusstruktur kopiert einfach die aktuelle Statusstruktur in den vorherigen Status.

Das Verfahren zum Überprüfen des Vorhandenseins des Konto-Objekts in der Kollektionsliste:

//+------------------------------------------------------------------+
//| Check the presence of the account object in the collection list  |
//+------------------------------------------------------------------+
bool CAccountsCollection::IsPresent(CAccount *account)
  {
   int total=this.m_list_accounts.Total();
   if(total==0)
      return false;
   for(int i=0;i<total;i++)
     {
      CAccount* check=this.m_list_accounts.At(i);
      if(check==NULL)
         continue;
      if(check.IsEqual(account))
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Die Methode erhält den Zeiger auf das Konto-Objekt, dessen Daten in der Kollektionsliste gefunden werden sollen. Die Suche erfolgt über eine Kontonummer sowie über Kunden- und Firmennamen über die Methode IsEqual(), die ich beim Anlegen der Objektklasse Konto bereits beschrieben habe.
Verwenden Sie die Objektliste des Kontos (in einer Schleife) , um das Objekt aus der Liste zu erhalten und vergleichen Sie seine Daten mit den Daten des an die Methode übergebenen Objekts.
Wenn die Daten übereinstimmen, wird true
zurückgegeben,
andernfalls false, wenn nach dem Ende der Schleife keine übereinstimmende Objekte gefunden werden.

Die Methode, die den Objektindex des Kontos in der Liste mit den aktuellen Kontodaten zurückgibt:

//+------------------------------------------------------------------+
//| Return the account object index with the current account data    |
//+------------------------------------------------------------------+
int CAccountsCollection::Index(void)
  {
   int total=this.m_list_accounts.Total();
   if(total==0)
      return WRONG_VALUE;
   for(int i=0;i<total;i++)
     {
      CAccount* account=this.m_list_accounts.At(i);
      if(account==NULL)
         continue;
      if(account.Login()==::AccountInfoInteger(ACCOUNT_LOGIN)    &&
         account.Company()==::AccountInfoString(ACCOUNT_COMPANY) &&
         account.Name()==::AccountInfoString(ACCOUNT_NAME)         
        ) return i;                                                
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Verwenden Sie die Objektliste des Kontos (in einer Schleife), um das Objekt zu erhalten und seine Kontodaten (Login, Kunden- und Firmennamen) mit den Kontodaten zu vergleichen, mit denen das Programm gestartet wurde. Der Schleifenindex wird im Falle einer Übereinstimmung zurückgegeben. Nach dem Ende der Schleife wird -1 zurückgegeben, wenn das Objekt mit den aktuellen Kontodaten nicht gefunden wird.

Die folgenden Methoden werden im Abschnitt 'public' der Klasse hinzugefügt:

Die Methode, die den Variablenwert zurückgibt, der den Index des Konto-Objekts mit den aktuellen Kontodaten speichert, die Methode, die das Flag einer aufgetretenen Änderung der Kontoeigenschaften zurückgibt. Außerdem gibt es einen Destruktor der Klasse (um alle Konten aus der Liste in den Dateien zu speichern), die Methode, die das Konto-Objekt zur Kollektionsliste hinzufügt, die Methoden zum Speichern und Laden/Speichern der Objekte in/aus der Datei und die Methode zum Aktualisieren der aktuellen Kontodaten im Objekt des aktuellen Kontos:

public:
//--- Return the full account collection list "as is"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                         }
//--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
   CArrayObj        *GetList(ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByAccountProperty(this.GetList(),property,value,mode);}
//--- Return the (1) current object account index, (2) flag of an occurred event in the account data
   int               IndexCurrentAccount(void)                                                        const { return this.m_index_current;                                          }
   bool              IsAccountEvent(void)                                                             const { return this.m_is_account_event;                                       }

//--- Constructor, destructor
                     CAccountsCollection();
                    ~CAccountsCollection();
//--- Add the account object to the list
   bool              AddToList(CAccount* account);
//--- (1) Save account objects from the list to the files
//--- (2) Save account objects from the files to the list
   bool              SaveObjects(void);
   bool              LoadObjects(void);
//--- Update the current account data
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Lassen Sie uns diese Methoden betrachten.

Die Bibliotheksdateien sind im Terminalordner Files\DoEasy\ mit Ordnern für jede Klasse zu speichern (wenn die Klasse die Dateien speichern muss). Es gibt auch die Klassenvariable m_folder_name, mit der Sie den Namen des Ordners, in dem Konto-Objekte gespeichert werden, festlegen können. Initialisieren Sie sie in der Initialisierungsliste des Klassenkonstruktors sofort zusammen mit der Flag-Variablen einer aufgetretenen Änderung der Kontoeigenschaften:

//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CAccountsCollection::CAccountsCollection(void) : m_folder_name(DIRECTORY+"Accounts"),m_is_account_event(false)
  {
   this.m_list_accounts.Clear();
   this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN);
   this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID);
   ::ZeroMemory(this.m_struct_prev_account);

//--- Create the folder for storing account files
   ::ResetLastError();
   if(!::FolderCreate(this.m_folder_name,FILE_COMMON))
      Print(DFUN,TextByLanguage("Не удалось создать папку хранения файлов. Ошибка ","Could not create file storage folder. Error "),::GetLastError());
//--- Create the current account object and add it to the list
   CAccount* account=new CAccount();
   if(account!=NULL)
     {
      if(!this.AddToList(account))
        {
         Print(DFUN_ERR_LINE,TextByLanguage("Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию.","Error. Failed to add current account object to collection list."));
         delete account;
        }
      else
         account.PrintShort();
     }
   else
      Print(DFUN,TextByLanguage("Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта.","Error. Failed to create an account object with current account data."));

//--- Download account objects from the files to the collection
   this.LoadObjects();
//--- Save the current account index
   this.m_index_current=this.Index();
  }
//+------------------------------------------------------------------+

Als Nächstes setzen Sie im Klassenkonstruktor die Struktur mit den vorherigen Daten des aktuellen Kontos und erzeugen Sie den Ordner zum Speichern der Klassendateien, der sich unter "Common_data_folder"\Files\DoEasy\Accounts für die Klasse befinden soll.
Ein Konto-Objekt mit den aktuellen Kontodaten wird dann erstellt und mit der Methode AddToList() zur Kollektionsliste der Konten hinzugefügt. Wenn kein Objekt zur Liste hinzugefügt wird, wird die entsprechende Nachricht an das Journal gesendet, andernfalls wird die Nachricht mit kurzen Kontoeigenschaften (Login, Kundenname, Firmenname, Kontostand, Hebelwirkung und Kontoart, wenn nicht Netting) angezeigt.
Der nächste Schritt ist Eintragen der Konto-Objekte in die Kollektionsliste. Dies sind die Konto-Objekte mit ihren Speicherdateien im Ordner, in dem die Klassenobjekte gespeichert sind.
Der letzte Schritt ist die Suche nach dem Objektindex mit den aktuellen Kontodaten und Zuweisen von m_index_current zu seiner Variablen, deren Wert von der Methode IndexCurrentAccount() zur Verwendung in Programmen zurückgegeben wird.

Die Methode zum Speichern aller Objekte aus der Kollektionsliste in die entsprechenden Dateien wird im Destruktor der Klasse aufgerufen:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CAccountsCollection::~CAccountsCollection(void)
  {
//--- Save account objects from the list to the files
   this.SaveObjects();
  }
//+------------------------------------------------------------------+

Die Methode zum Hinzufügen des Konto-Objekts zur Kollektionsliste:

//+------------------------------------------------------------------+
//| Add the account object to the list                               |
//+------------------------------------------------------------------+
bool CAccountsCollection::AddToList(CAccount *account)
  {
   if(account==NULL)
      return false;
   if(!this.IsPresent(account))
      return this.m_list_accounts.Add(account);
   return false;
  }
//+------------------------------------------------------------------+

Die Methode erhält den Zeiger auf das Konto-Objekt, dann wird die Methode IsPresent() verwendet, um das Vorhandensein eines solchen Objekts in der Kollektionsliste zu überprüfen. Wenn es noch kein solches Objekt gibt, wird es zur Kollektionsliste hinzugefügt und das Ergebnis des Hinzufügens wird zurückgegeben.

Die Methode zum Speichern von Konto-Objekten aus der Kollektionsliste in die Dateien:

//+------------------------------------------------------------------+
//| Save account objects from the list to the files                  |
//+------------------------------------------------------------------+
bool CAccountsCollection::SaveObjects(void)
  {
   bool res=true;
   int total=this.m_list_accounts.Total();
   if(total==0)
      return false;
   for(int i=0;i<total;i++)
     {
      CAccount* account=this.m_list_accounts.At(i);
      if(account==NULL)
         continue;
      string file_name=this.m_folder_name+"\\"+account.Server()+" "+(string)account.Login()+".bin";
      if(::FileIsExist(file_name,FILE_COMMON))
         ::FileDelete(file_name,FILE_COMMON); 
      ::ResetLastError();
      int handle=::FileOpen(file_name,FILE_WRITE|FILE_BIN|FILE_COMMON);
      if(handle==INVALID_HANDLE)
        {
         ::Print(DFUN,TextByLanguage("Не удалось открыть для записи файл ","Could not open file for writing: "),file_name,TextByLanguage(". Ошибка ",". Error "),(string)::GetLastError());
         return false;
        }
      res &=account.Save(handle);
      ::FileClose(handle);
     }
   return res;
  }
//+------------------------------------------------------------------+

Verwenden Sie die Kollektionsliste (in einer Schleife) , um das Konto-Objekt aus der Liste und Erstellen Sie einen Dateinamen, der aus dem Pfad zum Ordner des Konto-Objekts, Servername und Login (Kontonummer) mit der Erweiterung ".bin" besteht. Wenn eine solche Datei im Objektordner des Kontos existiert, wird die Datei gelöscht und eine neue zum Schreiben geöffnet. Das geöffnete Datei-Handle wird an die virtuelle Methode Save() der Klasse CAccount übergeben, die ich zuvor beschrieben habe, und das Ergebnis Dateispeicherung wird der Variablen res hinzugefügt, die das Ergebnis des Schreibens in die Datei aller Konto-Objekte aus der Kollektionsliste zurückgibt. Die zum Schreiben geöffnete Datei wird nach dem Speichern des Objekts geschlossen.

Die Methode zum Herunterladen von Konto-Objekten aus den Dateien in die Kollektionsliste:

//+------------------------------------------------------------------+
//| Download account objects from the files to the list              |
//+------------------------------------------------------------------+
bool CAccountsCollection::LoadObjects(void)
  {
   bool res=true;
   string name="";
   long handle_search=::FileFindFirst(this.m_folder_name+"\\*",name,FILE_COMMON);
   if(handle_search!=INVALID_HANDLE)
     {
      do
        {
         string file_name=this.m_folder_name+"\\"+name;
         ::ResetLastError();
         int handle_file=::FileOpen(m_folder_name+"\\"+name,FILE_BIN|FILE_READ|FILE_COMMON);
         if(handle_file!=INVALID_HANDLE)
           {
            CAccount* account=new CAccount();
            if(account!=NULL)
              {
               if(!account.Load(handle_file))
                 {
                  delete account;
                  ::FileClose(handle_file);
                  res &=false;
                  continue;
                 }
               if(this.IsPresent(account))
                 {
                  delete account;
                  ::FileClose(handle_file);
                  res &=false;
                  continue;
                 }
               if(!this.AddToList(account))
                 {
                  delete account;
                  res &=false;
                 }
              }
           }
         ::FileClose(handle_file);
        }
      while(::FileFindNext(handle_search,name));
      ::FileFindClose(handle_search);
     }
   return res;
  }
//+------------------------------------------------------------------+

Zuerst finden Sie die allererste Datei im Ordner, in dem die Bibliothekskontenobjektdateien gespeichert sind. Als Nächstes öffnen Sie eine weitere gefundene Datei zum Lesen in der Do-while-Schleife, erzeugen Sie ein neues Konto-Objekt und laden Sie Daten aus der Datei mit der virtuellen Methode Load() der Klasse CAccount. Wenn es kein solches Objekt gibt (mit den gleichen Kontodaten), wird das Objekt in die Liste aufgenommen. Im Falle eines Fehlers beim Lesen von Daten aus der Datei in das Objekt oder beim Hinzufügen des Objekts zur Liste, entfernen Sie dieses neue Objekt (um Speicherlecks zu vermeiden) und schließen Sie die Datei.
Nach dem Ende der Schleife wird das Ergebnis des Lesens von Daten aus den Dateien in das Konto-Objekt und deren Platz in der Kollektionsliste zurückgegeben.

Das Verfahren zum Aktualisieren der Daten des aktuellen Konto-Objekts:

//+------------------------------------------------------------------+
//| Update the current account data                                  |
//+------------------------------------------------------------------+
void CAccountsCollection::Refresh(void)
  {
   if(this.m_index_current==WRONG_VALUE)
      return;
   CAccount* account=this.m_list_accounts.At(this.m_index_current);
   if(account==NULL)
      return;
   ::ZeroMemory(this.m_struct_curr_account);
   this.m_is_account_event=false;
   this.SetAccountsParams(account);
   
//--- Erster Start
   if(!this.m_struct_prev_account.login)
     {
      this.SavePrevValues();
     }
//--- If the account hash changed
   if(this.m_struct_curr_account.hash_sum!=this.m_struct_prev_account.hash_sum)
     {
      this.m_is_account_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Hier ist das erste, was wir tun, die Gültigkeit des Index des Konto-Objekts zu überprüfen, das die aktuellen Kontodaten enthält. Wenn es aus irgendeinem Grund nicht erhalten wurde, wird die Methode verlassen. Als Nächstes erhalten Sie das Konto-Objekt mit den aktuellen Kontodaten über seinen Index in der Liste, setzen Sie die Datenstruktur des aktuellen Kontos zurück, setzen Sie das Flag zum Ändern der Eigenschaften des Konto-Objekts zurück und rufen Sie die Methode zum Einstellen der Eigenschaften des Konto-Objekts auf. Die gleiche Methode kopiert die neuesten (neu gelesenen) Eigenschaften in die Datenstruktur des aktuellen Kontos, um sie anschließend mit dem vorherigen Status des Kontos zu vergleichen und die Änderungen zu erkennen.
Als Nächstes definieren Sie, welche Daten in der Datenstruktur des vorherigen Status des Kontos gesetzt werden. Wenn das Login-Feld Null enthält, bedeutet das, dass die Struktur nie gefüllt wurde und dies der erste Start ist. So füllen wir einfach die Struktur mit den vorherigen Zustandsdaten mit den Daten aus der aktuellen Zustandsstruktur.
Als Nächstes überprüfen Sie die Hash-Veränderung, indem Sie den aktuellen Status-Hash mit dem vorherigen Status eins vergleichen. Wenn es Änderungen gibt, setzen Sie das Flag des aufgetretenen Ereignisses zur Änderung der Kontoeigenschaften und speichern Sie den aktuellen Status als den vorherigen für ihren nachfolgenden Vergleich.
Implementieren Sie später die Verfolgung wichtiger Kontostatusänderungen und das Senden von Ereignismeldungen zu diesen wichtigen Änderungen am Programm.

Da die gesamte Arbeit mit der Klasse aus dem Basisobjekt der Bibliothek (Klasse CEngine) erfolgt, wechseln Sie in die Datei Engine.mqh und fügen Sie die notwendige Funktionsweise hinzu.

Binden Sie zunächst die Datei für die Kollektion der Konten ein:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

Im 'private' Bereich der Klasse erzeugen Sie das Objekt der Kollektion der Konten und fügen Sie die Methode für das Arbeiten mit der Kollektion der Konten hinzu:

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Kollektion der historischen Aufträge und Deals
   CMarketCollection    m_market;                        // Kollektion der Marktorder und Deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CArrayObj            m_list_counters;                 // Liste der Timerzähler
   bool                 m_first_start;                   // Flag des Erststarts
   bool                 m_is_hedge;                      // Flag des Hedging-Kontos
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Flag eines Handelsereignisses des Kontos
   bool                 m_is_history_trade_event;        // Flag eines historischen Handelsereignisses auf dem Konto
   ENUM_TRADE_EVENT     m_last_trade_event;              // Account last trading event
//--- Rückgabe des Zählerindex über die ID
   int                  CounterIndex(const int id) const;
//--- Return (1) the first launch flag, (2) the flag presence in a trading event
   bool                 IsFirstStart(void);
//--- Working with (1) order, deal and position, as well as (2) account events
   void                 TradeEventsControl(void);
   void                 AccountEventsControl(void);
//--- Rückgabe der letzten (1) Pending-Order, (2) Marktorder, (3) letzten Position, (4) Position nach Ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Return the last (1) removed pending order, (2) historical market order, (3) historical order (market or pending) by its ticket
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Rückgabe des (1) ersten und (2) des letzten historischen Marktorder aus der Liste aller Positionen, (3) des letzten Deals
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:

Erstellen Sie im Klassenkonstruktor einen neuen Timerzähler für die Arbeit mit der Kollektion der Konten:

//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_last_trade_event(TRADE_EVENT_NO_EVENT)
  {
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
        }
   #endif 
  }
//+------------------------------------------------------------------+

Wir haben die Timer und ihre Zähler im dritten Teil der Bibliotheksbeschreibung diskutiert.

Fügen Sie in der Klasse von OnTimer() den Timer der Kollektion der Konten hinzu:

//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer der Kollektion der historischen Aufträge, Deals Marktorders und Positionen
   int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the order, deal and position collections events
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else
            this.TradeEventsControl();
        }
     }
//--- Account collection timer
   index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the account collections
            if(counter.IsTimeDone())
               this.AccountEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else
            this.AccountEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+

Der Timer der Kollektion der Konten funktioniert ähnlich wie der Timer für die Order-, Deal- und Positionserfassung, der im dritten Teil der Bibliotheksbeschreibung beschrieben wird (im Abschnitt zur Entwicklung des Bibliotheksbasisobjekts — der Klasse CEngine). Der einzige Unterschied zum Timer für Kollektionen der Orders, Deals und Positionen besteht darin, dass eine andere Methode zur Behandlung von Ereignissen aufgerufen wird — die AccountEventsControl().

Fügen wir die Methode zur Überprüfung der Änderungen in den Eigenschaften des aktuellen Kontos hinzu:

//+------------------------------------------------------------------+
//| Check the account events                                         |
//+------------------------------------------------------------------+
void CEngine::AccountEventsControl(void)
  {
   this.m_accounts.Refresh();
  }
//+------------------------------------------------------------------+

Die Methode ruft einfach die Methode Refresh() der Klasse CAccountsCollection auf.

Schreiben Sie im 'public' Teil der Klasse CEngine die beiden Methoden, die die Ereignis und Konto Kollektionslisten an das Programm zurückgeben. Dies ermöglicht uns den direkten Zugriff auf die Kollektionslisten aus unseren Programmen:

public:
   //--- Rückgabe der Liste aller (1) Positionen, (2) Pending-Order und (3) Marktorders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Rückgabe der Liste aller historischen (1) Aufträge, (2) gelöschten Pending-Orders, (3) Deals, (4) Positionen nach deren ID
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Return the account list
   CArrayObj*           GetListAllAccounts(void)                        { return this.m_accounts.GetList();    }
//--- Return the event list 
   CArrayObj*           GetListAllEvents(void)                          { return this.m_events.GetList();      }
//--- Rücksetzen des letzten Handelsereignisses
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_last_trade_event;      }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
   bool                 IsTester(void)                            const { return this.m_is_tester;             }
//--- Erstellen der Timerzählers
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/Destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Alles ist bereit, um die Klasse der Kollektion der Konten zu testen. Aber bevor wir mit dem Testen beginnen, ändern wir die Ereignisklasse der Kollektionen CEventsCollection::Refresh. Hinzufügen der Prüfung zur Zeile 233 der Auflistung, um gelegentliche Aktivierungen bei der Definition von Ereignissen zu beseitigen, bei denen ein altes Ereignis zusammen mit dem neuen an das Programm gesendet wird:

//--- Wenn das Ereignis in Kontohistorie existiert
   if(is_history_event)
     {
      //--- If the number of historical orders increased (MQL5, MQL4)
      if(new_history_orders>0 && new_market_pendings<0)
        {
         //--- Erhalt der Liste nur der entfernten Pending-Orders

Ich habe auch eher dumme Fehler beim Schreiben von Handelsfunktionen für MQL4 für die Arbeit im MetaTrader 4 (die Datei DELib.mqh) behoben. Es geht darum, dass die Funktion OrderSend() in MQL4 ein Auftragsticket und keinen booleschen Wert zurückgeben. Anscheinend fange ich an, MQL4 zu vergessen :)

Betrachten wir folgendes Beispiel:
Das Überprüfen des Ergebnisses der Operation MQL4-Funktion sah wie folgt aus (dies ist korrekt für MQL5):

if( ! OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))

Ich habe den Fehler behoben, indem ich korrekte Prüfungen für MQL4 implementiert habe:

if(OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue)==WRONG_VALUE)

Dies ist keine große Sache für den Tester, aber es ist immer noch ein Fehler.
Ich werde die vollwertigen Handelsklassen bald vorstellen, so dass diese Funktionen aus der Bibliothek entfernt werden.

Testen der Kollektion von Konten

Verwenden wir den EA TestDoEasyPart12_1.mq5, das wir bereits entwickelt haben, und speichern wir es unter dem Namen TestDoEasyPart12_2.mq5 im gleichen Ordner \MQL5\Experts\TestDoEasy.

In den Eingabeparameter des EAs wird die Variable zum Umschalten des Aussehens der im Journal angezeigten vorhandenen Kontodaten eingeführt — kurz (standardmäßig false) oder voll ( true):

//--- Eingabeparameter
input ulong    InpMagic             =  123;  // Magic number
input double   InpLots              =  0.1;  // Lots
input uint     InpStopLoss          =  50;   // StopLoss in points
input uint     InpTakeProfit        =  50;   // TakeProfit in points
input uint     InpDistance          =  50;   // Pending orders distance (points)
input uint     InpDistanceSL        =  50;   // StopLimit orders distance (points)
input uint     InpSlippage          =  0;    // Slippage in points
input double   InpWithdrawal        =  10;   // Withdrawal funds (in tester)
input uint     InpButtShiftX        =  40;   // Buttons X shift 
input uint     InpButtShiftY        =  10;   // Buttons Y shift 
input uint     InpTrailingStop      =  50;   // Trailing Stop (points)
input uint     InpTrailingStep      =  20;   // Trailing Step (points)
input uint     InpTrailingStart     =  0;    // Trailing Start (points)
input uint     InpStopLossModify    =  20;   // StopLoss for modification (points)
input uint     InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
input bool     InpFullProperties    =  false;// Show full accounts properties
//--- Globale Variablen

Fügen Sie folgenden Code zu der Funktion OnInit() hinzu:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;

//--- Check and remove remaining EA graphical objects
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Set trailing activation button status
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Set CTrade trading class parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Fast check of the account object
   CArrayObj* list=engine.GetListAllAccounts();
   if(list!=NULL)
     {
      int total=list.Total();
      if(total>0)
         Print("\n",TextByLanguage("=========== Список сохранённых аккаунтов ===========","=========== List of saved accounts ==========="));
      for(int i=0;i<total;i++)
        {
         CAccount* account=list.At(i);
         if(account==NULL)
            continue;
         Sleep(100);
         if(InpFullProperties)
            account.Print();
         else
            account.PrintShort();
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Hier erhalten wir die Liste der Kollektion der Konten mit der Methode GetListAllAccounts() der Klasse CEngine. Wir erhalten jedes nachfolgende Objekt von ihr in einer Schleife, die seine Eigenschaften im Journal je nach Eingabewert anzeigt — entweder einen kurzen Eintrag oder die vollständige Liste der Eigenschaften des Konto-Objekts.

Starten Sie den EA und sehen Sie, was im Journal anzeigt wird, wenn die Kurzbuchung ausgewählt ist ("Show full accounts properties" = falsch):


Jetzt wählen Sie die vollständige Liste aus — drücken Sie F7 und setzen Sie "Show full accounts properties" auf "true" im Parameterfenster:


Jetzt wird die vollständige Liste der Eigenschaften für jedes der vorhandenen Konten im Journal angezeigt.

Beachten Sie, dass Sie, um Konten in die Datei zu schreiben, eine Verbindung zum ersten Konto herstellen, sich wieder mit dem zweiten verbinden, dann zum dritten wechseln und so weiter. Mit anderen Worten, frühere Kontodaten werden bei jeder Verbindung zu einem neuen Konto in die Datei geschrieben.

Was kommt als Nächstes?

Im nächsten Artikel werden wir einige wichtige Ereignisse bei der Änderung der Kontoeigenschaften verfolgen und mit der Arbeit an Symbolobjekten und deren Kollektion beginnen.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie testen und herunterladen können.
Stellen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.

Zurück zum Inhalt

Frühere Artikel dieser Serie:

Teil 1. Konzept, Datenverwaltung.
Teil 2. Erhebung (Collection) historischer Aufträge und Deals.
Teil 3. Erhebung (Collection) von Marktorders und Positionen, Organisieren der Suche
Teil 4. Handelsereignisse. Konzept.
Teil 5. Klassen und Kollektionen von Handelsereignissen. Senden von Ereignissen an das Programm.
Teil 6. Ereignisse auf Netting-Konten.
Teil 7. Ereignis der Aktivierung einer StopLimit-Order, Vorbereiten der Funktionsweise bei Änderungen von Orders und Positionen.
Teil 8. Ereignisse von Änderungen von Orders und Positionen.
Teil 9. Kompatibilität mit MQL4 - Datenvorbereitung.
Teil 10. Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und Aktivierung von Pending-Orders.
Teil 11. Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen.


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/6952

Beigefügte Dateien |
MQL5.zip (124.15 KB)
MQL4.zip (124.15 KB)
Entwicklung eines plattformübergreifenden Grider-EAs (Teil III): Korrekturbasiertes Raster mit Martingal Entwicklung eines plattformübergreifenden Grider-EAs (Teil III): Korrekturbasiertes Raster mit Martingal

In diesem Artikel werden wir versuchen, den bestmögliche, rasterbasierten EA zu entwickeln. Wie üblich wird dies ein plattformübergreifender EA sein, der sowohl mit MetaTrader 4 als auch mit MetaTrader 5 arbeiten kann. Der erste EA war gut genug, außer dass er über einen langen Zeitraum keinen Gewinn erzielen konnte. Der zweite EA konnte in Zeiträumen von mehr als einigen Jahren arbeiten. Leider konnte er nicht mehr als 50% Gewinn pro Jahr bei einem maximalen Drawdown von weniger als 50% erzielen.

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XI). Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XI). Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen

Wir setzen die Entwicklung einer großen plattformübergreifenden Bibliothek fort, die die Entwicklung von Programmen für MetaTrader 5 und MetaTrader 4 Plattformen vereinfacht. Im zehnten Teil haben wir unsere Arbeit an der Bibliothekskompatibilität mit MQL4 fortgesetzt und die Ereignisse der Eröffnung von Positionen und der Aktivierung von offenen Orders definiert. In diesem Artikel werden wir die Ereignisse des Schließens von Positionen definieren und die nicht verwendeten Auftragseigenschaften beseitigen.

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XIII): Ereignisse des Kontoobjekts Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XIII): Ereignisse des Kontoobjekts

Der Artikel behandelt die Arbeit mit Kontoereignissen, um wichtige Änderungen in den Kontoeigenschaften zu verfolgen, die den automatisierten Handel beeinflussen. Wir haben bereits im vorherigen Artikel bei der Entwicklung der Kollektion von Kontoobjekten einige Funktionen zur Verfolgung von Kontoereignissen implementiert.

Hier sind der neue MetaTrader 5 und MQL5 Hier sind der neue MetaTrader 5 und MQL5

Dies ist nur ein kurzer Überblick über MetaTrader 5. Ich kann nicht alle neuen Funktionen des Systems in so kurzer Zeit beschreiben. Die Tests begannen am 09.09.2009. Das ist ein symbolisches Datum und ich bin sicher, dass es eine Glückszahl werden wird. Es sind ein paar Tage vergangen, seit ich die Beta-Version des MetaTrader-5-Terminals und MQL5 bekommen habe. Ich konnte noch nicht alle Funktionen ausprobieren, doch ich bin jetzt schon beeindruckt.