Библиотека для простого и быстрого создания программ для MetaTrader (Часть XII): Класс объекта "аккаунт", коллекция объектов-аккаунтов

Artyom Trishkin | 6 июня, 2019

Содержание

Перед началом создания торговых классов библиотеки, подготовим несколько классов, необходимых для торговли и неотъемлемо с нею связанных, а именно: нам необходимы данные по аккаунту (торговому счёту) и по торгуемым символам. Непосредственно в данной статье займёмся объектом-аккаунтом.

Так как данные по аккаунту могут меняться прямо в процессе торговли, сделаем класс объекта-аккаунта и затем — коллекцию объектов-аккаунтов. Далее создадим отслеживание событий на счёте. Это позволит нам вовремя отслеживать изменение предоставленного кредитного плеча на счёте, отслеживать изменение баланса, прибыли/убытка, размер собственных средств и данных по ограничениям торгового счёта.


Объект-аккаунт

Объект аккаунта (торгового счёта) идентичен ранее нами созданным объектам, описанным в предыдущих статьях. Единственное отличие этого объекта от ранее рассмотренных будет в том, что он не будет неким абстрактным объектом с наследниками, уточняющими статус конкретного объекта. Объект-аккаунт будет самостоятельным объектом со всеми свойствами счёта. И далее такие объекты будут добавлены в коллекцию объектов-аккаунтов, что позволит сравнивать данные разных счетов по различным параметрам.

Начнём, пожалуй, как обычно — с создания всех необходимых для работы с классом перечислений свойств объекта-аккаунта.
Откроем в справке редактора свойства счёта:

Для функции AccountInfoInteger()

ENUM_ACCOUNT_INFO_INTEGER

Идентификатор

Описание

Тип свойства

ACCOUNT_LOGIN

Номер счета

long

ACCOUNT_TRADE_MODE

Тип торгового счета

ENUM_ACCOUNT_TRADE_MODE

ACCOUNT_LEVERAGE

Размер предоставленного плеча

long

ACCOUNT_LIMIT_ORDERS

Максимально допустимое количество действующих отложенных ордеров

int

ACCOUNT_MARGIN_SO_MODE

Режим задания минимально допустимого уровня залоговых средств

ENUM_ACCOUNT_STOPOUT_MODE

ACCOUNT_TRADE_ALLOWED

Разрешенность торговли для текущего счета

bool

ACCOUNT_TRADE_EXPERT

Разрешенность торговли для эксперта

bool

ACCOUNT_MARGIN_MODE

Режим расчета маржи

ENUM_ACCOUNT_MARGIN_MODE

ACCOUNT_CURRENCY_DIGITS

Количество знаков после запятой для валюты счета, необходимых для точного отображения торговых результатов

int


Для функции AccountInfoDouble()

ENUM_ACCOUNT_INFO_DOUBLE

Идентификатор

Описание

Тип свойства

ACCOUNT_BALANCE

Баланс счета в валюте депозита

double

ACCOUNT_CREDIT

Размер предоставленного кредита в валюте депозита

double

ACCOUNT_PROFIT

Размер текущей прибыли на счете в валюте депозита

double

ACCOUNT_EQUITY

Значение собственных средств на счете в валюте депозита

double

ACCOUNT_MARGIN

Размер зарезервированных залоговых средств на счете  в валюте депозита

double

ACCOUNT_MARGIN_FREE

Размер свободных средств на счете  в валюте депозита, доступных для открытия позиции

double

ACCOUNT_MARGIN_LEVEL

Уровень залоговых средств на счете в процентах

double

ACCOUNT_MARGIN_SO_CALL

Уровень залоговых средств, при котором требуется пополнение счета (Margin Call). В зависимости от установленного ACCOUNT_MARGIN_SO_MODE выражается в процентах либо в валюте депозита

double

ACCOUNT_MARGIN_SO_SO

Уровень залоговых средств, при достижении которого происходит принудительное закрытие самой убыточной позиции (Stop Out). В зависимости от установленного ACCOUNT_MARGIN_SO_MODE выражается в процентах либо в валюте депозита

double

ACCOUNT_MARGIN_INITIAL

Размер средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам

double

ACCOUNT_MARGIN_MAINTENANCE

Размер средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям

double

ACCOUNT_ASSETS

Текущий размер активов на счёте

double

ACCOUNT_LIABILITIES

Текущий размер обязательств на счёте

double

ACCOUNT_COMMISSION_BLOCKED

Текущая сумма заблокированных комиссий по счёту

double


Для функции AccountInfoString()

ENUM_ACCOUNT_INFO_STRING

Идентификатор

Описание

Тип свойства

ACCOUNT_NAME

Имя клиента

string

ACCOUNT_SERVER

Имя торгового сервера

string

ACCOUNT_CURRENCY

Валюта депозита

string

ACCOUNT_COMPANY

Имя компании, обслуживающей счет

string


Объект-аккаунт будет наделён всеми этими свойствами, и устанавливаться они будут в конструкторе класса.

В файле Defines.mqh библиотеки впишем целочисленные, вещественные и строковые свойства объекта-аккаунта, соответствующие приведённым выше таблицам свойств счёта.
Так как ранее мы создали перечисления для работы с событиями счёта, то логично будет разместить данные о свойствах счёта перед ранее созданными данными для работы с событиями счёта:

//+------------------------------------------------------------------+
//| Данные для работы со счетами                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Целочисленные свойства счёта                                     |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
   ACCOUNT_PROP_LOGIN,                                      // Номер счёта
   ACCOUNT_PROP_TRADE_MODE,                                 // Тип торгового счета
   ACCOUNT_PROP_LEVERAGE,                                   // Размер предоставленного плеча
   ACCOUNT_PROP_LIMIT_ORDERS,                               // Максимально допустимое количество действующих отложенных ордеров
   ACCOUNT_PROP_MARGIN_SO_MODE,                             // Режим задания минимально допустимого уровня залоговых средств
   ACCOUNT_PROP_TRADE_ALLOWED,                              // Разрешенность торговли для текущего счета со стороны сервера
   ACCOUNT_PROP_TRADE_EXPERT,                               // Разрешенность торговли для эксперта со стороны сервера
   ACCOUNT_PROP_MARGIN_MODE,                                // Режим расчета маржи
   ACCOUNT_PROP_CURRENCY_DIGITS                             // Количество знаков после запятой для валюты счета, необходимых для точного отображения торговых результатов
  };
#define ACCOUNT_PROP_INTEGER_TOTAL    (9)                   // Общее количество целочисленных свойств счетов
#define ACCOUNT_PROP_INTEGER_SKIP     (0)                   // Количество неиспользуемых в сортировке целочисленных свойств счетов
//+------------------------------------------------------------------+
//| Вещественные свойства счёта                                      |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_DOUBLE
  {
   ACCOUNT_PROP_BALANCE = ACCOUNT_PROP_INTEGER_TOTAL,       // Баланс счета в валюте депозита
   ACCOUNT_PROP_CREDIT,                                     // Размер предоставленного кредита в валюте депозита
   ACCOUNT_PROP_PROFIT,                                     // Размер текущей прибыли на счете в валюте депозита
   ACCOUNT_PROP_EQUITY,                                     // Значение собственных средств на счете в валюте депозита
   ACCOUNT_PROP_MARGIN,                                     // Размер зарезервированных залоговых средств на счете  в валюте депозита
   ACCOUNT_PROP_MARGIN_FREE,                                // Размер свободных средств на счете  в валюте депозита, доступных для открытия позиции
   ACCOUNT_PROP_MARGIN_LEVEL,                               // Уровень залоговых средств на счете в процентах
   ACCOUNT_PROP_MARGIN_SO_CALL,                             // Уровень залоговых средств, при котором требуется пополнение счета (Margin Call)
   ACCOUNT_PROP_MARGIN_SO_SO,                               // Уровень залоговых средств, при достижении которого происходит принудительное закрытие самой убыточной позиции (Stop Out)
   ACCOUNT_PROP_MARGIN_INITIAL,                             // Размер средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам 
   ACCOUNT_PROP_MARGIN_MAINTENANCE,                         // Размер средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям
   ACCOUNT_PROP_ASSETS,                                     // Текущий размер активов на счёте
   ACCOUNT_PROP_LIABILITIES,                                // Текущий размер обязательств на счёте
   ACCOUNT_PROP_COMMISSION_BLOCKED                          // Текущая сумма заблокированных комиссий по счёту
  };
#define ACCOUNT_PROP_DOUBLE_TOTAL     (14)                  // Общее количество вещественных свойств счетов
#define ACCOUNT_PROP_DOUBLE_SKIP      (0)                   // Количество неиспользуемых в сортировке вещественных свойств счетов
//+------------------------------------------------------------------+
//| Строковые свойства счёта                                         |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_STRING
  {
   ACCOUNT_PROP_NAME = (ACCOUNT_PROP_INTEGER_TOTAL+ACCOUNT_PROP_DOUBLE_TOTAL), // Имя клиента
   ACCOUNT_PROP_SERVER,                                     // Имя торгового сервера
   ACCOUNT_PROP_CURRENCY,                                   // Валюта депозита
   ACCOUNT_PROP_COMPANY                                     // Имя компании, обслуживающей счет
  };
#define ACCOUNT_PROP_STRING_TOTAL     (4)                   // Общее количество строковых свойств счетов
#define ACCOUNT_PROP_STRING_SKIP      (0)                   // Количество неиспользуемых в сортировке строковых свойств счетов
//+------------------------------------------------------------------+
//| Возможные критерии сортировки счетов                             |
//+------------------------------------------------------------------+
#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_TRADE_MODE          =  1,                      // Сортировать по типу торгового счета
   SORT_BY_ACCOUNT_LEVERAGE            =  2,                      // Сортировать по размеру предоставленного плеча
   SORT_BY_ACCOUNT_LIMIT_ORDERS        =  3,                      // Сортировать по максимально допустимому количеству действующих отложенных ордеров
   SORT_BY_ACCOUNT_MARGIN_SO_MODE      =  4,                      // Сортировать по режиму задания минимально допустимого уровня залоговых средств
   SORT_BY_ACCOUNT_TRADE_ALLOWED       =  5,                      // Сортировать по разрешенности торговли для текущего счета
   SORT_BY_ACCOUNT_TRADE_EXPERT        =  6,                      // Сортировать по разрешенности торговли для эксперта
   SORT_BY_ACCOUNT_MARGIN_MODE         =  7,                      // Сортировать по режиму расчета маржи
   SORT_BY_ACCOUNT_CURRENCY_DIGITS     =  8,                      // Сортировать по количеству знаков после запятой для валюты счета
    
   SORT_BY_ACCOUNT_BALANCE             =  FIRST_ACC_DBL_PROP,     // Сортировать по балансу счета в валюте депозита
   SORT_BY_ACCOUNT_CREDIT              =  FIRST_ACC_DBL_PROP+1,   // Сортировать по размеру предоставленного кредита в валюте депозита
   SORT_BY_ACCOUNT_PROFIT              =  FIRST_ACC_DBL_PROP+2,   // Сортировать по размеру текущей прибыли на счете в валюте депозита
   SORT_BY_ACCOUNT_EQUITY              =  FIRST_ACC_DBL_PROP+3,   // Сортировать по значению собственных средств на счете в валюте депозита
   SORT_BY_ACCOUNT_MARGIN              =  FIRST_ACC_DBL_PROP+4,   // Сортировать по размеру зарезервированных залоговых средств на счете в валюте депозита
   SORT_BY_ACCOUNT_MARGIN_FREE         =  FIRST_ACC_DBL_PROP+5,   // Сортировать по размеру свободных средств на счете в валюте депозита, доступных для открытия позиции
   SORT_BY_ACCOUNT_MARGIN_LEVEL        =  FIRST_ACC_DBL_PROP+6,   // Сортировать по уровню залоговых средств на счете в процентах
   SORT_BY_ACCOUNT_MARGIN_SO_CALL      =  FIRST_ACC_DBL_PROP+7,   // Сортировать по уровню залоговых средств, при котором требуется пополнение счета (Margin Call)
   SORT_BY_ACCOUNT_MARGIN_SO_SO        =  FIRST_ACC_DBL_PROP+8,   // Сортировать по уровню залоговых средств, при достижении которого происходит принудительное закрытие самой убыточной позиции (Stop Out)
   SORT_BY_ACCOUNT_MARGIN_INITIAL      =  FIRST_ACC_DBL_PROP+9,   // Сортировать по размеру средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам 
   SORT_BY_ACCOUNT_MARGIN_MAINTENANCE  =  FIRST_ACC_DBL_PROP+10,  // Сортировать по размеру средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям
   SORT_BY_ACCOUNT_ASSETS              =  FIRST_ACC_DBL_PROP+11,  // Сортировать по текущему размеру активов на счёте
   SORT_BY_ACCOUNT_LIABILITIES         =  FIRST_ACC_DBL_PROP+12,  // Сортировать по текущему размеру обязательств на счёте
   SORT_BY_ACCOUNT_COMMISSION_BLOCKED  =  FIRST_ACC_DBL_PROP+13,  // Сортировать по текущей сумме заблокированных комиссий по счёту

   SORT_BY_ACCOUNT_NAME                =  FIRST_ACC_STR_PROP,     // Сортировать по имени клиента
   SORT_BY_ACCOUNT_SERVER              =  FIRST_ACC_STR_PROP+1,   // Сортировать по имени торгового сервера
   SORT_BY_ACCOUNT_CURRENCY            =  FIRST_ACC_STR_PROP+2,   // Сортировать по валюте депозита
   SORT_BY_ACCOUNT_COMPANY             =  FIRST_ACC_STR_PROP+3    // Сортировать по имени компании, обслуживающей счет
  };
//+------------------------------------------------------------------+
//| Данные для работы с событиями счёта                              |
//+------------------------------------------------------------------+

Здесь всё для нас уже должно быть знакомо по прошлым статьям, поэтому не будем задерживаться на рассмотрении организации перечислений, задания неиспользуемых свойств объекта и макроподстановок для указания количества пропускаемых свойств для точного расчёта адреса начальной константы  перечисления следующего типа свойств объекта — всё это было рассмотрено нами в одной из предыдущих статей — в частности в шестой части описания библиотеки в разделе " Реализация обработки событий на неттинговом счёте".

Единственное, на чём здесь остановимся, так это на свойстве "Режим расчёта маржи":
Так как перечисления ENUM_ACCOUNT_MARGIN_MODE нет в MQL4, то для компиляции в MQL4 нам необходимо это перечисление указать. Для этого впишем его в конец файла ToMQL4.mqh:

//+------------------------------------------------------------------+
//| Режим расчета маржи                                              |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_MARGIN_MODE
  {
   ACCOUNT_MARGIN_MODE_RETAIL_NETTING,
   ACCOUNT_MARGIN_MODE_EXCHANGE,
   ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
  };
//+------------------------------------------------------------------+

Теперь, когда все данные подготовлены, можно создать объект-аккаунт.

В папке библиотеки \MQL5\Include\DoEasy\Objects\ создадим подпапку Accounts, и в этой новой папке создадим новый класс CAccount в файле Account.mqh.

Во вновь созданном файле класса сразу же впишем объявления всех необходимых методов.
Для объектов библиотеки большинство этих методов уже являются "стандартными". Тут, правда, есть небольшая оговорка: так как данный класс не предполагает наличие наследников, то в нём отсутствует защищённый конструктор класса, принимающий и устанавливающий статус объекта. Поэтому и у объекта-аккаунта отсутствует свойство "статус" и его конструктор не принимает никаких аргументов. В то же время, мы оставим виртуальные методы, возвращающие флаги поддержания объектом того или иного свойства — для возможных потомков класса в будущем:

//+------------------------------------------------------------------+
//|                                                      Account.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Класс аккаунта                                                   |
//+------------------------------------------------------------------+
class CAccount : public CObject
  {
private:
   long              m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL];           // Целочисленные свойства
   double            m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL];          // Вещественные свойства
   string            m_string_prop[ACCOUNT_PROP_STRING_TOTAL];          // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство аккаунта
   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:
//--- Конструктор                   
                     CAccount(void);
protected:
   
public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство аккаунта
   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;                }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство аккаунта
   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)];               }

//--- Возвращает флаг расчёта уровне MarginCall и StopOut в процентах
   bool              IsPercentsForSOLevels(void)                                 const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT;          }
//--- Возвращает флаг поддержания объектом-аккаунтом данного свойства
   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; }

//--- Сравнивает объекты CAccount между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-аккаунта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CAccount между собой по свойствам счёта (для поиска равных объектов-аккаунтов)
   bool              IsEqual(CAccount* compared_account) const;
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-аккаунта          |
//+------------------------------------------------------------------+
//--- Возвращает целочисленные свойства аккаунта
   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);                            }
   
//--- Возвращает вещественные свойства аккаунта
   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);                         }
   
//--- Возвращает строковые свойства аккаунта
   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);                                    }

//+------------------------------------------------------------------+
//| Описания свойств объекта-аккаунта                                |
//+------------------------------------------------------------------+
//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства аккаунта
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_ACCOUNT_PROP_STRING property);
//--- Возвращает наименование типа торгового счёта
   string            TradeModeDescription(void)    const;
//--- Возвращает описание режима задания минимально допустимого уровня залоговых средств
   string            MarginSOModeDescription(void) const;
//--- Возвращает описание режима расчета маржи
   string            MarginModeDescription(void)   const;
//--- Выводит в журнал описание свойств аккаунта (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал краткое описание счёта
   void              PrintShort(void);
//---
  };
//+------------------------------------------------------------------+

За пределами тела класса напишем конструктор класса:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CAccount::CAccount(void)
  {
//--- Сохранение целочисленных свойств
   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 ;
   
//--- Сохранение вещественных свойств
   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);
   
//--- Сохранение строковых свойств
   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);
   
  }
//+-------------------------------------------------------------------+

Здесь всё понятно: каждому свойству объекта присваивается соответствующее свойство счёта при помощи AccountInfo-функций.
Для двух отсутствующих в MQL4 свойств сделан выбор при помощи директив условной компиляции: для свойств "режим расчёта маржи" и "количество знаков после запятой для валюты счета" для MQL5 получаем соответствующие свойства, а для MQL4 просто возвращаем для первого свойства ACCOUNT_MARGIN_MODE_RETAIL_HEDGING (хеджевый счёт) из перечисления ENUM_ACCOUNT_MARGIN_MODE, для второго — 2 знака после запятой.

Напишем реализацию метода для поиска и сортировки объектов-аккаунтов в их списке-коллекции.
Метод идентичен ранее рассмотренным нами таким же методам в объектах библиотеки, поэтому здесь ограничимся лишь его листингом:

//+-------------------------------------------------------------------+
//|Сравнивает объекты CAccount между собой по всем возможным свойствам|
//+-------------------------------------------------------------------+
int CAccount::Compare(const CObject *node,const int mode=0) const
  {
   const CAccount *account_compared=node;
//--- сравнение целочисленных свойств двух аккаунтов
   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);
     }
//--- сравнение вещественных свойств двух аккаунтов
   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);
     }
//--- сравнение строковых свойств двух аккаунтов
   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;
  }
//+------------------------------------------------------------------+

Для сравнения двух объектов-аккаунтов нам необходимо сравнить их неизменяемые свойства чтобы определить принадлежность этих объектов к разным счетам. Для точной идентификации счёта есть его номер (логин), имя пользователя и наименование компании. Именно эти свойства двух сравниваемых счетов и будем проверять у объектов-аккаунтов в методе сравнения двух объектов-аккаунтов:

//+------------------------------------------------------------------+
//| Сравнивает объекты CAccount между собой по свойствам счёта       |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на сравниваемый объект и проверяются три свойства двух объектов — наименование компании, номер счёта и имя клиента. Если любые из свойств объектов не равны — значит эти объекты принадлежат разным счетам — возвращаем false. После успешного прохождения всех трёх сравнений возвращаем true— объекты равны между собой.

Остальные методы класса являются "сервисными" и в отдельном рассмотрении, думаю, не нуждаются — в них всё понятно из их листинга, да и похожие методы мы неоднократно рассматривали в предыдущих частях описания библиотеки:

//+------------------------------------------------------------------+
//| Выводит в журнал свойства аккаунта                               |
//+------------------------------------------------------------------+
void CAccount::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров аккаунта","The beginning of the parameter list of Account")," ==================");
   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 the parameter list of Account")," ==================\n");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание счёта                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства аккаунта             |
//+------------------------------------------------------------------+
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("Количество знаков после запятой для валюты счета","The number of decimal places in the account currency")+": "+
                                                   (string)this.GetProperty(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 the deposit currency")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())+" "+this.Currency()                                            :
      property==ACCOUNT_PROP_MARGIN_FREE     ?  
         TextByLanguage("Свободные средства на счете, доступные для открытия позиции","Free margin of an account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_MARGIN_LEVEL    ?  TextByLanguage("Уровень залоговых средств на счете в процентах","Account margin level in percents")+": "+
                                                   ::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 an account to cover the margin of all pending orders ")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_MARGIN_MAINTENANCE  ? 
         TextByLanguage("Зарезервированные средства для обеспечения минимальной суммы по всем открытым позициям","Min equity reserved on an account to cover the min amount of all open positions")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_ASSETS          ?  TextByLanguage("Текущий размер активов на счёте","The current assets of an account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_LIABILITIES     ?  TextByLanguage("Текущий размер обязательств на счёте","The current liabilities on an account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      property==ACCOUNT_PROP_COMMISSION_BLOCKED ?  
         TextByLanguage("Сумма заблокированных комиссий по счёту","The current blocked commission amount on an account")+": "+
                                                   ::DoubleToString(this.GetProperty(property),(int)this.CurrencyDigits())                                                                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства аккаунта                 |
//+------------------------------------------------------------------+
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 a company that serves the account")+": \""+this.GetProperty(property)+"\""  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает наименование типа торгового счёта                     |
//+------------------------------------------------------------------+
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")
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание режима задания                               |
//| минимально допустимого уровня залоговых средств                  |
//+------------------------------------------------------------------+
string CAccount::MarginSOModeDescription(void) const
  {
   return
     (
      this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT  ? TextByLanguage("Уровень задается в процентах","Account stop out mode in percents") : 
      TextByLanguage("Уровень задается в деньгах","Account stop out mode in money")
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание режима расчета маржи                         |
//+------------------------------------------------------------------+
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")
     );
  }
//+------------------------------------------------------------------+

Полный листинг класса-аккаунта можно посмотреть в прикреплённых в конце статьи файлах. Протестируем работу класса.

Тест объекта-аккаунта

Чтобы проверить работу класса, верно ли он выполняет свою функцию по получению данных счёта, временно подключим файл класса к главному объекту библиотеки — классу CEngine:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
#include "Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {

После данного подключения файла с классом объекта-аккаунта, наша программа будет видеть этот объект, что нам сейчас и нужно.

Для тестирования работы класса возьмём советник из прошлой статьи — \MQL5\Experts\TestDoEasy\Part11\TestDoEasyPart11.mq5 и сохраним его под новым именем TestDoEasyPart12_1.mq5 в папке \MQL5\Experts\TestDoEasy\Part12.

Для подключения и тестирования объекта-аккаунта добавим строки в обработчик OnInit() советника (проверять будем при инициализации):

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();

//--- Установка глобальных переменных советника
   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;

//--- Проверка и удаление неудалённых графических объектов советника
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Установка параметров торгового класса CTrade
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Быстрая проверка объекта-аккаунта
   CAccount* acc=new CAccount();
   if(acc!=NULL)                
     {                          
      acc.PrintShort();         
      acc.Print();              
      delete acc;               
     }                          
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Здесь: создаём объект-аккаунт и, при успешном его создании, выводим в журнал сначала короткую запись о счёте, а затем полный список параметров счёта. По окончании удаляем объект-аккаунт.

Запустим советник на графике любого инструмента и посмотрим в журнал "Эксперты":


Все данные о счёте выводятся правильно.

Коллекция объектов-аккаунтов

Так как при смене счёта все советники переинициализируются, то соответственно — вызываются сначала деструкторы, а затем и конструкторы классов, и таким образом прошлый объект-аккаунт, который был до смены счёта, советник теряет. А нам нужно для коллекции аккаунтов помнить данные прошлых счетов, к которым подключался терминал. Для этого будем в деструкторе класса — коллекции аккаунтов сохранять данные текущего объекта-аккаунта (счёта) в файл, а в конструкторе класса будем загружать данные из файлов. Таким образом, коллекция аккаунтов будет наполняться данными о всех счетах, к которым подключался терминал за время работы программы на основе нашей библиотеки. А так как файлы будут храниться в общей папке всех клиентских терминалов, то каждый запущенный терминал, если на нём работает программа на основе библиотеки, будет видеть все счета, к которым подключались терминалы на компьютере.
В новом классе - коллекции аккаунтов будет возможность сравнивать данные о всех имеющихся аккаунтах по различным их параметрам.

Для сохранения объекта-аккаунта в файл нам необходимо создать в классе CAccount метод для сохранения данных в файл.
У нас все создаваемые объекты унаследованы от CObject — базового объекта стандартной библиотеки. И в этом классе уже предусмотрены виртуальные методы сохранения и загрузки объекта в файл:

//+------------------------------------------------------------------+
//| 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);   }
   //--- method of identifying the object
   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);      }
  };
//+------------------------------------------------------------------+

Методы ничего не делают, и нам нужно их переопределить в наших классах-наследниках там, где они нужны, а именно — в классе CAccount.

Для сохранения объекта-аккаунта, всех его свойств в файл, будем использовать простую структуру и сохранять эту структуру в файл. Но поля объекта содержат строки, а это уже не POD-структура. Значит нам необходимо преобразовать все строковые свойства объекта при их сохранении в поля структуры в uchar-массивы с постоянным размером, и тогда мы сможем сохранять все данные о свойствах объекта-аккаунта в файл как структуру функцией FileWriteArray().

Для задания каталога хранения файлов библиотеки и постоянного размера uchar-массивов создадим макроподстановки в файле Defines.mqh:

#define DIRECTORY                   ("DoEasy\\")               // Каталог библиотеки для расположения папок объектов классов
#define UCHAR_ARRAY_SIZE            (64)                       // Размер uchar-массивов для хранения string-свойств

Так как длина строки комментариев ограничена 64 символами, то размер для массивов зададим именно таким. Я исходил именно из размера строки комментария ордера, так как далее может случиться так, что нам может потребоваться сохранять объекты ордеров в файлы, и длина меньше 64 символов нас не устроит. Вполне может оказаться, что для строковых свойств счёта выделен больший размер под строку. Если тестирование покажет недостаточность данного размера для хранения названий компаний, обслуживающих счёт, то его всегда можно будет увеличить.

Создадим в приватной секции класса CAccount необходимую структуру для сохранения свойств объекта-аккаунта и переменные-члены класса для работы с файлами:

//+------------------------------------------------------------------+
//| Класс аккаунта                                                   |
//+------------------------------------------------------------------+
class CAccount : public CObject
  {
private:
   struct SData
     {
      //--- Целочисленные свойства счёта
      long           login;                        // ACCOUNT_LOGIN (Номер счёта)
      int            trade_mode;                   // ACCOUNT_TRADE_MODE (Тип торгового счета)
      long           leverage;                     // ACCOUNT_LEVERAGE (Размер предоставленного плеча)
      int            limit_orders;                 // ACCOUNT_LIMIT_ORDERS (Максимально допустимое количество действующих отложенных ордеров)
      int            margin_so_mode;               // ACCOUNT_MARGIN_SO_MODE (Режим задания минимально допустимого уровня залоговых средств)
      bool           trade_allowed;                // ACCOUNT_TRADE_ALLOWED (Разрешенность торговли для текущего счета со стороны сервера)
      bool           trade_expert;                 // ACCOUNT_TRADE_EXPERT (Разрешенность торговли для эксперта со стороны сервера)
      int            margin_mode;                  // ACCOUNT_MARGIN_MODE (Режим расчета маржи)
      int            currency_digits;              // ACCOUNT_CURRENCY_DIGITS (Количество знаков после запятой для валюты счета)
      //--- Вещественные свойства счёта
      double         balance;                      // ACCOUNT_BALANCE (Баланс счета в валюте депозита)
      double         credit;                       // ACCOUNT_CREDIT (Размер предоставленного кредита в валюте депозита)
      double         profit;                       // ACCOUNT_PROFIT (Размер текущей прибыли на счете в валюте депозита)
      double         equity;                       // ACCOUNT_EQUITY (Значение собственных средств на счете в валюте депозита)
      double         margin;                       // ACCOUNT_MARGIN (Размер зарезервированных залоговых средств на счете  в валюте депозита)
      double         margin_free;                  // ACCOUNT_MARGIN_FREE (Размер свободных средств на счете  в валюте депозита, доступных для открытия позиции)
      double         margin_level;                 // ACCOUNT_MARGIN_LEVEL (Уровень залоговых средств на счете в процентах)
      double         margin_so_call;               // ACCOUNT_MARGIN_SO_CALL (Уровень залоговых средств, при котором происходит MarginCall)
      double         margin_so_so;                 // ACCOUNT_MARGIN_SO_SO (Уровень залоговых средств, при достижении которого происходит StopOut)
      double         margin_initial;               // ACCOUNT_MARGIN_INITIAL (Размер средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам)
      double         margin_maintenance;           // ACCOUNT_MARGIN_MAINTENANCE (Размер средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям)
      double         assets;                       // ACCOUNT_ASSETS (Текущий размер активов на счёте)
      double         liabilities;                  // ACCOUNT_LIABILITIES (Текущий размер обязательств на счёте)
      double         comission_blocked;            // ACCOUNT_COMMISSION_BLOCKED (Текущая сумма заблокированных комиссий по счёту)
      //--- Строковые свойства счёта
      uchar          name[UCHAR_ARRAY_SIZE];       // ACCOUNT_NAME (Имя клиента)
      uchar          server[UCHAR_ARRAY_SIZE];     // ACCOUNT_SERVER (Имя торгового сервера)
      uchar          currency[UCHAR_ARRAY_SIZE];   // ACCOUNT_CURRENCY (Валюта депозита)
      uchar          company[UCHAR_ARRAY_SIZE];    // ACCOUNT_COMPANY (Имя компании, обслуживающей счет)
     };
   SData             m_struct_obj;                                      // Структура объекта-аккаунта
   uchar             m_uchar_array[];                                   // uchar-массив структуры объекта-аккаунта

   
//--- Свойства объекта
   long              m_long_prop[ACCOUNT_PROP_INTEGER_TOTAL];           // Целочисленные свойства
   double            m_double_prop[ACCOUNT_PROP_DOUBLE_TOTAL];          // Вещественные свойства
   string            m_string_prop[ACCOUNT_PROP_STRING_TOTAL];          // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство аккаунта
   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:
//--- Создаёт (1) структуру объекта-аккаунта, (2) объект-аккаунт из структуры
   bool              ObjectToStruct(void);
   void              StructToObject(void);
public:
Из листинга видно, что в защищённой секции класса CAccount объявлены дополнительно два метода — для создания структуры из полей свойств объекта и обратный метод для создания объекта-аккаунта из структуры.

Первый метод будет использоваться для записи объекта-аккаунта в файл, второй — для чтения из файла.

За пределами тела класса напишем эти методы:

//+------------------------------------------------------------------+
//| Создаёт структуру объекта-аккаунта                               |
//+------------------------------------------------------------------+
bool CAccount::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   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();
//--- Сохранение вещественных свойств
   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();
//--- Сохранение строковых свойств
   ::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);
   //--- Сохранение структуры в uchar-массив
   ::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;
  }
//+------------------------------------------------------------------+

Как видно из листинга, все целочисленные и вещественные свойства объекта сохраняются в одноимённых полях структуры. А для сохранения строковых свойств используем преобразование строки в uchar-массив, и уже его сохраняем в соответствующие поля структуры.
По завершению сохранения свойств объекта, вся структура сохраняется в uchar-массив, который впоследствии будем сохранять в файл.

//+------------------------------------------------------------------+
//| Создаёт объект-аккаунт из структуры                              |
//+------------------------------------------------------------------+
void CAccount::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   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;
//--- Сохранение вещественных свойств
   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;
//--- Сохранение строковых свойств
   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);
  }
//+------------------------------------------------------------------+

Метод обратного преобразования полей структуры в свойства объекта-аккаунта практически идентичен первому, выше рассмотренному.
Здесь строковые свойства объекта-аккаунта получаем преобразованием uchar-массивов структуры в строки.

Теперь всё готово для создания виртуальных методов сохранения и загрузки объекта в/из файла.

В публичной секции класса CAccount объявим виртуальные методы Save() и Load():

public:
//--- Конструктор
                     CAccount(void);
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство аккаунта
   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;                }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство аккаунта
   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)];               }

//--- Возвращает флаг расчёта уровне MarginCall и StopOut в процентах
   bool              IsPercentsForSOLevels(void)                                 const { return this.MarginSOMode()==ACCOUNT_STOPOUT_MODE_PERCENT;          }
//--- Возвращает флаг поддержания объектом-аккаунтом данного свойства
   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; }

//--- Сравнивает объекты CAccount между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-аккаунта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CAccount между собой по свойствам счёта (для поиска равных объектов-аккаунтов)
   bool              IsEqual(CAccount* compared_account) const;
//--- (1) Сохраняет объект-аккаунт в файл, (2), загружает объект-аккаунт из файла
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-аккаунта          |
//+------------------------------------------------------------------+

Напишем методы сохранения объекта-аккаунта в файл и загрузки из файла:

//+------------------------------------------------------------------+
//| Сохраняет объект-аккаунт в файл                                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Здесь:

Метод загрузки данных объекта из файла:

//+------------------------------------------------------------------+
//| Загружает объект-аккаунт из файла                                |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Здесь:

  • в метод передаётся хендл ранее открытого для чтения файла
  • загружаем в uchar-массив данные из файла
  • сохраняем в POD-структуру данные из массива
  • записываем в поля объекта данные из POD-структуры
Доработка объекта-аккаунта для загрузки/сохранения данных в файл завершена.

Итак, работа коллекции аккаунтов будет организована следующим образом: при зауске программы на исполнение будет проверяться текущий счёт, создаваться объект-аккаунт с данными текущего счёта и помещаться в список-коллекцию аккаунтов. Далее будем просматривать папку с файлами ранее сохранённых аккаунтов, и если в ней есть файлы, то будем читать их по очереди, проверять на несовпадение с текущим аккаунтом и помещать в список-коллекцию аккаунтов. После того, как список будет создан, будем в таймере проверять состояние данных текущего счёта и при их изменении будем фиксировать произошедшие изменения.
Для некоторых изменений впоследствии создадим события и будем отправлять эти события в программу для контроля изменения параметров счёта. Например, внезапное изменение предоставленного плеча — весьма ощутимое и неприятное изменение, о котором необходимо вовремя уведомить пользователя и его программу.

Так как нам потребуется работа в таймере и новый список-коллекция, то создадим для них макроподстановки с параметрами таймера и идентификатором списка в файле Defines.mqh, а заодно поменяем названия ранее созданных макроподстановок для таймера коллекции ордеров, сделок и позиций (добавим к их наименованию " ORD", чтобы различать принадлежность макроподстановок к таймеру той, или иной коллекции). Паузу для обновления данных аккаунта установим в одну секунду — думаю, этого достаточно будет для отслеживания изменений и несильной нагрузки на систему:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
#define DFUN_ERR_LINE               (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN                        (__FUNCTION__+": ")        // "Описание функции"
#define COUNTRY_LANG                ("Russian")                // Язык страны
#define END_TIME                    (D'31.12.3000 23:59:59')   // Конечная дата для запросов данных истории счёта
#define TIMER_FREQUENCY             (16)                       // Минимальная частота таймера библиотеки в милисекундах
//--- Параметры таймера коллекции ордеров и сделок
#define COLLECTION_ORD_PAUSE        (250)                      // Пауза таймера коллекции ордеров и сделок в милисекундах
#define COLLECTION_ORD_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера коллекции ордеров и сделок
#define COLLECTION_ORD_COUNTER_ID   (1)                        // Идентификатор счётчика таймера коллекции ордеров и сделок
//--- Параметры таймера коллекции аккаунтов
#define COLLECTION_ACC_PAUSE        (1000)                     // Пауза таймера коллекции аккаунтов в милисекундах
#define COLLECTION_ACC_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера аккаунтов
#define COLLECTION_ACC_COUNTER_ID   (2)                        // Идентификатор счётчика таймера аккаунтов
//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID       (0x7778+1)                 // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID        (0x7778+2)                 // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID        (0x7778+3)                 // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID       (0x7778+4)                 // Идентификатор списка коллекции аккаунтов
//--- Параметры данных для файловых операций
#define DIRECTORY                   ("DoEasy\\")               // Каталог библиотеки для расположения папок объектов
#define UCHAR_ARRAY_SIZE            (64)                       // Размер uchar-массивов для хранения string-свойств

В тексте класса CEngine заменим COLLECTION_PAUSE, COLLECTION_COUNTER_STEP и COLLECTION_COUNTER_ID на соответствующие им новые наименования макроподстановок: COLLECTION_ORD_PAUSE, COLLECTION_ORD_COUNTER_STEP и COLLECTION_ORD_COUNTER_ID.

Раз уж мы создаём коллекцию аккаунтов, то это подразумевает возможность сравнения свойств нескольких объектов-аккаунтов. Для этого нам необходимо в классе для выборки объектов, удовлетворяющих критерию, CSelect, описанному в третьей части описания библиотеки, добавить методы выбора и фильтрации в коллекции аккаунтов.

Откроем файл Select.mqh, расположенный в папке сервисных классов библиотеки \MQL5\Include\DoEasy\Services, подключим к нему файл с классом-аккаунтом и впишем новые методы для работы с объектами-аккаунтами:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Список-хранилище                                                 |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Объект-хранилище для хранения сортированных списков коллекций
//+------------------------------------------------------------------+
//| Класс для выборки объектов, удовлетворяющих критерию             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Метод сравнения двух величин
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
//+------------------------------------------------------------------+
//| Методы работы с ордерами                                         |
//+------------------------------------------------------------------+
   //--- Возвращает список ордеров, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   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);
   //--- Возвращает индекс ордера в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   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);
   //--- Возвращает индекс ордера в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   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);
//+------------------------------------------------------------------+
//| Методы работы с событиями                                        |
//+------------------------------------------------------------------+
   //--- Возвращает список событий, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   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);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   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);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   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);
//+------------------------------------------------------------------+
//| Методы работы с аккаунтами                                       |
//+------------------------------------------------------------------+
   //--- Возвращает список аккаунтов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   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);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   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);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   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);
  };
//+------------------------------------------------------------------+

За пределами тела класса впишем реализацию объявленных методов:

//+------------------------------------------------------------------+
//| Методы работы со списками аккаунтов                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список аккаунтов, у которых одно из целочисленных     |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает список аккаунтов, у которых одно из вещественных      |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает список аккаунтов, у которых одно из строковых         |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс аккаунта в списке                              |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Работа методов рассматривалась в третьей части описания библиотеки, так что здесь не будем задерживаться на их описании. При желании всегда можно вернуться и освежить информацию.

Создадим заготовку класса-коллекции аккаунтов.

В папке библиотеки MQL5\Include\DoEasy\Collections\ создадим новый файл-класс AccountsCollection.mqh, подключим к нему необходимые для его работы файлы классов и сразу же заполним его ставшими уже стандартными для данной библиотеки методами:

//+------------------------------------------------------------------+
//|                                           AccountsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"                    
#include "..\Services\Select.mqh"         
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Коллекция аккаунтов                                              |
//+------------------------------------------------------------------+
class CAccountsCollection : public CListObj
  {
private:
   CListObj          m_list_accounts;                 // Список объектов-аккаунтов
public:
//--- Возвращает полный список-коллекцию событий "как есть"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                            }
//--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);   }
//--- Конструктор
                     CAccountsCollection();

  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

В данном минимальном исполнении в конструкторе класса подготавливается список, в котором будут храниться объекты-аккаунты:

  • список очищается
  • задаётся сортировка списка по номеру счёта и
  • списку присваивается идентификатор списка-коллекции аккаунтов.

Работа класса-коллекции аккаунтов будет организована таким образом: во время работы программы, прикрёплённой к графику символа, мы имеем доступ к текущим данным только одного аккаунта — можем отслеживать изменения его свойств и реагировать на их изменения. Остальные аккаунты (счета) мы можем лишь "наблюдать" в программе — их последнее состояние на момент подключения к новому счёту. Поэтому в списке-коллекции аккаунтов у нас будут находиться объекты всех счетов, к которым мы когда-либо подключались, но изменения состояния мы сможем отслеживать только для текущего счёта. При этом у нас будет возможность сравнивать данные всех имеющихся у нас счетов по любым из их свойств.

Для отслеживания изменений значащих свойств счёта воспользуемся контролем хеш-суммы — сравнением суммы всех свойств аккаунта на текущее время с суммой на прошлой проверке. Как только сумма изменится — будем проверять что именно изменилось и ставить флаг произошедшего изменения. Затем, когда будем делать отслеживание событий счёта (значащих изменений свойств аккаунта), нам данный флаг будет сигнализировать о том, что нужно проверить все контролируемые свойства и отправить события об изменившихся свойствах в программу.

Давайте сразу впишем все необходимые переменные и методы класса, и далее их разберём:

//+------------------------------------------------------------------+
//|                                           AccountsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Accounts\Account.mqh"
//+------------------------------------------------------------------+
//| Коллекция аккаунтов                                              |
//+------------------------------------------------------------------+
class CAccountsCollection : public CListObj
  {
private:
   struct MqlDataAccount
     {
      double         hash_sum;               // Хэш-сумма данных аккаунта
      //--- Целочисленные свойства счёта
      long           login;                  // ACCOUNT_LOGIN (Номер счёта)
      long           leverage;               // ACCOUNT_LEVERAGE (Размер предоставленного плеча)
      int            limit_orders;           // ACCOUNT_LIMIT_ORDERS (Максимально допустимое количество действующих отложенных ордеров)
      bool           trade_allowed;          // ACCOUNT_TRADE_ALLOWED (Разрешенность торговли для текущего счета со стороны сервера)
      bool           trade_expert;           // ACCOUNT_TRADE_EXPERT (Разрешенность торговли для эксперта со стороны сервера)
      //--- Вещественные свойства счёта
      double         balance;                // ACCOUNT_BALANCE (Баланс счета в валюте депозита)
      double         credit;                 // ACCOUNT_CREDIT (Размер предоставленного кредита в валюте депозита)
      double         profit;                 // ACCOUNT_PROFIT (Размер текущей прибыли на счете в валюте депозита)
      double         equity;                 // ACCOUNT_EQUITY (Значение собственных средств на счете в валюте депозита)
      double         margin;                 // ACCOUNT_MARGIN (Размер зарезервированных залоговых средств на счете  в валюте депозита)
      double         margin_free;            // ACCOUNT_MARGIN_FREE (Размер свободных средств на счете  в валюте депозита, доступных для открытия позиции)
      double         margin_level;           // ACCOUNT_MARGIN_LEVEL (Уровень залоговых средств на счете в процентах)
      double         margin_so_call;         // ACCOUNT_MARGIN_SO_CALL (Уровень залоговых средств, при котором происходит MarginCall)
      double         margin_so_so;           // ACCOUNT_MARGIN_SO_SO (Уровень залоговых средств, при достижении которого происходит StopOut)
      double         margin_initial;         // ACCOUNT_MARGIN_INITIAL (Размер средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам)
      double         margin_maintenance;     // ACCOUNT_MARGIN_MAINTENANCE (Размер средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям)
      double         assets;                 // ACCOUNT_ASSETS (Текущий размер активов на счёте)
      double         liabilities;            // ACCOUNT_LIABILITIES (Текущий размер обязательств на счёте)
      double         comission_blocked;      // ACCOUNT_COMMISSION_BLOCKED (Текущая сумма заблокированных комиссий по счёту)
     };
   MqlDataAccount    m_struct_curr_account;  // Текущие данные счёта
   MqlDataAccount    m_struct_prev_account;  // Прошлые данные счёта
   
   CListObj          m_list_accounts;                                   // Список объектов-аккаунтов
   string            m_folder_name;                                     // Имя папки хранения объектов-аккаунтов
   int               m_index_current;                                   // Индекс объекта-аккаунта с данными текущего счёта
   bool              m_is_account_event;                                // Флаг события в данных аккаунта

//--- Записывает данные текущего счёта в свойства объекта-аккаунта
  void               SetAccountsParams(CAccount* account);
//--- Сохраняет текущие значения состояния данных текущего аккаунта как прошлые
   void              SavePrevValues(void)                                                                   { this.m_struct_prev_account=this.m_struct_curr_account;                }
//--- Проверяет наличие объекта-аккаунта в списке-коллекции
   bool              IsPresent(CAccount* account);
//--- Находит и возвращает индекс объекта-аккаунта с данными текущего счёта
   int               Index(void);
public:
//--- Возвращает полный список-коллекцию аккаунтов "как есть"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                         }
//--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);}
//--- Возвращает (1) индекс текущего объекта-аккаунта, (2) флаг произошедшего события в данных аккаунта
   int               IndexCurrentAccount(void)                                                        const { return this.m_index_current;                                          }
   bool              IsAccountEvent(void)                                                             const { return this.m_is_account_event;                                       }

//--- Конструктор, деструктор
                     CAccountsCollection();
                    ~CAccountsCollection();
//--- Добавляет объект-аккаунт в список
   bool              AddToList(CAccount* account);
//--- (1) Сохраняет объекты-аккаунты из списка в файлы
//--- (2) Загружает объекты-аккаунты из файлов в список
   bool              SaveObjects(void);
   bool              LoadObjects(void);
//--- Обновляет данные текущего аккаунта
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

В приватной секции класса у нас создана структура MqlDataAccount для хранения значимых свойств аккаунта. В неё будут записываться все отслеживаемые свойства объекта-аккаунта. Переменных, имеющих тип данной структуры, у нас две — одна для хранения текущих данных аккаунта, другая — для хранения прошлых данных. Единственное свойство, которое является неизменяемым в структуре — это login, где хранится номер счёта. Значение этого поля будем использовать для определения первого запуска — если в структуре в поле login содержится ноль — значит это первый запуск, и необходимо сохранить текущее состояние счёта как прошлое для последующего их сравнения. В поле хеш-суммы структуры будем записывать сумму значений всех полей структуры и сравнивать это значение со значением, записанным в структуре "прошлого" состояния счёта. При обнаружении разницы значений этих полей двух структур будем считать, что произошло изменение свойств объекта-аккаунта.

Так как в списке-коллекции аккаунтов будут храниться данные разных счетов — всех, к которым мы подключались за время работы программы на основе библиотеки, и текущего счёта, к которому есть подключение в данный момент, а данные счетов, сохранённые в список чтением из файла не могут отслеживаться, то нам необходимо точно знать индекс объекта-аккаунта в списке, который является объектом текущего счёта, и который нам необходимо отслеживать. По этому индексу мы будем получать объект-аккаунт и проверять состояние его свойств в таймере. Там же у нас имеется переменная-член класса, которая будет флагом изменения свойств объекта-аккаунта, и переменная, в которой будет записан адрес папки в каталоге библиотеки, где мы будем хранить объекты данного класса.

В этой же приватной секции у нас расположены четыре метода, сразу же рассмотрим их реализацию.

Метод для записи данных текущего счёта в свойства объекта-аккаунта:

//+------------------------------------------------------------------+
//| Записывает данные текущего счёта в свойства объекта-аккаунта     |
//+------------------------------------------------------------------+
void CAccountsCollection::SetAccountsParams(CAccount *account)
  {
   if(account==NULL)
      return;
//--- Номер счёта
   this.m_struct_curr_account.login=account.Login();
//--- Размер предоставленного плеча
   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;
//--- Максимально допустимое количество действующих отложенных ордеров
   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;
//--- Разрешенность торговли для текущего счета со стороны сервера
   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;
//--- Разрешенность торговли для эксперта со стороны сервера
   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.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;
//--- Размер предоставленного кредита в валюте депозита
   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;
//--- Размер текущей прибыли на счете в валюте депозита
   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;
//--- Значение собственных средств на счете в валюте депозита
   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;
//--- Размер зарезервированных залоговых средств на счете  в валюте депозита
   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;
//--- Размер свободных средств на счете в валюте депозита, доступных для открытия позиции
   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;
//--- Уровень залоговых средств на счете в процентах
   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;
//--- Уровень залоговых средств, при котором происходит MarginCall
   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
   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;
//--- Размер средств, зарезервированных на счёте, для обеспечения гарантийной суммы по всем отложенным ордерам
   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;
//--- Размер средств, зарезервированных на счёте, для обеспечения минимальной суммы по всем открытым позициям
   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;
//--- Текущий размер активов на счёте
   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;
//--- Текущий размер обязательств на счёте
   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;
//--- Текущая сумма заблокированных комиссий по счёту
   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;
  }
//+------------------------------------------------------------------+

Рассмотрим на примере обновления значения предоставленного плеча:
в метод передаётся указатель на объект-аккаунт и записываются данные текущего счёта в поля объекта-аккаунта и в поля структуры с данными текущего состояния счёта. Затем к хеш-сумме прибавляется значение каждого полученного свойства.

Метод для сохранения структуры текущего состояния счёта в структуру предыдущего состояния SavePrevValues() просто копирует структуру текущего состояния в структуру прошлого состояния.

Метод проверки наличия объекта-аккаунта в списке-коллекции:

//+------------------------------------------------------------------+
//| Проверяет наличие объекта-аккаунта в списке-коллекции            |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на объект-аккаунт, данные которого нужно найти в списке-коллекции. Поиск осуществляется по данным номера счёта, имени клиента и компани методом IsEqual(), ранее рассмотренном нами здесь при создании класса объекта-аккаунта.
Просто в цикле по списку объектов-аккаунтов получаем из списка объект и сравниваем его данные с данными объекта, переданного в метод.
При совпадении данных возвращаем true
.
Иначе, если по завершению цикла равных объектов не найдено, возвращается false.

Метод, возвращающий индекс объекта-аккаунта в списке с данными текущего счёта:

//+------------------------------------------------------------------+
//| Возвращает индекс объекта-аккаунта с данными текущего счёта      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

В цикле по списку объектов-аккаунтов получаем объект и сравниваем его данные счёта (логин, имя клиента и компании) с данными счёта, на котором запущена программа. При совпадении возвращается индекс цикла. По окончании цикла, если объект с данными текущего счёта не найден, возвращается -1.

В публичной секции класса у нас добавлены методы:

Метод, возвращающий значение переменной, хранящей индекс объекта-аккаунта с данными текущего счёта, метод, возвращающий флаг произошедшего изменения свойств счёта. Добавлен деструктор класса (в нём будем сохранять все имеющиеся в списке аккаунты в файлы), метод, добавляющий объект-аккаунт в список-коллекцию, методы сохранения и загрузки объектов в файл/из файла и метод обновления данных текущего счёта в текущем объекта-аккаунте:

public:
//--- Возвращает полный список-коллекцию аккаунтов "как есть"
   CArrayObj        *GetList(void)                                                                          { return &this.m_list_accounts;                                         }
//--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);}
//--- Возвращает (1) индекс текущего объекта-аккаунта, (2) флаг произошедшего события в данных аккаунта
   int               IndexCurrentAccount(void)                                                        const { return this.m_index_current;                                          }
   bool              IsAccountEvent(void)                                                             const { return this.m_is_account_event;                                       }

//--- Конструктор, деструктор
                     CAccountsCollection();
                    ~CAccountsCollection();
//--- Добавляет объект-аккаунт в список
   bool              AddToList(CAccount* account);
//--- (1) Сохраняет объекты-аккаунты из списка в файлы
//--- (2) Загружает объекты-аккаунты из файлов в список
   bool              SaveObjects(void);
   bool              LoadObjects(void);
//--- Обновляет данные текущего аккаунта
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Рассмотрим эти методы.

Файлы библиотеки будем сохранять в каталоге терминала Files\DoEasy\, а в нем — свои папки для каждого класса (если для класса будет необходимость в сохранении файлов). Для задания наименования папки хранения объектов-аккаунтов у нас объявлена переменная-член класса m_folder_name. Инициализируем её сразу в конструкторе класса, в его списке инициализации, и переменную-флаг произошедшего изменения свойств счёта:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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);

//--- Создание папки хранения файлов аккаунтов
   ::ResetLastError();
   if(!::FolderCreate(this.m_folder_name,FILE_COMMON))
      Print(DFUN,TextByLanguage("Не удалось создать папку хранения файлов. Ошибка ","Could not create file storage folder. Error "),::GetLastError());
//--- Создание и добавление в список объекта-аккаунта текущего счёта
   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."));

//--- Загрузка объектов-аккаунтов из файлов в коллекцию
   this.LoadObjects();
//--- Сохранение индекса текущего аккаунта
   this.m_index_current=this.Index();
  }
//+------------------------------------------------------------------+

Далее в конструкторе класса обнуляется структура с прошлыми данными текущего счёта, создаётся папка хранения файлов класса, которая для данного класса будет иметь путь "Общая_папка_данных"\Files\DoEasy\Accounts.
Затем создаётся объект-аккаунт с текущими данными счёта и при помощи метода AddToList() добавляется в список-коллекцию аккаунтов. Если добавить объект в список не удалось, то об этом будет выведено сообщение в журнал, иначе — в журнал будет выведено сообщение с краткими свойствами счёта: логин, имя клиента, имя компании, баланс счёта, размер предоставленного плеча и тип счёта (если не неттинг).
Следующим шагом в список-коллекцию загружаются объекты-аккаунты, файлы сохранения которых имеются в папке хранения объетов данного класса.
Последним шагом ищем индекс объекта с данными текущего счёта и присваиваем его переменной m_index_current, значение которой возвращается методом IndexCurrentAccount() для использования в программах.

В деструкторе класса вызывается метод сохранения всех объектов из списка-коллекции в соответствующие им файлы:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CAccountsCollection::~CAccountsCollection(void)
  {
//--- Сохранение объектов-аккаунтов из списка в файлы
   this.SaveObjects();
  }
//+------------------------------------------------------------------+

Метод добавления объекта-аккаунта в список-коллекцию:

//+------------------------------------------------------------------+
//| Добавляет объект-аккаунт в список                                |
//+------------------------------------------------------------------+
bool CAccountsCollection::AddToList(CAccount *account)
  {
   if(account==NULL)
      return false;
   if(!this.IsPresent(account))
      return this.m_list_accounts.Add(account);
   return false;
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на объект-аккаунт, затем при помощи метода IsPresent() проверяется наличие такого объекта в списке-коллекции, и если такого объекта ещё нет, то объект добавяется в список-коллекцию и возвращается результат его добавления.

Метод сохранения объектов-аккаунтов из списка-коллекции в файлы:

//+------------------------------------------------------------------+
//| Сохраняет объекты-аккаунты из списка в файлы                     |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

В цикле по списку-коллекции получаем объект-аккаунт из списка, создаём имя файла, состоящее из пути к папке объектов-аккаунтов, имени сервера и логина (номера счёта) с расширением ".bin". Если такой файл существует в папке объектов-аккаунтов, то файл удаляется и открывается новый для записи. Хендл открытого файла передаётся в виртуальный метод Save() класса CAccount рассмотренный нами ранее, и результат сохранения файла добавляется в переменную res, возвращающую из метода результат записи в файл всех объектов-аккаунтов из списка-коллекции. После сохранения объекта файл, открытый для записи, закрывается.

Метод загрузки объектов-аккаунтов из файлов в список-коллекцию:

//+------------------------------------------------------------------+
//| Загружает объекты-аккаунты из файлов в список                    |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Сначала находим самый первый файл в папке хранения файлов объектов-аккаунтов библиотеки, и далее в цикле do-while открываем очередной найденный файл для чтения, создаём новый объект-аккаунт, загружаем в него данные из файла при помощи виртуального метода Load() класса CAccount, и если такого объекта (с такими же данными счёта) нет в списке, то объект добавляется в список. В любых ошибочных ситуациях при загрузке данных в объект из файла или при добавлении объекта в список, нам нужно обязательно удалить этот новый объект (для избегания утечки памяти) и закрыть открытый файл.
По окончании работы цикла возвращается результат загрузки данных в объекты-аккаунты из файлов и размещения этих объектов в списке-коллекции.

Метод обновления данных текущего объекта-аккаунта:

//+------------------------------------------------------------------+
//| Обновляет данные текущего аккаунта                               |
//+------------------------------------------------------------------+
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);
   
//--- Первый запуск
   if(!this.m_struct_prev_account.login)
     {
      this.SavePrevValues();
     }
//--- Если хэш-сумма аккаунта изменилась
   if(this.m_struct_curr_account.hash_sum!=this.m_struct_prev_account.hash_sum)
     {
      this.m_is_account_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Здесь: первым делом проверяем корректность индекса объекта-аккаунта с данными текущего счёта, и если он по какой-либо причине получен не был, то уходим из метода. Далее получаем из списка объект-аккаунт с данными текущего счёта по его индексу в списке, обнуляем структуру данных текущего счёта, сбрасываем флаг изменения свойств объекта-аккаунта и вызываем метод установки свойств объекта-аккаунта. Этот же метод скопирует в структуру данных текущего счёта самые последние — только что считанные — свойства для последующего их сравнения с предыдущим состоянием счёта и определения их изменения.
Далее смотрим что у нас записано в структуре данных прошлого состояния счёта, и если поле логина нулевое, то это значит, что структура ещё ни разу не заполнялась, и это первый запуск. Соответственно, мы просто заполняем структуру с данными прошлого состояния данными из структуры текущего состояния.
Затем проверяем изменение хеш-суммы сравнением хеш-суммы текущего состояния с хеш-суммой прошлого состояния. Если есть изменения, то ставим флаг произошедшего события изменения свойств аккаунта и сохраняем текущее состояние как прошлое для последующего их сравнения.
Позже сделаем отслеживание важных изменений состояния счёта и отправку в программу сообщений-событий об этих важных изменениях.

Так как вся работа с данным классом будет осуществляться как обычно из базового объекта библиотеки — класса CEngine, то перейдём к файлу Engine.mqh и добавим необходимый функционал.

В первую очередь подключим файл коллекции аккаунтов:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

В приватной секции класса создадим объект-коллекцию аккаунтов и добавим метод для работы с коллекцией аккаунтов:

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CArrayObj            m_list_counters;                 // Список счётчиков таймера
   bool                 m_first_start;                   // Флаг первого запуска
   bool                 m_is_hedge;                      // Флаг хедж-счёта
   bool                 m_is_tester;                     // Флаг работы в тестере
   bool                 m_is_market_trade_event;         // Флаг торгового события на счёте
   bool                 m_is_history_trade_event;        // Флаг торгового события в истории счёта
   ENUM_TRADE_EVENT     m_last_trade_event;              // Последнее торговое событие на счёте
//--- Возвращает индекс счётчика по id
   int                  CounterIndex(const int id) const;
//--- Возвращает (1) флаг первого запуска, (2) факт наличия флага в торговом событии
   bool                 IsFirstStart(void);
//--- Работа с событиями (1) ордеров, сделок и позиций, (2) аккаунтов
   void                 TradeEventsControl(void);
   void                 AccountEventsControl(void);
//--- Возвращает последний (1) рыночный отложенный ордер, (2) маркет-ордер, (3) последнюю позицию, (4) позицию по тикету
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Возвращает последний (1) удалённый отложенный ордер, (2) исторический маркет-ордер, (3) исторический ордер (маркет или отложенный) по его тикету
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Возвращает (1) первый и (2) последний исторический маркет-ордер из списка всех ордеров позиции, (3) последнюю сделку
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:

В конструкторе класса создадим новый счётчик таймера для работы с коллекцией аккаунтов:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
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 
  }
//+------------------------------------------------------------------+

Работу таймеров и их счётчиков мы обсуждали в третьей части описания библиотеки.

В обработчике OnTimer() класса добавим таймер коллекции аккаунтов:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
   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.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else
            this.TradeEventsControl();
        }
     }
//--- Таймер коллекции аккаунтов
   index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
            if(counter.IsTimeDone())
               this.AccountEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else
            this.AccountEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+

Таймер коллекции аккаунтов работает идентично таймеру коллекции ордеров, сделок и позиций и обсуждался в третьей части описания библиотеки при обсуждении создания базового объекта библиотеки — класса CEngine. Единственным отличием от таймера коллекции ордеров, сделок и позиций является вызов иного метода обработки событий коллекции — вызов метода AccountEventsControl().

Добавим метод проверки изменений свойств текущего аккаунта:

//+------------------------------------------------------------------+
//| Проверка событий аккаунта                                        |
//+------------------------------------------------------------------+
void CEngine::AccountEventsControl(void)
  {
   this.m_accounts.Refresh();
  }
//+------------------------------------------------------------------+

Данный метод просто вызывает метод Refresh() класса CAccountsCollection.

В публичной секции класса CEngine пропишем два метода, возвращающие в программу списки коллекций событий и аккаунтов, что даст нам возможность из своих программ обращаться напрямую к этим спискам коллекций:

public:
   //--- Возвращает список рыночных (1) позиций, (2) отложенных ордеров и (3) маркет-ордеров
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Возвращает список исторических (1) ордеров, (2) удалённых отложенных ордеров, (3) сделок, (4) всех маркет-ордеров позиции по её идентификатору
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Возвращает список аккаунтов
   CArrayObj*           GetListAllAccounts(void)                        { return this.m_accounts.GetList();    }
//--- Возвращает список событий 
   CArrayObj*           GetListAllEvents(void)                          { return this.m_events.GetList();      }
//--- Сбрасывает последнее торговое событие
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Возвращает (1) последнее торговое событие, (2) флаг счёта-хедж, (3) флаг работы в тестере
   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;             }
//--- Создаёт счётчик таймера
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Таймер
   void                 OnTimer(void);
//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

У нас всё готово для тестирования класса-коллекции аккаунтов. Но прежде чем приступим к тестированию, внесём поправку в работу метода CEventsCollection::Refresh класса-коллекции событий. В строку 233 листинга впишем проверку, без которой в некоторых случаях были неверные срабатывания при определении событий, и в программу иногда отправлялось наряду с новым событием и давно забытое старое:

//--- Если событие в истории счёта
   if(is_history_event)
     {
      //--- Если увеличилось количество исторических ордеров (MQL5, MQL4)
      if(new_history_orders>0 && new_market_pendings<0)
        {
         //--- Получаем список только удалённых отложенных ордеров

Так же были исправлены "детские" ошибки, мною допущенные при написании торговых функций для MQL4 для работы в тестере MetaTrader4 в файле DELib.mqh. Как-то я упустил из виду, что функции OrderSend() возвращают в MQL4 тикет ордера, а не булево значение — видно начинаю забывать MQL4 :)

Для примера:
Проверка результата работы функции для MQL4 была такой (для MQL5 это верно):

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

Исправил на правильные проверки для MQL4:

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

В принципе, для тестера это было совсем не критично, но и совершенно неверно.
Скоро будут полноценные торговые классы, и эти функции будут удалены из библиотеки.

Тест коллекции аккаунтов

Что ж, давайте для теста возьмём советник из этой же статьи, созданный нами ранее TestDoEasyPart12_1.mq5, и сохраним его под новым именем TestDoEasyPart12_2.mq5 в той же папке \MQL5\Experts\TestDoEasy.

Во входные параметры советника введём переменную для переключения вида распечатываемой в журнал информации об имеющихся аккаунтах — либо краткая (false по умолчанию), либо полная ( true):

//--- input variables
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
//--- global variables

И в обработчике OnInit() впишем код:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();

//--- Установка глобальных переменных советника
   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;

//--- Проверка и удаление неудалённых графических объектов советника
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Установка параметров торгового класса CTrade
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Быстрая проверка коллекции объектов-аккаунтов
   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);
  }
//+------------------------------------------------------------------+

Здесь: получаем список-коллекцию аккаунтов при помощи метода GetListAllAccounts() класса CEngine. В цикле по списку получаем из него каждый последующий объект и распечатываем его свойства в журнале в зависимости от значения входной переменной — либо краткую запись, либо полный список свойсв объекта-аккаунта.

Запустим советник на график и посмотрим что он нам выводит в журнал при выборе краткой записи (в настройках Show full accounts properties = false):


Теперь выберем вывод полных свойств — нажмём F7 и в окне настроек параметров для Show full accounts properties укажем true:


Теперь в журнал выводится полный список свойств каждого из имеющихся аккаунтов.

Хочу обратить внимание, что для того чтобы аккаунты были записаны в файл, нужно подключиться к первому счёту, затем переподключиться ко второму, затем к следующему, и т.д. Т.е — при каждом подключении к новому счёту данные прошлого аккаунта записываются в файл.

Что дальше

В следующей статье сделаем отслеживание некоторых важных событий изменения свойств счёта и начнём работу над объектами-символами и их коллекцией.

Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового советника. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.

К содержанию

Статьи этой серии:

Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций.
Часть 8. События модификации ордеров и позиций.
Часть 9. Совместимость с MQL4 - Подготовка данных.
Часть 10. Совместимость с MQL4 - События открытия позиций и активации отложенных ордеров.
Часть 11. Совместимость с MQL4 - События закрытия позиций.